React前端应用集成Prometheus HTTP指标:prom-client无法捕获5xx错误及正确状态码的问题排查
解决Prometheus无法收集5xx错误HTTP指标的问题
我看了你的代码,发现几个关键问题导致5xx错误的指标没被正确收集,下面一步步帮你修复:
1. 先修复/metrics路由的致命错误
你的/metrics路由同时发送了React的index.html和Prometheus指标数据,这会导致响应重复结束的错误,而且Prometheus根本无法正确解析指标。把这个路由改成只返回指标:
app.get('/metrics', (req, res) => { res.setHeader('Content-Type', Prometheus.register.contentType); res.end(Prometheus.register.metrics()); });
2. 调整中间件顺序,确保错误请求也能触发指标收集
当前的中间件顺序里,错误处理中间件之后才是指标收集逻辑,但你需要确保所有请求(包括错误请求)都能走到指标收集中间件。另外,React SPA的路由 fallback 应该放在业务路由之后、metrics路由之前,避免影响指标接口的访问。
调整后的完整中间件/路由顺序:
- 先注册记录请求开始时间的中间件
- 静态资源和favicon中间件
- 业务路由(/、/bad)
- React SPA fallback路由(返回index.html)
- /metrics路由
- 错误处理中间件
- 404处理中间件
- 最后注册指标收集中间件
3. 确保错误处理中间件调用next()
在错误处理中间件里,处理完错误后必须调用next(),这样才能让后续的指标收集中间件执行,捕获到500状态码:
app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ error: err.message }); next(); // 关键:让指标收集中间件执行 });
修复后的完整server.js代码
const express = require('express') const Prometheus = require('prom-client'); const favicon = require('express-favicon'); const path = require('path'); const app = express() const port = process.env.PORT || 8082 // 收集Prometheus默认指标 const metricsInterval = Prometheus.collectDefaultMetrics() // 定义HTTP请求时长直方图指标 const httpRequestDurationMicroseconds = new Prometheus.Histogram({ name: 'http_request_duration_ms', help: 'Duration of HTTP requests in ms', labelNames: ['method', 'route', 'code'], buckets: [0.10, 5, 15, 50, 100, 200, 300, 400, 500] }) // 记录请求开始时间的中间件(第一个注册,确保所有请求都能捕获到) app.use((req, res, next) => { res.locals.startEpoch = Date.now(); next(); }); // 静态资源和favicon处理 app.use(favicon(__dirname + '/build/favicon.ico')); app.use(express.static(__dirname)); app.use(express.static(path.join(__dirname, 'build'))); // 业务路由 app.get('/', (req, res, next) => { setTimeout(() => { res.json({ message: 'Hello World!' }); next(); // 调用next触发指标收集 }, Math.round(Math.random() * 200)); }); app.get('/bad', (req, res, next) => { next(new Error('My Error')); // 传递错误到错误处理中间件 }); // React SPA路由fallback:所有非API/非metrics的请求返回index.html app.get('*', (req, res, next) => { res.sendFile(path.join(__dirname, 'build', 'index.html')); next(); // 调用next触发指标收集 }); // Prometheus指标接口 app.get('/metrics', (req, res) => { res.setHeader('Content-Type', Prometheus.register.contentType); res.end(Prometheus.register.metrics()); }); // 错误处理中间件(必须带4个参数,Express才会识别为错误处理) app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ error: err.message }); next(); // 关键:让指标收集中间件执行 }); // 404处理中间件 app.use((req, res, next) => { res.status(404).json({ error: 'Not Found' }); next(); }); // 指标收集中间件(最后一个注册,确保所有请求都能走到这里) app.use((req, res, next) => { const responseTimeInMs = Date.now() - res.locals.startEpoch; // 优先用路由定义的path,避免动态参数导致指标标签爆炸(比如/api/users/123会变成/api/users/:id) const route = req.route ? req.route.path : req.path; httpRequestDurationMicroseconds .labels(req.method, route, res.statusCode) .observe(responseTimeInMs); next(); }); // 启动服务器 const server = app.listen(port, () => { console.log(`Example app listening on port ${port}!`) }) // 优雅关闭逻辑 process.on('SIGTERM', () => { clearInterval(metricsInterval) server.close((err) => { if (err) { console.error(err) process.exit(1) } process.exit(0) }) })
为什么之前的代码无法收集5xx指标?
/metrics路由错误:同时发送HTML和指标,导致响应异常,Prometheus无法正常抓取数据,甚至可能让Node.js抛出错误。- 错误处理中间件未调用
next():当错误发生时,错误处理中间件处理完后没有继续传递请求到指标收集中间件,导致500请求的指标被遗漏。 - 中间件顺序不合理:React SPA的fallback路由如果放在前面,会拦截
/metrics请求,导致无法访问指标接口。
现在你可以测试:
- 访问
/bad,会返回500错误,此时/metrics里会出现code="500"的指标 - 访问不存在的路径,会返回404,指标里也会有
code="404"的记录
内容的提问来源于stack exchange,提问作者BIndu_Madhav
相关产品推荐
相关产品推荐

