forked from jshiffer/matterbridge
190 lines
4.4 KiB
Go
190 lines
4.4 KiB
Go
package logr
|
|
|
|
import (
|
|
"fmt"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
logrPkg string
|
|
)
|
|
|
|
func init() {
|
|
// Calc current package name
|
|
pcs := make([]uintptr, 2)
|
|
_ = runtime.Callers(0, pcs)
|
|
tmp := runtime.FuncForPC(pcs[1]).Name()
|
|
logrPkg = getPackageName(tmp)
|
|
}
|
|
|
|
// LogRec collects raw, unformatted data to be logged.
|
|
// TODO: pool these? how to reliably know when targets are done with them? Copy for each target?
|
|
type LogRec struct {
|
|
mux sync.RWMutex
|
|
time time.Time
|
|
|
|
level Level
|
|
logger Logger
|
|
|
|
template string
|
|
newline bool
|
|
args []interface{}
|
|
|
|
stackPC []uintptr
|
|
stackCount int
|
|
|
|
// flushes Logr and target queues when not nil.
|
|
flush chan struct{}
|
|
|
|
// remaining fields calculated by `prep`
|
|
msg string
|
|
frames []runtime.Frame
|
|
}
|
|
|
|
// NewLogRec creates a new LogRec with the current time and optional stack trace.
|
|
func NewLogRec(lvl Level, logger Logger, template string, args []interface{}, incStacktrace bool) *LogRec {
|
|
rec := &LogRec{time: time.Now(), logger: logger, level: lvl, template: template, args: args}
|
|
if incStacktrace {
|
|
rec.stackPC = make([]uintptr, DefaultMaxStackFrames)
|
|
rec.stackCount = runtime.Callers(2, rec.stackPC)
|
|
}
|
|
return rec
|
|
}
|
|
|
|
// newFlushLogRec creates a LogRec that flushes the Logr queue and
|
|
// any target queues that support flushing.
|
|
func newFlushLogRec(logger Logger) *LogRec {
|
|
return &LogRec{logger: logger, flush: make(chan struct{})}
|
|
}
|
|
|
|
// prep resolves all args and field values to strings, and
|
|
// resolves stack trace to frames.
|
|
func (rec *LogRec) prep() {
|
|
rec.mux.Lock()
|
|
defer rec.mux.Unlock()
|
|
|
|
// resolve args
|
|
if rec.template == "" {
|
|
if rec.newline {
|
|
rec.msg = fmt.Sprintln(rec.args...)
|
|
} else {
|
|
rec.msg = fmt.Sprint(rec.args...)
|
|
}
|
|
} else {
|
|
rec.msg = fmt.Sprintf(rec.template, rec.args...)
|
|
}
|
|
|
|
// resolve stack trace
|
|
if rec.stackCount > 0 {
|
|
frames := runtime.CallersFrames(rec.stackPC[:rec.stackCount])
|
|
for {
|
|
f, more := frames.Next()
|
|
rec.frames = append(rec.frames, f)
|
|
if !more {
|
|
break
|
|
}
|
|
}
|
|
|
|
// remove leading logr package entries.
|
|
var start int
|
|
for i, frame := range rec.frames {
|
|
pkg := getPackageName(frame.Function)
|
|
if pkg != "" && pkg != logrPkg {
|
|
start = i
|
|
break
|
|
}
|
|
}
|
|
rec.frames = rec.frames[start:]
|
|
}
|
|
}
|
|
|
|
// WithTime returns a shallow copy of the log record while replacing
|
|
// the time. This can be used by targets and formatters to adjust
|
|
// the time, or take ownership of the log record.
|
|
func (rec *LogRec) WithTime(time time.Time) *LogRec {
|
|
rec.mux.RLock()
|
|
defer rec.mux.RUnlock()
|
|
|
|
return &LogRec{
|
|
time: time,
|
|
level: rec.level,
|
|
logger: rec.logger,
|
|
template: rec.template,
|
|
newline: rec.newline,
|
|
args: rec.args,
|
|
msg: rec.msg,
|
|
stackPC: rec.stackPC,
|
|
stackCount: rec.stackCount,
|
|
frames: rec.frames,
|
|
}
|
|
}
|
|
|
|
// Logger returns the `Logger` that created this `LogRec`.
|
|
func (rec *LogRec) Logger() Logger {
|
|
return rec.logger
|
|
}
|
|
|
|
// Time returns this log record's time stamp.
|
|
func (rec *LogRec) Time() time.Time {
|
|
// no locking needed as this field is not mutated.
|
|
return rec.time
|
|
}
|
|
|
|
// Level returns this log record's Level.
|
|
func (rec *LogRec) Level() Level {
|
|
// no locking needed as this field is not mutated.
|
|
return rec.level
|
|
}
|
|
|
|
// Fields returns this log record's Fields.
|
|
func (rec *LogRec) Fields() Fields {
|
|
// no locking needed as this field is not mutated.
|
|
return rec.logger.fields
|
|
}
|
|
|
|
// Msg returns this log record's message text.
|
|
func (rec *LogRec) Msg() string {
|
|
rec.mux.RLock()
|
|
defer rec.mux.RUnlock()
|
|
return rec.msg
|
|
}
|
|
|
|
// StackFrames returns this log record's stack frames or
|
|
// nil if no stack trace was required.
|
|
func (rec *LogRec) StackFrames() []runtime.Frame {
|
|
rec.mux.RLock()
|
|
defer rec.mux.RUnlock()
|
|
return rec.frames
|
|
}
|
|
|
|
// String returns a string representation of this log record.
|
|
func (rec *LogRec) String() string {
|
|
if rec.flush != nil {
|
|
return "[flusher]"
|
|
}
|
|
|
|
f := &DefaultFormatter{}
|
|
buf := rec.logger.logr.BorrowBuffer()
|
|
defer rec.logger.logr.ReleaseBuffer(buf)
|
|
buf, _ = f.Format(rec, true, buf)
|
|
return strings.TrimSpace(buf.String())
|
|
}
|
|
|
|
// getPackageName reduces a fully qualified function name to the package name
|
|
// By sirupsen: https://github.com/sirupsen/logrus/blob/master/entry.go
|
|
func getPackageName(f string) string {
|
|
for {
|
|
lastPeriod := strings.LastIndex(f, ".")
|
|
lastSlash := strings.LastIndex(f, "/")
|
|
if lastPeriod > lastSlash {
|
|
f = f[:lastPeriod]
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
return f
|
|
}
|