因为未注册全局Recovery中间件或未配置日志输出,导致panic被gin.Recovery捕获后未记录错误详情;需使用gin.RecoveryWithWriter(log.Writer())并避免在health handler中触发panic。

为什么 GET /health 返回 500 却没日志?
Gin 默认捕获 panic 但不记录错误详情,尤其在中间件或 handler 里直接 panic(比如空指针解引用)时,c.AbortWithStatus(500) 会静默失败。务必在启动前注册全局 recovery 中间件并启用日志输出:
router.Use(gin.RecoveryWithWriter(log.Writer()))
更稳妥的做法是避免 panic,改用显式错误判断:
- 检查依赖服务连通性时,用
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)防止阻塞 - 数据库 ping 失败应返回
c.JSON(503, gin.H{"status": "db_unavailable", "error": err.Error()}),而非 panic - 不要在
healthhandler 里调用未加超时控制的 HTTP 客户端请求
gin.Context 里怎么安全读取系统指标?
Go 运行时指标(如 goroutine 数、内存分配)需通过 runtime 和 debug 包获取,但注意:这些值是快照,且 debug.ReadGCStats 会触发 STW(虽极短)。推荐组合使用:
var m runtime.MemStats
runtime.ReadMemStats(&m)
c.JSON(200, gin.H{
"goroutines": runtime.NumGoroutine(),
"heap_alloc": m.Alloc,
"heap_sys": m.Sys,
"gc_next": m.NextGC,
})
关键点:
立即学习“go语言免费学习笔记(深入)”;
-
runtime.ReadMemStats是线程安全的,可直接调用 - 避免高频调用
debug.ReadGCStats(如每秒多次),它会累积 GC 历史,建议缓存结果或降频 - 若需进程 CPU 使用率,得自己采样
/proc/self/stat(Linux)或runtime.MemStats.TotalAlloc差值估算,Gin 本身不提供该能力
如何让监控接口支持依赖服务连通性检查?
真实场景中,/health 往往要检查 Redis、MySQL、下游 API 等。别把所有检查塞进一个 handler——用可插拔的检查器模式:
type Checker func() error
var checks = map[string]Checker{
"redis": func() error {
return redisClient.Ping(context.Background()).Err()
},
"mysql": func() error {
return db.Ping()
},
}
// handler 中遍历执行
for name, check := range checks {
if err := check(); err != nil {
c.JSON(503, gin.H{"status": "unhealthy", "failed_check": name, "error": err.Error()})
return
}
}
注意事项:
- 每个检查必须带 context 超时,否则一个慢依赖会拖垮整个健康检查
- 避免在检查器里复用长连接对象(如未设置 idle timeout 的 http.Client),连接池耗尽会导致后续请求失败
- 生产环境建议将依赖检查拆到
/health/live(存活)和/health/ready(就绪),前者只查进程是否存活,后者才查依赖
为什么 k8s readiness probe 总失败,但本地 curl 正常?
常见原因是容器网络策略或 DNS 配置差异:k8s pod 内默认无 /etc/resolv.conf 权限,或启用了 hostNetwork: true 导致监听地址不对。排查重点:
- 确认 Gin 启动时监听的是
:8080而非127.0.0.1:8080(后者在容器内无法被 kubelet 访问) - 检查 liveness/readiness probe 的
initialDelaySeconds是否过短——Gin 启动后还需加载配置、建连依赖,建议设为 10s 起 - 如果用了自签名证书或重定向,probe 默认不跟随跳转、不校验证书,确保 endpoint 是纯 HTTP 且返回 2xx
- k8s 1.24+ 对 probe 超时更严格,
timeoutSeconds必须显式设为 ≥1,否则默认 1s 可能不够
最易忽略的一点:Gin 的 router.Run() 是阻塞调用,若在 main 函数里没用 goroutine 启动,probe 请求根本发不到——但这个错误不会报错,只会让容器卡在启动态。


















