@autter/runtime-node 1.4.0 → 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 +106 -16
- 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 +1272 -1141
- package/dist/index.d.cts +420 -42
- package/dist/index.d.ts +420 -42
- package/dist/index.js +1083 -917
- 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) {
|
|
@@ -17,8 +83,8 @@ var IpWindow = class {
|
|
|
17
83
|
return entry.count <= this.limitPerMinute;
|
|
18
84
|
}
|
|
19
85
|
};
|
|
20
|
-
function firstForwardedFor(
|
|
21
|
-
const raw = Array.isArray(
|
|
86
|
+
function firstForwardedFor(value) {
|
|
87
|
+
const raw = Array.isArray(value) ? value[0] : value;
|
|
22
88
|
return raw ? (raw.split(",")[0] ?? "").trim() : "";
|
|
23
89
|
}
|
|
24
90
|
var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
|
|
@@ -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;
|
|
@@ -204,9 +405,9 @@ function createBrowserRelayHandler(opts) {
|
|
|
204
405
|
|
|
205
406
|
// src/server.ts
|
|
206
407
|
import { ServerResponse } from "http";
|
|
207
|
-
import { randomUUID
|
|
408
|
+
import { randomUUID } from "crypto";
|
|
208
409
|
import {
|
|
209
|
-
context,
|
|
410
|
+
context as context2,
|
|
210
411
|
trace as trace2,
|
|
211
412
|
SpanKind,
|
|
212
413
|
SpanStatusCode,
|
|
@@ -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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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;
|
|
452
|
+
}
|
|
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 inFlight2 = null;
|
|
314
|
-
const doFlush = async () => {
|
|
315
|
-
const targets = options.targets ?? activeFlushTargets();
|
|
316
|
-
if (targets.length === 0) return true;
|
|
317
|
-
let timer2;
|
|
318
|
-
const timedOut = new Promise((resolve) => {
|
|
319
|
-
timer2 = 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(timer2);
|
|
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 (!inFlight2) {
|
|
349
|
-
inFlight2 = doFlush().finally(() => {
|
|
350
|
-
inFlight2 = null;
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
if (isDebugEnabled()) debugLog(`auto-flush requested on ${reason}`);
|
|
354
|
-
return inFlight2;
|
|
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(value2, r) {
|
|
494
|
-
let out = value2.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(value2, r, state, depth) {
|
|
513
|
-
if (typeof value2 === "string") return redactString(value2, r);
|
|
514
|
-
let isArray = false;
|
|
515
|
-
try {
|
|
516
|
-
isArray = Array.isArray(value2);
|
|
517
|
-
} catch {
|
|
518
|
-
return r.mask;
|
|
519
|
-
}
|
|
520
|
-
if (isArray) {
|
|
521
|
-
return redactArray(value2, r, state, depth);
|
|
522
|
-
}
|
|
523
|
-
if (typeof value2 === "object" && value2 !== null) {
|
|
524
|
-
return redactObject(value2, r, state, depth);
|
|
525
|
-
}
|
|
526
|
-
return value2;
|
|
527
|
-
}
|
|
528
|
-
function redactArray(value2, r, state, depth) {
|
|
529
|
-
const existing = state.ancestors.get(value2);
|
|
530
|
-
if (existing) return r.mask;
|
|
531
|
-
if (!canTraverse(depth, state)) return r.mask;
|
|
532
|
-
const out = [];
|
|
533
|
-
state.ancestors.set(value2, out);
|
|
534
|
-
let length;
|
|
535
|
-
try {
|
|
536
|
-
length = value2.length;
|
|
537
|
-
} catch {
|
|
538
|
-
state.ancestors.delete(value2);
|
|
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(value2);
|
|
556
|
-
return out;
|
|
557
|
-
}
|
|
558
|
-
function redactObject(value2, r, state, depth) {
|
|
559
|
-
const existing = state.ancestors.get(value2);
|
|
560
|
-
if (existing) return r.mask;
|
|
561
|
-
if (!canTraverse(depth, state)) return r.mask;
|
|
562
|
-
const out = {};
|
|
563
|
-
state.ancestors.set(value2, out);
|
|
564
|
-
let count = 0;
|
|
565
|
-
let truncated = false;
|
|
566
|
-
try {
|
|
567
|
-
for (const key in value2) {
|
|
568
|
-
if (!Object.prototype.propertyIsEnumerable.call(
|
|
569
|
-
value2,
|
|
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 = value2[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(value2);
|
|
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, value2, r) {
|
|
618
|
-
const lowered = key.toLowerCase();
|
|
619
|
-
if (USAGE_TOKEN_KEYS.has(lowered)) {
|
|
620
|
-
return !(typeof value2 === "number" && Number.isFinite(value2) && value2 >= 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 value2;
|
|
650
|
-
try {
|
|
651
|
-
value2 = 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 (value2 === void 0) continue;
|
|
661
|
-
out[key] = isSensitiveKey(key, value2, r) ? r.mask : redactValue(value2, r, state, 0);
|
|
539
|
+
resource = redactResource(metrics2.resource, redactor);
|
|
540
|
+
} catch (error) {
|
|
541
|
+
resultCallback({ code: ExportResultCode.FAILED, error });
|
|
542
|
+
return;
|
|
662
543
|
}
|
|
663
|
-
|
|
664
|
-
|
|
544
|
+
this.inner.export({ ...metrics2, resource }, resultCallback);
|
|
545
|
+
}
|
|
546
|
+
forceFlush() {
|
|
547
|
+
return this.inner.forceFlush();
|
|
665
548
|
}
|
|
666
|
-
|
|
667
|
-
|
|
549
|
+
shutdown() {
|
|
550
|
+
return this.inner.shutdown();
|
|
668
551
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
function makeRedactor(options) {
|
|
672
|
-
if (options === false) {
|
|
673
|
-
return (attributes) => ({ ...attributes ?? {} });
|
|
552
|
+
selectAggregationTemporality(instrumentType) {
|
|
553
|
+
return this.inner.selectAggregationTemporality ? this.inner.selectAggregationTemporality(instrumentType) : AggregationTemporality.CUMULATIVE;
|
|
674
554
|
}
|
|
675
|
-
|
|
676
|
-
return (attributes) => redactWith(attributes, r);
|
|
677
|
-
}
|
|
555
|
+
};
|
|
678
556
|
|
|
679
557
|
// src/memory.ts
|
|
680
558
|
import { PerformanceObserver } from "perf_hooks";
|
|
@@ -721,388 +599,381 @@ function startMemoryMetrics() {
|
|
|
721
599
|
};
|
|
722
600
|
}
|
|
723
601
|
|
|
724
|
-
// src/
|
|
725
|
-
import {
|
|
726
|
-
import {
|
|
727
|
-
|
|
728
|
-
var
|
|
729
|
-
var
|
|
730
|
-
var
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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);
|
|
774
654
|
}
|
|
775
|
-
return
|
|
655
|
+
return wrapped;
|
|
776
656
|
};
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
)
|
|
794
|
-
|
|
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;
|
|
795
684
|
}
|
|
796
|
-
function
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
const
|
|
801
|
-
|
|
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 {
|
|
802
704
|
}
|
|
803
|
-
return
|
|
705
|
+
return original.apply(this, args);
|
|
804
706
|
};
|
|
805
|
-
return userAttributes(merge(current, userAttributes(next), 0));
|
|
806
|
-
}
|
|
807
|
-
function traceAttributes(state) {
|
|
808
|
-
return Object.fromEntries(
|
|
809
|
-
Object.entries(operationAttributes(state)).filter(([, item]) => item !== null && item !== void 0).map(([key, item]) => [
|
|
810
|
-
key,
|
|
811
|
-
typeof item === "object" ? JSON.stringify(item) : item
|
|
812
|
-
])
|
|
813
|
-
);
|
|
814
707
|
}
|
|
815
|
-
function
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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)
|
|
824
729
|
);
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
kvlistValue: {
|
|
836
|
-
values: Object.entries(input).slice(0, 128).map(([key, item]) => ({ key, value: value(item) }))
|
|
837
|
-
}
|
|
838
|
-
};
|
|
839
|
-
return { stringValue: String(input ?? "").slice(0, 32e3) };
|
|
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
|
+
});
|
|
840
740
|
}
|
|
841
|
-
function
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
return;
|
|
846
|
-
const span = trace.getActiveSpan()?.spanContext();
|
|
847
|
-
const user = mergeContext(state?.attributes ?? {}, attributes);
|
|
848
|
-
const exceptions = Object.fromEntries(
|
|
849
|
-
Object.entries(user).filter(([key]) => key.startsWith("exception."))
|
|
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]
|
|
850
745
|
);
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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
|
|
857
753
|
});
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
attributes: Object.entries(attrs).map(([key, item]) => ({
|
|
864
|
-
key,
|
|
865
|
-
value: value(item)
|
|
866
|
-
})),
|
|
867
|
-
...span && /^[a-f0-9]{32}$/.test(span.traceId) && !/^0+$/.test(span.traceId) ? { traceId: span.traceId, spanId: span.spanId } : {}
|
|
868
|
-
};
|
|
869
|
-
if (config?.options?.console !== false)
|
|
870
|
-
console.log(
|
|
871
|
-
JSON.stringify({ ...attrs, level, message: record.body.stringValue })
|
|
872
|
-
);
|
|
873
|
-
if (!config) return;
|
|
874
|
-
if (stopping) {
|
|
875
|
-
dropped++;
|
|
876
|
-
return;
|
|
754
|
+
req[REQUEST_STATE] = state;
|
|
755
|
+
try {
|
|
756
|
+
if (!res.headersSent) res.setHeader(header, requestId);
|
|
757
|
+
exposeOnCors(res, header);
|
|
758
|
+
} catch {
|
|
877
759
|
}
|
|
878
|
-
const
|
|
879
|
-
if (
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
"
|
|
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]
|
|
883
827
|
);
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
if (flushing) return flushing;
|
|
900
|
-
if (!config || !queue.length) return;
|
|
901
|
-
if (timer) {
|
|
902
|
-
clearTimeout(timer);
|
|
903
|
-
timer = void 0;
|
|
904
|
-
}
|
|
905
|
-
const activeConfig = config;
|
|
906
|
-
const deadline = Date.now() + 1e4;
|
|
907
|
-
flushing = (async () => {
|
|
908
|
-
const count = queue.length;
|
|
909
|
-
for (let remaining = count; remaining > 0; ) {
|
|
910
|
-
const batch = [];
|
|
911
|
-
inFlightBytes = 0;
|
|
912
|
-
while (batch.length < Math.min(50, remaining) && queue.length) {
|
|
913
|
-
const next = queue[0];
|
|
914
|
-
if (batch.length && inFlightBytes + next.bytes > 512 * 1024) break;
|
|
915
|
-
batch.push(queue.shift());
|
|
916
|
-
inFlightBytes += next.bytes;
|
|
917
|
-
queueBytes -= next.bytes;
|
|
918
|
-
}
|
|
919
|
-
remaining -= batch.length;
|
|
920
|
-
inFlight = batch.length;
|
|
921
|
-
const body = JSON.stringify({
|
|
922
|
-
resourceLogs: [
|
|
923
|
-
{
|
|
924
|
-
resource: {
|
|
925
|
-
attributes: Object.entries({
|
|
926
|
-
"service.name": activeConfig.service,
|
|
927
|
-
"deployment.environment.name": activeConfig.environment,
|
|
928
|
-
...activeConfig.release ? { "service.version": activeConfig.release } : {}
|
|
929
|
-
}).map(([key, item]) => ({ key, value: value(item) }))
|
|
930
|
-
},
|
|
931
|
-
scopeLogs: [
|
|
932
|
-
{
|
|
933
|
-
scope: { name: "autter-runtime" },
|
|
934
|
-
logRecords: batch.map((entry) => entry.record)
|
|
935
|
-
}
|
|
936
|
-
]
|
|
937
|
-
}
|
|
938
|
-
]
|
|
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"
|
|
939
843
|
});
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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);
|
|
971
875
|
}
|
|
972
|
-
|
|
973
|
-
failureStreak = 0;
|
|
876
|
+
} catch {
|
|
974
877
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
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
|
|
990
897
|
);
|
|
898
|
+
if (value) target.headers.set("access-control-expose-headers", value);
|
|
991
899
|
}
|
|
992
|
-
|
|
993
|
-
queueBytes = 0;
|
|
994
|
-
config = null;
|
|
995
|
-
if (timer) clearTimeout(timer);
|
|
996
|
-
timer = void 0;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
function createRuntimeLogger(attributes = {}) {
|
|
1000
|
-
const attrs = userAttributes(attributes);
|
|
1001
|
-
return {
|
|
1002
|
-
debug: (message, extra) => emit("debug", message, mergeContext(attrs, extra)),
|
|
1003
|
-
info: (message, extra) => emit("info", message, mergeContext(attrs, extra)),
|
|
1004
|
-
warn: (message, extra) => emit("warning", message, mergeContext(attrs, extra)),
|
|
1005
|
-
error: (error, extra) => emit("error", error instanceof Error ? error.message : String(error), {
|
|
1006
|
-
...attrs,
|
|
1007
|
-
...extra,
|
|
1008
|
-
...error instanceof Error ? {
|
|
1009
|
-
"exception.type": error.name,
|
|
1010
|
-
"exception.stacktrace": error.stack ?? ""
|
|
1011
|
-
} : {}
|
|
1012
|
-
})
|
|
900
|
+
return target;
|
|
1013
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
|
+
}
|
|
1014
911
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
parent?.sealed ? {} : parent?.attributes ?? {},
|
|
1024
|
-
attributes
|
|
1025
|
-
),
|
|
1026
|
-
startedAt: Date.now(),
|
|
1027
|
-
outcome: "succeeded",
|
|
1028
|
-
steps: []
|
|
1029
|
-
};
|
|
1030
|
-
const operation = {
|
|
1031
|
-
id: state.id,
|
|
1032
|
-
setContext: (attrs) => {
|
|
1033
|
-
if (!state.sealed) {
|
|
1034
|
-
state.attributes = mergeContext(state.attributes, attrs);
|
|
1035
|
-
trace.getActiveSpan()?.setAttributes(traceAttributes(state));
|
|
1036
|
-
}
|
|
1037
|
-
},
|
|
1038
|
-
outcome: (status, message) => {
|
|
1039
|
-
if (state.sealed) return;
|
|
1040
|
-
state.outcome = status;
|
|
1041
|
-
state.message = message;
|
|
1042
|
-
if (status === "failed")
|
|
1043
|
-
state.reportingStack = new Error("Operation outcome reported here").stack ?? "";
|
|
1044
|
-
},
|
|
1045
|
-
step: async (stepName, stepFn) => {
|
|
1046
|
-
const started = Date.now();
|
|
1047
|
-
let status = "succeeded";
|
|
1048
|
-
try {
|
|
1049
|
-
return await stepFn();
|
|
1050
|
-
} catch (error) {
|
|
1051
|
-
status = "failed";
|
|
1052
|
-
throw error;
|
|
1053
|
-
} finally {
|
|
1054
|
-
if (!state.sealed && state.steps.length < 64)
|
|
1055
|
-
state.steps.push({
|
|
1056
|
-
name: String(safe({ name: stepName }).name).slice(0, 200),
|
|
1057
|
-
status,
|
|
1058
|
-
durationMs: Date.now() - started
|
|
1059
|
-
});
|
|
1060
|
-
}
|
|
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 {
|
|
1061
920
|
}
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
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;
|
|
1065
940
|
try {
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
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);
|
|
1071
952
|
} finally {
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
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 } : {}
|
|
1075
961
|
});
|
|
1076
|
-
if (
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
...traceAttributes(state),
|
|
1082
|
-
"autter.outcome.stack": state.reportingStack ?? ""
|
|
1083
|
-
}
|
|
1084
|
-
);
|
|
1085
|
-
emit(
|
|
1086
|
-
state.outcome === "failed" ? "error" : "info",
|
|
1087
|
-
state.message ?? `${state.name}: ${state.outcome}`,
|
|
1088
|
-
{
|
|
1089
|
-
...operationAttributes(state),
|
|
1090
|
-
...thrown instanceof Error ? {
|
|
1091
|
-
"exception.type": thrown.name,
|
|
1092
|
-
"exception.stacktrace": thrown.stack ?? ""
|
|
1093
|
-
} : {}
|
|
1094
|
-
},
|
|
1095
|
-
{
|
|
1096
|
-
"autter.event.type": "operation",
|
|
1097
|
-
"autter.operation.outcome": state.outcome,
|
|
1098
|
-
"autter.operation.duration_ms": Date.now() - state.startedAt,
|
|
1099
|
-
"autter.operation.steps": state.steps
|
|
962
|
+
if (options.waitUntil) {
|
|
963
|
+
try {
|
|
964
|
+
options.waitUntil(flushRuntimeLogs().catch(() => {
|
|
965
|
+
}));
|
|
966
|
+
} catch {
|
|
1100
967
|
}
|
|
1101
|
-
|
|
1102
|
-
state.sealed = true;
|
|
968
|
+
}
|
|
1103
969
|
}
|
|
1104
|
-
}
|
|
1105
|
-
|
|
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);
|
|
1106
977
|
}
|
|
1107
978
|
|
|
1108
979
|
// src/server.ts
|
|
@@ -1326,36 +1197,43 @@ async function runLlmSpan(tracer, info, fn) {
|
|
|
1326
1197
|
kind: SpanKind.CLIENT,
|
|
1327
1198
|
attributes: llmBaseAttributes(info)
|
|
1328
1199
|
});
|
|
1329
|
-
const
|
|
1200
|
+
const usage = {};
|
|
1201
|
+
let responseModel;
|
|
1202
|
+
const setResponseModel = (model) => {
|
|
1203
|
+
responseModel = model;
|
|
1204
|
+
span.setAttribute("gen_ai.response.model", model);
|
|
1205
|
+
};
|
|
1330
1206
|
const handle = {
|
|
1331
|
-
setUsage: (
|
|
1332
|
-
|
|
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
|
+
},
|
|
1333
1215
|
setResponseModel,
|
|
1334
1216
|
setModel: setResponseModel,
|
|
1335
|
-
setAttributes: (attributes) => span.setAttributes(attributes)
|
|
1217
|
+
setAttributes: (attributes) => span.setAttributes(activeRedactor(attributes))
|
|
1336
1218
|
};
|
|
1337
1219
|
try {
|
|
1338
|
-
const result = await
|
|
1339
|
-
trace2.setSpan(
|
|
1220
|
+
const result = await context2.with(
|
|
1221
|
+
trace2.setSpan(context2.active(), span),
|
|
1340
1222
|
() => fn(handle)
|
|
1341
1223
|
);
|
|
1342
1224
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
1343
1225
|
return result;
|
|
1344
1226
|
} catch (err) {
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
span.setAttribute("error.type", err.name);
|
|
1348
|
-
}
|
|
1349
|
-
span.setStatus({
|
|
1350
|
-
code: SpanStatusCode.ERROR,
|
|
1351
|
-
message: err instanceof Error ? err.message : String(err)
|
|
1352
|
-
});
|
|
1227
|
+
recordError(span, err);
|
|
1228
|
+
if (err instanceof Error) span.setAttribute("error.type", err.name);
|
|
1353
1229
|
throw err;
|
|
1354
1230
|
} finally {
|
|
1355
1231
|
span.end();
|
|
1232
|
+
recordOperationAiUsage({ ...usage, model: responseModel ?? info.model });
|
|
1356
1233
|
}
|
|
1357
1234
|
}
|
|
1358
1235
|
function recordLlmCall(tracer, call) {
|
|
1236
|
+
recordOperationAiUsage({ ...call, model: call.model });
|
|
1359
1237
|
const durationMs = Math.max(0, call.durationMs ?? 0);
|
|
1360
1238
|
const endTime = /* @__PURE__ */ new Date();
|
|
1361
1239
|
const startTime = new Date(endTime.getTime() - durationMs);
|
|
@@ -1368,50 +1246,52 @@ function recordLlmCall(tracer, call) {
|
|
|
1368
1246
|
}
|
|
1369
1247
|
);
|
|
1370
1248
|
if (call.error !== void 0 && call.error !== null) {
|
|
1249
|
+
recordError(span, call.error);
|
|
1371
1250
|
if (call.error instanceof Error) {
|
|
1372
|
-
span.recordException(call.error);
|
|
1373
1251
|
span.setAttribute("error.type", call.error.name);
|
|
1374
1252
|
}
|
|
1375
|
-
span.setStatus({
|
|
1376
|
-
code: SpanStatusCode.ERROR,
|
|
1377
|
-
message: call.error instanceof Error ? call.error.message : String(call.error)
|
|
1378
|
-
});
|
|
1379
1253
|
} else {
|
|
1380
1254
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
1381
1255
|
}
|
|
1382
1256
|
span.end(endTime);
|
|
1383
1257
|
}
|
|
1384
|
-
async function runWithSpan(tracer, name, fn, attributes) {
|
|
1385
|
-
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
|
+
});
|
|
1386
1263
|
try {
|
|
1387
|
-
const result = await
|
|
1388
|
-
trace2.setSpan(
|
|
1264
|
+
const result = await context2.with(
|
|
1265
|
+
trace2.setSpan(context2.active(), span),
|
|
1389
1266
|
fn
|
|
1390
1267
|
);
|
|
1391
1268
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
1392
1269
|
return result;
|
|
1393
1270
|
} catch (err) {
|
|
1394
|
-
|
|
1395
|
-
span.setStatus({
|
|
1396
|
-
code: SpanStatusCode.ERROR,
|
|
1397
|
-
message: err instanceof Error ? err.message : String(err)
|
|
1398
|
-
});
|
|
1271
|
+
recordError(span, err);
|
|
1399
1272
|
throw err;
|
|
1400
1273
|
} finally {
|
|
1401
1274
|
span.end();
|
|
1402
1275
|
}
|
|
1403
1276
|
}
|
|
1404
|
-
function
|
|
1405
|
-
const
|
|
1406
|
-
const
|
|
1407
|
-
if (
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
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);
|
|
1411
1291
|
}
|
|
1412
1292
|
function captureExpressRoute(_span, response) {
|
|
1413
1293
|
if (!(response instanceof ServerResponse)) return;
|
|
1414
|
-
const rpcMetadata = getRPCMetadata(
|
|
1294
|
+
const rpcMetadata = getRPCMetadata(context2.active());
|
|
1415
1295
|
if (rpcMetadata?.type !== RPCType.HTTP) return;
|
|
1416
1296
|
const setRoute = () => {
|
|
1417
1297
|
if (rpcMetadata.route) return;
|
|
@@ -1420,6 +1300,21 @@ function captureExpressRoute(_span, response) {
|
|
|
1420
1300
|
};
|
|
1421
1301
|
response.prependListener("finish", setRoute);
|
|
1422
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;
|
|
1423
1318
|
}
|
|
1424
1319
|
var active = null;
|
|
1425
1320
|
var activeAlwaysOnProvider = null;
|
|
@@ -1431,15 +1326,27 @@ function initAutterServer(options) {
|
|
|
1431
1326
|
""
|
|
1432
1327
|
);
|
|
1433
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
|
+
);
|
|
1434
1335
|
const environment = options.environment ?? process.env.NODE_ENV ?? "production";
|
|
1435
1336
|
if (isDebugEnabled() || options.debug === true) setDebugMode(true);
|
|
1436
1337
|
debugLog(`initialising service=${options.service} endpoint=${endpoint}`);
|
|
1437
1338
|
activeRedactor = makeRedactor(options.redactAttributes ?? true);
|
|
1438
|
-
const
|
|
1339
|
+
const distro = options.distro ?? SDK_IDENTITY;
|
|
1340
|
+
configureCompatCheck({ endpoint, enabled: options.compatCheck !== false, sdk: distro });
|
|
1341
|
+
const resource = new Resource2({
|
|
1439
1342
|
[ATTR_SERVICE_NAME]: options.service,
|
|
1440
|
-
"service.instance.id": (options.instanceId || process.env.AUTTER_RUNTIME_INSTANCE_ID ||
|
|
1343
|
+
"service.instance.id": (options.instanceId || process.env.AUTTER_RUNTIME_INSTANCE_ID || randomUUID()).slice(0, 128),
|
|
1441
1344
|
...options.release ? { [ATTR_SERVICE_VERSION]: options.release } : {},
|
|
1442
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,
|
|
1443
1350
|
// Tells the ingester request metrics arrive on the metrics pipe, so
|
|
1444
1351
|
// it must not also fold our server spans into usage rollups (that
|
|
1445
1352
|
// would double-count every sampled request).
|
|
@@ -1454,30 +1361,24 @@ function initAutterServer(options) {
|
|
|
1454
1361
|
let sampler = retainTraces ? new RecordUnsampledSampler(headSampler) : headSampler;
|
|
1455
1362
|
if (options.llmTracing !== false) sampler = new LlmAwareSampler(sampler);
|
|
1456
1363
|
const errorTraceBuffer = retainTraces ? new ErrorTraceRetentionProcessor(
|
|
1457
|
-
new BatchSpanProcessor(
|
|
1458
|
-
new CountingExporter(
|
|
1459
|
-
new OTLPTraceExporter({ url: `${endpoint}/v1/traces`, headers })
|
|
1460
|
-
),
|
|
1461
|
-
{ scheduledDelayMillis: 2e3 }
|
|
1462
|
-
),
|
|
1364
|
+
new BatchSpanProcessor(traceExporter(), { scheduledDelayMillis: 2e3 }),
|
|
1463
1365
|
retainOnError,
|
|
1464
1366
|
slowThresholdMs
|
|
1465
1367
|
) : null;
|
|
1466
|
-
const mainSpanProcessor = new BatchSpanProcessor(
|
|
1467
|
-
new CountingExporter(
|
|
1468
|
-
new OTLPTraceExporter({ url: `${endpoint}/v1/traces`, headers })
|
|
1469
|
-
)
|
|
1470
|
-
);
|
|
1368
|
+
const mainSpanProcessor = new BatchSpanProcessor(traceExporter());
|
|
1471
1369
|
const metricReader = new PeriodicExportingMetricReader({
|
|
1472
|
-
exporter: new
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
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
|
+
),
|
|
1481
1382
|
exportIntervalMillis: options.metricIntervalMs ?? 6e4
|
|
1482
1383
|
});
|
|
1483
1384
|
const sdk = new NodeSDK({
|
|
@@ -1503,16 +1404,14 @@ function initAutterServer(options) {
|
|
|
1503
1404
|
});
|
|
1504
1405
|
sdk.start();
|
|
1505
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");
|
|
1506
1410
|
const alwaysOnProvider = new BasicTracerProvider({
|
|
1507
1411
|
resource,
|
|
1508
1412
|
sampler: new AlwaysOnSampler(),
|
|
1509
1413
|
spanProcessors: [
|
|
1510
|
-
new BatchSpanProcessor(
|
|
1511
|
-
new CountingExporter(
|
|
1512
|
-
new OTLPTraceExporter({ url: `${endpoint}/v1/traces`, headers })
|
|
1513
|
-
),
|
|
1514
|
-
{ scheduledDelayMillis: 2e3 }
|
|
1515
|
-
)
|
|
1414
|
+
new BatchSpanProcessor(traceExporter(), { scheduledDelayMillis: 2e3 })
|
|
1516
1415
|
]
|
|
1517
1416
|
});
|
|
1518
1417
|
const errorTracer = alwaysOnProvider.getTracer("autter-errors");
|
|
@@ -1522,52 +1421,65 @@ function initAutterServer(options) {
|
|
|
1522
1421
|
errorTraceBuffer?.retainActiveTrace();
|
|
1523
1422
|
telemetryStats.markCaptured();
|
|
1524
1423
|
const isError = error instanceof Error;
|
|
1525
|
-
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) : "";
|
|
1526
1426
|
const redacted = activeRedactor(attributes);
|
|
1527
1427
|
const activeSpan = trace2.getActiveSpan();
|
|
1528
1428
|
const reuseActive = activeSpan?.isRecording() === true;
|
|
1529
1429
|
const span = reuseActive ? activeSpan : errorTracer.startSpan(isError ? error.name : "Error");
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
}
|
|
1541
|
-
|
|
1542
|
-
|
|
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
|
+
});
|
|
1543
1453
|
} else {
|
|
1544
1454
|
const stack = new Error().stack?.split("\n").filter((line, i) => i === 0 || !line.includes("captureException")).join("\n");
|
|
1545
1455
|
span.addEvent("exception", {
|
|
1546
1456
|
"exception.type": isError ? error.name : "Error",
|
|
1547
1457
|
"exception.message": message,
|
|
1548
|
-
...stack ? { "exception.stacktrace": stack } : {},
|
|
1549
|
-
...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
|
|
1550
1461
|
});
|
|
1551
1462
|
}
|
|
1552
1463
|
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
1553
1464
|
if (!reuseActive) span.end();
|
|
1554
1465
|
}
|
|
1555
|
-
function captureMessage2(message,
|
|
1556
|
-
if (
|
|
1466
|
+
function captureMessage2(message, severity = "warning", attributes) {
|
|
1467
|
+
if (severity === "error" || severity === "fatal") {
|
|
1557
1468
|
errorTraceBuffer?.retainActiveTrace();
|
|
1558
1469
|
}
|
|
1559
1470
|
telemetryStats.markCaptured();
|
|
1560
1471
|
const stack = new Error().stack?.split("\n").filter((line, i) => i === 0 || !line.includes("captureMessage")).join("\n");
|
|
1472
|
+
const safeMessage = activeRedactor.text(message);
|
|
1561
1473
|
const span = errorTracer.startSpan("Message", {
|
|
1562
|
-
attributes: { "autter.severity":
|
|
1474
|
+
attributes: { "autter.severity": severity, ...activeRedactor(attributes) }
|
|
1563
1475
|
});
|
|
1564
1476
|
span.addEvent("exception", {
|
|
1565
1477
|
"exception.type": "Message",
|
|
1566
|
-
"exception.message":
|
|
1567
|
-
...stack ? { "exception.stacktrace": stack } : {},
|
|
1568
|
-
"autter.severity":
|
|
1478
|
+
"exception.message": safeMessage,
|
|
1479
|
+
...stack ? { "exception.stacktrace": activeRedactor.text(stack) } : {},
|
|
1480
|
+
"autter.severity": severity
|
|
1569
1481
|
});
|
|
1570
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
1482
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
|
|
1571
1483
|
span.end();
|
|
1572
1484
|
}
|
|
1573
1485
|
function reportOutcome2(name, message, attributes) {
|
|
@@ -1593,12 +1505,14 @@ function initAutterServer(options) {
|
|
|
1593
1505
|
apiKey: options.apiKey,
|
|
1594
1506
|
service: options.service,
|
|
1595
1507
|
environment,
|
|
1508
|
+
distro,
|
|
1596
1509
|
release: options.release,
|
|
1597
1510
|
options: options.logging,
|
|
1598
1511
|
redact: activeRedactor,
|
|
1599
|
-
run: (name, fn, attributes) => runWithSpan(processTracer, name, fn, activeRedactor(attributes)),
|
|
1512
|
+
run: (name, fn, attributes, links) => runWithSpan(processTracer, name, fn, activeRedactor(attributes), links),
|
|
1600
1513
|
reportOutcome: reportOutcome2
|
|
1601
1514
|
});
|
|
1515
|
+
hookRequests = options.logging?.requests === true;
|
|
1602
1516
|
if (options.captureGlobalErrors !== false) {
|
|
1603
1517
|
process.on("uncaughtExceptionMonitor", (err) => {
|
|
1604
1518
|
captureException2(err, { "autter.unhandled": true });
|
|
@@ -1637,6 +1551,7 @@ function initAutterServer(options) {
|
|
|
1637
1551
|
trackLlmCall: (call) => recordLlmCall(llmTracer, call),
|
|
1638
1552
|
shutdown: async () => {
|
|
1639
1553
|
const logShutdown = await Promise.allSettled([shutdownRuntimeLogger()]);
|
|
1554
|
+
hookRequests = false;
|
|
1640
1555
|
stopMemoryMetrics?.();
|
|
1641
1556
|
active = null;
|
|
1642
1557
|
activeAlwaysOnProvider = null;
|
|
@@ -1657,17 +1572,35 @@ function captureException(error, attributes) {
|
|
|
1657
1572
|
active.captureException(error, attributes);
|
|
1658
1573
|
return;
|
|
1659
1574
|
}
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
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)
|
|
1665
1584
|
});
|
|
1585
|
+
recordError(span, error);
|
|
1666
1586
|
span.end();
|
|
1667
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));
|
|
1668
1596
|
function withProcessSpan(name, fn, attributes) {
|
|
1669
1597
|
if (active) return active.withProcessSpan(name, fn, attributes);
|
|
1670
|
-
return runWithSpan(
|
|
1598
|
+
return runWithSpan(
|
|
1599
|
+
trace2.getTracer("autter-processes"),
|
|
1600
|
+
name,
|
|
1601
|
+
fn,
|
|
1602
|
+
activeRedactor(attributes)
|
|
1603
|
+
);
|
|
1671
1604
|
}
|
|
1672
1605
|
function withLlmCall(info, fn) {
|
|
1673
1606
|
if (active) return active.withLlmCall(info, fn);
|
|
@@ -1680,20 +1613,21 @@ function trackLlmCall(call) {
|
|
|
1680
1613
|
}
|
|
1681
1614
|
recordLlmCall(trace2.getTracer("autter-llm"), call);
|
|
1682
1615
|
}
|
|
1683
|
-
function captureMessage(message,
|
|
1616
|
+
function captureMessage(message, severity = "warning", attributes) {
|
|
1684
1617
|
if (active) {
|
|
1685
|
-
active.captureMessage(message,
|
|
1618
|
+
active.captureMessage(message, severity, attributes);
|
|
1686
1619
|
return;
|
|
1687
1620
|
}
|
|
1621
|
+
const safeMessage = activeRedactor.text(message);
|
|
1688
1622
|
const span = trace2.getTracer("autter-errors").startSpan("Message", {
|
|
1689
|
-
attributes: { "autter.severity":
|
|
1623
|
+
attributes: { "autter.severity": severity, ...activeRedactor(attributes) }
|
|
1690
1624
|
});
|
|
1691
1625
|
span.addEvent("exception", {
|
|
1692
1626
|
"exception.type": "Message",
|
|
1693
|
-
"exception.message":
|
|
1694
|
-
"autter.severity":
|
|
1627
|
+
"exception.message": safeMessage,
|
|
1628
|
+
"autter.severity": severity
|
|
1695
1629
|
});
|
|
1696
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
1630
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
|
|
1697
1631
|
span.end();
|
|
1698
1632
|
}
|
|
1699
1633
|
function reportOutcome(name, message, attributes) {
|
|
@@ -1715,7 +1649,7 @@ function makeSafeCapture(options) {
|
|
|
1715
1649
|
const clean = (attributes) => attributes ? { ...redactAttributes(attributes, options) } : void 0;
|
|
1716
1650
|
return {
|
|
1717
1651
|
captureException: (error, attributes) => captureException(error, clean(attributes)),
|
|
1718
|
-
captureMessage: (message,
|
|
1652
|
+
captureMessage: (message, severity, attributes) => captureMessage(message, severity ?? "warning", clean(attributes))
|
|
1719
1653
|
};
|
|
1720
1654
|
}
|
|
1721
1655
|
function alwaysOnTracer(name) {
|
|
@@ -1787,15 +1721,26 @@ function extractUsage(result) {
|
|
|
1787
1721
|
const r = result;
|
|
1788
1722
|
const usage = r?.usage ?? r?.message?.usage;
|
|
1789
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;
|
|
1790
1726
|
const input = usage?.prompt_tokens ?? usage?.input_tokens ?? meta?.promptTokenCount ?? void 0;
|
|
1791
1727
|
const output = usage?.completion_tokens ?? usage?.output_tokens ?? meta?.candidatesTokenCount ?? void 0;
|
|
1792
1728
|
const responseModel = r?.model ?? r?.modelVersion;
|
|
1793
1729
|
return {
|
|
1794
1730
|
inputTokens: typeof input === "number" ? input : void 0,
|
|
1795
1731
|
outputTokens: typeof output === "number" ? output : void 0,
|
|
1732
|
+
cacheReadTokens: typeof cached === "number" ? cached : void 0,
|
|
1796
1733
|
responseModel: typeof responseModel === "string" ? responseModel : void 0
|
|
1797
1734
|
};
|
|
1798
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
|
+
}
|
|
1799
1744
|
function applyUsage(span, usage) {
|
|
1800
1745
|
if (usage.inputTokens !== void 0) {
|
|
1801
1746
|
span.setAttribute("gen_ai.usage.input_tokens", usage.inputTokens);
|
|
@@ -1808,14 +1753,10 @@ function applyUsage(span, usage) {
|
|
|
1808
1753
|
}
|
|
1809
1754
|
}
|
|
1810
1755
|
function endWithError(span, err) {
|
|
1811
|
-
|
|
1812
|
-
span.setStatus({
|
|
1813
|
-
code: SpanStatusCode2.ERROR,
|
|
1814
|
-
message: err instanceof Error ? err.message : String(err)
|
|
1815
|
-
});
|
|
1756
|
+
recordError(span, err);
|
|
1816
1757
|
span.end();
|
|
1817
1758
|
}
|
|
1818
|
-
function wrapStream(stream, span) {
|
|
1759
|
+
function wrapStream(stream, span, model) {
|
|
1819
1760
|
let ended = false;
|
|
1820
1761
|
const seen = {
|
|
1821
1762
|
inputTokens: void 0,
|
|
@@ -1826,6 +1767,7 @@ function wrapStream(stream, span) {
|
|
|
1826
1767
|
if (ended) return;
|
|
1827
1768
|
ended = true;
|
|
1828
1769
|
applyUsage(span, seen);
|
|
1770
|
+
rollUp(model, seen);
|
|
1829
1771
|
span.setAttribute("autter.llm.streamed", true);
|
|
1830
1772
|
if (err !== void 0) {
|
|
1831
1773
|
endWithError(span, err);
|
|
@@ -1847,6 +1789,7 @@ function wrapStream(stream, span) {
|
|
|
1847
1789
|
const usage = extractUsage(step.value);
|
|
1848
1790
|
if (usage.inputTokens !== void 0) seen.inputTokens = usage.inputTokens;
|
|
1849
1791
|
if (usage.outputTokens !== void 0) seen.outputTokens = usage.outputTokens;
|
|
1792
|
+
if (usage.cacheReadTokens !== void 0) seen.cacheReadTokens = usage.cacheReadTokens;
|
|
1850
1793
|
if (usage.responseModel) seen.responseModel = usage.responseModel;
|
|
1851
1794
|
} else {
|
|
1852
1795
|
finish();
|
|
@@ -1857,9 +1800,9 @@ function wrapStream(stream, span) {
|
|
|
1857
1800
|
throw err;
|
|
1858
1801
|
}
|
|
1859
1802
|
},
|
|
1860
|
-
async return(
|
|
1803
|
+
async return(value2) {
|
|
1861
1804
|
finish();
|
|
1862
|
-
return inner.return ? inner.return(
|
|
1805
|
+
return inner.return ? inner.return(value2) : { done: true, value: void 0 };
|
|
1863
1806
|
},
|
|
1864
1807
|
async throw(err) {
|
|
1865
1808
|
finish(err);
|
|
@@ -1869,8 +1812,8 @@ function wrapStream(stream, span) {
|
|
|
1869
1812
|
};
|
|
1870
1813
|
};
|
|
1871
1814
|
}
|
|
1872
|
-
const
|
|
1873
|
-
return typeof
|
|
1815
|
+
const value = Reflect.get(target, prop, receiver);
|
|
1816
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
1874
1817
|
}
|
|
1875
1818
|
});
|
|
1876
1819
|
}
|
|
@@ -1892,7 +1835,7 @@ function instrumentLlmClient(client, options) {
|
|
|
1892
1835
|
"gen_ai.system": provider,
|
|
1893
1836
|
"gen_ai.request.model": request.model,
|
|
1894
1837
|
...userId ? { "autter.user_id": userId } : {},
|
|
1895
|
-
...options?.attributes
|
|
1838
|
+
...redactWithActive(options?.attributes)
|
|
1896
1839
|
}
|
|
1897
1840
|
});
|
|
1898
1841
|
let result;
|
|
@@ -1904,9 +1847,11 @@ function instrumentLlmClient(client, options) {
|
|
|
1904
1847
|
}
|
|
1905
1848
|
if (!(result instanceof Promise)) {
|
|
1906
1849
|
if (result && typeof result === "object" && Symbol.asyncIterator in result) {
|
|
1907
|
-
return wrapStream(result, span);
|
|
1850
|
+
return wrapStream(result, span, request.model);
|
|
1908
1851
|
}
|
|
1909
|
-
|
|
1852
|
+
const usage = extractUsage(result);
|
|
1853
|
+
applyUsage(span, usage);
|
|
1854
|
+
rollUp(request.model, usage);
|
|
1910
1855
|
span.setStatus({ code: SpanStatusCode2.OK });
|
|
1911
1856
|
span.end();
|
|
1912
1857
|
return result;
|
|
@@ -1914,9 +1859,11 @@ function instrumentLlmClient(client, options) {
|
|
|
1914
1859
|
return result.then(
|
|
1915
1860
|
(resolved) => {
|
|
1916
1861
|
if (resolved && typeof resolved === "object" && Symbol.asyncIterator in resolved) {
|
|
1917
|
-
return wrapStream(resolved, span);
|
|
1862
|
+
return wrapStream(resolved, span, request.model);
|
|
1918
1863
|
}
|
|
1919
|
-
|
|
1864
|
+
const usage = extractUsage(resolved);
|
|
1865
|
+
applyUsage(span, usage);
|
|
1866
|
+
rollUp(request.model, usage);
|
|
1920
1867
|
span.setStatus({ code: SpanStatusCode2.OK });
|
|
1921
1868
|
span.end();
|
|
1922
1869
|
return resolved;
|
|
@@ -1932,30 +1879,30 @@ function instrumentLlmClient(client, options) {
|
|
|
1932
1879
|
if (cached) return cached;
|
|
1933
1880
|
const proxy = new Proxy(target, {
|
|
1934
1881
|
get(t, prop, receiver) {
|
|
1935
|
-
const
|
|
1936
|
-
if (typeof prop === "symbol") return
|
|
1882
|
+
const value = Reflect.get(t, prop, receiver);
|
|
1883
|
+
if (typeof prop === "symbol") return value;
|
|
1937
1884
|
const propPath = path ? `${path}.${prop}` : prop;
|
|
1938
|
-
if (typeof
|
|
1885
|
+
if (typeof value === "function") {
|
|
1939
1886
|
if (CALL_METHODS.has(prop)) {
|
|
1940
1887
|
return (...args) => {
|
|
1941
1888
|
const first = args[0];
|
|
1942
1889
|
if (first && typeof first === "object" && typeof first.model === "string") {
|
|
1943
1890
|
return traceCall(
|
|
1944
|
-
|
|
1891
|
+
value,
|
|
1945
1892
|
t,
|
|
1946
1893
|
propPath,
|
|
1947
1894
|
args
|
|
1948
1895
|
);
|
|
1949
1896
|
}
|
|
1950
|
-
return
|
|
1897
|
+
return value.apply(t, args);
|
|
1951
1898
|
};
|
|
1952
1899
|
}
|
|
1953
|
-
return
|
|
1900
|
+
return value.bind(t);
|
|
1954
1901
|
}
|
|
1955
|
-
if (
|
|
1956
|
-
return deepProxy(
|
|
1902
|
+
if (value !== null && typeof value === "object" && !(value instanceof Promise)) {
|
|
1903
|
+
return deepProxy(value, propPath);
|
|
1957
1904
|
}
|
|
1958
|
-
return
|
|
1905
|
+
return value;
|
|
1959
1906
|
}
|
|
1960
1907
|
});
|
|
1961
1908
|
proxied.set(target, proxy);
|
|
@@ -2007,26 +1954,245 @@ ${frames.join("\n")}`;
|
|
|
2007
1954
|
});
|
|
2008
1955
|
} };
|
|
2009
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
|
+
}
|
|
2010
2149
|
export {
|
|
2150
|
+
CODE_PATTERN,
|
|
2151
|
+
COMPAT_MANIFEST,
|
|
2152
|
+
INGESTER_VERSION_HEADER,
|
|
2153
|
+
RuntimeError,
|
|
2154
|
+
SDK_IDENTITY,
|
|
2155
|
+
autterErrorResponse,
|
|
2156
|
+
autterFastify,
|
|
2157
|
+
autterRequests,
|
|
2011
2158
|
captureException,
|
|
2012
2159
|
captureMessage,
|
|
2160
|
+
consoleSink,
|
|
2013
2161
|
createBrowserRelayFetchHandler,
|
|
2014
2162
|
createBrowserRelayHandler,
|
|
2015
2163
|
createRuntimeLogger,
|
|
2164
|
+
defineRuntimeErrors,
|
|
2016
2165
|
emitLlmSelftestTrace,
|
|
2166
|
+
enrichDeployment,
|
|
2167
|
+
enrichEdgeGeo,
|
|
2168
|
+
enrichRequestSize,
|
|
2169
|
+
enrichUserAgent,
|
|
2170
|
+
errorAttributes,
|
|
2171
|
+
evaluateCompat,
|
|
2172
|
+
fetchIngesterCompat,
|
|
2173
|
+
fileSink,
|
|
2017
2174
|
flushRuntimeLogs,
|
|
2175
|
+
initAutterLogging,
|
|
2018
2176
|
initAutterServer,
|
|
2019
2177
|
installAutterAutoFlush,
|
|
2020
2178
|
instrumentLlmClient,
|
|
2179
|
+
isRuntimeErrorLike,
|
|
2021
2180
|
makeSafeCapture,
|
|
2181
|
+
otlpSink,
|
|
2022
2182
|
redactAttributes,
|
|
2183
|
+
redactText,
|
|
2023
2184
|
reportOutcome,
|
|
2185
|
+
runDoctor,
|
|
2186
|
+
runInBackground,
|
|
2187
|
+
runtimeContext,
|
|
2024
2188
|
runtimeLogStats,
|
|
2025
2189
|
runtimeLogger,
|
|
2026
2190
|
sanitizeBrowserPayload,
|
|
2027
2191
|
startCaughtExceptionSampler,
|
|
2192
|
+
toClientError,
|
|
2028
2193
|
trackLlmCall,
|
|
2029
2194
|
withLlmCall,
|
|
2030
2195
|
withProcessSpan,
|
|
2031
|
-
withRuntimeOperation
|
|
2196
|
+
withRuntimeOperation,
|
|
2197
|
+
withRuntimeRequest
|
|
2032
2198
|
};
|