mirror of
https://github.com/cwinfo/matterbridge.git
synced 2024-11-25 11:01:35 +00:00
183 lines
4.4 KiB
Go
183 lines
4.4 KiB
Go
|
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 ""
|
||
|
}
|