@provide-io/telemetry 0.2.2 → 0.2.4
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 +3 -3
- package/dist/backpressure.d.ts +0 -4
- package/dist/backpressure.d.ts.map +1 -1
- package/dist/backpressure.js +8 -1
- package/dist/classification.d.ts.map +1 -1
- package/dist/classification.js +1 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/consent.d.ts.map +1 -1
- package/dist/consent.js +3 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +1 -0
- package/dist/health.d.ts +4 -0
- package/dist/health.d.ts.map +1 -1
- package/dist/health.js +16 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/pii.d.ts +19 -0
- package/dist/pii.d.ts.map +1 -1
- package/dist/pii.js +50 -8
- package/dist/react.d.ts +9 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +9 -0
- package/dist/receipts.d.ts +2 -0
- package/dist/receipts.d.ts.map +1 -1
- package/dist/receipts.js +19 -5
- package/dist/resilience.d.ts.map +1 -1
- package/dist/resilience.js +2 -1
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +55 -9
- package/dist/sampling.d.ts.map +1 -1
- package/dist/sampling.js +12 -5
- package/dist/tracing.d.ts.map +1 -1
- package/dist/tracing.js +1 -0
- package/package.json +8 -7
- package/src/backpressure.ts +6 -1
- package/src/classification.ts +1 -0
- package/src/config.ts +5 -0
- package/src/consent.ts +3 -0
- package/src/context.ts +1 -0
- package/src/health.ts +14 -0
- package/src/index.ts +4 -1
- package/src/pii.ts +58 -6
- package/src/react.ts +9 -0
- package/src/receipts.ts +20 -5
- package/src/resilience.ts +2 -1
- package/src/runtime.ts +57 -10
- package/src/sampling.ts +14 -5
- package/src/tracing.ts +1 -0
package/src/runtime.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
configFromEnv,
|
|
13
13
|
setupTelemetry,
|
|
14
14
|
} from './config';
|
|
15
|
+
import { ConfigurationError } from './exceptions';
|
|
15
16
|
|
|
16
17
|
/** Minimal interface for providers that can be flushed and shut down cleanly. */
|
|
17
18
|
export interface ShutdownableProvider {
|
|
@@ -47,6 +48,7 @@ export function _areProvidersRegistered(): boolean {
|
|
|
47
48
|
|
|
48
49
|
function deepFreeze<T extends object>(obj: T): Readonly<T> {
|
|
49
50
|
for (const val of Object.values(obj)) {
|
|
51
|
+
// Stryker disable next-line ConditionalExpression,EqualityOperator,LogicalOperator: frozen-object guard — all sub-conditions required but only observable with deeply nested mutable objects
|
|
50
52
|
if (typeof val === 'object' && val !== null && !Object.isFrozen(val)) {
|
|
51
53
|
deepFreeze(val as object);
|
|
52
54
|
}
|
|
@@ -62,6 +64,7 @@ export function getRuntimeConfig(): Readonly<TelemetryConfig> {
|
|
|
62
64
|
|
|
63
65
|
/** Merge hot-reloadable overrides into the active config and re-apply policies. */
|
|
64
66
|
export function updateRuntimeConfig(overrides: RuntimeOverrides): void {
|
|
67
|
+
validateRuntimeOverrides(overrides);
|
|
65
68
|
const base = _activeConfig ?? configFromEnv();
|
|
66
69
|
const merged: TelemetryConfig = { ...base };
|
|
67
70
|
for (const [key, value] of Object.entries(overrides)) {
|
|
@@ -73,6 +76,53 @@ export function updateRuntimeConfig(overrides: RuntimeOverrides): void {
|
|
|
73
76
|
setupTelemetry(_activeConfig);
|
|
74
77
|
}
|
|
75
78
|
|
|
79
|
+
function validateRate(name: string, value: number | undefined): void {
|
|
80
|
+
if (value === undefined) return;
|
|
81
|
+
if (!Number.isFinite(value) || value < 0 || value > 1) {
|
|
82
|
+
// Stryker disable next-line StringLiteral: error message content
|
|
83
|
+
throw new ConfigurationError(`${name} must be in [0, 1], got ${String(value)}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function validateNonNegativeInteger(name: string, value: number | undefined): void {
|
|
88
|
+
if (value === undefined) return;
|
|
89
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
90
|
+
// Stryker disable next-line StringLiteral: error message content
|
|
91
|
+
throw new ConfigurationError(`${name} must be a non-negative integer, got ${String(value)}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function validateNonNegativeNumber(name: string, value: number | undefined): void {
|
|
96
|
+
if (value === undefined) return;
|
|
97
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
98
|
+
// Stryker disable next-line StringLiteral: error message content
|
|
99
|
+
throw new ConfigurationError(`${name} must be >= 0, got ${String(value)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* Stryker disable StringLiteral: field names in validation calls are only used in error messages — mutating them does not change validation behavior */
|
|
104
|
+
function validateRuntimeOverrides(overrides: RuntimeOverrides): void {
|
|
105
|
+
validateRate('samplingLogsRate', overrides.samplingLogsRate);
|
|
106
|
+
validateRate('samplingTracesRate', overrides.samplingTracesRate);
|
|
107
|
+
validateRate('samplingMetricsRate', overrides.samplingMetricsRate);
|
|
108
|
+
validateNonNegativeInteger('backpressureLogsMaxsize', overrides.backpressureLogsMaxsize);
|
|
109
|
+
validateNonNegativeInteger('backpressureTracesMaxsize', overrides.backpressureTracesMaxsize);
|
|
110
|
+
validateNonNegativeInteger('backpressureMetricsMaxsize', overrides.backpressureMetricsMaxsize);
|
|
111
|
+
validateNonNegativeInteger('exporterLogsRetries', overrides.exporterLogsRetries);
|
|
112
|
+
validateNonNegativeInteger('exporterTracesRetries', overrides.exporterTracesRetries);
|
|
113
|
+
validateNonNegativeInteger('exporterMetricsRetries', overrides.exporterMetricsRetries);
|
|
114
|
+
validateNonNegativeNumber('exporterLogsBackoffMs', overrides.exporterLogsBackoffMs);
|
|
115
|
+
validateNonNegativeNumber('exporterTracesBackoffMs', overrides.exporterTracesBackoffMs);
|
|
116
|
+
validateNonNegativeNumber('exporterMetricsBackoffMs', overrides.exporterMetricsBackoffMs);
|
|
117
|
+
validateNonNegativeNumber('exporterLogsTimeoutMs', overrides.exporterLogsTimeoutMs);
|
|
118
|
+
validateNonNegativeNumber('exporterTracesTimeoutMs', overrides.exporterTracesTimeoutMs);
|
|
119
|
+
validateNonNegativeNumber('exporterMetricsTimeoutMs', overrides.exporterMetricsTimeoutMs);
|
|
120
|
+
validateNonNegativeInteger('securityMaxAttrValueLength', overrides.securityMaxAttrValueLength);
|
|
121
|
+
validateNonNegativeInteger('securityMaxAttrCount', overrides.securityMaxAttrCount);
|
|
122
|
+
validateNonNegativeInteger('piiMaxDepth', overrides.piiMaxDepth);
|
|
123
|
+
}
|
|
124
|
+
/* Stryker restore StringLiteral */
|
|
125
|
+
|
|
76
126
|
const _COLD_FIELDS: (keyof TelemetryConfig)[] = [
|
|
77
127
|
'serviceName',
|
|
78
128
|
'environment',
|
|
@@ -91,11 +141,13 @@ export function reloadRuntimeFromEnv(): void {
|
|
|
91
141
|
(k) => JSON.stringify(current[k]) !== JSON.stringify(fresh[k]),
|
|
92
142
|
);
|
|
93
143
|
if (drifted.length > 0) {
|
|
144
|
+
/* Stryker disable StringLiteral: warning message content */
|
|
94
145
|
console.warn(
|
|
95
146
|
'[provide-telemetry] runtime.cold_field_drift:',
|
|
96
147
|
drifted.join(', '),
|
|
97
148
|
'— restart required to apply',
|
|
98
149
|
);
|
|
150
|
+
/* Stryker restore StringLiteral */
|
|
99
151
|
}
|
|
100
152
|
}
|
|
101
153
|
// Apply only hot fields via overrides
|
|
@@ -123,6 +175,7 @@ export function reloadRuntimeFromEnv(): void {
|
|
|
123
175
|
sloEnableRedMetrics: fresh.sloEnableRedMetrics,
|
|
124
176
|
sloEnableUseMetrics: fresh.sloEnableUseMetrics,
|
|
125
177
|
piiMaxDepth: fresh.piiMaxDepth,
|
|
178
|
+
strictSchema: fresh.strictSchema,
|
|
126
179
|
};
|
|
127
180
|
updateRuntimeConfig(overrides);
|
|
128
181
|
}
|
|
@@ -135,8 +188,8 @@ const PROVIDER_CHANGING_FIELDS: (keyof TelemetryConfig)[] = [
|
|
|
135
188
|
|
|
136
189
|
/**
|
|
137
190
|
* Apply config changes.
|
|
138
|
-
* If provider-changing fields differ and providers are already registered,
|
|
139
|
-
*
|
|
191
|
+
* If provider-changing fields differ and providers are already registered, fail fast:
|
|
192
|
+
* provider replacement requires explicit process restart to avoid async export loss.
|
|
140
193
|
* Otherwise delegates to setupTelemetry.
|
|
141
194
|
*/
|
|
142
195
|
export function reconfigureTelemetry(config: Partial<TelemetryConfig>): void {
|
|
@@ -148,15 +201,9 @@ export function reconfigureTelemetry(config: Partial<TelemetryConfig>): void {
|
|
|
148
201
|
(k) => JSON.stringify(current[k]) !== JSON.stringify(proposed[k]),
|
|
149
202
|
);
|
|
150
203
|
if (changed) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// Stryker disable LogicalOperator: ?? vs && is equivalent here — forceFlush/shutdown return Promise (truthy) so && still resolves; when undefined, Promise.allSettled wraps both in Promise.resolve
|
|
154
|
-
void Promise.allSettled(providers.map((p) => p.forceFlush?.() ?? Promise.resolve())).then(
|
|
155
|
-
() => Promise.allSettled(providers.map((p) => p.shutdown?.() ?? Promise.resolve())),
|
|
204
|
+
throw new ConfigurationError(
|
|
205
|
+
'provider-changing reconfiguration is unsupported after OpenTelemetry providers are installed; restart the process and call setupTelemetry() with the new config',
|
|
156
206
|
);
|
|
157
|
-
// Stryker restore LogicalOperator
|
|
158
|
-
_providersRegistered = false;
|
|
159
|
-
_registeredProviders = [];
|
|
160
207
|
}
|
|
161
208
|
}
|
|
162
209
|
|
package/src/sampling.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { ConfigurationError } from './exceptions';
|
|
9
|
+
import { _droppedField, _emittedField, _incrementHealth } from './health';
|
|
9
10
|
|
|
10
11
|
export interface SamplingPolicy {
|
|
11
12
|
defaultRate: number;
|
|
@@ -55,12 +56,20 @@ export function shouldSample(signal: string, key?: string): boolean {
|
|
|
55
56
|
const lookupKey = key ?? signal;
|
|
56
57
|
const rate = overrides && lookupKey in overrides ? overrides[lookupKey] : _policy.defaultRate;
|
|
57
58
|
const clamped = _clamp(rate);
|
|
58
|
-
|
|
59
|
-
if (clamped <= 0)
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
/* Stryker disable ConditionalExpression,EqualityOperator,BlockStatement: boundary not observable (Math.random [0,1)); health counter updates tested but perTest coverage misattributes */
|
|
60
|
+
if (clamped <= 0) {
|
|
61
|
+
_incrementHealth(_droppedField(signal));
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
if (clamped >= 1) {
|
|
65
|
+
_incrementHealth(_emittedField(signal));
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
/* Stryker restore ConditionalExpression,EqualityOperator,BlockStatement */
|
|
62
69
|
// Stryker disable next-line EqualityOperator: Math.random() is in [0,1) so < 1.0 and <= 1.0 are equivalent (random never equals 1.0)
|
|
63
|
-
|
|
70
|
+
const sampled = Math.random() < clamped;
|
|
71
|
+
_incrementHealth(sampled ? _emittedField(signal) : _droppedField(signal));
|
|
72
|
+
return sampled;
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
export function _resetSamplingForTests(): void {
|
package/src/tracing.ts
CHANGED
|
@@ -88,6 +88,7 @@ const NOOP_TRACE_ID = '00000000000000000000000000000000';
|
|
|
88
88
|
* Used to decide whether to inject synthetic random IDs.
|
|
89
89
|
*/
|
|
90
90
|
function _isNoopSpan(span: Span): boolean {
|
|
91
|
+
// Stryker disable next-line ConditionalExpression: without a registered OTel SDK all spans are noop — mutating to true is equivalent
|
|
91
92
|
return span.spanContext().traceId === NOOP_TRACE_ID;
|
|
92
93
|
}
|
|
93
94
|
|