@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
package/src/context.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 provide.io llc. All rights reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Module-level context binding — mirrors Python provide.telemetry bind_context/unbind_context.
|
|
6
|
+
*
|
|
7
|
+
* In browser environments, context is stored in a module-level object. All log calls
|
|
8
|
+
* in the same JS execution context share the same bindings.
|
|
9
|
+
*
|
|
10
|
+
* In Node.js environments, AsyncLocalStorage provides per-async-context isolation
|
|
11
|
+
* (useful for SSR / worker processes where multiple requests run concurrently).
|
|
12
|
+
* Use runWithContext() to scope bindings to a single request/operation.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
type Context = Record<string, unknown>;
|
|
16
|
+
|
|
17
|
+
// ── AsyncLocalStorage type (Node.js / Cloudflare Workers) ─────────────────────
|
|
18
|
+
type ALS = {
|
|
19
|
+
getStore(): Context | undefined;
|
|
20
|
+
run<T>(store: Context, fn: () => T): T;
|
|
21
|
+
enterWith(store: Context): void;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// ── AsyncLocalStorage (Node.js / Cloudflare Workers) ──────────────────────────
|
|
25
|
+
let _asyncLocalStorage: ALS | null = null;
|
|
26
|
+
let _AlsConstructor: (new () => ALS) | null = null;
|
|
27
|
+
try {
|
|
28
|
+
// Dynamic require so the import doesn't break browser bundles.
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
30
|
+
const als = require('node:async_hooks') as { AsyncLocalStorage: new () => ALS };
|
|
31
|
+
_AlsConstructor = als.AsyncLocalStorage;
|
|
32
|
+
_asyncLocalStorage = new _AlsConstructor();
|
|
33
|
+
} catch {
|
|
34
|
+
// Not available — fall back to module-level context below.
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── Fallback: module-level context (browser / single-thread) ──────────────────
|
|
38
|
+
let _moduleCtx: Context = {};
|
|
39
|
+
|
|
40
|
+
function getStore(): Context {
|
|
41
|
+
if (_asyncLocalStorage) {
|
|
42
|
+
return _asyncLocalStorage.getStore() ?? _moduleCtx;
|
|
43
|
+
}
|
|
44
|
+
return _moduleCtx;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function ensureStore(): Context {
|
|
48
|
+
if (_asyncLocalStorage) {
|
|
49
|
+
const store = _asyncLocalStorage.getStore();
|
|
50
|
+
if (store) return store;
|
|
51
|
+
const next = { ..._moduleCtx };
|
|
52
|
+
_asyncLocalStorage.enterWith(next);
|
|
53
|
+
return next;
|
|
54
|
+
}
|
|
55
|
+
return _moduleCtx;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Bind key/value pairs into the current context.
|
|
60
|
+
* These fields are merged into every log record emitted after this call.
|
|
61
|
+
*/
|
|
62
|
+
export function bindContext(values: Context): void {
|
|
63
|
+
const store = ensureStore();
|
|
64
|
+
Object.assign(store, values);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Remove specific keys from the current context.
|
|
69
|
+
*/
|
|
70
|
+
export function unbindContext(...keys: string[]): void {
|
|
71
|
+
const store = ensureStore();
|
|
72
|
+
for (const k of keys) delete store[k];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Clear all context bindings.
|
|
77
|
+
*/
|
|
78
|
+
export function clearContext(): void {
|
|
79
|
+
if (_asyncLocalStorage) {
|
|
80
|
+
const store = _asyncLocalStorage.getStore();
|
|
81
|
+
if (store) {
|
|
82
|
+
for (const k of Object.keys(store)) delete store[k];
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
_moduleCtx = {};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Return a snapshot of the current context (no side effects).
|
|
91
|
+
*/
|
|
92
|
+
export function getContext(): Context {
|
|
93
|
+
return { ...getStore() };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Run fn with additional context values scoped to its execution.
|
|
98
|
+
* In Node.js, uses AsyncLocalStorage so the bindings are isolated per-request.
|
|
99
|
+
* In browser, temporarily binds then restores the previous state.
|
|
100
|
+
* Mirrors Python: contextvars copy_context().run(fn) pattern.
|
|
101
|
+
*/
|
|
102
|
+
export function runWithContext<T>(values: Context, fn: () => T): T {
|
|
103
|
+
if (_asyncLocalStorage) {
|
|
104
|
+
const inherited = { ...getStore(), ...values };
|
|
105
|
+
return _asyncLocalStorage.run(inherited, fn);
|
|
106
|
+
}
|
|
107
|
+
const prev = { ...getStore() };
|
|
108
|
+
bindContext(values);
|
|
109
|
+
try {
|
|
110
|
+
return fn();
|
|
111
|
+
} finally {
|
|
112
|
+
_moduleCtx = prev;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Bind a session ID that propagates across all telemetry events.
|
|
118
|
+
*/
|
|
119
|
+
export function bindSessionContext(sessionId: string): void {
|
|
120
|
+
bindContext({ session_id: sessionId });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Return the current session ID, or null if not set.
|
|
125
|
+
*/
|
|
126
|
+
export function getSessionId(): string | null {
|
|
127
|
+
const sessionId = getStore()['session_id'];
|
|
128
|
+
return typeof sessionId === 'string' ? sessionId : null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Clear the session ID.
|
|
133
|
+
*/
|
|
134
|
+
export function clearSessionContext(): void {
|
|
135
|
+
unbindContext('session_id');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Reset to empty context (used in tests). */
|
|
139
|
+
export function _resetContext(): void {
|
|
140
|
+
// Recreate ALS so no enterWith-seeded store leaks between tests.
|
|
141
|
+
// The null branch is only reachable in environments without node:async_hooks (e.g. browsers).
|
|
142
|
+
/* v8 ignore next */
|
|
143
|
+
_asyncLocalStorage = _AlsConstructor ? new _AlsConstructor() : null;
|
|
144
|
+
_moduleCtx = {};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Disable AsyncLocalStorage for testing the module-level fallback path. */
|
|
148
|
+
export function _disableAsyncLocalStorageForTest(): ALS | null {
|
|
149
|
+
const prev = _asyncLocalStorage;
|
|
150
|
+
_asyncLocalStorage = null;
|
|
151
|
+
return prev;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Re-enable AsyncLocalStorage after testing (pass value from _disable call). */
|
|
155
|
+
export function _restoreAsyncLocalStorageForTest(saved: ALS | null): void {
|
|
156
|
+
_asyncLocalStorage = saved;
|
|
157
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 provide.io llc. All rights reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Exception hierarchy — mirrors Python provide.telemetry.exceptions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class TelemetryError extends Error {
|
|
9
|
+
constructor(message?: string) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'TelemetryError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Raised when telemetry configuration is invalid.
|
|
17
|
+
* Also extends Error directly for maximum compatibility.
|
|
18
|
+
*/
|
|
19
|
+
export class ConfigurationError extends TelemetryError {
|
|
20
|
+
constructor(message?: string) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = 'ConfigurationError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -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
|
+
/**
|
|
5
|
+
* Stable error fingerprinting — generates a 12-char hex hash from
|
|
6
|
+
* exception type + top 3 stack frames (file:function, no line numbers).
|
|
7
|
+
*
|
|
8
|
+
* Cross-language compatible: same error in Python and TypeScript produces
|
|
9
|
+
* the same fingerprint when the call path is equivalent.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { shortHash12 } from './hash';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse an Error stack trace into normalized frames: `basename:function`.
|
|
16
|
+
* Returns at most 3 frames (most recent).
|
|
17
|
+
*/
|
|
18
|
+
function extractFrames(stack: string | undefined): string[] {
|
|
19
|
+
// Stryker disable next-line ConditionalExpression -- equivalent: falsy stack always yields no frames
|
|
20
|
+
if (!stack) return [];
|
|
21
|
+
const frames: string[] = [];
|
|
22
|
+
// V8: "at functionName (filename:line:col)" or "at filename:line:col"
|
|
23
|
+
// Stryker disable next-line Regex -- \s+ vs \s and trailing \d+ vs \d are equivalent for real stacks
|
|
24
|
+
const v8Re = /at\s+(?:(.+?)\s+\()?(.*?):\d+:\d+\)?/g;
|
|
25
|
+
// SpiderMonkey/JSC: "functionName@filename:line:col"
|
|
26
|
+
// Stryker disable next-line Regex -- trailing \d+ vs \d is equivalent for file extraction
|
|
27
|
+
const smRe = /(.+?)@(.*?):\d+:\d+/g;
|
|
28
|
+
for (const re of [v8Re, smRe]) {
|
|
29
|
+
let match: RegExpExecArray | null;
|
|
30
|
+
while ((match = re.exec(stack)) !== null) {
|
|
31
|
+
const func = String(match[1] || '').toLowerCase();
|
|
32
|
+
const file = String(match[2] || '');
|
|
33
|
+
const parts = file.split('/');
|
|
34
|
+
const last = parts[parts.length - 1] || '';
|
|
35
|
+
const winParts = last.split('\\');
|
|
36
|
+
const leaf = winParts[winParts.length - 1] || '';
|
|
37
|
+
const basename = leaf.replace(/\.[^.]+$/, '').toLowerCase();
|
|
38
|
+
if (basename) {
|
|
39
|
+
frames.push(`${basename}:${func}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return frames.slice(-3);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Compute a stable 12-char hex fingerprint for an error.
|
|
48
|
+
*/
|
|
49
|
+
export function computeErrorFingerprint(errorName: string, stack?: string): string {
|
|
50
|
+
const parts = [errorName.toLowerCase()];
|
|
51
|
+
parts.push(...extractFrames(stack));
|
|
52
|
+
return shortHash12(parts.join(':'));
|
|
53
|
+
}
|
package/src/hash.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 provide.io llc. All rights reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
const INITIAL_HASH: number[] = [
|
|
5
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
const ROUND_CONSTANTS: number[] = [
|
|
9
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
10
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
11
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
12
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
13
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
14
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
15
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
16
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
function add32(...values: number[]): number {
|
|
20
|
+
let sum = 0;
|
|
21
|
+
for (const value of values) {
|
|
22
|
+
sum = (sum + (value >>> 0)) >>> 0;
|
|
23
|
+
}
|
|
24
|
+
return sum;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function rotateRight(value: number, bits: number): number {
|
|
28
|
+
return (value >>> bits) | (value << (32 - bits));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function sha256Hex(input: string): string {
|
|
32
|
+
const bytes = new TextEncoder().encode(input);
|
|
33
|
+
const bitLength = bytes.length * 8;
|
|
34
|
+
const paddedLength = Math.ceil((bytes.length + 9) / 64) * 64;
|
|
35
|
+
const padded = new Uint8Array(paddedLength);
|
|
36
|
+
padded.set(bytes);
|
|
37
|
+
padded[bytes.length] = 0x80;
|
|
38
|
+
|
|
39
|
+
const view = new DataView(padded.buffer);
|
|
40
|
+
// Stryker disable next-line ArithmeticOperator: highBits is always 0 for inputs under 512 MB — division vs multiplication both produce 0 after ToUint32 coercion
|
|
41
|
+
const highBits = Math.floor(bitLength / 0x100000000);
|
|
42
|
+
const lowBits = bitLength >>> 0;
|
|
43
|
+
// Stryker disable next-line BooleanLiteral: highBits is 0 for all testable inputs — big-endian vs little-endian of 0 is identical
|
|
44
|
+
view.setUint32(paddedLength - 8, highBits, false);
|
|
45
|
+
view.setUint32(paddedLength - 4, lowBits, false);
|
|
46
|
+
|
|
47
|
+
const words = new Uint32Array(64);
|
|
48
|
+
let [h0, h1, h2, h3, h4, h5, h6, h7] = INITIAL_HASH;
|
|
49
|
+
|
|
50
|
+
for (let offset = 0; offset < paddedLength; offset += 64) {
|
|
51
|
+
for (let i = 0; i < 16; i++) {
|
|
52
|
+
words[i] = view.getUint32(offset + i * 4, false);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Stryker disable next-line EqualityOperator: Uint32Array(64) silently ignores writes to index 64 — the extra iteration is a no-op
|
|
56
|
+
for (let i = 16; i < 64; i++) {
|
|
57
|
+
const w15 = words[i - 15] as number;
|
|
58
|
+
const w2 = words[i - 2] as number;
|
|
59
|
+
const sigma0 = rotateRight(w15, 7) ^ rotateRight(w15, 18) ^ (w15 >>> 3);
|
|
60
|
+
const sigma1 = rotateRight(w2, 17) ^ rotateRight(w2, 19) ^ (w2 >>> 10);
|
|
61
|
+
words[i] = add32(words[i - 16] as number, sigma0, words[i - 7] as number, sigma1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let a = h0;
|
|
65
|
+
let b = h1;
|
|
66
|
+
let c = h2;
|
|
67
|
+
let d = h3;
|
|
68
|
+
let e = h4;
|
|
69
|
+
let f = h5;
|
|
70
|
+
let g = h6;
|
|
71
|
+
let h = h7;
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < 64; i++) {
|
|
74
|
+
const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
|
|
75
|
+
const choose = (e & f) ^ (~e & g);
|
|
76
|
+
const temp1 = add32(h, sum1, choose, ROUND_CONSTANTS[i] as number, words[i] as number);
|
|
77
|
+
const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
|
|
78
|
+
const majority = (a & b) ^ (a & c) ^ (b & c);
|
|
79
|
+
const temp2 = add32(sum0, majority);
|
|
80
|
+
|
|
81
|
+
h = g;
|
|
82
|
+
g = f;
|
|
83
|
+
f = e;
|
|
84
|
+
e = add32(d, temp1);
|
|
85
|
+
d = c;
|
|
86
|
+
c = b;
|
|
87
|
+
b = a;
|
|
88
|
+
a = add32(temp1, temp2);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
h0 = add32(h0, a);
|
|
92
|
+
h1 = add32(h1, b);
|
|
93
|
+
h2 = add32(h2, c);
|
|
94
|
+
h3 = add32(h3, d);
|
|
95
|
+
h4 = add32(h4, e);
|
|
96
|
+
h5 = add32(h5, f);
|
|
97
|
+
h6 = add32(h6, g);
|
|
98
|
+
h7 = add32(h7, h);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return [h0, h1, h2, h3, h4, h5, h6, h7]
|
|
102
|
+
.map((word) => word.toString(16).padStart(8, '0'))
|
|
103
|
+
.join('');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function shortHash12(input: string): string {
|
|
107
|
+
return sha256Hex(input).slice(0, 12);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Generate `numBytes` random bytes and return them as a lowercase hex string.
|
|
112
|
+
* Uses the Web Crypto API (available in Node.js 15+, browsers, and edge runtimes).
|
|
113
|
+
*/
|
|
114
|
+
export function randomHex(numBytes: number): string {
|
|
115
|
+
const bytes = new Uint8Array(numBytes);
|
|
116
|
+
crypto.getRandomValues(bytes);
|
|
117
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
118
|
+
}
|
package/src/health.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 provide.io llc. All rights reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Internal self-observability counters — mirrors Python provide.telemetry.health.
|
|
6
|
+
* Canonical 25-field layout: 8 per signal (logs, traces, metrics) + 1 global.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface HealthSnapshot {
|
|
10
|
+
// Logs (8)
|
|
11
|
+
logsEmitted: number;
|
|
12
|
+
logsDropped: number;
|
|
13
|
+
exportFailuresLogs: number;
|
|
14
|
+
retriesLogs: number;
|
|
15
|
+
exportLatencyMsLogs: number;
|
|
16
|
+
asyncBlockingRiskLogs: number;
|
|
17
|
+
circuitStateLogs: string;
|
|
18
|
+
circuitOpenCountLogs: number;
|
|
19
|
+
// Traces (8)
|
|
20
|
+
tracesEmitted: number;
|
|
21
|
+
tracesDropped: number;
|
|
22
|
+
exportFailuresTraces: number;
|
|
23
|
+
retriesTraces: number;
|
|
24
|
+
exportLatencyMsTraces: number;
|
|
25
|
+
asyncBlockingRiskTraces: number;
|
|
26
|
+
circuitStateTraces: string;
|
|
27
|
+
circuitOpenCountTraces: number;
|
|
28
|
+
// Metrics (8)
|
|
29
|
+
metricsEmitted: number;
|
|
30
|
+
metricsDropped: number;
|
|
31
|
+
exportFailuresMetrics: number;
|
|
32
|
+
retriesMetrics: number;
|
|
33
|
+
exportLatencyMsMetrics: number;
|
|
34
|
+
asyncBlockingRiskMetrics: number;
|
|
35
|
+
circuitStateMetrics: string;
|
|
36
|
+
circuitOpenCountMetrics: number;
|
|
37
|
+
// Global (1)
|
|
38
|
+
setupError: string | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Numeric fields that live in the mutable _state object (not derived circuit fields). */
|
|
42
|
+
type NumericHealthField =
|
|
43
|
+
| 'logsEmitted'
|
|
44
|
+
| 'logsDropped'
|
|
45
|
+
| 'tracesEmitted'
|
|
46
|
+
| 'tracesDropped'
|
|
47
|
+
| 'metricsEmitted'
|
|
48
|
+
| 'metricsDropped'
|
|
49
|
+
| 'exportFailuresLogs'
|
|
50
|
+
| 'exportFailuresTraces'
|
|
51
|
+
| 'exportFailuresMetrics'
|
|
52
|
+
| 'retriesLogs'
|
|
53
|
+
| 'retriesTraces'
|
|
54
|
+
| 'retriesMetrics'
|
|
55
|
+
| 'exportLatencyMsLogs'
|
|
56
|
+
| 'exportLatencyMsTraces'
|
|
57
|
+
| 'exportLatencyMsMetrics'
|
|
58
|
+
| 'asyncBlockingRiskLogs'
|
|
59
|
+
| 'asyncBlockingRiskTraces'
|
|
60
|
+
| 'asyncBlockingRiskMetrics';
|
|
61
|
+
|
|
62
|
+
let _setupError: string | null = null;
|
|
63
|
+
|
|
64
|
+
const _state = {
|
|
65
|
+
logsEmitted: 0,
|
|
66
|
+
logsDropped: 0,
|
|
67
|
+
tracesEmitted: 0,
|
|
68
|
+
tracesDropped: 0,
|
|
69
|
+
metricsEmitted: 0,
|
|
70
|
+
metricsDropped: 0,
|
|
71
|
+
exportFailuresLogs: 0,
|
|
72
|
+
exportFailuresTraces: 0,
|
|
73
|
+
exportFailuresMetrics: 0,
|
|
74
|
+
retriesLogs: 0,
|
|
75
|
+
retriesTraces: 0,
|
|
76
|
+
retriesMetrics: 0,
|
|
77
|
+
exportLatencyMsLogs: 0,
|
|
78
|
+
exportLatencyMsTraces: 0,
|
|
79
|
+
exportLatencyMsMetrics: 0,
|
|
80
|
+
asyncBlockingRiskLogs: 0,
|
|
81
|
+
asyncBlockingRiskTraces: 0,
|
|
82
|
+
asyncBlockingRiskMetrics: 0,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Lazy reference to avoid circular dependency at module load time.
|
|
86
|
+
// resilience.ts imports from health.ts, so we register the callback after load.
|
|
87
|
+
type CircuitStateResult = { state: string; openCount: number; cooldownRemainingMs: number };
|
|
88
|
+
type CircuitStateFn = (signal: string) => CircuitStateResult;
|
|
89
|
+
|
|
90
|
+
const _defaultCircuitState: CircuitStateResult = {
|
|
91
|
+
state: 'closed',
|
|
92
|
+
openCount: 0,
|
|
93
|
+
cooldownRemainingMs: 0,
|
|
94
|
+
};
|
|
95
|
+
let _circuitStateFn: CircuitStateFn = () => _defaultCircuitState;
|
|
96
|
+
|
|
97
|
+
/** Called by resilience module to register the getCircuitState function. */
|
|
98
|
+
export function _registerCircuitStateFn(fn: CircuitStateFn): void {
|
|
99
|
+
_circuitStateFn = fn;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getHealthSnapshot(): HealthSnapshot {
|
|
103
|
+
const csLogs = _circuitStateFn('logs');
|
|
104
|
+
const csTraces = _circuitStateFn('traces');
|
|
105
|
+
const csMetrics = _circuitStateFn('metrics');
|
|
106
|
+
return {
|
|
107
|
+
..._state,
|
|
108
|
+
circuitStateLogs: csLogs.state,
|
|
109
|
+
circuitStateTraces: csTraces.state,
|
|
110
|
+
circuitStateMetrics: csMetrics.state,
|
|
111
|
+
circuitOpenCountLogs: csLogs.openCount,
|
|
112
|
+
circuitOpenCountTraces: csTraces.openCount,
|
|
113
|
+
circuitOpenCountMetrics: csMetrics.openCount,
|
|
114
|
+
setupError: _setupError,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function setSetupError(error: string | null): void {
|
|
119
|
+
_setupError = error;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function _incrementHealth(field: NumericHealthField, by: number = 1): void {
|
|
123
|
+
_state[field] += by;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Map a signal name to the per-signal export-failures field. */
|
|
127
|
+
export function _exportFailuresField(
|
|
128
|
+
signal: string,
|
|
129
|
+
): 'exportFailuresLogs' | 'exportFailuresTraces' | 'exportFailuresMetrics' {
|
|
130
|
+
if (signal === 'traces') return 'exportFailuresTraces';
|
|
131
|
+
if (signal === 'metrics') return 'exportFailuresMetrics';
|
|
132
|
+
return 'exportFailuresLogs';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Map a signal name to the per-signal retries field. */
|
|
136
|
+
export function _retriesField(signal: string): 'retriesLogs' | 'retriesTraces' | 'retriesMetrics' {
|
|
137
|
+
if (signal === 'traces') return 'retriesTraces';
|
|
138
|
+
if (signal === 'metrics') return 'retriesMetrics';
|
|
139
|
+
return 'retriesLogs';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Map a signal name to the per-signal export latency field. */
|
|
143
|
+
export function _exportLatencyField(
|
|
144
|
+
signal: string,
|
|
145
|
+
): 'exportLatencyMsLogs' | 'exportLatencyMsTraces' | 'exportLatencyMsMetrics' {
|
|
146
|
+
if (signal === 'traces') return 'exportLatencyMsTraces';
|
|
147
|
+
if (signal === 'metrics') return 'exportLatencyMsMetrics';
|
|
148
|
+
return 'exportLatencyMsLogs';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function _recordExportLatency(signal: string, ms: number): void {
|
|
152
|
+
_state[_exportLatencyField(signal)] = ms;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function _resetHealthForTests(): void {
|
|
156
|
+
_state.logsEmitted = 0;
|
|
157
|
+
_state.logsDropped = 0;
|
|
158
|
+
_state.tracesEmitted = 0;
|
|
159
|
+
_state.tracesDropped = 0;
|
|
160
|
+
_state.metricsEmitted = 0;
|
|
161
|
+
_state.metricsDropped = 0;
|
|
162
|
+
_state.exportFailuresLogs = 0;
|
|
163
|
+
_state.exportFailuresTraces = 0;
|
|
164
|
+
_state.exportFailuresMetrics = 0;
|
|
165
|
+
_state.retriesLogs = 0;
|
|
166
|
+
_state.retriesTraces = 0;
|
|
167
|
+
_state.retriesMetrics = 0;
|
|
168
|
+
_state.exportLatencyMsLogs = 0;
|
|
169
|
+
_state.exportLatencyMsTraces = 0;
|
|
170
|
+
_state.exportLatencyMsMetrics = 0;
|
|
171
|
+
_state.asyncBlockingRiskLogs = 0;
|
|
172
|
+
_state.asyncBlockingRiskTraces = 0;
|
|
173
|
+
_state.asyncBlockingRiskMetrics = 0;
|
|
174
|
+
_setupError = null;
|
|
175
|
+
}
|