github.com/ethersphere/bee/v2@v2.2.0/pkg/log/formatter.go (about) 1 // Copyright 2022 The Swarm Authors. 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 // Note: the following code is derived (borrows) from: github.com/go-logr/logr 6 7 package log 8 9 import ( 10 "bytes" 11 "encoding" 12 "fmt" 13 "path/filepath" 14 "reflect" 15 "runtime" 16 "strconv" 17 "strings" 18 ) 19 20 // MessageCategory indicates which category or categories 21 // of messages should include the caller in the log lines. 22 type MessageCategory int 23 24 const ( 25 CategoryNone MessageCategory = iota 26 CategoryAll 27 CategoryError 28 CategoryWarning 29 CategoryInfo 30 CategoryDebug 31 ) 32 33 // Marshaler is an optional interface that logged values may choose to 34 // implement. Loggers with structured output, such as JSON, should 35 // log the object return by the MarshalLog method instead of the 36 // original value. 37 type Marshaler interface { 38 // MarshalLog can be used to: 39 // - ensure that structs are not logged as strings when the original 40 // value has a String method: return a different type without a 41 // String method 42 // - select which fields of a complex type should get logged: 43 // return a simpler struct with fewer fields 44 // - log unexported fields: return a different struct 45 // with exported fields 46 // 47 // It may return any value of any type. 48 MarshalLog() interface{} 49 } 50 51 // PseudoStruct is a list of key-value pairs that gets logged as a struct. 52 // E.g.: PseudoStruct{"f1", 1, "f2", true, "f3", []int{}}. 53 type PseudoStruct []interface{} 54 55 // fmtOptions carries parameters which influence the way logs are generated/formatted. 56 type fmtOptions struct { 57 caller MessageCategory 58 logCallerFunc bool 59 logTimestamp bool 60 timestampLayout string 61 maxLogDepth int 62 jsonOutput bool 63 callerDepth int 64 } 65 66 const ( 67 null = "null" // null is a placeholder for nil values. 68 noValue = "<no-value>" // noValue is a placeholder for missing values. 69 70 // maxLogDepthExceeded is printed as the last value in 71 // the recursive chain when the depth limit is exceeded. 72 maxLogDepthExceeded = `"<max-log-depth-exceeded>"` 73 ) 74 75 // caller represents the original call site for a log line. The File and 76 // Line fields will always be provided, while the Func field is optional. 77 type caller struct { 78 // File is the basename of the file for this call site. 79 File string `json:"file"` 80 // Line is the line number in the file for this call site. 81 Line int `json:"line"` 82 // Func is the function name for this call site, or empty if 83 // fmtOptions.logCallerFunc is not enabled. 84 Func string `json:"function,omitempty"` 85 } 86 87 // newFormatter constructs a formatter which 88 // behavior is influenced by the given options. 89 func newFormatter(opts fmtOptions) *formatter { 90 return &formatter{opts: opts} 91 } 92 93 // formatter is responsible for formatting the output of the log messages. 94 type formatter struct { 95 opts fmtOptions 96 } 97 98 // render produces a log line where the base is 99 // never escaped; the opposite is true for args. 100 func (f *formatter) render(base, args []interface{}) []byte { 101 buf := bytes.NewBuffer(make([]byte, 0, 1024)) 102 if f.opts.jsonOutput { 103 buf.WriteByte('{') 104 } 105 f.flatten(buf, base, false, false) 106 f.flatten(buf, args, len(base) > 0, true) 107 if f.opts.jsonOutput { 108 buf.WriteByte('}') 109 } 110 buf.WriteByte('\n') 111 return buf.Bytes() 112 } 113 114 // flatten renders a list of key-value pairs into a buffer. If continuing is 115 // true, it assumes that the buffer has previous values and will emit a 116 // separator (which depends on the output format) before the first pair is 117 // written. If escapeKeys is true, the keys are assumed to have 118 // non-JSON-compatible characters in them and must be evaluated for escapes. 119 func (f *formatter) flatten(buf *bytes.Buffer, kvList []interface{}, continuing bool, escapeKeys bool) { 120 // This logic overlaps with sanitize() but saves one type-cast per key, 121 // which can be measurable. 122 if len(kvList)%2 != 0 { 123 kvList = append(kvList, noValue) 124 } 125 for i := 0; i < len(kvList); i += 2 { 126 k, ok := kvList[i].(string) 127 if !ok { 128 k = f.nonStringKey(kvList[i]) 129 kvList[i] = k 130 } 131 v := kvList[i+1] 132 133 if i > 0 || continuing { 134 if f.opts.jsonOutput { 135 buf.WriteByte(',') 136 } else { 137 // In theory the format could be something we don't understand. 138 // In practice, we control it, so it won't be. 139 buf.WriteByte(' ') 140 } 141 } 142 143 if escapeKeys { 144 buf.WriteString(prettyString(k)) 145 } else { 146 // The following is faster. 147 buf.WriteByte('"') 148 buf.WriteString(k) 149 buf.WriteByte('"') 150 } 151 if f.opts.jsonOutput { 152 buf.WriteByte(':') 153 } else { 154 buf.WriteByte('=') 155 } 156 buf.WriteString(f.prettyWithFlags(v, 0, 0)) 157 } 158 } 159 160 // prettyWithFlags prettifies the given value. 161 // TODO: This is not fast. Most of the overhead goes here. 162 func (f *formatter) prettyWithFlags(value interface{}, flags uint32, depth int) string { 163 const flagRawStruct = 0x1 // Do not print braces on structs. 164 165 if depth > f.opts.maxLogDepth { 166 return maxLogDepthExceeded 167 } 168 169 // Handle types that take full control of logging. 170 if v, ok := value.(Marshaler); ok { 171 // Replace the value with what the type wants to get logged. 172 // That then gets handled below via reflection. 173 value = invokeMarshaler(v) 174 } 175 176 // Handle types that want to format themselves. 177 switch v := value.(type) { 178 case fmt.Stringer: 179 value = invokeStringer(v) 180 case error: 181 value = invokeError(v) 182 } 183 184 // Handling the most common types without reflect is a small perf win. 185 switch v := value.(type) { 186 case bool: 187 return strconv.FormatBool(v) 188 case string: 189 return prettyString(v) 190 case int: 191 return strconv.FormatInt(int64(v), 10) 192 case int8: 193 return strconv.FormatInt(int64(v), 10) 194 case int16: 195 return strconv.FormatInt(int64(v), 10) 196 case int32: 197 return strconv.FormatInt(int64(v), 10) 198 case int64: 199 return strconv.FormatInt(v, 10) 200 case uint: 201 return strconv.FormatUint(uint64(v), 10) 202 case uint8: 203 return strconv.FormatUint(uint64(v), 10) 204 case uint16: 205 return strconv.FormatUint(uint64(v), 10) 206 case uint32: 207 return strconv.FormatUint(uint64(v), 10) 208 case uint64: 209 return strconv.FormatUint(v, 10) 210 case uintptr: 211 return strconv.FormatUint(uint64(v), 10) 212 case float32: 213 return strconv.FormatFloat(float64(v), 'f', -1, 32) 214 case float64: 215 return strconv.FormatFloat(v, 'f', -1, 64) 216 case complex64: 217 return `"` + strconv.FormatComplex(complex128(v), 'f', -1, 64) + `"` 218 case complex128: 219 return `"` + strconv.FormatComplex(v, 'f', -1, 128) + `"` 220 case PseudoStruct: 221 buf := bytes.NewBuffer(make([]byte, 0, 1024)) 222 v = f.sanitize(v) 223 if flags&flagRawStruct == 0 { 224 buf.WriteByte('{') 225 } 226 for i := 0; i < len(v); i += 2 { 227 if i > 0 { 228 buf.WriteByte(',') 229 } 230 k, _ := v[i].(string) // sanitize() above means no need to check success arbitrary keys might need escaping. 231 buf.WriteString(prettyString(k)) 232 buf.WriteByte(':') 233 buf.WriteString(f.prettyWithFlags(v[i+1], 0, depth+1)) 234 } 235 if flags&flagRawStruct == 0 { 236 buf.WriteByte('}') 237 } 238 return buf.String() 239 } 240 241 buf := bytes.NewBuffer(make([]byte, 0, 256)) 242 t := reflect.TypeOf(value) 243 if t == nil { 244 return null 245 } 246 v := reflect.ValueOf(value) 247 switch t.Kind() { 248 case reflect.Bool: 249 return strconv.FormatBool(v.Bool()) 250 case reflect.String: 251 return prettyString(v.String()) 252 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 253 return strconv.FormatInt(v.Int(), 10) 254 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 255 return strconv.FormatUint(v.Uint(), 10) 256 case reflect.Float32: 257 return strconv.FormatFloat(v.Float(), 'f', -1, 32) 258 case reflect.Float64: 259 return strconv.FormatFloat(v.Float(), 'f', -1, 64) 260 case reflect.Complex64: 261 return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 64) + `"` 262 case reflect.Complex128: 263 return `"` + strconv.FormatComplex(v.Complex(), 'f', -1, 128) + `"` 264 case reflect.Struct: 265 if flags&flagRawStruct == 0 { 266 buf.WriteByte('{') 267 } 268 for i := 0; i < t.NumField(); i++ { 269 fld := t.Field(i) 270 if fld.PkgPath != "" { 271 // Reflect says this field is only defined for non-exported fields. 272 continue 273 } 274 if !v.Field(i).CanInterface() { 275 // Reflect isn't clear exactly what this means, but we can't use it. 276 continue 277 } 278 name := "" 279 omitempty := false 280 if tag, found := fld.Tag.Lookup("json"); found { 281 if tag == "-" { 282 continue 283 } 284 if comma := strings.Index(tag, ","); comma != -1 { 285 if n := tag[:comma]; n != "" { 286 name = n 287 } 288 rest := tag[comma:] 289 if strings.Contains(rest, ",omitempty,") || strings.HasSuffix(rest, ",omitempty") { 290 omitempty = true 291 } 292 } else { 293 name = tag 294 } 295 } 296 if omitempty && isEmpty(v.Field(i)) { 297 continue 298 } 299 if i > 0 { 300 buf.WriteByte(',') 301 } 302 if fld.Anonymous && fld.Type.Kind() == reflect.Struct && name == "" { 303 buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), flags|flagRawStruct, depth+1)) 304 continue 305 } 306 if name == "" { 307 name = fld.Name 308 } 309 // Field names can't contain characters which need escaping. 310 buf.WriteByte('"') 311 buf.WriteString(name) 312 buf.WriteByte('"') 313 buf.WriteByte(':') 314 buf.WriteString(f.prettyWithFlags(v.Field(i).Interface(), 0, depth+1)) 315 } 316 if flags&flagRawStruct == 0 { 317 buf.WriteByte('}') 318 } 319 return buf.String() 320 case reflect.Slice, reflect.Array: 321 buf.WriteByte('[') 322 for i := 0; i < v.Len(); i++ { 323 if i > 0 { 324 buf.WriteByte(',') 325 } 326 e := v.Index(i) 327 buf.WriteString(f.prettyWithFlags(e.Interface(), 0, depth+1)) 328 } 329 buf.WriteByte(']') 330 return buf.String() 331 case reflect.Map: 332 buf.WriteByte('{') 333 // This does not sort the map keys, for best perf. 334 it := v.MapRange() 335 i := 0 336 for it.Next() { 337 if i > 0 { 338 buf.WriteByte(',') 339 } 340 // If a map key supports TextMarshaler, use it. 341 var keystr string 342 if m, ok := it.Key().Interface().(encoding.TextMarshaler); ok { 343 txt, err := m.MarshalText() 344 if err != nil { 345 keystr = fmt.Sprintf("<error-MarshalText: %s>", err.Error()) 346 } else { 347 keystr = string(txt) 348 } 349 keystr = prettyString(keystr) 350 } else { 351 // prettyWithFlags will produce already-escaped values. 352 keystr = f.prettyWithFlags(it.Key().Interface(), 0, depth+1) 353 if t.Key().Kind() != reflect.String { 354 // JSON only does string keys. Unlike Go's standard JSON, we'll convert just about anything to a string. 355 keystr = prettyString(keystr) 356 } 357 } 358 buf.WriteString(keystr) 359 buf.WriteByte(':') 360 buf.WriteString(f.prettyWithFlags(it.Value().Interface(), 0, depth+1)) 361 i++ 362 } 363 buf.WriteByte('}') 364 return buf.String() 365 case reflect.Ptr, reflect.Interface: 366 if v.IsNil() { 367 return null 368 } 369 return f.prettyWithFlags(v.Elem().Interface(), 0, depth) 370 } 371 return fmt.Sprintf(`"<unhandled-%s>"`, t.Kind().String()) 372 } 373 374 // caller captures basic information about the caller (filename, line, function). 375 func (f *formatter) caller() caller { 376 pc, file, line, ok := runtime.Caller(f.opts.callerDepth + 3) 377 if !ok { 378 return caller{"<unknown>", 0, ""} 379 } 380 caller := caller{File: filepath.Base(file), Line: line} 381 if f.opts.logCallerFunc { 382 if fp := runtime.FuncForPC(pc); fp != nil { 383 caller.Func = fp.Name() 384 } 385 } 386 return caller 387 } 388 389 // nonStringKey converts non-string value v to string. 390 func (f *formatter) nonStringKey(v interface{}) string { 391 return fmt.Sprintf("<non-string-key: %s>", f.snippet(v)) 392 } 393 394 // snippet produces a short snippet string of an arbitrary value. 395 func (f *formatter) snippet(v interface{}) string { 396 const snipLen = 16 397 398 snip := f.prettyWithFlags(v, 0, 0) 399 if len(snip) > snipLen { 400 snip = snip[:snipLen] 401 } 402 return snip 403 } 404 405 // sanitize ensures that a list of key-value pairs has a value for every key 406 // (adding a value if needed) and that each key is a string (substituting a key 407 // if needed). 408 func (f *formatter) sanitize(kvList []interface{}) []interface{} { 409 if len(kvList)%2 != 0 { 410 kvList = append(kvList, noValue) 411 } 412 for i := 0; i < len(kvList); i += 2 { 413 _, ok := kvList[i].(string) 414 if !ok { 415 kvList[i] = f.nonStringKey(kvList[i]) 416 } 417 } 418 return kvList 419 } 420 421 // isEmpty is similar to the IsZero() reflect.Value method, except that 422 // in the case of Array and Struct it does not go into depth. 423 func isEmpty(v reflect.Value) bool { 424 switch v.Kind() { 425 case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 426 return v.Len() == 0 427 case reflect.Bool: 428 return !v.Bool() 429 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 430 return v.Int() == 0 431 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 432 return v.Uint() == 0 433 case reflect.Float32, reflect.Float64: 434 return v.Float() == 0 435 case reflect.Complex64, reflect.Complex128: 436 return v.Complex() == 0 437 case reflect.Interface, reflect.Ptr: 438 return v.IsNil() 439 } 440 return false 441 } 442 443 // prettyString prettify the given string. 444 // TODO: try to avoid allocations. 445 func prettyString(s string) string { 446 // Avoid escaping (which does allocations) if we can. 447 if needsEscape(s) { 448 return strconv.Quote(s) 449 } 450 b := bytes.NewBuffer(make([]byte, 0, 1024)) 451 b.WriteByte('"') 452 b.WriteString(s) 453 b.WriteByte('"') 454 return b.String() 455 } 456 457 // needsEscape determines whether the input string needs 458 // to be escaped or not, without doing any allocations. 459 func needsEscape(s string) bool { 460 for _, r := range s { 461 if !strconv.IsPrint(r) || r == '\\' || r == '"' { 462 return true 463 } 464 } 465 return false 466 } 467 468 // invokeMarshaler returns panic-safe output from the Marshaler.MarshalLog() method. 469 func invokeMarshaler(m Marshaler) (ret interface{}) { 470 defer func() { 471 if r := recover(); r != nil { 472 ret = fmt.Sprintf("<panic: %s>", r) 473 } 474 }() 475 return m.MarshalLog() 476 } 477 478 // invokeStringer returns panic-safe output from the fmt.Stringer.String() method. 479 func invokeStringer(s fmt.Stringer) (ret string) { 480 defer func() { 481 if r := recover(); r != nil { 482 ret = fmt.Sprintf("<panic: %s>", r) 483 } 484 }() 485 return s.String() 486 } 487 488 // invokeError returns panic-safe output from the error. Error() method. 489 func invokeError(e error) (ret string) { 490 defer func() { 491 if r := recover(); r != nil { 492 ret = fmt.Sprintf("<panic: %s>", r) 493 } 494 }() 495 return e.Error() 496 }