github.com/go-kit/log@v0.2.1/json_logger.go (about) 1 package log 2 3 import ( 4 "encoding" 5 "encoding/json" 6 "fmt" 7 "io" 8 "reflect" 9 ) 10 11 type jsonLogger struct { 12 io.Writer 13 } 14 15 // NewJSONLogger returns a Logger that encodes keyvals to the Writer as a 16 // single JSON object. Each log event produces no more than one call to 17 // w.Write. The passed Writer must be safe for concurrent use by multiple 18 // goroutines if the returned Logger will be used concurrently. 19 func NewJSONLogger(w io.Writer) Logger { 20 return &jsonLogger{w} 21 } 22 23 func (l *jsonLogger) Log(keyvals ...interface{}) error { 24 n := (len(keyvals) + 1) / 2 // +1 to handle case when len is odd 25 m := make(map[string]interface{}, n) 26 for i := 0; i < len(keyvals); i += 2 { 27 k := keyvals[i] 28 var v interface{} = ErrMissingValue 29 if i+1 < len(keyvals) { 30 v = keyvals[i+1] 31 } 32 merge(m, k, v) 33 } 34 enc := json.NewEncoder(l.Writer) 35 enc.SetEscapeHTML(false) 36 return enc.Encode(m) 37 } 38 39 func merge(dst map[string]interface{}, k, v interface{}) { 40 var key string 41 switch x := k.(type) { 42 case string: 43 key = x 44 case fmt.Stringer: 45 key = safeString(x) 46 default: 47 key = fmt.Sprint(x) 48 } 49 50 // We want json.Marshaler and encoding.TextMarshaller to take priority over 51 // err.Error() and v.String(). But json.Marshall (called later) does that by 52 // default so we force a no-op if it's one of those 2 case. 53 switch x := v.(type) { 54 case json.Marshaler: 55 case encoding.TextMarshaler: 56 case error: 57 v = safeError(x) 58 case fmt.Stringer: 59 v = safeString(x) 60 } 61 62 dst[key] = v 63 } 64 65 func safeString(str fmt.Stringer) (s string) { 66 defer func() { 67 if panicVal := recover(); panicVal != nil { 68 if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() { 69 s = "NULL" 70 } else { 71 s = fmt.Sprintf("PANIC in String method: %v", panicVal) 72 } 73 } 74 }() 75 s = str.String() 76 return 77 } 78 79 func safeError(err error) (s interface{}) { 80 defer func() { 81 if panicVal := recover(); panicVal != nil { 82 if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() { 83 s = nil 84 } else { 85 s = fmt.Sprintf("PANIC in Error method: %v", panicVal) 86 } 87 } 88 }() 89 s = err.Error() 90 return 91 }