🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@fluojs/metrics

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@fluojs/metrics - npm Package Compare versions

Comparing version
1.0.0-beta.3
to
1.0.0-beta.4
+15
-1
dist/metrics-module.js

@@ -101,2 +101,3 @@ function _applyDecs(e, t, n, r, o, i) { var a, c, u, s, f, l, p, d = Symbol.metadata || Symbol.for("Symbol.metadata"), m = Object.defineProperty, h = Object.create, y = [h(null), h(null)], v = t.length; function g(t, n, r) { return function (o, i) { n && (i = o, o = e); for (var a = 0; a < t.length; a++) i = t[a].apply(o, r ? [i] : []); return r ? i : o; }; } function b(e, t, n, r) { if ("function" != typeof e && (r || void 0 !== e)) throw new TypeError(t + " must " + (n || "be") + " a function" + (r ? "" : " or undefined")); return e; } function applyDec(e, t, n, r, o, i, u, s, f, l, p) { function d(e) { if (!p(e)) throw new TypeError("Attempted to access private element on non-instance"); } var h = [].concat(t[0]), v = t[3], w = !u, D = 1 === o, S = 3 === o, j = 4 === o, E = 2 === o; function I(t, n, r) { return function (o, i) { return n && (i = o, o = e), r && r(o), P[t].call(o, i); }; } if (!w) { var P = {}, k = [], F = S ? "get" : j || D ? "set" : "value"; if (f ? (l || D ? P = { get: _setFunctionName(function () { return v(this); }, r, "get"), set: function (e) { t[4](this, e); } } : P[F] = v, l || _setFunctionName(P[F], r, E ? "" : F)) : l || (P = Object.getOwnPropertyDescriptor(e, r)), !l && !f) { if ((c = y[+s][r]) && 7 !== (c ^ o)) throw Error("Decorating two elements with the same name (" + P[F].name + ") is not supported yet"); y[+s][r] = o < 3 ? 1 : o; } } for (var N = e, O = h.length - 1; O >= 0; O -= n ? 2 : 1) { var T = b(h[O], "A decorator", "be", !0), z = n ? h[O - 1] : void 0, A = {}, H = { kind: ["field", "accessor", "method", "getter", "setter", "class"][o], name: r, metadata: a, addInitializer: function (e, t) { if (e.v) throw new TypeError("attempted to call addInitializer after decoration was finished"); b(t, "An initializer", "be", !0), i.push(t); }.bind(null, A) }; if (w) c = T.call(z, N, H), A.v = 1, b(c, "class decorators", "return") && (N = c);else if (H.static = s, H.private = f, c = H.access = { has: f ? p.bind() : function (e) { return r in e; } }, j || (c.get = f ? E ? function (e) { return d(e), P.value; } : I("get", 0, d) : function (e) { return e[r]; }), E || S || (c.set = f ? I("set", 0, d) : function (e, t) { e[r] = t; }), N = T.call(z, D ? { get: P.get, set: P.set } : P[F], H), A.v = 1, D) { if ("object" == typeof N && N) (c = b(N.get, "accessor.get")) && (P.get = c), (c = b(N.set, "accessor.set")) && (P.set = c), (c = b(N.init, "accessor.init")) && k.unshift(c);else if (void 0 !== N) throw new TypeError("accessor decorators must return an object with get, set, or init properties or undefined"); } else b(N, (l ? "field" : "method") + " decorators", "return") && (l ? k.unshift(N) : P[F] = N); } return o < 2 && u.push(g(k, s, 1), g(i, s, 0)), l || w || (f ? D ? u.splice(-1, 0, I("get", s), I("set", s)) : u.push(E ? P[F] : b.call.bind(P[F])) : m(e, r, P)), N; } function w(e) { return m(e, d, { configurable: !0, enumerable: !0, value: a }); } return void 0 !== i && (a = i[d]), a = h(null == a ? null : a), f = [], l = function (e) { e && f.push(g(e)); }, p = function (t, r) { for (var i = 0; i < n.length; i++) { var a = n[i], c = a[1], l = 7 & c; if ((8 & c) == t && !l == r) { var p = a[2], d = !!a[3], m = 16 & c; applyDec(t ? e : e.prototype, a, m, d ? "#" + p : _toPropertyKey(p), l, l < 2 ? [] : t ? s = s || [] : u = u || [], f, !!t, d, r, t && d ? function (t) { return _checkInRHS(t) === e; } : o); } } }, p(8, 0), p(0, 0), p(8, 1), p(0, 1), l(u), l(s), c = f, v || w(e), { e: c, get c() { var n = []; return v && [w(e = applyDec(e, [t], r, e.name, 5, n)), g(n, 1)]; } }; }

