/expvar 需手动注册 handler 且变量须显式 publish;未注册变量不显示,重复 publish 会 panic;敏感信息需隐藏,高并发浮点计数应换 Prometheus。

为什么 /debug/vars 返回空 JSON 或 404?
因为 expvar 本身不启动 HTTP 服务,也不自动挂载 handler——它只准备好了数据和 expvar.Handler(),但没人把它注册到路由上。
- 必须显式调用
http.Handle("/debug/vars", expvar.Handler()),否则访问就是 404 - 别用
http.StripPrefix套路径前缀(比如/api/debug/vars),expvar.Handler()只响应精确匹配的/debug/vars - 如果你用了自定义
http.ServeMux(不是http.DefaultServeMux),得手动把 handler 挂上去,不能依赖 init 时的默认行为 - 检查是否在
main()之前就启了 server,而 handler 是之后才注册的——顺序错也会导致 404
expvar.NewInt("x") 定义后为啥没出现在 /debug/vars?
因为 expvar.NewInt 创建的是“未注册的裸变量”,它只是个 *expvar.Int 实例,还没放进全局注册表。
- 正确做法:先创建,再显式注册——
var reqCount = expvar.NewInt("http_requests_total"); expvar.Publish("http_requests_total", reqCount) - 更省事写法:直接用
expvar.Register(&reqCount)(注意传指针) - 常见错误:在函数内部定义
var x = expvar.NewInt("tmp"),然后没逃逸、被 GC 回收,指标就消失了 - 变量名不能含点号(如
"api.latency"),会被当成嵌套对象处理;推荐用下划线,如"api_latency_ms"
如何暴露一组关联指标(比如连接池状态)?
别自己实现 expvar.Var 接口或在 String() 里拼 JSON 字符串——那样只会变成一个不可展开的字符串字段。
- 用
expvar.NewMap("db_pool")创建映射容器,它已嵌入expvar.Map并支持Set - 每个子项用
expvar.NewInt创建后,调poolMap.Set("idle", idleCounter)挂载,不是赋值给 struct 字段 - 最后调
expvar.Publish("db_pool", poolMap)注册到根命名空间 - 避免在
String()方法里做 DB 查询或 HTTP 请求——/debug/vars是同步阻塞式读取,卡住整个响应
生产环境能直接用 /debug/vars 吗?
不能。它没有认证、无 TLS、无路径控制,且默认暴露 cmdline、memstats 等敏感信息,公网暴露等于裸奔。
立即学习“go语言免费学习笔记(深入)”;
- 建议改路径,比如挂到
/internal/metrics,并加中间件鉴权(如 Basic Auth 或 IP 白名单) - 可调
expvar.Unregister("cmdline")和expvar.Unregister("memstats")移除高风险项(但num_goroutine别动) -
expvar.Float的Add非原子,高并发下数值会漂移;需要准确浮点计数请换prometheus.Counter - 它不支持直方图、标签、聚合,也无采样机制——真要监控延迟分布,别硬扛,直接上
prometheus/client_golang
最常被忽略的一点:你写的 expvar.Publish 如果名字重复,程序会 panic;而这种 panic 往往发生在服务启动后很久,靠日志很难回溯——起名务必加前缀,比如 "myapp_http_requests_total"。


















