答案:设计统一的AppError结构体,通过实现Unwrap()保留原始错误并支持errors.Is和errors.As,使用WrapError逐层封装携带上下文,在日志中递归打印错误链以提升可追溯性。
在Golang多层系统中,错误传递的清晰性和可追溯性至关重要。直接返回原始错误或使用
拼接信息会丢失上下文,难以定位问题源头。设计一个合理的错误封装结构,能帮助我们在不破坏类型系统的同时,保留调用栈、错误原因和业务语义。
1. 错误封装的核心原则
一个好的错误封装结构应满足以下几点:
保留原始错误
:每一层可以添加上下文,但不能丢弃底层错误
支持类型判断
:能通过
和
进行错误匹配和提取
携带元信息
:如错误码、层级、操作描述、时间戳等
不影响性能
:避免过度包装或反射开销
2. 定义统一的错误结构体
创建一个自定义错误类型,实现
接口,并嵌入必要的字段:
type AppError struct {Code stringMessage stringCause errorLevel string // 如 "repo", "service", "handler"Time time.Time}func (e *AppError) Error() string {if e.Cause == nil {return fmt.Sprintf("[%s] %s: %s", e.Level, e.Code, e.Message)}return fmt.Sprintf("[%s] %s: %s - caused by: %v", e.Level, e.Code, e.Message, e.Cause)}func (e *AppError) Unwrap() error {return e.Cause}
通过实现
,该错误可与
和
协同工作。
立即学习
“
go语言免费学习笔记(深入)
”;
3. 提供便捷的封装函数
在各层调用时,使用工厂函数快速构建封装错误:
func WrapError(err error, code, message, level string) error {if err == nil {return nil}return &AppError{Code: code,Message: message,Cause: err,Level: level,Time: time.Now(),}}// 用于创建根错误(无cause)func NewError(code, message, level string) error {return &AppError{Code: code,Message: message,Cause: nil,Level: level,Time: time.Now(),}}
这样在服务层调用数据层出错时,可以这样处理:
php版微信js-sdk支付接口类
php版微信js-sdk支付接口类
下载
users, err := r.db.QueryUsers()if err != nil {return nil, WrapError(err, "DB_QUERY_FAILED", "failed to query users", "repo")}
4. 在调用链中逐层封装
假设调用路径是 handler → service → repository,在每一层都用
包装,形成错误链:
repository 层:数据库连接失败 → 返回
service 层:处理用户逻辑失败 →
handler 层:API响应 →
最终错误包含完整调用路径,可用
判断是否为某类根本错误,或用
提取
获取元信息。
5. 日志与监控中的使用建议
记录错误时,推荐递归打印所有cause,便于排查:
func PrintErrorChain(err error) {for i := 0; err != nil; i++ {prefix := strings.Repeat(" ", i)if appErr, ok := err.(*AppError); ok {log.Printf("%s[%s] %s: %s", prefix, appErr.Level, appErr.Code, appErr.Message)} else {log.Printf("%s%v", prefix, err)}err = errors.Unwrap(err)}}
也可将
序列化为JSON输出到日志系统,方便检索分析。
基本上就这些。关键在于统一结构、逐层封装、合理使用
标准库
的错误工具。不复杂但容易忽略的是保持错误链的完整性,避免中间层“吃掉”原错误只返回字符串。
fmt.Errorferrors.Iserrors.AserrorUnwrap()errors.Iserrors.AsWrapErrorNewError("DB_CONN", ...)WrapError(repoErr, "USER_LOAD", ...)WrapError(serviceErr, "API_USER_GET", ...)errors.Iserrors.As*AppErrorAppError