const REGISTRY_MODE_LABELS = ['mode'];
const FRAMEWORK_PLATFORM_GAUGES = new WeakSet();
const HEALTH_STATUSES = ['healthy', 'unhealthy', 'degraded'];

@@ -115,5 +116,9 @@ const READINESS_STATUSES = ['ready', 'not-ready', 'degraded'];

if (existing instanceof Gauge) {
if (!FRAMEWORK_PLATFORM_GAUGES.has(existing)) {
throw new Error(`Metric name "${config.name}" is already registered by the application. Built-in platform telemetry requires framework-owned gauges.`);
}
assertGaugeLabelSchema(existing, config);
return existing;
}
return new Gauge({
const gauge = new Gauge({
help: config.help,

@@ -124,3 +129,12 @@ labelNames: [...config.labelNames],

});
FRAMEWORK_PLATFORM_GAUGES.add(gauge);
return gauge;
}
function assertGaugeLabelSchema(gauge, config) {
const registeredLabels = (gauge.labelNames ?? []).join(',');
const expectedLabels = config.labelNames.join(',');
if (registeredLabels !== expectedLabels) {
throw new Error(`Metric name "${config.name}" is already registered with labels [${registeredLabels}]. Built-in platform telemetry requires labels [${expectedLabels}].`);
}
}
class RuntimePlatformTelemetry {

@@ -127,0 +141,0 @@ readinessGauge;

+4
-4

@@ -11,3 +11,3 @@ {

],
"version": "1.0.0-beta.3",
"version": "1.0.0-beta.4",
"private": false,

@@ -40,5 +40,5 @@ "license": "MIT",

"prom-client": "^15.1.3",
"@fluojs/di": "^1.0.0-beta.6",
"@fluojs/http": "^1.0.0-beta.9",
"@fluojs/runtime": "^1.0.0-beta.9"
"@fluojs/di": "^1.0.0-beta.7",
"@fluojs/http": "^1.0.0-beta.10",
"@fluojs/runtime": "^1.0.0-beta.12"
},

@@ -45,0 +45,0 @@ "devDependencies": {

@@ -5,3 +5,3 @@ # @fluojs/metrics

fluo 애플리케이션을 위한 Prometheus 메트릭 노출 모듈입니다. `MetricsModule`을 마운트하여 Node.js 기본 메트릭과 선택적인 저지수(low-cardinality) HTTP 요청 모니터링 기능이 포함된 `/metrics` 엔드포인트를 제공합니다.
HTTP metric과 platform telemetry를 포함해 fluo 애플리케이션을 위한 Prometheus metric을 노출합니다.

@@ -26,46 +26,50 @@ ## 목차

- Prometheus나 VictoriaMetrics 수집기에 애플리케이션 및 시스템 메트릭을 내보내야 할 때.
- 수동 계측 없이 자동화된 HTTP 요청 지연 시간 및 횟수 메트릭을 원할 때.
- 메트릭 데이터를 fluo의 런타임 헬스체크 및 준비 상태와 동기화하고 싶을 때.
- 애플리케이션이 Prometheus-compatible scraping을 위한 `/metrics` endpoint를 노출해야 할 때
- 손으로 middleware를 작성하지 않고 HTTP latency와 request count를 계측해야 할 때
- application telemetry를 fluo readiness 및 health state와 맞춰야 할 때
## 빠른 시작
루트 모듈에 `MetricsModule.forRoot()`를 추가하여 기본 `/metrics` 엔드포인트를 활성화합니다. HTTP 요청 수와 지연 시간까지 계측하려면 `http: true` 또는 `http` 옵션 객체를 함께 전달합니다.
```typescript
```ts
import { MetricsModule } from '@fluojs/metrics';
import { Module } from '@fluojs/core';
import { MetricsModule } from '@fluojs/metrics';
@Module({
imports: [
MetricsModule.forRoot({ http: true }),
],
imports: [MetricsModule.forRoot({ http: true })],
})
class AppModule {}
// GET /metrics → Prometheus 텍스트 형식
```
`MetricsModule.forRoot()`는 기본적으로 `GET /metrics`를 노출합니다. `http: true`를 전달한 경우에만 HTTP 요청 계측 미들웨어가 설치됩니다. 운영 환경에서는 이 경계를 명시적으로 다루세요. 플랫폼 프록시/네트워크 제어를 붙이기 전까지 `path: false`로 비활성화하거나, 전용 endpoint middleware를 연결하는 방식을 권장합니다.
`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를 연결할 수 있습니다.
Scrape endpoint는 active `prom-client` Registry output을 해당 Registry의 Prometheus content type으로 반환합니다. `MetricsModule.forRoot()`는 `registry` option을 전달하지 않는 한 격리된 Registry를 생성합니다. framework metric과 application-defined metric이 하나의 scrape surface를 의도적으로 공유해야 할 때만 shared `Registry`를 전달하세요.
## 공통 패턴
### HTTP 라벨 정규화
### HTTP path label 정규화
`MetricsModule`은 HTTP 메트릭을 수집하는 미들웨어를 포함합니다. 기본적으로 경로 라벨을 템플릿 형태(예: `/users/123` → `/users/:id`)로 정규화하여, 라벨 카디널리티(cardinality) 폭발을 방지합니다.
```typescript
```ts
MetricsModule.forRoot({
http: {
pathLabelMode: 'template', // 기본 동작
pathLabelMode: 'template',
unknownPathLabel: 'UNKNOWN',
},
})
});
```
`pathLabelMode: 'raw'`는 이제 안전하지 않은 명시적 opt-in으로 취급됩니다. 경로 공간이 유한하다는 것을 보장할 수 있을 때만 `allowUnsafeRawPathLabelMode: true`와 함께 사용하세요.
`pathLabelMode: 'raw'`는 안전하지 않은 opt-in입니다. 경로 공간이 유한하다는 것을 증명할 수 있을 때만 `allowUnsafeRawPathLabelMode: true`와 함께 사용하세요.
### Custom path label 정규화
```ts
MetricsModule.forRoot({
http: {
pathLabelNormalizer: ({ path }) => (path.startsWith('/api/v1') ? '/api/v1/:resource' : path),
},
});
```
### 메트릭 엔드포인트 보호 또는 비활성화
```typescript
```ts
import { ForbiddenException, type MiddlewareContext, type Next } from '@fluojs/http';

@@ -92,33 +96,20 @@

특수한 경로 매핑이 필요한 경우 커스텀 normalizer를 제공할 수 있습니다.
`endpointMiddleware`는 class-based `@fluojs/http` middleware constructor를 받으며 metrics scrape endpoint에만 바인딩됩니다. middleware function이나 global middleware declaration은 이 option의 패키지 계약이 아닙니다.
```typescript
MetricsModule.forRoot({
http: {
pathLabelNormalizer: ({ path }) => (path.startsWith('/api/v1') ? '/api/v1/:resource' : path),
},
})
```
### Framework metric과 app metric이 하나의 registry를 공유하기
### 공유 Registry (권장)
```ts
import { Counter, Registry } from 'prom-client';
import { MetricsModule } from '@fluojs/metrics';
커스텀 애플리케이션 메트릭을 프레임워크가 제공하는 메트릭과 하나의 `/metrics` 엔드포인트에서 통합하려면, 공유 `Registry` 인스턴스를 전달하세요.
const registry = new Registry();
```typescript
import { Registry, Counter } from 'prom-client';
import { MetricsModule, MetricsService } from '@fluojs/metrics';
const sharedRegistry = new Registry();
// 커스텀 메트릭 등록
const ordersTotal = new Counter({
new Counter({
name: 'orders_total',
help: '처리된 총 주문 수',
registers: [sharedRegistry],
help: 'Total orders processed',
registers: [registry],
});
@Module({
imports: [
MetricsModule.forRoot({ http: true, registry: sharedRegistry }),
],
imports: [MetricsModule.forRoot({ http: true, registry })],
})

@@ -128,7 +119,7 @@ class AppModule {}

여러 `MetricsModule` 인스턴스가 같은 Registry를 의도적으로 공유하는 경우, 내장 HTTP 메트릭은 기존 `http_requests_total`, `http_errors_total`, `http_request_duration_seconds` collector를 재사용합니다. 애플리케이션이 직접 등록한 중복 메트릭 이름은 Prometheus Registry 규칙대로 계속 빠르게 실패합니다.
여러 `MetricsModule` 인스턴스가 같은 Registry를 의도적으로 공유하는 경우, 내장 HTTP 메트릭은 기존 `http_requests_total`, `http_errors_total`, `http_request_duration_seconds` collector를 재사용합니다. 내장 플랫폼 텔레메트리 Gauge도 같은 ownership 규칙을 따릅니다. 모듈이 만든 `fluo_component_ready`, `fluo_component_health`, `fluo_metrics_registry_mode` Gauge는 framework ownership과 label schema가 일치할 때만 재사용합니다. 애플리케이션이 직접 등록한 중복 메트릭 이름은 Prometheus Registry 규칙대로 계속 빠르게 실패합니다.
### 중복 메트릭 이름은 계속 빠르게 실패합니다
Prometheus 메트릭 이름은 하나의 Registry 안에서 고유해야 합니다. 공유 Registry 모드는 애플리케이션 메트릭의 중복 이름을 조용히 덮어쓰지 않고 이 동작을 유지합니다.
Prometheus 메트릭 이름은 하나의 Registry 안에서 고유해야 합니다. 공유 Registry 모드는 애플리케이션 메트릭의 중복 이름을 조용히 덮어쓰지 않고 이 동작을 유지합니다. 애플리케이션이 내장 HTTP collector 또는 플랫폼 텔레메트리 Gauge 이름을 미리 등록한 경우, `MetricsModule.forRoot()`는 app-owned collector를 재사용하지 않고 충돌을 거부합니다.

@@ -141,6 +132,7 @@ ### 런타임 플랫폼 텔레메트리

- `fluo_component_health`: 정상 상태 시 1, 아닐 시 0.
- `fluo_metrics_registry_mode`: active registry mode가 `isolated`인지 `shared`인지 나타냅니다.
이 데이터는 매 스크레이프 시점에 `PLATFORM_SHELL`을 쿼리하여 갱신됩니다. 초기화 시 환경 라벨을 제공할 수 있습니다.
```typescript
```ts
MetricsModule.forRoot({

@@ -151,3 +143,3 @@ platformTelemetry: {

},
})
});
```

