package logger import ( "runtime" "strings" "sync" "github.com/sirupsen/logrus" ) 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 = 8 ) func init() { // start at the bottom of the stack before the package-name cache is primed minimumCallerDepth = 1 } // 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 && pkg != "github.com/sirupsen/logrus" { return &f //nolint:scopelint } } // if we got here, we failed to find the caller's context return nil } // CallerHook is a hook designed for dealing with logs in test scenarios. type CallerHook struct{} // NewCallerHook installs a test hook for a given local logger. func NewCallerHook(logger *logrus.Logger) *CallerHook { hook := new(CallerHook) logger.Hooks.Add(hook) return hook } func (t *CallerHook) Fire(e *logrus.Entry) error { if e.HasCaller() { e.Caller = getCaller() } return nil } func (t *CallerHook) Levels() []logrus.Level { return logrus.AllLevels }