Go中错误日志分级需借助zap等结构化日志库或自定义LevelError类型,按错误来源、行为和上下文区分Warn/Err/Fatal级别,普通错误如参数校验失败用Warn,严重错误如DB连接丢失用Fatal或标记critical。
在 Go 中实现错误日志分级,关键不是靠 error 类型本身(它不带级别),而是通过日志库的上下文封装、自定义错误类型或结构化日志字段来区分“普通错误”和“严重错误”。标准库 log 不支持分级,需借助成熟日志库(如 zap、zerolog 或 logrus)并配合合理的错误建模。
zap 是高性能结构化日志库,原生支持 Debug、Info、Warn、Error、Fatal 等级别。普通错误用 Warn 或 Error,严重错误(如服务不可用、数据损坏、认证失效)用 Fatal 或打上 "severity": "critical" 字段。
示例:
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
Warn;输入来自内部服务且 schema 已约定 → Error
将错误分级映射到 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"))...)
}
}