@@ -160,3 +152,3 @@

- `PLATFORM_SHELL` 등록 자체가 빠진 경우에는 스크레이프가 계속 성공하고 플랫폼 텔레메트리 시리즈만 생략됩니다.
- 이전 스크레이프에서 플랫폼 텔레메트리를 노출한 뒤 `PLATFORM_SHELL`을 사용할 수 없게 되면, stale `fluo_component_ready` 및 `fluo_component_health` 시리즈를 제거한 뒤 메트릭을 반환합니다.
- 직전 성공 스크레이프에서 플랫폼 텔레메트리를 노출한 뒤 `PLATFORM_SHELL`을 사용할 수 없게 되면, stale `fluo_component_ready` 및 `fluo_component_health` 시리즈를 제거한 뒤 메트릭을 반환합니다.
- 그 외의 `PLATFORM_SHELL` resolve 실패는 조용히 삼키지 않고 스크레이프 실패로 그대로 드러납니다.

@@ -168,6 +160,6 @@

```typescript
```ts
MetricsModule.forRoot({
defaultMetrics: false,
})
});
```

@@ -182,2 +174,3 @@

- `HttpMetricsMiddleware` 및 HTTP path-label 옵션 타입
- `provider`(현재는 `'prometheus'`만 지원)와 endpoint `middleware`를 포함한 module option
- `prom-client`의 `Registry`

@@ -188,9 +181,10 @@

- `path`의 기본값은 `'/metrics'`이며, `path: false`로 스크레이프 엔드포인트를 완전히 비활성화할 수 있습니다.
- scrape response는 active Registry의 Prometheus content type과 Registry contents를 사용합니다.
- `defaultMetrics`의 기본값은 `true`이며, `defaultMetrics: false`로 해당 Registry의 Prometheus 기본 프로세스/Node.js collector를 끌 수 있습니다.
- `endpointMiddleware`는 스크레이프 엔드포인트에만 route-scoped middleware를 바인딩합니다.
- `endpointMiddleware`는 class-based route-scoped middleware를 스크레이프 엔드포인트에만 바인딩합니다.
- HTTP 메트릭은 `http: true` 또는 `http` 옵션 객체를 전달한 경우에만 설치되며, 설치된 뒤에는 기본적으로 템플릿 기반 경로 라벨 정규화를 사용합니다.
- 내장 HTTP collector는 같은 Registry를 공유하는 모듈 인스턴스 사이에서 재사용되며, 커스텀 애플리케이션 메트릭 이름 충돌은 Prometheus의 중복 이름 실패 동작을 유지합니다.
- 내장 HTTP collector와 플랫폼 텔레메트리 Gauge는 같은 Registry를 공유하는 모듈 인스턴스 사이에서 framework-owned이고 예상 label schema를 가진 경우에만 재사용되며, 커스텀 애플리케이션 메트릭 이름 충돌은 Prometheus의 중복 이름 실패 동작을 유지합니다.
- raw path 라벨은 `allowUnsafeRawPathLabelMode: true`를 명시한 bounded internal route에서만 사용해야 합니다.
- 플랫폼 텔레메트리는 `PLATFORM_SHELL`이 실제로 누락된 경우에만 생략되며, 그 외 resolve 실패는 스크레이프를 실패시킵니다.
- 이전에 노출된 플랫폼 텔레메트리 시리즈는 `PLATFORM_SHELL`을 사용할 수 없게 된 스크레이프에서 제거됩니다.
- 직전 성공 스크레이프에서 노출된 플랫폼 텔레메트리 시리즈는 `PLATFORM_SHELL`을 사용할 수 없게 된 스크레이프에서 제거됩니다.

@@ -197,0 +191,0 @@ ## 관련 패키지

@@ -41,4 +41,6 @@ # @fluojs/metrics

`MetricsModule.forRoot()` still exposes `GET /metrics` by default. Pass `http: true` (or an `http` options object) when you want the module to install HTTP request instrumentation middleware. 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.
`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.
The scrape endpoint returns the active `prom-client` registry output with that registry's Prometheus content type. `MetricsModule.forRoot()` creates an isolated registry unless you pass a `registry` option; pass a shared `Registry` only when framework metrics and application-defined metrics intentionally share one scrape surface.
## Common Patterns

