golang检查外部包错误是否被包装的lint插件wrapcheck的使用
Golang检查外部包错误是否被包装的lint插件wrapcheck的使用
Wrapcheck是一个简单的Go语言lint工具,用于检查从外部包返回的错误是否被包装,以帮助在调试时识别错误来源。
安装
要求Go版本 >= v1.22.0
$ go install github.com/tomarrell/wrapcheck/v2/cmd/wrapcheck@v2
Wrapcheck也可以作为golangci-lint元linter的一部分使用。
配置
可以通过在本地目录或主目录中使用.wrapcheck.yaml
文件来配置wrapcheck。
# 指定要忽略的签名子字符串数组
ignoreSigs:
- .Errorf(
- errors.New(
- errors.Unwrap(
- errors.Join(
- .Wrap(
- .Wrapf(
- .WithMessage(
- .WithMessagef(
- .WithStack(
# 指定额外要忽略的签名子字符串数组
extraIgnoreSigs:
- .CustomError(
- .SpecificWrap(
# 指定要忽略的签名正则表达式数组
ignoreSigRegexps:
- \\.New.*Error\(
# 指定要忽略的包glob模式数组
ignorePackageGlobs:
- encoding/*
- github.com/pkg/*
# 定义要忽略的接口正则表达式列表
ignoreInterfaceRegexps:
- ^(?i)c(?-i)ach(ing|e)
# 是否报告来自包内部的错误
reportInternalErrors: true
使用
要检查程序中的所有包:
$ wrapcheck ./...
示例
当你在调试Go程序时,看到如下错误日志:
time="2020-08-04T11:36:27+02:00" level=error error="sql: error no rows"
很难定位错误来源,特别是当你有多个类似的方法:
func (db *DB) getUserByID(userID string) (User, error) {
sql := `SELECT * FROM user WHERE id = $1;`
var u User
if err := db.conn.Get(&u, sql, userID); err != nil {
return User{}, err // wrapcheck error: error returned from external package is unwrapped
}
return u, nil
}
func (db *DB) getItemByID(itemID string) (Item, error) {
sql := `SELECT * FROM item WHERE id = $1;`
var i Item
if err := db.conn.Get(&i, sql, itemID); err != nil {
return Item{}, err // wrapcheck error: error returned from external package is unwrapped
}
return i, nil
}
解决方法是在返回外部包错误时包装它:
func (db *DB) getUserByID(userID string) (User, error) {
sql := `SELECT * FROM user WHERE id = $1;`
var u User
if err := db.Conn.Get(&u, sql, userID); err != nil {
return User{}, fmt.Errorf("failed to get user by ID: %v", err) // No error!
}
return u, nil
}
func (db *DB) getItemByID(itemID string) (Item, error) {
sql := `SELECT * FROM item WHERE id = $1;`
var i Item
if err := db.Conn.Get(&i, sql, itemID); err != nil {
return Item{}, fmt.Errorf("failed to get item by ID: %v", err) // No error!
}
return i, nil
}
现在你的日志会更具有描述性,更容易定位错误来源:
time="2020-08-04T11:36:27+02:00" level=error error="failed to get user by ID: sql: error no rows"
为什么需要包装错误
Go中的错误是简单的值,它们只包含满足接口所需的最小信息:
type Error interface {
Error() string
}
这是一个很好的特性,但也可能是一个限制。特别是当你试图识别程序中错误的来源时。
从Go 1.13开始,使用fmt.Errorf(...)
包装错误是Go中推荐的方式来组合错误以添加附加信息。
解决方案
在调用可能返回错误的外部库时,我们可以包装错误以添加有关调用点的附加信息。
例如:
func (db *DB) createUser(name, email, city string) error {
sql := `INSERT INTO customer (name, email, city) VALUES ($1, $2, $3);`
if _, err := tx.Exec(sql, name, email, city); err != nil {
// %v verb preferred to prevent error becoming part of external API
return fmt.Errorf("failed to insert user: %v", err)
}
return nil
}
这个解决方案允许你添加上下文,这些上下文将被传递给调用者,使得在调试时更容易识别来源。
贡献
与大多数静态分析工具一样,这个linter可能会遗漏一些晦涩的情况。如果你遇到应该覆盖但没有覆盖的情况,请提交一个问题,包括该情况的最小可重现示例。
更多关于golang检查外部包错误是否被包装的lint插件wrapcheck的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于golang检查外部包错误是否被包装的lint插件wrapcheck的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Golang 错误包装检查工具 wrapcheck
wrapcheck 是一个用于检查 Go 代码中错误处理是否适当包装外部错误的静态分析工具(linter)。它确保从外部包返回的错误在被返回给调用者之前被适当地包装,以提供更多上下文信息。
安装 wrapcheck
可以通过以下命令安装 wrapcheck:
go install github.com/tomarrell/wrapcheck/v2/cmd/wrapcheck@latest
基本使用
wrapcheck 可以单独运行或集成到 golangci-lint 中:
# 单独运行
wrapcheck ./...
# 作为 golangci-lint 的一部分
golangci-lint run --enable=wrapcheck
配置
wrapcheck 支持通过 .wrapcheck.yaml
文件进行配置。示例配置:
# 需要检查的包列表(默认检查所有外部包)
ignorePackageGlobs:
- "encoding/*"
- "io"
# 允许不包装的错误类型
ignoreInterfaceRegexps:
- "^error$"
- "^runtime\\.errorString$"
# 忽略特定函数返回的错误
ignoreFunctionRegexps:
- "\\.Close\\(\\)$"
- "\\.Write\\(\\)$"
示例代码
不符合 wrapcheck 要求的代码
package main
import (
"database/sql"
"fmt"
"os"
)
func getUserFromDB(db *sql.DB, id int) (*User, error) {
var user User
err := db.QueryRow("SELECT * FROM users WHERE id = ?", id).Scan(&user)
if err != nil {
return nil, err // wrapcheck 会报错:外部包错误未被包装
}
return &user, nil
}
func readConfig() ([]byte, error) {
data, err := os.ReadFile("config.json")
return data, err // wrapcheck 会报错:外部包错误未被包装
}
符合 wrapcheck 要求的代码
package main
import (
"database/sql"
"fmt"
"os"
)
func getUserFromDB(db *sql.DB, id int) (*User, error) {
var user User
err := db.QueryRow("SELECT * FROM users WHERE id = ?", id).Scan(&user)
if err != nil {
return nil, fmt.Errorf("failed to get user %d: %w", id, err) // 使用 %w 包装错误
}
return &user, nil
}
func readConfig() ([]byte, error) {
data, err := os.ReadFile("config.json")
if err != nil {
return nil, fmt.Errorf("failed to read config: %w", err) // 使用 %w 包装错误
}
return data, nil
}
为什么使用 wrapcheck
- 更好的错误上下文:包装错误可以提供更多关于错误发生位置的上下文信息
- 更容易调试:包装后的错误栈可以更清晰地追踪问题源头
- 一致性:确保整个代码库中的错误处理方式一致
- 兼容性:使用
%w
包装的错误仍然可以通过errors.Is
和errors.As
进行检查
与其他工具集成
wrapcheck 可以很好地与其他 Go 工具集成:
- golangci-lint:在配置文件中启用 wrapcheck
- pre-commit:在提交前运行检查
- CI/CD 管道:作为构建过程的一部分运行
替代方案
如果你不想使用 wrapcheck,也可以考虑其他类似工具:
- errcheck:检查是否处理了所有错误
- errorlint:检查错误处理的最佳实践
- go-consistent:检查代码风格一致性,包括错误处理
wrapcheck 专注于错误包装这一特定方面,是 Go 错误处理工具链中有价值的补充。