package log import ( "encoding" "encoding/json" "fmt" "io" "reflect" ) type jsonLogger struct { io.Writer } // NewJSONLogger returns a Logger that encodes keyvals to the Writer as a // single JSON object. Each log event produces no more than one call to // w.Write. The passed Writer must be safe for concurrent use by multiple // goroutines if the returned Logger will be used concurrently. func NewJSONLogger(w io.Writer) Logger { return &jsonLogger{w} } func (l *jsonLogger) Log(keyvals ...interface{}) error { n := (len(keyvals) + 1) / 2 // +1 to handle case when len is odd m := make(map[string]interface{}, n) for i := 0; i < len(keyvals); i += 2 { k := keyvals[i] var v interface{} = ErrMissingValue if i+1 < len(keyvals) { v = keyvals[i+1] } merge(m, k, v) } return json.NewEncoder(l.Writer).Encode(m) } func merge(dst map[string]interface{}, k, v interface{}) { var key string switch x := k.(type) { case string: key = x case fmt.Stringer: key = safeString(x) default: key = fmt.Sprint(x) } // We want json.Marshaler and encoding.TextMarshaller to take priority over // err.Error() and v.String(). But json.Marshall (called later) does that by // default so we force a no-op if it's one of those 2 case. switch x := v.(type) { case json.Marshaler: case encoding.TextMarshaler: case error: v = safeError(x) case fmt.Stringer: v = safeString(x) } dst[key] = v } func safeString(str fmt.Stringer) (s string) { defer func() { if panicVal := recover(); panicVal != nil { if v := reflect.ValueOf(str); v.Kind() == reflect.Ptr && v.IsNil() { s = "NULL" } else { panic(panicVal) } } }() s = str.String() return } func safeError(err error) (s interface{}) { defer func() { if panicVal := recover(); panicVal != nil { if v := reflect.ValueOf(err); v.Kind() == reflect.Ptr && v.IsNil() { s = nil } else { panic(panicVal) } } }() s = err.Error() return }