@@ -57,4 +59,14 @@

`pathLabelMode: 'raw'` is now treated as an unsafe opt-in. You must pass `allowUnsafeRawPathLabelMode: true` only when you can prove the path space is bounded.
`pathLabelMode: 'raw'` is an unsafe opt-in. You must pass `allowUnsafeRawPathLabelMode: true` only when you can prove the path space is bounded.
### Custom path label normalization
```ts
MetricsModule.forRoot({
http: {
pathLabelNormalizer: ({ path }) => (path.startsWith('/api/v1') ? '/api/v1/:resource' : path),
},
});
```
### Protect or disable the metrics endpoint

@@ -84,2 +96,4 @@

`endpointMiddleware` accepts class-based `@fluojs/http` middleware constructors and binds them only to the metrics scrape endpoint. Middleware functions or global middleware declarations are not the package contract for this option.
### Share one registry for framework and app metrics

@@ -105,7 +119,7 @@

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. Application-defined duplicate names still fail fast.
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. 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. Application-defined duplicate names still fail fast.
### Duplicate metric names still fail fast
Prometheus metric names must stay unique inside a registry. Shared-registry mode keeps that behavior intact instead of silently shadowing metrics.
Prometheus metric names must stay unique inside a registry. Shared-registry mode keeps that behavior intact instead of silently shadowing metrics. If an application predefines a built-in HTTP collector or platform telemetry gauge name, `MetricsModule.forRoot()` rejects the collision instead of reusing an app-owned collector.

