Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
431 lines
10 KiB
431 lines
10 KiB
package logrus |
|
|
|
import ( |
|
"bytes" |
|
"context" |
|
"fmt" |
|
"os" |
|
"reflect" |
|
"runtime" |
|
"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 |
|
minimumCallerDepth int |
|
|
|
// Used for caller information initialisation |
|
callerInitOnce sync.Once |
|
) |
|
|
|
const ( |
|
maximumCallerDepth int = 25 |
|
knownLogrusFrames int = 4 |
|
) |
|
|
|
func init() { |
|
// start at the bottom of the stack before the package-name cache is primed |
|
minimumCallerDepth = 1 |
|
} |
|
|
|
// Defines the key when adding errors using WithError. |
|
var ErrorKey = "error" |
|
|
|
// An entry is the final or intermediate Logrus logging entry. It contains all |
|
// the fields passed with WithField{,s}. It's finally logged when Trace, Debug, |
|
// Info, Warn, Error, Fatal or Panic is called on it. These objects can be |
|
// reused and passed around as much as you wish to avoid field duplication. |
|
type Entry struct { |
|
Logger *Logger |
|
|
|
// Contains all the fields set by the user. |
|
Data Fields |
|
|
|
// Time at which the log entry was created |
|
Time time.Time |
|
|
|
// Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic |
|
// This field will be set on entry firing and the value will be equal to the one in Logger struct field. |
|
Level Level |
|
|
|
// Calling method, with package name |
|
Caller *runtime.Frame |
|
|
|
// Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic |
|
Message string |
|
|
|
// When formatter is called in entry.log(), a Buffer may be set to entry |
|
Buffer *bytes.Buffer |
|
|
|
// Contains the context set by the user. Useful for hook processing etc. |
|
Context context.Context |
|
|
|
// err may contain a field formatting error |
|
err string |
|
} |
|
|
|
func NewEntry(logger *Logger) *Entry { |
|
return &Entry{ |
|
Logger: logger, |
|
// Default is three fields, plus one optional. Give a little extra room. |
|
Data: make(Fields, 6), |
|
} |
|
} |
|
|
|
func (entry *Entry) Dup() *Entry { |
|
data := make(Fields, len(entry.Data)) |
|
for k, v := range entry.Data { |
|
data[k] = v |
|
} |
|
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err} |
|
} |
|
|
|
// Returns the bytes representation of this entry from the formatter. |
|
func (entry *Entry) Bytes() ([]byte, error) { |
|
return entry.Logger.Formatter.Format(entry) |
|
} |
|
|
|
// 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 |
|
} |
|
|
|
// Add 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) |
|
} |
|
|
|
// Add a context to the Entry. |
|
func (entry *Entry) WithContext(ctx context.Context) *Entry { |
|
dataCopy := make(Fields, len(entry.Data)) |
|
for k, v := range entry.Data { |
|
dataCopy[k] = v |
|
} |
|
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx} |
|
} |
|
|
|
// Add a single field to the Entry. |
|
func (entry *Entry) WithField(key string, value interface{}) *Entry { |
|
return entry.WithFields(Fields{key: value}) |
|
} |
|
|
|
// Add a map of fields to the Entry. |
|
func (entry *Entry) WithFields(fields Fields) *Entry { |
|
data := make(Fields, len(entry.Data)+len(fields)) |
|
for k, v := range entry.Data { |
|
data[k] = v |
|
} |
|
fieldErr := entry.err |
|
for k, v := range fields { |
|
isErrField := false |
|
if t := reflect.TypeOf(v); t != nil { |
|
switch { |
|
case t.Kind() == reflect.Func, t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Func: |
|
isErrField = true |
|
} |
|
} |
|
if isErrField { |
|
tmp := fmt.Sprintf("can not add field %q", k) |
|
if fieldErr != "" { |
|
fieldErr = entry.err + ", " + tmp |
|
} else { |
|
fieldErr = tmp |
|
} |
|
} else { |
|
data[k] = v |
|
} |
|
} |
|
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context} |
|
} |
|
|
|
// Overrides the time of the Entry. |
|
func (entry *Entry) WithTime(t time.Time) *Entry { |
|
dataCopy := make(Fields, len(entry.Data)) |
|
for k, v := range entry.Data { |
|
dataCopy[k] = v |
|
} |
|
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: t, err: entry.err, Context: entry.Context} |
|
} |
|
|
|
// getPackageName reduces a fully qualified function name to the package name |
|
// There really ought to be 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 := 0; i < maximumCallerDepth; i++ { |
|
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 //nolint:scopelint |
|
} |
|
} |
|
|
|
// if we got here, we failed to find the caller's context |
|
return nil |
|
} |
|
|
|
func (entry Entry) HasCaller() (has bool) { |
|
return entry.Logger != nil && |
|
entry.Logger.ReportCaller && |
|
entry.Caller != nil |
|
} |
|
|
|
func (entry *Entry) log(level Level, msg string) { |
|
var buffer *bytes.Buffer |
|
|
|
newEntry := entry.Dup() |
|
|
|
if newEntry.Time.IsZero() { |
|
newEntry.Time = time.Now() |
|
} |
|
|
|
newEntry.Level = level |
|
newEntry.Message = msg |
|
|
|
newEntry.Logger.mu.Lock() |
|
reportCaller := newEntry.Logger.ReportCaller |
|
newEntry.Logger.mu.Unlock() |
|
|
|
if reportCaller { |
|
newEntry.Caller = getCaller() |
|
} |
|
|
|
newEntry.fireHooks() |
|
|
|
buffer = getBuffer() |
|
defer func() { |
|
newEntry.Buffer = nil |
|
putBuffer(buffer) |
|
}() |
|
buffer.Reset() |
|
newEntry.Buffer = buffer |
|
|
|
newEntry.write() |
|
|
|
newEntry.Buffer = nil |
|
|
|
// To avoid Entry#log() returning a value that only would make sense for |
|
// panic() to use in Entry#Panic(), we avoid the allocation by checking |
|
// directly here. |
|
if level <= PanicLevel { |
|
panic(newEntry) |
|
} |
|
} |
|
|
|
func (entry *Entry) fireHooks() { |
|
var tmpHooks LevelHooks |
|
entry.Logger.mu.Lock() |
|
tmpHooks = make(LevelHooks, len(entry.Logger.Hooks)) |
|
for k, v := range entry.Logger.Hooks { |
|
tmpHooks[k] = v |
|
} |
|
entry.Logger.mu.Unlock() |
|
|
|
err := tmpHooks.Fire(entry.Level, entry) |
|
if err != nil { |
|
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err) |
|
} |
|
} |
|
|
|
func (entry *Entry) write() { |
|
serialized, err := entry.Logger.Formatter.Format(entry) |
|
if err != nil { |
|
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err) |
|
return |
|
} |
|
entry.Logger.mu.Lock() |
|
defer entry.Logger.mu.Unlock() |
|
if _, err := entry.Logger.Out.Write(serialized); err != nil { |
|
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err) |
|
} |
|
} |
|
|
|
func (entry *Entry) Log(level Level, args ...interface{}) { |
|
if entry.Logger.IsLevelEnabled(level) { |
|
entry.log(level, fmt.Sprint(args...)) |
|
} |
|
} |
|
|
|
func (entry *Entry) Trace(args ...interface{}) { |
|
entry.Log(TraceLevel, args...) |
|
} |
|
|
|
func (entry *Entry) Debug(args ...interface{}) { |
|
entry.Log(DebugLevel, args...) |
|
} |
|
|
|
func (entry *Entry) Print(args ...interface{}) { |
|
entry.Info(args...) |
|
} |
|
|
|
func (entry *Entry) Info(args ...interface{}) { |
|
entry.Log(InfoLevel, args...) |
|
} |
|
|
|
func (entry *Entry) Warn(args ...interface{}) { |
|
entry.Log(WarnLevel, args...) |
|
} |
|
|
|
func (entry *Entry) Warning(args ...interface{}) { |
|
entry.Warn(args...) |
|
} |
|
|
|
func (entry *Entry) Error(args ...interface{}) { |
|
entry.Log(ErrorLevel, args...) |
|
} |
|
|
|
func (entry *Entry) Fatal(args ...interface{}) { |
|
entry.Log(FatalLevel, args...) |
|
entry.Logger.Exit(1) |
|
} |
|
|
|
func (entry *Entry) Panic(args ...interface{}) { |
|
entry.Log(PanicLevel, args...) |
|
} |
|
|
|
// Entry Printf family functions |
|
|
|
func (entry *Entry) Logf(level Level, format string, args ...interface{}) { |
|
if entry.Logger.IsLevelEnabled(level) { |
|
entry.Log(level, fmt.Sprintf(format, args...)) |
|
} |
|
} |
|
|
|
func (entry *Entry) Tracef(format string, args ...interface{}) { |
|
entry.Logf(TraceLevel, format, args...) |
|
} |
|
|
|
func (entry *Entry) Debugf(format string, args ...interface{}) { |
|
entry.Logf(DebugLevel, format, args...) |
|
} |
|
|
|
func (entry *Entry) Infof(format string, args ...interface{}) { |
|
entry.Logf(InfoLevel, format, args...) |
|
} |
|
|
|
func (entry *Entry) Printf(format string, args ...interface{}) { |
|
entry.Infof(format, args...) |
|
} |
|
|
|
func (entry *Entry) Warnf(format string, args ...interface{}) { |
|
entry.Logf(WarnLevel, format, args...) |
|
} |
|
|
|
func (entry *Entry) Warningf(format string, args ...interface{}) { |
|
entry.Warnf(format, args...) |
|
} |
|
|
|
func (entry *Entry) Errorf(format string, args ...interface{}) { |
|
entry.Logf(ErrorLevel, format, args...) |
|
} |
|
|
|
func (entry *Entry) Fatalf(format string, args ...interface{}) { |
|
entry.Logf(FatalLevel, format, args...) |
|
entry.Logger.Exit(1) |
|
} |
|
|
|
func (entry *Entry) Panicf(format string, args ...interface{}) { |
|
entry.Logf(PanicLevel, format, args...) |
|
} |
|
|
|
// Entry Println family functions |
|
|
|
func (entry *Entry) Logln(level Level, args ...interface{}) { |
|
if entry.Logger.IsLevelEnabled(level) { |
|
entry.Log(level, entry.sprintlnn(args...)) |
|
} |
|
} |
|
|
|
func (entry *Entry) Traceln(args ...interface{}) { |
|
entry.Logln(TraceLevel, args...) |
|
} |
|
|
|
func (entry *Entry) Debugln(args ...interface{}) { |
|
entry.Logln(DebugLevel, args...) |
|
} |
|
|
|
func (entry *Entry) Infoln(args ...interface{}) { |
|
entry.Logln(InfoLevel, args...) |
|
} |
|
|
|
func (entry *Entry) Println(args ...interface{}) { |
|
entry.Infoln(args...) |
|
} |
|
|
|
func (entry *Entry) Warnln(args ...interface{}) { |
|
entry.Logln(WarnLevel, args...) |
|
} |
|
|
|
func (entry *Entry) Warningln(args ...interface{}) { |
|
entry.Warnln(args...) |
|
} |
|
|
|
func (entry *Entry) Errorln(args ...interface{}) { |
|
entry.Logln(ErrorLevel, args...) |
|
} |
|
|
|
func (entry *Entry) Fatalln(args ...interface{}) { |
|
entry.Logln(FatalLevel, args...) |
|
entry.Logger.Exit(1) |
|
} |
|
|
|
func (entry *Entry) Panicln(args ...interface{}) { |
|
entry.Logln(PanicLevel, args...) |
|
} |
|
|
|
// Sprintlnn => Sprint no newline. This is to get the behavior of how |
|
// fmt.Sprintln where spaces are always added between operands, regardless of |
|
// their type. Instead of vendoring the Sprintln implementation to spare a |
|
// string allocation, we do the simplest thing. |
|
func (entry *Entry) sprintlnn(args ...interface{}) string { |
|
msg := fmt.Sprintln(args...) |
|
return msg[:len(msg)-1] |
|
}
|
|
|