github.com/turingchain2020/turingchain@v1.1.21/common/log/log15/format.go (about) 1 // Copyright Turing Corp. 2018 All Rights Reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package log15 6 7 import ( 8 "bytes" 9 "encoding/json" 10 "fmt" 11 "reflect" 12 "strconv" 13 "strings" 14 "sync" 15 "time" 16 ) 17 18 const ( 19 timeFormat = "2006-01-02T15:04:05-0700" 20 termTimeFormat = "01-02|15:04:05" 21 floatFormat = 'f' 22 termMsgJust = 40 23 ) 24 25 // Format is the interface implemented by StreamHandler formatters. 26 type Format interface { 27 Format(r *Record) []byte 28 } 29 30 // FormatFunc returns a new Format object which uses 31 // the given function to perform record formatting. 32 func FormatFunc(f func(*Record) []byte) Format { 33 return formatFunc(f) 34 } 35 36 type formatFunc func(*Record) []byte 37 38 func (f formatFunc) Format(r *Record) []byte { 39 return f(r) 40 } 41 42 // TerminalFormat formats log records optimized for human readability on 43 // a terminal with color-coded level output and terser human friendly timestamp. 44 // This format should only be used for interactive programs or while developing. 45 // 46 // [TIME] [LEVEL] MESAGE key=value key=value ... 47 // 48 // Example: 49 // 50 // [May 16 20:58:45] [DBUG] remove route ns=haproxy addr=127.0.0.1:50002 51 // 52 func TerminalFormat() Format { 53 return FormatFunc(func(r *Record) []byte { 54 var color = 0 55 switch r.Lvl { 56 case LvlCrit: 57 color = 35 58 case LvlError: 59 color = 31 60 case LvlWarn: 61 color = 33 62 case LvlInfo: 63 color = 32 64 case LvlDebug: 65 color = 36 66 } 67 68 b := &bytes.Buffer{} 69 lvl := strings.ToUpper(r.Lvl.String()) 70 if color > 0 { 71 fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), r.Msg) 72 } else { 73 fmt.Fprintf(b, "[%s] [%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Msg) 74 } 75 76 // try to justify the log output for short messages 77 if len(r.Ctx) > 0 && len(r.Msg) < termMsgJust { 78 b.Write(bytes.Repeat([]byte{' '}, termMsgJust-len(r.Msg))) 79 } 80 81 // print the keys logfmt style 82 logfmt(b, r.Ctx, color) 83 return b.Bytes() 84 }) 85 } 86 87 // LogfmtFormat prints records in logfmt format, an easy machine-parseable but human-readable 88 // format for key/value pairs. 89 // 90 // For more details see: http://godoc.org/github.com/kr/logfmt 91 // 92 func LogfmtFormat() Format { 93 return FormatFunc(func(r *Record) []byte { 94 common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg} 95 buf := &bytes.Buffer{} 96 logfmt(buf, append(common, r.Ctx...), 0) 97 return buf.Bytes() 98 }) 99 } 100 101 func logfmt(buf *bytes.Buffer, ctx []interface{}, color int) { 102 for i := 0; i < len(ctx); i += 2 { 103 if i != 0 { 104 buf.WriteByte(' ') 105 } 106 107 k, ok := ctx[i].(string) 108 v := formatLogfmtValue(ctx[i+1]) 109 if !ok { 110 k, v = errorKey, formatLogfmtValue(k) 111 } 112 113 // XXX: we should probably check that all of your key bytes aren't invalid 114 if color > 0 { 115 fmt.Fprintf(buf, "\x1b[%dm%s\x1b[0m=%s", color, k, v) 116 } else { 117 buf.WriteString(k) 118 buf.WriteByte('=') 119 buf.WriteString(v) 120 } 121 } 122 123 buf.WriteByte('\n') 124 } 125 126 // JSONFormat formats log records as JSON objects separated by newlines. 127 // It is the equivalent of JSONFormatEx(false, true). 128 func JSONFormat() Format { 129 return JSONFormatEx(false, true) 130 } 131 132 // JSONFormatEx formats log records as JSON objects. If pretty is true, 133 // records will be pretty-printed. If lineSeparated is true, records 134 // will be logged with a new line between each record. 135 func JSONFormatEx(pretty, lineSeparated bool) Format { 136 jsonMarshal := json.Marshal 137 if pretty { 138 jsonMarshal = func(v interface{}) ([]byte, error) { 139 return json.MarshalIndent(v, "", " ") 140 } 141 } 142 143 return FormatFunc(func(r *Record) []byte { 144 props := make(map[string]interface{}) 145 146 props[r.KeyNames.Time] = r.Time 147 props[r.KeyNames.Lvl] = r.Lvl.String() 148 props[r.KeyNames.Msg] = r.Msg 149 150 for i := 0; i < len(r.Ctx); i += 2 { 151 k, ok := r.Ctx[i].(string) 152 if !ok { 153 props[errorKey] = fmt.Sprintf("%+v is not a string key", r.Ctx[i]) 154 } 155 props[k] = formatJSONValue(r.Ctx[i+1]) 156 } 157 158 b, err := jsonMarshal(props) 159 if err != nil { 160 b, _ = jsonMarshal(map[string]string{ 161 errorKey: err.Error(), 162 }) 163 return b 164 } 165 166 if lineSeparated { 167 b = append(b, '\n') 168 } 169 170 return b 171 }) 172 } 173 174 func formatShared(value interface{}) (result interface{}) { 175 defer func() { 176 if err := recover(); err != nil { 177 if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() { 178 result = "nil" 179 } else { 180 panic(err) 181 } 182 } 183 }() 184 185 switch v := value.(type) { 186 case time.Time: 187 return v.Format(timeFormat) 188 189 case error: 190 return v.Error() 191 192 case fmt.Stringer: 193 return v.String() 194 195 default: 196 return v 197 } 198 } 199 200 func formatJSONValue(value interface{}) interface{} { 201 value = formatShared(value) 202 switch value.(type) { 203 case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string: 204 return value 205 default: 206 return fmt.Sprintf("%+v", value) 207 } 208 } 209 210 // formatValue formats a value for serialization 211 func formatLogfmtValue(value interface{}) string { 212 if value == nil { 213 return "nil" 214 } 215 216 if t, ok := value.(time.Time); ok { 217 // Performance optimization: No need for escaping since the provided 218 // timeFormat doesn't have any escape characters, and escaping is 219 // expensive. 220 return t.Format(timeFormat) 221 } 222 value = formatShared(value) 223 switch v := value.(type) { 224 case bool: 225 return strconv.FormatBool(v) 226 case float32: 227 return strconv.FormatFloat(float64(v), floatFormat, 3, 64) 228 case float64: 229 return strconv.FormatFloat(v, floatFormat, 3, 64) 230 case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: 231 return fmt.Sprintf("%d", value) 232 case string: 233 return escapeString(v) 234 default: 235 return escapeString(fmt.Sprintf("%+v", value)) 236 } 237 } 238 239 var stringBufPool = sync.Pool{ 240 New: func() interface{} { return new(bytes.Buffer) }, 241 } 242 243 func escapeString(s string) string { 244 needsQuotes := false 245 needsEscape := false 246 for _, r := range s { 247 if r <= ' ' || r == '=' || r == '"' { 248 needsQuotes = true 249 } 250 if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' { 251 needsEscape = true 252 } 253 } 254 if !needsEscape && !needsQuotes { 255 return s 256 } 257 e := stringBufPool.Get().(*bytes.Buffer) 258 e.WriteByte('"') 259 for _, r := range s { 260 switch r { 261 case '\\', '"': 262 e.WriteByte('\\') 263 e.WriteByte(byte(r)) 264 case '\n': 265 e.WriteString("\\n") 266 case '\r': 267 e.WriteString("\\r") 268 case '\t': 269 e.WriteString("\\t") 270 default: 271 e.WriteRune(r) 272 } 273 } 274 e.WriteByte('"') 275 var ret string 276 if needsQuotes { 277 ret = e.String() 278 } else { 279 ret = string(e.Bytes()[1 : e.Len()-1]) 280 } 281 e.Reset() 282 stringBufPool.Put(e) 283 return ret 284 }