| 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] |
| } |