1 // Copyright (c) 2015-2016 The btcsuite developers
2 // Use of this source code is governed by an ISC
3 // license that can be found in the LICENSE file.
12 "github.com/btcsuite/btcd/chaincfg/chainhash"
13 "github.com/btcsuite/btcd/txscript"
14 "github.com/btcsuite/btcd/wire"
15 "github.com/btcsuite/btclog"
19 // maxRejectReasonLen is the maximum length of a sanitized reject reason
20 // that will be logged.
21 maxRejectReasonLen = 250
24 // log is a logger that is initialized with no output filters. This
25 // means the package will not perform any logging by default until the caller
29 // The default amount of logging is none.
34 // DisableLog disables all library log output. Logging output is disabled
35 // by default until UseLogger is called.
40 // UseLogger uses a specified Logger to output package logging info.
41 func UseLogger(logger btclog.Logger) {
45 // LogClosure is a closure that can be printed with %v to be used to
46 // generate expensive-to-create data for a detailed log level and avoid doing
47 // the work if the data isn't printed.
48 type logClosure func() string
50 func (c logClosure) String() string {
54 func newLogClosure(c func() string) logClosure {
58 // directionString is a helper function that returns a string that represents
59 // the direction of a connection (inbound or outbound).
60 func directionString(inbound bool) string {
67 // formatLockTime returns a transaction lock time as a human-readable string.
68 func formatLockTime(lockTime uint32) string {
69 // The lock time field of a transaction is either a block height at
70 // which the transaction is finalized or a timestamp depending on if the
71 // value is before the lockTimeThreshold. When it is under the
72 // threshold it is a block height.
73 if lockTime < txscript.LockTimeThreshold {
74 return fmt.Sprintf("height %d", lockTime)
77 return time.Unix(int64(lockTime), 0).String()
80 // invSummary returns an inventory message as a human-readable string.
81 func invSummary(invList []*wire.InvVect) string {
83 invLen := len(invList)
88 // One inventory item.
92 case wire.InvTypeError:
93 return fmt.Sprintf("error %s", iv.Hash)
94 case wire.InvTypeWitnessBlock:
95 return fmt.Sprintf("witness block %s", iv.Hash)
96 case wire.InvTypeBlock:
97 return fmt.Sprintf("block %s", iv.Hash)
98 case wire.InvTypeWitnessTx:
99 return fmt.Sprintf("witness tx %s", iv.Hash)
101 return fmt.Sprintf("tx %s", iv.Hash)
104 return fmt.Sprintf("unknown (%d) %s", uint32(iv.Type), iv.Hash)
107 // More than one inv item.
108 return fmt.Sprintf("size %d", invLen)
111 // locatorSummary returns a block locator as a human-readable string.
112 func locatorSummary(locator []*chainhash.Hash, stopHash *chainhash.Hash) string {
113 if len(locator) > 0 {
114 return fmt.Sprintf("locator %s, stop %s", locator[0], stopHash)
117 return fmt.Sprintf("no locator, stop %s", stopHash)
121 // sanitizeString strips any characters which are even remotely dangerous, such
122 // as html control characters, from the passed string. It also limits it to
123 // the passed maximum size, which can be 0 for unlimited. When the string is
124 // limited, it will also add "..." to the string to indicate it was truncated.
125 func sanitizeString(str string, maxLength uint) string {
126 const safeChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY" +
127 "Z01234567890 .,;_/:?@"
129 // Strip any characters not in the safeChars string removed.
130 str = strings.Map(func(r rune) rune {
131 if strings.ContainsRune(safeChars, r) {
137 // Limit the string to the max allowed length.
138 if maxLength > 0 && uint(len(str)) > maxLength {
139 str = str[:maxLength]
145 // messageSummary returns a human-readable string which summarizes a message.
146 // Not all messages have or need a summary. This is used for debug logging.
147 func messageSummary(msg wire.Message) string {
148 switch msg := msg.(type) {
149 case *wire.MsgVersion:
150 return fmt.Sprintf("agent %s, pver %d, block %d",
151 msg.UserAgent, msg.ProtocolVersion, msg.LastBlock)
153 case *wire.MsgVerAck:
156 case *wire.MsgGetAddr:
160 return fmt.Sprintf("%d addr", len(msg.AddrList))
163 // No summary - perhaps add nonce.
166 // No summary - perhaps add nonce.
171 case *wire.MsgMemPool:
175 return fmt.Sprintf("hash %s, %d inputs, %d outputs, lock %s",
176 msg.TxHash(), len(msg.TxIn), len(msg.TxOut),
177 formatLockTime(msg.LockTime))
180 header := &msg.Header
181 return fmt.Sprintf("hash %s, ver %d, %d tx, %s", msg.BlockHash(),
182 header.Version, len(msg.Transactions), header.Timestamp)
185 return invSummary(msg.InvList)
187 case *wire.MsgNotFound:
188 return invSummary(msg.InvList)
190 case *wire.MsgGetData:
191 return invSummary(msg.InvList)
193 case *wire.MsgGetBlocks:
194 return locatorSummary(msg.BlockLocatorHashes, &msg.HashStop)
196 case *wire.MsgGetHeaders:
197 return locatorSummary(msg.BlockLocatorHashes, &msg.HashStop)
199 case *wire.MsgHeaders:
200 return fmt.Sprintf("num %d", len(msg.Headers))
202 case *wire.MsgReject:
203 // Ensure the variable length strings don't contain any
204 // characters which are even remotely dangerous such as HTML
205 // control characters, etc. Also limit them to sane length for
207 rejCommand := sanitizeString(msg.Cmd, wire.CommandSize)
208 rejReason := sanitizeString(msg.Reason, maxRejectReasonLen)
209 summary := fmt.Sprintf("cmd %v, code %v, reason %v", rejCommand,
211 if rejCommand == wire.CmdBlock || rejCommand == wire.CmdTx {
212 summary += fmt.Sprintf(", hash %v", msg.Hash)
217 // No summary for other messages.