blob: b21dd08ca3ad5e6811a76a1ae8b48bf1bca295c0 [file] [log] [blame]
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
}