Søren Rasmussen
07a23c1845
Some checks reported errors
continuous-integration/drone/push Build encountered an error
205 lines
5.2 KiB
Go
205 lines
5.2 KiB
Go
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
|
|
}
|