@superblocksteam/telemetry 2.0.173-next.4 → 2.0.174
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/dist/browser/console-logging.js +7 -2
- package/dist/browser/console-logging.js.map +1 -1
- package/dist/browser/early-console-buffer.js +14 -12
- package/dist/browser/early-console-buffer.js.map +1 -1
- package/dist/browser/index.js +41 -12
- package/dist/browser/index.js.map +1 -1
- package/dist/browser/init.d.ts +1 -1
- package/dist/browser/init.d.ts.map +1 -1
- package/dist/browser/init.js +52 -42
- package/dist/browser/init.js.map +1 -1
- package/dist/browser/instrumentations.js +15 -11
- package/dist/browser/instrumentations.js.map +1 -1
- package/dist/browser/logs.d.ts +1 -1
- package/dist/browser/logs.d.ts.map +1 -1
- package/dist/browser/logs.js +18 -11
- package/dist/browser/logs.js.map +1 -1
- package/dist/browser/metrics.js +12 -9
- package/dist/browser/metrics.js.map +1 -1
- package/dist/browser/resilient-exporter.d.ts +1 -1
- package/dist/browser/resilient-exporter.d.ts.map +1 -1
- package/dist/browser/resilient-exporter.js +12 -8
- package/dist/browser/resilient-exporter.js.map +1 -1
- package/dist/browser/sanitizer.js +6 -2
- package/dist/browser/sanitizer.js.map +1 -1
- package/dist/browser/traced-socket.js +18 -14
- package/dist/browser/traced-socket.js.map +1 -1
- package/dist/common/contracts/tier2-traces.js +44 -33
- package/dist/common/contracts/tier2-traces.js.map +1 -1
- package/dist/common/deployment-type.js +8 -5
- package/dist/common/deployment-type.js.map +1 -1
- package/dist/common/guardrails.js +38 -26
- package/dist/common/guardrails.js.map +1 -1
- package/dist/common/index.js +24 -8
- package/dist/common/index.js.map +1 -1
- package/dist/common/log-sanitizer.js +55 -37
- package/dist/common/log-sanitizer.js.map +1 -1
- package/dist/common/policy-evaluator.d.ts +1 -1
- package/dist/common/policy-evaluator.d.ts.map +1 -1
- package/dist/common/policy-evaluator.js +9 -5
- package/dist/common/policy-evaluator.js.map +1 -1
- package/dist/common/resource.js +25 -18
- package/dist/common/resource.js.map +1 -1
- package/dist/common/tier-hints.js +22 -12
- package/dist/common/tier-hints.js.map +1 -1
- package/dist/common/trace-sanitizer.js +10 -6
- package/dist/common/trace-sanitizer.js.map +1 -1
- package/dist/common/traced-socket-types.js +2 -1
- package/dist/common/traced-socket-types.js.map +1 -1
- package/dist/index.js +38 -6
- package/dist/index.js.map +1 -1
- package/dist/lint/forbidden-attributes.js +24 -19
- package/dist/lint/forbidden-attributes.js.map +1 -1
- package/dist/lint/index.js +8 -1
- package/dist/lint/index.js.map +1 -1
- package/dist/llmobs/index.js +9 -2
- package/dist/llmobs/index.js.map +1 -1
- package/dist/llmobs/tier1-exporter.js +12 -6
- package/dist/llmobs/tier1-exporter.js.map +1 -1
- package/dist/llmobs/tier2-summarizer.js +5 -1
- package/dist/llmobs/tier2-summarizer.js.map +1 -1
- package/dist/node/dynamic-attribute-processor.js +7 -2
- package/dist/node/dynamic-attribute-processor.js.map +1 -1
- package/dist/node/exporters/resilient-exporter.d.ts +1 -1
- package/dist/node/exporters/resilient-exporter.d.ts.map +1 -1
- package/dist/node/exporters/resilient-exporter.js +12 -8
- package/dist/node/exporters/resilient-exporter.js.map +1 -1
- package/dist/node/index.js +26 -7
- package/dist/node/index.js.map +1 -1
- package/dist/node/init.d.ts +3 -3
- package/dist/node/init.d.ts.map +1 -1
- package/dist/node/init.js +72 -66
- package/dist/node/init.js.map +1 -1
- package/dist/node/local-obs-mode.js +4 -1
- package/dist/node/local-obs-mode.js.map +1 -1
- package/dist/node/log-processor.d.ts +1 -1
- package/dist/node/log-processor.d.ts.map +1 -1
- package/dist/node/log-processor.js +55 -47
- package/dist/node/log-processor.js.map +1 -1
- package/dist/node/metrics-client.js +5 -1
- package/dist/node/metrics-client.js.map +1 -1
- package/dist/node/safe-logger.js +12 -8
- package/dist/node/safe-logger.js.map +1 -1
- package/dist/node/sanitizing-processor.d.ts +2 -2
- package/dist/node/sanitizing-processor.d.ts.map +1 -1
- package/dist/node/sanitizing-processor.js +10 -6
- package/dist/node/sanitizing-processor.js.map +1 -1
- package/dist/node/service-tracers.js +10 -7
- package/dist/node/service-tracers.js.map +1 -1
- package/dist/node/traced-socket.d.ts +3 -3
- package/dist/node/traced-socket.d.ts.map +1 -1
- package/dist/node/traced-socket.js +52 -48
- package/dist/node/traced-socket.js.map +1 -1
- package/dist/npm-registry/emitter.js +47 -34
- package/dist/npm-registry/emitter.js.map +1 -1
- package/dist/npm-registry/index.js +19 -3
- package/dist/npm-registry/index.js.map +1 -1
- package/dist/npm-registry/sanitize.js +27 -16
- package/dist/npm-registry/sanitize.js.map +1 -1
- package/dist/npm-registry/spans.js +12 -7
- package/dist/npm-registry/spans.js.map +1 -1
- package/dist/testing/in-memory-exporter.d.ts +1 -1
- package/dist/testing/in-memory-exporter.d.ts.map +1 -1
- package/dist/testing/in-memory-exporter.js +12 -6
- package/dist/testing/in-memory-exporter.js.map +1 -1
- package/dist/testing/index.js +15 -3
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/metrics-capture.js +15 -12
- package/dist/testing/metrics-capture.js.map +1 -1
- package/dist/testing/test-init.js +31 -24
- package/dist/testing/test-init.js.map +1 -1
- package/dist/types/index.js +17 -1
- package/dist/types/index.js.map +1 -1
- package/dist/types/policy.js +24 -21
- package/dist/types/policy.js.map +1 -1
- package/dist-esm/browser/console-logging.d.ts +18 -0
- package/dist-esm/browser/console-logging.d.ts.map +1 -0
- package/dist-esm/browser/console-logging.js +27 -0
- package/dist-esm/browser/console-logging.js.map +1 -0
- package/dist-esm/browser/early-console-buffer.d.ts +49 -0
- package/dist-esm/browser/early-console-buffer.d.ts.map +1 -0
- package/dist-esm/browser/early-console-buffer.js +181 -0
- package/dist-esm/browser/early-console-buffer.js.map +1 -0
- package/dist-esm/browser/index.d.ts +18 -0
- package/dist-esm/browser/index.d.ts.map +1 -0
- package/dist-esm/browser/index.js +23 -0
- package/dist-esm/browser/index.js.map +1 -0
- package/dist-esm/browser/init.d.ts +100 -0
- package/dist-esm/browser/init.d.ts.map +1 -0
- package/dist-esm/browser/init.js +317 -0
- package/dist-esm/browser/init.js.map +1 -0
- package/dist-esm/browser/instrumentations.d.ts +67 -0
- package/dist-esm/browser/instrumentations.d.ts.map +1 -0
- package/dist-esm/browser/instrumentations.js +79 -0
- package/dist-esm/browser/instrumentations.js.map +1 -0
- package/dist-esm/browser/logs.d.ts +57 -0
- package/dist-esm/browser/logs.d.ts.map +1 -0
- package/dist-esm/browser/logs.js +80 -0
- package/dist-esm/browser/logs.js.map +1 -0
- package/dist-esm/browser/metrics.d.ts +29 -0
- package/dist-esm/browser/metrics.d.ts.map +1 -0
- package/dist-esm/browser/metrics.js +46 -0
- package/dist-esm/browser/metrics.js.map +1 -0
- package/dist-esm/browser/resilient-exporter.d.ts +43 -0
- package/dist-esm/browser/resilient-exporter.d.ts.map +1 -0
- package/dist-esm/browser/resilient-exporter.js +84 -0
- package/dist-esm/browser/resilient-exporter.js.map +1 -0
- package/dist-esm/browser/sanitizer.d.ts +35 -0
- package/dist-esm/browser/sanitizer.d.ts.map +1 -0
- package/dist-esm/browser/sanitizer.js +207 -0
- package/dist-esm/browser/sanitizer.js.map +1 -0
- package/dist-esm/browser/traced-socket.d.ts +102 -0
- package/dist-esm/browser/traced-socket.d.ts.map +1 -0
- package/dist-esm/browser/traced-socket.js +199 -0
- package/dist-esm/browser/traced-socket.js.map +1 -0
- package/dist-esm/common/contracts/tier2-traces.d.ts +116 -0
- package/dist-esm/common/contracts/tier2-traces.d.ts.map +1 -0
- package/dist-esm/common/contracts/tier2-traces.js +725 -0
- package/dist-esm/common/contracts/tier2-traces.js.map +1 -0
- package/dist-esm/common/deployment-type.d.ts +18 -0
- package/dist-esm/common/deployment-type.d.ts.map +1 -0
- package/dist-esm/common/deployment-type.js +27 -0
- package/dist-esm/common/deployment-type.js.map +1 -0
- package/dist-esm/common/guardrails.d.ts +132 -0
- package/dist-esm/common/guardrails.d.ts.map +1 -0
- package/dist-esm/common/guardrails.js +230 -0
- package/dist-esm/common/guardrails.js.map +1 -0
- package/dist-esm/common/index.d.ts +17 -0
- package/dist-esm/common/index.d.ts.map +1 -0
- package/dist-esm/common/index.js +17 -0
- package/dist-esm/common/index.js.map +1 -0
- package/dist-esm/common/log-sanitizer.d.ts +163 -0
- package/dist-esm/common/log-sanitizer.d.ts.map +1 -0
- package/dist-esm/common/log-sanitizer.js +745 -0
- package/dist-esm/common/log-sanitizer.js.map +1 -0
- package/dist-esm/common/policy-evaluator.d.ts +103 -0
- package/dist-esm/common/policy-evaluator.d.ts.map +1 -0
- package/dist-esm/common/policy-evaluator.js +196 -0
- package/dist-esm/common/policy-evaluator.js.map +1 -0
- package/dist-esm/common/resource.d.ts +79 -0
- package/dist-esm/common/resource.d.ts.map +1 -0
- package/dist-esm/common/resource.js +121 -0
- package/dist-esm/common/resource.js.map +1 -0
- package/dist-esm/common/tier-hints.d.ts +182 -0
- package/dist-esm/common/tier-hints.d.ts.map +1 -0
- package/dist-esm/common/tier-hints.js +199 -0
- package/dist-esm/common/tier-hints.js.map +1 -0
- package/dist-esm/common/trace-sanitizer.d.ts +82 -0
- package/dist-esm/common/trace-sanitizer.d.ts.map +1 -0
- package/dist-esm/common/trace-sanitizer.js +230 -0
- package/dist-esm/common/trace-sanitizer.js.map +1 -0
- package/dist-esm/common/traced-socket-types.d.ts +43 -0
- package/dist-esm/common/traced-socket-types.d.ts.map +1 -0
- package/dist-esm/common/traced-socket-types.js +16 -0
- package/dist-esm/common/traced-socket-types.js.map +1 -0
- package/dist-esm/index.d.ts +45 -0
- package/dist-esm/index.d.ts.map +1 -0
- package/dist-esm/index.js +56 -0
- package/dist-esm/index.js.map +1 -0
- package/dist-esm/lint/forbidden-attributes.d.ts +149 -0
- package/dist-esm/lint/forbidden-attributes.d.ts.map +1 -0
- package/dist-esm/lint/forbidden-attributes.js +210 -0
- package/dist-esm/lint/forbidden-attributes.js.map +1 -0
- package/dist-esm/lint/index.d.ts +9 -0
- package/dist-esm/lint/index.d.ts.map +1 -0
- package/dist-esm/lint/index.js +9 -0
- package/dist-esm/lint/index.js.map +1 -0
- package/dist-esm/llmobs/index.d.ts +22 -0
- package/dist-esm/llmobs/index.d.ts.map +1 -0
- package/dist-esm/llmobs/index.js +22 -0
- package/dist-esm/llmobs/index.js.map +1 -0
- package/dist-esm/llmobs/tier1-exporter.d.ts +146 -0
- package/dist-esm/llmobs/tier1-exporter.d.ts.map +1 -0
- package/dist-esm/llmobs/tier1-exporter.js +193 -0
- package/dist-esm/llmobs/tier1-exporter.js.map +1 -0
- package/dist-esm/llmobs/tier2-summarizer.d.ts +268 -0
- package/dist-esm/llmobs/tier2-summarizer.d.ts.map +1 -0
- package/dist-esm/llmobs/tier2-summarizer.js +652 -0
- package/dist-esm/llmobs/tier2-summarizer.js.map +1 -0
- package/dist-esm/node/dynamic-attribute-processor.d.ts +43 -0
- package/dist-esm/node/dynamic-attribute-processor.d.ts.map +1 -0
- package/dist-esm/node/dynamic-attribute-processor.js +74 -0
- package/dist-esm/node/dynamic-attribute-processor.js.map +1 -0
- package/dist-esm/node/exporters/resilient-exporter.d.ts +91 -0
- package/dist-esm/node/exporters/resilient-exporter.d.ts.map +1 -0
- package/dist-esm/node/exporters/resilient-exporter.js +132 -0
- package/dist-esm/node/exporters/resilient-exporter.js.map +1 -0
- package/dist-esm/node/index.d.ts +13 -0
- package/dist-esm/node/index.d.ts.map +1 -0
- package/dist-esm/node/index.js +13 -0
- package/dist-esm/node/index.js.map +1 -0
- package/dist-esm/node/init.d.ts +97 -0
- package/dist-esm/node/init.d.ts.map +1 -0
- package/dist-esm/node/init.js +359 -0
- package/dist-esm/node/init.js.map +1 -0
- package/dist-esm/node/local-obs-mode.d.ts +17 -0
- package/dist-esm/node/local-obs-mode.d.ts.map +1 -0
- package/dist-esm/node/local-obs-mode.js +19 -0
- package/dist-esm/node/local-obs-mode.js.map +1 -0
- package/dist-esm/node/log-processor.d.ts +132 -0
- package/dist-esm/node/log-processor.d.ts.map +1 -0
- package/dist-esm/node/log-processor.js +389 -0
- package/dist-esm/node/log-processor.js.map +1 -0
- package/dist-esm/node/metrics-client.d.ts +88 -0
- package/dist-esm/node/metrics-client.d.ts.map +1 -0
- package/dist-esm/node/metrics-client.js +205 -0
- package/dist-esm/node/metrics-client.js.map +1 -0
- package/dist-esm/node/safe-logger.d.ts +55 -0
- package/dist-esm/node/safe-logger.d.ts.map +1 -0
- package/dist-esm/node/safe-logger.js +154 -0
- package/dist-esm/node/safe-logger.js.map +1 -0
- package/dist-esm/node/sanitizing-processor.d.ts +67 -0
- package/dist-esm/node/sanitizing-processor.d.ts.map +1 -0
- package/dist-esm/node/sanitizing-processor.js +163 -0
- package/dist-esm/node/sanitizing-processor.js.map +1 -0
- package/dist-esm/node/service-tracers.d.ts +52 -0
- package/dist-esm/node/service-tracers.d.ts.map +1 -0
- package/dist-esm/node/service-tracers.js +78 -0
- package/dist-esm/node/service-tracers.js.map +1 -0
- package/dist-esm/node/traced-socket.d.ts +120 -0
- package/dist-esm/node/traced-socket.d.ts.map +1 -0
- package/dist-esm/node/traced-socket.js +334 -0
- package/dist-esm/node/traced-socket.js.map +1 -0
- package/dist-esm/npm-registry/emitter.d.ts +330 -0
- package/dist-esm/npm-registry/emitter.d.ts.map +1 -0
- package/dist-esm/npm-registry/emitter.js +546 -0
- package/dist-esm/npm-registry/emitter.js.map +1 -0
- package/dist-esm/npm-registry/index.d.ts +12 -0
- package/dist-esm/npm-registry/index.d.ts.map +1 -0
- package/dist-esm/npm-registry/index.js +12 -0
- package/dist-esm/npm-registry/index.js.map +1 -0
- package/dist-esm/npm-registry/sanitize.d.ts +174 -0
- package/dist-esm/npm-registry/sanitize.d.ts.map +1 -0
- package/dist-esm/npm-registry/sanitize.js +331 -0
- package/dist-esm/npm-registry/sanitize.js.map +1 -0
- package/dist-esm/npm-registry/spans.d.ts +61 -0
- package/dist-esm/npm-registry/spans.d.ts.map +1 -0
- package/dist-esm/npm-registry/spans.js +86 -0
- package/dist-esm/npm-registry/spans.js.map +1 -0
- package/dist-esm/package.json +1 -0
- package/dist-esm/testing/in-memory-exporter.d.ts +179 -0
- package/dist-esm/testing/in-memory-exporter.d.ts.map +1 -0
- package/dist-esm/testing/in-memory-exporter.js +250 -0
- package/dist-esm/testing/in-memory-exporter.js.map +1 -0
- package/dist-esm/testing/index.d.ts +9 -0
- package/dist-esm/testing/index.d.ts.map +1 -0
- package/dist-esm/testing/index.js +9 -0
- package/dist-esm/testing/index.js.map +1 -0
- package/dist-esm/testing/metrics-capture.d.ts +29 -0
- package/dist-esm/testing/metrics-capture.d.ts.map +1 -0
- package/dist-esm/testing/metrics-capture.js +71 -0
- package/dist-esm/testing/metrics-capture.js.map +1 -0
- package/dist-esm/testing/test-init.d.ts +80 -0
- package/dist-esm/testing/test-init.d.ts.map +1 -0
- package/dist-esm/testing/test-init.js +137 -0
- package/dist-esm/testing/test-init.js.map +1 -0
- package/dist-esm/types/index.d.ts +84 -0
- package/dist-esm/types/index.d.ts.map +1 -0
- package/dist-esm/types/index.js +7 -0
- package/dist-esm/types/index.js.map +1 -0
- package/dist-esm/types/policy.d.ts +92 -0
- package/dist-esm/types/policy.d.ts.map +1 -0
- package/dist-esm/types/policy.js +122 -0
- package/dist-esm/types/policy.js.map +1 -0
- package/package.json +17 -11
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metrics Client for Node.js Telemetry
|
|
3
|
+
*
|
|
4
|
+
* Provides a simple interface for recording metrics via OpenTelemetry.
|
|
5
|
+
* This is the recommended way to record metrics in services using the telemetry package.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Converts Labels to OTEL Attributes.
|
|
9
|
+
*/
|
|
10
|
+
function labelsToAttributes(labels) {
|
|
11
|
+
if (!labels)
|
|
12
|
+
return {};
|
|
13
|
+
const attributes = {};
|
|
14
|
+
for (const [key, value] of Object.entries(labels)) {
|
|
15
|
+
attributes[key] = String(value);
|
|
16
|
+
}
|
|
17
|
+
return attributes;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* TTL configuration for gauge values.
|
|
21
|
+
*/
|
|
22
|
+
const GAUGE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
23
|
+
const GAUGE_MAX_ENTRIES = 10_000; // Max entries per gauge to prevent unbounded growth
|
|
24
|
+
const GAUGE_CLEANUP_INTERVAL_MS = 60 * 1000; // Cleanup every minute
|
|
25
|
+
/**
|
|
26
|
+
* TTL-based map for gauge values with automatic cleanup.
|
|
27
|
+
*/
|
|
28
|
+
class TTLGaugeMap {
|
|
29
|
+
ttlMs;
|
|
30
|
+
maxEntries;
|
|
31
|
+
entries = new Map();
|
|
32
|
+
cleanupTimer = null;
|
|
33
|
+
constructor(ttlMs = GAUGE_TTL_MS, maxEntries = GAUGE_MAX_ENTRIES) {
|
|
34
|
+
this.ttlMs = ttlMs;
|
|
35
|
+
this.maxEntries = maxEntries;
|
|
36
|
+
// Start periodic cleanup
|
|
37
|
+
this.cleanupTimer = setInterval(() => this.cleanup(), GAUGE_CLEANUP_INTERVAL_MS);
|
|
38
|
+
// Don't prevent Node.js from exiting
|
|
39
|
+
this.cleanupTimer.unref();
|
|
40
|
+
}
|
|
41
|
+
set(labelsKey, value) {
|
|
42
|
+
// If at max capacity, remove oldest entry
|
|
43
|
+
if (this.entries.size >= this.maxEntries && !this.entries.has(labelsKey)) {
|
|
44
|
+
this.evictOldest();
|
|
45
|
+
}
|
|
46
|
+
this.entries.set(labelsKey, {
|
|
47
|
+
value,
|
|
48
|
+
updatedAt: Date.now(),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Gets all non-expired entries.
|
|
53
|
+
*/
|
|
54
|
+
getAll() {
|
|
55
|
+
const now = Date.now();
|
|
56
|
+
const result = new Map();
|
|
57
|
+
for (const [key, entry] of this.entries) {
|
|
58
|
+
if (now - entry.updatedAt <= this.ttlMs) {
|
|
59
|
+
result.set(key, entry.value);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Removes expired entries.
|
|
66
|
+
*/
|
|
67
|
+
cleanup() {
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
for (const [key, entry] of this.entries) {
|
|
70
|
+
if (now - entry.updatedAt > this.ttlMs) {
|
|
71
|
+
this.entries.delete(key);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Evicts the oldest entry when at max capacity.
|
|
77
|
+
*/
|
|
78
|
+
evictOldest() {
|
|
79
|
+
let oldestKey = null;
|
|
80
|
+
let oldestTime = Infinity;
|
|
81
|
+
for (const [key, entry] of this.entries) {
|
|
82
|
+
if (entry.updatedAt < oldestTime) {
|
|
83
|
+
oldestTime = entry.updatedAt;
|
|
84
|
+
oldestKey = key;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (oldestKey) {
|
|
88
|
+
this.entries.delete(oldestKey);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Stops the cleanup timer.
|
|
93
|
+
*/
|
|
94
|
+
destroy() {
|
|
95
|
+
if (this.cleanupTimer) {
|
|
96
|
+
clearInterval(this.cleanupTimer);
|
|
97
|
+
this.cleanupTimer = null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Metrics client that provides a unified interface for recording OTEL metrics.
|
|
103
|
+
*/
|
|
104
|
+
export class MetricsClient {
|
|
105
|
+
meter;
|
|
106
|
+
counters = new Map();
|
|
107
|
+
histograms = new Map();
|
|
108
|
+
// Gauges use TTL-based maps to prevent unbounded memory growth
|
|
109
|
+
gaugeValues = new Map();
|
|
110
|
+
constructor(meter) {
|
|
111
|
+
this.meter = meter;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Creates or retrieves a counter and returns a handle.
|
|
115
|
+
*/
|
|
116
|
+
counter(name, options) {
|
|
117
|
+
if (!this.counters.has(name)) {
|
|
118
|
+
this.counters.set(name, this.meter.createCounter(name, options));
|
|
119
|
+
}
|
|
120
|
+
const counter = this.counters.get(name);
|
|
121
|
+
return {
|
|
122
|
+
inc: (labels, value = 1) => {
|
|
123
|
+
counter.add(value, labelsToAttributes(labels));
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Creates or retrieves a gauge and returns a handle.
|
|
129
|
+
* Note: OTEL gauges are async/observable, so we store the value and emit via callback.
|
|
130
|
+
* Values are automatically expired after 5 minutes to prevent unbounded memory growth.
|
|
131
|
+
*/
|
|
132
|
+
gauge(name) {
|
|
133
|
+
if (!this.gaugeValues.has(name)) {
|
|
134
|
+
const ttlMap = new TTLGaugeMap();
|
|
135
|
+
this.gaugeValues.set(name, ttlMap);
|
|
136
|
+
// Create an observable gauge that reads from the TTL map
|
|
137
|
+
this.meter.createObservableGauge(name, {}).addCallback((result) => {
|
|
138
|
+
const ttlMapRef = this.gaugeValues.get(name);
|
|
139
|
+
if (ttlMapRef) {
|
|
140
|
+
// getAll() only returns non-expired entries
|
|
141
|
+
for (const [labelsKey, value] of ttlMapRef.getAll()) {
|
|
142
|
+
const labels = JSON.parse(labelsKey);
|
|
143
|
+
result.observe(value, labelsToAttributes(labels));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
set: (labels, value) => {
|
|
150
|
+
const ttlMap = this.gaugeValues.get(name);
|
|
151
|
+
const labelsKey = JSON.stringify(labels);
|
|
152
|
+
ttlMap.set(labelsKey, value);
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Creates or retrieves a histogram and returns a handle.
|
|
158
|
+
*/
|
|
159
|
+
histogram(name, options) {
|
|
160
|
+
if (!this.histograms.has(name)) {
|
|
161
|
+
this.histograms.set(name, this.meter.createHistogram(name, options
|
|
162
|
+
? {
|
|
163
|
+
...(options.bucketBoundaries
|
|
164
|
+
? {
|
|
165
|
+
advice: {
|
|
166
|
+
explicitBucketBoundaries: options.bucketBoundaries,
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
: {}),
|
|
170
|
+
...(options.description
|
|
171
|
+
? { description: options.description }
|
|
172
|
+
: {}),
|
|
173
|
+
...(options.unit ? { unit: options.unit } : {}),
|
|
174
|
+
}
|
|
175
|
+
: undefined));
|
|
176
|
+
}
|
|
177
|
+
const histogram = this.histograms.get(name);
|
|
178
|
+
return {
|
|
179
|
+
observe: (labels, value) => {
|
|
180
|
+
histogram.record(value, labelsToAttributes(labels));
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Increments a counter by name (creates if not exists). `options` is only
|
|
186
|
+
* applied on the instrument's first registration; see `counter`.
|
|
187
|
+
*/
|
|
188
|
+
incCounter(name, labels, value = 1, options) {
|
|
189
|
+
this.counter(name, options).inc(labels, value);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Sets a gauge value by name (creates if not exists).
|
|
193
|
+
*/
|
|
194
|
+
setGauge(name, labels, value) {
|
|
195
|
+
this.gauge(name).set(labels, value);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Observes a histogram value by name (creates if not exists). `options` is
|
|
199
|
+
* only applied on the instrument's first registration; see `histogram`.
|
|
200
|
+
*/
|
|
201
|
+
observeHistogram(name, labels, value, options) {
|
|
202
|
+
this.histogram(name, options).observe(labels, value);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=metrics-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics-client.js","sourceRoot":"","sources":["../../src/node/metrics-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy-Aware Safe Logger (Pino Wrapper)
|
|
3
|
+
*
|
|
4
|
+
* Wraps a Pino logger to redact forbidden fields (e.g. stack, prompt, code)
|
|
5
|
+
* from log call arguments so Tier 1 data is not written to stdout/log export.
|
|
6
|
+
*
|
|
7
|
+
* @see https://github.com/superblocksteam/engineering/blob/main/projects/o11y-refactor/epics/epic-c4-logging-strategy.md
|
|
8
|
+
*/
|
|
9
|
+
import { type LoggingPolicyConfig } from "./log-processor.js";
|
|
10
|
+
/**
|
|
11
|
+
* Minimal logger interface compatible with Pino.
|
|
12
|
+
* Use createSafeLogger(pinoLogger, policy) with a Pino logger instance.
|
|
13
|
+
*/
|
|
14
|
+
export interface SafeLoggerLike {
|
|
15
|
+
trace(...args: unknown[]): void;
|
|
16
|
+
debug(...args: unknown[]): void;
|
|
17
|
+
info(...args: unknown[]): void;
|
|
18
|
+
warn(...args: unknown[]): void;
|
|
19
|
+
error(...args: unknown[]): void;
|
|
20
|
+
fatal(...args: unknown[]): void;
|
|
21
|
+
child(bindings: Record<string, unknown>): SafeLoggerLike;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Redact and sanitize a single log argument: key-based (forbidden fields) and
|
|
25
|
+
* value-based (stack traces, PII, policy redactPatterns). Error instances are
|
|
26
|
+
* converted to a plain object first. Exported for use by callers (e.g. server
|
|
27
|
+
* RemoteLogger redactRecord) so stdout and OTLP paths share the same rules.
|
|
28
|
+
*/
|
|
29
|
+
export declare function redactForbiddenFields(arg: unknown, policy: LoggingPolicyConfig): unknown;
|
|
30
|
+
/**
|
|
31
|
+
* Creates a logger wrapper that redacts policy-forbidden fields from log calls
|
|
32
|
+
* and applies the policy-effective log level (exportMode + envLevel) so that
|
|
33
|
+
* both the root and any child loggers respect exportMode (e.g. ERRORS_ONLY → warn floor).
|
|
34
|
+
*
|
|
35
|
+
* Use when logging in contexts where output may be exported (e.g. cloud-prem).
|
|
36
|
+
* Forbidden fields (stack, prompt, code, filePath, etc.) are replaced with
|
|
37
|
+
* "[REDACTED]" so they never appear in exported logs.
|
|
38
|
+
*
|
|
39
|
+
* @param baseLogger - Underlying Pino (or compatible) logger
|
|
40
|
+
* @param policy - Logging policy (e.g. from getLoggingPolicy(telemetryPolicy))
|
|
41
|
+
* @param envLevel - Optional env-configured level (e.g. SUPERBLOCKS_SERVER_LOG_LEVEL).
|
|
42
|
+
* When provided, base logger level is set from getMinimumPinoLevelFromLoggingPolicy(policy, envLevel)
|
|
43
|
+
* so exportMode (e.g. ERRORS_ONLY) is respected; children inherit this level in .child().
|
|
44
|
+
* @returns A logger that redacts forbidden fields and respects policy level (same interface as baseLogger)
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* const policy = getLoggingPolicy(telemetryPolicy);
|
|
49
|
+
* const safeLogger = createSafeLogger(logger, policy, envs.get('SUPERBLOCKS_SERVER_LOG_LEVEL'));
|
|
50
|
+
* safeLogger.error({ err, stack: err.stack }, 'Request failed');
|
|
51
|
+
* // Exported logs see stack: "[REDACTED]"
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function createSafeLogger<T extends SafeLoggerLike>(baseLogger: T, policy: LoggingPolicyConfig, envLevel?: string): T;
|
|
55
|
+
//# sourceMappingURL=safe-logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safe-logger.d.ts","sourceRoot":"","sources":["../../src/node/safe-logger.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAC;AAE5B;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChC,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChC,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChC,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAChC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,cAAc,CAAC;CAC1D;AA+BD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,OAAO,EACZ,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAaT;AAsCD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,cAAc,EACvD,UAAU,EAAE,CAAC,EACb,MAAM,EAAE,mBAAmB,EAC3B,QAAQ,CAAC,EAAE,MAAM,GAChB,CAAC,CA6EH"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy-Aware Safe Logger (Pino Wrapper)
|
|
3
|
+
*
|
|
4
|
+
* Wraps a Pino logger to redact forbidden fields (e.g. stack, prompt, code)
|
|
5
|
+
* from log call arguments so Tier 1 data is not written to stdout/log export.
|
|
6
|
+
*
|
|
7
|
+
* @see https://github.com/superblocksteam/engineering/blob/main/projects/o11y-refactor/epics/epic-c4-logging-strategy.md
|
|
8
|
+
*/
|
|
9
|
+
import { sanitizeLogMessageForExport, sanitizeLogRecordForExport, } from "../common/log-sanitizer.js";
|
|
10
|
+
import { getMinimumPinoLevelFromLoggingPolicy, } from "./log-processor.js";
|
|
11
|
+
/** Log level methods only. "child" is handled separately so returned children are also wrapped with createSafeLogger. */
|
|
12
|
+
const LOG_METHODS = new Set([
|
|
13
|
+
"trace",
|
|
14
|
+
"debug",
|
|
15
|
+
"info",
|
|
16
|
+
"warn",
|
|
17
|
+
"error",
|
|
18
|
+
"fatal",
|
|
19
|
+
]);
|
|
20
|
+
/**
|
|
21
|
+
* Convert an Error to a plain object so it can be redacted like any other object.
|
|
22
|
+
* Includes name, message, stack, and any own enumerable properties (e.g. code, statusCode).
|
|
23
|
+
*/
|
|
24
|
+
function errorToPlainObject(err) {
|
|
25
|
+
const obj = {
|
|
26
|
+
message: err.message,
|
|
27
|
+
name: err.name,
|
|
28
|
+
stack: err.stack,
|
|
29
|
+
};
|
|
30
|
+
const errRecord = err;
|
|
31
|
+
for (const key of Object.keys(err)) {
|
|
32
|
+
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
33
|
+
obj[key] = errRecord[key];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return obj;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Redact and sanitize a single log argument: key-based (forbidden fields) and
|
|
40
|
+
* value-based (stack traces, PII, policy redactPatterns). Error instances are
|
|
41
|
+
* converted to a plain object first. Exported for use by callers (e.g. server
|
|
42
|
+
* RemoteLogger redactRecord) so stdout and OTLP paths share the same rules.
|
|
43
|
+
*/
|
|
44
|
+
export function redactForbiddenFields(arg, policy) {
|
|
45
|
+
if (arg === null || typeof arg !== "object") {
|
|
46
|
+
return arg;
|
|
47
|
+
}
|
|
48
|
+
if (arg instanceof Error) {
|
|
49
|
+
return redactForbiddenFields(errorToPlainObject(arg), policy);
|
|
50
|
+
}
|
|
51
|
+
return sanitizeLogRecordForExport(arg, policy.forbiddenFields, policy.redactPatterns, policy.sensitivePatterns);
|
|
52
|
+
}
|
|
53
|
+
function redactArg(arg, policy) {
|
|
54
|
+
return redactForbiddenFields(arg, policy);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Redact log call arguments: first arg (merge object) with key+value sanitization,
|
|
58
|
+
* and message string (second arg) with value-based sanitization (patterns, stack, PII).
|
|
59
|
+
*/
|
|
60
|
+
function redactArgs(args, policy) {
|
|
61
|
+
if (args.length === 0)
|
|
62
|
+
return args;
|
|
63
|
+
const [first, ...rest] = args;
|
|
64
|
+
const redactedFirst = typeof first === "object" && first !== null
|
|
65
|
+
? redactArg(first, policy)
|
|
66
|
+
: typeof first === "string"
|
|
67
|
+
? sanitizeLogMessageForExport(first, policy.redactPatterns, policy.sensitivePatterns)
|
|
68
|
+
: first;
|
|
69
|
+
const redactedRest = rest.map((r, i) => i === 0 && typeof r === "string"
|
|
70
|
+
? sanitizeLogMessageForExport(r, policy.redactPatterns, policy.sensitivePatterns)
|
|
71
|
+
: r);
|
|
72
|
+
return [redactedFirst, ...redactedRest];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Creates a logger wrapper that redacts policy-forbidden fields from log calls
|
|
76
|
+
* and applies the policy-effective log level (exportMode + envLevel) so that
|
|
77
|
+
* both the root and any child loggers respect exportMode (e.g. ERRORS_ONLY → warn floor).
|
|
78
|
+
*
|
|
79
|
+
* Use when logging in contexts where output may be exported (e.g. cloud-prem).
|
|
80
|
+
* Forbidden fields (stack, prompt, code, filePath, etc.) are replaced with
|
|
81
|
+
* "[REDACTED]" so they never appear in exported logs.
|
|
82
|
+
*
|
|
83
|
+
* @param baseLogger - Underlying Pino (or compatible) logger
|
|
84
|
+
* @param policy - Logging policy (e.g. from getLoggingPolicy(telemetryPolicy))
|
|
85
|
+
* @param envLevel - Optional env-configured level (e.g. SUPERBLOCKS_SERVER_LOG_LEVEL).
|
|
86
|
+
* When provided, base logger level is set from getMinimumPinoLevelFromLoggingPolicy(policy, envLevel)
|
|
87
|
+
* so exportMode (e.g. ERRORS_ONLY) is respected; children inherit this level in .child().
|
|
88
|
+
* @returns A logger that redacts forbidden fields and respects policy level (same interface as baseLogger)
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const policy = getLoggingPolicy(telemetryPolicy);
|
|
93
|
+
* const safeLogger = createSafeLogger(logger, policy, envs.get('SUPERBLOCKS_SERVER_LOG_LEVEL'));
|
|
94
|
+
* safeLogger.error({ err, stack: err.stack }, 'Request failed');
|
|
95
|
+
* // Exported logs see stack: "[REDACTED]"
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export function createSafeLogger(baseLogger, policy, envLevel) {
|
|
99
|
+
if (envLevel !== undefined) {
|
|
100
|
+
const effectiveLevel = getMinimumPinoLevelFromLoggingPolicy(policy, envLevel);
|
|
101
|
+
baseLogger.level = effectiveLevel;
|
|
102
|
+
}
|
|
103
|
+
return new Proxy(baseLogger, {
|
|
104
|
+
get(target, prop) {
|
|
105
|
+
if (typeof prop === "symbol")
|
|
106
|
+
return Reflect.get(target, prop);
|
|
107
|
+
const value = target[prop];
|
|
108
|
+
if (LOG_METHODS.has(prop) && typeof value === "function") {
|
|
109
|
+
return (...args) => {
|
|
110
|
+
let redacted;
|
|
111
|
+
try {
|
|
112
|
+
redacted = redactArgs(args, policy);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Redaction can throw (e.g. Proxy with throwing getters, circular refs).
|
|
116
|
+
// Never emit original args — use a safe fallback so PII/Tier 1 is not exported.
|
|
117
|
+
redacted = [
|
|
118
|
+
{ "[SafeLogger]": "redaction_failed" },
|
|
119
|
+
"[SafeLogger] Log omitted: redaction failed",
|
|
120
|
+
];
|
|
121
|
+
target.error("[SafeLogger] Redaction failed; log omitted to avoid leaking data");
|
|
122
|
+
}
|
|
123
|
+
return value.apply(target, redacted);
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (prop === "child" && typeof value === "function") {
|
|
127
|
+
return (bindings) => {
|
|
128
|
+
// Redact forbidden fields from child bindings so they never appear in log output
|
|
129
|
+
// (e.g. user-email from req.logTags would otherwise be merged into every log line)
|
|
130
|
+
let redactedBindings;
|
|
131
|
+
try {
|
|
132
|
+
redactedBindings = redactArg(bindings, policy);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// Never pass original bindings — use empty so PII/Tier 1 is not exported.
|
|
136
|
+
redactedBindings = {};
|
|
137
|
+
target.error("[SafeLogger] Child bindings omitted: redaction failed");
|
|
138
|
+
}
|
|
139
|
+
const child = value.call(target, redactedBindings);
|
|
140
|
+
// Ensure child respects same level as parent (exportMode / localMaxLevel)
|
|
141
|
+
const parentWithLevel = target;
|
|
142
|
+
const childWithLevel = child;
|
|
143
|
+
if (parentWithLevel.level !== undefined &&
|
|
144
|
+
childWithLevel.level !== undefined) {
|
|
145
|
+
childWithLevel.level = parentWithLevel.level;
|
|
146
|
+
}
|
|
147
|
+
return createSafeLogger(child, policy);
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return value;
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=safe-logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safe-logger.js","sourceRoot":"","sources":["../../src/node/safe-logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitizing Span Processor (Tier 2)
|
|
3
|
+
*
|
|
4
|
+
* Sanitizes spans before export per epic-a3-sanitized-traces.
|
|
5
|
+
* - All spans are passed to localProcessor (full fidelity for local/on-prem retention).
|
|
6
|
+
* - Only contract-compliant, sanitized spans are exported to Tier 2.
|
|
7
|
+
* - Fail-closed: when Tier 2 export is disabled, no exporter is attached.
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from "@opentelemetry/api";
|
|
10
|
+
import type { ReadableSpan, Span, SpanExporter, SpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
11
|
+
import { TelemetryPolicyEvaluator } from "../common/policy-evaluator.js";
|
|
12
|
+
import { TraceSanitizer } from "../common/trace-sanitizer.js";
|
|
13
|
+
/** No-op span processor for the "local" path when only Tier 2 export is used. */
|
|
14
|
+
declare class NoopSpanProcessor implements SpanProcessor {
|
|
15
|
+
onStart(): void;
|
|
16
|
+
onEnd(): void;
|
|
17
|
+
shutdown(): Promise<void>;
|
|
18
|
+
forceFlush(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export declare const noopSpanProcessor: NoopSpanProcessor;
|
|
21
|
+
/** Logger interface for processor messages (e.g. queue full, export failure). */
|
|
22
|
+
interface SanitizingProcessorLogger {
|
|
23
|
+
debug?(obj: object, msg?: string): void;
|
|
24
|
+
debug?(msg: string): void;
|
|
25
|
+
error?(obj: object, msg?: string): void;
|
|
26
|
+
error?(msg: string): void;
|
|
27
|
+
warn(obj: object, msg?: string): void;
|
|
28
|
+
warn(msg: string): void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Span processor that sanitizes spans before Tier 2 export.
|
|
32
|
+
*
|
|
33
|
+
* CRITICAL: This processor owns the export path. The exporter is internal.
|
|
34
|
+
* For Tier 1: All spans passed to localProcessor (e.g. no-op or local collector).
|
|
35
|
+
* For Tier 2: Only contract-compliant spans, sanitized, then batched and exported.
|
|
36
|
+
*
|
|
37
|
+
* NOTE: The wrapped tier2Exporter must tolerate concurrent export() calls.
|
|
38
|
+
* The periodic timer and the batch-size trigger can both fire flushExports()
|
|
39
|
+
* in the same tick. OTLPTraceExporter is safe; custom exporters should verify.
|
|
40
|
+
*/
|
|
41
|
+
export declare class SanitizingSpanProcessor implements SpanProcessor {
|
|
42
|
+
private readonly exportTimeoutMs;
|
|
43
|
+
private readonly sanitizer;
|
|
44
|
+
private readonly localProcessor;
|
|
45
|
+
private readonly tier2Exporter;
|
|
46
|
+
private readonly logger;
|
|
47
|
+
private pendingExports;
|
|
48
|
+
private exportTimer;
|
|
49
|
+
private droppedQueueFull;
|
|
50
|
+
/** Tracks in-flight fire-and-forget flushes so shutdown/forceFlush can await them. */
|
|
51
|
+
private readonly inflight;
|
|
52
|
+
constructor(policyEvaluator: TelemetryPolicyEvaluator, localProcessor: SpanProcessor, tier2Exporter: SpanExporter | null, traceSanitizer: TraceSanitizer, logger: SanitizingProcessorLogger, exportTimeoutMs?: number);
|
|
53
|
+
onStart(span: Span, parentContext: Context): void;
|
|
54
|
+
onEnd(span: ReadableSpan): void;
|
|
55
|
+
/** Fire-and-forget batch flush — tracks the in-flight promise so shutdown/forceFlush can await it. */
|
|
56
|
+
private flushExports;
|
|
57
|
+
/**
|
|
58
|
+
* Awaitable batch flush for drain paths (shutdown, forceFlush).
|
|
59
|
+
* Always resolves — times out after exportTimeoutMs if the exporter
|
|
60
|
+
* never calls its callback (e.g. broken socket, hung downstream).
|
|
61
|
+
*/
|
|
62
|
+
private flushExportsAsync;
|
|
63
|
+
shutdown(): Promise<void>;
|
|
64
|
+
forceFlush(): Promise<void>;
|
|
65
|
+
}
|
|
66
|
+
export {};
|
|
67
|
+
//# sourceMappingURL=sanitizing-processor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitizing-processor.d.ts","sourceRoot":"","sources":["../../src/node/sanitizing-processor.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,KAAK,EACV,YAAY,EACZ,IAAI,EACJ,YAAY,EACZ,aAAa,EACd,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAO9D,iFAAiF;AACjF,cAAM,iBAAkB,YAAW,aAAa;IAC9C,OAAO,IAAI,IAAI;IACf,KAAK,IAAI,IAAI;IACP,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IACzB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAClC;AAED,eAAO,MAAM,iBAAiB,mBAA0B,CAAC;AAGzD,iFAAiF;AACjF,UAAU,yBAAyB;IACjC,KAAK,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,KAAK,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,KAAK,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAKD;;;;;;;;;;GAUG;AACH,qBAAa,uBAAwB,YAAW,aAAa;IAiBzD,OAAO,CAAC,QAAQ,CAAC,eAAe;IAhBlC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAiB;IAC3C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAgB;IAC/C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;IACnD,OAAO,CAAC,cAAc,CAAsB;IAC5C,OAAO,CAAC,WAAW,CAA+C;IAClE,OAAO,CAAC,gBAAgB,CAAS;IACjC,sFAAsF;IACtF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;gBAGnD,eAAe,EAAE,wBAAwB,EACzC,cAAc,EAAE,aAAa,EAC7B,aAAa,EAAE,YAAY,GAAG,IAAI,EAClC,cAAc,EAAE,cAAc,EAC9B,MAAM,EAAE,yBAAyB,EAChB,eAAe,SAAO;IAoBzC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,GAAG,IAAI;IAIjD,KAAK,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IA8B/B,sGAAsG;IACtG,OAAO,CAAC,YAAY;IAOpB;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAoDnB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAezB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;CAUlC"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitizing Span Processor (Tier 2)
|
|
3
|
+
*
|
|
4
|
+
* Sanitizes spans before export per epic-a3-sanitized-traces.
|
|
5
|
+
* - All spans are passed to localProcessor (full fidelity for local/on-prem retention).
|
|
6
|
+
* - Only contract-compliant, sanitized spans are exported to Tier 2.
|
|
7
|
+
* - Fail-closed: when Tier 2 export is disabled, no exporter is attached.
|
|
8
|
+
*/
|
|
9
|
+
import { ExportResultCode } from "@opentelemetry/core";
|
|
10
|
+
import { TelemetryTier } from "../types/policy.js";
|
|
11
|
+
const BATCH_SIZE = 512;
|
|
12
|
+
/** Max pending spans before dropping. Aligns with BatchSpanProcessor / resilient-exporter queue limits. */
|
|
13
|
+
const MAX_PENDING_EXPORTS = 2048;
|
|
14
|
+
/** No-op span processor for the "local" path when only Tier 2 export is used. */
|
|
15
|
+
class NoopSpanProcessor {
|
|
16
|
+
onStart() { }
|
|
17
|
+
onEnd() { }
|
|
18
|
+
async shutdown() { }
|
|
19
|
+
async forceFlush() { }
|
|
20
|
+
}
|
|
21
|
+
export const noopSpanProcessor = new NoopSpanProcessor();
|
|
22
|
+
const EXPORT_INTERVAL_MS = 5000;
|
|
23
|
+
/**
|
|
24
|
+
* Span processor that sanitizes spans before Tier 2 export.
|
|
25
|
+
*
|
|
26
|
+
* CRITICAL: This processor owns the export path. The exporter is internal.
|
|
27
|
+
* For Tier 1: All spans passed to localProcessor (e.g. no-op or local collector).
|
|
28
|
+
* For Tier 2: Only contract-compliant spans, sanitized, then batched and exported.
|
|
29
|
+
*
|
|
30
|
+
* NOTE: The wrapped tier2Exporter must tolerate concurrent export() calls.
|
|
31
|
+
* The periodic timer and the batch-size trigger can both fire flushExports()
|
|
32
|
+
* in the same tick. OTLPTraceExporter is safe; custom exporters should verify.
|
|
33
|
+
*/
|
|
34
|
+
export class SanitizingSpanProcessor {
|
|
35
|
+
exportTimeoutMs;
|
|
36
|
+
sanitizer;
|
|
37
|
+
localProcessor;
|
|
38
|
+
tier2Exporter;
|
|
39
|
+
logger;
|
|
40
|
+
pendingExports = [];
|
|
41
|
+
exportTimer = null;
|
|
42
|
+
droppedQueueFull = false;
|
|
43
|
+
/** Tracks in-flight fire-and-forget flushes so shutdown/forceFlush can await them. */
|
|
44
|
+
inflight = new Set();
|
|
45
|
+
constructor(policyEvaluator, localProcessor, tier2Exporter, traceSanitizer, logger, exportTimeoutMs = 5000) {
|
|
46
|
+
this.exportTimeoutMs = exportTimeoutMs;
|
|
47
|
+
this.sanitizer = traceSanitizer;
|
|
48
|
+
this.localProcessor = localProcessor;
|
|
49
|
+
this.logger = logger;
|
|
50
|
+
this.tier2Exporter = policyEvaluator.isExportEnabled(TelemetryTier.TIER_2_OPERATIONAL)
|
|
51
|
+
? tier2Exporter
|
|
52
|
+
: null;
|
|
53
|
+
if (this.tier2Exporter) {
|
|
54
|
+
this.exportTimer = setInterval(() => this.flushExports("periodic"), EXPORT_INTERVAL_MS);
|
|
55
|
+
this.exportTimer.unref();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
onStart(span, parentContext) {
|
|
59
|
+
this.localProcessor.onStart(span, parentContext);
|
|
60
|
+
}
|
|
61
|
+
onEnd(span) {
|
|
62
|
+
this.localProcessor.onEnd(span);
|
|
63
|
+
if (this.tier2Exporter) {
|
|
64
|
+
const sanitized = this.sanitizer.sanitize(span);
|
|
65
|
+
if (sanitized) {
|
|
66
|
+
if (this.pendingExports.length >= MAX_PENDING_EXPORTS) {
|
|
67
|
+
if (!this.droppedQueueFull) {
|
|
68
|
+
this.droppedQueueFull = true;
|
|
69
|
+
const msg = "Tier 2 pending queue full, dropping new spans until next flush";
|
|
70
|
+
const log = this.logger.error ?? this.logger.warn;
|
|
71
|
+
log({
|
|
72
|
+
component: "SanitizingSpanProcessor",
|
|
73
|
+
maxPendingExports: MAX_PENDING_EXPORTS,
|
|
74
|
+
}, msg);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
this.pendingExports.push(sanitized);
|
|
79
|
+
if (this.pendingExports.length >= BATCH_SIZE) {
|
|
80
|
+
this.flushExports("batch");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** Fire-and-forget batch flush — tracks the in-flight promise so shutdown/forceFlush can await it. */
|
|
87
|
+
flushExports(phase) {
|
|
88
|
+
const p = this.flushExportsAsync(phase).finally(() => this.inflight.delete(p));
|
|
89
|
+
this.inflight.add(p);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Awaitable batch flush for drain paths (shutdown, forceFlush).
|
|
93
|
+
* Always resolves — times out after exportTimeoutMs if the exporter
|
|
94
|
+
* never calls its callback (e.g. broken socket, hung downstream).
|
|
95
|
+
*/
|
|
96
|
+
flushExportsAsync(phase) {
|
|
97
|
+
if (!this.tier2Exporter || this.pendingExports.length === 0) {
|
|
98
|
+
return Promise.resolve();
|
|
99
|
+
}
|
|
100
|
+
if (this.pendingExports.length < MAX_PENDING_EXPORTS) {
|
|
101
|
+
this.droppedQueueFull = false;
|
|
102
|
+
}
|
|
103
|
+
const batch = this.pendingExports.splice(0, BATCH_SIZE);
|
|
104
|
+
return new Promise((resolve) => {
|
|
105
|
+
const timer = setTimeout(() => {
|
|
106
|
+
this.logger.warn({
|
|
107
|
+
component: "SanitizingSpanProcessor",
|
|
108
|
+
batchSize: batch.length,
|
|
109
|
+
exportTimeoutMs: this.exportTimeoutMs,
|
|
110
|
+
phase,
|
|
111
|
+
}, "Tier 2 span export timed out");
|
|
112
|
+
resolve();
|
|
113
|
+
}, this.exportTimeoutMs).unref();
|
|
114
|
+
try {
|
|
115
|
+
this.tier2Exporter.export(batch, (result) => {
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
if (result.code !== ExportResultCode.SUCCESS) {
|
|
118
|
+
this.logger.warn({
|
|
119
|
+
component: "SanitizingSpanProcessor",
|
|
120
|
+
err: result.error,
|
|
121
|
+
batchSize: batch.length,
|
|
122
|
+
}, "Tier 2 span export failed");
|
|
123
|
+
}
|
|
124
|
+
resolve();
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
this.logger.warn({
|
|
130
|
+
component: "SanitizingSpanProcessor",
|
|
131
|
+
err,
|
|
132
|
+
batchSize: batch.length,
|
|
133
|
+
}, "Tier 2 span export threw synchronously");
|
|
134
|
+
resolve();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async shutdown() {
|
|
139
|
+
if (this.exportTimer) {
|
|
140
|
+
clearInterval(this.exportTimer);
|
|
141
|
+
this.exportTimer = null;
|
|
142
|
+
}
|
|
143
|
+
while (this.pendingExports.length > 0) {
|
|
144
|
+
await this.flushExportsAsync("shutdown");
|
|
145
|
+
}
|
|
146
|
+
await Promise.allSettled([...this.inflight]);
|
|
147
|
+
await this.localProcessor.shutdown();
|
|
148
|
+
if (this.tier2Exporter) {
|
|
149
|
+
await this.tier2Exporter.shutdown();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async forceFlush() {
|
|
153
|
+
while (this.pendingExports.length > 0) {
|
|
154
|
+
await this.flushExportsAsync("forceFlush");
|
|
155
|
+
}
|
|
156
|
+
await Promise.allSettled([...this.inflight]);
|
|
157
|
+
await this.localProcessor.forceFlush();
|
|
158
|
+
if (this.tier2Exporter?.forceFlush) {
|
|
159
|
+
await this.tier2Exporter.forceFlush();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=sanitizing-processor.js.map
|