github.com/tendermint/tmlibs@v0.9.0/log/tmfmt_logger.go (about) 1 package log 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "sync" 8 "time" 9 10 kitlog "github.com/go-kit/kit/log" 11 kitlevel "github.com/go-kit/kit/log/level" 12 "github.com/go-logfmt/logfmt" 13 ) 14 15 type tmfmtEncoder struct { 16 *logfmt.Encoder 17 buf bytes.Buffer 18 } 19 20 func (l *tmfmtEncoder) Reset() { 21 l.Encoder.Reset() 22 l.buf.Reset() 23 } 24 25 var tmfmtEncoderPool = sync.Pool{ 26 New: func() interface{} { 27 var enc tmfmtEncoder 28 enc.Encoder = logfmt.NewEncoder(&enc.buf) 29 return &enc 30 }, 31 } 32 33 type tmfmtLogger struct { 34 w io.Writer 35 } 36 37 // NewTMFmtLogger returns a logger that encodes keyvals to the Writer in 38 // Tendermint custom format. Note complex types (structs, maps, slices) 39 // formatted as "%+v". 40 // 41 // Each log event produces no more than one call to w.Write. 42 // The passed Writer must be safe for concurrent use by multiple goroutines if 43 // the returned Logger will be used concurrently. 44 func NewTMFmtLogger(w io.Writer) kitlog.Logger { 45 return &tmfmtLogger{w} 46 } 47 48 func (l tmfmtLogger) Log(keyvals ...interface{}) error { 49 enc := tmfmtEncoderPool.Get().(*tmfmtEncoder) 50 enc.Reset() 51 defer tmfmtEncoderPool.Put(enc) 52 53 const unknown = "unknown" 54 lvl := "none" 55 msg := unknown 56 module := unknown 57 58 // indexes of keys to skip while encoding later 59 excludeIndexes := make([]int, 0) 60 61 for i := 0; i < len(keyvals)-1; i += 2 { 62 // Extract level 63 if keyvals[i] == kitlevel.Key() { 64 excludeIndexes = append(excludeIndexes, i) 65 switch keyvals[i+1].(type) { 66 case string: 67 lvl = keyvals[i+1].(string) 68 case kitlevel.Value: 69 lvl = keyvals[i+1].(kitlevel.Value).String() 70 default: 71 panic(fmt.Sprintf("level value of unknown type %T", keyvals[i+1])) 72 } 73 // and message 74 } else if keyvals[i] == msgKey { 75 excludeIndexes = append(excludeIndexes, i) 76 msg = keyvals[i+1].(string) 77 // and module (could be multiple keyvals; if such case last keyvalue wins) 78 } else if keyvals[i] == moduleKey { 79 excludeIndexes = append(excludeIndexes, i) 80 module = keyvals[i+1].(string) 81 } 82 } 83 84 // Form a custom Tendermint line 85 // 86 // Example: 87 // D[05-02|11:06:44.322] Stopping AddrBook (ignoring: already stopped) 88 // 89 // Description: 90 // D - first character of the level, uppercase (ASCII only) 91 // [05-02|11:06:44.322] - our time format (see https://golang.org/src/time/format.go) 92 // Stopping ... - message 93 enc.buf.WriteString(fmt.Sprintf("%c[%s] %-44s ", lvl[0]-32, time.Now().UTC().Format("01-02|15:04:05.000"), msg)) 94 95 if module != unknown { 96 enc.buf.WriteString("module=" + module + " ") 97 } 98 99 KeyvalueLoop: 100 for i := 0; i < len(keyvals)-1; i += 2 { 101 for _, j := range excludeIndexes { 102 if i == j { 103 continue KeyvalueLoop 104 } 105 } 106 107 err := enc.EncodeKeyval(keyvals[i], keyvals[i+1]) 108 if err == logfmt.ErrUnsupportedValueType { 109 enc.EncodeKeyval(keyvals[i], fmt.Sprintf("%+v", keyvals[i+1])) 110 } else if err != nil { 111 return err 112 } 113 } 114 115 // Add newline to the end of the buffer 116 if err := enc.EndRecord(); err != nil { 117 return err 118 } 119 120 // The Logger interface requires implementations to be safe for concurrent 121 // use by multiple goroutines. For this implementation that means making 122 // only one call to l.w.Write() for each call to Log. 123 if _, err := l.w.Write(enc.buf.Bytes()); err != nil { 124 return err 125 } 126 return nil 127 }