460 lines
10 KiB
Go
460 lines
10 KiB
Go
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 format != "" {
|
|
if len(v) == 0 {
|
|
fmt.Fprint(buf, format)
|
|
} else {
|
|
fmt.Fprintf(buf, format, v...)
|
|
}
|
|
} else {
|
|
fmt.Fprint(buf, 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()
|