package log import ( "context" "fmt" "os" "runtime" "strconv" "strings" "sync" "time" "github.com/pengsrc/go-shared/buffer" "github.com/pengsrc/go-shared/convert" ) // A EventCallerPool is a type-safe wrapper around a sync.BytesBufferPool. type EventCallerPool struct { p *sync.Pool } // NewEventCallerPool constructs a new BytesBufferPool. func NewEventCallerPool() EventCallerPool { return EventCallerPool{ p: &sync.Pool{ New: func() interface{} { return &EventCaller{} }, }, } } // Get retrieves a EventCaller from the pool, creating one if necessary. func (p EventCallerPool) Get() *EventCaller { e := p.p.Get().(*EventCaller) e.pool = p e.Defined = false e.PC = 0 e.File = "" e.Line = 0 return e } func (p EventCallerPool) put(caller *EventCaller) { p.p.Put(caller) } // EventCaller represents the caller of a logging function. type EventCaller struct { pool EventCallerPool Defined bool PC uintptr File string Line int } // Free returns the EventCaller to its EventCallerPool. // Callers must not retain references to the EventCaller after calling Free. func (ec *EventCaller) Free() { ec.pool.put(ec) } // String returns the full path and line number of the caller. func (ec EventCaller) String() string { return ec.FullPath() } // FullPath returns a /full/path/to/package/file:line description of the // caller. func (ec EventCaller) FullPath() string { if !ec.Defined { return "undefined" } buf := buffer.GlobalBytesPool().Get() defer buf.Free() buf.AppendString(ec.File) buf.AppendByte(':') buf.AppendInt(int64(ec.Line)) return buf.String() } // TrimmedPath returns a package/file:line description of the caller, // preserving only the leaf directory name and file name. func (ec EventCaller) TrimmedPath() string { if !ec.Defined { return "undefined" } // nb. To make sure we trim the path correctly on Windows too, we // counter-intuitively need to use '/' and *not* os.PathSeparator here, // because the path given originates from Go stdlib, specifically // runtime.Caller() which (as of Mar/17) returns forward slashes even on // Windows. // // See https://github.com/golang/go/issues/3335 // and https://github.com/golang/go/issues/18151 // // for discussion on the issue on Go side. // // Find the last separator. // idx := strings.LastIndexByte(ec.File, '/') if idx == -1 { return ec.FullPath() } // Find the penultimate separator. idx = strings.LastIndexByte(ec.File[:idx], '/') if idx == -1 { return ec.FullPath() } buf := buffer.GlobalBytesPool().Get() defer buf.Free() // Keep everything after the penultimate separator. buf.AppendString(ec.File[idx+1:]) buf.AppendByte(':') buf.AppendInt(int64(ec.Line)) return buf.String() } // A EventPool is a type-safe wrapper around a sync.BytesBufferPool. type EventPool struct { p *sync.Pool } // NewEventPool constructs a new BytesBufferPool. func NewEventPool() EventPool { return EventPool{ p: &sync.Pool{ New: func() interface{} { return &Event{} }, }, } } // Get retrieves a Event from the pool, creating one if necessary. func (p EventPool) Get() *Event { e := p.p.Get().(*Event) e.buffer = buffer.GlobalBytesPool().Get() e.pool = p e.level = MuteLevel e.lw = nil e.ctx = nil e.ctxKeys = nil e.isEnabled = false e.isCallerEnabled = false return e } func (p EventPool) put(event *Event) { event.buffer.Free() event.ctx = nil event.ctxKeys = nil p.p.Put(event) } // Event represents a log event. It is instanced by one of the with method of // logger and finalized by the log method such as Debug(). type Event struct { buffer *buffer.BytesBuffer pool EventPool level Level lw LevelWriter ctx context.Context ctxKeys *[]interface{} ctxKeysMap *map[interface{}]string isEnabled bool isCallerEnabled bool } // Free returns the Event to its EventPool. // Callers must not retain references to the Event after calling Free. func (e *Event) Free() { e.pool.put(e) } // Message writes the *Event to level writer. // // NOTICE: Once this method is called, the *Event should be disposed. // Calling twice can have unexpected result. func (e *Event) Message(message string) { if !e.isEnabled { return } e.write(message) } // Messagef writes the *Event to level writer. // // NOTICE: Once this method is called, the *Event should be disposed. // Calling twice can have unexpected result. func (e *Event) Messagef(format string, v ...interface{}) { if !e.isEnabled { return } e.write(format, v...) } // Byte appends string key and byte value to event. func (e *Event) Byte(key string, value byte) *Event { return e.appendField(key, func() { e.buffer.AppendByte(value) }) } // Bytes appends string key and bytes value to event. func (e *Event) Bytes(key string, value []byte) *Event { return e.appendField(key, func() { if needsQuote(string(value)) { e.buffer.AppendString(strconv.Quote(string(value))) } else { e.buffer.AppendBytes(value) } }) } // String appends string key and string value to event. func (e *Event) String(key string, value string) *Event { return e.appendField(key, func() { if needsQuote(string(value)) { e.buffer.AppendString(strconv.Quote(value)) } else { e.buffer.AppendString(value) } }) } // Int appends string key and int value to event. func (e *Event) Int(key string, value int) *Event { return e.appendField(key, func() { e.buffer.AppendInt(int64(value)) }) } // Int32 appends string key and int32 value to event. func (e *Event) Int32(key string, value int32) *Event { return e.appendField(key, func() { e.buffer.AppendInt(int64(value)) }) } // Int64 appends string key and int64 value to event. func (e *Event) Int64(key string, value int64) *Event { return e.appendField(key, func() { e.buffer.AppendInt(value) }) } // Uint appends string key and uint value to event. func (e *Event) Uint(key string, value uint) *Event { return e.appendField(key, func() { e.buffer.AppendUint(uint64(value)) }) } // Uint32 appends string key and uint32 value to event. func (e *Event) Uint32(key string, value uint32) *Event { return e.appendField(key, func() { e.buffer.AppendUint(uint64(value)) }) } // Uint64 appends string key and uint64 value to event. func (e *Event) Uint64(key string, value uint64) *Event { return e.appendField(key, func() { e.buffer.AppendUint(value) }) } // Float32 appends string key and float32 value to event. func (e *Event) Float32(key string, value float32) *Event { return e.appendField(key, func() { e.buffer.AppendFloat(float64(value), 32) }) } // Float64 appends string key and float value to event. func (e *Event) Float64(key string, value float64) *Event { return e.appendField(key, func() { e.buffer.AppendFloat(value, 64) }) } // Bool appends string key and bool value to event. func (e *Event) Bool(key string, value bool) *Event { return e.appendField(key, func() { e.buffer.AppendBool(value) }) } // Time appends string key and time value to event. func (e *Event) Time(key string, value time.Time, format string) *Event { buf := buffer.GlobalBytesPool().Get() defer buf.Free() buf.AppendTime(value, format) return e.Bytes(key, buf.Bytes()) } // Error appends string key and error value to event. func (e *Event) Error(key string, err error) *Event { return e.String(key, err.Error()) } // Interface appends string key and interface value to event. func (e *Event) Interface(key string, value interface{}) *Event { switch v := value.(type) { case byte: e.Byte(key, v) case []byte: e.Bytes(key, v) case string: e.String(key, v) case int: e.Int(key, v) case int32: e.Int32(key, v) case int64: e.Int64(key, v) case uint: e.Uint(key, v) case uint32: e.Uint32(key, v) case uint64: e.Uint64(key, v) case float32: e.Float32(key, v) case float64: e.Float64(key, v) case bool: e.Bool(key, v) case time.Time: e.Time(key, v, convert.ISO8601Milli) case error: e.Error(key, v) case nil: e.String(key, "nil") default: panic(fmt.Sprintf("unknown field type: %v", value)) } return e } func (e *Event) appendField(key string, appendFunc func()) *Event { if !e.isEnabled { return e } // Ignore field with empty key. if len(key) <= 0 { return e } // Append space if event field not empty. if e.buffer.Len() != 0 { e.buffer.AppendByte(' ') } e.buffer.AppendString(key) e.buffer.AppendString("=") appendFunc() return e } func (e *Event) write(format string, v ...interface{}) { defer e.Free() if !e.isEnabled { return } // Append interested contexts. if e.ctx != nil && e.ctxKeys != nil && e.ctxKeysMap != nil { for _, key := range *e.ctxKeys { if value := e.ctx.Value(key); value != nil { e.Interface((*e.ctxKeysMap)[key], e.ctx.Value(key)) } } } // Append caller. if e.isCallerEnabled { ec := newEventCaller(runtime.Caller(callerSkipOffset)) e.String("source", ec.TrimmedPath()) } // Compose and store current log. buf := buffer.GlobalBytesPool().Get() defer buf.Free() // Format print message. if len(v) == 0 { fmt.Fprint(buf, format) } else { fmt.Fprintf(buf, format, v...) } // Append filed. buf.AppendByte(' ') buf.AppendBytes(e.buffer.Bytes()) // Finally write. if _, err := e.lw.WriteLevel(e.level, buf.Bytes()); err != nil { fmt.Fprintf(os.Stderr, "log: could not write event: %v", err) } switch e.level { case PanicLevel: panic(buf.String()) case FatalLevel: os.Exit(1) } } func newEventCaller(pc uintptr, file string, line int, ok bool) (ec *EventCaller) { ec = eventCallerPool.Get() if ok { ec.PC = pc ec.File = file ec.Line = line ec.Defined = true } return } func newEvent( ctx context.Context, ctxKeys *[]interface{}, ctxKeysMap *map[interface{}]string, level Level, lw LevelWriter, isEnabled bool, isCallerEnabled bool, ) (e *Event) { e = eventPool.Get() e.level = level e.lw = lw e.ctx = ctx e.ctxKeys = ctxKeys e.ctxKeysMap = ctxKeysMap e.isEnabled = isEnabled e.isCallerEnabled = isCallerEnabled return } func needsQuote(s string) bool { for i := range s { if s[i] < 0x20 || s[i] > 0x7e || s[i] == ' ' || s[i] == '\\' || s[i] == '"' { return true } } return false } const callerSkipOffset = 2 // eventCallerPool is a pool of newEvent callers. var eventCallerPool = NewEventCallerPool() // eventPool is a pool of events. var eventPool = NewEventPool()