因为recover只对当前goroutine有效,微服务中http handler、消息消费等大量异步goroutine若panic未显式捕获,将静默失败、丢失堆栈、监控失效;sentry需手动captureexception并注入trace_id,prometheus需主动记录结构化error指标,zap日志须含error_code和trace_id才支持聚合与追踪。

Go 微服务里 panic 为什么不能靠 recover 全局兜底?
因为 recover 只对当前 goroutine 有效,而微服务中大量使用 goroutine 启动 HTTP handler、消息消费、定时任务等——一旦某个协程 panic 且未被显式捕获,进程不会退出,但该协程直接消失,错误静默丢失,监控完全失效。
常见错误现象:panic: assignment to entry in nil map 出现在某个请求处理中,日志里只有一行 http: panic serving ...,没有堆栈,无法定位源头。
- HTTP server 默认的
http.DefaultServeMux在 panic 时仅打印基础信息,不记录完整堆栈 - gRPC server 的
RecoverFromPanic中间件需手动启用,且默认不采集上下文(如 traceID、requestID) - 第三方库(如
sqlx、redis客户端)抛出的 panic 通常发生在回调或异步路径,recover很难覆盖到
error 类型该怎么封装才便于监控和分类?
直接返回 fmt.Errorf("failed to get user: %w", err) 不够——它丢失了错误类型、业务码、可追溯性。监控系统需要结构化字段:错误码(code)、层级(layer)、是否重试(retryable)、traceID。
推荐用自定义 error 类型,而非字符串拼接:
type AppError struct {
Code string `json:"code"`
Message string `json:"message"`
Layer string `json:"layer"` // "db", "rpc", "http"
Retry bool `json:"retry"`
TraceID string `json:"trace_id"`
}
func NewDBError(msg string, err error) *AppError {
return &AppError{
Code: "ERR_DB_001",
Message: msg,
Layer: "db",
Retry: true,
TraceID: trace.FromContext(context.Background()).TraceID().String(),
}
}
- 避免用
errors.Is判断底层 error(如os.IsTimeout),而应统一通过AppError.Code匹配业务逻辑分支 - HTTP 层将
*AppError映射为标准状态码:ERR_RPC_XXX → 503,ERR_VALIDATION → 400 - 不要在 error message 里拼接敏感数据(如用户 ID、token),message 仅用于展示,结构化字段承载语义
如何让 Sentry / Prometheus 真正捕获 Go 微服务的错误?
Sentry 默认只上报未捕获 panic,Prometheus 的 go_error_count 指标默认为 0——两者都需要显式集成,且配置点容易遗漏。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
关键实操点:
- Sentry 初始化必须在
main()最早执行,并设置BeforeSend过滤掉健康检查类错误(如/healthz返回的ErrDBUnreachable) - Prometheus 需手动调用
promauto.NewCounterVec记录 error 分类:errors_total{layer="rpc",code="ERR_TIMEOUT"},而不是依赖 runtime 指标 - gRPC 拦截器中上报错误时,要从
ctx提取trace.SpanFromContext(ctx).SpanContext().TraceID().String(),否则 Sentry 里看不到链路关联 - HTTP middleware 中捕获
*AppError后,必须调用sentry.CaptureException(),不能只打日志
为什么 zap 日志 + error 堆栈还不够?
因为 zap 输出的 stacktrace 字段是字符串,Prometheus 无法聚合,Sentry 无法做错误聚类,ELK 里也难以按 error code 精确过滤。
真正可用的日志至少要包含三要素:结构化 error 字段、trace 上下文、可索引的 level 标签。
- 用
zap.String("error_code", err.Code)替代zap.String("error", err.Error()) - 所有日志必须带
zap.String("trace_id", traceID),且 traceID 要从入参 context 统一透传,不能每次 new - 对高频错误(如 redis 连接超时),加
zap.Bool("sampled", rand.Float64() 避免日志刷屏,但确保 Sentry 仍 100% 上报 - 避免在 defer 里打 error 日志——此时 response 已写出,trace 可能已 close,
traceID为空
最常被忽略的是 error 的生命周期管理:一个 *AppError 创建后,如果被多次 fmt.Errorf("%w") 包装,原始 Code 和 TraceID 就丢了。务必在最外层 handler 统一构造并上报,中间层只做 error 类型判断和轻量包装。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










