matterbridge/vendor/github.com/mattermost/logr/v2/logrec.go

183 lines
4.4 KiB
Go
Raw Permalink Normal View History

2021-10-16 14:11:32 -07:00
package logr
import (
"fmt"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
)
// 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
msg string
newline bool
fields []Field
stackPC []uintptr
stackCount int
// flushes Logr and target queues when not nil.
flush chan struct{}
// remaining fields calculated by `prep`
frames []runtime.Frame
fieldsAll []Field
caller string
}
// NewLogRec creates a new LogRec with the current time and optional stack trace.
func NewLogRec(lvl Level, logger Logger, msg string, fields []Field, incStacktrace bool) *LogRec {
rec := &LogRec{time: time.Now(), logger: logger, level: lvl, msg: msg, fields: fields}
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 stack trace to frames.
func (rec *LogRec) prep() {
rec.mux.Lock()
defer rec.mux.Unlock()
// include log rec fields and logger fields added via "With"
rec.fieldsAll = make([]Field, 0, len(rec.fields)+len(rec.logger.fields))
rec.fieldsAll = append(rec.fieldsAll, rec.logger.fields...)
rec.fieldsAll = append(rec.fieldsAll, rec.fields...)
filter := rec.logger.lgr.options.stackFilter
// resolve stack trace
if rec.stackCount > 0 {
rec.frames = make([]runtime.Frame, 0, rec.stackCount)
frames := runtime.CallersFrames(rec.stackPC[:rec.stackCount])
for {
frame, more := frames.Next()
// remove all package entries that are in filter.
pkg := ResolvePackageName(frame.Function)
if _, ok := filter[pkg]; !ok && pkg != "" {
rec.frames = append(rec.frames, frame)
}
if !more {
break
}
}
}
// calc caller if stack trace provided
if len(rec.frames) > 0 {
rec.caller = calcCaller(rec.frames)
}
}
// 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,
msg: rec.msg,
newline: rec.newline,
fields: rec.fields,
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() []Field {
// no locking needed as this field is not mutated.
return rec.fieldsAll
}
// 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
}
// Caller returns this log record's caller info, meaning the file and line
// number where this log record was emitted. Returns empty string if no
// stack trace was provided.
func (rec *LogRec) Caller() string {
rec.mux.RLock()
defer rec.mux.RUnlock()
return rec.caller
}
// 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.lgr.BorrowBuffer()
defer rec.logger.lgr.ReleaseBuffer(buf)
buf, _ = f.Format(rec, rec.Level(), buf)
return strings.TrimSpace(buf.String())
}
func calcCaller(frames []runtime.Frame) string {
for _, frame := range frames {
if frame.File == "" {
continue
}
dir, file := filepath.Split(frame.File)
base := filepath.Base(dir)
return fmt.Sprintf("%s/%s:%d", base, file, frame.Line)
}
return ""
}