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

如何为Zap Logger的.Error()方法添加自定义逻辑?

问题描述

现有日志代码如下:

logger, _ := zap.NewProduction()
logger.Error("hello")

希望.Error()方法执行额外操作,比如将特定消息日志发送至邮箱、仅将错误日志发送至Telegram机器人等。

尝试使用钩子实现,但无法获取完整字段,示例代码如下:

core := zapcore.RegisterHooks(logger.Core(), func(e zapcore.Entry) error {
    if e.Message == "[Recovery from panic]" {
        teleBot.SendMessageToAllChatIDs(fmt.Sprintf(
            "log_name: %s\nlevel: %s\nts: %s\nmsg: %s\nstack: %s",
            e.LoggerName,
            e.Level.String(),
            e.Time.Format(time.RFC1123),
            e.Message,
            e.Stack,
        ))
    }
    return nil
})
解决方案

zap原生RegisterHooks提供的钩子仅接收zapcore.Entry,确实无法获取日志的完整结构化字段。要拿到完整字段并执行自定义操作,可通过以下两种方式实现:

方法1:自定义Core包装器

通过包装原有Core,在写入日志时同时获取Entry和字段,是最灵活的实现方式:

type HookedCore struct {
    zapcore.Core
    hook func(zapcore.Entry, []zapcore.Field) error
}

func (c *HookedCore) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcore.CheckedEntry {
    if c.Enabled(entry.Level) {
        return checked.AddCore(entry, c)
    }
    return checked
}

func (c *HookedCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
    // 先执行原有Core的日志写入逻辑
    if err := c.Core.Write(entry, fields); err != nil {
        return err
    }
    // 执行自定义钩子逻辑,这里能拿到完整的Entry和结构化字段
    return c.hook(entry, fields)
}

// 生成带自定义钩子的Core
func NewHookedCore(core zapcore.Core, hook func(zapcore.Entry, []zapcore.Field) error) zapcore.Core {
    return &HookedCore{Core: core, hook: hook}
}

使用示例:

// 初始化原有logger
logger, _ := zap.NewProduction()
// 包装Core并添加钩子逻辑
hookedCore := NewHookedCore(logger.Core(), func(entry zapcore.Entry, fields []zapcore.Field) error {
    // 过滤错误级别或特定消息
    if entry.Level == zapcore.ErrorLevel || entry.Message == "[Recovery from panic]" {
        // 格式化结构化字段为可读字符串
        fieldStr := ""
        for _, f := range fields {
            fieldStr += fmt.Sprintf("%s: %v\n", f.Key, f.Interface)
        }
        // 组装完整消息
        fullMsg := fmt.Sprintf(
            "日志名称: %s\n级别: %s\n时间: %s\n消息: %s\n堆栈: %s\n附加字段:\n%s",
            entry.LoggerName,
            entry.Level.String(),
            entry.Time.Format(time.RFC1123),
            entry.Message,
            entry.Stack,
            fieldStr,
        )
        // 发送Telegram消息
        teleBot.SendMessageToAllChatIDs(fullMsg)
        // 这里可添加发送邮箱的逻辑
    }
    return nil
})
// 用新Core创建logger
newLogger := logger.WithOptions(zap.WrapCore(func(_ zapcore.Core) zapcore.Core {
    return hookedCore
}))

// 调用Error时会自动触发钩子
newLogger.Error("请求处理失败", zap.String("user_id", "123"), zap.Int("status_code", 500))

方法2:封装SugaredLogger(适合简单场景)

如果不需要处理复杂的结构化字段,可直接封装自定义的Error方法:

type CustomLogger struct {
    *zap.SugaredLogger
}

func (l *CustomLogger) Errorf(format string, args ...interface{}) {
    // 先执行原有日志记录
    l.SugaredLogger.Errorf(format, args...)
    // 执行额外操作,比如发送消息到Telegram
    msg := fmt.Sprintf(format, args...)
    teleBot.SendMessageToAllChatIDs(msg)
    // 这里可添加发送邮箱的逻辑
}

// 使用示例
logger, _ := zap.NewProduction()
customLogger := &CustomLogger{logger.Sugar()}
customLogger.Errorf("用户[%s]登录失败", "foo")

注意:方法1适合需要处理完整结构化字段的场景,方法2适合快速实现简单的额外操作。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 22:20:56