必须使用自定义recovery中间件:捕获panic后调用debug.stack()记录堆栈,通过c.abortwithstatusjson返回结构化友好错误(如code=5000、msg="服务器内部错误"),并确保日志写入独立文件或上报sentry,同时注入trace id以支持链路追踪。

接口 panic 时如何让 Gin 自动记录堆栈并返回友好错误
Gin 默认遇到 panic 会直接崩溃或返回空白响应,根本看不到哪行代码出的问题。必须主动启用 Recovery() 中间件,但它默认只打印到标准输出,线上环境根本没法查。
实操建议:
- 用
gin.Default()会自动加载Recovery(),但日志输出不可控;更稳妥的是显式调用router.Use(gin.RecoveryWithWriter(customWriter)) - 自定义 writer 要实现
io.Writer接口,把 panic 日志写入文件或发送到 Sentry;别直接用os.Stdout,否则日志和业务日志混在一起 - 在 Recovery 回调里手动提取
recover()的值和debug.Stack(),再塞进结构化日志(比如用zap打 log.With(zap.String("trace", string(stack)))) - 注意:
Recovery()只捕获当前 goroutine 的 panic,如果异步 goroutine panic,它完全收不到
HTTP 状态码不匹配业务错误时怎么统一透出异常信息
前端常抱怨“500 错误没提示”,其实是后端把所有错误都塞进了 ctx.AbortWithStatusJSON(500, ...),连参数校验失败也返回 500 —— 这违反 REST 语义,也掩盖真实问题。
实操建议:
- 定义错误类型,比如
type AppError struct { Code int `json:"code"` Msg string `json:"msg"` },用errors.Is()或类型断言做判断 - 在全局中间件里统一处理:遇到
AppError就按err.Code返回对应 status,其他 panic 或未知 error 才走 500 - 避免在 handler 里反复写
ctx.JSON(400, ...);改用封装好的ctx.Error(err)方法,内部自动映射状态码 - 特别注意:Gin 的
ctx.Error()是用于记录错误日志的,不是返回响应 —— 别混淆,它不会发给前端
如何让 trace ID 贯穿整个请求链路(包括 panic 日志)
没有 trace ID,你根本分不清是哪个请求触发了 panic,尤其在并发高、日志滚动快的场景下,纯靠时间戳基本没法定位。
实操建议:
- 在最外层中间件生成
traceID := uuid.New().String(),存进ctx.Request.Context()(不是gin.Context),再通过ctx.Set("trace_id", traceID)暴露给 handler - 所有日志(包括 Recovery 中的 panic 日志)都必须带上这个 trace ID;
zap推荐用zap.String("trace_id", traceID),别拼字符串 - panic 发生时,
RecoveryWithWriter的回调函数里拿不到原始gin.Context,得提前把 trace ID 存进recover()前的 goroutine-local storage(比如用context.WithValue()传下去) - 别用
time.Now().UnixNano()当 trace ID —— 并发下可能重复,且无法关联分布式调用
第三方库报错(比如数据库超时)怎么避免暴露敏感信息给前端
PostgreSQL 报错带连接地址、表名甚至 SQL 片段,MySQL 错误里有用户名,直接透出等于送攻击面。
实操建议:
- 对已知错误类型做白名单过滤:比如
pgconn.PgError提取pgerr.Message和pgerr.Code,忽略pgerr.Detail和pgerr.Where - 用
strings.Contains(err.Error(), "timeout")这类模糊匹配太脆弱;优先用错误类型断言 + 官方 error code(如pgerr.SqlState() == "57014"表示查询取消) - 所有外部错误必须过一层转换函数,例如
NormalizeDBError(err),返回预设的业务错误码(如ErrDBTimeout),绝不直接fmt.Errorf("db: %w", err) - 开发环境可开 debug 模式透出原始 error,但上线前必须确保
GIN_MODE=release且该开关被关闭
真正难的不是加日志或写中间件,而是让每层错误都有明确归属、状态码不越界、trace ID 不丢失、敏感字段不泄露 —— 这些点漏掉任意一个,排查成本就翻倍。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











