在项目中发生了错误时我们都会打印Error级别的日志,但是即使有日志采集,在对发生Error时的告警通知和信息采集都不一定能快速且完善,目前对日志的告警也只是限于一些指标上的阈值告警,对于一些偶发或者非必现的Error其实我们还是很难及时发现,使用Sentry可以解决这类痛点,在可能产生Error的地方我们除了打印Error日志,同时还会对Error事件进行Sentry上报,Sentry会记录详细的相关issue并告警通知。
在golang的项目中我对易用性和性能及流行程度的综合考虑下选择使用zap作为日志组件来打印日志。项目的Error日志必然是散落到各个角落,对于新写的代码,你可以在打Error的时候在加上一行Sentry的上报,对于已有的项目你可能需要全局搜索Error日志的打印再去人工添加代码,这样的操作显然是不科学的,必然有更好的方案。
经过对zap源码阅读可以知道zap支持大多数其他日志组件都支持的Hooks回调和自定义Core的方式添加日志打印后的额外操作。那么我们可以使用这个机制,在打印日志时检测日志级别判断符合我们需要的Error以上级别的错误都自动上报的到Sentry。
在zap的Hooks中有一个限制,就是我们在zap的logger中通常会添加一些Fields来固定打印一些额外的信息,比如requestid,用户当前请求的id,服务进程id等,在Hooks的回调实现中,阅读源码会发现回调函数中不支持获取这些Fields,只涉及对Entry的读取,Entry即一条日志的基本信息包括Level、Time、LoggerName、 Message、Caller、Stack等信息,而Fields中的信息也都是我们所需要的,所以这里我不使用Hooks的方法来实现自动上报,而是使用zap的Core机制。
如果不需要Fields信息也可以使用Hooks来实现。Hook函数签名为:func(Entry) error
, 即接收一个zap的Entry作为参数,你可以在这个函数里面做你需要完成的任务后返回error即可,然后将Hook函数注册到Logger上即可。Hook函数最终也是通过实现一个自定义Core的方式来把这些注册的Hooks都执行。
这里贴一下Hooks在zap中的用法:
``` package main
import ( "fmt"
"go.uber.org/zap" "go.uber.org/zap/zapcore"
)
// DemoHook 用于生成一个示例Hook函数,Hook函数实现当前日志大于Error级别时打印Message的长度 // 这里使用函数的方法返回一个Hook函数的好处时外层函数可以根据需要传入一些参数供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 = logger.WithOptions(zap.Hooks(DemoHook())) // 将回调函数注册到logger生成一个新的logger logger.Info("123456789") // 不打印长度信息 logger.Error("123456789") // 打印长度信息 } ```
执行结果:
{"level":"info","msg":"123456789"}
{"level":"error","msg":"123456789"}
message length:9
可以看到定义Hook函数的时候无法接收到Fields的相关参数,所以需要Fields参数时,需要自定义Core。zap在执行最终日志打印的时候会将其logger中的所有core中的Write方法都遍历执行,因此我们只需要在自定义的Core中实现我们自己的操作即可,在这里需要实现对Sentry客户端的调用上报信息到Sentry,最后将core添加到logger中即可。
在zap中core是一个接口,所以自定义core需要实现其定义的5个方法,相对于Hooks的实现较为复杂。
以下是一个Sentry Core的实现和使用示例:
``` // Package main is a demo for zapcore for sentry capture error
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 case zapcore.DPanicLevel: return sentry.LevelFatal case zapcore.PanicLevel: return sentry.LevelFatal case zapcore.FatalLevel: return sentry.LevelFatal 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 sentrycore的Core结构体,用于实现Core接口 type sentryCore struct { client sentry.Client // sentry客户端 cfg SentryCoreConfig // core配置 zapcore.LevelEnabler // LevelEnabler接口 flushTimeout time.Duration // sentry上报的flush时间
fields map[string]interface{} // 保存Fields
}
// With接口方法的实际实现,对传入fields进行设置日志打印时的打印解析方式并添加到已有的fields中 func (c sentryCore) with(fs []zapcore.Field) sentryCore { // Copy our map. m := make(map[string]interface{}, len(c.fields)) for k, v := range c.fields { m[k] = v }
// Add fields to an in-memory encoder. enc := zapcore.NewMapObjectEncoder() for _, f := range fs { f.AddTo(enc) } // Merge the two maps. for k, v := range enc.Fields { m[k] = v } return &sentryCore{ client: c.client, cfg: c.cfg, fields: m, LevelEnabler: c.LevelEnabler, }
}
// With 实现Core接口的With方法 func (c *sentryCore) With(fs []zapcore.Field) zapcore.Core { return c.with(fs) }
// Check 实现Core接口的Check方法,只有大于在core配置中的的Level才会被打印 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 }
// Write 实现Core接口的Write方法,对sentry进行上报,Fields作为Extra信息上报 func (c *sentryCore) Write(ent zapcore.Entry, fs []zapcore.Field) error { clone := c.with(fs)
event := sentry.NewEvent() event.Message = ent.Message event.Timestamp = ent.Time.Unix() event.Level = sentryLevel(ent.Level) event.Platform = "demo" event.Extra = clone.fields event.Tags = c.cfg.Tags if !c.cfg.DisableStacktrace { trace := sentry.NewStacktrace() if trace != nil { event.Exception = []sentry.Exception{{ Type: ent.Message, Value: ent.Caller.TrimmedPath(), Stacktrace: trace, }} } } hub := c.cfg.Hub if hub == nil { hub = sentry.CurrentHub() } _ = c.client.CaptureEvent(event, nil, hub.Scope()) if ent.Level > zapcore.ErrorLevel { c.client.Flush(c.flushTimeout) } return nil
}
// Sync 实现Core接口的Sync方法 func (c *sentryCore) Sync() error { c.client.Flush(c.flushTimeout) return nil }
// NewSentryCore 生成Core对象 func NewSentryCore(cfg SentryCoreConfig, sentryClient *sentry.Client) zapcore.Core {
core := sentryCore{ client: sentryClient, cfg: &cfg, LevelEnabler: cfg.Level, flushTimeout: 3 * time.Second, fields: make(map[string]interface{}), } if cfg.FlushTimeout > 0 { core.flushTimeout = cfg.FlushTimeout } return &core
}
// 生成SentryCore对象并添加到Logger中 func main() { // 默认logger logger := zap.NewExample()
// sentrycore配置 cfg := SentryCoreConfig{ Level: zap.ErrorLevel, Tags: map[string]string{ "source": "demo", }, } // 生成sentry客户端 sentryClient, err := sentry.NewClient(sentry.ClientOptions{ Dsn: "http://abc:xyz@sentry.host.com/id", Debug: true, AttachStacktrace: true, }) if err != nil { fmt.Println(err) } // 生成sentryCore sCore := NewSentryCore(cfg, sentryClient) // 添加sentryCore到默认logger产生新的logger,使用该logger即可自动上报sentry 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 ia an error"))) time.Sleep(2 * time.Second) // sleep避免程序结束太快而导致上报失败
} ```
运行结果:
[Sentry] 2020/03/15 17:46:10 Integration installed: ContextifyFrames
[Sentry] 2020/03/15 17:46:10 Integration installed: Environment
[Sentry] 2020/03/15 17:46:10 Integration installed: Modules
[Sentry] 2020/03/15 17:46:10 Integration installed: IgnoreErrors
{"level":"info","msg":"info log"}
{"level":"error","msg":"this log will be auto captured by sentry","f1":"v1","error":"this ia an error"}
[Sentry] 2020/03/15 17:46:10 Sending error event [81e966c6573e4134b3d8b842ea0fb738] to sentry.host.com project: id
sentry页面信息:
网友207.*.*.53[Redmond]2021-04-19 12:06
网友116.*.*.215[火星]2021-04-19 12:06
网友216.*.*.226[Seattle]2021-04-19 11:55
网友66.*.*.85[Mountain View]2021-04-19 11:53
发表评论
亲~ 评论内容是必须的哟! o(∩_∩)o
昵称
邮箱
主页
评论