Go中错误日志分级需借助zap等结构化日志库或自定义LevelError类型,按错误来源、行为和上下文区分Warn/Err/Fatal级别,普通错误如参数校验失败用Warn,严重错误如DB连接丢失用Fatal或标记critical。

在 Go 中实现错误日志分级,关键不是靠 error 类型本身(它不带级别),而是通过日志库的上下文封装、自定义错误类型或结构化日志字段来区分“普通错误”和“严重错误”。标准库 log 不支持分级,需借助成熟日志库(如 zap、zerolog 或 logrus)并配合合理的错误建模。
使用 zap 封装带级别的错误日志
zap 是高性能结构化日志库,原生支持 Debug、Info、Warn、Error、Fatal 等级别。普通错误用 Warn 或 Error,严重错误(如服务不可用、数据损坏、认证失效)用 Fatal 或打上 "severity": "critical" 字段。
- 普通错误:记录但不中断流程,例如用户参数校验失败、第三方 API 临时超时
- 严重错误:需告警、人工介入或触发降级/重启,例如数据库连接永久丢失、配置加载失败、JWT 密钥缺失
示例:
logger.Warn("user login failed",
zap.String("user_id", userID),
zap.Error(err),
zap.String("reason", "invalid_password"))
logger.Error("failed to initialize database",
zap.String("dsn", dsn),
zap.Error(err),
zap.String("severity", "critical")) // 显式标记严重性
定义可分级的自定义错误类型
通过实现 interface{ Error() string; Severity() string },让错误自带级别语义。配合日志器自动提取级别字段,避免每次手动判断。
立即学习“go语言免费学习笔记(深入)”;
type LevelError struct {
msg string
severity string // "info", "warn", "error", "critical"
err error
}
func (e *LevelError) Error() string { return e.msg }
func (e *LevelError) Severity() string { return e.severity }
func (e *LevelError) Unwrap() error { return e.err }
// 使用
err := &LevelError{
msg: "cache write timeout",
severity: "warn",
err: ctx.Err(),
}
logger.With(zap.String("severity", err.Severity())).Warn(err.Error(), zap.Error(err))
按错误来源和行为做分级决策
不依赖错误字符串匹配,而是结合错误类型、底层原因(如是否是 net.OpError)、重试次数、调用上下文来判定严重性:
- 网络类错误(
net.OpError、url.Error):单次失败 →Warn;连续 3 次失败 →Error并标记retries_exhausted:true - 数据库错误(如
pgconn.PgError):唯一约束冲突 →Warn;连接 refused / transaction abort →Error+severity:critical - 解析类错误(JSON/XML 解析失败):输入来自用户 →
Warn;输入来自内部服务且 schema 已约定 →Error
在 HTTP handler 中统一错误响应与日志联动
将错误分级映射到 HTTP 状态码和日志行为,避免业务代码到处写 logger.Error:
func handleUserCreate(w http.ResponseWriter, r *http.Request) {
user, err := parseUser(r.Body)
if err != nil {
logError(logger, err, "parse_user_body", "warn") // 普通错误:400 + warn
http.Error(w, "bad request", http.StatusBadRequest)
return
}
if err := db.Create(&user).Error; err != nil {
logError(logger, err, "create_user_db", "critical") // 严重错误:500 + critical + alert
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
}
func logError(l *zap.Logger, err error, op string, level string) {
fields := []zap.Field{
zap.String("op", op),
zap.Error(err),
}
switch level {
case "warn":
l.Warn("operation failed", fields...)
case "critical":
l.Error("critical operation failure", append(fields, zap.String("alert", "true"))...)
}
}










