package logrus
import (
"bytes"
"context"
"fmt"
"maps"
"os"
"reflect"
"runtime"
"strconv"
"strings"
"sync"
"time"
)
var (
logrusPackage string
minimumCallerDepth = 1
callerInitOnce sync.Once
)
const (
maximumCallerDepth int = 25
knownLogrusFrames int = 4
)
var ErrorKey = "error"
type Entry struct {
Logger *Logger
Data Fields
Time time.Time
Level Level
Caller *runtime.Frame
Message string
Buffer *bytes.Buffer
Context context.Context
err string
}
func NewEntry(logger *Logger) *Entry {
return &Entry{
Logger: logger,
Data: make(Fields, defaultFields+3),
}
}
func (entry *Entry) Dup() *Entry {
dup := entry.dup()
dup.Data = maps.Clone(entry.Data)
return dup
}
func (entry *Entry) dup() *Entry {
return &Entry{
Logger: entry.Logger,
Time: entry.Time,
Caller: entry.Caller,
Context: entry.Context,
err: entry.err,
}
}
func (entry *Entry) Bytes() ([]byte, error) {
entry.Logger.mu.Lock()
formatter := entry.Logger.Formatter
entry.Logger.mu.Unlock()
return formatter.Format(entry)
}
func (entry *Entry) String() (string, error) {
serialized, err := entry.Bytes()
if err != nil {
return "", err
}
str := string(serialized)
return str, nil
}
func (entry *Entry) WithError(err error) *Entry {
dup := entry.dup()
dup.Data = maps.Clone(entry.Data)
if dup.Data == nil {
dup.Data = make(Fields, 1)
}
dup.Data[ErrorKey] = err
return dup
}
func (entry *Entry) WithContext(ctx context.Context) *Entry {
dup := entry.dup()
dup.Data = maps.Clone(entry.Data)
dup.Context = ctx
return dup
}
func (entry *Entry) WithField(key string, value any) *Entry {
dup := entry.dup()
dup.Data = maps.Clone(entry.Data)
if isInvalidField(value) {
if dup.err != "" {
dup.err += ", skipping unsupported field " + strconv.Quote(key)
} else {
dup.err = "skipping unsupported field " + strconv.Quote(key)
}
return dup
}
if dup.Data == nil {
dup.Data = make(Fields, 1)
}
dup.Data[key] = value
return dup
}
func (entry *Entry) WithFields(fields Fields) *Entry {
dup := entry.dup()
dup.Data = make(Fields, len(entry.Data)+len(fields))
maps.Copy(dup.Data, entry.Data)
for key, value := range fields {
if isInvalidField(value) {
if dup.err != "" {
dup.err += ", skipping unsupported field " + strconv.Quote(key)
} else {
dup.err = "skipping unsupported field " + strconv.Quote(key)
}
} else {
dup.Data[key] = value
}
}
return dup
}
func isInvalidField(v any) bool {
t := reflect.TypeOf(v)
if t == nil {
return false
}
return t.Kind() == reflect.Func || t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Func
}
func (entry *Entry) WithTime(t time.Time) *Entry {
dup := entry.dup()
dup.Data = maps.Clone(entry.Data)
dup.Time = t
return dup
}
func getPackageName(f string) string {
for {
lastPeriod := strings.LastIndex(f, ".")
lastSlash := strings.LastIndex(f, "/")
if lastPeriod > lastSlash {
f = f[:lastPeriod]
} else {
break
}
}
return f
}
func getCaller() *runtime.Frame {
callerInitOnce.Do(func() {
pcs := make([]uintptr, maximumCallerDepth)
_ = runtime.Callers(0, pcs)
for i := range maximumCallerDepth {
funcName := runtime.FuncForPC(pcs[i]).Name()
if strings.Contains(funcName, "getCaller") {
logrusPackage = getPackageName(funcName)
break
}
}
minimumCallerDepth = knownLogrusFrames
})
pcs := make([]uintptr, maximumCallerDepth)
depth := runtime.Callers(minimumCallerDepth, pcs)
frames := runtime.CallersFrames(pcs[:depth])
for f, again := frames.Next(); again; f, again = frames.Next() {
pkg := getPackageName(f.Function)
if pkg != logrusPackage {
return &f
}
}
return nil
}
func (entry Entry) HasCaller() bool {
return entry.Caller != nil
}
func (entry *Entry) logArgs(level Level, panicAfter bool, args ...any) {
entry.log(level, panicAfter, sprint(args...))
}
func (entry *Entry) logf(level Level, panicAfter bool, format string, args ...any) {
entry.log(level, panicAfter, fmt.Sprintf(format, args...))
}
func (entry *Entry) logln(level Level, panicAfter bool, args ...any) {
if len(args) <= 1 {
entry.log(level, panicAfter, sprint(args...))
return
}
msg := fmt.Sprintln(args...)
msg = msg[:len(msg)-1]
entry.log(level, panicAfter, msg)
}
func (entry *Entry) log(level Level, panicAfter bool, msg string) {
newEntry := entry.dup()
newEntry.Data = maps.Clone(entry.Data)
if newEntry.Time.IsZero() {
newEntry.Time = time.Now()
}
newEntry.Level = level
newEntry.Message = msg
logger := newEntry.Logger
logger.mu.Lock()
reportCaller := logger.ReportCaller
bufPool := newEntry.getBufferPool()
logger.mu.Unlock()
if reportCaller && newEntry.Caller == nil {
newEntry.Caller = getCaller()
}
hooks := logger.hooksForLevel(level)
newEntry.fireHooks(hooks)
buffer := bufPool.Get()
defer func() {
newEntry.Buffer = nil
buffer.Reset()
bufPool.Put(buffer)
}()
buffer.Reset()
newEntry.Buffer = buffer
newEntry.write()
newEntry.Buffer = nil
if panicAfter {
panic(newEntry)
}
}
func (entry *Entry) getBufferPool() (pool BufferPool) {
if entry.Logger.BufferPool != nil {
return entry.Logger.BufferPool
}
return bufferPool
}
func (entry *Entry) fireHooks(hooks []Hook) {
for _, hook := range hooks {
if err := hook.Fire(entry); err != nil {
_, _ = fmt.Fprintln(os.Stderr, "Failed to fire hook:", err)
return
}
}
}
func (entry *Entry) write() {
entry.Logger.mu.Lock()
formatter := entry.Logger.Formatter
entry.Logger.mu.Unlock()
serialized, err := formatter.Format(entry)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "Failed to format entry:", err)
return
}
entry.Logger.mu.Lock()
defer entry.Logger.mu.Unlock()
if _, err := entry.Logger.Out.Write(serialized); err != nil {
_, _ = fmt.Fprintln(os.Stderr, "Failed to write to log:", err)
}
}
func (entry *Entry) Log(level Level, args ...any) {
const panicAfter = false
if entry.Logger.IsLevelEnabled(level) {
entry.logArgs(level, panicAfter, args...)
}
}
func (entry *Entry) Trace(args ...any) {
entry.Log(TraceLevel, args...)
}
func (entry *Entry) Debug(args ...any) {
entry.Log(DebugLevel, args...)
}
func (entry *Entry) Print(args ...any) {
entry.Info(args...)
}
func (entry *Entry) Info(args ...any) {
entry.Log(InfoLevel, args...)
}
func (entry *Entry) Warn(args ...any) {
entry.Log(WarnLevel, args...)
}
func (entry *Entry) Warning(args ...any) {
entry.Warn(args...)
}
func (entry *Entry) Error(args ...any) {
entry.Log(ErrorLevel, args...)
}
func (entry *Entry) Fatal(args ...any) {
entry.Log(FatalLevel, args...)
entry.Logger.Exit(1)
}
func (entry *Entry) Panic(args ...any) {
const panicAfter = true
if entry.Logger.IsLevelEnabled(PanicLevel) {
entry.logArgs(PanicLevel, panicAfter, args...)
}
}
func (entry *Entry) Logf(level Level, format string, args ...any) {
const panicAfter = false
if entry.Logger.IsLevelEnabled(level) {
entry.logf(level, panicAfter, format, args...)
}
}
func (entry *Entry) Tracef(format string, args ...any) {
entry.Logf(TraceLevel, format, args...)
}
func (entry *Entry) Debugf(format string, args ...any) {
entry.Logf(DebugLevel, format, args...)
}
func (entry *Entry) Infof(format string, args ...any) {
entry.Logf(InfoLevel, format, args...)
}
func (entry *Entry) Printf(format string, args ...any) {
entry.Infof(format, args...)
}
func (entry *Entry) Warnf(format string, args ...any) {
entry.Logf(WarnLevel, format, args...)
}
func (entry *Entry) Warningf(format string, args ...any) {
entry.Warnf(format, args...)
}
func (entry *Entry) Errorf(format string, args ...any) {
entry.Logf(ErrorLevel, format, args...)
}
func (entry *Entry) Fatalf(format string, args ...any) {
entry.Logf(FatalLevel, format, args...)
entry.Logger.Exit(1)
}
func (entry *Entry) Panicf(format string, args ...any) {
const panicAfter = true
if entry.Logger.IsLevelEnabled(PanicLevel) {
entry.logf(PanicLevel, panicAfter, format, args...)
}
}
func (entry *Entry) Logln(level Level, args ...any) {
const panicAfter = false
if entry.Logger.IsLevelEnabled(level) {
entry.logln(level, panicAfter, args...)
}
}
func (entry *Entry) Traceln(args ...any) {
entry.Logln(TraceLevel, args...)
}
func (entry *Entry) Debugln(args ...any) {
entry.Logln(DebugLevel, args...)
}
func (entry *Entry) Infoln(args ...any) {
entry.Logln(InfoLevel, args...)
}
func (entry *Entry) Println(args ...any) {
entry.Infoln(args...)
}
func (entry *Entry) Warnln(args ...any) {
entry.Logln(WarnLevel, args...)
}
func (entry *Entry) Warningln(args ...any) {
entry.Warnln(args...)
}
func (entry *Entry) Errorln(args ...any) {
entry.Logln(ErrorLevel, args...)
}
func (entry *Entry) Fatalln(args ...any) {
entry.Logln(FatalLevel, args...)
entry.Logger.Exit(1)
}
func (entry *Entry) Panicln(args ...any) {
const panicAfter = true
if entry.Logger.IsLevelEnabled(PanicLevel) {
entry.logln(PanicLevel, panicAfter, args...)
}
}
func sprint(args ...any) string {
switch len(args) {
case 0:
return ""
case 1:
if msg, ok := args[0].(string); ok {
return msg
}
}
return fmt.Sprint(args...)
}