@armoriq/sdk-dev 0.8.5 → 0.8.7
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 +2 -30
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/client.d.ts +0 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +15 -39
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -5
- package/dist/index.js.map +1 -1
- package/dist/observability/index.d.ts +5 -3
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js.map +1 -1
- package/dist/observability/otel-runtime.d.ts +54 -4
- package/dist/observability/otel-runtime.d.ts.map +1 -1
- package/dist/observability/otel-runtime.js +244 -52
- package/dist/observability/otel-runtime.js.map +1 -1
- package/dist/observability/otel-session.d.ts +23 -4
- package/dist/observability/otel-session.d.ts.map +1 -1
- package/dist/observability/otel-session.js +51 -24
- package/dist/observability/otel-session.js.map +1 -1
- package/dist/observability/otel-shutdown.d.ts +1 -1
- package/dist/observability/otel-shutdown.d.ts.map +1 -1
- package/dist/observability/otel-shutdown.js +1 -13
- package/dist/observability/otel-shutdown.js.map +1 -1
- package/dist/observability/otel-span-spool.d.ts +55 -0
- package/dist/observability/otel-span-spool.d.ts.map +1 -0
- package/dist/observability/otel-span-spool.js +94 -0
- package/dist/observability/otel-span-spool.js.map +1 -0
- package/dist/observability/otel-spool-export.d.ts +64 -0
- package/dist/observability/otel-spool-export.d.ts.map +1 -0
- package/dist/observability/otel-spool-export.js +279 -0
- package/dist/observability/otel-spool-export.js.map +1 -0
- package/dist/observability/otlp-http-transport.d.ts +20 -0
- package/dist/observability/otlp-http-transport.d.ts.map +1 -0
- package/dist/observability/otlp-http-transport.js +141 -0
- package/dist/observability/otlp-http-transport.js.map +1 -0
- package/dist/observability/policy-lease.d.ts +25 -0
- package/dist/observability/policy-lease.d.ts.map +1 -1
- package/dist/observability/policy-lease.js +49 -0
- package/dist/observability/policy-lease.js.map +1 -1
- package/dist/session.d.ts +4 -3
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +5 -4
- package/dist/session.js.map +1 -1
- package/dist/token_usage.d.ts +10 -33
- package/dist/token_usage.d.ts.map +1 -1
- package/dist/token_usage.js +38 -85
- package/dist/token_usage.js.map +1 -1
- package/package.json +10 -10
- package/dist/session_transcripts.d.ts +0 -8
- package/dist/session_transcripts.d.ts.map +0 -1
- package/dist/session_transcripts.js +0 -72
- package/dist/session_transcripts.js.map +0 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/** SDK-owned OpenTelemetry runtime. It never registers a global provider. */
|
|
2
|
-
import type
|
|
2
|
+
import { type Span, type SpanOptions, type Tracer } from '@opentelemetry/api';
|
|
3
3
|
import { type ObservabilityOptions, type ResolvedOtelConfig } from './otel-config';
|
|
4
|
-
import { type PolicyLease } from './policy-lease';
|
|
4
|
+
import { type PolicyLease, type PolicyLeaseStore } from './policy-lease';
|
|
5
5
|
import { type ContentCaptureAuthority } from './content-capture';
|
|
6
|
+
import { type SpanSink } from './otel-span-spool';
|
|
7
|
+
import { type SpooledExportResult } from './otel-spool-export';
|
|
6
8
|
export type TelemetryAttributeValue = string | number | boolean | string[];
|
|
7
9
|
export type TelemetryAttributes = Readonly<Record<string, unknown>>;
|
|
8
10
|
export interface AttributeContext {
|
|
@@ -18,7 +20,19 @@ export interface TelemetryRuntimeOptions {
|
|
|
18
20
|
options?: ObservabilityOptions;
|
|
19
21
|
/** Test/host hook; production defaults to the policy-lease HTTP endpoint. */
|
|
20
22
|
leaseFetcher?: (signal?: AbortSignal) => Promise<PolicyLease>;
|
|
23
|
+
leaseStore?: PolicyLeaseStore;
|
|
24
|
+
/** Takes the place of the OTLP request for the `armoriq` exporter. */
|
|
25
|
+
spanSink?: SpanSink;
|
|
21
26
|
}
|
|
27
|
+
export interface SpanIds {
|
|
28
|
+
traceId: string;
|
|
29
|
+
spanId: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Only a tracer from the runtime's own provider honors the ids; a
|
|
33
|
+
* caller-owned provider assigns its own.
|
|
34
|
+
*/
|
|
35
|
+
export declare function startRootSpanWithIds(runtime: ArmorIQTelemetryRuntime, tracer: Tracer, name: string, options: SpanOptions, ids: SpanIds): Span;
|
|
22
36
|
/**
|
|
23
37
|
* Manages one private exporter, or borrows a caller-owned provider. A runtime
|
|
24
38
|
* is request/session scoped and `close()` is idempotent and fail-open.
|
|
@@ -29,21 +43,38 @@ export declare class ArmorIQTelemetryRuntime {
|
|
|
29
43
|
private readonly apiKey;
|
|
30
44
|
private readonly sdkVersion;
|
|
31
45
|
private readonly leaseFetcher?;
|
|
46
|
+
private readonly leaseStore?;
|
|
47
|
+
private readonly spanSink?;
|
|
48
|
+
private readonly leaseBinding;
|
|
49
|
+
private readonly spoolKey;
|
|
50
|
+
private readonly storeAbort;
|
|
32
51
|
private lease;
|
|
33
52
|
private refreshing;
|
|
34
53
|
private firstExternalAuthorityAwaited;
|
|
35
54
|
private externalAuthorityUnavailable;
|
|
36
55
|
private nextRefreshAt;
|
|
37
56
|
private provider;
|
|
57
|
+
private exporter;
|
|
38
58
|
private policyLeaseAbort;
|
|
39
|
-
private
|
|
59
|
+
private transport;
|
|
60
|
+
private readonly spoolCalls;
|
|
61
|
+
private readonly spoolSlots;
|
|
40
62
|
private closed;
|
|
41
63
|
constructor(options: TelemetryRuntimeOptions);
|
|
64
|
+
currentOrFreshSnapshot(): Promise<PolicyLease>;
|
|
42
65
|
policySnapshot(options?: {
|
|
43
66
|
awaitFresh?: boolean;
|
|
44
67
|
}): Promise<PolicyLease>;
|
|
68
|
+
/**
|
|
69
|
+
* Resolves at once while a current lease is held. Otherwise it adopts a
|
|
70
|
+
* current lease from the store, or waits for a lease request: the real
|
|
71
|
+
* lease, or the fallback after a failure or the request's 5 s abort.
|
|
72
|
+
*/
|
|
73
|
+
refreshPolicy(): Promise<void>;
|
|
74
|
+
private adoptStoredLease;
|
|
45
75
|
/** Start at most one control-plane request; request callers never await it. */
|
|
46
76
|
private refreshInBackground;
|
|
77
|
+
private startRefresh;
|
|
47
78
|
private applyCeiling;
|
|
48
79
|
/**
|
|
49
80
|
* The ceiling-applied lease this runtime already knows about, computed
|
|
@@ -78,9 +109,14 @@ export declare class ArmorIQTelemetryRuntime {
|
|
|
78
109
|
* time for a disallowed capture mode.
|
|
79
110
|
*/
|
|
80
111
|
reapplyContentCeiling(snapshot: PolicyLease, attributes: TelemetryAttributes): Record<string, TelemetryAttributeValue>;
|
|
112
|
+
private storeLease;
|
|
81
113
|
private fetchPolicyLease;
|
|
82
114
|
tracer(snapshot: PolicyLease): Tracer | null;
|
|
83
115
|
private privateProvider;
|
|
116
|
+
private resource;
|
|
117
|
+
private pipelineExporter;
|
|
118
|
+
private firstPartyTracesUrl;
|
|
119
|
+
private ownedTransport;
|
|
84
120
|
/**
|
|
85
121
|
* Construct the OTLP delegate from explicit values. The convenience
|
|
86
122
|
* exporter reads process.env while merging defaults, which could leak a
|
|
@@ -98,6 +134,20 @@ export declare class ArmorIQTelemetryRuntime {
|
|
|
98
134
|
/** Stable, externally safe root span names prevent identifier leakage. */
|
|
99
135
|
externalTraceName(value: string): string;
|
|
100
136
|
forceFlush(): Promise<void>;
|
|
101
|
-
|
|
137
|
+
/** Process-exit drain: a bounded flush that then fails what is still in flight; the runtime stays open. */
|
|
138
|
+
drainForExit(): Promise<void>;
|
|
139
|
+
private drainExports;
|
|
140
|
+
/** The `binding` of every batch this runtime's `spanSink` receives. */
|
|
141
|
+
get spoolBinding(): string;
|
|
142
|
+
/**
|
|
143
|
+
* Ships batches this runtime's `spanSink` wrote, one OTLP request each,
|
|
144
|
+
* under its current authoritative lease. Resolves to one result per entry of
|
|
145
|
+
* `batches`, in order.
|
|
146
|
+
*/
|
|
147
|
+
exportSpooled(batches: readonly unknown[]): Promise<SpooledExportResult[]>;
|
|
148
|
+
private openSpoolCall;
|
|
149
|
+
private shipSpooled;
|
|
150
|
+
private shippedAttributes;
|
|
151
|
+
close(deadlineMs?: number): Promise<void>;
|
|
102
152
|
}
|
|
103
153
|
//# sourceMappingURL=otel-runtime.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otel-runtime.d.ts","sourceRoot":"","sources":["../../src/observability/otel-runtime.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAE7E,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"otel-runtime.d.ts","sourceRoot":"","sources":["../../src/observability/otel-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
/** SDK-owned OpenTelemetry runtime. It never registers a global provider. */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.ArmorIQTelemetryRuntime = void 0;
|
|
5
|
+
exports.startRootSpanWithIds = startRootSpanWithIds;
|
|
6
|
+
const api_1 = require("@opentelemetry/api");
|
|
5
7
|
const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
|
|
6
8
|
const sdk_trace_node_1 = require("@opentelemetry/sdk-trace-node");
|
|
7
9
|
const resources_1 = require("@opentelemetry/resources");
|
|
@@ -13,6 +15,9 @@ const policy_lease_1 = require("./policy-lease");
|
|
|
13
15
|
const content_capture_1 = require("./content-capture");
|
|
14
16
|
const otel_shutdown_1 = require("./otel-shutdown");
|
|
15
17
|
const otel_export_ceiling_1 = require("./otel-export-ceiling");
|
|
18
|
+
const otlp_http_transport_1 = require("./otlp-http-transport");
|
|
19
|
+
const otel_span_spool_1 = require("./otel-span-spool");
|
|
20
|
+
const otel_spool_export_1 = require("./otel-spool-export");
|
|
16
21
|
const SENSITIVE_KEY = /(?:api[_-]?key|auth(?:orization)?|bearer|cookie|credential|password|passphrase|secret|private[_-]?key|ssn|card(?:_number)?)/i;
|
|
17
22
|
const SECRET_TOKEN_KEY = /(?:^|[_.-])token(?:$|[_.-])/i;
|
|
18
23
|
const USAGE_TOKEN_KEY = /^gen_ai\.usage\.(?:input|output|cache_read|cache_write)_tokens$/;
|
|
@@ -37,7 +42,7 @@ const MAX_FINISH_REASONS = 16;
|
|
|
37
42
|
const MAX_FINISH_REASON_CANDIDATES = 64;
|
|
38
43
|
const SENSITIVE_VALUE = /(?:\beyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b|\b(?:ak|sk|pk|rk|ghp|github_pat)_[A-Za-z0-9_-]{12,}\b|-----BEGIN [A-Z0-9 ]+-----)/i;
|
|
39
44
|
const EMAIL = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i;
|
|
40
|
-
const CONTENT_KEY = /^(?:armoriq\.(?:content
|
|
45
|
+
const CONTENT_KEY = /^(?:armoriq\.(?:content\..*|observability\.error\.summary)|gen_ai\.(?:prompt|completion|input\.messages|output\.messages|tool\.(?:args|arguments|result|call\.(?:arguments|result))))$/;
|
|
41
46
|
const EXTERNAL_METADATA_ATTRIBUTES = new Set([
|
|
42
47
|
'gen_ai.request.model',
|
|
43
48
|
'gen_ai.response.model',
|
|
@@ -118,7 +123,46 @@ const EXTERNAL_CONTENT_ATTRIBUTES = new Set([
|
|
|
118
123
|
'gen_ai.tool.call.arguments', 'gen_ai.tool.call.result',
|
|
119
124
|
]);
|
|
120
125
|
const CLOSE_DEADLINE_MS = 1_500;
|
|
126
|
+
const EXPORT_CONCURRENCY_LIMIT = 30;
|
|
127
|
+
const MAX_TIMER_MS = 2_147_483_647;
|
|
121
128
|
const FIRST_EXTERNAL_AUTHORITY_WAIT_MS = 1_400;
|
|
129
|
+
const SPOOL_EXPORT_CONCURRENCY = 8;
|
|
130
|
+
const TRACER_NAME = 'armoriq-sdk';
|
|
131
|
+
class PresetIdGenerator {
|
|
132
|
+
random = new sdk_trace_base_1.RandomIdGenerator();
|
|
133
|
+
preset = null;
|
|
134
|
+
generateTraceId() {
|
|
135
|
+
return this.preset?.traceId ?? this.random.generateTraceId();
|
|
136
|
+
}
|
|
137
|
+
generateSpanId() {
|
|
138
|
+
return this.preset?.spanId ?? this.random.generateSpanId();
|
|
139
|
+
}
|
|
140
|
+
withIds(ids, start) {
|
|
141
|
+
this.preset = ids;
|
|
142
|
+
try {
|
|
143
|
+
return start();
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
this.preset = null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const presetIdGenerators = new WeakMap();
|
|
151
|
+
function presetIdGenerator(runtime) {
|
|
152
|
+
const existing = presetIdGenerators.get(runtime);
|
|
153
|
+
if (existing)
|
|
154
|
+
return existing;
|
|
155
|
+
const created = new PresetIdGenerator();
|
|
156
|
+
presetIdGenerators.set(runtime, created);
|
|
157
|
+
return created;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Only a tracer from the runtime's own provider honors the ids; a
|
|
161
|
+
* caller-owned provider assigns its own.
|
|
162
|
+
*/
|
|
163
|
+
function startRootSpanWithIds(runtime, tracer, name, options, ids) {
|
|
164
|
+
return presetIdGenerator(runtime).withIds(ids, () => tracer.startSpan(name, options, api_1.ROOT_CONTEXT));
|
|
165
|
+
}
|
|
122
166
|
function bounded(promise, timeoutMillis = CLOSE_DEADLINE_MS) {
|
|
123
167
|
return new Promise((resolve, reject) => {
|
|
124
168
|
const timer = setTimeout(() => resolve(undefined), timeoutMillis);
|
|
@@ -131,6 +175,9 @@ function bounded(promise, timeoutMillis = CLOSE_DEADLINE_MS) {
|
|
|
131
175
|
});
|
|
132
176
|
});
|
|
133
177
|
}
|
|
178
|
+
function validDeadline(value) {
|
|
179
|
+
return value > 0 ? Math.min(value, MAX_TIMER_MS) : CLOSE_DEADLINE_MS;
|
|
180
|
+
}
|
|
134
181
|
function cleanString(value, length = 256) {
|
|
135
182
|
if (SENSITIVE_VALUE.test(value))
|
|
136
183
|
return '[REDACTED:CREDENTIAL]';
|
|
@@ -180,9 +227,27 @@ function safeValue(key, value, external) {
|
|
|
180
227
|
function validExternalId(value) {
|
|
181
228
|
return /^[A-Za-z0-9][A-Za-z0-9._:/@+-]{0,127}$/.test(value) && (0, otel_config_1.sanitizeExternalTelemetryValue)(value, 128) !== undefined;
|
|
182
229
|
}
|
|
230
|
+
function sessionIdentity(context, external) {
|
|
231
|
+
const identity = {};
|
|
232
|
+
if (external) {
|
|
233
|
+
const sessionId = (0, otel_config_1.sanitizeExternalTelemetryValue)(context.sessionId, 128);
|
|
234
|
+
if (sessionId)
|
|
235
|
+
identity['session.id'] = sessionId;
|
|
236
|
+
if (context.userId && validExternalId(context.userId))
|
|
237
|
+
identity['user.id'] = context.userId;
|
|
238
|
+
return identity;
|
|
239
|
+
}
|
|
240
|
+
const sessionId = safeValue('armoriq.session_id', context.sessionId, false);
|
|
241
|
+
if (sessionId !== undefined)
|
|
242
|
+
identity['armoriq.session_id'] = sessionId;
|
|
243
|
+
return identity;
|
|
244
|
+
}
|
|
183
245
|
function hasOwn(value, key) {
|
|
184
246
|
return Object.prototype.hasOwnProperty.call(value, key);
|
|
185
247
|
}
|
|
248
|
+
function holdsCurrentAuthority(lease) {
|
|
249
|
+
return Boolean(lease?.authoritative && (0, policy_lease_1.isLeaseCurrent)(lease));
|
|
250
|
+
}
|
|
186
251
|
/**
|
|
187
252
|
* Manages one private exporter, or borrows a caller-owned provider. A runtime
|
|
188
253
|
* is request/session scoped and `close()` is idempotent and fail-open.
|
|
@@ -193,14 +258,22 @@ class ArmorIQTelemetryRuntime {
|
|
|
193
258
|
apiKey;
|
|
194
259
|
sdkVersion;
|
|
195
260
|
leaseFetcher;
|
|
261
|
+
leaseStore;
|
|
262
|
+
spanSink;
|
|
263
|
+
leaseBinding;
|
|
264
|
+
spoolKey;
|
|
265
|
+
storeAbort = new AbortController();
|
|
196
266
|
lease = null;
|
|
197
267
|
refreshing = null;
|
|
198
268
|
firstExternalAuthorityAwaited = false;
|
|
199
269
|
externalAuthorityUnavailable = false;
|
|
200
270
|
nextRefreshAt = 0;
|
|
201
271
|
provider = null;
|
|
272
|
+
exporter = null;
|
|
202
273
|
policyLeaseAbort = null;
|
|
203
|
-
|
|
274
|
+
transport = null;
|
|
275
|
+
spoolCalls = new Set();
|
|
276
|
+
spoolSlots = new otel_spool_export_1.ExportSlots(SPOOL_EXPORT_CONCURRENCY);
|
|
204
277
|
closed = false;
|
|
205
278
|
constructor(options) {
|
|
206
279
|
this.config = (0, otel_config_1.resolveOtelConfig)(options.options);
|
|
@@ -208,17 +281,24 @@ class ArmorIQTelemetryRuntime {
|
|
|
208
281
|
this.apiKey = options.apiKey;
|
|
209
282
|
this.sdkVersion = options.sdkVersion;
|
|
210
283
|
this.leaseFetcher = options.leaseFetcher;
|
|
284
|
+
this.leaseStore = options.leaseStore;
|
|
285
|
+
this.spanSink = options.spanSink;
|
|
286
|
+
this.leaseBinding = (0, policy_lease_1.policyLeaseBinding)(this.backendEndpoint, options.apiKey);
|
|
287
|
+
this.spoolKey = { apiKey: options.apiKey, binding: this.leaseBinding };
|
|
211
288
|
if (this.config.enabled && this.config.exporter !== 'provider') {
|
|
212
289
|
(0, otel_shutdown_1.registerOwnedTelemetryRuntime)(this);
|
|
213
290
|
}
|
|
214
291
|
}
|
|
292
|
+
async currentOrFreshSnapshot() {
|
|
293
|
+
return this.policySnapshot({ awaitFresh: !holdsCurrentAuthority(this.lease) });
|
|
294
|
+
}
|
|
215
295
|
async policySnapshot(options = {}) {
|
|
216
296
|
if (!this.config.enabled || this.closed)
|
|
217
297
|
return Object.freeze({ ...(0, policy_lease_1.fallbackPolicyLease)(), captureMode: 'off' });
|
|
218
298
|
if (this.externalAuthorityUnavailable && this.config.exporter === 'external') {
|
|
219
299
|
return Object.freeze({ ...(0, policy_lease_1.fallbackPolicyLease)(), captureMode: 'off' });
|
|
220
300
|
}
|
|
221
|
-
if (!options.awaitFresh &&
|
|
301
|
+
if (!options.awaitFresh && holdsCurrentAuthority(this.lease))
|
|
222
302
|
return this.applyCeiling(this.lease);
|
|
223
303
|
const refresh = this.refreshInBackground(options.awaitFresh === true);
|
|
224
304
|
const mustAwaitExternalAuthority = this.config.exporter === 'external' && !this.firstExternalAuthorityAwaited;
|
|
@@ -242,16 +322,40 @@ class ArmorIQTelemetryRuntime {
|
|
|
242
322
|
}
|
|
243
323
|
return this.applyCeiling(this.lease && (0, policy_lease_1.isLeaseCurrent)(this.lease) ? this.lease : (0, policy_lease_1.fallbackPolicyLease)());
|
|
244
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* Resolves at once while a current lease is held. Otherwise it adopts a
|
|
327
|
+
* current lease from the store, or waits for a lease request: the real
|
|
328
|
+
* lease, or the fallback after a failure or the request's 5 s abort.
|
|
329
|
+
*/
|
|
330
|
+
async refreshPolicy() {
|
|
331
|
+
if (!this.config.enabled || this.closed || holdsCurrentAuthority(this.lease))
|
|
332
|
+
return;
|
|
333
|
+
await this.adoptStoredLease();
|
|
334
|
+
if (this.closed || holdsCurrentAuthority(this.lease))
|
|
335
|
+
return;
|
|
336
|
+
await (this.refreshing ?? this.startRefresh());
|
|
337
|
+
}
|
|
338
|
+
async adoptStoredLease() {
|
|
339
|
+
if (!this.leaseStore)
|
|
340
|
+
return;
|
|
341
|
+
const stored = await (0, policy_lease_1.readStoredPolicyLease)(this.leaseStore, this.leaseBinding, this.storeAbort.signal);
|
|
342
|
+
if (stored && !holdsCurrentAuthority(this.lease))
|
|
343
|
+
this.lease = stored;
|
|
344
|
+
}
|
|
245
345
|
/** Start at most one control-plane request; request callers never await it. */
|
|
246
346
|
refreshInBackground(force = false) {
|
|
247
347
|
if (this.refreshing)
|
|
248
348
|
return this.refreshing;
|
|
249
349
|
if (!force && Date.now() < this.nextRefreshAt)
|
|
250
350
|
return null;
|
|
351
|
+
return this.startRefresh();
|
|
352
|
+
}
|
|
353
|
+
startRefresh() {
|
|
251
354
|
this.nextRefreshAt = Date.now() + 1_000;
|
|
252
355
|
this.refreshing = this.fetchPolicyLease()
|
|
253
356
|
.then((lease) => {
|
|
254
357
|
this.lease = lease;
|
|
358
|
+
this.storeLease(lease);
|
|
255
359
|
return lease;
|
|
256
360
|
})
|
|
257
361
|
.finally(() => {
|
|
@@ -420,6 +524,11 @@ class ArmorIQTelemetryRuntime {
|
|
|
420
524
|
next['armoriq.observability.policy.revision'] = snapshot.revision;
|
|
421
525
|
return next;
|
|
422
526
|
}
|
|
527
|
+
storeLease(lease) {
|
|
528
|
+
if (!lease.authoritative || !this.leaseStore)
|
|
529
|
+
return;
|
|
530
|
+
(0, policy_lease_1.writeStoredPolicyLease)(this.leaseStore, lease, this.leaseBinding, this.storeAbort.signal);
|
|
531
|
+
}
|
|
423
532
|
async fetchPolicyLease() {
|
|
424
533
|
const controller = new AbortController();
|
|
425
534
|
this.policyLeaseAbort = controller;
|
|
@@ -450,9 +559,9 @@ class ArmorIQTelemetryRuntime {
|
|
|
450
559
|
return null;
|
|
451
560
|
try {
|
|
452
561
|
if (this.config.exporter === 'provider') {
|
|
453
|
-
return this.config.tracerProvider?.getTracer(
|
|
562
|
+
return this.config.tracerProvider?.getTracer(TRACER_NAME, this.sdkVersion) ?? null;
|
|
454
563
|
}
|
|
455
|
-
return this.privateProvider()?.getTracer(
|
|
564
|
+
return this.privateProvider()?.getTracer(TRACER_NAME, this.sdkVersion) ?? null;
|
|
456
565
|
}
|
|
457
566
|
catch {
|
|
458
567
|
return null;
|
|
@@ -461,18 +570,14 @@ class ArmorIQTelemetryRuntime {
|
|
|
461
570
|
privateProvider() {
|
|
462
571
|
if (this.provider || this.closed || !this.config.enabled)
|
|
463
572
|
return this.provider;
|
|
464
|
-
const isExternal = this.config.exporter === 'external';
|
|
465
|
-
const url = isExternal ? this.config.endpoint : `${this.backendEndpoint}/v1/traces`;
|
|
466
|
-
if (!url)
|
|
467
|
-
return null;
|
|
468
573
|
try {
|
|
469
|
-
const exporter = this.
|
|
574
|
+
const exporter = this.pipelineExporter();
|
|
575
|
+
if (!exporter)
|
|
576
|
+
return null;
|
|
577
|
+
this.exporter = exporter;
|
|
470
578
|
this.provider = new sdk_trace_node_1.NodeTracerProvider({
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
'service.version': this.sdkVersion,
|
|
474
|
-
...this.config.resourceAttributes,
|
|
475
|
-
}),
|
|
579
|
+
idGenerator: presetIdGenerator(this),
|
|
580
|
+
resource: this.resource(),
|
|
476
581
|
// A span queued under a permissive lease may still be sitting in
|
|
477
582
|
// BatchSpanProcessor's in-memory buffer when the lease becomes
|
|
478
583
|
// stricter (or is revoked). Wrap both the exporter -- the true
|
|
@@ -483,7 +588,7 @@ class ArmorIQTelemetryRuntime {
|
|
|
483
588
|
// not whatever was authoritative at creation time, governs what
|
|
484
589
|
// actually ships.
|
|
485
590
|
spanProcessors: [
|
|
486
|
-
new otel_export_ceiling_1.CeilingReapplyingSpanProcessor(new sdk_trace_base_1.BatchSpanProcessor(new otel_export_ceiling_1.CeilingReapplyingSpanExporter(exporter, this), { exportTimeoutMillis:
|
|
591
|
+
new otel_export_ceiling_1.CeilingReapplyingSpanProcessor(new sdk_trace_base_1.BatchSpanProcessor(new otel_export_ceiling_1.CeilingReapplyingSpanExporter(exporter, this), { exportTimeoutMillis: this.config.timeoutMillis }), this),
|
|
487
592
|
],
|
|
488
593
|
});
|
|
489
594
|
return this.provider;
|
|
@@ -492,28 +597,50 @@ class ArmorIQTelemetryRuntime {
|
|
|
492
597
|
return null;
|
|
493
598
|
}
|
|
494
599
|
}
|
|
600
|
+
resource() {
|
|
601
|
+
return (0, resources_1.resourceFromAttributes)({
|
|
602
|
+
'service.name': this.config.serviceName,
|
|
603
|
+
'service.version': this.sdkVersion,
|
|
604
|
+
...this.config.resourceAttributes,
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
pipelineExporter() {
|
|
608
|
+
const isExternal = this.config.exporter === 'external';
|
|
609
|
+
if (this.spanSink && !isExternal) {
|
|
610
|
+
const sink = new otel_span_spool_1.SpanSinkExporter(this.spanSink, this.spoolKey);
|
|
611
|
+
this.transport = sink;
|
|
612
|
+
return sink;
|
|
613
|
+
}
|
|
614
|
+
const url = isExternal ? this.config.endpoint : this.firstPartyTracesUrl();
|
|
615
|
+
if (!url)
|
|
616
|
+
return null;
|
|
617
|
+
const headers = isExternal ? this.config.headers : { 'x-api-key': this.apiKey };
|
|
618
|
+
const transport = this.ownedTransport(url, headers);
|
|
619
|
+
this.transport = transport;
|
|
620
|
+
return this.createExporter(url, transport, this.config.timeoutMillis);
|
|
621
|
+
}
|
|
622
|
+
firstPartyTracesUrl() {
|
|
623
|
+
return `${this.backendEndpoint}/v1/traces`;
|
|
624
|
+
}
|
|
625
|
+
ownedTransport(url, headers) {
|
|
626
|
+
// The wire serializer is protobuf. Content type is SDK-owned and must
|
|
627
|
+
// be set after arbitrary caller headers.
|
|
628
|
+
return new otlp_http_transport_1.OwnedOtlpHttpTransport(new URL(url), {
|
|
629
|
+
...headers,
|
|
630
|
+
'content-type': 'application/x-protobuf',
|
|
631
|
+
});
|
|
632
|
+
}
|
|
495
633
|
/**
|
|
496
634
|
* Construct the OTLP delegate from explicit values. The convenience
|
|
497
635
|
* exporter reads process.env while merging defaults, which could leak a
|
|
498
636
|
* caller's external headers into an ArmorIQ-only exporter.
|
|
499
637
|
*/
|
|
500
|
-
createExporter(url,
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
this.ownedAgents.add(agent);
|
|
505
|
-
return agent;
|
|
506
|
-
};
|
|
507
|
-
return new otlp_exporter_base_1.OTLPExporterBase((0, node_http_1.createOtlpHttpExportDelegate)({
|
|
508
|
-
url,
|
|
509
|
-
// The wire serializer is protobuf. Content type is SDK-owned and must
|
|
510
|
-
// be set after arbitrary caller headers.
|
|
511
|
-
headers: () => ({ ...headers, 'content-type': 'application/x-protobuf' }),
|
|
512
|
-
timeoutMillis: Math.min(this.config.timeoutMillis, CLOSE_DEADLINE_MS),
|
|
513
|
-
concurrencyLimit: 1,
|
|
638
|
+
createExporter(url, transport, timeoutMillis) {
|
|
639
|
+
return new otlp_exporter_base_1.OTLPExporterBase((0, otlp_exporter_base_1.createOtlpNetworkExportDelegate)({
|
|
640
|
+
timeoutMillis,
|
|
641
|
+
concurrencyLimit: EXPORT_CONCURRENCY_LIMIT,
|
|
514
642
|
compression: 'none',
|
|
515
|
-
|
|
516
|
-
}, otlp_transformer_1.ProtobufTraceSerializer));
|
|
643
|
+
}, otlp_transformer_1.ProtobufTraceSerializer, (0, node_http_1.createOtlpHttpExporterMetrics)('otlp_http_span_exporter', otlp_transformer_1.TraceExporterMetricsHelper, url, undefined), transport));
|
|
517
644
|
}
|
|
518
645
|
safeAttributes(snapshot, attributes, context) {
|
|
519
646
|
return this.safeAttributesWithAuthority(snapshot, attributes, context, false);
|
|
@@ -566,14 +693,7 @@ class ArmorIQTelemetryRuntime {
|
|
|
566
693
|
}
|
|
567
694
|
result['armoriq.observability.capture.mode'] = snapshot.captureMode;
|
|
568
695
|
result['armoriq.observability.policy.revision'] = snapshot.revision;
|
|
569
|
-
|
|
570
|
-
const sessionId = (0, otel_config_1.sanitizeExternalTelemetryValue)(context.sessionId, 128);
|
|
571
|
-
if (sessionId)
|
|
572
|
-
result['session.id'] = sessionId;
|
|
573
|
-
if (context.userId && validExternalId(context.userId))
|
|
574
|
-
result['user.id'] = context.userId;
|
|
575
|
-
}
|
|
576
|
-
return result;
|
|
696
|
+
return Object.assign(result, sessionIdentity(context, external));
|
|
577
697
|
}
|
|
578
698
|
externalAttributeAllowed(key, mode) {
|
|
579
699
|
if (EXTERNAL_METADATA_ATTRIBUTES.has(key) || EXTERNAL_POLICY_METADATA_ATTRIBUTES.has(key))
|
|
@@ -640,39 +760,111 @@ class ArmorIQTelemetryRuntime {
|
|
|
640
760
|
if (this.config.exporter === 'provider')
|
|
641
761
|
return;
|
|
642
762
|
try {
|
|
643
|
-
await bounded(this.provider?.forceFlush()
|
|
763
|
+
await bounded(this.drainExports(this.provider?.forceFlush(), this.exporter));
|
|
644
764
|
}
|
|
645
765
|
catch {
|
|
646
766
|
// Export failure is intentionally fail-open.
|
|
647
767
|
}
|
|
648
768
|
}
|
|
649
|
-
|
|
769
|
+
/** Process-exit drain: a bounded flush that then fails what is still in flight; the runtime stays open. */
|
|
770
|
+
async drainForExit() {
|
|
771
|
+
await this.forceFlush();
|
|
772
|
+
this.transport?.abandon(false);
|
|
773
|
+
}
|
|
774
|
+
async drainExports(batches, exporter) {
|
|
775
|
+
await batches?.catch(() => undefined);
|
|
776
|
+
await exporter?.forceFlush?.();
|
|
777
|
+
}
|
|
778
|
+
/** The `binding` of every batch this runtime's `spanSink` receives. */
|
|
779
|
+
get spoolBinding() {
|
|
780
|
+
return this.leaseBinding;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Ships batches this runtime's `spanSink` wrote, one OTLP request each,
|
|
784
|
+
* under its current authoritative lease. Resolves to one result per entry of
|
|
785
|
+
* `batches`, in order.
|
|
786
|
+
*/
|
|
787
|
+
async exportSpooled(batches) {
|
|
788
|
+
const parsed = await (0, otel_spool_export_1.parseSpooledSpanBatches)(batches, {
|
|
789
|
+
key: this.spoolKey,
|
|
790
|
+
resource: this.resource(),
|
|
791
|
+
scope: { name: TRACER_NAME, version: this.sdkVersion },
|
|
792
|
+
});
|
|
793
|
+
const available = this.config.enabled && !this.closed && this.config.exporter === 'armoriq';
|
|
794
|
+
const shippable = available && parsed.some((entry) => 'spans' in entry);
|
|
795
|
+
const call = shippable && this.openSpoolCall();
|
|
796
|
+
if (!call)
|
|
797
|
+
return (0, otel_spool_export_1.settleShippable)(parsed, otel_spool_export_1.SPOOL_UNAVAILABLE);
|
|
798
|
+
try {
|
|
799
|
+
await (0, otel_spool_export_1.untilAborted)(this.refreshPolicy(), call.signal);
|
|
800
|
+
const ship = (spans) => this.shipSpooled(spans, call);
|
|
801
|
+
return await (0, otel_spool_export_1.exportSpooledBatches)(parsed, ship, this.spoolSlots, call.signal);
|
|
802
|
+
}
|
|
803
|
+
finally {
|
|
804
|
+
call.end();
|
|
805
|
+
this.spoolCalls.delete(call);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
openSpoolCall() {
|
|
809
|
+
const url = this.firstPartyTracesUrl();
|
|
810
|
+
let transport;
|
|
811
|
+
try {
|
|
812
|
+
transport = this.ownedTransport(url, { 'x-api-key': this.apiKey });
|
|
813
|
+
}
|
|
814
|
+
catch {
|
|
815
|
+
return null;
|
|
816
|
+
}
|
|
817
|
+
const exporter = this.createExporter(url, transport, this.config.timeoutMillis);
|
|
818
|
+
const call = new otel_spool_export_1.SpoolExportCall(transport, exporter);
|
|
819
|
+
this.spoolCalls.add(call);
|
|
820
|
+
return call;
|
|
821
|
+
}
|
|
822
|
+
shipSpooled(spans, call) {
|
|
823
|
+
if (!holdsCurrentAuthority(this.lease))
|
|
824
|
+
return Promise.resolve(otel_spool_export_1.SPOOL_POLICY_UNAVAILABLE);
|
|
825
|
+
const snapshot = this.applyCeiling(this.lease);
|
|
826
|
+
if (snapshot.captureMode === 'off')
|
|
827
|
+
return Promise.resolve(otel_spool_export_1.SPOOL_DISCARDED);
|
|
828
|
+
const project = (span) => ({
|
|
829
|
+
...span,
|
|
830
|
+
attributes: this.shippedAttributes(snapshot, span.attributes),
|
|
831
|
+
});
|
|
832
|
+
return call.send(spans.map(project));
|
|
833
|
+
}
|
|
834
|
+
shippedAttributes(snapshot, attributes) {
|
|
835
|
+
const governed = this.reapplyContentCeiling(snapshot, attributes);
|
|
836
|
+
Object.assign(governed, (0, content_capture_1.trustedContentDiagnostics)(attributes, governed));
|
|
837
|
+
const shipped = {};
|
|
838
|
+
for (const key of Object.keys(attributes))
|
|
839
|
+
if (hasOwn(governed, key))
|
|
840
|
+
shipped[key] = governed[key];
|
|
841
|
+
return Object.assign(shipped, governed);
|
|
842
|
+
}
|
|
843
|
+
async close(deadlineMs = CLOSE_DEADLINE_MS) {
|
|
650
844
|
if (this.closed)
|
|
651
845
|
return;
|
|
652
846
|
this.closed = true;
|
|
653
847
|
(0, otel_shutdown_1.unregisterOwnedTelemetryRuntime)(this);
|
|
848
|
+
this.storeAbort.abort();
|
|
654
849
|
this.policyLeaseAbort?.abort();
|
|
655
850
|
this.policyLeaseAbort = null;
|
|
851
|
+
this.spoolCalls.forEach((call) => call.abort(otel_spool_export_1.SPOOL_UNAVAILABLE));
|
|
656
852
|
if (this.config.exporter === 'provider')
|
|
657
853
|
return;
|
|
658
854
|
const provider = this.provider;
|
|
855
|
+
const exporter = this.exporter;
|
|
856
|
+
const transport = this.transport;
|
|
659
857
|
this.provider = null;
|
|
858
|
+
this.exporter = null;
|
|
859
|
+
this.transport = null;
|
|
660
860
|
try {
|
|
661
|
-
await bounded(provider?.shutdown()
|
|
861
|
+
await bounded(this.drainExports(provider?.shutdown(), exporter), validDeadline(deadlineMs));
|
|
662
862
|
}
|
|
663
863
|
catch {
|
|
664
864
|
// Export failure is intentionally fail-open.
|
|
665
865
|
}
|
|
666
866
|
finally {
|
|
667
|
-
|
|
668
|
-
try {
|
|
669
|
-
agent.destroy();
|
|
670
|
-
}
|
|
671
|
-
catch {
|
|
672
|
-
// Agents are SDK-owned and are best-effort only.
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
this.ownedAgents.clear();
|
|
867
|
+
transport?.abandon(true);
|
|
676
868
|
}
|
|
677
869
|
}
|
|
678
870
|
}
|