diff --git a/caller.go b/caller.go new file mode 100644 index 0000000..a7c53af --- /dev/null +++ b/caller.go @@ -0,0 +1,105 @@ +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 +} diff --git a/go.mod b/go.mod index dff1c37..80b0496 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,10 @@ go 1.17 require ( github.com/caarlos0/env/v6 v6.9.2 github.com/go-kit/kit v0.12.0 - github.com/lixh00/loki-client-go v1.0.1 + git.echol.cn/loser/loki-client-go v1.0.1 github.com/natefinch/lumberjack v2.0.0+incompatible github.com/prometheus/common v0.34.0 go.uber.org/zap v1.21.0 - gorm.io/driver/mysql v1.3.2 gorm.io/gorm v1.23.5 ) diff --git a/log/say.go b/log/say.go index ee76012..7e6e5fe 100644 --- a/log/say.go +++ b/log/say.go @@ -1,6 +1,9 @@ package log -import "go.uber.org/zap" +import ( + "encoding/json" + "go.uber.org/zap" +) // Debug uses fmt.Sprint to construct and log a message. func Debug(args ...interface{}) { @@ -73,3 +76,10 @@ func Fatalf(template string, args ...interface{}) { defer zap.S().Sync() zap.S().Fatalf(template, args...) } + +// Pretty provides pretty trace level logging +func Pretty(v ...interface{}) { + b, _ := json.MarshalIndent(v, "", " ") + defer zap.S().Sync() + zap.S().Info(string(b)) +} diff --git a/loki.go b/loki.go index 718b3e5..d4fa7d2 100644 --- a/loki.go +++ b/loki.go @@ -4,8 +4,8 @@ import ( "encoding/json" "errors" "fmt" + "git.echol.cn/loser/loki-client-go/loki" "github.com/go-kit/kit/log" - "github.com/lixh00/loki-client-go/loki" "github.com/prometheus/common/model" "go.uber.org/zap" "go.uber.org/zap/zapcore"