mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 15:46:26 -04:00
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.9.4 to 1.10.2. - [Release notes](https://github.com/sirupsen/logrus/releases) - [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md) - [Commits](https://github.com/sirupsen/logrus/compare/v1.9.4...v1.10.2) --- updated-dependencies: - dependency-name: github.com/sirupsen/logrus dependency-version: 1.10.2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
568 lines
15 KiB
Go
568 lines
15 KiB
Go
package logrus
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"fmt"
|
||
"maps"
|
||
"os"
|
||
"reflect"
|
||
"runtime"
|
||
"strconv"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
var (
|
||
|
||
// qualified package name, cached at first use
|
||
logrusPackage string
|
||
|
||
// Positions in the call stack when tracing to report the calling method.
|
||
//
|
||
// Start at the bottom of the stack before the package-name cache is primed.
|
||
minimumCallerDepth = 1
|
||
|
||
// Used for caller information initialisation
|
||
callerInitOnce sync.Once
|
||
)
|
||
|
||
const (
|
||
maximumCallerDepth int = 25
|
||
knownLogrusFrames int = 4
|
||
)
|
||
|
||
// ErrorKey defines the key when adding errors using [WithError], [Logger.WithError].
|
||
var ErrorKey = "error"
|
||
|
||
// Entry represents a single log event. It may be either an intermediate
|
||
// entry (created via WithField(s), WithContext, etc.) or a final entry
|
||
// that is emitted when one of the level methods (Trace, Debug, Info,
|
||
// Warn, Error, Fatal, Panic) is called.
|
||
//
|
||
// An Entry always belongs to a Logger. A nil Logger is invalid and will
|
||
// cause a panic when the entry is logged. Use [NewEntry] or Logger methods
|
||
// to construct entries.
|
||
//
|
||
// Entries are safe to reuse for adding fields and may be passed around
|
||
// to avoid field duplication. Each log operation operates on a copy
|
||
// of the Entry’s data to avoid mutation during formatting.
|
||
//
|
||
//nolint:recvcheck // Entry methods intentionally use both pointer and value receivers.
|
||
type Entry struct {
|
||
// Logger is the Logger that owns this entry and is responsible for
|
||
// formatting, hooks, and output. It must not be nil. An Entry without
|
||
// a Logger is invalid and will panic when logged.
|
||
Logger *Logger
|
||
|
||
// Data contains all user-defined fields attached to this entry.
|
||
Data Fields
|
||
|
||
// Time is the timestamp for the log event. If zero when the entry is
|
||
// logged, it defaults to the current time.
|
||
Time time.Time
|
||
|
||
// Level is the severity of the log entry. It is set when the entry
|
||
// is fired and reflects the level used for that log call.
|
||
Level Level
|
||
|
||
// Caller contains the calling method information.
|
||
//
|
||
// When [Logger.ReportCaller] is enabled, Caller is populated automatically at
|
||
// log time if it is nil. Hooks and formatters may inspect Caller.
|
||
//
|
||
// Applications generally should not modify Caller unless they intentionally
|
||
// want to provide custom caller information.
|
||
Caller *runtime.Frame
|
||
|
||
// Message is the log message supplied to one of the logging methods
|
||
// (Trace, Debug, Info, Warn, Error, Fatal, or Panic). It is set when
|
||
// the entry is logged.
|
||
Message string
|
||
|
||
// Buffer is a reusable buffer provided to the formatter. It is set
|
||
// before formatting in the normal log path; when nil, formatters
|
||
// allocate their own.
|
||
Buffer *bytes.Buffer
|
||
|
||
// Context carries user-provided context for hooks and formatters.
|
||
Context context.Context
|
||
|
||
// err contains internal field-formatting errors.
|
||
err string
|
||
}
|
||
|
||
// NewEntry creates a new [Entry] associated with the provided Logger.
|
||
// The logger must not be nil. Passing a nil logger results in a
|
||
// panic when a logging method (e.g., [Entry.Info], [Entry.Error], etc.)
|
||
// is called.
|
||
func NewEntry(logger *Logger) *Entry {
|
||
return &Entry{
|
||
Logger: logger,
|
||
// Reserve default predefined fields and a little extra room.
|
||
Data: make(Fields, defaultFields+3),
|
||
}
|
||
}
|
||
|
||
// Dup creates a copy of the entry for further modification.
|
||
//
|
||
// Data is cloned to avoid mutating the original entry. Other fields
|
||
// (Logger, Time, Context, etc.) are copied by value.
|
||
func (entry *Entry) Dup() *Entry {
|
||
dup := entry.dup()
|
||
dup.Data = maps.Clone(entry.Data)
|
||
return dup
|
||
}
|
||
|
||
// dup copies the entry fields shared by derived entries except Data, which
|
||
// callers must copy or initialize as appropriate for their use.
|
||
func (entry *Entry) dup() *Entry {
|
||
return &Entry{
|
||
Logger: entry.Logger,
|
||
Time: entry.Time,
|
||
Caller: entry.Caller,
|
||
Context: entry.Context,
|
||
err: entry.err,
|
||
}
|
||
}
|
||
|
||
// Bytes returns the bytes representation of this entry from the formatter.
|
||
func (entry *Entry) Bytes() ([]byte, error) {
|
||
// Snapshot the formatter under the lock to protect against concurrent
|
||
// SetFormatter calls, then release the lock before formatting.
|
||
// This avoids a data race and prevents a deadlock if Format() triggers
|
||
// reentrant logging (e.g., a field's MarshalJSON calls logrus).
|
||
//
|
||
// See:
|
||
//
|
||
// - https://github.com/sirupsen/logrus/issues/1440
|
||
// - https://github.com/sirupsen/logrus/issues/1448
|
||
entry.Logger.mu.Lock()
|
||
formatter := entry.Logger.Formatter
|
||
entry.Logger.mu.Unlock()
|
||
|
||
return formatter.Format(entry)
|
||
}
|
||
|
||
// String returns the string representation from the reader and ultimately the
|
||
// formatter.
|
||
func (entry *Entry) String() (string, error) {
|
||
serialized, err := entry.Bytes()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
str := string(serialized)
|
||
return str, nil
|
||
}
|
||
|
||
// WithError adds an error as single field (using the key defined in [ErrorKey])
|
||
// to the Entry.
|
||
func (entry *Entry) WithError(err error) *Entry {
|
||
return entry.WithField(ErrorKey, err)
|
||
}
|
||
|
||
// WithContext adds a context to the Entry.
|
||
func (entry *Entry) WithContext(ctx context.Context) *Entry {
|
||
dup := entry.dup()
|
||
dup.Data = maps.Clone(entry.Data)
|
||
dup.Context = ctx
|
||
return dup
|
||
}
|
||
|
||
// WithField adds a single field to the Entry.
|
||
func (entry *Entry) WithField(key string, value any) *Entry {
|
||
dup := entry.dup()
|
||
dup.Data = maps.Clone(entry.Data)
|
||
dup.addField(key, value)
|
||
return dup
|
||
}
|
||
|
||
// WithFields adds a map of fields to the Entry.
|
||
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 {
|
||
dup.addField(key, value)
|
||
}
|
||
return dup
|
||
}
|
||
|
||
// WithTime overrides the time of the Entry.
|
||
func (entry *Entry) WithTime(t time.Time) *Entry {
|
||
dup := entry.dup()
|
||
dup.Data = maps.Clone(entry.Data)
|
||
dup.Time = t
|
||
return dup
|
||
}
|
||
|
||
func (entry *Entry) addField(key string, value any) {
|
||
if _, ok := value.(error); !ok {
|
||
t := reflect.TypeOf(value)
|
||
if t != nil && (t.Kind() == reflect.Func || t.Kind() == reflect.Pointer && t.Elem().Kind() == reflect.Func) {
|
||
if entry.err != "" {
|
||
entry.err += ", skipping unsupported field " + strconv.Quote(key)
|
||
} else {
|
||
entry.err = "skipping unsupported field " + strconv.Quote(key)
|
||
}
|
||
return
|
||
}
|
||
}
|
||
|
||
if entry.Data == nil {
|
||
entry.Data = make(Fields, 1)
|
||
}
|
||
entry.Data[key] = value
|
||
}
|
||
|
||
// getPackageName reduces a fully qualified function name to the package name
|
||
// There really ought to be a better way...
|
||
func getPackageName(f string) string {
|
||
for {
|
||
lastPeriod := strings.LastIndex(f, ".")
|
||
lastSlash := strings.LastIndex(f, "/")
|
||
if lastPeriod > lastSlash {
|
||
f = f[:lastPeriod]
|
||
} else {
|
||
break
|
||
}
|
||
}
|
||
|
||
return f
|
||
}
|
||
|
||
// getCaller retrieves the name of the first non-logrus calling function
|
||
func getCaller() *runtime.Frame {
|
||
// cache this package's fully-qualified name
|
||
callerInitOnce.Do(func() {
|
||
pcs := make([]uintptr, maximumCallerDepth)
|
||
_ = runtime.Callers(0, pcs)
|
||
|
||
// dynamic get the package name and the minimum caller depth
|
||
for i := range maximumCallerDepth {
|
||
funcName := runtime.FuncForPC(pcs[i]).Name()
|
||
if strings.Contains(funcName, "getCaller") {
|
||
logrusPackage = getPackageName(funcName)
|
||
break
|
||
}
|
||
}
|
||
|
||
minimumCallerDepth = knownLogrusFrames
|
||
})
|
||
|
||
// Restrict the lookback frames to avoid runaway lookups
|
||
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 the caller isn't part of this package, we're done
|
||
if pkg != logrusPackage {
|
||
return &f
|
||
}
|
||
}
|
||
|
||
// if we got here, we failed to find the caller's context
|
||
return nil
|
||
}
|
||
|
||
// HasCaller reports whether this Entry contains caller information.
|
||
//
|
||
// Caller may be set explicitly, or populated at log time when
|
||
// [Logger.ReportCaller] is enabled.
|
||
//
|
||
// Deprecated: use [Entry.Caller] != nil instead.
|
||
//
|
||
//go:fix inline
|
||
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...))
|
||
}
|
||
|
||
// logln uses Sprintln for multiple arguments to preserve Println-style
|
||
// spacing between args, then trims the trailing newline.
|
||
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] // Trim the newline added by Sprintln; logging adds its own.
|
||
entry.log(level, panicAfter, msg)
|
||
}
|
||
|
||
// log writes msg at level. If panicAfter is true, it panics with the fully
|
||
// populated entry after hooks and output have completed.
|
||
//
|
||
// The explicit flag keeps panic behavior limited to Panic, Panicf, and
|
||
// Panicln while avoiding a return value used only as the panic value.
|
||
// See #1283 and commits f96066e and 5f8c666.
|
||
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()
|
||
|
||
// Preserve explicitly set caller information.
|
||
if reportCaller && newEntry.Caller == nil {
|
||
newEntry.Caller = getCaller()
|
||
}
|
||
|
||
// Select hooks based on the level for this log call. Hooks receive the
|
||
// Entry and may mutate it, but that does not affect which hooks are
|
||
// fired for this event.
|
||
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
|
||
|
||
// Panic here so the panic value contains the fully populated entry without
|
||
// requiring log to return it to the caller.
|
||
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() {
|
||
// Snapshot the formatter under the lock to protect against concurrent
|
||
// SetFormatter calls, then release the lock before formatting.
|
||
// This avoids a deadlock when Format() triggers reentrant logging (e.g.,
|
||
// a field's MarshalJSON calls logrus). See #1448, #1440.
|
||
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
|
||
}
|
||
|
||
// Re-acquire the lock to serialize writes to the underlying io.Writer.
|
||
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)
|
||
}
|
||
}
|
||
|
||
// Log logs a message at the specified level.
|
||
//
|
||
// Using Log with [PanicLevel] or [FatalLevel] intentionally does not
|
||
// trigger a panic or exit. Log treats the level as logging severity only;
|
||
// use [Entry.Panic] or [Entry.Fatal] when those side effects are desired.
|
||
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...)
|
||
}
|
||
}
|
||
|
||
// Entry Printf family functions
|
||
|
||
// Logf logs a formatted message at the specified level.
|
||
//
|
||
// Using Logf with [PanicLevel] or [FatalLevel] intentionally does not
|
||
// trigger a panic or exit. Logf treats the level as logging severity only;
|
||
// use [Entry.Panicf] or [Entry.Fatalf] when those side effects are desired.
|
||
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...)
|
||
}
|
||
}
|
||
|
||
// Entry Println family functions
|
||
|
||
// Logln logs a message at the specified level with Println-style spacing.
|
||
//
|
||
// Using Logln with [PanicLevel] or [FatalLevel] intentionally does not
|
||
// trigger a panic or exit. Logln treats the level as logging severity only;
|
||
// use [Entry.Panicln] or [Entry.Fatalln] when those side effects are desired.
|
||
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...)
|
||
}
|
||
}
|
||
|
||
// sprint is fmt.Sprint with fast paths for zero or one string argument.
|
||
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...)
|
||
}
|