错误信息不应直接返回原始error字符串,须脱敏、本地化并统一响应格式;应定义可识别的自定义错误类型,通过errors.As匹配并在handler中分别处理日志与用户提示。
error.Error() 的原始字符串Go 标准库和多数第三方包返回的 error(比如 os.Open 失败时的 "open /tmp/xxx: no such file or directory")包含路径、系统

/app/internal/config.yaml){"code": 400, "message": "文件上传失败"})避免用 fmt.Errorf("failed to save user: %w", err) 层层包裹后仍靠 err.Error() 提取信息;应定义可识别的错误类型,便于上层 switch 判断并映射为用户消息。
type ErrUserNotFound struct{ UserID int }
func (e *ErrUserNotFound) Error() string { return "user not found" }
func (e *ErrUserNotFound) StatusCode() int { return 404 }
func (e *ErrUserNotFound) UserMessage() string { return "用户不存在,请检查 ID" }
// 使用
if errors.As(err, &ErrUserNotFound{}) {
http.Error(w, "用户不存在,请检查 ID", 404)
}
strings.Contains(err.Error(), "no such file") 做判断——脆弱且不可维护UserMessage() 方法,由 handler 统一调用,不散落在各处errors.As 或 errors.Is 匹配,而不是 == 或 strings 操作常见错误是写个通用 error middleware,把所有 panic 和 error 都转成 500 Internal Server Error,结果本该是 400 的参数校验失败也变成 500,掩盖了真实问题。
*ErrInvalidEmail),直接返回 400 + 友好提示error 和 panic,记录日志并返回泛化提示(如“服务暂时不可用”)message 字段,不用 error、msg、desc 等混用同一个错误对象,log 时要完整(含 stack trace、cause、context),但返回给前端时只取 UserMessage() 结果。二者必须分离,不能共用一个字符串输出。
err := db.QueryRow("SELECT ...").Scan(&u.ID)
if err != nil {
log.Printf("user.load failed, user_id=%d, err=%+v", userID, err) // %+v 保留栈和 wrapped error
http.Error(w, uErr.UserMessage(), uErr.StatusCode())
}
github.com/pkg/errors 或 Go 1.13+ fmt.Errorf("%w") 保留错误链,方便日志追踪log.Printf 中的 %+v 是关键,它会展开 wrapped error 和 goroutine stack,而 %v 不会UserMessage() 实现里调用 log 或触发网络请求——它只负责返回纯文本