在NextJs中使用Prometheus暴露指标
作者:shizhishi |
发布时间: |
分类:前端进阶 |
声明:个人技术博客 • 非官方 • 仅供技术学习交流
我们都知道`NextJs`使用`Promethus`只能显示总路由的指标,无法显示其他具体路由的指标,如:`/api/auth`,`/home` 这种情况,就需要我们自定义指标了 npm install prom-client 需要我们编写`/api/metrics`开放指标接口 import type { NextA...
我们都知道`NextJs`使用`Promethus`只能显示总路由的指标,无法显示其他具体路由的指标,如:`/api/auth`,`/home`
这种情况,就需要我们自定义指标了
### 一. 安装依赖
```
npm install prom-client
```
### 二. 开放默认指标
需要我们编写`/api/metrics`开放指标接口
```
import type { NextApiRequest, NextApiResponse } from 'next'
import { collectDefaultMetrics, register } from 'prom-client'
// 收集默认指标(如 CPU、内存)
collectDefaultMetrics({ register })
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
res.setHeader('Content-Type', register.contentType)
res.send(await register.metrics())
}
```
### 三. 编写自定义指标方法
```
// 自定义业务指标(示例:HTTP 请求计数和耗时)
import { Histogram } from 'prom-client'
const httpRequestDurationMicroseconds = new Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP 请求耗时(秒)',
labelNames: ['method', 'route', 'status'],
buckets: [0.1, 0.5, 1, 2, 5]
})
export { httpRequestDurationMicroseconds }
```
### 四. 在middleware.ts(中间件)记录自定义指标
因为**Next@15**引入了**nodeJs**运行,而**Next@14**之前都是边缘运行(Edge runtime)。在`prom-client`的库里需要使用在**nodejs**环境中才有的库,所以不同版本需要使用不同方法
#### 在Next@15中
修改中间件的运行(`runtime`)
```
export const config = {
runtime: 'nodejs',
matcher: [
'/home/:path*',
'/medicine/:path*',
'/stock/:path*',
'/login/:path*',
'/api/:path*',
'/auth/:path*',
'/log/:path*',
'/notification/:path*'
]
}
```
直接在**middleware**中间件( `runtime:nodejs` )中写
```
export async function middleware(req: NextRequest) {
const start = Date.now()
const res = NextResponse.next()
const duration = (Date.now() - start) / 1000
httpRequestDurationMicroseconds.labels({
method: req.method,
route: req.nextUrl.pathname,
status: res.status.toString()
}).observe(duration)
return res
}
```
#### 在Next@14及以下的版本中
创建接口执行记录自定义指标的操作
```
// 接口 /api/httpHistogram
import type { NextApiRequest, NextApiResponse } from 'next'
import { httpRequestDurationMicroseconds } from '@/tools/httpRequestDurationMicroseconds'
import ResponseService from '@/tools/res'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const { method, route, status, duration } = req.body
httpRequestDurationMicroseconds.labels({
method: method,
route: route,
status: status
}).observe(duration)
return ResponseService.success(res,'Successfully recorded HTTP request duration',200)
}
```
在**middleware**中间件执行请求
```
if (
req.nextUrl.pathname !== '/api/httpHistogram' && //记录自定义指标
req.nextUrl.pathname !== '/api/metrics' //暴露指标的接口
) {
await MiddleAxios({
url: url + '/api/httpHistogram',
map: 'post',
data: {
method: req.method,
route: req.nextUrl.pathname,
status: res.status.toString(),
duration: duration
}
})
}
```
### 完成
可以在**/api/metrics**看到开放的指标
```
# HELP process_cpu_user_seconds_total Total user CPU time spent in seconds.
# TYPE process_cpu_user_seconds_total counter
process_cpu_user_seconds_total 0.922
# HELP process_cpu_system_seconds_total Total system CPU time spent in seconds.
# TYPE process_cpu_system_seconds_total counter
process_cpu_system_seconds_total 1.25
# HELP process_cpu_seconds_total Total user and system CPU time spent in seconds.
# TYPE process_cpu_seconds_total counter
process_cpu_seconds_total 2.172
# HELP process_start_time_seconds Start time of the process since unix epoch in seconds.
# TYPE process_start_time_seconds gauge
process_start_time_seconds 1746197011
...
# HELP nodejs_version_info Node.js version info.
# TYPE nodejs_version_info gauge
nodejs_version_info{version="v21.0.0",major="21",minor="0",patch="0"} 1
# HELP nodejs_gc_duration_seconds Garbage collection duration by kind, one of major, minor, incremental or weakcb.
# TYPE nodejs_gc_duration_seconds histogram
nodejs_gc_duration_seconds_bucket{le="0.001",kind="incremental"} 2
nodejs_gc_duration_seconds_bucket{le="0.01",kind="incremental"} 2
nodejs_gc_duration_seconds_bucket{le="0.1",kind="incremental"} 2
...
//查看接口后会出现自定义指标
# HELP http_request_duration_seconds HTTP 请求耗时(秒)
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.1",method="GET",route="/api/jwt",status="200"} 7
http_request_duration_seconds_bucket{le="0.5",method="GET",route="/api/jwt",status="200"} 7
http_request_duration_seconds_bucket{le="1",method="GET",route="/api/jwt",status="200"} 7
http_request_duration_seconds_bucket{le="2",method="GET",route="/api/jwt",status="200"} 7
http_request_duration_seconds_bucket{le="5",method="GET",route="/api/jwt",status="200"} 7
http_request_duration_seconds_bucket{le="+Inf",method="GET",route="/api/jwt",status="200"} 7
http_request_duration_seconds_sum{method="GET",route="/api/jwt",status="200"} 0.008
http_request_duration_seconds_count{method="GET",route="/api/jwt",status="200"} 7
http_request_duration_seconds_bucket{le="0.1",method="GET",route="/api/redis",status="200"} 7
http_request_duration_seconds_bucket{le="0.5",method="GET",route="/api/redis",status="200"} 7
http_request_duration_seconds_bucket{le="1",method="GET",route="/api/redis",status="200"} 7
http_request_duration_seconds_bucket{le="2",method="GET",route="/api/redis",status="200"} 7
http_request_duration_seconds_bucket{le="5",method="GET",route="/api/redis",status="200"} 7
http_request_duration_seconds_bucket{le="+Inf",method="GET",route="/api/redis",status="200"} 7
http_request_duration_seconds_sum{method="GET",route="/api/redis",status="200"} 0.009000000000000001
http_request_duration_seconds_count{method="GET",route="/api/redis",status="200"} 7
http_request_duration_seconds_bucket{le="0.1",method="GET",route="/medicine/medicine",status="200"} 0
http_request_duration_seconds_bucket{le="0.5",method="GET",route="/medicine/medicine",status="200"} 0
```