golang错误处理增强插件库oops的使用
Golang 错误处理增强插件库 oops 的使用
Oops 是一个全面的 Go 错误处理库,提供带有丰富上下文信息的结构化错误管理。它设计为 Go 内置 error
的替代品,添加了强大的功能,如堆栈跟踪、源代码片段、结构化属性和开发者友好的调试提示。
🎯 主要特性
- 🔧 无缝替代:无缝替换标准 Go 错误处理
- 📊 丰富上下文:添加结构化属性、用户信息
- 🐛 调试友好:开箱即用的堆栈跟踪和源代码片段
- 🔗 错误链:用额外上下文包装和组合错误
- 🛡️ 恐慌恢复:内置 panic 处理与错误转换
- ✅ 断言:用于验证的单行断言辅助
- ⚡ 性能:零依赖、轻量且快速
- 📝 日志集成:适用于所有主流 Go 日志库
- ✂️ 关注点分离:错误处理和日志记录是分开的工作
- 🍳 易集成:无需大规模重构
🚀 安装
go get github.com/samber/oops
💡 快速开始
基础示例
import "github.com/samber/oops"
func main() {
// 带上下文的简单错误
err := oops.
In("user-service").
Tags("database", "postgres").
Code("network_failure").
User("user-123", "email", "foo@bar.com").
With("path", "/hello/world").
Errorf("failed to fetch user: %s", "connection timeout")
// 错误包装
if err != nil {
return oops.
Trace("req-123").
With("product_id", "456").
Wrapf(err, "user operation failed")
}
}
错误构建器方法
方法 | 描述 |
---|---|
.New(message string) error |
创建并返回满足 error 接口的 oops.OopsError 对象 |
.Errorf(format string, args ...any) error |
格式化错误并返回满足 error 接口的 oops.OopsError 对象 |
.Wrap(err error) error |
将错误包装成满足 error 接口的 oops.OopsError 对象 |
.Wrapf(err error, format string, args ...any) error |
包装错误并格式化错误消息 |
.Recover(cb func()) error |
处理 panic 并返回满足 error 接口的 oops.OopsError 对象 |
.Recoverf(cb func(), format string, args ...any) error |
处理 panic 并格式化错误消息 |
.Assert(condition bool) OopsErrorBuilder |
如果条件为 false 则 panic,断言可以链式调用 |
.Assertf(condition bool, format string, args ...any) OopsErrorBuilder |
如果条件为 false 则 panic 并格式化错误消息 |
.Join(err1 error, err2 error, ...) error |
返回包装给定错误的错误 |
上下文构建器方法
方法 | 描述 |
---|---|
.With(string, any) |
提供键值对属性列表 |
.WithContext(context.Context, ...any) |
提供上下文中声明的值列表 |
.Code(string) |
设置描述错误的代码或 slug |
.Public(string) |
设置对终端用户安全的显示消息 |
.Time(time.Time) |
设置错误时间(默认:time.Now() ) |
.Since(time.Time) |
设置错误持续时间 |
.Duration(time.Duration) |
设置错误持续时间 |
.In(string) |
设置功能类别或域 |
.Tags(...string) |
添加多个标签,描述返回错误的功能 |
.Trace(string) |
添加事务 ID、跟踪 ID、关联 ID…(默认:ULID) |
.Span(string) |
添加表示工作单元或操作的 span…(默认:ULID) |
.Hint(string) |
设置快速调试提示 |
.Owner(string) |
设置负责处理此错误的同事/团队名称/电子邮件 |
.User(string, any...) |
提供用户 ID 和键值链 |
.Tenant(string, any...) |
提供租户 ID 和键值链 |
.Request(*http.Request, bool) |
提供 http 请求 |
.Response(*http.Response, bool) |
提供 http 响应 |
🧠 完整示例
错误包装示例
// 带错误包装
err0 := oops.
In("repository").
Tags("database", "sql").
Wrapf(sql.Exec(query), "could not fetch user") // 当 sql.Exec() 为 nil 时 Wrapf 返回 nil
// 带 panic 恢复
err1 := oops.
In("repository").
Tags("database", "sql").
Recover(func () {
panic("caramba!")
})
// 带断言
err2 := oops.
In("repository").
Tags("database", "sql").
Recover(func () {
// ...
oops.Assertf(time.Now().Weekday() == 1, "This code should run on Monday only.")
// ...
})
// oops.New
err3 := oops.
In("repository").
Tags("database", "sql").
New("an error message")
// oops.Errorf
err4 := oops.
In("repository").
Tags("database", "sql").
Errorf("an error message: %d", 42)
上下文丰富示例
// 带公共面向消息的简单错误
err0 := oops.
Public("Could not fetch user.").
Errorf("sql: bad connection")
// 带可选域的简单错误
err2 := oops.
In("repository").
Tags("database", "sql").
Errorf("could not fetch user")
// 带自定义属性
ctx := context.WithContext(context.Background(), "a key", "value")
err3 := oops.
With("driver", "postgresql").
With("query", query).
With("query.duration", queryDuration).
With("lorem", func() string { return "ipsum" }). // 延迟评估
WithContext(ctx, "a key", "another key").
Errorf("could not fetch user")
// 带 trace+span
err4 := oops.
Trace(traceID).
Span(spanID).
Errorf("could not fetch user")
// 带提示和所有权,帮助开发者解决问题
err5 := oops.
Hint("The user could have been removed. Please check deleted_at column.").
Owner("Slack: #api-gateway").
Errorf("could not fetch user")
Panic 处理示例
func mayPanic() {
panic("permission denied")
}
func handlePanic() error {
return oops.
Code("iam_authz_missing_permission").
In("authz").
With("permission", "post.create").
Trace("6710668a-2b2a-4de6-b8cf-3272a476a1c9").
Hint("Runbook: https://doc.acme.org/doc/abcd.md").
Recoverf(func() {
// ...
mayPanic()
// ...
}, "unexpected error %d", 42)
}
断言示例
func mayPanic() {
x := 42
oops.
Trace("6710668a-2b2a-4de6-b8cf-3272a476a1c9").
Hint("Runbook: https://doc.acme.org/doc/abcd.md").
Assertf(time.Now().Weekday() == 1, "This code should run on Monday only.").
With("x", x).
Assertf(x == 42, "expected x to be equal to 42, but got %d", x)
oops.Assert(re.Match(email))
// ...
}
func handlePanic() error {
return oops.
Code("iam_authz_missing_permission").
In("authz").
Recover(func() {
// ...
mayPanic()
// ...
})
}
📫 日志集成
一些日志器可能需要自定义格式化程序来从 oops.OopsError
中提取属性。
可用日志器:
- log
- slog
- logrus
- zerolog
🥷 最佳实践
公共面向错误消息
err := oops.
Public("Could not fetch user.").
Errorf("sql: bad connection")
userMessage := oops.GetPublic(err, "Unexpected error")
错误构建器重用
errorBuilder := oops.
In("iam").
Trace("77cb6664").
With("hello", "world")
err := mayFail1()
if err != nil {
return errorBuilder.Wrap(err)
}
err = mayFail2()
if err != nil {
return errorBuilder.Wrap(err)
}
return errorBuilder.Wrap(mayFail3())
调用者/被调用者属性
func a() error {
return b()
}
func b() error {
return oops.
In("iam").
Trace("4ea76885-a371-46b0-8ce0-b72b277fa9af").
With("hello", "world").
Wrapf(c(), "something failed")
}
func c() error {
return d()
}
func d() error {
return oops.
Code("iam_missing_permission").
In("authz").
Time(time.Now()).
With("permission", "post.create").
Hint("Runbook: https://doc.acme.org/doc/abcd.md").
User("user-123", "firstname", "john", "lastname", "doe").
Tenant("organization-123", "name", "Microsoft").
Errorf("permission denied")
}
Oops 提供了强大的错误处理能力,可以帮助开发者创建更结构化、更易于调试的错误信息。通过丰富的上下文信息和堆栈跟踪,可以大大简化错误诊断过程。
更多关于golang错误处理增强插件库oops的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于golang错误处理增强插件库oops的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang 错误处理增强插件库 oops 使用指南
oops 是一个轻量级的 Go 错误处理增强库,它可以帮助开发者更好地追踪、记录和处理错误。下面我将详细介绍 oops 的使用方法,并提供示例代码。
安装 oops
首先,使用 go get 安装 oops 库:
go get github.com/samber/oops
基本用法
1. 创建带有上下文的错误
package main
import (
"fmt"
"github.com/samber/oops"
)
func main() {
err := oops.
Code("user_not_found").
In("auth").
Time("2023-01-01T00:00:00Z").
With("user_id", 1234).
Errorf("user %d not found", 1234)
fmt.Printf("Error: %v\n", err)
fmt.Printf("Detailed error: %+v\n", err)
}
2. 包装现有错误
func getUser(id int) error {
_, err := db.GetUser(id)
if err != nil {
return oops.
Wrapf(err, "failed to fetch user with id %d", id).
With("user_id", id).
In("repository").
Code("db_error")
}
return nil
}
高级特性
1. 错误层级追踪
func processOrder(orderID int) error {
err := validateOrder(orderID)
if err != nil {
return oops.
Wrapf(err, "failed to process order %d", orderID).
With("order_id", orderID).
In("service")
}
return nil
}
func validateOrder(orderID int) error {
err := checkInventory(orderID)
if err != nil {
return oops.
Wrapf(err, "order validation failed for order %d", orderID).
In("validation")
}
return nil
}
func checkInventory(orderID int) error {
return oops.
Errorf("insufficient inventory for order %d", orderID).
In("inventory")
}
2. 错误断言和提取
func handleError(err error) {
if oopsErr, ok := oops.AsOops(err); ok {
fmt.Println("Error code:", oopsErr.Code())
fmt.Println("Error domain:", oopsErr.Domain())
fmt.Println("Error timestamp:", oopsErr.Timestamp())
fmt.Println("Error context:", oopsErr.Context())
}
// 获取堆栈信息
stack := oops.Stack(err)
fmt.Println("Stack trace:", stack)
}
3. 自定义错误格式
func customFormatter(err error) string {
oopsErr, _ := oops.AsOops(err)
return fmt.Sprintf(
"[%s] %s (code: %s, domain: %s)",
oopsErr.Timestamp(),
oopsErr.Error(),
oopsErr.Code(),
oopsErr.Domain(),
)
}
func main() {
oops.SetFormatter(customFormatter)
err := oops.
Code("test_error").
In("test").
Errorf("this is a test error")
fmt.Println(err) // 使用自定义格式输出
}
最佳实践
- 在服务边界添加上下文:在服务层、仓库层等边界处添加适当的上下文信息
func (s *UserService) GetUser(id int) (*User, error) {
user, err := s.repo.FindByID(id)
if err != nil {
return nil, oops.
Wrapf(err, "failed to get user in service layer").
With("user_id", id).
In("user_service").
Code("service_error")
}
return user, nil
}
- 使用错误码进行分类:定义清晰的错误码体系
const (
ErrUserNotFound = "user_not_found"
ErrDBConnection = "db_connection_error"
ErrInvalidInput = "invalid_input"
)
func validateInput(input string) error {
if input == "" {
return oops.
Errorf("input cannot be empty").
Code(ErrInvalidInput)
}
return nil
}
- 记录完整的错误链:在日志中记录完整的错误信息
func logError(err error) {
logger.Error().
Err(err).
Str("stack", oops.Stack(err)).
Msg("An error occurred")
}
oops 库通过提供丰富的上下文信息和堆栈跟踪,大大增强了 Go 原生的错误处理能力,使得错误调试和问题定位变得更加容易。