@provide-io/telemetry 0.2.2
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.md +247 -0
- package/dist/backpressure.d.ts +19 -0
- package/dist/backpressure.d.ts.map +1 -0
- package/dist/backpressure.js +51 -0
- package/dist/cardinality.d.ts +15 -0
- package/dist/cardinality.d.ts.map +1 -0
- package/dist/cardinality.js +69 -0
- package/dist/classification.d.ts +29 -0
- package/dist/classification.d.ts.map +1 -0
- package/dist/classification.js +58 -0
- package/dist/config.d.ts +156 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +350 -0
- package/dist/consent.d.ts +11 -0
- package/dist/consent.d.ts.map +1 -0
- package/dist/consent.js +50 -0
- package/dist/context.d.ts +60 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +127 -0
- package/dist/exceptions.d.ts +14 -0
- package/dist/exceptions.d.ts.map +1 -0
- package/dist/exceptions.js +21 -0
- package/dist/fingerprint.d.ts +5 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +50 -0
- package/dist/hash.d.ts +8 -0
- package/dist/hash.d.ts.map +1 -0
- package/dist/hash.js +102 -0
- package/dist/health.d.ts +54 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +102 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +59 -0
- package/dist/logger.d.ts +28 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +254 -0
- package/dist/metrics.d.ts +78 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +238 -0
- package/dist/otel-logs.d.ts +29 -0
- package/dist/otel-logs.d.ts.map +1 -0
- package/dist/otel-logs.js +127 -0
- package/dist/otel-noop.d.ts +13 -0
- package/dist/otel-noop.d.ts.map +1 -0
- package/dist/otel-noop.js +5 -0
- package/dist/otel.d.ts +20 -0
- package/dist/otel.d.ts.map +1 -0
- package/dist/otel.js +80 -0
- package/dist/pii.d.ts +43 -0
- package/dist/pii.d.ts.map +1 -0
- package/dist/pii.js +278 -0
- package/dist/pretty.d.ts +12 -0
- package/dist/pretty.d.ts.map +1 -0
- package/dist/pretty.js +85 -0
- package/dist/propagation.d.ts +52 -0
- package/dist/propagation.d.ts.map +1 -0
- package/dist/propagation.js +183 -0
- package/dist/react.d.ts +38 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +72 -0
- package/dist/receipts.d.ts +26 -0
- package/dist/receipts.d.ts.map +1 -0
- package/dist/receipts.js +69 -0
- package/dist/resilience.d.ts +26 -0
- package/dist/resilience.d.ts.map +1 -0
- package/dist/resilience.js +183 -0
- package/dist/runtime.d.ts +33 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +133 -0
- package/dist/sampling.d.ts +9 -0
- package/dist/sampling.d.ts.map +1 -0
- package/dist/sampling.js +53 -0
- package/dist/sanitize.d.ts +6 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +7 -0
- package/dist/schema.d.ts +41 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +109 -0
- package/dist/shutdown.d.ts +2 -0
- package/dist/shutdown.d.ts.map +1 -0
- package/dist/shutdown.js +15 -0
- package/dist/slo.d.ts +25 -0
- package/dist/slo.d.ts.map +1 -0
- package/dist/slo.js +115 -0
- package/dist/testing.d.ts +10 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +51 -0
- package/dist/tracing.d.ts +51 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/tracing.js +181 -0
- package/package.json +139 -0
- package/src/backpressure.ts +68 -0
- package/src/cardinality.ts +83 -0
- package/src/classification.ts +87 -0
- package/src/config.ts +589 -0
- package/src/consent.ts +61 -0
- package/src/context.ts +157 -0
- package/src/exceptions.ts +24 -0
- package/src/fingerprint.ts +53 -0
- package/src/hash.ts +118 -0
- package/src/health.ts +175 -0
- package/src/index.ts +183 -0
- package/src/logger.ts +287 -0
- package/src/metrics.ts +204 -0
- package/src/otel-logs.ts +161 -0
- package/src/otel-noop.ts +19 -0
- package/src/otel.ts +112 -0
- package/src/pii.ts +358 -0
- package/src/pretty.ts +93 -0
- package/src/propagation.ts +222 -0
- package/src/react.ts +98 -0
- package/src/receipts.ts +97 -0
- package/src/resilience.ts +220 -0
- package/src/runtime.ts +171 -0
- package/src/sampling.ts +68 -0
- package/src/sanitize.ts +8 -0
- package/src/schema.ts +135 -0
- package/src/shutdown.ts +18 -0
- package/src/slo.ts +156 -0
- package/src/testing.ts +56 -0
- package/src/tracing.ts +211 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 provide.io llc. All rights reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Exporter resilience — retry, backoff, timeout, and circuit breaker.
|
|
5
|
+
* Mirrors Python provide.telemetry.resilience.
|
|
6
|
+
*/
|
|
7
|
+
import { _exportFailuresField, _incrementHealth, _recordExportLatency, _registerCircuitStateFn, _retriesField, } from './health';
|
|
8
|
+
export class TelemetryTimeoutError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'TelemetryTimeoutError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const DEFAULT_POLICY = {
|
|
15
|
+
retries: 0,
|
|
16
|
+
backoffMs: 0,
|
|
17
|
+
timeoutMs: 10000,
|
|
18
|
+
failOpen: true,
|
|
19
|
+
};
|
|
20
|
+
export const CIRCUIT_BREAKER_THRESHOLD = 3;
|
|
21
|
+
export const CIRCUIT_BASE_COOLDOWN_MS = 30000;
|
|
22
|
+
const CIRCUIT_MAX_COOLDOWN_MS = 1024000;
|
|
23
|
+
// Stryker disable next-line ObjectLiteral
|
|
24
|
+
let _policies = {};
|
|
25
|
+
// Stryker disable next-line ObjectLiteral
|
|
26
|
+
export const _consecutiveTimeouts = { logs: 0, traces: 0, metrics: 0 };
|
|
27
|
+
// Stryker disable next-line ObjectLiteral
|
|
28
|
+
export const _circuitTrippedAt = { logs: 0, traces: 0, metrics: 0 };
|
|
29
|
+
// Stryker disable next-line ObjectLiteral
|
|
30
|
+
export const _openCount = { logs: 0, traces: 0, metrics: 0 };
|
|
31
|
+
/* Stryker disable BooleanLiteral: initial false values are reset by _resetResilienceForTests before each test — equivalent mutant */
|
|
32
|
+
// Stryker disable next-line ObjectLiteral
|
|
33
|
+
export const _halfOpenProbing = {
|
|
34
|
+
logs: false,
|
|
35
|
+
traces: false,
|
|
36
|
+
metrics: false,
|
|
37
|
+
};
|
|
38
|
+
/* Stryker restore BooleanLiteral */
|
|
39
|
+
export function setExporterPolicy(signal, policy) {
|
|
40
|
+
_policies[signal] = { ...DEFAULT_POLICY, ...policy };
|
|
41
|
+
}
|
|
42
|
+
export function getExporterPolicy(signal) {
|
|
43
|
+
return { ...(_policies[signal] ?? DEFAULT_POLICY) };
|
|
44
|
+
}
|
|
45
|
+
// Stryker disable next-line BlockStatement
|
|
46
|
+
function _sleep(ms) {
|
|
47
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
48
|
+
}
|
|
49
|
+
async function _withTimeout(fn, timeoutMs) {
|
|
50
|
+
// Stryker disable next-line ConditionalExpression,EqualityOperator
|
|
51
|
+
if (timeoutMs <= 0)
|
|
52
|
+
return fn();
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const timer = setTimeout(() => {
|
|
55
|
+
// Stryker disable next-line StringLiteral: timeout error message content is not tested
|
|
56
|
+
reject(new TelemetryTimeoutError(`operation timed out after ${timeoutMs}ms`));
|
|
57
|
+
}, timeoutMs);
|
|
58
|
+
fn().then((val) => {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
resolve(val);
|
|
61
|
+
}, (err) => {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
reject(err);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export async function runWithResilience(signal, fn) {
|
|
68
|
+
const policy = _policies[signal] ?? { ...DEFAULT_POLICY };
|
|
69
|
+
const attempts = Math.max(1, policy.retries + 1);
|
|
70
|
+
// Ensure per-signal dicts are initialized for custom signals.
|
|
71
|
+
if (!(signal in _openCount))
|
|
72
|
+
_openCount[signal] = 0;
|
|
73
|
+
// Stryker disable next-line ConditionalExpression: custom signal init — skipping is equivalent since undefined is falsy like false
|
|
74
|
+
if (!(signal in _halfOpenProbing))
|
|
75
|
+
_halfOpenProbing[signal] = false;
|
|
76
|
+
// Circuit breaker check.
|
|
77
|
+
const failField = _exportFailuresField(signal);
|
|
78
|
+
const retryField = _retriesField(signal);
|
|
79
|
+
// Stryker disable next-line ConditionalExpression
|
|
80
|
+
if (_consecutiveTimeouts[signal] >= CIRCUIT_BREAKER_THRESHOLD) {
|
|
81
|
+
// Reject concurrent callers while a half-open probe is already in flight.
|
|
82
|
+
if (_halfOpenProbing[signal]) {
|
|
83
|
+
_incrementHealth(failField);
|
|
84
|
+
if (policy.failOpen)
|
|
85
|
+
return null;
|
|
86
|
+
throw new TelemetryTimeoutError('circuit breaker open: probe in progress');
|
|
87
|
+
}
|
|
88
|
+
const cooldown = Math.min(CIRCUIT_BASE_COOLDOWN_MS * 2 ** _openCount[signal], CIRCUIT_MAX_COOLDOWN_MS);
|
|
89
|
+
const elapsed = Date.now() - _circuitTrippedAt[signal];
|
|
90
|
+
if (elapsed < cooldown) {
|
|
91
|
+
_incrementHealth(failField);
|
|
92
|
+
if (policy.failOpen)
|
|
93
|
+
return null;
|
|
94
|
+
throw new TelemetryTimeoutError('circuit breaker open: too many consecutive timeouts');
|
|
95
|
+
}
|
|
96
|
+
// Half-open: cooldown elapsed — allow one probe.
|
|
97
|
+
_halfOpenProbing[signal] = true;
|
|
98
|
+
}
|
|
99
|
+
let lastError = null;
|
|
100
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
101
|
+
const started = Date.now();
|
|
102
|
+
try {
|
|
103
|
+
const result = await _withTimeout(fn, policy.timeoutMs);
|
|
104
|
+
_recordExportLatency(signal, Date.now() - started);
|
|
105
|
+
if (_halfOpenProbing[signal]) {
|
|
106
|
+
_halfOpenProbing[signal] = false;
|
|
107
|
+
_consecutiveTimeouts[signal] = 0;
|
|
108
|
+
_openCount[signal] = Math.max(0, _openCount[signal] - 1);
|
|
109
|
+
// Stryker disable next-line BlockStatement: else-block body on non-probe success — removing is equivalent since timeouts are already 0 on fresh closed circuit
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
_consecutiveTimeouts[signal] = 0;
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
118
|
+
_incrementHealth(failField);
|
|
119
|
+
if (err instanceof TelemetryTimeoutError) {
|
|
120
|
+
if (_halfOpenProbing[signal]) {
|
|
121
|
+
_halfOpenProbing[signal] = false;
|
|
122
|
+
_openCount[signal] += 1;
|
|
123
|
+
_circuitTrippedAt[signal] = Date.now();
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
_consecutiveTimeouts[signal] = (_consecutiveTimeouts[signal] ?? 0) + 1;
|
|
127
|
+
// Stryker disable next-line ConditionalExpression,EqualityOperator
|
|
128
|
+
if (_consecutiveTimeouts[signal] >= CIRCUIT_BREAKER_THRESHOLD) {
|
|
129
|
+
_openCount[signal] += 1;
|
|
130
|
+
_circuitTrippedAt[signal] = Date.now();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else if (_halfOpenProbing[signal]) {
|
|
135
|
+
_halfOpenProbing[signal] = false;
|
|
136
|
+
_openCount[signal] += 1;
|
|
137
|
+
_circuitTrippedAt[signal] = Date.now();
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
_consecutiveTimeouts[signal] = 0;
|
|
141
|
+
}
|
|
142
|
+
// Stryker disable next-line ArithmeticOperator
|
|
143
|
+
if (attempt < attempts - 1) {
|
|
144
|
+
_incrementHealth(retryField);
|
|
145
|
+
// Stryker disable next-line ConditionalExpression,EqualityOperator
|
|
146
|
+
if (policy.backoffMs > 0)
|
|
147
|
+
await _sleep(policy.backoffMs);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (policy.failOpen)
|
|
152
|
+
return null;
|
|
153
|
+
// Stryker disable next-line StringLiteral: unreachable fallback — lastError is always set by the catch block above
|
|
154
|
+
/* v8 ignore next */
|
|
155
|
+
throw lastError ?? new Error('all retry attempts failed');
|
|
156
|
+
}
|
|
157
|
+
export function getCircuitState(signal) {
|
|
158
|
+
const openCount = _openCount[signal] ?? 0;
|
|
159
|
+
if (_halfOpenProbing[signal]) {
|
|
160
|
+
return { state: 'half-open', openCount, cooldownRemainingMs: 0 };
|
|
161
|
+
}
|
|
162
|
+
if ((_consecutiveTimeouts[signal] ?? 0) >= CIRCUIT_BREAKER_THRESHOLD) {
|
|
163
|
+
const cooldown = Math.min(CIRCUIT_BASE_COOLDOWN_MS * 2 ** openCount, CIRCUIT_MAX_COOLDOWN_MS);
|
|
164
|
+
const remaining = cooldown - (Date.now() - _circuitTrippedAt[signal]);
|
|
165
|
+
// Stryker disable next-line EqualityOperator: > 0 vs >= 0 — exact millisecond boundary P≈0
|
|
166
|
+
if (remaining > 0) {
|
|
167
|
+
return { state: 'open', openCount, cooldownRemainingMs: remaining };
|
|
168
|
+
}
|
|
169
|
+
return { state: 'half-open', openCount, cooldownRemainingMs: 0 };
|
|
170
|
+
}
|
|
171
|
+
return { state: 'closed', openCount, cooldownRemainingMs: 0 };
|
|
172
|
+
}
|
|
173
|
+
// Register with health module to break circular dependency.
|
|
174
|
+
_registerCircuitStateFn(getCircuitState);
|
|
175
|
+
export function _resetResilienceForTests() {
|
|
176
|
+
_policies = {};
|
|
177
|
+
for (const k of ['logs', 'traces', 'metrics']) {
|
|
178
|
+
_consecutiveTimeouts[k] = 0;
|
|
179
|
+
_circuitTrippedAt[k] = 0;
|
|
180
|
+
_openCount[k] = 0;
|
|
181
|
+
_halfOpenProbing[k] = false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime reconfiguration helpers.
|
|
3
|
+
* Mirrors Python provide.telemetry.runtime.
|
|
4
|
+
*/
|
|
5
|
+
import { type RuntimeOverrides, type TelemetryConfig } from './config';
|
|
6
|
+
/** Minimal interface for providers that can be flushed and shut down cleanly. */
|
|
7
|
+
export interface ShutdownableProvider {
|
|
8
|
+
forceFlush?(): Promise<void>;
|
|
9
|
+
shutdown?(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/** Store the live providers so shutdownTelemetry can flush and drain them. */
|
|
12
|
+
export declare function _storeRegisteredProviders(providers: ShutdownableProvider[]): void;
|
|
13
|
+
/** Return the currently registered providers (snapshot). */
|
|
14
|
+
export declare function _getRegisteredProviders(): ShutdownableProvider[];
|
|
15
|
+
/** Called by registerOtelProviders once providers are live. */
|
|
16
|
+
export declare function _markProvidersRegistered(): void;
|
|
17
|
+
/** Return true if OTEL providers have been registered. */
|
|
18
|
+
export declare function _areProvidersRegistered(): boolean;
|
|
19
|
+
/** Return the active runtime config (or env-derived defaults if none set). */
|
|
20
|
+
export declare function getRuntimeConfig(): Readonly<TelemetryConfig>;
|
|
21
|
+
/** Merge hot-reloadable overrides into the active config and re-apply policies. */
|
|
22
|
+
export declare function updateRuntimeConfig(overrides: RuntimeOverrides): void;
|
|
23
|
+
/** Reload config from env vars and apply only hot-reloadable fields. */
|
|
24
|
+
export declare function reloadRuntimeFromEnv(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Apply config changes.
|
|
27
|
+
* If provider-changing fields differ and providers are already registered, performs a
|
|
28
|
+
* best-effort shutdown (fire-and-forget) then re-initialises — matching Go/Python behaviour.
|
|
29
|
+
* Otherwise delegates to setupTelemetry.
|
|
30
|
+
*/
|
|
31
|
+
export declare function reconfigureTelemetry(config: Partial<TelemetryConfig>): void;
|
|
32
|
+
export declare function _resetRuntimeForTests(): void;
|
|
33
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EAGrB,MAAM,UAAU,CAAC;AAElB,iFAAiF;AACjF,MAAM,WAAW,oBAAoB;IACnC,UAAU,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,QAAQ,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAQD,8EAA8E;AAC9E,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,oBAAoB,EAAE,GAAG,IAAI,CAEjF;AAED,4DAA4D;AAC5D,wBAAgB,uBAAuB,IAAI,oBAAoB,EAAE,CAEhE;AAED,+DAA+D;AAC/D,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AAED,0DAA0D;AAC1D,wBAAgB,uBAAuB,IAAI,OAAO,CAEjD;AAWD,8EAA8E;AAC9E,wBAAgB,gBAAgB,IAAI,QAAQ,CAAC,eAAe,CAAC,CAG5D;AAED,mFAAmF;AACnF,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,gBAAgB,GAAG,IAAI,CAUrE;AAWD,wEAAwE;AACxE,wBAAgB,oBAAoB,IAAI,IAAI,CA0C3C;AAQD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAuB3E;AAED,wBAAgB,qBAAqB,IAAI,IAAI,CAI5C"}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 provide.io llc. All rights reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Runtime reconfiguration helpers.
|
|
5
|
+
* Mirrors Python provide.telemetry.runtime.
|
|
6
|
+
*/
|
|
7
|
+
import { configFromEnv, setupTelemetry, } from './config';
|
|
8
|
+
let _activeConfig = null;
|
|
9
|
+
// Stryker disable next-line BooleanLiteral: initial false is overwritten by _resetRuntimeForTests() in every test beforeEach — equivalent mutant
|
|
10
|
+
let _providersRegistered = false;
|
|
11
|
+
// Stryker disable next-line ArrayDeclaration: initial [] is overwritten by _resetRuntimeForTests() in every test beforeEach — equivalent mutant
|
|
12
|
+
let _registeredProviders = [];
|
|
13
|
+
/** Store the live providers so shutdownTelemetry can flush and drain them. */
|
|
14
|
+
export function _storeRegisteredProviders(providers) {
|
|
15
|
+
_registeredProviders = providers;
|
|
16
|
+
}
|
|
17
|
+
/** Return the currently registered providers (snapshot). */
|
|
18
|
+
export function _getRegisteredProviders() {
|
|
19
|
+
return [..._registeredProviders];
|
|
20
|
+
}
|
|
21
|
+
/** Called by registerOtelProviders once providers are live. */
|
|
22
|
+
export function _markProvidersRegistered() {
|
|
23
|
+
_providersRegistered = true;
|
|
24
|
+
}
|
|
25
|
+
/** Return true if OTEL providers have been registered. */
|
|
26
|
+
export function _areProvidersRegistered() {
|
|
27
|
+
return _providersRegistered;
|
|
28
|
+
}
|
|
29
|
+
function deepFreeze(obj) {
|
|
30
|
+
for (const val of Object.values(obj)) {
|
|
31
|
+
if (typeof val === 'object' && val !== null && !Object.isFrozen(val)) {
|
|
32
|
+
deepFreeze(val);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return Object.freeze(obj);
|
|
36
|
+
}
|
|
37
|
+
/** Return the active runtime config (or env-derived defaults if none set). */
|
|
38
|
+
export function getRuntimeConfig() {
|
|
39
|
+
const cfg = _activeConfig ?? configFromEnv();
|
|
40
|
+
return deepFreeze({ ...cfg });
|
|
41
|
+
}
|
|
42
|
+
/** Merge hot-reloadable overrides into the active config and re-apply policies. */
|
|
43
|
+
export function updateRuntimeConfig(overrides) {
|
|
44
|
+
const base = _activeConfig ?? configFromEnv();
|
|
45
|
+
const merged = { ...base };
|
|
46
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
47
|
+
if (value !== undefined) {
|
|
48
|
+
merged[key] = value;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
_activeConfig = merged;
|
|
52
|
+
setupTelemetry(_activeConfig);
|
|
53
|
+
}
|
|
54
|
+
const _COLD_FIELDS = [
|
|
55
|
+
'serviceName',
|
|
56
|
+
'environment',
|
|
57
|
+
'version',
|
|
58
|
+
'otelEnabled',
|
|
59
|
+
'otlpEndpoint',
|
|
60
|
+
'otlpHeaders',
|
|
61
|
+
];
|
|
62
|
+
/** Reload config from env vars and apply only hot-reloadable fields. */
|
|
63
|
+
export function reloadRuntimeFromEnv() {
|
|
64
|
+
const fresh = configFromEnv();
|
|
65
|
+
const current = _activeConfig;
|
|
66
|
+
if (current) {
|
|
67
|
+
const drifted = _COLD_FIELDS.filter((k) => JSON.stringify(current[k]) !== JSON.stringify(fresh[k]));
|
|
68
|
+
if (drifted.length > 0) {
|
|
69
|
+
console.warn('[provide-telemetry] runtime.cold_field_drift:', drifted.join(', '), '— restart required to apply');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Apply only hot fields via overrides
|
|
73
|
+
const overrides = {
|
|
74
|
+
samplingLogsRate: fresh.samplingLogsRate,
|
|
75
|
+
samplingTracesRate: fresh.samplingTracesRate,
|
|
76
|
+
samplingMetricsRate: fresh.samplingMetricsRate,
|
|
77
|
+
backpressureLogsMaxsize: fresh.backpressureLogsMaxsize,
|
|
78
|
+
backpressureTracesMaxsize: fresh.backpressureTracesMaxsize,
|
|
79
|
+
backpressureMetricsMaxsize: fresh.backpressureMetricsMaxsize,
|
|
80
|
+
exporterLogsRetries: fresh.exporterLogsRetries,
|
|
81
|
+
exporterLogsBackoffMs: fresh.exporterLogsBackoffMs,
|
|
82
|
+
exporterLogsTimeoutMs: fresh.exporterLogsTimeoutMs,
|
|
83
|
+
exporterLogsFailOpen: fresh.exporterLogsFailOpen,
|
|
84
|
+
exporterTracesRetries: fresh.exporterTracesRetries,
|
|
85
|
+
exporterTracesBackoffMs: fresh.exporterTracesBackoffMs,
|
|
86
|
+
exporterTracesTimeoutMs: fresh.exporterTracesTimeoutMs,
|
|
87
|
+
exporterTracesFailOpen: fresh.exporterTracesFailOpen,
|
|
88
|
+
exporterMetricsRetries: fresh.exporterMetricsRetries,
|
|
89
|
+
exporterMetricsBackoffMs: fresh.exporterMetricsBackoffMs,
|
|
90
|
+
exporterMetricsTimeoutMs: fresh.exporterMetricsTimeoutMs,
|
|
91
|
+
exporterMetricsFailOpen: fresh.exporterMetricsFailOpen,
|
|
92
|
+
securityMaxAttrValueLength: fresh.securityMaxAttrValueLength,
|
|
93
|
+
securityMaxAttrCount: fresh.securityMaxAttrCount,
|
|
94
|
+
sloEnableRedMetrics: fresh.sloEnableRedMetrics,
|
|
95
|
+
sloEnableUseMetrics: fresh.sloEnableUseMetrics,
|
|
96
|
+
piiMaxDepth: fresh.piiMaxDepth,
|
|
97
|
+
};
|
|
98
|
+
updateRuntimeConfig(overrides);
|
|
99
|
+
}
|
|
100
|
+
const PROVIDER_CHANGING_FIELDS = [
|
|
101
|
+
'otelEnabled',
|
|
102
|
+
'otlpEndpoint',
|
|
103
|
+
'otlpHeaders',
|
|
104
|
+
];
|
|
105
|
+
/**
|
|
106
|
+
* Apply config changes.
|
|
107
|
+
* If provider-changing fields differ and providers are already registered, performs a
|
|
108
|
+
* best-effort shutdown (fire-and-forget) then re-initialises — matching Go/Python behaviour.
|
|
109
|
+
* Otherwise delegates to setupTelemetry.
|
|
110
|
+
*/
|
|
111
|
+
export function reconfigureTelemetry(config) {
|
|
112
|
+
const current = getRuntimeConfig();
|
|
113
|
+
const proposed = { ...current, ...config };
|
|
114
|
+
if (_providersRegistered) {
|
|
115
|
+
const changed = PROVIDER_CHANGING_FIELDS.some((k) => JSON.stringify(current[k]) !== JSON.stringify(proposed[k]));
|
|
116
|
+
if (changed) {
|
|
117
|
+
// Best-effort async shutdown — fire-and-forget, errors ignored (mirrors Go's `_ = ShutdownTelemetry(ctx)`)
|
|
118
|
+
const providers = _getRegisteredProviders();
|
|
119
|
+
// Stryker disable LogicalOperator: ?? vs && is equivalent here — forceFlush/shutdown return Promise (truthy) so && still resolves; when undefined, Promise.allSettled wraps both in Promise.resolve
|
|
120
|
+
void Promise.allSettled(providers.map((p) => p.forceFlush?.() ?? Promise.resolve())).then(() => Promise.allSettled(providers.map((p) => p.shutdown?.() ?? Promise.resolve())));
|
|
121
|
+
// Stryker restore LogicalOperator
|
|
122
|
+
_providersRegistered = false;
|
|
123
|
+
_registeredProviders = [];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
setupTelemetry(proposed);
|
|
127
|
+
_activeConfig = proposed;
|
|
128
|
+
}
|
|
129
|
+
export function _resetRuntimeForTests() {
|
|
130
|
+
_activeConfig = null;
|
|
131
|
+
_providersRegistered = false;
|
|
132
|
+
_registeredProviders = [];
|
|
133
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface SamplingPolicy {
|
|
2
|
+
defaultRate: number;
|
|
3
|
+
overrides?: Record<string, number>;
|
|
4
|
+
}
|
|
5
|
+
export declare function setSamplingPolicy(signal: string, policy: SamplingPolicy): void;
|
|
6
|
+
export declare function getSamplingPolicy(signal: string): SamplingPolicy;
|
|
7
|
+
export declare function shouldSample(signal: string, key?: string): boolean;
|
|
8
|
+
export declare function _resetSamplingForTests(): void;
|
|
9
|
+
//# sourceMappingURL=sampling.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sampling.d.ts","sourceRoot":"","sources":["../src/sampling.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAmBD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAQ9E;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAOhE;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAalE;AAED,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
|
package/dist/sampling.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 provide.io llc. All rights reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Runtime sampling policy — mirrors Python provide.telemetry.sampling.
|
|
5
|
+
*/
|
|
6
|
+
import { ConfigurationError } from './exceptions';
|
|
7
|
+
const DEFAULT_POLICY = { defaultRate: 1.0 };
|
|
8
|
+
let _policies = {};
|
|
9
|
+
const VALID_SIGNALS = new Set(['logs', 'traces', 'metrics']);
|
|
10
|
+
function _validateSignal(signal) {
|
|
11
|
+
if (!VALID_SIGNALS.has(signal)) {
|
|
12
|
+
throw new ConfigurationError(`unknown signal "${signal}", expected one of [logs, metrics, traces]`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function _clamp(rate) {
|
|
16
|
+
return Math.max(0, Math.min(1, rate));
|
|
17
|
+
}
|
|
18
|
+
export function setSamplingPolicy(signal, policy) {
|
|
19
|
+
_validateSignal(signal);
|
|
20
|
+
_policies[signal] = {
|
|
21
|
+
defaultRate: _clamp(policy.defaultRate),
|
|
22
|
+
overrides: policy.overrides
|
|
23
|
+
? Object.fromEntries(Object.entries(policy.overrides).map(([k, v]) => [k, _clamp(v)]))
|
|
24
|
+
: undefined,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function getSamplingPolicy(signal) {
|
|
28
|
+
_validateSignal(signal);
|
|
29
|
+
const _policy = _policies[signal] ?? DEFAULT_POLICY;
|
|
30
|
+
return {
|
|
31
|
+
defaultRate: _policy.defaultRate,
|
|
32
|
+
overrides: _policy.overrides ? { ..._policy.overrides } : undefined,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function shouldSample(signal, key) {
|
|
36
|
+
_validateSignal(signal);
|
|
37
|
+
const _policy = _policies[signal] ?? DEFAULT_POLICY;
|
|
38
|
+
const overrides = _policy.overrides;
|
|
39
|
+
const lookupKey = key ?? signal;
|
|
40
|
+
const rate = overrides && lookupKey in overrides ? overrides[lookupKey] : _policy.defaultRate;
|
|
41
|
+
const clamped = _clamp(rate);
|
|
42
|
+
// Stryker disable next-line ConditionalExpression,EqualityOperator: equivalent mutant — Math.random() in [0,1) so boundary is not observable
|
|
43
|
+
if (clamped <= 0)
|
|
44
|
+
return false;
|
|
45
|
+
// Stryker disable next-line ConditionalExpression,EqualityOperator: equivalent mutant — Math.random() in [0,1) so boundary is not observable
|
|
46
|
+
if (clamped >= 1)
|
|
47
|
+
return true;
|
|
48
|
+
// Stryker disable next-line EqualityOperator: Math.random() is in [0,1) so < 1.0 and <= 1.0 are equivalent (random never equals 1.0)
|
|
49
|
+
return Math.random() < clamped;
|
|
50
|
+
}
|
|
51
|
+
export function _resetSamplingForTests() {
|
|
52
|
+
_policies = {};
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,uBAAuB,EAAE,MAAM,OAAO,CAAC"}
|
package/dist/sanitize.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 provide.io llc. All rights reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Re-exports sanitize() and DEFAULT_SANITIZE_FIELDS from pii.ts for backwards compatibility.
|
|
5
|
+
* New code should import from './pii' directly.
|
|
6
|
+
*/
|
|
7
|
+
export { sanitize, DEFAULT_SANITIZE_FIELDS } from './pii';
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { TelemetryError } from './exceptions';
|
|
2
|
+
export declare class EventSchemaError extends TelemetryError {
|
|
3
|
+
constructor(message?: string);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Structured event record returned by event().
|
|
7
|
+
* Fields spread directly into pino log objects.
|
|
8
|
+
*/
|
|
9
|
+
export interface EventRecord {
|
|
10
|
+
event: string;
|
|
11
|
+
domain: string;
|
|
12
|
+
action: string;
|
|
13
|
+
resource?: string;
|
|
14
|
+
status: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build a structured EventRecord from 3 (DAS) or 4 (DARS) segments.
|
|
18
|
+
*
|
|
19
|
+
* In strict mode: validates each segment matches /^[a-z][a-z0-9_]*$/.
|
|
20
|
+
*
|
|
21
|
+
* Usage: `log.info({ ...event('auth', 'login', 'success'), userId: '123' })`
|
|
22
|
+
*/
|
|
23
|
+
export declare function event(...segments: string[]): EventRecord;
|
|
24
|
+
/**
|
|
25
|
+
* Build a dot-joined event name string from segments.
|
|
26
|
+
* In strict mode (default): enforces 3–5 segments, each matching /^[a-z][a-z0-9_]*$/.
|
|
27
|
+
* In relaxed mode: requires at least 1 segment, skips count and format checks.
|
|
28
|
+
*/
|
|
29
|
+
export declare function eventName(...segments: string[]): string;
|
|
30
|
+
/**
|
|
31
|
+
* Validate an already-assembled event name string.
|
|
32
|
+
* In strict mode (default), enforces 3–5 dot-separated lowercase segments.
|
|
33
|
+
* In relaxed mode, only checks that each segment is non-empty.
|
|
34
|
+
*/
|
|
35
|
+
export declare function validateEventName(name: string, strict?: boolean): void;
|
|
36
|
+
/**
|
|
37
|
+
* Verify that all required keys are present in obj.
|
|
38
|
+
* Throws EventSchemaError listing the missing keys.
|
|
39
|
+
*/
|
|
40
|
+
export declare function validateRequiredKeys(obj: Record<string, unknown>, keys: string[]): void;
|
|
41
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,qBAAa,gBAAiB,SAAQ,cAAc;gBACtC,OAAO,CAAC,EAAE,MAAM;CAI7B;AAMD;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,WAAW,CA0BxD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAqBvD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,OAAc,GAAG,IAAI,CAsB5E;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAKvF"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 provide.io llc. All rights reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Event schema validation — mirrors Python provide.telemetry.schema.events.
|
|
5
|
+
*/
|
|
6
|
+
import { getConfig } from './config';
|
|
7
|
+
import { TelemetryError } from './exceptions';
|
|
8
|
+
export class EventSchemaError extends TelemetryError {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'EventSchemaError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const SEGMENT_RE = /^[a-z][a-z0-9_]*$/;
|
|
15
|
+
const MIN_SEGMENTS = 3;
|
|
16
|
+
const MAX_SEGMENTS = 5;
|
|
17
|
+
/**
|
|
18
|
+
* Build a structured EventRecord from 3 (DAS) or 4 (DARS) segments.
|
|
19
|
+
*
|
|
20
|
+
* In strict mode: validates each segment matches /^[a-z][a-z0-9_]*$/.
|
|
21
|
+
*
|
|
22
|
+
* Usage: `log.info({ ...event('auth', 'login', 'success'), userId: '123' })`
|
|
23
|
+
*/
|
|
24
|
+
export function event(...segments) {
|
|
25
|
+
if (segments.length !== 3 && segments.length !== 4) {
|
|
26
|
+
throw new EventSchemaError(`event() requires 3 or 4 segments (DA[R]S), got ${segments.length}`);
|
|
27
|
+
}
|
|
28
|
+
const strict = getConfig().strictSchema;
|
|
29
|
+
if (strict) {
|
|
30
|
+
for (const seg of segments) {
|
|
31
|
+
if (!SEGMENT_RE.test(seg)) {
|
|
32
|
+
throw new EventSchemaError(`segment '${seg}' does not match pattern ^[a-z][a-z0-9_]*$`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const name = segments.join('.');
|
|
37
|
+
if (segments.length === 3) {
|
|
38
|
+
return { event: name, domain: segments[0], action: segments[1], status: segments[2] };
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
event: name,
|
|
42
|
+
domain: segments[0],
|
|
43
|
+
action: segments[1],
|
|
44
|
+
resource: segments[2],
|
|
45
|
+
status: segments[3],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Build a dot-joined event name string from segments.
|
|
50
|
+
* In strict mode (default): enforces 3–5 segments, each matching /^[a-z][a-z0-9_]*$/.
|
|
51
|
+
* In relaxed mode: requires at least 1 segment, skips count and format checks.
|
|
52
|
+
*/
|
|
53
|
+
export function eventName(...segments) {
|
|
54
|
+
if (segments.length === 0) {
|
|
55
|
+
// Stryker disable next-line StringLiteral: error message content doesn't affect behavior
|
|
56
|
+
throw new EventSchemaError(`expected ${MIN_SEGMENTS}-${MAX_SEGMENTS} segments, got 0`);
|
|
57
|
+
}
|
|
58
|
+
const strict = getConfig().strictSchema;
|
|
59
|
+
if (strict) {
|
|
60
|
+
if (segments.length < MIN_SEGMENTS || segments.length > MAX_SEGMENTS) {
|
|
61
|
+
throw new EventSchemaError(`expected ${MIN_SEGMENTS}-${MAX_SEGMENTS} segments, got ${segments.length}`);
|
|
62
|
+
}
|
|
63
|
+
// Stryker disable next-line EqualityOperator: segments[length] is undefined; SEGMENT_RE.test('undefined') returns true so no extra throw — equivalent
|
|
64
|
+
for (let i = 0; i < segments.length; i++) {
|
|
65
|
+
if (!SEGMENT_RE.test(segments[i])) {
|
|
66
|
+
// Stryker disable next-line StringLiteral
|
|
67
|
+
throw new EventSchemaError(`invalid event segment: segment[${i}]=${segments[i]}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return segments.join('.');
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Validate an already-assembled event name string.
|
|
75
|
+
* In strict mode (default), enforces 3–5 dot-separated lowercase segments.
|
|
76
|
+
* In relaxed mode, only checks that each segment is non-empty.
|
|
77
|
+
*/
|
|
78
|
+
export function validateEventName(name, strict = true) {
|
|
79
|
+
const segments = name.split('.');
|
|
80
|
+
if (strict) {
|
|
81
|
+
if (segments.length < MIN_SEGMENTS || segments.length > MAX_SEGMENTS) {
|
|
82
|
+
// Stryker disable next-line StringLiteral
|
|
83
|
+
throw new EventSchemaError(`expected ${MIN_SEGMENTS}-${MAX_SEGMENTS} segments, got ${segments.length}`);
|
|
84
|
+
}
|
|
85
|
+
// Stryker disable next-line EqualityOperator: same as above — undefined segment passes SEGMENT_RE
|
|
86
|
+
for (let i = 0; i < segments.length; i++) {
|
|
87
|
+
if (!SEGMENT_RE.test(segments[i])) {
|
|
88
|
+
// Stryker disable next-line StringLiteral
|
|
89
|
+
throw new EventSchemaError(`invalid event segment: segment[${i}]=${segments[i]}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// Stryker disable next-line ConditionalExpression: an empty input 'split' always has length >= 1; the `< 1` check is never triggered and is unreachable
|
|
95
|
+
if (segments.length < 1 || segments.some((s) => s.length === 0)) {
|
|
96
|
+
throw new EventSchemaError('event name must have at least one non-empty segment');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Verify that all required keys are present in obj.
|
|
102
|
+
* Throws EventSchemaError listing the missing keys.
|
|
103
|
+
*/
|
|
104
|
+
export function validateRequiredKeys(obj, keys) {
|
|
105
|
+
const missing = keys.filter((k) => !(k in obj));
|
|
106
|
+
if (missing.length > 0) {
|
|
107
|
+
throw new EventSchemaError(`missing required keys: ${missing.sort().join(', ')}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shutdown.d.ts","sourceRoot":"","sources":["../src/shutdown.ts"],"names":[],"mappings":"AAaA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAIvD"}
|
package/dist/shutdown.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 provide.io llc. All rights reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* shutdownTelemetry — flushes and shuts down any OTEL providers registered by
|
|
5
|
+
* registerOtelProviders. Safe to call before process exit or on hot-reload.
|
|
6
|
+
*
|
|
7
|
+
* Uses Promise.allSettled so a failure in one provider's forceFlush/shutdown
|
|
8
|
+
* does not prevent the others from draining.
|
|
9
|
+
*/
|
|
10
|
+
import { _getRegisteredProviders } from './runtime';
|
|
11
|
+
export async function shutdownTelemetry() {
|
|
12
|
+
const providers = _getRegisteredProviders();
|
|
13
|
+
await Promise.allSettled(providers.map((p) => p.forceFlush?.() ?? Promise.resolve()));
|
|
14
|
+
await Promise.allSettled(providers.map((p) => p.shutdown?.() ?? Promise.resolve()));
|
|
15
|
+
}
|
package/dist/slo.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare function recordRedMetrics(opts: {
|
|
2
|
+
route: string;
|
|
3
|
+
method: string;
|
|
4
|
+
statusCode: number;
|
|
5
|
+
durationMs: number;
|
|
6
|
+
}): void;
|
|
7
|
+
export declare function recordUseMetrics(opts: {
|
|
8
|
+
resource: string;
|
|
9
|
+
utilization: number;
|
|
10
|
+
unit?: string;
|
|
11
|
+
}): void;
|
|
12
|
+
export interface ErrorClassification {
|
|
13
|
+
errorType: 'server' | 'client' | 'timeout' | 'unknown';
|
|
14
|
+
errorCode: number;
|
|
15
|
+
errorName: string;
|
|
16
|
+
category: 'server_error' | 'client_error' | 'timeout' | 'unknown';
|
|
17
|
+
severity: 'critical' | 'warning' | 'info' | 'unknown';
|
|
18
|
+
'error.type': string;
|
|
19
|
+
'error.category': string;
|
|
20
|
+
'error.severity': string;
|
|
21
|
+
'http.status_code': string;
|
|
22
|
+
}
|
|
23
|
+
export declare function classifyError(excName: string, statusCode: number): ErrorClassification;
|
|
24
|
+
export declare function _resetSloForTests(): void;
|
|
25
|
+
//# sourceMappingURL=slo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slo.d.ts","sourceRoot":"","sources":["../src/slo.ts"],"names":[],"mappings":"AAiDA,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,IAAI,CAgBP;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,IAAI,CAIP;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,CAAC;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,cAAc,GAAG,cAAc,GAAG,SAAS,GAAG,SAAS,CAAC;IAClE,QAAQ,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;IAEtD,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,mBAAmB,CAsDtF;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAIxC"}
|