@autter/runtime-node 1.3.4 → 1.5.0
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 +114 -17
- package/dist/chunk-3TJHUCZ6.js +1680 -0
- package/dist/chunk-GI47WGRR.cjs +779 -0
- package/dist/chunk-L6SRL65J.cjs +202 -0
- package/dist/chunk-L7HWKMCZ.js +779 -0
- package/dist/chunk-P4VRTFAV.cjs +1680 -0
- package/dist/chunk-ZS4WQJQR.js +202 -0
- package/dist/cli.cjs +40 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +40 -0
- package/dist/index.cjs +1286 -747
- package/dist/index.d.cts +443 -27
- package/dist/index.d.ts +443 -27
- package/dist/index.js +1112 -543
- package/dist/sinks-RFV76lF1.d.cts +313 -0
- package/dist/sinks-RFV76lF1.d.ts +313 -0
- package/dist/testing.cjs +121 -0
- package/dist/testing.d.cts +57 -0
- package/dist/testing.d.ts +57 -0
- package/dist/testing.js +121 -0
- package/package.json +16 -5
package/dist/index.js
CHANGED
|
@@ -1,3 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CODE_PATTERN,
|
|
3
|
+
DEFAULT_REQUEST_ID_HEADER,
|
|
4
|
+
RuntimeError,
|
|
5
|
+
captureExceptionAsLog,
|
|
6
|
+
compileIgnore,
|
|
7
|
+
configureRuntimeLogger,
|
|
8
|
+
consoleSink,
|
|
9
|
+
createRequestState,
|
|
10
|
+
createRuntimeLogger,
|
|
11
|
+
currentOperation,
|
|
12
|
+
defineRuntimeErrors,
|
|
13
|
+
emitSummary,
|
|
14
|
+
errorAttributes,
|
|
15
|
+
errorCodeOf,
|
|
16
|
+
errorInternal,
|
|
17
|
+
fileSink,
|
|
18
|
+
flushRuntimeLogs,
|
|
19
|
+
isRuntimeErrorLike,
|
|
20
|
+
local,
|
|
21
|
+
makeRedactor,
|
|
22
|
+
normalizeRoutePath,
|
|
23
|
+
noteOperationError,
|
|
24
|
+
notifyException,
|
|
25
|
+
otlpSink,
|
|
26
|
+
parseUserAgent,
|
|
27
|
+
recordOperationAiUsage,
|
|
28
|
+
redactAttributes,
|
|
29
|
+
redactText,
|
|
30
|
+
reportExplicitFailure,
|
|
31
|
+
requestOutcome,
|
|
32
|
+
resolveRequestId,
|
|
33
|
+
responseStatusOf,
|
|
34
|
+
runInBackground,
|
|
35
|
+
runtimeContext,
|
|
36
|
+
runtimeLogStats,
|
|
37
|
+
runtimeLogger,
|
|
38
|
+
shutdownRuntimeLogger,
|
|
39
|
+
toClientError,
|
|
40
|
+
withRuntimeOperation
|
|
41
|
+
} from "./chunk-3TJHUCZ6.js";
|
|
42
|
+
import {
|
|
43
|
+
runDoctor
|
|
44
|
+
} from "./chunk-ZS4WQJQR.js";
|
|
45
|
+
import {
|
|
46
|
+
COMPAT_MANIFEST,
|
|
47
|
+
CountingExporter,
|
|
48
|
+
INGESTER_VERSION_HEADER,
|
|
49
|
+
SDK_IDENTITY,
|
|
50
|
+
configureCompatCheck,
|
|
51
|
+
debugLog,
|
|
52
|
+
ensureCompatConfigured,
|
|
53
|
+
evaluateCompat,
|
|
54
|
+
featuresForBrowserEvents,
|
|
55
|
+
fetchIngesterCompat,
|
|
56
|
+
installAutterAutoFlush,
|
|
57
|
+
isDebugEnabled,
|
|
58
|
+
noteCompatFeature,
|
|
59
|
+
noteRelayRejection,
|
|
60
|
+
observeIngesterResponse,
|
|
61
|
+
registerFlushTarget,
|
|
62
|
+
setDebugMode,
|
|
63
|
+
telemetryStats,
|
|
64
|
+
unregisterFlushTargets
|
|
65
|
+
} from "./chunk-L7HWKMCZ.js";
|
|
66
|
+
|
|
1
67
|
// src/relay.ts
|
|
2
68
|
var IpWindow = class {
|
|
3
69
|
constructor(limitPerMinute) {
|
|
@@ -36,7 +102,120 @@ var EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
|
36
102
|
"timing"
|
|
37
103
|
]);
|
|
38
104
|
var SEVERITIES = /* @__PURE__ */ new Set(["fatal", "error", "warning", "info"]);
|
|
39
|
-
|
|
105
|
+
var CONTEXT_MAX_DEPTH = 6;
|
|
106
|
+
var CONTEXT_MAX_NODES = 256;
|
|
107
|
+
var CONTEXT_MAX_STRING = 4e3;
|
|
108
|
+
var CONTEXT_MAX_ARRAY = 100;
|
|
109
|
+
var CONTEXT_MAX_KEYS = 100;
|
|
110
|
+
function byteLength(text) {
|
|
111
|
+
return new TextEncoder().encode(text).length;
|
|
112
|
+
}
|
|
113
|
+
function boundContext(value) {
|
|
114
|
+
let nodes = 0;
|
|
115
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
116
|
+
const walk = (v, depth) => {
|
|
117
|
+
if (v === null) return null;
|
|
118
|
+
const t = typeof v;
|
|
119
|
+
if (t === "string") return v.slice(0, CONTEXT_MAX_STRING);
|
|
120
|
+
if (t === "number" || t === "boolean") return v;
|
|
121
|
+
if (t !== "object") return void 0;
|
|
122
|
+
if (depth >= CONTEXT_MAX_DEPTH || nodes >= CONTEXT_MAX_NODES) return void 0;
|
|
123
|
+
const obj = v;
|
|
124
|
+
if (seen.has(obj)) return void 0;
|
|
125
|
+
seen.add(obj);
|
|
126
|
+
let isArr = false;
|
|
127
|
+
try {
|
|
128
|
+
isArr = Array.isArray(v);
|
|
129
|
+
} catch {
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
if (isArr) {
|
|
133
|
+
const arr = v;
|
|
134
|
+
const out2 = [];
|
|
135
|
+
let len = 0;
|
|
136
|
+
try {
|
|
137
|
+
len = arr.length;
|
|
138
|
+
} catch {
|
|
139
|
+
return out2;
|
|
140
|
+
}
|
|
141
|
+
for (let i = 0; i < len && i < CONTEXT_MAX_ARRAY; i++) {
|
|
142
|
+
if (nodes >= CONTEXT_MAX_NODES) break;
|
|
143
|
+
nodes++;
|
|
144
|
+
let el;
|
|
145
|
+
try {
|
|
146
|
+
el = walk(arr[i], depth + 1);
|
|
147
|
+
} catch {
|
|
148
|
+
el = void 0;
|
|
149
|
+
}
|
|
150
|
+
if (el !== void 0) out2.push(el);
|
|
151
|
+
}
|
|
152
|
+
return out2;
|
|
153
|
+
}
|
|
154
|
+
let keys;
|
|
155
|
+
try {
|
|
156
|
+
keys = Object.keys(obj);
|
|
157
|
+
} catch {
|
|
158
|
+
return void 0;
|
|
159
|
+
}
|
|
160
|
+
const out = {};
|
|
161
|
+
for (let i = 0; i < keys.length && i < CONTEXT_MAX_KEYS; i++) {
|
|
162
|
+
if (nodes >= CONTEXT_MAX_NODES) break;
|
|
163
|
+
const key = keys[i];
|
|
164
|
+
if (key === void 0) continue;
|
|
165
|
+
nodes++;
|
|
166
|
+
let child;
|
|
167
|
+
try {
|
|
168
|
+
child = walk(obj[key], depth + 1);
|
|
169
|
+
} catch {
|
|
170
|
+
child = void 0;
|
|
171
|
+
}
|
|
172
|
+
if (child !== void 0) out[key] = child;
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
};
|
|
176
|
+
let result;
|
|
177
|
+
try {
|
|
178
|
+
result = walk(value, 0);
|
|
179
|
+
} catch {
|
|
180
|
+
result = void 0;
|
|
181
|
+
}
|
|
182
|
+
return result && typeof result === "object" ? result : {};
|
|
183
|
+
}
|
|
184
|
+
async function readBodyBounded(request, maxBody) {
|
|
185
|
+
const body = request.body;
|
|
186
|
+
if (!body) {
|
|
187
|
+
const text = await request.text();
|
|
188
|
+
return byteLength(text) > maxBody ? { tooLarge: true } : { tooLarge: false, text };
|
|
189
|
+
}
|
|
190
|
+
const reader = body.getReader();
|
|
191
|
+
const chunks = [];
|
|
192
|
+
let total = 0;
|
|
193
|
+
for (; ; ) {
|
|
194
|
+
const { done, value } = await reader.read();
|
|
195
|
+
if (done) break;
|
|
196
|
+
if (!value) continue;
|
|
197
|
+
total += value.byteLength;
|
|
198
|
+
if (total > maxBody) {
|
|
199
|
+
void Promise.resolve().then(() => reader.cancel()).catch(() => {
|
|
200
|
+
});
|
|
201
|
+
return { tooLarge: true };
|
|
202
|
+
}
|
|
203
|
+
chunks.push(value);
|
|
204
|
+
}
|
|
205
|
+
const buf = new Uint8Array(total);
|
|
206
|
+
let offset = 0;
|
|
207
|
+
for (const chunk of chunks) {
|
|
208
|
+
buf.set(chunk, offset);
|
|
209
|
+
offset += chunk.byteLength;
|
|
210
|
+
}
|
|
211
|
+
return { tooLarge: false, text: new TextDecoder().decode(buf) };
|
|
212
|
+
}
|
|
213
|
+
function relayContext(raw, redactor) {
|
|
214
|
+
const bounded = boundContext(raw);
|
|
215
|
+
return Array.isArray(bounded) ? {} : { context: redactor(bounded) };
|
|
216
|
+
}
|
|
217
|
+
var defaultRelayRedactor = makeRedactor(true);
|
|
218
|
+
function sanitizeBrowserPayload(raw, redactor = defaultRelayRedactor) {
|
|
40
219
|
if (typeof raw !== "object" || raw === null) return null;
|
|
41
220
|
const p = raw;
|
|
42
221
|
if (p.version !== 1) return null;
|
|
@@ -53,16 +232,16 @@ function sanitizeBrowserPayload(raw) {
|
|
|
53
232
|
type: e.type,
|
|
54
233
|
timestamp: e.timestamp,
|
|
55
234
|
...typeof e.severity === "string" && SEVERITIES.has(e.severity) ? { severity: e.severity } : {},
|
|
56
|
-
message: typeof e.message === "string" ? e.message.slice(0, 4e3) : "",
|
|
57
|
-
...typeof e.name === "string" ? { name: e.name.slice(0, 200) } : {},
|
|
235
|
+
message: typeof e.message === "string" ? redactor.text(e.message.slice(0, 4512)).slice(0, 4e3) : "",
|
|
236
|
+
...typeof e.name === "string" ? { name: redactor.text(e.name.slice(0, 712)).slice(0, 200) } : {},
|
|
58
237
|
...typeof e.durationMs === "number" && Number.isFinite(e.durationMs) ? { durationMs: Math.max(0, Math.min(12e4, e.durationMs)) } : {},
|
|
59
|
-
...typeof e.stack === "string" ? { stack: e.stack.slice(0, 32e3) } : {},
|
|
238
|
+
...typeof e.stack === "string" ? { stack: redactor.text(e.stack.slice(0, 32512)).slice(0, 32e3) } : {},
|
|
60
239
|
...typeof e.errorType === "string" ? { errorType: e.errorType.slice(0, 200) } : {},
|
|
61
240
|
...typeof e.filename === "string" ? { filename: e.filename.split("?")[0].slice(0, 1e3) } : {},
|
|
62
241
|
...typeof e.line === "number" ? { line: e.line } : {},
|
|
63
242
|
...typeof e.column === "number" ? { column: e.column } : {},
|
|
64
243
|
...typeof e.route === "string" ? { route: e.route.split("?")[0].slice(0, 1e3) } : {},
|
|
65
|
-
...typeof e.context === "object" && e.context !== null ?
|
|
244
|
+
...typeof e.context === "object" && e.context !== null ? relayContext(e.context, redactor) : {}
|
|
66
245
|
});
|
|
67
246
|
}
|
|
68
247
|
return {
|
|
@@ -71,11 +250,16 @@ function sanitizeBrowserPayload(raw) {
|
|
|
71
250
|
service: p.service.slice(0, 200),
|
|
72
251
|
environment: p.environment.slice(0, 100),
|
|
73
252
|
...typeof p.release === "string" ? { release: p.release.slice(0, 200) } : {},
|
|
253
|
+
// @autter/runtime-browser version, for the ingester's compat records.
|
|
254
|
+
...typeof p.sdk === "string" && /^\d{1,6}\.\d{1,6}\.\d{1,6}[0-9A-Za-z.+-]{0,20}$/.test(p.sdk) ? { sdk: p.sdk } : {},
|
|
74
255
|
events
|
|
75
256
|
};
|
|
76
257
|
}
|
|
77
258
|
function forward(payload, opts) {
|
|
78
|
-
const
|
|
259
|
+
const endpoint = (opts.endpoint ?? DEFAULT_ENDPOINT).replace(/\/$/, "");
|
|
260
|
+
const url = `${endpoint}/v1/browser`;
|
|
261
|
+
const compat = opts.compatCheck !== false;
|
|
262
|
+
if (compat) ensureCompatConfigured(endpoint);
|
|
79
263
|
void fetch(url, {
|
|
80
264
|
method: "POST",
|
|
81
265
|
headers: {
|
|
@@ -84,6 +268,14 @@ function forward(payload, opts) {
|
|
|
84
268
|
},
|
|
85
269
|
body: JSON.stringify(payload),
|
|
86
270
|
signal: AbortSignal.timeout(1e4)
|
|
271
|
+
}).then((response) => {
|
|
272
|
+
if (!compat) return;
|
|
273
|
+
const types = new Set(
|
|
274
|
+
(payload.events ?? []).map((e) => String(e.type))
|
|
275
|
+
);
|
|
276
|
+
observeIngesterResponse({ status: response.status, headers: response.headers });
|
|
277
|
+
for (const feature of featuresForBrowserEvents(types)) noteCompatFeature(feature);
|
|
278
|
+
noteRelayRejection(types, response.status, response.headers);
|
|
87
279
|
}).catch((err) => {
|
|
88
280
|
(opts.onError ?? ((e) => console.warn("autter relay forward failed:", e)))(
|
|
89
281
|
err
|
|
@@ -92,6 +284,7 @@ function forward(payload, opts) {
|
|
|
92
284
|
}
|
|
93
285
|
function createBrowserRelayFetchHandler(opts) {
|
|
94
286
|
const maxBody = opts.maxBodyBytes ?? DEFAULT_MAX_BODY;
|
|
287
|
+
const redactor = opts.redact === void 0 || opts.redact === true ? defaultRelayRedactor : makeRedactor(opts.redact);
|
|
95
288
|
const limiter = opts.perIpRateLimit === false ? null : new IpWindow(opts.perIpRateLimit ?? 120);
|
|
96
289
|
return async (request) => {
|
|
97
290
|
if (request.method !== "POST") {
|
|
@@ -105,21 +298,28 @@ function createBrowserRelayFetchHandler(opts) {
|
|
|
105
298
|
});
|
|
106
299
|
}
|
|
107
300
|
}
|
|
108
|
-
|
|
109
|
-
|
|
301
|
+
let bounded;
|
|
302
|
+
try {
|
|
303
|
+
bounded = await readBodyBounded(request, maxBody);
|
|
304
|
+
} catch {
|
|
305
|
+
return new Response(JSON.stringify({ error: "invalid json" }), {
|
|
306
|
+
status: 400
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
if (bounded.tooLarge) {
|
|
110
310
|
return new Response(JSON.stringify({ error: "payload too large" }), {
|
|
111
311
|
status: 413
|
|
112
312
|
});
|
|
113
313
|
}
|
|
114
314
|
let raw;
|
|
115
315
|
try {
|
|
116
|
-
raw = JSON.parse(text);
|
|
316
|
+
raw = JSON.parse(bounded.text);
|
|
117
317
|
} catch {
|
|
118
318
|
return new Response(JSON.stringify({ error: "invalid json" }), {
|
|
119
319
|
status: 400
|
|
120
320
|
});
|
|
121
321
|
}
|
|
122
|
-
const payload = sanitizeBrowserPayload(raw);
|
|
322
|
+
const payload = sanitizeBrowserPayload(raw, redactor);
|
|
123
323
|
if (!payload) {
|
|
124
324
|
return new Response(JSON.stringify({ error: "invalid payload" }), {
|
|
125
325
|
status: 400
|
|
@@ -131,6 +331,7 @@ function createBrowserRelayFetchHandler(opts) {
|
|
|
131
331
|
}
|
|
132
332
|
function createBrowserRelayHandler(opts) {
|
|
133
333
|
const maxBody = opts.maxBodyBytes ?? DEFAULT_MAX_BODY;
|
|
334
|
+
const redactor = opts.redact === void 0 || opts.redact === true ? defaultRelayRedactor : makeRedactor(opts.redact);
|
|
134
335
|
const limiter = opts.perIpRateLimit === false ? null : new IpWindow(opts.perIpRateLimit ?? 120);
|
|
135
336
|
function respond(res, status, body) {
|
|
136
337
|
res.statusCode = status;
|
|
@@ -142,7 +343,7 @@ function createBrowserRelayHandler(opts) {
|
|
|
142
343
|
}
|
|
143
344
|
}
|
|
144
345
|
function handleParsed(raw, res) {
|
|
145
|
-
const payload = sanitizeBrowserPayload(raw);
|
|
346
|
+
const payload = sanitizeBrowserPayload(raw, redactor);
|
|
146
347
|
if (!payload) {
|
|
147
348
|
respond(res, 400, { error: "invalid payload" });
|
|
148
349
|
return;
|
|
@@ -206,8 +407,8 @@ function createBrowserRelayHandler(opts) {
|
|
|
206
407
|
import { ServerResponse } from "http";
|
|
207
408
|
import { randomUUID } from "crypto";
|
|
208
409
|
import {
|
|
209
|
-
context,
|
|
210
|
-
trace,
|
|
410
|
+
context as context2,
|
|
411
|
+
trace as trace2,
|
|
211
412
|
SpanKind,
|
|
212
413
|
SpanStatusCode,
|
|
213
414
|
TraceFlags
|
|
@@ -219,7 +420,7 @@ import {
|
|
|
219
420
|
} from "@opentelemetry/exporter-metrics-otlp-http";
|
|
220
421
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
221
422
|
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
|
222
|
-
import { Resource } from "@opentelemetry/resources";
|
|
423
|
+
import { Resource as Resource2 } from "@opentelemetry/resources";
|
|
223
424
|
import { ExplicitBucketHistogramAggregation, PeriodicExportingMetricReader, View } from "@opentelemetry/sdk-metrics";
|
|
224
425
|
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
225
426
|
import {
|
|
@@ -235,50 +436,83 @@ import {
|
|
|
235
436
|
ATTR_SERVICE_VERSION
|
|
236
437
|
} from "@opentelemetry/semantic-conventions";
|
|
237
438
|
|
|
238
|
-
// src/
|
|
439
|
+
// src/redact-exporter.ts
|
|
239
440
|
import { ExportResultCode } from "@opentelemetry/core";
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
441
|
+
import { Resource } from "@opentelemetry/resources";
|
|
442
|
+
import {
|
|
443
|
+
AggregationTemporality
|
|
444
|
+
} from "@opentelemetry/sdk-metrics";
|
|
445
|
+
var scrubbedResources = /* @__PURE__ */ new WeakMap();
|
|
446
|
+
function redactResource(resource, redactor) {
|
|
447
|
+
const cached = scrubbedResources.get(resource);
|
|
448
|
+
if (cached) return cached;
|
|
449
|
+
const scrubbed = new Resource(redactor.exported(resource.attributes));
|
|
450
|
+
scrubbedResources.set(resource, scrubbed);
|
|
451
|
+
return scrubbed;
|
|
246
452
|
}
|
|
247
|
-
function
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
453
|
+
function redactSpan(span, redactor) {
|
|
454
|
+
const status = span.status.message ? { ...span.status, message: redactor.text(span.status.message) } : span.status;
|
|
455
|
+
return {
|
|
456
|
+
name: redactor.text(span.name),
|
|
457
|
+
kind: span.kind,
|
|
458
|
+
spanContext: () => span.spanContext(),
|
|
459
|
+
parentSpanId: span.parentSpanId,
|
|
460
|
+
startTime: span.startTime,
|
|
461
|
+
endTime: span.endTime,
|
|
462
|
+
status,
|
|
463
|
+
attributes: redactor.exported(span.attributes),
|
|
464
|
+
links: span.links.map(
|
|
465
|
+
(link) => link.attributes ? { ...link, attributes: redactor.exported(link.attributes) } : link
|
|
466
|
+
),
|
|
467
|
+
events: span.events.map((event) => ({
|
|
468
|
+
...event,
|
|
469
|
+
name: redactor.text(event.name),
|
|
470
|
+
...event.attributes ? { attributes: redactor.exported(event.attributes) } : {}
|
|
471
|
+
})),
|
|
472
|
+
duration: span.duration,
|
|
473
|
+
ended: span.ended,
|
|
474
|
+
resource: redactResource(span.resource, redactor),
|
|
475
|
+
instrumentationLibrary: span.instrumentationLibrary,
|
|
476
|
+
droppedAttributesCount: span.droppedAttributesCount,
|
|
477
|
+
droppedEventsCount: span.droppedEventsCount,
|
|
478
|
+
droppedLinksCount: span.droppedLinksCount
|
|
479
|
+
};
|
|
251
480
|
}
|
|
252
|
-
var
|
|
253
|
-
|
|
254
|
-
pendingCaptures = 0;
|
|
255
|
-
markCaptured(count = 1) {
|
|
256
|
-
this.pendingCaptures += count;
|
|
257
|
-
}
|
|
258
|
-
/** An exporter batch left the process successfully. */
|
|
259
|
-
onExported(spanCount) {
|
|
260
|
-
this.pendingCaptures = Math.max(0, this.pendingCaptures - spanCount);
|
|
261
|
-
if (debugEnabled && spanCount > 0) {
|
|
262
|
-
debugLog(`exported ${spanCount} span${spanCount === 1 ? "" : "s"}`);
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
markAllFlushed() {
|
|
266
|
-
this.pendingCaptures = 0;
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
var telemetryStats = new TelemetryStats();
|
|
270
|
-
var CountingExporter = class {
|
|
271
|
-
constructor(inner) {
|
|
481
|
+
var RedactingSpanExporter = class {
|
|
482
|
+
constructor(inner, redactor) {
|
|
272
483
|
this.inner = inner;
|
|
484
|
+
this.redactor = redactor;
|
|
273
485
|
}
|
|
274
486
|
inner;
|
|
487
|
+
redactor;
|
|
275
488
|
export(spans, resultCallback) {
|
|
276
|
-
this.
|
|
277
|
-
|
|
278
|
-
|
|
489
|
+
const redactor = this.redactor();
|
|
490
|
+
let scrubbed = spans;
|
|
491
|
+
let failed = 0;
|
|
492
|
+
if (redactor.enabled) {
|
|
493
|
+
scrubbed = [];
|
|
494
|
+
for (const span of spans) {
|
|
495
|
+
try {
|
|
496
|
+
scrubbed.push(redactSpan(span, redactor));
|
|
497
|
+
} catch {
|
|
498
|
+
failed++;
|
|
499
|
+
}
|
|
279
500
|
}
|
|
280
|
-
|
|
281
|
-
|
|
501
|
+
}
|
|
502
|
+
if (!failed) {
|
|
503
|
+
this.inner.export(scrubbed, resultCallback);
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
const error = new Error(`autter-runtime: ${failed} span(s) could not be redacted and were not exported`);
|
|
507
|
+
const fail = () => resultCallback({ code: ExportResultCode.FAILED, error });
|
|
508
|
+
if (!scrubbed.length) {
|
|
509
|
+
fail();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
this.inner.export(
|
|
513
|
+
scrubbed,
|
|
514
|
+
(result) => result.code === ExportResultCode.SUCCESS ? fail() : resultCallback(result)
|
|
515
|
+
);
|
|
282
516
|
}
|
|
283
517
|
shutdown() {
|
|
284
518
|
return this.inner.shutdown();
|
|
@@ -287,394 +521,38 @@ var CountingExporter = class {
|
|
|
287
521
|
return this.inner.forceFlush?.() ?? Promise.resolve();
|
|
288
522
|
}
|
|
289
523
|
};
|
|
290
|
-
var
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
function unregisterFlushTargets() {
|
|
295
|
-
flushTargets.clear();
|
|
296
|
-
}
|
|
297
|
-
function activeFlushTargets() {
|
|
298
|
-
return [...flushTargets.values()];
|
|
299
|
-
}
|
|
300
|
-
var DEFAULT_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
301
|
-
var SIGNAL_EXIT_CODES = {
|
|
302
|
-
SIGINT: 130,
|
|
303
|
-
SIGTERM: 143
|
|
304
|
-
};
|
|
305
|
-
function installAutterAutoFlush(options = {}) {
|
|
306
|
-
const logger = options.logger ?? console;
|
|
307
|
-
const log = options.log !== false;
|
|
308
|
-
const warnOnUnflushedExit = options.warnOnUnflushedExit !== false;
|
|
309
|
-
const timeoutMs = Math.max(100, options.timeoutMs ?? 3e3);
|
|
310
|
-
const signals = options.signals ?? DEFAULT_SIGNALS;
|
|
311
|
-
const flushOnUncaughtException = options.flushOnUncaughtException !== false;
|
|
312
|
-
let disposed = false;
|
|
313
|
-
let inFlight = null;
|
|
314
|
-
const doFlush = async () => {
|
|
315
|
-
const targets = options.targets ?? activeFlushTargets();
|
|
316
|
-
if (targets.length === 0) return true;
|
|
317
|
-
let timer;
|
|
318
|
-
const timedOut = new Promise((resolve) => {
|
|
319
|
-
timer = setTimeout(() => resolve(false), timeoutMs);
|
|
320
|
-
});
|
|
321
|
-
const drained = Promise.allSettled(
|
|
322
|
-
targets.map(
|
|
323
|
-
(target) => Promise.resolve().then(() => target.forceFlush())
|
|
324
|
-
)
|
|
325
|
-
).then(
|
|
326
|
-
(results) => results.every((result) => result.status === "fulfilled")
|
|
327
|
-
);
|
|
328
|
-
let ok;
|
|
329
|
-
try {
|
|
330
|
-
ok = await Promise.race([drained, timedOut]);
|
|
331
|
-
} finally {
|
|
332
|
-
clearTimeout(timer);
|
|
333
|
-
}
|
|
334
|
-
if (ok) {
|
|
335
|
-
telemetryStats.markAllFlushed();
|
|
336
|
-
if (log) {
|
|
337
|
-
logger.log("[autter] telemetry flushed");
|
|
338
|
-
}
|
|
339
|
-
} else {
|
|
340
|
-
logger.error(
|
|
341
|
-
`[autter] flush did not finish within ${timeoutMs}ms \u2014 some telemetry may be lost`
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
return ok;
|
|
345
|
-
};
|
|
346
|
-
const flush = (reason = "exit") => {
|
|
347
|
-
if (disposed) return Promise.resolve(false);
|
|
348
|
-
if (!inFlight) {
|
|
349
|
-
inFlight = doFlush().finally(() => {
|
|
350
|
-
inFlight = null;
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
if (isDebugEnabled()) debugLog(`auto-flush requested on ${reason}`);
|
|
354
|
-
return inFlight;
|
|
355
|
-
};
|
|
356
|
-
const signalCounts = /* @__PURE__ */ new Map();
|
|
357
|
-
let handlingUncaughtException = false;
|
|
358
|
-
const onUncaughtException = (error) => {
|
|
359
|
-
if (process.listenerCount("uncaughtException") > 1) return;
|
|
360
|
-
if (handlingUncaughtException) return;
|
|
361
|
-
handlingUncaughtException = true;
|
|
362
|
-
logger.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
363
|
-
const keepAlive = setTimeout(() => {
|
|
364
|
-
}, timeoutMs + 50);
|
|
365
|
-
void flush("uncaughtException").finally(() => {
|
|
366
|
-
clearTimeout(keepAlive);
|
|
367
|
-
process.exit(1);
|
|
368
|
-
});
|
|
369
|
-
};
|
|
370
|
-
const onSignal = (signal) => {
|
|
371
|
-
const count = (signalCounts.get(signal) ?? 0) + 1;
|
|
372
|
-
signalCounts.set(signal, count);
|
|
373
|
-
const exitCode = SIGNAL_EXIT_CODES[signal] ?? 1;
|
|
374
|
-
if (count > 1) {
|
|
375
|
-
process.exit(exitCode);
|
|
376
|
-
}
|
|
377
|
-
const otherHandlers = process.listenerCount(signal) - 1;
|
|
378
|
-
if (otherHandlers > 0) {
|
|
379
|
-
void flush(signal);
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
void flush(signal).finally(() => {
|
|
383
|
-
process.exit(exitCode);
|
|
384
|
-
});
|
|
385
|
-
};
|
|
386
|
-
const MAX_BEFORE_EXIT_FLUSHES = 10;
|
|
387
|
-
let beforeExitFlushes = 0;
|
|
388
|
-
const onBeforeExit = () => {
|
|
389
|
-
if (disposed) return;
|
|
390
|
-
const targets = options.targets ?? activeFlushTargets();
|
|
391
|
-
const worthIt = beforeExitFlushes === 0 ? targets.length > 0 : telemetryStats.pendingCaptures > 0;
|
|
392
|
-
if (!worthIt || beforeExitFlushes >= MAX_BEFORE_EXIT_FLUSHES) return;
|
|
393
|
-
beforeExitFlushes++;
|
|
394
|
-
const keepAlive = setTimeout(() => {
|
|
395
|
-
}, timeoutMs);
|
|
396
|
-
void flush("beforeExit").finally(() => clearTimeout(keepAlive));
|
|
397
|
-
};
|
|
398
|
-
const onExit = () => {
|
|
399
|
-
if (!warnOnUnflushedExit || disposed) return;
|
|
400
|
-
const pending = telemetryStats.pendingCaptures;
|
|
401
|
-
if (pending > 0) {
|
|
402
|
-
logger.error(
|
|
403
|
-
`[autter] process exiting with ~${pending} captured item(s) never confirmed exported \u2014 await autter.shutdown() on graceful shutdown`
|
|
404
|
-
);
|
|
405
|
-
}
|
|
406
|
-
};
|
|
407
|
-
const signalHandlers = /* @__PURE__ */ new Map();
|
|
408
|
-
process.on("beforeExit", onBeforeExit);
|
|
409
|
-
if (flushOnUncaughtException) {
|
|
410
|
-
process.on("uncaughtException", onUncaughtException);
|
|
411
|
-
}
|
|
412
|
-
for (const signal of signals) {
|
|
413
|
-
const handler = () => onSignal(signal);
|
|
414
|
-
signalHandlers.set(signal, handler);
|
|
415
|
-
process.on(signal, handler);
|
|
416
|
-
}
|
|
417
|
-
process.on("exit", onExit);
|
|
418
|
-
return {
|
|
419
|
-
flush,
|
|
420
|
-
dispose() {
|
|
421
|
-
disposed = true;
|
|
422
|
-
process.removeListener("beforeExit", onBeforeExit);
|
|
423
|
-
process.removeListener("uncaughtException", onUncaughtException);
|
|
424
|
-
process.removeListener("exit", onExit);
|
|
425
|
-
for (const [signal, handler] of signalHandlers) {
|
|
426
|
-
process.removeListener(signal, handler);
|
|
427
|
-
}
|
|
428
|
-
signalHandlers.clear();
|
|
429
|
-
}
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// src/redact.ts
|
|
434
|
-
var SENSITIVE_KEY_PATTERNS = [
|
|
435
|
-
/e-?mail/,
|
|
436
|
-
/pass(word|wd|phrase)|^pass$/,
|
|
437
|
-
/token/,
|
|
438
|
-
/secret/,
|
|
439
|
-
/credential/,
|
|
440
|
-
/(api|access|secret|private|consumer|client|signing|encryption)-?[_.]?key/,
|
|
441
|
-
/^-?x?-?authorization$|^auth(-|_|$)|bearer/,
|
|
442
|
-
/cookie/,
|
|
443
|
-
/phone|msisdn/,
|
|
444
|
-
/\bssn\b|social[-_ ]?security/,
|
|
445
|
-
/cvv|cvc|card([-_. ]?(number|num|no))?$/,
|
|
446
|
-
/credit[-_.]?card/,
|
|
447
|
-
/connection[-_.]?string/,
|
|
448
|
-
/recovery[-_.]?code|\botp\b|magic[-_.]?link/
|
|
449
|
-
];
|
|
450
|
-
var PRIVATE_KEY_BLOCK_RE = /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g;
|
|
451
|
-
var EMAIL_VALUE_RE = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
|
|
452
|
-
var JWT_RE = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g;
|
|
453
|
-
var OPENAI_KEY_RE = /\bsk-[A-Za-z0-9]{20,}\b/g;
|
|
454
|
-
var GITHUB_TOKEN_RE = /\bgh[pousr]_[A-Za-z0-9]{20,}\b/g;
|
|
455
|
-
var AWS_KEY_RE = /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g;
|
|
456
|
-
var SLACK_TOKEN_RE = /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g;
|
|
457
|
-
var BEARER_RE = /\bbearer\s+[A-Za-z0-9._~+/=-]{10,}/gi;
|
|
458
|
-
var URL_CREDENTIALS_RE = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/:@]+:[^\s/@]+@/gi;
|
|
459
|
-
function toCaseInsensitive(pattern) {
|
|
460
|
-
return typeof pattern === "string" ? new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i") : new RegExp(pattern.source, pattern.flags.includes("i") ? pattern.flags : `${pattern.flags}i`);
|
|
461
|
-
}
|
|
462
|
-
function compile(options) {
|
|
463
|
-
const keyPatterns = [...SENSITIVE_KEY_PATTERNS];
|
|
464
|
-
const valuePatterns = [
|
|
465
|
-
JWT_RE,
|
|
466
|
-
OPENAI_KEY_RE,
|
|
467
|
-
GITHUB_TOKEN_RE,
|
|
468
|
-
AWS_KEY_RE,
|
|
469
|
-
SLACK_TOKEN_RE,
|
|
470
|
-
BEARER_RE
|
|
471
|
-
];
|
|
472
|
-
if (options?.additionalKeyPatterns) {
|
|
473
|
-
for (const p of options.additionalKeyPatterns) {
|
|
474
|
-
keyPatterns.push(toCaseInsensitive(p));
|
|
475
|
-
}
|
|
524
|
+
var RedactingMetricExporter = class {
|
|
525
|
+
constructor(inner, redactor) {
|
|
526
|
+
this.inner = inner;
|
|
527
|
+
this.redactor = redactor;
|
|
476
528
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
529
|
+
inner;
|
|
530
|
+
redactor;
|
|
531
|
+
export(metrics2, resultCallback) {
|
|
532
|
+
const redactor = this.redactor();
|
|
533
|
+
if (!redactor.enabled) {
|
|
534
|
+
this.inner.export(metrics2, resultCallback);
|
|
535
|
+
return;
|
|
482
536
|
}
|
|
483
|
-
|
|
484
|
-
return {
|
|
485
|
-
keyPatterns,
|
|
486
|
-
valuePatterns,
|
|
487
|
-
urlCredentials: URL_CREDENTIALS_RE,
|
|
488
|
-
privateKeyBlock: PRIVATE_KEY_BLOCK_RE,
|
|
489
|
-
mask: options?.mask ?? "[redacted]",
|
|
490
|
-
scrubEmailValues: options?.scrubEmailValues !== false
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
function redactString(value, r) {
|
|
494
|
-
let out = value.replace(r.privateKeyBlock, r.mask);
|
|
495
|
-
for (const re of r.valuePatterns) {
|
|
496
|
-
out = out.replace(re, r.mask);
|
|
497
|
-
}
|
|
498
|
-
out = out.replace(r.urlCredentials, "$1" + r.mask + "@");
|
|
499
|
-
if (r.scrubEmailValues) {
|
|
500
|
-
out = out.replace(EMAIL_VALUE_RE, r.mask);
|
|
501
|
-
}
|
|
502
|
-
return out;
|
|
503
|
-
}
|
|
504
|
-
var MAX_REDACTION_DEPTH = 64;
|
|
505
|
-
var MAX_REDACTION_WORK = 1e4;
|
|
506
|
-
var MAX_COLLECTION_ENTRIES = 1e3;
|
|
507
|
-
function canTraverse(depth, state) {
|
|
508
|
-
if (depth > MAX_REDACTION_DEPTH || state.remainingWork <= 0) return false;
|
|
509
|
-
state.remainingWork -= 1;
|
|
510
|
-
return true;
|
|
511
|
-
}
|
|
512
|
-
function redactValue(value, r, state, depth) {
|
|
513
|
-
if (typeof value === "string") return redactString(value, r);
|
|
514
|
-
let isArray = false;
|
|
515
|
-
try {
|
|
516
|
-
isArray = Array.isArray(value);
|
|
517
|
-
} catch {
|
|
518
|
-
return r.mask;
|
|
519
|
-
}
|
|
520
|
-
if (isArray) {
|
|
521
|
-
return redactArray(value, r, state, depth);
|
|
522
|
-
}
|
|
523
|
-
if (typeof value === "object" && value !== null) {
|
|
524
|
-
return redactObject(value, r, state, depth);
|
|
525
|
-
}
|
|
526
|
-
return value;
|
|
527
|
-
}
|
|
528
|
-
function redactArray(value, r, state, depth) {
|
|
529
|
-
const existing = state.ancestors.get(value);
|
|
530
|
-
if (existing) return r.mask;
|
|
531
|
-
if (!canTraverse(depth, state)) return r.mask;
|
|
532
|
-
const out = [];
|
|
533
|
-
state.ancestors.set(value, out);
|
|
534
|
-
let length;
|
|
535
|
-
try {
|
|
536
|
-
length = value.length;
|
|
537
|
-
} catch {
|
|
538
|
-
state.ancestors.delete(value);
|
|
539
|
-
return r.mask;
|
|
540
|
-
}
|
|
541
|
-
const limit = Math.min(length, MAX_COLLECTION_ENTRIES);
|
|
542
|
-
for (let i = 0; i < limit; i += 1) {
|
|
543
|
-
let item;
|
|
537
|
+
let resource;
|
|
544
538
|
try {
|
|
545
|
-
|
|
546
|
-
} catch {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}
|
|
550
|
-
out.push(redactValue(item, r, state, depth + 1));
|
|
551
|
-
}
|
|
552
|
-
if (length > limit) {
|
|
553
|
-
out.push(r.mask);
|
|
554
|
-
}
|
|
555
|
-
state.ancestors.delete(value);
|
|
556
|
-
return out;
|
|
557
|
-
}
|
|
558
|
-
function redactObject(value, r, state, depth) {
|
|
559
|
-
const existing = state.ancestors.get(value);
|
|
560
|
-
if (existing) return r.mask;
|
|
561
|
-
if (!canTraverse(depth, state)) return r.mask;
|
|
562
|
-
const out = {};
|
|
563
|
-
state.ancestors.set(value, out);
|
|
564
|
-
let count = 0;
|
|
565
|
-
let truncated = false;
|
|
566
|
-
try {
|
|
567
|
-
for (const key in value) {
|
|
568
|
-
if (!Object.prototype.propertyIsEnumerable.call(
|
|
569
|
-
value,
|
|
570
|
-
key
|
|
571
|
-
)) {
|
|
572
|
-
continue;
|
|
573
|
-
}
|
|
574
|
-
if (count >= MAX_COLLECTION_ENTRIES) {
|
|
575
|
-
truncated = true;
|
|
576
|
-
break;
|
|
577
|
-
}
|
|
578
|
-
let nestedValue;
|
|
579
|
-
try {
|
|
580
|
-
nestedValue = value[key];
|
|
581
|
-
} catch {
|
|
582
|
-
out[key] = r.mask;
|
|
583
|
-
count += 1;
|
|
584
|
-
continue;
|
|
585
|
-
}
|
|
586
|
-
count += 1;
|
|
587
|
-
out[key] = isSensitiveKey(key, nestedValue, r) ? r.mask : redactValue(
|
|
588
|
-
nestedValue,
|
|
589
|
-
r,
|
|
590
|
-
state,
|
|
591
|
-
depth + 1
|
|
592
|
-
);
|
|
593
|
-
}
|
|
594
|
-
} catch {
|
|
595
|
-
truncated = true;
|
|
596
|
-
}
|
|
597
|
-
if (truncated) {
|
|
598
|
-
out.__redaction_truncated__ = r.mask;
|
|
599
|
-
}
|
|
600
|
-
state.ancestors.delete(value);
|
|
601
|
-
return out;
|
|
602
|
-
}
|
|
603
|
-
var USAGE_TOKEN_KEYS = /* @__PURE__ */ new Set([
|
|
604
|
-
"input_tokens",
|
|
605
|
-
"output_tokens",
|
|
606
|
-
"prompt_tokens",
|
|
607
|
-
"completion_tokens",
|
|
608
|
-
"total_tokens",
|
|
609
|
-
"token_count",
|
|
610
|
-
"gen_ai.usage.input_tokens",
|
|
611
|
-
"gen_ai.usage.output_tokens",
|
|
612
|
-
"gen_ai.usage.prompt_tokens",
|
|
613
|
-
"gen_ai.usage.completion_tokens",
|
|
614
|
-
"gen_ai.usage.total_tokens",
|
|
615
|
-
"gen_ai.usage.token_count"
|
|
616
|
-
]);
|
|
617
|
-
function isSensitiveKey(key, value, r) {
|
|
618
|
-
const lowered = key.toLowerCase();
|
|
619
|
-
if (USAGE_TOKEN_KEYS.has(lowered)) {
|
|
620
|
-
return !(typeof value === "number" && Number.isFinite(value) && value >= 0);
|
|
621
|
-
}
|
|
622
|
-
return r.keyPatterns.some((re) => re.test(lowered));
|
|
623
|
-
}
|
|
624
|
-
function redactAttributes(attributes, options) {
|
|
625
|
-
const r = compile(options);
|
|
626
|
-
return redactWith(attributes, r);
|
|
627
|
-
}
|
|
628
|
-
function redactWith(attributes, r) {
|
|
629
|
-
const out = {};
|
|
630
|
-
if (!attributes) return out;
|
|
631
|
-
const state = {
|
|
632
|
-
ancestors: /* @__PURE__ */ new WeakMap(),
|
|
633
|
-
remainingWork: MAX_REDACTION_WORK
|
|
634
|
-
};
|
|
635
|
-
let count = 0;
|
|
636
|
-
let truncated = false;
|
|
637
|
-
try {
|
|
638
|
-
for (const key in attributes) {
|
|
639
|
-
if (!Object.prototype.propertyIsEnumerable.call(
|
|
640
|
-
attributes,
|
|
641
|
-
key
|
|
642
|
-
)) {
|
|
643
|
-
continue;
|
|
644
|
-
}
|
|
645
|
-
if (count >= MAX_COLLECTION_ENTRIES || state.remainingWork <= 0) {
|
|
646
|
-
truncated = true;
|
|
647
|
-
break;
|
|
648
|
-
}
|
|
649
|
-
let value;
|
|
650
|
-
try {
|
|
651
|
-
value = attributes[key];
|
|
652
|
-
} catch {
|
|
653
|
-
out[key] = r.mask;
|
|
654
|
-
count += 1;
|
|
655
|
-
state.remainingWork -= 1;
|
|
656
|
-
continue;
|
|
657
|
-
}
|
|
658
|
-
count += 1;
|
|
659
|
-
state.remainingWork -= 1;
|
|
660
|
-
if (value === void 0) continue;
|
|
661
|
-
out[key] = isSensitiveKey(key, value, r) ? r.mask : redactValue(value, r, state, 0);
|
|
539
|
+
resource = redactResource(metrics2.resource, redactor);
|
|
540
|
+
} catch (error) {
|
|
541
|
+
resultCallback({ code: ExportResultCode.FAILED, error });
|
|
542
|
+
return;
|
|
662
543
|
}
|
|
663
|
-
|
|
664
|
-
truncated = true;
|
|
544
|
+
this.inner.export({ ...metrics2, resource }, resultCallback);
|
|
665
545
|
}
|
|
666
|
-
|
|
667
|
-
|
|
546
|
+
forceFlush() {
|
|
547
|
+
return this.inner.forceFlush();
|
|
668
548
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
function makeRedactor(options) {
|
|
672
|
-
if (options === false) {
|
|
673
|
-
return (attributes) => ({ ...attributes ?? {} });
|
|
549
|
+
shutdown() {
|
|
550
|
+
return this.inner.shutdown();
|
|
674
551
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
}
|
|
552
|
+
selectAggregationTemporality(instrumentType) {
|
|
553
|
+
return this.inner.selectAggregationTemporality ? this.inner.selectAggregationTemporality(instrumentType) : AggregationTemporality.CUMULATIVE;
|
|
554
|
+
}
|
|
555
|
+
};
|
|
678
556
|
|
|
679
557
|
// src/memory.ts
|
|
680
558
|
import { PerformanceObserver } from "perf_hooks";
|
|
@@ -721,6 +599,383 @@ function startMemoryMetrics() {
|
|
|
721
599
|
};
|
|
722
600
|
}
|
|
723
601
|
|
|
602
|
+
// src/requests.ts
|
|
603
|
+
import { AsyncResource } from "async_hooks";
|
|
604
|
+
import { context, trace } from "@opentelemetry/api";
|
|
605
|
+
var REQUEST_STATE = /* @__PURE__ */ Symbol.for("autter.runtime.request");
|
|
606
|
+
var BOUND = /* @__PURE__ */ Symbol.for("autter.runtime.bound");
|
|
607
|
+
var CAPTURED = /* @__PURE__ */ new WeakSet();
|
|
608
|
+
var capturer = () => {
|
|
609
|
+
};
|
|
610
|
+
function setRequestExceptionCapturer(fn) {
|
|
611
|
+
capturer = fn;
|
|
612
|
+
}
|
|
613
|
+
function markCaptured(error) {
|
|
614
|
+
if (error && typeof error === "object") CAPTURED.add(error);
|
|
615
|
+
}
|
|
616
|
+
function expressRouteOf(req) {
|
|
617
|
+
const props = req;
|
|
618
|
+
const path = props?.route?.path;
|
|
619
|
+
if (typeof path !== "string" || path === "") return null;
|
|
620
|
+
const base = typeof props?.baseUrl === "string" ? props.baseUrl : "";
|
|
621
|
+
const route = base + path;
|
|
622
|
+
return route.startsWith("/") ? route : null;
|
|
623
|
+
}
|
|
624
|
+
function defaultShouldCapture(error) {
|
|
625
|
+
return error instanceof RuntimeError || errorCodeOf(error) !== void 0 || responseStatusOf(error) >= 500;
|
|
626
|
+
}
|
|
627
|
+
function captureAtBoundary(error, state, rule = void 0) {
|
|
628
|
+
const wanted = typeof rule === "function" ? rule(error) : rule === void 0 ? defaultShouldCapture(error) : rule;
|
|
629
|
+
if (!wanted) return;
|
|
630
|
+
if (error && typeof error === "object" && CAPTURED.has(error)) return;
|
|
631
|
+
markCaptured(error);
|
|
632
|
+
try {
|
|
633
|
+
if (state && !state.sealed) local.run(state, () => capturer(error));
|
|
634
|
+
else capturer(error);
|
|
635
|
+
} catch {
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function bindEmitter(emitter, state) {
|
|
639
|
+
const target = emitter;
|
|
640
|
+
if (!target || typeof target !== "object" || target[BOUND]) return;
|
|
641
|
+
target[BOUND] = true;
|
|
642
|
+
const wrappers = /* @__PURE__ */ new WeakMap();
|
|
643
|
+
const wrap = (listener) => {
|
|
644
|
+
if (typeof listener !== "function") return listener;
|
|
645
|
+
let wrapped = wrappers.get(listener);
|
|
646
|
+
if (!wrapped) {
|
|
647
|
+
wrapped = function(...args) {
|
|
648
|
+
return local.run(
|
|
649
|
+
state,
|
|
650
|
+
() => listener.apply(this, args)
|
|
651
|
+
);
|
|
652
|
+
};
|
|
653
|
+
wrappers.set(listener, wrapped);
|
|
654
|
+
}
|
|
655
|
+
return wrapped;
|
|
656
|
+
};
|
|
657
|
+
for (const method of [
|
|
658
|
+
"on",
|
|
659
|
+
"addListener",
|
|
660
|
+
"prependListener",
|
|
661
|
+
"once",
|
|
662
|
+
"prependOnceListener"
|
|
663
|
+
]) {
|
|
664
|
+
const original = target[method];
|
|
665
|
+
if (typeof original !== "function") continue;
|
|
666
|
+
target[method] = function(event, listener) {
|
|
667
|
+
return original.call(this, event, wrap(listener));
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
for (const method of ["removeListener", "off"]) {
|
|
671
|
+
const original = target[method];
|
|
672
|
+
if (typeof original !== "function") continue;
|
|
673
|
+
target[method] = function(event, listener) {
|
|
674
|
+
const wrapped = typeof listener === "function" ? wrappers.get(listener) ?? listener : listener;
|
|
675
|
+
return original.call(this, event, wrapped);
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function exposeHeaderValue(current, header) {
|
|
680
|
+
const text = Array.isArray(current) ? current.join(", ") : String(current ?? "");
|
|
681
|
+
const names = text.split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
|
|
682
|
+
if (names.includes("*") || names.includes(header)) return null;
|
|
683
|
+
return names.length ? `${text}, ${header}` : header;
|
|
684
|
+
}
|
|
685
|
+
function exposeOnCors(res, header) {
|
|
686
|
+
const original = res.writeHead;
|
|
687
|
+
res.writeHead = function(...args) {
|
|
688
|
+
try {
|
|
689
|
+
const inline = args.slice(1).find(
|
|
690
|
+
(arg) => !!arg && typeof arg === "object" && !Array.isArray(arg)
|
|
691
|
+
);
|
|
692
|
+
const inlineKey = (name) => inline ? Object.keys(inline).find((key) => key.toLowerCase() === name) : void 0;
|
|
693
|
+
const cors = res.getHeader("access-control-allow-origin") !== void 0 || inlineKey("access-control-allow-origin") !== void 0;
|
|
694
|
+
if (cors) {
|
|
695
|
+
const key = inlineKey("access-control-expose-headers");
|
|
696
|
+
const value = exposeHeaderValue(
|
|
697
|
+
key && inline ? inline[key] : res.getHeader("access-control-expose-headers"),
|
|
698
|
+
header
|
|
699
|
+
);
|
|
700
|
+
if (value && key && inline) inline[key] = value;
|
|
701
|
+
else if (value) res.setHeader("access-control-expose-headers", value);
|
|
702
|
+
}
|
|
703
|
+
} catch {
|
|
704
|
+
}
|
|
705
|
+
return original.apply(this, args);
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
function pathOf(req) {
|
|
709
|
+
const raw = req.originalUrl ?? req.url ?? "/";
|
|
710
|
+
return raw.split(/[?#]/)[0] || "/";
|
|
711
|
+
}
|
|
712
|
+
function finishRequest(state, input) {
|
|
713
|
+
if (state.sealed) return;
|
|
714
|
+
state.name = (input.name ?? `${input.method} ${input.route}`).slice(0, 200);
|
|
715
|
+
state.outcome = requestOutcome({
|
|
716
|
+
...state.explicitOutcome ? { explicit: state.outcome } : {},
|
|
717
|
+
...state.error !== void 0 ? { error: state.error } : {},
|
|
718
|
+
...state.errorThrown ? { thrown: true } : {},
|
|
719
|
+
status: input.status,
|
|
720
|
+
aborted: input.aborted
|
|
721
|
+
});
|
|
722
|
+
const span = trace.getActiveSpan();
|
|
723
|
+
if (span?.isRecording()) span.setAttribute("autter.operation.outcome", state.outcome);
|
|
724
|
+
if (input.span) {
|
|
725
|
+
const remote = trace.wrapSpanContext(input.span);
|
|
726
|
+
context.with(
|
|
727
|
+
trace.setSpan(context.active(), remote),
|
|
728
|
+
() => reportExplicitFailure(state)
|
|
729
|
+
);
|
|
730
|
+
} else reportExplicitFailure(state);
|
|
731
|
+
emitSummary(state, {
|
|
732
|
+
http: {
|
|
733
|
+
"http.request.method": input.method,
|
|
734
|
+
"http.route": input.route,
|
|
735
|
+
"http.response.status_code": input.status,
|
|
736
|
+
"autter.request.aborted": input.aborted
|
|
737
|
+
},
|
|
738
|
+
...input.span ? { span: input.span } : {}
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
function beginNodeRequest(req, res, options, route) {
|
|
742
|
+
const header = (options.requestIdHeader ?? DEFAULT_REQUEST_ID_HEADER).toLowerCase();
|
|
743
|
+
const requestId = resolveRequestId(
|
|
744
|
+
options.trustRequestId === false ? void 0 : req.headers[header]
|
|
745
|
+
);
|
|
746
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
747
|
+
const path = pathOf(req);
|
|
748
|
+
const state = createRequestState({
|
|
749
|
+
name: `${method} ${normalizeRoutePath(path)}`,
|
|
750
|
+
requestId,
|
|
751
|
+
req,
|
|
752
|
+
res
|
|
753
|
+
});
|
|
754
|
+
req[REQUEST_STATE] = state;
|
|
755
|
+
try {
|
|
756
|
+
if (!res.headersSent) res.setHeader(header, requestId);
|
|
757
|
+
exposeOnCors(res, header);
|
|
758
|
+
} catch {
|
|
759
|
+
}
|
|
760
|
+
const span = trace.getActiveSpan();
|
|
761
|
+
if (span?.isRecording())
|
|
762
|
+
span.setAttributes({
|
|
763
|
+
"autter.request.id": requestId,
|
|
764
|
+
"autter.operation.kind": "request"
|
|
765
|
+
});
|
|
766
|
+
const spanContext = span?.spanContext();
|
|
767
|
+
bindEmitter(req, state);
|
|
768
|
+
bindEmitter(res, state);
|
|
769
|
+
let finished = false;
|
|
770
|
+
const done = () => {
|
|
771
|
+
finishRequest(state, {
|
|
772
|
+
method,
|
|
773
|
+
route: route?.() ?? expressRouteOf(req) ?? normalizeRoutePath(path),
|
|
774
|
+
status: res.statusCode,
|
|
775
|
+
aborted: !finished,
|
|
776
|
+
...spanContext ? { span: spanContext } : {}
|
|
777
|
+
});
|
|
778
|
+
};
|
|
779
|
+
res.once("finish", () => {
|
|
780
|
+
finished = true;
|
|
781
|
+
done();
|
|
782
|
+
});
|
|
783
|
+
res.once("close", done);
|
|
784
|
+
return state;
|
|
785
|
+
}
|
|
786
|
+
function autterRequests(options = {}) {
|
|
787
|
+
const ignored = compileIgnore(options.ignore);
|
|
788
|
+
return function autterRequestsMiddleware(req, res, next) {
|
|
789
|
+
if (req[REQUEST_STATE] || ignored(pathOf(req))) {
|
|
790
|
+
next();
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const state = beginNodeRequest(req, res, options);
|
|
794
|
+
local.run(state, () => next());
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
function autterErrorResponse(options = {}) {
|
|
798
|
+
return function autterErrorResponseMiddleware(error, req, res, next) {
|
|
799
|
+
const state = req[REQUEST_STATE];
|
|
800
|
+
if (state) noteOperationError(error, true, state);
|
|
801
|
+
captureAtBoundary(error, state, options.capture);
|
|
802
|
+
if (res.headersSent) {
|
|
803
|
+
next(error);
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
const header = (options.requestIdHeader ?? DEFAULT_REQUEST_ID_HEADER).toLowerCase();
|
|
807
|
+
const inbound = req.headers?.[header];
|
|
808
|
+
const requestId = state?.requestId ?? (typeof inbound === "string" ? inbound.slice(0, 128) : void 0);
|
|
809
|
+
res.statusCode = responseStatusOf(error);
|
|
810
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
811
|
+
res.end(JSON.stringify(toClientError(error, requestId)));
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
var RESOURCE = /* @__PURE__ */ Symbol.for("autter.runtime.resource");
|
|
815
|
+
function autterFastify(fastify, options, done) {
|
|
816
|
+
const opts = options ?? {};
|
|
817
|
+
const header = (opts.requestIdHeader ?? DEFAULT_REQUEST_ID_HEADER).toLowerCase();
|
|
818
|
+
const ignored = compileIgnore(opts.ignore);
|
|
819
|
+
const stateOf = (request) => request[REQUEST_STATE];
|
|
820
|
+
fastify.addHook("onRequest", ((request, reply, next) => {
|
|
821
|
+
if (ignored(request.url) || request.raw[REQUEST_STATE]) {
|
|
822
|
+
next();
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
const requestId = resolveRequestId(
|
|
826
|
+
opts.trustRequestId === false ? void 0 : request.headers[header]
|
|
827
|
+
);
|
|
828
|
+
const method = String(request.method ?? "GET").toUpperCase();
|
|
829
|
+
const state = createRequestState({
|
|
830
|
+
name: `${method} ${normalizeRoutePath(request.url)}`,
|
|
831
|
+
requestId,
|
|
832
|
+
req: request.raw,
|
|
833
|
+
res: reply.raw
|
|
834
|
+
});
|
|
835
|
+
request[REQUEST_STATE] = state;
|
|
836
|
+
request.raw[REQUEST_STATE] = state;
|
|
837
|
+
reply.header(header, requestId);
|
|
838
|
+
const span = trace.getActiveSpan();
|
|
839
|
+
if (span?.isRecording())
|
|
840
|
+
span.setAttributes({
|
|
841
|
+
"autter.request.id": requestId,
|
|
842
|
+
"autter.operation.kind": "request"
|
|
843
|
+
});
|
|
844
|
+
const spanContext = span?.spanContext();
|
|
845
|
+
bindEmitter(request.raw, state);
|
|
846
|
+
bindEmitter(reply.raw, state);
|
|
847
|
+
let finished = false;
|
|
848
|
+
const finish = (aborted) => finishRequest(state, {
|
|
849
|
+
method,
|
|
850
|
+
route: request.routeOptions?.url ?? request.routerPath ?? normalizeRoutePath(request.url),
|
|
851
|
+
status: reply.raw.statusCode || reply.statusCode,
|
|
852
|
+
aborted,
|
|
853
|
+
...spanContext ? { span: spanContext } : {}
|
|
854
|
+
});
|
|
855
|
+
reply.raw.once("finish", () => {
|
|
856
|
+
finished = true;
|
|
857
|
+
finish(false);
|
|
858
|
+
});
|
|
859
|
+
reply.raw.once("close", () => finish(!finished));
|
|
860
|
+
local.run(state, () => {
|
|
861
|
+
request[RESOURCE] = new AsyncResource("autter-request");
|
|
862
|
+
next();
|
|
863
|
+
});
|
|
864
|
+
}));
|
|
865
|
+
fastify.addHook("preHandler", ((request, _reply, next) => {
|
|
866
|
+
const resource = request[RESOURCE];
|
|
867
|
+
if (resource) resource.runInAsyncScope(() => next());
|
|
868
|
+
else next();
|
|
869
|
+
}));
|
|
870
|
+
fastify.addHook("onSend", ((request, reply, payload, next) => {
|
|
871
|
+
try {
|
|
872
|
+
if (stateOf(request) && reply.getHeader("access-control-allow-origin") !== void 0) {
|
|
873
|
+
const value = exposeHeaderValue(reply.getHeader("access-control-expose-headers"), header);
|
|
874
|
+
if (value) reply.header("access-control-expose-headers", value);
|
|
875
|
+
}
|
|
876
|
+
} catch {
|
|
877
|
+
}
|
|
878
|
+
next(null, payload);
|
|
879
|
+
}));
|
|
880
|
+
fastify.addHook("onError", ((request, _reply, error, next) => {
|
|
881
|
+
const state = stateOf(request);
|
|
882
|
+
if (state) noteOperationError(error, true, state);
|
|
883
|
+
captureAtBoundary(error, state);
|
|
884
|
+
next();
|
|
885
|
+
}));
|
|
886
|
+
done();
|
|
887
|
+
}
|
|
888
|
+
autterFastify[/* @__PURE__ */ Symbol.for("skip-override")] = true;
|
|
889
|
+
autterFastify[/* @__PURE__ */ Symbol.for("fastify.display-name")] = "autter-runtime";
|
|
890
|
+
function withHeaders(response, header, requestId) {
|
|
891
|
+
const apply = (target) => {
|
|
892
|
+
target.headers.set(header, requestId);
|
|
893
|
+
if (target.headers.get("access-control-allow-origin") !== null) {
|
|
894
|
+
const value = exposeHeaderValue(
|
|
895
|
+
target.headers.get("access-control-expose-headers") ?? "",
|
|
896
|
+
header
|
|
897
|
+
);
|
|
898
|
+
if (value) target.headers.set("access-control-expose-headers", value);
|
|
899
|
+
}
|
|
900
|
+
return target;
|
|
901
|
+
};
|
|
902
|
+
try {
|
|
903
|
+
return apply(response);
|
|
904
|
+
} catch {
|
|
905
|
+
try {
|
|
906
|
+
return apply(new Response(response.body, response));
|
|
907
|
+
} catch {
|
|
908
|
+
return response;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
function withRuntimeRequest(handler, options = {}) {
|
|
913
|
+
const header = (options.requestIdHeader ?? DEFAULT_REQUEST_ID_HEADER).toLowerCase();
|
|
914
|
+
const ignored = compileIgnore(options.ignore);
|
|
915
|
+
return async (request, ...rest) => {
|
|
916
|
+
let pathname = "/";
|
|
917
|
+
try {
|
|
918
|
+
pathname = new URL(request.url, "http://localhost").pathname;
|
|
919
|
+
} catch {
|
|
920
|
+
}
|
|
921
|
+
if (ignored(pathname)) return handler(request, ...rest);
|
|
922
|
+
const requestId = resolveRequestId(
|
|
923
|
+
options.trustRequestId === false ? void 0 : request.headers.get(header)
|
|
924
|
+
);
|
|
925
|
+
const method = String(request.method ?? "GET").toUpperCase();
|
|
926
|
+
const route = options.route ?? normalizeRoutePath(pathname);
|
|
927
|
+
const state = createRequestState({
|
|
928
|
+
name: `${method} ${options.name ?? route}`,
|
|
929
|
+
requestId,
|
|
930
|
+
req: request
|
|
931
|
+
});
|
|
932
|
+
const span = trace.getActiveSpan();
|
|
933
|
+
if (span?.isRecording())
|
|
934
|
+
span.setAttributes({
|
|
935
|
+
"autter.request.id": requestId,
|
|
936
|
+
"autter.operation.kind": "request"
|
|
937
|
+
});
|
|
938
|
+
const spanContext = span?.spanContext();
|
|
939
|
+
let status = 500;
|
|
940
|
+
try {
|
|
941
|
+
const response = await local.run(state, async () => {
|
|
942
|
+
try {
|
|
943
|
+
return await handler(request, ...rest);
|
|
944
|
+
} catch (error) {
|
|
945
|
+
noteOperationError(error, true, state);
|
|
946
|
+
captureAtBoundary(error, state, true);
|
|
947
|
+
throw error;
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
status = response.status;
|
|
951
|
+
return withHeaders(response, header, requestId);
|
|
952
|
+
} finally {
|
|
953
|
+
const aborted = request.signal?.aborted === true;
|
|
954
|
+
finishRequest(state, {
|
|
955
|
+
method,
|
|
956
|
+
route,
|
|
957
|
+
name: `${method} ${options.name ?? route}`,
|
|
958
|
+
status,
|
|
959
|
+
aborted,
|
|
960
|
+
...spanContext ? { span: spanContext } : {}
|
|
961
|
+
});
|
|
962
|
+
if (options.waitUntil) {
|
|
963
|
+
try {
|
|
964
|
+
options.waitUntil(flushRuntimeLogs().catch(() => {
|
|
965
|
+
}));
|
|
966
|
+
} catch {
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
function startHookRequest(res) {
|
|
973
|
+
const req = res.req;
|
|
974
|
+
if (!req || req[REQUEST_STATE]) return;
|
|
975
|
+
const state = beginNodeRequest(req, res, {});
|
|
976
|
+
local.enterWith(state);
|
|
977
|
+
}
|
|
978
|
+
|
|
724
979
|
// src/server.ts
|
|
725
980
|
var LLM_MARKER_ATTRIBUTES = [
|
|
726
981
|
"gen_ai.operation.name",
|
|
@@ -849,7 +1104,7 @@ var ErrorTraceRetentionProcessor = class {
|
|
|
849
1104
|
}
|
|
850
1105
|
/** Promote the active trace, if any — called on captured errors. */
|
|
851
1106
|
retainActiveTrace() {
|
|
852
|
-
const active2 =
|
|
1107
|
+
const active2 = trace2.getActiveSpan()?.spanContext();
|
|
853
1108
|
if (!active2 || (active2.traceFlags & TraceFlags.SAMPLED) !== 0) return;
|
|
854
1109
|
const entry = this.entryFor(active2.traceId);
|
|
855
1110
|
if (entry.retained) return;
|
|
@@ -942,36 +1197,43 @@ async function runLlmSpan(tracer, info, fn) {
|
|
|
942
1197
|
kind: SpanKind.CLIENT,
|
|
943
1198
|
attributes: llmBaseAttributes(info)
|
|
944
1199
|
});
|
|
945
|
-
const
|
|
1200
|
+
const usage = {};
|
|
1201
|
+
let responseModel;
|
|
1202
|
+
const setResponseModel = (model) => {
|
|
1203
|
+
responseModel = model;
|
|
1204
|
+
span.setAttribute("gen_ai.response.model", model);
|
|
1205
|
+
};
|
|
946
1206
|
const handle = {
|
|
947
|
-
setUsage: (
|
|
948
|
-
|
|
1207
|
+
setUsage: (next) => {
|
|
1208
|
+
Object.assign(usage, next);
|
|
1209
|
+
span.setAttributes(usageAttributes(next));
|
|
1210
|
+
},
|
|
1211
|
+
setCost: (usd) => {
|
|
1212
|
+
usage.costUsd = usd;
|
|
1213
|
+
span.setAttribute("autter.llm.cost_usd", usd);
|
|
1214
|
+
},
|
|
949
1215
|
setResponseModel,
|
|
950
1216
|
setModel: setResponseModel,
|
|
951
|
-
setAttributes: (attributes) => span.setAttributes(attributes)
|
|
1217
|
+
setAttributes: (attributes) => span.setAttributes(activeRedactor(attributes))
|
|
952
1218
|
};
|
|
953
1219
|
try {
|
|
954
|
-
const result = await
|
|
955
|
-
|
|
1220
|
+
const result = await context2.with(
|
|
1221
|
+
trace2.setSpan(context2.active(), span),
|
|
956
1222
|
() => fn(handle)
|
|
957
1223
|
);
|
|
958
1224
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
959
1225
|
return result;
|
|
960
1226
|
} catch (err) {
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
span.setAttribute("error.type", err.name);
|
|
964
|
-
}
|
|
965
|
-
span.setStatus({
|
|
966
|
-
code: SpanStatusCode.ERROR,
|
|
967
|
-
message: err instanceof Error ? err.message : String(err)
|
|
968
|
-
});
|
|
1227
|
+
recordError(span, err);
|
|
1228
|
+
if (err instanceof Error) span.setAttribute("error.type", err.name);
|
|
969
1229
|
throw err;
|
|
970
1230
|
} finally {
|
|
971
1231
|
span.end();
|
|
1232
|
+
recordOperationAiUsage({ ...usage, model: responseModel ?? info.model });
|
|
972
1233
|
}
|
|
973
1234
|
}
|
|
974
1235
|
function recordLlmCall(tracer, call) {
|
|
1236
|
+
recordOperationAiUsage({ ...call, model: call.model });
|
|
975
1237
|
const durationMs = Math.max(0, call.durationMs ?? 0);
|
|
976
1238
|
const endTime = /* @__PURE__ */ new Date();
|
|
977
1239
|
const startTime = new Date(endTime.getTime() - durationMs);
|
|
@@ -984,50 +1246,52 @@ function recordLlmCall(tracer, call) {
|
|
|
984
1246
|
}
|
|
985
1247
|
);
|
|
986
1248
|
if (call.error !== void 0 && call.error !== null) {
|
|
1249
|
+
recordError(span, call.error);
|
|
987
1250
|
if (call.error instanceof Error) {
|
|
988
|
-
span.recordException(call.error);
|
|
989
1251
|
span.setAttribute("error.type", call.error.name);
|
|
990
1252
|
}
|
|
991
|
-
span.setStatus({
|
|
992
|
-
code: SpanStatusCode.ERROR,
|
|
993
|
-
message: call.error instanceof Error ? call.error.message : String(call.error)
|
|
994
|
-
});
|
|
995
1253
|
} else {
|
|
996
1254
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
997
1255
|
}
|
|
998
1256
|
span.end(endTime);
|
|
999
1257
|
}
|
|
1000
|
-
async function runWithSpan(tracer, name, fn, attributes) {
|
|
1001
|
-
const span = tracer.startSpan(name, {
|
|
1258
|
+
async function runWithSpan(tracer, name, fn, attributes, links) {
|
|
1259
|
+
const span = tracer.startSpan(name, {
|
|
1260
|
+
attributes,
|
|
1261
|
+
...links?.length ? { links } : {}
|
|
1262
|
+
});
|
|
1002
1263
|
try {
|
|
1003
|
-
const result = await
|
|
1004
|
-
|
|
1264
|
+
const result = await context2.with(
|
|
1265
|
+
trace2.setSpan(context2.active(), span),
|
|
1005
1266
|
fn
|
|
1006
1267
|
);
|
|
1007
1268
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
1008
1269
|
return result;
|
|
1009
1270
|
} catch (err) {
|
|
1010
|
-
|
|
1011
|
-
span.setStatus({
|
|
1012
|
-
code: SpanStatusCode.ERROR,
|
|
1013
|
-
message: err instanceof Error ? err.message : String(err)
|
|
1014
|
-
});
|
|
1271
|
+
recordError(span, err);
|
|
1015
1272
|
throw err;
|
|
1016
1273
|
} finally {
|
|
1017
1274
|
span.end();
|
|
1018
1275
|
}
|
|
1019
1276
|
}
|
|
1020
|
-
function
|
|
1021
|
-
const
|
|
1022
|
-
const
|
|
1023
|
-
if (
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1277
|
+
function recordError(span, err) {
|
|
1278
|
+
const isError = err instanceof Error;
|
|
1279
|
+
const message = activeRedactor.text(isError ? err.message : String(err));
|
|
1280
|
+
if (isError) {
|
|
1281
|
+
span.addEvent("exception", {
|
|
1282
|
+
"exception.type": err.name,
|
|
1283
|
+
"exception.message": message,
|
|
1284
|
+
...err.stack ? { "exception.stacktrace": activeRedactor.text(err.stack) } : {}
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1287
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
1288
|
+
}
|
|
1289
|
+
function redactWithActive(attributes) {
|
|
1290
|
+
return activeRedactor(attributes);
|
|
1027
1291
|
}
|
|
1028
1292
|
function captureExpressRoute(_span, response) {
|
|
1029
1293
|
if (!(response instanceof ServerResponse)) return;
|
|
1030
|
-
const rpcMetadata = getRPCMetadata(
|
|
1294
|
+
const rpcMetadata = getRPCMetadata(context2.active());
|
|
1031
1295
|
if (rpcMetadata?.type !== RPCType.HTTP) return;
|
|
1032
1296
|
const setRoute = () => {
|
|
1033
1297
|
if (rpcMetadata.route) return;
|
|
@@ -1036,6 +1300,21 @@ function captureExpressRoute(_span, response) {
|
|
|
1036
1300
|
};
|
|
1037
1301
|
response.prependListener("finish", setRoute);
|
|
1038
1302
|
response.prependListener("close", setRoute);
|
|
1303
|
+
if (hookRequests) startHookRequest(response);
|
|
1304
|
+
}
|
|
1305
|
+
var hookRequests = false;
|
|
1306
|
+
function structuredErrorAttributes(error) {
|
|
1307
|
+
const attrs = { ...errorAttributes(error, { causes: false }) };
|
|
1308
|
+
const internal = errorInternal(error);
|
|
1309
|
+
if (internal) {
|
|
1310
|
+
try {
|
|
1311
|
+
attrs["autter.error.internal"] = JSON.stringify(
|
|
1312
|
+
activeRedactor(internal)
|
|
1313
|
+
).slice(0, 4e3);
|
|
1314
|
+
} catch {
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
return attrs;
|
|
1039
1318
|
}
|
|
1040
1319
|
var active = null;
|
|
1041
1320
|
var activeAlwaysOnProvider = null;
|
|
@@ -1047,15 +1326,27 @@ function initAutterServer(options) {
|
|
|
1047
1326
|
""
|
|
1048
1327
|
);
|
|
1049
1328
|
const headers = { authorization: `Bearer ${options.apiKey}` };
|
|
1329
|
+
const traceExporter = () => new CountingExporter(
|
|
1330
|
+
new RedactingSpanExporter(
|
|
1331
|
+
new OTLPTraceExporter({ url: `${endpoint}/v1/traces`, headers }),
|
|
1332
|
+
() => activeRedactor
|
|
1333
|
+
)
|
|
1334
|
+
);
|
|
1050
1335
|
const environment = options.environment ?? process.env.NODE_ENV ?? "production";
|
|
1051
1336
|
if (isDebugEnabled() || options.debug === true) setDebugMode(true);
|
|
1052
1337
|
debugLog(`initialising service=${options.service} endpoint=${endpoint}`);
|
|
1053
1338
|
activeRedactor = makeRedactor(options.redactAttributes ?? true);
|
|
1054
|
-
const
|
|
1339
|
+
const distro = options.distro ?? SDK_IDENTITY;
|
|
1340
|
+
configureCompatCheck({ endpoint, enabled: options.compatCheck !== false, sdk: distro });
|
|
1341
|
+
const resource = new Resource2({
|
|
1055
1342
|
[ATTR_SERVICE_NAME]: options.service,
|
|
1056
1343
|
"service.instance.id": (options.instanceId || process.env.AUTTER_RUNTIME_INSTANCE_ID || randomUUID()).slice(0, 128),
|
|
1057
1344
|
...options.release ? { [ATTR_SERVICE_VERSION]: options.release } : {},
|
|
1058
1345
|
"deployment.environment": environment,
|
|
1346
|
+
// OTel semconv for SDK distributions: the ingester records which
|
|
1347
|
+
// Autter SDK version each service runs (version compatibility checks).
|
|
1348
|
+
"telemetry.distro.name": distro.name,
|
|
1349
|
+
"telemetry.distro.version": distro.version,
|
|
1059
1350
|
// Tells the ingester request metrics arrive on the metrics pipe, so
|
|
1060
1351
|
// it must not also fold our server spans into usage rollups (that
|
|
1061
1352
|
// would double-count every sampled request).
|
|
@@ -1070,30 +1361,24 @@ function initAutterServer(options) {
|
|
|
1070
1361
|
let sampler = retainTraces ? new RecordUnsampledSampler(headSampler) : headSampler;
|
|
1071
1362
|
if (options.llmTracing !== false) sampler = new LlmAwareSampler(sampler);
|
|
1072
1363
|
const errorTraceBuffer = retainTraces ? new ErrorTraceRetentionProcessor(
|
|
1073
|
-
new BatchSpanProcessor(
|
|
1074
|
-
new CountingExporter(
|
|
1075
|
-
new OTLPTraceExporter({ url: `${endpoint}/v1/traces`, headers })
|
|
1076
|
-
),
|
|
1077
|
-
{ scheduledDelayMillis: 2e3 }
|
|
1078
|
-
),
|
|
1364
|
+
new BatchSpanProcessor(traceExporter(), { scheduledDelayMillis: 2e3 }),
|
|
1079
1365
|
retainOnError,
|
|
1080
1366
|
slowThresholdMs
|
|
1081
1367
|
) : null;
|
|
1082
|
-
const mainSpanProcessor = new BatchSpanProcessor(
|
|
1083
|
-
new CountingExporter(
|
|
1084
|
-
new OTLPTraceExporter({ url: `${endpoint}/v1/traces`, headers })
|
|
1085
|
-
)
|
|
1086
|
-
);
|
|
1368
|
+
const mainSpanProcessor = new BatchSpanProcessor(traceExporter());
|
|
1087
1369
|
const metricReader = new PeriodicExportingMetricReader({
|
|
1088
|
-
exporter: new
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1370
|
+
exporter: new RedactingMetricExporter(
|
|
1371
|
+
new OTLPMetricExporter({
|
|
1372
|
+
url: `${endpoint}/v1/metrics`,
|
|
1373
|
+
headers,
|
|
1374
|
+
// Deltas, not lifetime totals: the ingester SUMs data points
|
|
1375
|
+
// into runtime_metrics_1m, and the default (cumulative)
|
|
1376
|
+
// temporality would re-count every past request on each
|
|
1377
|
+
// 60 s export.
|
|
1378
|
+
temporalityPreference: AggregationTemporalityPreference.DELTA
|
|
1379
|
+
}),
|
|
1380
|
+
() => activeRedactor
|
|
1381
|
+
),
|
|
1097
1382
|
exportIntervalMillis: options.metricIntervalMs ?? 6e4
|
|
1098
1383
|
});
|
|
1099
1384
|
const sdk = new NodeSDK({
|
|
@@ -1119,16 +1404,14 @@ function initAutterServer(options) {
|
|
|
1119
1404
|
});
|
|
1120
1405
|
sdk.start();
|
|
1121
1406
|
const stopMemoryMetrics = options.memoryMetrics === false ? null : startMemoryMetrics();
|
|
1407
|
+
noteCompatFeature("endpoint_latency");
|
|
1408
|
+
if (stopMemoryMetrics) noteCompatFeature("memory_metrics");
|
|
1409
|
+
if (options.llmTracing !== false) noteCompatFeature("llm_calls");
|
|
1122
1410
|
const alwaysOnProvider = new BasicTracerProvider({
|
|
1123
1411
|
resource,
|
|
1124
1412
|
sampler: new AlwaysOnSampler(),
|
|
1125
1413
|
spanProcessors: [
|
|
1126
|
-
new BatchSpanProcessor(
|
|
1127
|
-
new CountingExporter(
|
|
1128
|
-
new OTLPTraceExporter({ url: `${endpoint}/v1/traces`, headers })
|
|
1129
|
-
),
|
|
1130
|
-
{ scheduledDelayMillis: 2e3 }
|
|
1131
|
-
)
|
|
1414
|
+
new BatchSpanProcessor(traceExporter(), { scheduledDelayMillis: 2e3 })
|
|
1132
1415
|
]
|
|
1133
1416
|
});
|
|
1134
1417
|
const errorTracer = alwaysOnProvider.getTracer("autter-errors");
|
|
@@ -1138,31 +1421,43 @@ function initAutterServer(options) {
|
|
|
1138
1421
|
errorTraceBuffer?.retainActiveTrace();
|
|
1139
1422
|
telemetryStats.markCaptured();
|
|
1140
1423
|
const isError = error instanceof Error;
|
|
1141
|
-
const message = isError ? error.message : String(error);
|
|
1424
|
+
const message = activeRedactor.text(isError ? error.message : String(error));
|
|
1425
|
+
const stackText = isError && error.stack ? activeRedactor.text(error.stack) : "";
|
|
1142
1426
|
const redacted = activeRedactor(attributes);
|
|
1143
|
-
const activeSpan =
|
|
1427
|
+
const activeSpan = trace2.getActiveSpan();
|
|
1144
1428
|
const reuseActive = activeSpan?.isRecording() === true;
|
|
1145
1429
|
const span = reuseActive ? activeSpan : errorTracer.startSpan(isError ? error.name : "Error");
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1430
|
+
const structured = structuredErrorAttributes(error);
|
|
1431
|
+
const causes = errorAttributes(error);
|
|
1432
|
+
const declared = Object.fromEntries(
|
|
1433
|
+
Object.entries(causes).filter(([key]) => key !== "autter.error.internal")
|
|
1434
|
+
);
|
|
1435
|
+
const requestId = currentOperation()?.requestId;
|
|
1436
|
+
span.setAttributes({
|
|
1437
|
+
"autter.severity": "error",
|
|
1438
|
+
...redacted,
|
|
1439
|
+
...structured,
|
|
1440
|
+
...requestId ? { "autter.request.id": requestId } : {}
|
|
1441
|
+
});
|
|
1442
|
+
markCaptured(error);
|
|
1443
|
+
noteOperationError(error);
|
|
1444
|
+
notifyException(error, redacted);
|
|
1445
|
+
if (isError && stackText) {
|
|
1446
|
+
span.addEvent("exception", {
|
|
1447
|
+
"exception.type": error.name,
|
|
1448
|
+
"exception.message": message,
|
|
1449
|
+
"exception.stacktrace": stackText,
|
|
1450
|
+
...redacted["autter.handled"] === true ? { "autter.handled": true, "autter.sampled": redacted["autter.sampled"] === true } : {},
|
|
1451
|
+
...declared
|
|
1452
|
+
});
|
|
1159
1453
|
} else {
|
|
1160
1454
|
const stack = new Error().stack?.split("\n").filter((line, i) => i === 0 || !line.includes("captureException")).join("\n");
|
|
1161
1455
|
span.addEvent("exception", {
|
|
1162
1456
|
"exception.type": isError ? error.name : "Error",
|
|
1163
1457
|
"exception.message": message,
|
|
1164
|
-
...stack ? { "exception.stacktrace": stack } : {},
|
|
1165
|
-
...redacted["autter.handled"] === true ? { "autter.handled": true, "autter.sampled": redacted["autter.sampled"] === true } : {}
|
|
1458
|
+
...stack ? { "exception.stacktrace": activeRedactor.text(stack) } : {},
|
|
1459
|
+
...redacted["autter.handled"] === true ? { "autter.handled": true, "autter.sampled": redacted["autter.sampled"] === true } : {},
|
|
1460
|
+
...declared
|
|
1166
1461
|
});
|
|
1167
1462
|
}
|
|
1168
1463
|
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
@@ -1174,22 +1469,23 @@ function initAutterServer(options) {
|
|
|
1174
1469
|
}
|
|
1175
1470
|
telemetryStats.markCaptured();
|
|
1176
1471
|
const stack = new Error().stack?.split("\n").filter((line, i) => i === 0 || !line.includes("captureMessage")).join("\n");
|
|
1472
|
+
const safeMessage = activeRedactor.text(message);
|
|
1177
1473
|
const span = errorTracer.startSpan("Message", {
|
|
1178
1474
|
attributes: { "autter.severity": severity, ...activeRedactor(attributes) }
|
|
1179
1475
|
});
|
|
1180
1476
|
span.addEvent("exception", {
|
|
1181
1477
|
"exception.type": "Message",
|
|
1182
|
-
"exception.message":
|
|
1183
|
-
...stack ? { "exception.stacktrace": stack } : {},
|
|
1478
|
+
"exception.message": safeMessage,
|
|
1479
|
+
...stack ? { "exception.stacktrace": activeRedactor.text(stack) } : {},
|
|
1184
1480
|
"autter.severity": severity
|
|
1185
1481
|
});
|
|
1186
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
1482
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
|
|
1187
1483
|
span.end();
|
|
1188
1484
|
}
|
|
1189
1485
|
function reportOutcome2(name, message, attributes) {
|
|
1190
1486
|
errorTraceBuffer?.retainActiveTrace();
|
|
1191
1487
|
telemetryStats.markCaptured();
|
|
1192
|
-
const activeSpan =
|
|
1488
|
+
const activeSpan = trace2.getActiveSpan();
|
|
1193
1489
|
const reuseActive = activeSpan?.isRecording() === true;
|
|
1194
1490
|
const span = reuseActive ? activeSpan : errorTracer.startSpan("OutcomeFailure");
|
|
1195
1491
|
const safeOutcome = activeRedactor({ "autter.outcome.name": name.slice(0, 200), "autter.outcome.message": message.slice(0, 1e3) });
|
|
@@ -1204,6 +1500,19 @@ function initAutterServer(options) {
|
|
|
1204
1500
|
span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
|
|
1205
1501
|
if (!reuseActive) span.end();
|
|
1206
1502
|
}
|
|
1503
|
+
configureRuntimeLogger({
|
|
1504
|
+
endpoint,
|
|
1505
|
+
apiKey: options.apiKey,
|
|
1506
|
+
service: options.service,
|
|
1507
|
+
environment,
|
|
1508
|
+
distro,
|
|
1509
|
+
release: options.release,
|
|
1510
|
+
options: options.logging,
|
|
1511
|
+
redact: activeRedactor,
|
|
1512
|
+
run: (name, fn, attributes, links) => runWithSpan(processTracer, name, fn, activeRedactor(attributes), links),
|
|
1513
|
+
reportOutcome: reportOutcome2
|
|
1514
|
+
});
|
|
1515
|
+
hookRequests = options.logging?.requests === true;
|
|
1207
1516
|
if (options.captureGlobalErrors !== false) {
|
|
1208
1517
|
process.on("uncaughtExceptionMonitor", (err) => {
|
|
1209
1518
|
captureException2(err, { "autter.unhandled": true });
|
|
@@ -1214,6 +1523,7 @@ function initAutterServer(options) {
|
|
|
1214
1523
|
const flushTarget = {
|
|
1215
1524
|
forceFlush: async () => {
|
|
1216
1525
|
const results = await Promise.allSettled([
|
|
1526
|
+
flushRuntimeLogs(),
|
|
1217
1527
|
Promise.resolve().then(() => alwaysOnProvider.forceFlush()),
|
|
1218
1528
|
Promise.resolve().then(() => mainSpanProcessor.forceFlush()),
|
|
1219
1529
|
...errorTraceBuffer ? [Promise.resolve().then(() => errorTraceBuffer.forceFlush())] : [],
|
|
@@ -1240,6 +1550,8 @@ function initAutterServer(options) {
|
|
|
1240
1550
|
withLlmCall: (info, fn) => runLlmSpan(llmTracer, info, fn),
|
|
1241
1551
|
trackLlmCall: (call) => recordLlmCall(llmTracer, call),
|
|
1242
1552
|
shutdown: async () => {
|
|
1553
|
+
const logShutdown = await Promise.allSettled([shutdownRuntimeLogger()]);
|
|
1554
|
+
hookRequests = false;
|
|
1243
1555
|
stopMemoryMetrics?.();
|
|
1244
1556
|
active = null;
|
|
1245
1557
|
activeAlwaysOnProvider = null;
|
|
@@ -1248,6 +1560,7 @@ function initAutterServer(options) {
|
|
|
1248
1560
|
unregisterFlushTargets();
|
|
1249
1561
|
telemetryStats.markAllFlushed();
|
|
1250
1562
|
await Promise.allSettled([alwaysOnProvider.shutdown(), sdk.shutdown()]);
|
|
1563
|
+
if (logShutdown[0]?.status === "rejected") throw logShutdown[0].reason;
|
|
1251
1564
|
}
|
|
1252
1565
|
};
|
|
1253
1566
|
active = server;
|
|
@@ -1259,48 +1572,67 @@ function captureException(error, attributes) {
|
|
|
1259
1572
|
active.captureException(error, attributes);
|
|
1260
1573
|
return;
|
|
1261
1574
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1575
|
+
if (loggerOnlyCapture) {
|
|
1576
|
+
loggerOnlyCapture(error, attributes);
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
markCaptured(error);
|
|
1580
|
+
noteOperationError(error);
|
|
1581
|
+
notifyException(error, attributes);
|
|
1582
|
+
const span = trace2.getTracer("autter-errors").startSpan(error instanceof Error ? error.name : "Error", {
|
|
1583
|
+
attributes: activeRedactor(attributes)
|
|
1267
1584
|
});
|
|
1585
|
+
recordError(span, error);
|
|
1268
1586
|
span.end();
|
|
1269
1587
|
}
|
|
1588
|
+
var loggerOnlyCapture = null;
|
|
1589
|
+
function setLoggerOnlyCapture(fn) {
|
|
1590
|
+
loggerOnlyCapture = fn;
|
|
1591
|
+
}
|
|
1592
|
+
function serverActive() {
|
|
1593
|
+
return active !== null;
|
|
1594
|
+
}
|
|
1595
|
+
setRequestExceptionCapturer((error) => captureException(error));
|
|
1270
1596
|
function withProcessSpan(name, fn, attributes) {
|
|
1271
1597
|
if (active) return active.withProcessSpan(name, fn, attributes);
|
|
1272
|
-
return runWithSpan(
|
|
1598
|
+
return runWithSpan(
|
|
1599
|
+
trace2.getTracer("autter-processes"),
|
|
1600
|
+
name,
|
|
1601
|
+
fn,
|
|
1602
|
+
activeRedactor(attributes)
|
|
1603
|
+
);
|
|
1273
1604
|
}
|
|
1274
1605
|
function withLlmCall(info, fn) {
|
|
1275
1606
|
if (active) return active.withLlmCall(info, fn);
|
|
1276
|
-
return runLlmSpan(
|
|
1607
|
+
return runLlmSpan(trace2.getTracer("autter-llm"), info, fn);
|
|
1277
1608
|
}
|
|
1278
1609
|
function trackLlmCall(call) {
|
|
1279
1610
|
if (active) {
|
|
1280
1611
|
active.trackLlmCall(call);
|
|
1281
1612
|
return;
|
|
1282
1613
|
}
|
|
1283
|
-
recordLlmCall(
|
|
1614
|
+
recordLlmCall(trace2.getTracer("autter-llm"), call);
|
|
1284
1615
|
}
|
|
1285
1616
|
function captureMessage(message, severity = "warning", attributes) {
|
|
1286
1617
|
if (active) {
|
|
1287
1618
|
active.captureMessage(message, severity, attributes);
|
|
1288
1619
|
return;
|
|
1289
1620
|
}
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1621
|
+
const safeMessage = activeRedactor.text(message);
|
|
1622
|
+
const span = trace2.getTracer("autter-errors").startSpan("Message", {
|
|
1623
|
+
attributes: { "autter.severity": severity, ...activeRedactor(attributes) }
|
|
1292
1624
|
});
|
|
1293
1625
|
span.addEvent("exception", {
|
|
1294
1626
|
"exception.type": "Message",
|
|
1295
|
-
"exception.message":
|
|
1627
|
+
"exception.message": safeMessage,
|
|
1296
1628
|
"autter.severity": severity
|
|
1297
1629
|
});
|
|
1298
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
1630
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
|
|
1299
1631
|
span.end();
|
|
1300
1632
|
}
|
|
1301
1633
|
function reportOutcome(name, message, attributes) {
|
|
1302
1634
|
if (active) return active.reportOutcome(name, message, attributes);
|
|
1303
|
-
const span =
|
|
1635
|
+
const span = trace2.getTracer("autter-outcomes").startSpan("OutcomeFailure");
|
|
1304
1636
|
const safeOutcome = redactAttributes({ "autter.outcome.name": name.slice(0, 200), "autter.outcome.message": message.slice(0, 1e3) });
|
|
1305
1637
|
const safeName = String(safeOutcome["autter.outcome.name"] ?? "operation");
|
|
1306
1638
|
const safeMessage = String(safeOutcome["autter.outcome.message"] ?? "failed");
|
|
@@ -1321,7 +1653,7 @@ function makeSafeCapture(options) {
|
|
|
1321
1653
|
};
|
|
1322
1654
|
}
|
|
1323
1655
|
function alwaysOnTracer(name) {
|
|
1324
|
-
return activeAlwaysOnProvider ? activeAlwaysOnProvider.getTracer(name) :
|
|
1656
|
+
return activeAlwaysOnProvider ? activeAlwaysOnProvider.getTracer(name) : trace2.getTracer(name);
|
|
1325
1657
|
}
|
|
1326
1658
|
function autterLlmTracer() {
|
|
1327
1659
|
return alwaysOnTracer("autter-llm");
|
|
@@ -1341,7 +1673,7 @@ async function emitLlmSelftestTrace() {
|
|
|
1341
1673
|
attributes: { "autter.selftest": true }
|
|
1342
1674
|
},
|
|
1343
1675
|
(llm) => {
|
|
1344
|
-
traceId =
|
|
1676
|
+
traceId = trace2.getActiveSpan()?.spanContext().traceId ?? "";
|
|
1345
1677
|
llm.setUsage({ inputTokens: 1, outputTokens: 1 });
|
|
1346
1678
|
llm.setCost(0);
|
|
1347
1679
|
}
|
|
@@ -1389,15 +1721,26 @@ function extractUsage(result) {
|
|
|
1389
1721
|
const r = result;
|
|
1390
1722
|
const usage = r?.usage ?? r?.message?.usage;
|
|
1391
1723
|
const meta = r?.usageMetadata;
|
|
1724
|
+
const details = usage?.prompt_tokens_details;
|
|
1725
|
+
const cached = usage?.cache_read_input_tokens ?? (typeof details?.cached_tokens === "number" ? details.cached_tokens : void 0) ?? meta?.cachedContentTokenCount;
|
|
1392
1726
|
const input = usage?.prompt_tokens ?? usage?.input_tokens ?? meta?.promptTokenCount ?? void 0;
|
|
1393
1727
|
const output = usage?.completion_tokens ?? usage?.output_tokens ?? meta?.candidatesTokenCount ?? void 0;
|
|
1394
1728
|
const responseModel = r?.model ?? r?.modelVersion;
|
|
1395
1729
|
return {
|
|
1396
1730
|
inputTokens: typeof input === "number" ? input : void 0,
|
|
1397
1731
|
outputTokens: typeof output === "number" ? output : void 0,
|
|
1732
|
+
cacheReadTokens: typeof cached === "number" ? cached : void 0,
|
|
1398
1733
|
responseModel: typeof responseModel === "string" ? responseModel : void 0
|
|
1399
1734
|
};
|
|
1400
1735
|
}
|
|
1736
|
+
function rollUp(model, usage) {
|
|
1737
|
+
recordOperationAiUsage({
|
|
1738
|
+
model: usage.responseModel ?? model,
|
|
1739
|
+
...usage.inputTokens !== void 0 ? { inputTokens: usage.inputTokens } : {},
|
|
1740
|
+
...usage.outputTokens !== void 0 ? { outputTokens: usage.outputTokens } : {},
|
|
1741
|
+
...usage.cacheReadTokens !== void 0 ? { cacheReadTokens: usage.cacheReadTokens } : {}
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1401
1744
|
function applyUsage(span, usage) {
|
|
1402
1745
|
if (usage.inputTokens !== void 0) {
|
|
1403
1746
|
span.setAttribute("gen_ai.usage.input_tokens", usage.inputTokens);
|
|
@@ -1410,14 +1753,10 @@ function applyUsage(span, usage) {
|
|
|
1410
1753
|
}
|
|
1411
1754
|
}
|
|
1412
1755
|
function endWithError(span, err) {
|
|
1413
|
-
|
|
1414
|
-
span.setStatus({
|
|
1415
|
-
code: SpanStatusCode2.ERROR,
|
|
1416
|
-
message: err instanceof Error ? err.message : String(err)
|
|
1417
|
-
});
|
|
1756
|
+
recordError(span, err);
|
|
1418
1757
|
span.end();
|
|
1419
1758
|
}
|
|
1420
|
-
function wrapStream(stream, span) {
|
|
1759
|
+
function wrapStream(stream, span, model) {
|
|
1421
1760
|
let ended = false;
|
|
1422
1761
|
const seen = {
|
|
1423
1762
|
inputTokens: void 0,
|
|
@@ -1428,6 +1767,7 @@ function wrapStream(stream, span) {
|
|
|
1428
1767
|
if (ended) return;
|
|
1429
1768
|
ended = true;
|
|
1430
1769
|
applyUsage(span, seen);
|
|
1770
|
+
rollUp(model, seen);
|
|
1431
1771
|
span.setAttribute("autter.llm.streamed", true);
|
|
1432
1772
|
if (err !== void 0) {
|
|
1433
1773
|
endWithError(span, err);
|
|
@@ -1449,6 +1789,7 @@ function wrapStream(stream, span) {
|
|
|
1449
1789
|
const usage = extractUsage(step.value);
|
|
1450
1790
|
if (usage.inputTokens !== void 0) seen.inputTokens = usage.inputTokens;
|
|
1451
1791
|
if (usage.outputTokens !== void 0) seen.outputTokens = usage.outputTokens;
|
|
1792
|
+
if (usage.cacheReadTokens !== void 0) seen.cacheReadTokens = usage.cacheReadTokens;
|
|
1452
1793
|
if (usage.responseModel) seen.responseModel = usage.responseModel;
|
|
1453
1794
|
} else {
|
|
1454
1795
|
finish();
|
|
@@ -1494,7 +1835,7 @@ function instrumentLlmClient(client, options) {
|
|
|
1494
1835
|
"gen_ai.system": provider,
|
|
1495
1836
|
"gen_ai.request.model": request.model,
|
|
1496
1837
|
...userId ? { "autter.user_id": userId } : {},
|
|
1497
|
-
...options?.attributes
|
|
1838
|
+
...redactWithActive(options?.attributes)
|
|
1498
1839
|
}
|
|
1499
1840
|
});
|
|
1500
1841
|
let result;
|
|
@@ -1506,9 +1847,11 @@ function instrumentLlmClient(client, options) {
|
|
|
1506
1847
|
}
|
|
1507
1848
|
if (!(result instanceof Promise)) {
|
|
1508
1849
|
if (result && typeof result === "object" && Symbol.asyncIterator in result) {
|
|
1509
|
-
return wrapStream(result, span);
|
|
1850
|
+
return wrapStream(result, span, request.model);
|
|
1510
1851
|
}
|
|
1511
|
-
|
|
1852
|
+
const usage = extractUsage(result);
|
|
1853
|
+
applyUsage(span, usage);
|
|
1854
|
+
rollUp(request.model, usage);
|
|
1512
1855
|
span.setStatus({ code: SpanStatusCode2.OK });
|
|
1513
1856
|
span.end();
|
|
1514
1857
|
return result;
|
|
@@ -1516,9 +1859,11 @@ function instrumentLlmClient(client, options) {
|
|
|
1516
1859
|
return result.then(
|
|
1517
1860
|
(resolved) => {
|
|
1518
1861
|
if (resolved && typeof resolved === "object" && Symbol.asyncIterator in resolved) {
|
|
1519
|
-
return wrapStream(resolved, span);
|
|
1862
|
+
return wrapStream(resolved, span, request.model);
|
|
1520
1863
|
}
|
|
1521
|
-
|
|
1864
|
+
const usage = extractUsage(resolved);
|
|
1865
|
+
applyUsage(span, usage);
|
|
1866
|
+
rollUp(request.model, usage);
|
|
1522
1867
|
span.setStatus({ code: SpanStatusCode2.OK });
|
|
1523
1868
|
span.end();
|
|
1524
1869
|
return resolved;
|
|
@@ -1609,21 +1954,245 @@ ${frames.join("\n")}`;
|
|
|
1609
1954
|
});
|
|
1610
1955
|
} };
|
|
1611
1956
|
}
|
|
1957
|
+
|
|
1958
|
+
// src/logging-only.ts
|
|
1959
|
+
import {
|
|
1960
|
+
SpanStatusCode as SpanStatusCode3,
|
|
1961
|
+
trace as trace3
|
|
1962
|
+
} from "@opentelemetry/api";
|
|
1963
|
+
var activeLogging = null;
|
|
1964
|
+
function initAutterLogging(options) {
|
|
1965
|
+
if (activeLogging) return activeLogging;
|
|
1966
|
+
if (serverActive()) {
|
|
1967
|
+
console.warn(
|
|
1968
|
+
"[autter-runtime] initAutterLogging() ignored: initAutterServer() is already active and includes logging"
|
|
1969
|
+
);
|
|
1970
|
+
return {
|
|
1971
|
+
captureException,
|
|
1972
|
+
flush: flushRuntimeLogs,
|
|
1973
|
+
shutdown: async () => {
|
|
1974
|
+
}
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
if (isDebugEnabled() || options.debug === true) setDebugMode(true);
|
|
1978
|
+
const endpoint = (options.endpoint ?? "https://otlp.autter.dev").replace(/\/$/, "");
|
|
1979
|
+
const environment = options.environment ?? process.env.NODE_ENV ?? "production";
|
|
1980
|
+
const redact = makeRedactor(options.redactAttributes ?? true);
|
|
1981
|
+
const mode = options.exceptions ?? "auto";
|
|
1982
|
+
debugLog(`initialising logger-only service=${options.service} endpoint=${endpoint}`);
|
|
1983
|
+
function captureException2(error, attributes) {
|
|
1984
|
+
markCaptured(error);
|
|
1985
|
+
const redacted = redact(attributes);
|
|
1986
|
+
const span = trace3.getActiveSpan();
|
|
1987
|
+
if (mode === "auto" && span?.isRecording()) {
|
|
1988
|
+
noteOperationError(error);
|
|
1989
|
+
notifyException(error, redacted);
|
|
1990
|
+
span.setAttributes({ "autter.severity": "error", ...redacted, ...structuredErrorAttributes(error) });
|
|
1991
|
+
const declared = errorAttributes(error);
|
|
1992
|
+
if (error instanceof Error) {
|
|
1993
|
+
span.addEvent("exception", {
|
|
1994
|
+
"exception.type": error.name,
|
|
1995
|
+
"exception.message": error.message,
|
|
1996
|
+
...error.stack ? { "exception.stacktrace": error.stack } : {},
|
|
1997
|
+
...declared
|
|
1998
|
+
});
|
|
1999
|
+
} else {
|
|
2000
|
+
span.addEvent("exception", {
|
|
2001
|
+
"exception.type": "Error",
|
|
2002
|
+
"exception.message": String(error),
|
|
2003
|
+
...declared
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
span.setStatus({
|
|
2007
|
+
code: SpanStatusCode3.ERROR,
|
|
2008
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2009
|
+
});
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
notifyException(error, redacted);
|
|
2013
|
+
captureExceptionAsLog(error, redacted);
|
|
2014
|
+
}
|
|
2015
|
+
const captured = /* @__PURE__ */ new WeakSet();
|
|
2016
|
+
configureRuntimeLogger({
|
|
2017
|
+
endpoint,
|
|
2018
|
+
apiKey: options.apiKey ?? "",
|
|
2019
|
+
service: options.service,
|
|
2020
|
+
environment,
|
|
2021
|
+
...options.release ? { release: options.release } : {},
|
|
2022
|
+
...options.logging ? { options: options.logging } : {},
|
|
2023
|
+
redact,
|
|
2024
|
+
reportOutcome: (name, message, attributes) => {
|
|
2025
|
+
runtimeLogger.error(message, {
|
|
2026
|
+
...attributes,
|
|
2027
|
+
"autter.outcome.name": name.slice(0, 200),
|
|
2028
|
+
"autter.outcome.message": message.slice(0, 1e3)
|
|
2029
|
+
});
|
|
2030
|
+
},
|
|
2031
|
+
captureThrown: (error) => {
|
|
2032
|
+
if (error && typeof error === "object") {
|
|
2033
|
+
if (captured.has(error)) return;
|
|
2034
|
+
captured.add(error);
|
|
2035
|
+
}
|
|
2036
|
+
captureException2(error);
|
|
2037
|
+
}
|
|
2038
|
+
});
|
|
2039
|
+
setLoggerOnlyCapture(captureException2);
|
|
2040
|
+
const onCrash = (error) => captureException2(error, { "autter.unhandled": true });
|
|
2041
|
+
if (options.captureGlobalErrors !== false)
|
|
2042
|
+
process.on("uncaughtExceptionMonitor", onCrash);
|
|
2043
|
+
registerFlushTarget("logging", { forceFlush: () => flushRuntimeLogs() });
|
|
2044
|
+
let autoFlushHandle = options.autoFlush === false ? null : installAutterAutoFlush();
|
|
2045
|
+
const handle = {
|
|
2046
|
+
captureException: captureException2,
|
|
2047
|
+
flush: flushRuntimeLogs,
|
|
2048
|
+
shutdown: async () => {
|
|
2049
|
+
process.off("uncaughtExceptionMonitor", onCrash);
|
|
2050
|
+
setLoggerOnlyCapture(null);
|
|
2051
|
+
autoFlushHandle?.dispose();
|
|
2052
|
+
autoFlushHandle = null;
|
|
2053
|
+
unregisterFlushTargets();
|
|
2054
|
+
activeLogging = null;
|
|
2055
|
+
await shutdownRuntimeLogger();
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
activeLogging = handle;
|
|
2059
|
+
return handle;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
// src/enrichers.ts
|
|
2063
|
+
function headerOf(req, name) {
|
|
2064
|
+
const headers = req?.headers;
|
|
2065
|
+
if (!headers) return void 0;
|
|
2066
|
+
if (typeof headers.get === "function") {
|
|
2067
|
+
return headers.get(name) ?? void 0;
|
|
2068
|
+
}
|
|
2069
|
+
const value = headers[name.toLowerCase()];
|
|
2070
|
+
if (Array.isArray(value)) return value[0] === void 0 ? void 0 : String(value[0]);
|
|
2071
|
+
return value === void 0 || value === null ? void 0 : String(value);
|
|
2072
|
+
}
|
|
2073
|
+
function enrichUserAgent() {
|
|
2074
|
+
return (event, { req }) => {
|
|
2075
|
+
const ua = headerOf(req, "user-agent");
|
|
2076
|
+
if (!ua) return;
|
|
2077
|
+
const info = parseUserAgent(ua);
|
|
2078
|
+
if (info.browser) event.attributes["user_agent.browser"] = info.browser;
|
|
2079
|
+
if (info.os) event.attributes["user_agent.os"] = info.os;
|
|
2080
|
+
event.attributes["user_agent.device"] = info.device;
|
|
2081
|
+
event.attributes["user_agent.bot"] = info.bot;
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
function enrichRequestSize() {
|
|
2085
|
+
return (event, { req, res }) => {
|
|
2086
|
+
const request = Number(headerOf(req, "content-length"));
|
|
2087
|
+
if (Number.isFinite(request) && request >= 0)
|
|
2088
|
+
event.attributes["http.request.body.size"] = request;
|
|
2089
|
+
const raw = res?.getHeader?.(
|
|
2090
|
+
"content-length"
|
|
2091
|
+
);
|
|
2092
|
+
const response = Number(raw);
|
|
2093
|
+
if (raw !== void 0 && Number.isFinite(response) && response >= 0)
|
|
2094
|
+
event.attributes["http.response.body.size"] = response;
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
var COUNTRY_HEADERS = [
|
|
2098
|
+
"cf-ipcountry",
|
|
2099
|
+
"x-vercel-ip-country",
|
|
2100
|
+
"cloudfront-viewer-country",
|
|
2101
|
+
"x-country-code",
|
|
2102
|
+
"fastly-geo-country-code"
|
|
2103
|
+
];
|
|
2104
|
+
function enrichEdgeGeo() {
|
|
2105
|
+
return (event, { req }) => {
|
|
2106
|
+
for (const name of COUNTRY_HEADERS) {
|
|
2107
|
+
const value = headerOf(req, name)?.trim().toUpperCase();
|
|
2108
|
+
if (value && /^[A-Z]{2}$/.test(value) && value !== "XX" && value !== "T1") {
|
|
2109
|
+
event.attributes["geo.country.iso_code"] = value;
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
};
|
|
2114
|
+
}
|
|
2115
|
+
var REGION_VARS = [
|
|
2116
|
+
"AUTTER_REGION",
|
|
2117
|
+
"AWS_REGION",
|
|
2118
|
+
"VERCEL_REGION",
|
|
2119
|
+
"FLY_REGION",
|
|
2120
|
+
"RAILWAY_REPLICA_REGION",
|
|
2121
|
+
"GOOGLE_CLOUD_REGION",
|
|
2122
|
+
"CLOUD_RUN_REGION",
|
|
2123
|
+
"RENDER_REGION",
|
|
2124
|
+
"AZURE_REGION"
|
|
2125
|
+
];
|
|
2126
|
+
var COMMIT_VARS = [
|
|
2127
|
+
"AUTTER_COMMIT",
|
|
2128
|
+
"GIT_SHA",
|
|
2129
|
+
"GIT_COMMIT",
|
|
2130
|
+
"COMMIT_SHA",
|
|
2131
|
+
"SOURCE_COMMIT",
|
|
2132
|
+
"VERCEL_GIT_COMMIT_SHA",
|
|
2133
|
+
"GITHUB_SHA",
|
|
2134
|
+
"RENDER_GIT_COMMIT",
|
|
2135
|
+
"RAILWAY_GIT_COMMIT_SHA",
|
|
2136
|
+
"CF_PAGES_COMMIT_SHA",
|
|
2137
|
+
"HEROKU_SLUG_COMMIT",
|
|
2138
|
+
"SOURCE_VERSION"
|
|
2139
|
+
];
|
|
2140
|
+
function enrichDeployment(env = process.env) {
|
|
2141
|
+
const pick = (names) => names.map((name) => env[name]?.trim()).find((value) => value);
|
|
2142
|
+
const region = pick(REGION_VARS)?.slice(0, 64);
|
|
2143
|
+
const commit = pick(COMMIT_VARS)?.slice(0, 64);
|
|
2144
|
+
return (event) => {
|
|
2145
|
+
if (region) event.attributes["deployment.region"] = region;
|
|
2146
|
+
if (commit) event.attributes["deployment.commit"] = commit;
|
|
2147
|
+
};
|
|
2148
|
+
}
|
|
1612
2149
|
export {
|
|
2150
|
+
CODE_PATTERN,
|
|
2151
|
+
COMPAT_MANIFEST,
|
|
2152
|
+
INGESTER_VERSION_HEADER,
|
|
2153
|
+
RuntimeError,
|
|
2154
|
+
SDK_IDENTITY,
|
|
2155
|
+
autterErrorResponse,
|
|
2156
|
+
autterFastify,
|
|
2157
|
+
autterRequests,
|
|
1613
2158
|
captureException,
|
|
1614
2159
|
captureMessage,
|
|
2160
|
+
consoleSink,
|
|
1615
2161
|
createBrowserRelayFetchHandler,
|
|
1616
2162
|
createBrowserRelayHandler,
|
|
2163
|
+
createRuntimeLogger,
|
|
2164
|
+
defineRuntimeErrors,
|
|
1617
2165
|
emitLlmSelftestTrace,
|
|
2166
|
+
enrichDeployment,
|
|
2167
|
+
enrichEdgeGeo,
|
|
2168
|
+
enrichRequestSize,
|
|
2169
|
+
enrichUserAgent,
|
|
2170
|
+
errorAttributes,
|
|
2171
|
+
evaluateCompat,
|
|
2172
|
+
fetchIngesterCompat,
|
|
2173
|
+
fileSink,
|
|
2174
|
+
flushRuntimeLogs,
|
|
2175
|
+
initAutterLogging,
|
|
1618
2176
|
initAutterServer,
|
|
1619
2177
|
installAutterAutoFlush,
|
|
1620
2178
|
instrumentLlmClient,
|
|
2179
|
+
isRuntimeErrorLike,
|
|
1621
2180
|
makeSafeCapture,
|
|
2181
|
+
otlpSink,
|
|
1622
2182
|
redactAttributes,
|
|
2183
|
+
redactText,
|
|
1623
2184
|
reportOutcome,
|
|
2185
|
+
runDoctor,
|
|
2186
|
+
runInBackground,
|
|
2187
|
+
runtimeContext,
|
|
2188
|
+
runtimeLogStats,
|
|
2189
|
+
runtimeLogger,
|
|
1624
2190
|
sanitizeBrowserPayload,
|
|
1625
2191
|
startCaughtExceptionSampler,
|
|
2192
|
+
toClientError,
|
|
1626
2193
|
trackLlmCall,
|
|
1627
2194
|
withLlmCall,
|
|
1628
|
-
withProcessSpan
|
|
2195
|
+
withProcessSpan,
|
|
2196
|
+
withRuntimeOperation,
|
|
2197
|
+
withRuntimeRequest
|
|
1629
2198
|
};
|