在Golang的标准库中,error是一个内置的接口类型,仅包含一个Error() string方法,这种简单的设计虽然灵活,但无法携带错误的严重程度、错误码等额外信息,因此需要通过自定义扩展来实现错误等级分类。

定义错误等级常量
首先我们需要先定义常见的错误等级,通常按照严重程度从低到高可以分为调试、信息、警告、错误、致命错误几个级别,使用常量来避免硬编码带来的问题。
package errlevel
// 定义错误等级类型
type Level int
// 声明各等级常量
const (
DebugLevel Level = iota // 调试级别,用于开发阶段排查细节问题
InfoLevel // 信息级别,记录常规业务流程信息
WarnLevel // 警告级别,不影响主流程但存在潜在风险
ErrorLevel // 错误级别,影响部分功能但程序仍可运行
FatalLevel // 致命级别,会导致程序无法继续运行
)
// 实现String方法,方便输出等级名称
func (l Level) String() string {
switch l {
case DebugLevel:
return "DEBUG"
case InfoLevel:
return "INFO"
case WarnLevel:
return "WARN"
case ErrorLevel:
return "ERROR"
case FatalLevel:
return "FATAL"
default:
return "UNKNOWN"
}
}
自定义带等级的错误类型
接下来我们需要自定义一个错误类型,让它既实现error接口,又能携带错误等级、错误码、错误上下文等额外信息。
package errlevel
import "fmt"
// 自定义错误结构体
type LeveledError struct {
Level Level // 错误等级
Code int // 错误码,方便定位具体错误类型
Message string // 错误描述信息
Err error // 原始错误,用于错误链包装
}
// 实现error接口的Error方法
func (e *LeveledError) Error() string {
if e.Err != nil {
return fmt.Sprintf("[%s] code:%d message:%s cause:%s", e.Level.String(), e.Code, e.Message, e.Err.Error())
}
return fmt.Sprintf("[%s] code:%d message:%s", e.Level.String(), e.Code, e.Message)
}
// 实现Unwrap方法,支持Go 1.13+的错误链判断
func (e *LeveledError) Unwrap() error {
return e.Err
}
// 创建新的带等级错误
func New(level Level, code int, message string, err error) error {
return &LeveledError{
Level: level,
Code: code,
Message: message,
Err: err,
}
}
错误等级判断与处理
定义好错误类型后,我们可以编写通用的错误处理函数,根据错误的等级执行不同的逻辑,比如不同等级输出到不同的日志目标,或者触发不同的告警。
package errlevel
import (
"fmt"
"os"
)
// 处理带等级的错误
func HandleError(err error) {
if err == nil {
return
}
// 尝试将错误转换为带等级的错误
var leveledErr *LeveledError
if e, ok := err.(*LeveledError); ok {
leveledErr = e
} else {
// 如果是普通error,默认归类为错误级别
leveledErr = &LeveledError{
Level: ErrorLevel,
Code: 500,
Message: err.Error(),
Err: nil,
}
}
// 根据错误等级执行不同处理逻辑
switch leveledErr.Level {
case DebugLevel:
fmt.Printf("调试信息: %sn", leveledErr.Error())
case InfoLevel:
fmt.Printf("业务信息: %sn", leveledErr.Error())
case WarnLevel:
fmt.Printf("警告提示: %sn", leveledErr.Error())
case ErrorLevel:
fmt.Printf("运行错误: %sn", leveledErr.Error())
case FatalLevel:
fmt.Printf("致命错误: %sn", leveledErr.Error())
os.Exit(1)
}
}
实际应用示例
下面通过一个简单的业务场景展示如何使用上述错误等级分类体系,模拟用户查询时的不同错误情况。
package main
import (
"errors"
"your_project_path/errlevel" // 替换为实际的包路径
)
func queryUser(id int) error {
if id <= 0 {
// 参数错误属于错误级别
return errlevel.New(errlevel.ErrorLevel, 1001, "用户ID不能为负数", nil)
}
if id == 999 {
// 模拟用户不存在,属于信息级别的提示
return errlevel.New(errlevel.InfoLevel, 1002, "查询的用户不存在", nil)
}
if id == 1000 {
// 模拟数据库连接失败,属于致命错误
return errlevel.New(errlevel.FatalLevel, 2001, "数据库连接失败", errors.New("connection refused"))
}
// 正常情况返回nil
return nil
}
func main() {
// 测试用户ID为负数
err := queryUser(-1)
errlevel.HandleError(err)
// 测试查询不存在的用户
err = queryUser(999)
errlevel.HandleError(err)
// 测试数据库连接失败
err = queryUser(1000)
errlevel.HandleError(err)
}
注意事项
- 自定义错误类型时建议保留原始错误的引用,方便进行错误链的追溯和判断
- 错误等级的划分需要结合业务场景,不需要过度细分,避免增加使用复杂度
- 如果使用Go 1.13及以上版本,可以结合
errors.Is和errors.As方法来判断错误类型和等级,提升错误处理的灵活性 - 生产环境中建议将不同等级的错误输出到不同的日志文件,方便后续排查和分析