@@ -118,2 +132,3 @@ ### Runtime platform telemetry

- `fluo_component_health`: `1` when a component is healthy, otherwise `0`.
- `fluo_metrics_registry_mode`: `isolated` or `shared` for the active registry mode.

@@ -136,3 +151,3 @@ The platform snapshot is refreshed during each scrape, and you can attach environment labels up front.

- If `PLATFORM_SHELL` is not registered, the scrape still succeeds and omits the platform telemetry series.
- If `PLATFORM_SHELL` becomes unavailable after a previous successful scrape, stale `fluo_component_ready` and `fluo_component_health` series are removed before metrics are returned.
- 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.
- If resolving `PLATFORM_SHELL` fails for any other reason, the scrape surfaces that failure instead of swallowing it.

@@ -157,2 +172,3 @@

- `HttpMetricsMiddleware` and HTTP path-label option types
- Module options including `provider` (currently only `'prometheus'`) and endpoint `middleware`
- `Registry` from `prom-client`

@@ -163,9 +179,10 @@

- `path` defaults to `'/metrics'`, and `path: false` disables the scrape endpoint entirely.
- The scrape response uses the active registry's Prometheus content type and registry contents.
- `defaultMetrics` defaults to `true`, and `defaultMetrics: false` disables Prometheus default process and Node.js collectors for that registry.
- `endpointMiddleware` binds route-scoped middleware only to the scrape endpoint.
- `endpointMiddleware` binds class-based route-scoped middleware only to the scrape endpoint.
- HTTP metrics are installed only when `http: true` or an `http` options object is provided, and then default to template-normalized path labels.
- Built-in HTTP collectors are reused when module instances share one registry; custom application metric name collisions keep Prometheus' duplicate-name failure behavior.
- Built-in HTTP collectors and platform telemetry gauges are reused when module instances share one registry only if they are framework-owned and have the expected label schema; custom application metric name collisions keep Prometheus' duplicate-name failure behavior.
- Raw path labels require `allowUnsafeRawPathLabelMode: true` and should stay limited to bounded internal routes.
- Platform telemetry is omitted only when `PLATFORM_SHELL` is genuinely missing; other resolution failures fail the scrape.
- Stale platform telemetry series are removed when `PLATFORM_SHELL` becomes unavailable after a prior successful scrape.
- Stale platform telemetry series are removed when `PLATFORM_SHELL` becomes unavailable after the last successful scrape.

@@ -172,0 +189,0 @@ ## Related Packages