package log import ( "encoding/json" "fmt" "io" "reflect" "runtime/debug" "strconv" "time" ) type bufferWriter interface { Write(p []byte) (nn int, err error) WriteRune(r rune) (n int, err error) WriteString(s string) (n int, err error) } // JSONFormatter is a fast, efficient JSON formatter optimized for logging. // // * log entry keys are not escaped // Who uses complex keys when coding? Checked by HappyDevFormatter in case user does. // Nested object keys are escaped by json.Marshal(). // * Primitive types uses strconv // * Logger reserved key values (time, log name, level) require no conversion // * sync.Pool buffer for bytes.Buffer type JSONFormatter struct { name string } // NewJSONFormatter creates a new instance of JSONFormatter. func NewJSONFormatter(name string) *JSONFormatter { return &JSONFormatter{name: name} } func (jf *JSONFormatter) writeString(buf bufferWriter, s string) { b, err := json.Marshal(s) if err != nil { InternalLog.Error("Could not json.Marshal string.", "str", s) buf.WriteString(`"Could not marshal this key's string"`) return } buf.Write(b) } func (jf *JSONFormatter) writeError(buf bufferWriter, err error) { jf.writeString(buf, err.Error()) jf.set(buf, KeyMap.CallStack, string(debug.Stack())) return } func (jf *JSONFormatter) appendValue(buf bufferWriter, val interface{}) { if val == nil { buf.WriteString("null") return } // always show error stack even at cost of some performance. there's // nothing worse than looking at production logs without a clue if err, ok := val.(error); ok { jf.writeError(buf, err) return } value := reflect.ValueOf(val) kind := value.Kind() if kind == reflect.Ptr { if value.IsNil() { buf.WriteString("null") return } value = value.Elem() kind = value.Kind() } switch kind { case reflect.Bool: if value.Bool() { buf.WriteString("true") } else { buf.WriteString("false") } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: buf.WriteString(strconv.FormatInt(value.Int(), 10)) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: buf.WriteString(strconv.FormatUint(value.Uint(), 10)) case reflect.Float32: buf.WriteString(strconv.FormatFloat(value.Float(), 'g', -1, 32)) case reflect.Float64: buf.WriteString(strconv.FormatFloat(value.Float(), 'g', -1, 64)) default: var err error var b []byte if stringer, ok := val.(fmt.Stringer); ok { b, err = json.Marshal(stringer.String()) } else { b, err = json.Marshal(val) } if err != nil { InternalLog.Error("Could not json.Marshal value: ", "formatter", "JSONFormatter", "err", err.Error()) if s, ok := val.(string); ok { b, err = json.Marshal(s) } else if s, ok := val.(fmt.Stringer); ok { b, err = json.Marshal(s.String()) } else { b, err = json.Marshal(fmt.Sprintf("%#v", val)) } if err != nil { // should never get here, but JSONFormatter should never panic msg := "Could not Sprintf value" InternalLog.Error(msg) buf.WriteString(`"` + msg + `"`) return } } buf.Write(b) } } func (jf *JSONFormatter) set(buf bufferWriter, key string, val interface{}) { // WARNING: assumes this is not first key buf.WriteString(`, "`) buf.WriteString(key) buf.WriteString(`":`) jf.appendValue(buf, val) } // Format formats log entry as JSON. func (jf *JSONFormatter) Format(writer io.Writer, level int, msg string, args []interface{}) { buf := pool.Get() defer pool.Put(buf) const lead = `", "` const colon = `":"` buf.WriteString(`{"`) buf.WriteString(KeyMap.Time) buf.WriteString(`":"`) buf.WriteString(time.Now().Format(timeFormat)) buf.WriteString(`", "`) buf.WriteString(KeyMap.PID) buf.WriteString(`":"`) buf.WriteString(pidStr) buf.WriteString(`", "`) buf.WriteString(KeyMap.Level) buf.WriteString(`":"`) buf.WriteString(LevelMap[level]) buf.WriteString(`", "`) buf.WriteString(KeyMap.Name) buf.WriteString(`":"`) buf.WriteString(jf.name) buf.WriteString(`", "`) buf.WriteString(KeyMap.Message) buf.WriteString(`":`) jf.appendValue(buf, msg) var lenArgs = len(args) if lenArgs > 0 { if lenArgs == 1 { jf.set(buf, singleArgKey, args[0]) } else if lenArgs%2 == 0 { for i := 0; i < lenArgs; i += 2 { if key, ok := args[i].(string); ok { if key == "" { // show key is invalid jf.set(buf, badKeyAtIndex(i), args[i+1]) } else { jf.set(buf, key, args[i+1]) } } else { // show key is invalid jf.set(buf, badKeyAtIndex(i), args[i+1]) } } } else { jf.set(buf, warnImbalancedKey, args) } } buf.WriteString("}\n") buf.WriteTo(writer) } // LogEntry returns the JSON log entry object built by Format(). Used by // HappyDevFormatter to ensure any data logged while developing properly // logs in production. func (jf *JSONFormatter) LogEntry(level int, msg string, args []interface{}) map[string]interface{} { buf := pool.Get() defer pool.Put(buf) jf.Format(buf, level, msg, args) var entry map[string]interface{} err := json.Unmarshal(buf.Bytes(), &entry) if err != nil { panic("Unable to unmarhsal entry from JSONFormatter: " + err.Error() + " \"" + string(buf.Bytes()) + "\"") } return entry }