@fluojs/metrics 1.0.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +55 -23
- package/README.md +55 -22
- package/dist/http-metrics-middleware.d.ts +12 -0
- package/dist/http-metrics-middleware.d.ts.map +1 -1
- package/dist/http-metrics-middleware.js +67 -12
- package/dist/metrics-module.d.ts +7 -3
- package/dist/metrics-module.d.ts.map +1 -1
- package/dist/metrics-module.js +319 -73
- package/dist/metrics-service.d.ts +6 -1
- package/dist/metrics-service.d.ts.map +1 -1
- package/dist/metrics-service.js +6 -1
- package/dist/serialized-scrape-queue.d.ts +19 -0
- package/dist/serialized-scrape-queue.d.ts.map +1 -0
- package/dist/serialized-scrape-queue.js +43 -0
- package/package.json +9 -8
package/README.ko.md
CHANGED
|
@@ -22,7 +22,7 @@ pnpm add @fluojs/metrics
|
|
|
22
22
|
|
|
23
23
|
## 요구 사항
|
|
24
24
|
|
|
25
|
-
`@fluojs/metrics`는
|
|
25
|
+
`@fluojs/metrics`는 패키지 자체의 지원 계약인 Node.js `>=24.0.0 <27`을 요구합니다.
|
|
26
26
|
|
|
27
27
|
## 사용 시점
|
|
28
28
|
|
|
@@ -44,14 +44,15 @@ class AppModule {}
|
|
|
44
44
|
|
|
45
45
|
`MetricsModule.forRoot()`는 기본적으로 `GET /metrics`를 노출합니다. HTTP request instrumentation middleware를 설치하려면 `http: true` 또는 `http` option object를 전달하세요. HTTP 계측이 활성화되면 request total, error count, request duration을 기록합니다. 운영 환경에서는 scrape endpoint boundary를 명시적으로 다루세요. platform-level proxy가 준비될 때까지 `path: false`로 끄거나 dedicated endpoint middleware를 연결할 수 있습니다.
|
|
46
46
|
|
|
47
|
-
Scrape endpoint는 active `prom-client` Registry output을 해당 Registry의 Prometheus content type으로 반환합니다. `MetricsModule.forRoot()`는 `registry` option을
|
|
47
|
+
Scrape endpoint는 active `prom-client` Registry output을 해당 Registry의 Prometheus content type으로 반환합니다. `MetricsModule.forRoot()`는 bootstrap이 `METRICS_REGISTRY`를 구성하거나 legacy `registry` option을 제공하지 않는 한 application bootstrap마다 격리된 Registry를 생성합니다. 같은 dynamic module class를 다른 bootstrap에서 재사용해도 격리된 metric state는 새로 만들어집니다. framework metric과 application-defined metric이 하나의 scrape surface를 의도적으로 공유해야 할 때만 bootstrap에서 shared `Registry`를 구성하세요.
|
|
48
48
|
|
|
49
49
|
## 공개 책임
|
|
50
50
|
|
|
51
51
|
| 표면 | 책임 | 경계 |
|
|
52
52
|
| --- | --- | --- |
|
|
53
53
|
| `MetricsModule.forRoot(...)` | Prometheus scrape endpoint, default metrics, optional HTTP instrumentation, platform telemetry, registry ownership을 wiring합니다. | `provider`는 현재 `'prometheus'`만 받습니다. `path: false`는 scrape route와 route-scoped endpoint middleware를 비활성화합니다. |
|
|
54
|
-
| `MetricsService` | Active Registry 위에서 custom `Counter`, `Gauge`, `Histogram`을 만드는 application-facing facade이며, 고급 Registry 공유를 위한 `getRegistry()`도 제공합니다. | 비즈니스/application metric은 collector helper를 사용하세요. `getRegistry()`는 active `prom-client` Registry를 `
|
|
54
|
+
| `MetricsService` | Active Registry 위에서 custom `Counter`, `Gauge`, `Histogram`을 만드는 application-facing facade이며, 고급 Registry 공유를 위한 `getRegistry()`도 제공합니다. | `MetricsService`는 non-global service입니다. `MetricsModule.forRoot(...)` registration을 직접 import한 module 또는 `MetricsService`를 re-export하는 module을 import한 module에서 inject하세요. 관련 없는 sibling module에는 자동으로 제공되지 않습니다. 비즈니스/application metric은 collector helper를 사용하세요. `getRegistry()`는 active `prom-client` Registry를 bootstrap의 `METRICS_REGISTRY`로 직접 받을 수 없는 integration에 넘겨야 할 때만 사용하세요. |
|
|
55
|
+
| `METRICS_REGISTRY` | Shared `prom-client` Registry를 위한 bootstrap provider token입니다. | `bootstrapApplication()` provider가 module의 legacy `registry` option보다 우선하여 ownership을 가집니다. |
|
|
55
56
|
| `Registry` | Shared-registry setup을 위한 `prom-client` `Registry` constructor re-export입니다. | 같은 Prometheus Registry 구현체이므로 중복 metric name은 Prometheus semantics에 따라 계속 실패합니다. |
|
|
56
57
|
| `METER_PROVIDER` / `PrometheusMeterProvider` / meter type | Provider token 또는 backend-neutral counter/gauge/histogram facade가 필요한 first-party package integration용 low-level meter bridge입니다. | Application code는 package-level integration을 직접 조합하는 경우가 아니면 보통 이 token이 필요하지 않습니다. 현재 bundled provider backend는 Prometheus뿐입니다. |
|
|
57
58
|
| `middleware` | Framework HTTP metrics와 endpoint-scoped middleware 뒤의 module middleware chain에 참여하는 module-level middleware입니다. | Route-scoped가 아니므로 scrape route만 보호하려면 `endpointMiddleware`를 사용하세요. |
|
|
@@ -82,6 +83,21 @@ MetricsModule.forRoot({
|
|
|
82
83
|
});
|
|
83
84
|
```
|
|
84
85
|
|
|
86
|
+
### HTTP duration histogram bucket 구성
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
MetricsModule.forRoot({
|
|
90
|
+
http: {
|
|
91
|
+
durationHistogramBuckets: [0.01, 0.05, 0.1, 0.5, 1, 5],
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`durationHistogramBuckets`는 내장 HTTP request duration histogram의
|
|
97
|
+
`prom-client` 기본값을 대체합니다. 값의 단위는 초이며, alerting하려는
|
|
98
|
+
latency 범위에 맞게 정해야 합니다. 각 경계는 유한하고 엄격히 증가해야 하며,
|
|
99
|
+
잘못된 구성은 setup 중 거부됩니다.
|
|
100
|
+
|
|
85
101
|
### 메트릭 엔드포인트 보호 또는 비활성화
|
|
86
102
|
|
|
87
103
|
```ts
|
|
@@ -110,7 +126,7 @@ MetricsModule.forRoot({
|
|
|
110
126
|
|
|
111
127
|
### Custom metric은 한 번 생성하고 재사용하기
|
|
112
128
|
|
|
113
|
-
`MetricsService.counter(...)`, `gauge(...)`, `histogram(...)`은 active Registry에 Prometheus collector를 생성합니다. 각 custom metric은 provider construction 또는 application startup 중 한 번만 만들고, business action이 발생할 때는 반환된 collector를 재사용하세요.
|
|
129
|
+
`MetricsService.counter(...)`, `gauge(...)`, `histogram(...)`은 active Registry에 Prometheus collector를 생성합니다. `MetricsService`는 non-global service이므로, 이를 inject하는 provider 또는 controller는 `MetricsModule.forRoot(...)` registration을 직접 import하거나 `MetricsService`를 re-export하는 module을 import한 module에 속해야 합니다. 관련 없는 sibling module에는 자동으로 제공되지 않습니다. 각 custom metric은 provider construction 또는 application startup 중 한 번만 만들고, business action이 발생할 때는 반환된 collector를 재사용하세요.
|
|
114
130
|
|
|
115
131
|
```ts
|
|
116
132
|
import { Inject } from '@fluojs/core';
|
|
@@ -135,30 +151,42 @@ class OrdersService {
|
|
|
135
151
|
|
|
136
152
|
같은 이름으로 `MetricsService.counter(...)`를 다시 호출하면 collector를 다시 만들려고 하므로 Prometheus의 duplicate-name failure behavior를 따릅니다. 요청이나 command handler마다 새로 만들지 말고 collector를 저장해 재사용하세요.
|
|
137
153
|
|
|
138
|
-
`MetricsService.getRegistry()`는 module scrape endpoint, 내장 HTTP collector, platform telemetry, service를 통해 만든 custom collector가 함께 사용하는 동일한 active `prom-client` Registry를 반환합니다. Bootstrap을 직접 소유한다면 `
|
|
154
|
+
`MetricsService.getRegistry()`는 module scrape endpoint, 내장 HTTP collector, platform telemetry, service를 통해 만든 custom collector가 함께 사용하는 동일한 active `prom-client` Registry를 반환합니다. Bootstrap을 직접 소유한다면 public `METRICS_REGISTRY` token으로 명시적 shared registry를 구성하세요. `getRegistry()`는 DI로 `MetricsService`를 받은 advanced integration이 이미 활성화된 Registry에 third-party Prometheus collector를 등록해야 할 때 사용합니다.
|
|
139
155
|
|
|
140
156
|
### Framework metric과 app metric이 하나의 registry를 공유하기
|
|
141
157
|
|
|
158
|
+
애플리케이션이 `Counter`, `Registry` 같은 raw collector를 `prom-client`에서 직접
|
|
159
|
+
import하기로 했다면 애플리케이션 의존성에 `prom-client`를 추가하세요.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
pnpm add prom-client
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`@fluojs/metrics`는 내부적으로 `prom-client`를 사용하지만, 그 의존성만으로 애플리케이션에서
|
|
166
|
+
`prom-client`를 지원되는 transitive import로 사용할 수 있는 것은 아닙니다. 아래 setup은
|
|
167
|
+
`@fluojs/metrics`가 re-export하는 `Registry`를 사용하므로 해당 direct dependency가 필요하지 않습니다.
|
|
168
|
+
|
|
142
169
|
```ts
|
|
143
170
|
import { Module } from '@fluojs/core';
|
|
144
|
-
import {
|
|
145
|
-
import {
|
|
171
|
+
import { METRICS_REGISTRY, MetricsModule, Registry } from '@fluojs/metrics';
|
|
172
|
+
import { bootstrapApplication } from '@fluojs/runtime';
|
|
146
173
|
|
|
147
174
|
const registry = new Registry();
|
|
148
175
|
|
|
149
|
-
new Counter({
|
|
150
|
-
name: 'orders_total',
|
|
151
|
-
help: 'Total orders processed',
|
|
152
|
-
registers: [registry],
|
|
153
|
-
});
|
|
154
|
-
|
|
155
176
|
@Module({
|
|
156
|
-
imports: [MetricsModule.forRoot({ http: true
|
|
177
|
+
imports: [MetricsModule.forRoot({ http: true })],
|
|
157
178
|
})
|
|
158
179
|
class AppModule {}
|
|
180
|
+
|
|
181
|
+
const app = await bootstrapApplication({
|
|
182
|
+
rootModule: AppModule,
|
|
183
|
+
providers: [{ provide: METRICS_REGISTRY, useValue: registry }],
|
|
184
|
+
});
|
|
159
185
|
```
|
|
160
186
|
|
|
161
|
-
|
|
187
|
+
`Registry`는 `@fluojs/metrics`가 re-export하므로 이 setup에는 `prom-client` 직접 dependency가 필요하지 않습니다. application collector는 위의 `MetricsService` pattern으로 생성하세요.
|
|
188
|
+
|
|
189
|
+
여러 `MetricsModule` 인스턴스가 같은 Registry를 의도적으로 공유하는 경우, 내장 HTTP 메트릭은 framework ownership, label schema, effective HTTP instrumentation configuration이 모두 일치할 때만 기존 `http_requests_total`, `http_errors_total`, `http_request_duration_seconds` collector를 재사용합니다. Compatibility 검사는 `pathLabelMode`, 정확히 같은 `pathLabelNormalizer` 함수 참조, `unknownPathLabel` fallback 의미론, 순서가 있는 `durationHistogramBuckets` 값을 포함하므로 서로 다른 HTTP series policy를 하나의 collector set에 섞는 module instance는 빠르게 실패합니다. 내장 플랫폼 텔레메트리 Gauge도 같은 ownership 규칙을 따릅니다. 모듈이 만든 `fluo_component_ready`, `fluo_component_health`, `fluo_metrics_registry_mode` Gauge는 framework ownership과 label schema가 일치할 때만 재사용합니다. 플랫폼 텔레메트리 상태는 재사용된 Registry별로 추적되므로, 이후 스크레이프는 이전 module instance가 남긴 stale component readiness/health series를 제거한 뒤 메트릭을 반환합니다. Registry scrape wrapper는 최신 active module registration을 사용하며 마지막 registration이 종료되면 Registry의 원래 `metrics()` 함수를 복원합니다. 애플리케이션이 직접 등록한 중복 메트릭 이름은 Prometheus Registry 규칙대로 계속 빠르게 실패합니다.
|
|
162
190
|
|
|
163
191
|
### 중복 메트릭 이름은 계속 빠르게 실패합니다
|
|
164
192
|
|
|
@@ -170,9 +198,9 @@ Prometheus 메트릭 이름은 하나의 Registry 안에서 고유해야 합니
|
|
|
170
198
|
|
|
171
199
|
- `fluo_component_ready`: 준비 완료 시 1, 아닐 시 0.
|
|
172
200
|
- `fluo_component_health`: 정상 상태 시 1, 아닐 시 0.
|
|
173
|
-
- `fluo_metrics_registry_mode`:
|
|
201
|
+
- `fluo_metrics_registry_mode`: `MetricsModule.forRoot()`가 Registry를 생성하면 `mode="isolated"`, bootstrap이 `METRICS_REGISTRY`를 제공하거나 legacy `registry` option을 제공하면 `mode="shared"` label과 gauge value `1`을 노출합니다. 이 label은 bootstrap 또는 module registration 중 선택한 실효 Registry ownership configuration을 나타내며 scrape 시점에 Registry 공유 여부를 추론하지 않습니다.
|
|
174
202
|
|
|
175
|
-
이 데이터는
|
|
203
|
+
이 데이터는 built-in `/metrics` controller와 `MetricsService.getRegistry().metrics()`를 사용하는 advanced custom scraper를 포함해 active Registry가 스크레이프될 때마다 `PLATFORM_SHELL`을 쿼리하여 갱신됩니다. 초기화 시 환경 라벨을 제공할 수 있습니다.
|
|
176
204
|
|
|
177
205
|
```ts
|
|
178
206
|
MetricsModule.forRoot({
|
|
@@ -185,10 +213,10 @@ MetricsModule.forRoot({
|
|
|
185
213
|
|
|
186
214
|
### 런타임 플랫폼 텔레메트리 스크레이프 계약
|
|
187
215
|
|
|
188
|
-
플랫폼 텔레메트리는
|
|
216
|
+
플랫폼 텔레메트리는 built-in `/metrics` controller 또는 `MetricsService.getRegistry()`를 사용하는 advanced custom scraper가 active Registry를 스크레이프할 때마다 `PLATFORM_SHELL`을 resolve하여 `fluo_component_ready`와 `fluo_component_health`를 갱신합니다.
|
|
189
217
|
|
|
190
218
|
- `PLATFORM_SHELL` 등록 자체가 빠진 경우에는 스크레이프가 계속 성공하고 플랫폼 텔레메트리 시리즈만 생략됩니다.
|
|
191
|
-
- 직전 성공 스크레이프에서 플랫폼 텔레메트리를 노출한 뒤 `PLATFORM_SHELL`을 사용할 수 없게
|
|
219
|
+
- 직전 성공 스크레이프에서 플랫폼 텔레메트리를 노출한 뒤 `PLATFORM_SHELL`을 사용할 수 없게 되거나 이후 module instance가 재사용된 Registry를 다른 플랫폼 snapshot으로 갱신하면, stale `fluo_component_ready` 및 `fluo_component_health` 시리즈를 제거한 뒤 메트릭을 반환합니다.
|
|
192
220
|
- 그 외의 `PLATFORM_SHELL` resolve 실패는 조용히 삼키지 않고 스크레이프 실패로 그대로 드러납니다.
|
|
193
221
|
|
|
194
222
|
### 기본 프로세스/Node 메트릭 비활성화
|
|
@@ -204,26 +232,30 @@ MetricsModule.forRoot({
|
|
|
204
232
|
## 공개 API
|
|
205
233
|
|
|
206
234
|
- `MetricsModule.forRoot(options)`
|
|
235
|
+
- 의도적으로 공유하는 `Registry`를 위한 bootstrap 전용 token `METRICS_REGISTRY`
|
|
207
236
|
- `MetricsService` 및 `counter(...)`, `gauge(...)`, `histogram(...)`, `getRegistry()`
|
|
208
237
|
- `METER_PROVIDER` (Token)
|
|
209
238
|
- `PrometheusMeterProvider`
|
|
210
239
|
- Meter abstraction type: `MeterProvider`, `MeterCounter`, `MeterGauge`, `MeterHistogram`
|
|
211
240
|
- `HttpMetricsMiddleware` 및 HTTP path-label 옵션 타입
|
|
212
241
|
- `provider`(현재는 `'prometheus'`만 지원), module-level `middleware`, endpoint-scoped `endpointMiddleware`를 포함한 module option
|
|
213
|
-
- `prom-client
|
|
242
|
+
- `prom-client`에서 re-export한 `Registry`
|
|
214
243
|
|
|
215
244
|
### 운영 기본값
|
|
216
245
|
|
|
217
246
|
- `path`의 기본값은 `'/metrics'`입니다. `''`를 포함한 모든 문자열 path는 scrape endpoint를 노출하며, `path: false`로만 scrape endpoint를 완전히 비활성화할 수 있습니다.
|
|
218
|
-
- `registry
|
|
247
|
+
- bootstrap `METRICS_REGISTRY`와 legacy `registry` option을 모두 제공하지 않으면 application bootstrap마다 fresh isolated Registry, `MetricsService`, meter provider, telemetry collector set을 소유합니다.
|
|
248
|
+
- Bootstrap `METRICS_REGISTRY` provider는 legacy `registry` option보다 우선하며, 같은 token을 가진 관련 없는 module provider는 metrics ownership을 구성하지 않습니다.
|
|
219
249
|
- scrape response는 active Registry의 Prometheus content type과 Registry contents를 사용합니다.
|
|
220
250
|
- `defaultMetrics`의 기본값은 `true`이며, `defaultMetrics: false`로 해당 Registry의 Prometheus 기본 프로세스/Node.js collector를 끌 수 있습니다.
|
|
221
251
|
- `endpointMiddleware`는 class-based route-scoped middleware를 스크레이프 엔드포인트에만 바인딩합니다. HTTP 계측이 활성화된 경우 endpoint middleware 실패는 내장 HTTP collector에 집계됩니다.
|
|
222
252
|
- HTTP 메트릭은 `http: true` 또는 `http` 옵션 객체를 전달한 경우에만 설치되며, 설치된 뒤에는 기본적으로 템플릿 기반 경로 라벨 정규화를 사용합니다.
|
|
223
|
-
- 내장 HTTP
|
|
253
|
+
- `http.durationHistogramBuckets`는 내장 HTTP request duration histogram bucket을 명시적인 초 단위 경계로 대체합니다.
|
|
254
|
+
- 내장 HTTP collector는 같은 Registry를 공유하는 모듈 인스턴스 사이에서 framework-owned이고 예상 label schema 및 일치하는 HTTP instrumentation configuration을 가진 경우에만 재사용됩니다. 플랫폼 텔레메트리 Gauge는 framework-owned이고 예상 label schema를 가진 경우에만 재사용되며, 커스텀 애플리케이션 메트릭 이름 충돌은 Prometheus의 중복 이름 실패 동작을 유지합니다.
|
|
255
|
+
- Shared Registry 텔레메트리 refresh는 소유 metrics module이 하나라도 active인 동안 유지되고 마지막 module이 종료되면 원래 `metrics()` 함수를 복원합니다.
|
|
224
256
|
- raw path 라벨은 `allowUnsafeRawPathLabelMode: true`를 명시한 bounded internal route에서만 사용해야 합니다.
|
|
225
257
|
- 플랫폼 텔레메트리는 `PLATFORM_SHELL`이 실제로 누락된 경우에만 생략되며, 그 외 resolve 실패는 스크레이프를 실패시킵니다.
|
|
226
|
-
- 직전 성공 스크레이프에서 노출된 플랫폼 텔레메트리 시리즈는 `PLATFORM_SHELL`을 사용할 수 없게 된 스크레이프에서 제거됩니다.
|
|
258
|
+
- 직전 성공 스크레이프에서 노출된 플랫폼 텔레메트리 시리즈는 `PLATFORM_SHELL`을 사용할 수 없게 된 스크레이프 또는 이후 module instance가 재사용된 Registry를 다른 플랫폼 snapshot으로 갱신한 스크레이프에서 제거됩니다.
|
|
227
259
|
|
|
228
260
|
## 관련 패키지
|
|
229
261
|
|
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ pnpm add @fluojs/metrics
|
|
|
22
22
|
|
|
23
23
|
## Requirements
|
|
24
24
|
|
|
25
|
-
`@fluojs/metrics`
|
|
25
|
+
`@fluojs/metrics` requires Node.js `>=24.0.0 <27` as its package-owned support contract.
|
|
26
26
|
|
|
27
27
|
## When to Use
|
|
28
28
|
|
|
@@ -44,14 +44,15 @@ class AppModule {}
|
|
|
44
44
|
|
|
45
45
|
`MetricsModule.forRoot()` exposes `GET /metrics` by default. Pass `http: true` (or an `http` options object) when you want the module to install HTTP request instrumentation middleware. When HTTP instrumentation is enabled, the module records request totals, error counts, and request duration. For production deployments, make the scrape endpoint boundary explicit: either disable it with `path: false` until a platform-level proxy is in place, or attach dedicated endpoint middleware.
|
|
46
46
|
|
|
47
|
-
The scrape endpoint returns the active `prom-client` registry output with that registry's Prometheus content type. `MetricsModule.forRoot()` creates an isolated registry for each application bootstrap unless
|
|
47
|
+
The scrape endpoint returns the active `prom-client` registry output with that registry's Prometheus content type. `MetricsModule.forRoot()` creates an isolated registry for each application bootstrap unless the bootstrap configures `METRICS_REGISTRY` or the legacy `registry` option is supplied; reusing the same dynamic module class for another bootstrap receives fresh isolated metrics state. Configure a shared `Registry` at bootstrap only when framework metrics and application-defined metrics intentionally share one scrape surface.
|
|
48
48
|
|
|
49
49
|
## Public Responsibilities
|
|
50
50
|
|
|
51
51
|
| Surface | Responsibility | Boundary |
|
|
52
52
|
| --- | --- | --- |
|
|
53
53
|
| `MetricsModule.forRoot(...)` | Wires the Prometheus scrape endpoint, default metrics, optional HTTP instrumentation, platform telemetry, and registry ownership. | `provider` currently accepts only `'prometheus'`; `path: false` disables the scrape route and route-scoped endpoint middleware. |
|
|
54
|
-
| `MetricsService` | Application-facing facade for custom `Counter`, `Gauge`, and `Histogram` metrics on the active registry, plus `getRegistry()` for deliberate advanced registry sharing. | Use collector helpers for business/application metrics. Use `getRegistry()` only when an integration must hand the active `prom-client` Registry to code that cannot receive `
|
|
54
|
+
| `MetricsService` | Application-facing facade for custom `Counter`, `Gauge`, and `Histogram` metrics on the active registry, plus `getRegistry()` for deliberate advanced registry sharing. | `MetricsService` is non-global: inject it from a module that directly imports a `MetricsModule.forRoot(...)` registration or imports a module that re-exports `MetricsService`; unrelated sibling modules do not receive it automatically. Use collector helpers for business/application metrics. Use `getRegistry()` only when an integration must hand the active `prom-client` Registry to code that cannot receive `METRICS_REGISTRY` at bootstrap. |
|
|
55
|
+
| `METRICS_REGISTRY` | Bootstrap provider token for a shared `prom-client` Registry. | A provider supplied to `bootstrapApplication()` takes ownership over the module's legacy `registry` option. |
|
|
55
56
|
| `Registry` | Re-export of `prom-client`'s `Registry` constructor for shared-registry setups. | It is the same Prometheus registry implementation; duplicate metric names still fail according to Prometheus semantics. |
|
|
56
57
|
| `METER_PROVIDER` / `PrometheusMeterProvider` / meter types | Low-level meter bridge for first-party package integrations that need a provider token or backend-neutral counter/gauge/histogram facade. | Application code usually does not need this token unless it is composing package-level integrations; the only bundled provider backend today is Prometheus. |
|
|
57
58
|
| `middleware` | Module-level middleware that participates in the module middleware chain after framework HTTP metrics and endpoint-scoped middleware. | It is not route-scoped; use `endpointMiddleware` when only the scrape route should be protected. |
|
|
@@ -82,6 +83,21 @@ MetricsModule.forRoot({
|
|
|
82
83
|
});
|
|
83
84
|
```
|
|
84
85
|
|
|
86
|
+
### Configure HTTP duration histogram buckets
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
MetricsModule.forRoot({
|
|
90
|
+
http: {
|
|
91
|
+
durationHistogramBuckets: [0.01, 0.05, 0.1, 0.5, 1, 5],
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`durationHistogramBuckets` replaces the built-in HTTP request duration histogram's
|
|
97
|
+
`prom-client` defaults. Values are measured in seconds and must fit the latency
|
|
98
|
+
range you intend to alert on. Each boundary must be finite and strictly increasing;
|
|
99
|
+
invalid configuration is rejected during setup.
|
|
100
|
+
|
|
85
101
|
### Protect or disable the metrics endpoint
|
|
86
102
|
|
|
87
103
|
```ts
|
|
@@ -110,7 +126,7 @@ MetricsModule.forRoot({
|
|
|
110
126
|
|
|
111
127
|
### Create custom metrics once and reuse them
|
|
112
128
|
|
|
113
|
-
`MetricsService.counter(...)`, `gauge(...)`, and `histogram(...)` create Prometheus collectors on the active registry. Create each custom metric once during provider construction or application startup, then reuse the returned collector when business actions occur.
|
|
129
|
+
`MetricsService.counter(...)`, `gauge(...)`, and `histogram(...)` create Prometheus collectors on the active registry. `MetricsService` is non-global: the provider or controller that injects it must belong to a module that directly imports a `MetricsModule.forRoot(...)` registration or imports a module that re-exports `MetricsService`; unrelated sibling modules do not receive it automatically. Create each custom metric once during provider construction or application startup, then reuse the returned collector when business actions occur.
|
|
114
130
|
|
|
115
131
|
```ts
|
|
116
132
|
import { Inject } from '@fluojs/core';
|
|
@@ -135,30 +151,43 @@ class OrdersService {
|
|
|
135
151
|
|
|
136
152
|
Calling `MetricsService.counter(...)` again with the same name recreates the collector and follows Prometheus' duplicate-name failure behavior. Store and reuse the collector instead of creating it inside each request or command handler.
|
|
137
153
|
|
|
138
|
-
`MetricsService.getRegistry()` returns the same active `prom-client` Registry used by the module scrape endpoint, built-in HTTP collectors, platform telemetry, and custom collectors created through the service.
|
|
154
|
+
`MetricsService.getRegistry()` returns the same active `prom-client` Registry used by the module scrape endpoint, built-in HTTP collectors, platform telemetry, and custom collectors created through the service. When you own the bootstrap, configure an explicit shared registry with the public `METRICS_REGISTRY` token. Use `getRegistry()` for advanced integrations that receive `MetricsService` through DI and need to register a third-party Prometheus collector on the already active registry.
|
|
139
155
|
|
|
140
156
|
### Share one registry for framework and app metrics
|
|
141
157
|
|
|
158
|
+
If your application chooses to import raw collectors such as `Counter` or
|
|
159
|
+
`Registry` directly from `prom-client`, add it to your application's dependencies:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
pnpm add prom-client
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`@fluojs/metrics` uses `prom-client` internally, but its dependency does not make
|
|
166
|
+
`prom-client` a supported transitive import for your application. The setup below
|
|
167
|
+
uses the `Registry` re-export from `@fluojs/metrics`, so it does not require that
|
|
168
|
+
direct dependency.
|
|
169
|
+
|
|
142
170
|
```ts
|
|
143
171
|
import { Module } from '@fluojs/core';
|
|
144
|
-
import {
|
|
145
|
-
import {
|
|
172
|
+
import { METRICS_REGISTRY, MetricsModule, Registry } from '@fluojs/metrics';
|
|
173
|
+
import { bootstrapApplication } from '@fluojs/runtime';
|
|
146
174
|
|
|
147
175
|
const registry = new Registry();
|
|
148
176
|
|
|
149
|
-
new Counter({
|
|
150
|
-
name: 'orders_total',
|
|
151
|
-
help: 'Total orders processed',
|
|
152
|
-
registers: [registry],
|
|
153
|
-
});
|
|
154
|
-
|
|
155
177
|
@Module({
|
|
156
|
-
imports: [MetricsModule.forRoot({ http: true
|
|
178
|
+
imports: [MetricsModule.forRoot({ http: true })],
|
|
157
179
|
})
|
|
158
180
|
class AppModule {}
|
|
181
|
+
|
|
182
|
+
const app = await bootstrapApplication({
|
|
183
|
+
rootModule: AppModule,
|
|
184
|
+
providers: [{ provide: METRICS_REGISTRY, useValue: registry }],
|
|
185
|
+
});
|
|
159
186
|
```
|
|
160
187
|
|
|
161
|
-
|
|
188
|
+
`Registry` is re-exported by `@fluojs/metrics`, so this setup needs no direct `prom-client` dependency. Create application collectors through the `MetricsService` pattern above.
|
|
189
|
+
|
|
190
|
+
When multiple metrics module instances intentionally share the same registry, built-in HTTP metrics reuse the existing `http_requests_total`, `http_errors_total`, and `http_request_duration_seconds` collectors instead of registering duplicate framework metrics only when their framework ownership, label schema, and effective HTTP instrumentation configuration match. Compatibility includes `pathLabelMode`, the exact `pathLabelNormalizer` function reference, `unknownPathLabel` fallback semantics, and ordered `durationHistogramBuckets` values, so incompatible module instances fail fast instead of mixing different HTTP series policies into one collector set. Built-in platform telemetry gauges follow the same ownership rule: module-created `fluo_component_ready`, `fluo_component_health`, and `fluo_metrics_registry_mode` gauges are reused only when their framework ownership and label schema match. Platform telemetry state is tracked per reused registry, so a later scrape replaces stale module-owned component readiness and health series from an earlier module instance before metrics are returned. The registry scrape wrapper keeps using the latest active module registration and restores the Registry's original `metrics()` function after the last registration closes. Application-defined duplicate names still fail fast.
|
|
162
191
|
|
|
163
192
|
### Duplicate metric names still fail fast
|
|
164
193
|
|
|
@@ -170,9 +199,9 @@ The module emits fluo-specific gauges that mirror the platform shell and registe
|
|
|
170
199
|
|
|
171
200
|
- `fluo_component_ready`: `1` when a component is ready, otherwise `0`.
|
|
172
201
|
- `fluo_component_health`: `1` when a component is healthy, otherwise `0`.
|
|
173
|
-
- `fluo_metrics_registry_mode`: `isolated` or `shared`
|
|
202
|
+
- `fluo_metrics_registry_mode`: gauge value `1` with `mode="isolated"` when `MetricsModule.forRoot()` creates its registry, or `mode="shared"` when bootstrap supplies `METRICS_REGISTRY` or the legacy `registry` option is supplied. The label reports the effective registry ownership configuration selected during bootstrap or module registration; it does not infer registry sharing at scrape time.
|
|
174
203
|
|
|
175
|
-
The platform snapshot is refreshed during each scrape, and you can attach environment labels up front.
|
|
204
|
+
The platform snapshot is refreshed during each registry scrape, including advanced `MetricsService.getRegistry().metrics()` scrape paths, and you can attach environment labels up front.
|
|
176
205
|
|
|
177
206
|
```ts
|
|
178
207
|
MetricsModule.forRoot({
|
|
@@ -185,7 +214,7 @@ MetricsModule.forRoot({
|
|
|
185
214
|
|
|
186
215
|
### Runtime platform telemetry scrape contract
|
|
187
216
|
|
|
188
|
-
Platform telemetry refreshes `fluo_component_ready` and `fluo_component_health` on each
|
|
217
|
+
Platform telemetry refreshes `fluo_component_ready` and `fluo_component_health` on each active registry scrape by resolving `PLATFORM_SHELL`, whether the scrape flows through the built-in `/metrics` controller or an advanced custom scraper using `MetricsService.getRegistry()`.
|
|
189
218
|
|
|
190
219
|
- If `PLATFORM_SHELL` is not registered, the scrape still succeeds and omits the platform telemetry series.
|
|
191
220
|
- If `PLATFORM_SHELL` becomes unavailable after the last successful scrape, stale `fluo_component_ready` and `fluo_component_health` series are removed before metrics are returned.
|
|
@@ -204,26 +233,30 @@ MetricsModule.forRoot({
|
|
|
204
233
|
## Public API
|
|
205
234
|
|
|
206
235
|
- `MetricsModule.forRoot(options)`
|
|
236
|
+
- `METRICS_REGISTRY`, the bootstrap-only token for an intentionally shared `Registry`
|
|
207
237
|
- `MetricsService`, including `counter(...)`, `gauge(...)`, `histogram(...)`, and `getRegistry()`
|
|
208
238
|
- `METER_PROVIDER`
|
|
209
239
|
- `PrometheusMeterProvider`
|
|
210
240
|
- Meter abstraction types: `MeterProvider`, `MeterCounter`, `MeterGauge`, and `MeterHistogram`
|
|
211
241
|
- `HttpMetricsMiddleware` and HTTP path-label option types
|
|
212
242
|
- Module options including `provider` (currently only `'prometheus'`), module-level `middleware`, and endpoint-scoped `endpointMiddleware`
|
|
213
|
-
- `Registry
|
|
243
|
+
- `Registry`, re-exported from `prom-client`
|
|
214
244
|
|
|
215
245
|
### Operational defaults
|
|
216
246
|
|
|
217
247
|
- `path` defaults to `'/metrics'`, any string path including `''` exposes a scrape endpoint, and `path: false` disables the scrape endpoint entirely.
|
|
218
|
-
- When `registry` is
|
|
248
|
+
- When neither bootstrap `METRICS_REGISTRY` nor the legacy `registry` option is supplied, each application bootstrap owns a fresh isolated registry, `MetricsService`, meter provider, and telemetry collector set.
|
|
249
|
+
- A bootstrap `METRICS_REGISTRY` provider takes precedence over the legacy `registry` option; an unrelated module provider with the same token does not configure metrics ownership.
|
|
219
250
|
- The scrape response uses the active registry's Prometheus content type and registry contents.
|
|
220
251
|
- `defaultMetrics` defaults to `true`, and `defaultMetrics: false` disables Prometheus default process and Node.js collectors for that registry.
|
|
221
252
|
- `endpointMiddleware` binds class-based route-scoped middleware only to the scrape endpoint; with HTTP instrumentation enabled, endpoint middleware failures are counted by the built-in HTTP collectors.
|
|
222
253
|
- HTTP metrics are installed only when `http: true` or an `http` options object is provided, and then default to template-normalized path labels.
|
|
223
|
-
-
|
|
254
|
+
- `http.durationHistogramBuckets` replaces the built-in HTTP request duration histogram buckets with explicit second-based boundaries.
|
|
255
|
+
- Built-in HTTP collectors are reused when module instances share one registry only if they are framework-owned, have the expected label schema, and use matching HTTP instrumentation configuration; platform telemetry gauges are reused only if they are framework-owned and have the expected label schema; custom application metric name collisions keep Prometheus' duplicate-name failure behavior.
|
|
256
|
+
- Shared Registry telemetry refresh remains installed while any owning metrics module is active and restores the original `metrics()` function after the last module closes.
|
|
224
257
|
- Raw path labels require `allowUnsafeRawPathLabelMode: true` and should stay limited to bounded internal routes.
|
|
225
258
|
- Platform telemetry is omitted only when `PLATFORM_SHELL` is genuinely missing; other resolution failures fail the scrape.
|
|
226
|
-
- Stale platform telemetry series are removed when `PLATFORM_SHELL` becomes unavailable after the last successful scrape.
|
|
259
|
+
- Stale platform telemetry series are removed when `PLATFORM_SHELL` becomes unavailable after the last successful scrape or when a later module instance refreshes a reused registry with a different platform snapshot.
|
|
227
260
|
|
|
228
261
|
## Related Packages
|
|
229
262
|
|
|
@@ -13,6 +13,8 @@ export interface HttpMetricsPathLabelContext {
|
|
|
13
13
|
export type HttpMetricsPathLabelNormalizer = (context: HttpMetricsPathLabelContext) => string;
|
|
14
14
|
/** Options that tune HTTP request metric label generation. */
|
|
15
15
|
export interface HttpMetricsMiddlewareOptions {
|
|
16
|
+
/** Duration buckets in seconds for the built-in HTTP request histogram. */
|
|
17
|
+
durationHistogramBuckets?: readonly number[];
|
|
16
18
|
pathLabelMode?: HttpMetricsPathLabelMode;
|
|
17
19
|
pathLabelNormalizer?: HttpMetricsPathLabelNormalizer;
|
|
18
20
|
unknownPathLabel?: string;
|
|
@@ -28,6 +30,16 @@ export declare class HttpMetricsMiddleware implements Middleware {
|
|
|
28
30
|
private readonly pathLabelMode;
|
|
29
31
|
private readonly pathLabelNormalizer?;
|
|
30
32
|
private readonly unknownPathLabel;
|
|
33
|
+
/**
|
|
34
|
+
* Create the built-in HTTP request collectors in a Prometheus registry.
|
|
35
|
+
*
|
|
36
|
+
* @param registry Registry that owns or reuses the built-in HTTP collectors.
|
|
37
|
+
* @param options HTTP metric label and duration histogram configuration.
|
|
38
|
+
* @throws {Error} When raw path labels are configured without the explicit unsafe opt-in.
|
|
39
|
+
* @throws {Error} When duration histogram bucket boundaries are not finite and strictly increasing.
|
|
40
|
+
* @throws {Error} When an application-owned collector uses a built-in HTTP collector name.
|
|
41
|
+
* @throws {Error} When a reused framework collector has a different label schema or HTTP instrumentation configuration.
|
|
42
|
+
*/
|
|
31
43
|
constructor(registry: Registry, options?: HttpMetricsMiddlewareOptions);
|
|
32
44
|
private resolvePathLabel;
|
|
33
45
|
handle(context: MiddlewareContext, next: Next): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-metrics-middleware.d.ts","sourceRoot":"","sources":["../src/http-metrics-middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,EAAsB,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"http-metrics-middleware.d.ts","sourceRoot":"","sources":["../src/http-metrics-middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,iBAAiB,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAC1F,OAAO,EAAsB,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AA6BhE,oEAAoE;AACpE,MAAM,MAAM,wBAAwB,GAAG,KAAK,GAAG,UAAU,CAAC;AAE1D,wDAAwD;AACxD,MAAM,WAAW,2BAA2B;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,gBAAgB,CAAC;CAC3B;AAED,2EAA2E;AAC3E,MAAM,MAAM,8BAA8B,GAAG,CAAC,OAAO,EAAE,2BAA2B,KAAK,MAAM,CAAC;AAE9F,8DAA8D;AAC9D,MAAM,WAAW,4BAA4B;IAC3C,2EAA2E;IAC3E,wBAAwB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,mBAAmB,CAAC,EAAE,8BAA8B,CAAC;IACrD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAsBD;;GAEG;AACH,qBAAa,qBAAsB,YAAW,UAAU;IACtD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAoB;IAChD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAsB;IACtD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA2B;IACzD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAiC;IACtE,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAE1C;;;;;;;;;OASG;gBACS,QAAQ,EAAE,QAAQ,EAAE,OAAO,GAAE,4BAAiC;IAuB1E,OAAO,CAAC,gBAAgB;IAmBlB,MAAM,CAAC,OAAO,EAAE,iBAAiB,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBnE,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,oBAAoB;CAsB7B"}
|
|
@@ -2,6 +2,7 @@ import { Counter, Histogram } from 'prom-client';
|
|
|
2
2
|
import { createPrometheusCounter, createPrometheusHistogram } from './providers/prometheus-metrics-factory.js';
|
|
3
3
|
const FRAMEWORK_HTTP_COUNTERS = new WeakSet();
|
|
4
4
|
const FRAMEWORK_HTTP_HISTOGRAMS = new WeakSet();
|
|
5
|
+
const FRAMEWORK_HTTP_COLLECTOR_CONFIGURATION = new WeakMap();
|
|
5
6
|
|
|
6
7
|
/** Strategy used to label request paths in emitted HTTP metrics. */
|
|
7
8
|
|
|
@@ -37,28 +38,37 @@ export class HttpMetricsMiddleware {
|
|
|
37
38
|
pathLabelMode;
|
|
38
39
|
pathLabelNormalizer;
|
|
39
40
|
unknownPathLabel;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create the built-in HTTP request collectors in a Prometheus registry.
|
|
44
|
+
*
|
|
45
|
+
* @param registry Registry that owns or reuses the built-in HTTP collectors.
|
|
46
|
+
* @param options HTTP metric label and duration histogram configuration.
|
|
47
|
+
* @throws {Error} When raw path labels are configured without the explicit unsafe opt-in.
|
|
48
|
+
* @throws {Error} When duration histogram bucket boundaries are not finite and strictly increasing.
|
|
49
|
+
* @throws {Error} When an application-owned collector uses a built-in HTTP collector name.
|
|
50
|
+
* @throws {Error} When a reused framework collector has a different label schema or HTTP instrumentation configuration.
|
|
51
|
+
*/
|
|
40
52
|
constructor(registry, options = {}) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.
|
|
45
|
-
this.pathLabelNormalizer = options.pathLabelNormalizer;
|
|
46
|
-
this.unknownPathLabel = options.unknownPathLabel ?? 'UNKNOWN';
|
|
53
|
+
const collectorConfiguration = resolveHttpMetricsCollectorConfiguration(options);
|
|
54
|
+
this.pathLabelMode = collectorConfiguration.pathLabelMode;
|
|
55
|
+
this.pathLabelNormalizer = collectorConfiguration.pathLabelNormalizer;
|
|
56
|
+
this.unknownPathLabel = collectorConfiguration.unknownPathLabel;
|
|
47
57
|
this.requestsTotal = getOrCreateHttpCounter(registry, {
|
|
48
58
|
help: 'Total number of HTTP requests',
|
|
49
59
|
labelNames: ['method', 'path', 'status'],
|
|
50
60
|
name: 'http_requests_total'
|
|
51
|
-
});
|
|
61
|
+
}, collectorConfiguration);
|
|
52
62
|
this.errorsTotal = getOrCreateHttpCounter(registry, {
|
|
53
63
|
help: 'Total number of HTTP error responses (4xx/5xx)',
|
|
54
64
|
labelNames: ['method', 'path', 'status'],
|
|
55
65
|
name: 'http_errors_total'
|
|
56
|
-
});
|
|
66
|
+
}, collectorConfiguration);
|
|
57
67
|
this.requestDuration = getOrCreateHttpHistogram(registry, {
|
|
58
68
|
help: 'HTTP request duration in seconds',
|
|
59
69
|
labelNames: ['method', 'path', 'status'],
|
|
60
70
|
name: 'http_request_duration_seconds'
|
|
61
|
-
});
|
|
71
|
+
}, collectorConfiguration);
|
|
62
72
|
}
|
|
63
73
|
resolvePathLabel(request) {
|
|
64
74
|
if (this.pathLabelNormalizer) {
|
|
@@ -119,13 +129,14 @@ export class HttpMetricsMiddleware {
|
|
|
119
129
|
}
|
|
120
130
|
}
|
|
121
131
|
}
|
|
122
|
-
function getOrCreateHttpCounter(registry, config) {
|
|
132
|
+
function getOrCreateHttpCounter(registry, config, collectorConfiguration) {
|
|
123
133
|
const existing = registry.getSingleMetric(config.name);
|
|
124
134
|
if (existing instanceof Counter) {
|
|
125
135
|
if (!FRAMEWORK_HTTP_COUNTERS.has(existing)) {
|
|
126
136
|
throw new Error(`Metric name "${config.name}" is already registered by the application. Built-in HTTP metrics require framework-owned collectors.`);
|
|
127
137
|
}
|
|
128
138
|
assertHttpMetricLabelSchema(existing, config);
|
|
139
|
+
assertHttpMetricConfiguration(existing, config.name, collectorConfiguration);
|
|
129
140
|
return existing;
|
|
130
141
|
}
|
|
131
142
|
const counter = createPrometheusCounter(registry, {
|
|
@@ -134,23 +145,29 @@ function getOrCreateHttpCounter(registry, config) {
|
|
|
134
145
|
name: config.name
|
|
135
146
|
});
|
|
136
147
|
FRAMEWORK_HTTP_COUNTERS.add(counter);
|
|
148
|
+
FRAMEWORK_HTTP_COLLECTOR_CONFIGURATION.set(counter, collectorConfiguration);
|
|
137
149
|
return counter;
|
|
138
150
|
}
|
|
139
|
-
function getOrCreateHttpHistogram(registry, config) {
|
|
151
|
+
function getOrCreateHttpHistogram(registry, config, collectorConfiguration) {
|
|
140
152
|
const existing = registry.getSingleMetric(config.name);
|
|
141
153
|
if (existing instanceof Histogram) {
|
|
142
154
|
if (!FRAMEWORK_HTTP_HISTOGRAMS.has(existing)) {
|
|
143
155
|
throw new Error(`Metric name "${config.name}" is already registered by the application. Built-in HTTP metrics require framework-owned collectors.`);
|
|
144
156
|
}
|
|
145
157
|
assertHttpMetricLabelSchema(existing, config);
|
|
158
|
+
assertHttpMetricConfiguration(existing, config.name, collectorConfiguration);
|
|
146
159
|
return existing;
|
|
147
160
|
}
|
|
148
161
|
const histogram = createPrometheusHistogram(registry, {
|
|
149
162
|
help: config.help,
|
|
150
163
|
labelNames: [...config.labelNames],
|
|
151
|
-
name: config.name
|
|
164
|
+
name: config.name,
|
|
165
|
+
...(collectorConfiguration.durationHistogramBuckets ? {
|
|
166
|
+
buckets: [...collectorConfiguration.durationHistogramBuckets]
|
|
167
|
+
} : {})
|
|
152
168
|
});
|
|
153
169
|
FRAMEWORK_HTTP_HISTOGRAMS.add(histogram);
|
|
170
|
+
FRAMEWORK_HTTP_COLLECTOR_CONFIGURATION.set(histogram, collectorConfiguration);
|
|
154
171
|
return histogram;
|
|
155
172
|
}
|
|
156
173
|
function assertHttpMetricLabelSchema(metric, config) {
|
|
@@ -160,6 +177,44 @@ function assertHttpMetricLabelSchema(metric, config) {
|
|
|
160
177
|
throw new Error(`Metric name "${config.name}" is already registered with labels [${registeredLabels}]. Built-in HTTP metrics require labels [${expectedLabels}].`);
|
|
161
178
|
}
|
|
162
179
|
}
|
|
180
|
+
function resolveHttpMetricsCollectorConfiguration(options) {
|
|
181
|
+
if (options.pathLabelMode === 'raw' && options.allowUnsafeRawPathLabelMode !== true) {
|
|
182
|
+
throw new Error('HttpMetricsMiddleware pathLabelMode "raw" is disabled by default. Pass allowUnsafeRawPathLabelMode: true only when you have bounded path cardinality.');
|
|
183
|
+
}
|
|
184
|
+
let previousDurationHistogramBucket;
|
|
185
|
+
for (const durationHistogramBucket of options.durationHistogramBuckets ?? []) {
|
|
186
|
+
if (!Number.isFinite(durationHistogramBucket) || previousDurationHistogramBucket !== undefined && durationHistogramBucket <= previousDurationHistogramBucket) {
|
|
187
|
+
throw new Error('HttpMetricsMiddleware durationHistogramBuckets must contain finite, strictly increasing boundaries.');
|
|
188
|
+
}
|
|
189
|
+
previousDurationHistogramBucket = durationHistogramBucket;
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
durationHistogramBuckets: options.durationHistogramBuckets ? [...options.durationHistogramBuckets] : undefined,
|
|
193
|
+
pathLabelMode: options.pathLabelMode ?? 'template',
|
|
194
|
+
pathLabelNormalizer: options.pathLabelNormalizer,
|
|
195
|
+
unknownPathLabel: options.unknownPathLabel ?? 'UNKNOWN'
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function assertHttpMetricConfiguration(metric, metricName, expected) {
|
|
199
|
+
const registered = FRAMEWORK_HTTP_COLLECTOR_CONFIGURATION.get(metric);
|
|
200
|
+
if (!registered) {
|
|
201
|
+
throw new Error(`Metric name "${metricName}" is already registered as a framework-owned HTTP collector without HTTP instrumentation configuration metadata. Built-in HTTP metrics require matching HTTP collector configuration before reuse.`);
|
|
202
|
+
}
|
|
203
|
+
if (hasSameHttpMetricConfiguration(registered, expected)) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
throw new Error(`Metric name "${metricName}" is already registered with framework HTTP collector configuration ${describeHttpMetricConfiguration(registered)}. Built-in HTTP metrics require matching HTTP collector configuration before reuse; received ${describeHttpMetricConfiguration(expected)}.`);
|
|
207
|
+
}
|
|
208
|
+
function hasSameHttpMetricConfiguration(left, right) {
|
|
209
|
+
return hasSameDurationHistogramBuckets(left.durationHistogramBuckets, right.durationHistogramBuckets) && left.pathLabelMode === right.pathLabelMode && left.pathLabelNormalizer === right.pathLabelNormalizer && left.unknownPathLabel === right.unknownPathLabel;
|
|
210
|
+
}
|
|
211
|
+
function hasSameDurationHistogramBuckets(left, right) {
|
|
212
|
+
return left === right || left !== undefined && right !== undefined && left.length === right.length && left.every((bucket, index) => bucket === right[index]);
|
|
213
|
+
}
|
|
214
|
+
function describeHttpMetricConfiguration(configuration) {
|
|
215
|
+
const durationHistogramBuckets = configuration.durationHistogramBuckets ? `, durationHistogramBuckets=[${configuration.durationHistogramBuckets.join(',')}]` : '';
|
|
216
|
+
return `pathLabelMode="${configuration.pathLabelMode}", pathLabelNormalizer=${configuration.pathLabelNormalizer ? 'custom' : 'none'}, unknownPathLabel="${configuration.unknownPathLabel}"${durationHistogramBuckets}`;
|
|
217
|
+
}
|
|
163
218
|
function normalizePathToTemplate(path, params) {
|
|
164
219
|
if (!path) {
|
|
165
220
|
return '/';
|
package/dist/metrics-module.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { type Token } from '@fluojs/core';
|
|
1
2
|
import { type Middleware, type MiddlewareLike } from '@fluojs/http';
|
|
2
3
|
import { type ModuleType } from '@fluojs/runtime';
|
|
3
4
|
import { type Registry } from 'prom-client';
|
|
4
5
|
import { type HttpMetricsPathLabelMode, type HttpMetricsPathLabelNormalizer } from './http-metrics-middleware.js';
|
|
5
6
|
/** HTTP-specific metric labeling options exposed by `MetricsModule.forRoot(...)`. */
|
|
6
7
|
export interface MetricsHttpOptions {
|
|
8
|
+
/** Duration buckets in seconds for the built-in HTTP request histogram. */
|
|
9
|
+
durationHistogramBuckets?: readonly number[];
|
|
7
10
|
/** How request paths are converted into Prometheus label values. Defaults to route templates. */
|
|
8
11
|
pathLabelMode?: HttpMetricsPathLabelMode;
|
|
9
12
|
/** Custom path-label normalizer for bounded application-specific label values. */
|
|
@@ -36,9 +39,11 @@ export interface MetricsModuleOptions {
|
|
|
36
39
|
/** Instance label value. Defaults to `local`. */
|
|
37
40
|
instance?: string;
|
|
38
41
|
};
|
|
39
|
-
/**
|
|
42
|
+
/** Legacy shared-registry fallback. Prefer the `METRICS_REGISTRY` bootstrap provider. */
|
|
40
43
|
registry?: Registry;
|
|
41
44
|
}
|
|
45
|
+
/** Bootstrap provider token for a Registry shared by metrics module instances. */
|
|
46
|
+
export declare const METRICS_REGISTRY: Token<Registry>;
|
|
42
47
|
/** Module entry point that exposes `/metrics` and optional HTTP/runtime telemetry. */
|
|
43
48
|
export declare class MetricsModule {
|
|
44
49
|
private static registeredRegistries;
|
|
@@ -49,11 +54,10 @@ export declare class MetricsModule {
|
|
|
49
54
|
* ```ts
|
|
50
55
|
* MetricsModule.forRoot({
|
|
51
56
|
* http: { pathLabelMode: 'template' },
|
|
52
|
-
* registry: new Registry(),
|
|
53
57
|
* });
|
|
54
58
|
* ```
|
|
55
59
|
*
|
|
56
|
-
* @param options Metrics endpoint,
|
|
60
|
+
* @param options Metrics endpoint, HTTP middleware, and runtime telemetry configuration.
|
|
57
61
|
* @returns A runtime module that exposes metrics through the configured path.
|
|
58
62
|
*/
|
|
59
63
|
static forRoot(options?: MetricsModuleOptions): ModuleType;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics-module.d.ts","sourceRoot":"","sources":["../src/metrics-module.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"metrics-module.d.ts","sourceRoot":"","sources":["../src/metrics-module.ts"],"names":[],"mappings":"AAGA,OAAO,EAAU,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,EAA8B,KAAK,UAAU,EAAE,KAAK,cAAc,EAAuB,MAAM,cAAc,CAAC;AACrH,OAAO,EAEL,KAAK,UAAU,EAIhB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAgE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE1G,OAAO,EAGL,KAAK,wBAAwB,EAC7B,KAAK,8BAA8B,EACpC,MAAM,8BAA8B,CAAC;AAMtC,qFAAqF;AACrF,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,wBAAwB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,iGAAiG;IACjG,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,kFAAkF;IAClF,mBAAmB,CAAC,EAAE,8BAA8B,CAAC;IACrD,+DAA+D;IAC/D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,2EAA2E;IAC3E,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,kGAAkG;IAClG,IAAI,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAAC;IACpC,yHAAyH;IACzH,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,sHAAsH;IACtH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mGAAmG;IACnG,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;IAC9B,uGAAuG;IACvG,kBAAkB,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,UAAU,CAAC,CAAC;IAC/D,2EAA2E;IAC3E,iBAAiB,CAAC,EAAE;QAClB,iEAAiE;QACjE,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,iDAAiD;QACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,yFAAyF;IACzF,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,kFAAkF;AAClF,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,QAAQ,CAAuC,CAAC;AAErF,sFAAsF;AACtF,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAA2B;IAE9D;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,oBAAyB,GAAG,UAAU;IA+G9D,OAAO,CAAC,MAAM,CAAC,cAAc;CAyB9B"}
|