必须使用promhttp.Handler()暴露/metrics端点,因其严格遵循OpenMetrics规范,处理Accept头协商、gzip压缩及响应格式;手动拼字符串会因缺失header、不校验accept导致406错误或抓取失败。

直接暴露/metrics端点必须用promhttp.Handler()
自己拼字符串或用fmt.Fprintln写指标文本,会导致Prometheus抓取失败——它不认纯文本,只认符合OpenMetrics规范的响应头和格式。比如Accept: application/openmetrics-text; version=1.0.0协商、Content-Encoding: gzip支持、406 Not Acceptable错误返回,全靠promhttp.Handler()处理。
常见错误是写成:http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "# HELP ...") })。这会漏掉header、不压缩、不校验accept头,抓取时出现server returned HTTP status 406或解析失败。
- 必须用
http.Handle("/metrics", promhttp.Handler()),不是HandleFunc - 如果用了Gin/Echo,别套
gin.WrapH(promhttp.Handler()),改用router.GET("/metrics", func(c *gin.Context) { c.Status(200); c.Data(200, "text/plain; version=0.0.4; charset=utf-8", promhttp.Handler().ServeHTTP(...)) }这种显式方式,避免中间件路径冲突 - 端口别跟主服务混用;建议单独开一个监控端口(如
:9091),防止业务流量打爆指标接口
运行时指标注册不能漏掉prometheus.NewGoCollector()
只注册自定义指标,不注册Go运行时指标,等于只看“业务表”,不看“CPU和内存”。prometheus.MustRegister(prometheus.NewGoCollector())这一行必须有,否则go_goroutines、go_memstats_alloc_bytes、go_gc_duration_seconds这些关键指标根本不会出现在/metrics里。
注意:它和prometheus.NewProcessCollector(pid)不同,后者只暴露进程级信息(如CPU时间、打开文件数),而NewGoCollector()才是Go语言特有的runtime指标源,含GC阶段标签gc_phase="mark"、gc_phase="sweep"等,对定位卡顿极关键。
立即学习“go语言免费学习笔记(深入)”;
- 调用位置必须在
http.ListenAndServe之前,且早于任何自定义指标注册 - 不要在init里多次调用——重复注册会panic:
duplicate metrics collector - 若用
promauto包(如promauto.NewCounter),它默认注册到prometheus.DefaultRegisterer,和NewGoCollector()不冲突,但也要确保两者都注册成功
CounterVec和HistogramVec的标签设计决定查询灵活性
标签不是可有可无的装饰,而是后续所有PromQL查询的基础维度。比如http_requests_total{method="GET",path="/api/user",status="200"}能拆出“/api/user的GET成功率”,但如果只定义CounterVec带method和status,没加path,那你就没法按路径聚合或告警。
常见踩坑:把高基数字段(如user_id、request_id)当标签塞进去,导致指标爆炸(cardinality explosion),Prometheus内存飙升甚至OOM。
-
CounterVec适合固定低基数维度:method、status、handler、region -
HistogramVec的Buckets必须按实际P99预设,别用默认DefBuckets(最大10秒);线上API延迟若常达3~5秒,应设[]float64{0.1, 0.3, 0.5, 1, 3, 5, 10} - 避免动态生成label value;比如
path标签值写成r.URL.Path,遇到/user/123、/user/456就产生无数series;应正则归一化为/user/{id}
生产环境/metrics必须加基础访问控制
不加鉴权的/metrics等于把内存堆栈、goroutine快照、GC详情全盘公开。攻击者扫到这个端点,就能反推服务架构、负载水位,甚至结合go_memstats_heap_alloc_bytes判断是否可触发OOM。
BasicAuth是最轻量且有效的方案,但别在promhttp.Handler()外面再套一层鉴权中间件——Handler本身不处理auth,得由上层统一拦截。
- 示例:用
http.StripPrefix+http.HandlerFunc封装,先check auth header,再调用promhttp.Handler().ServeHTTP() - 禁止暴露在公网;K8s环境下应在Service里设
internalTrafficPolicy: Local,限制仅集群内访问 - 禁用
OPTIONS方法:在路由注册前加http.Handle("/metrics", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method == "OPTIONS" { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed); return } promhttp.Handler().ServeHTTP(w, r) }))
最易被忽略的是指标命名冲突和标签爆炸——前者让服务启动就panic,后者让Prometheus半夜开始OOM。这两点不提前卡死,后面所有可视化和告警都是空中楼阁。


















