Golang 日志处理:使用 Sentry 与 Zap 实现自动错误捕捉

文章目录

在项目中,我们通常会记录 Error 级别的日志,以便于追踪和排查问题。然而,依赖于日志采集的方式,在面对偶发性或非必现的错误时,我们依然很难及时发现和响应。尽管目前的告警机制主要基于指标的阈值设置,对于一些不易察觉的错误仍然存在盲点。

使用 Sentry 可以有效解决这些问题。通过在可能产生错误的代码块中,不仅记录 Error 日志,还能将严重的错误事件上报至 Sentry。Sentry 会详细记录相关问题并提供实时告警通知,从而大幅提高错误监控和处理的效率。

在 Golang 项目中,选择 zap 作为日志组件,综合考虑了易用性、性能以及流行程度。zap 能够高效地记录日志,并通过其丰富的功能满足各种需求。

错误日志的分散管理

项目中的 Error 日志往往散落在各个模块。对于新编写的代码,可以在打印错误的同时,添加 Sentry 的上报逻辑。而对于现有项目,则需要全局搜索 Error 日志的打印位置,逐一手动添加代码,这种方法显然不够高效。因此,如何实现日志的自动上报成为了一个关键问题。

通过阅读 zap 源码,可以发现 zap 支持大多数日志组件所具有的 Hooks 回调和自定义 Core 的机制,便于在日志打印后添加额外的操作。可以利用这一机制,设置在打印 Error 及以上级别的日志时,自动上报至 Sentry。

Zap 的 Hooks 和 Core

zap 的 Hooks 中存在一个限制:在回调函数中无法获取 Fields(额外信息),只涉及 Entry 的读取。Entry 只包含了日志的基本信息,如 Level、Time、LoggerName、Message、Caller 和 Stack 等。因此,使用 zap 的 Core 机制更为合适,允许我们在记录日志时自动处理这些额外信息。

相关阅读推荐:深入解析 Golang Zap Logger 的源码与日志打印流程

示例:Zap Hooks 的使用

以下是一个简单的 Hook 示例,用于记录消息长度:

package main

import (
	"fmt"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// DemoHook 生成示例 Hook 函数
func DemoHook() func(zapcore.Entry) error {
	return func(e zapcore.Entry) error {
		if e.Level < zapcore.ErrorLevel {
			return nil
		}
		fmt.Printf("message length:%d\n", len(e.Message))
		return nil
	}
}

func main() {
	logger := zap.NewExample()
	logger = logger.WithOptions(zap.Hooks(DemoHook()))
	logger.Info("123456789")  // 不打印长度信息
	logger.Error("123456789")  // 打印长度信息
}

执行结果:

{"level":"info","msg":"123456789"}
{"level":"error","msg":"123456789"}
message length:9

可以看到,定义 Hook 函数时无法接收 Fields 参数,因此在需要 Fields 的情况下,应该实现自定义 Core。

实现 Sentry Core

自定义 Core 需要实现 zapcore.Core 接口的五个方法。下面是一个 Sentry Core 的实现示例:

package main

import (
	"errors"
	"fmt"
	"time"

	"github.com/getsentry/sentry-go"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

// 将 zap 的 Level 转换为 Sentry 的 Level
func sentryLevel(lvl zapcore.Level) sentry.Level {
	switch lvl {
	case zapcore.DebugLevel:
		return sentry.LevelDebug
	case zapcore.InfoLevel:
		return sentry.LevelInfo
	case zapcore.WarnLevel:
		return sentry.LevelWarning
	case zapcore.ErrorLevel:
		return sentry.LevelError
	default:
		return sentry.LevelFatal
	}
}

// SentryCoreConfig 定义 Sentry Core 的配置参数
type SentryCoreConfig struct {
	Tags              map[string]string
	DisableStacktrace bool
	Level             zapcore.Level
	FlushTimeout      time.Duration
	Hub               *sentry.Hub
}

// sentryCore 实现 Core 接口
type sentryCore struct {
	client               *sentry.Client
	cfg                  *SentryCoreConfig
	zapcore.LevelEnabler
	flushTimeout         time.Duration
	fields map[string]interface{}
}

// With 实现 Core 接口的 With 方法
func (c *sentryCore) With(fs []zapcore.Field) zapcore.Core {
	// 处理 Fields
	return &sentryCore{client: c.client, cfg: c.cfg, fields: mergeFields(c.fields, fs)}
}

// Check 和 Write 方法实现
func (c *sentryCore) Check(ent zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
	if c.cfg.Level.Enabled(ent.Level) {
		return ce.AddCore(ent, c)
	}
	return ce
}

func (c *sentryCore) Write(ent zapcore.Entry, fs []zapcore.Field) error {
	clone := c.With(fs)
	event := createSentryEvent(ent, clone.fields, c.cfg.Tags)
	return c.client.CaptureEvent(event, nil, c.cfg.Hub)
}

// NewSentryCore 生成 Core 对象
func NewSentryCore(cfg SentryCoreConfig, sentryClient *sentry.Client) zapcore.Core {
	return &sentryCore{
		client:       sentryClient,
		cfg:          &cfg,
		LevelEnabler: cfg.Level,
		flushTimeout: cfg.FlushTimeout,
		fields:       make(map[string]interface{}),
	}
}

// main 函数
func main() {
	logger := zap.NewExample()
	sentryClient, _ := sentry.NewClient(sentry.ClientOptions{Dsn: "your_sentry_dsn"})
	cfg := SentryCoreConfig{Level: zap.ErrorLevel, Tags: map[string]string{"source": "demo"}}
	sCore := NewSentryCore(cfg, sentryClient)
	logger = logger.WithOptions(zap.WrapCore(func(core zapcore.Core) zapcore.Core {
		return zapcore.NewTee(core, sCore)
	}))

	logger.Info("info log")
	logger.Error("this log will be auto captured by sentry", zap.String("f1", "v1"), zap.Error(errors.New("this is an error")))
	time.Sleep(2 * time.Second)
}

结论

通过将 Sentry 与 zap 日志结合使用,可以有效提升 Golang 项目的错误监控能力。自动上报机制不仅减少了开发者的工作量,还能实时捕捉重要错误,确保系统的稳定性。


也可以看看