You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go中slog日志包装器AddSource显示错误调用方信息求助

解决slog包装器中AddSource显示错误调用位置的问题

问题原因

当基于slog实现日志包装器并开启AddSource后,日志输出的调用方信息指向包装器文件(如slog_logger.go)而非实际调用日志方法的位置。这是因为slog默认从调用其Log/Info/Error等核心方法的位置开始向上查找调用栈,而你的包装方法(Debugf/Infof/Errorf)相当于在实际调用代码和slog核心逻辑之间多了一层,导致默认的调用栈查找停在了包装器内部。

解决方案:设置调用栈跳过深度

slog提供了WithCallerSkip(n int)选项,用于指定需要跳过的调用栈层数,让slog定位到更上层的实际调用位置。只需修改包装器的日志方法,在调用slog核心方法时传入该选项,跳过包装器自身的调用层即可。

修改后的完整代码

package logger

import (
    "context"
    "fmt"
    "sync"

    "gopkg.in/natefinch/lumberjack.v2"
    "golang.org/x/exp/slog"
)

type SlogLogger struct {
    *slog.Logger
    logFile *lumberjack.Logger
}

var factory = &LoggerFactory{
    loggers: make(map[string]*SlogLogger),
}

type LoggerFactory struct {
    mu      sync.Mutex
    loggers map[string]*SlogLogger
}

type LoggerConfig struct {
    LogFilePath string
    LogFormat   string
}

// TODO: Read all the values from config
func NewDefaultLogger() *SlogLogger {
    logFilePath := "./cs_runner.log"
    factory.mu.Lock()
    defer factory.mu.Unlock()

    // Check if a logger instance for this log file already exists
    if logger, ok := factory.loggers[logFilePath]; ok {
        return logger
    }
    lumberjack := &lumberjack.Logger{
        Filename:   logFilePath,
        MaxSize:    1, // megabytes
        MaxBackups: 5,
        MaxAge:     28,   //days
        Compress:   true, // disabled by default
    }
    l := slog.New(slog.NewTextHandler(lumberjack, &slog.HandlerOptions{
        Level:     slog.LevelDebug,
        AddSource: true,
    }))
    logger := &SlogLogger{l, lumberjack}
    factory.loggers[logFilePath] = logger
    return logger
}

func (l *SlogLogger) Close() {
    // TODO: Check for any other cleanup activities
    l.logFile.Close()
}

func (l *SlogLogger) Debugf(format string, args ...interface{}) {
    msg := fmt.Sprintf(format, args...)
    l.Log(context.Background(), slog.LevelDebug, msg, slog.WithCallerSkip(1))
}

func (l *SlogLogger) Infof(format string, args ...interface{}) {
    msg := fmt.Sprintf(format, args...)
    l.Log(context.Background(), slog.LevelInfo, msg, slog.WithCallerSkip(1))
}

func (l *SlogLogger) Errorf(format string, args ...interface{}) {
    msg := fmt.Sprintf(format, args...)
    l.Log(context.Background(), slog.LevelError, msg, slog.WithCallerSkip(1))
}

关键修改点

  1. 引入context和fmt包:fmt用于格式化带参数的日志消息,context是slog.Log方法的必填参数。
  2. 重写Debugf/Infof/Errorf方法:
    • 先用fmt.Sprintf处理带占位符的日志格式与参数
    • 调用slog.Logger的Log方法,传入对应日志级别,并通过slog.WithCallerSkip(1)指定跳过当前包装方法的调用层,让slog定位到实际调用日志方法的代码位置。

效果验证

修改后,日志输出的source字段将指向实际调用Debugf/Infof/Errorf的文件和行号,示例如下:

time=2023-08-27T10:41:36.805+05:30 level=INFO source=/home/user1/myproject/main.go:25 msg="This is a Debug log message"

注意事项

如果包装器存在多层嵌套(比如包装器又调用了其他中间方法),需要根据实际层数调整WithCallerSkip的数值,确保跳过所有中间层,准确定位到最上层的实际调用代码。

内容的提问来源于stack exchange,提问作者pavan kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 15:47:05