@autter/runtime-node 1.4.0 → 1.6.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 +202 -17
- package/dist/chunk-ARN26OK6.cjs +810 -0
- package/dist/chunk-FBKYOHU2.js +1942 -0
- package/dist/chunk-OY2HTUWY.js +810 -0
- package/dist/chunk-UGNPCOIW.cjs +202 -0
- package/dist/chunk-UKYTXZOQ.cjs +1942 -0
- package/dist/chunk-Y4T7SQMO.js +202 -0
- package/dist/cli.cjs +60 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +60 -0
- package/dist/index.cjs +1558 -1140
- package/dist/index.d.cts +563 -44
- package/dist/index.d.ts +563 -44
- package/dist/index.js +1372 -919
- package/dist/sinks-CPGDyfU8.d.cts +341 -0
- package/dist/sinks-CPGDyfU8.d.ts +341 -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 +18 -5
package/dist/index.js
CHANGED
|
@@ -1,3 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CODE_PATTERN,
|
|
3
|
+
DEFAULT_REQUEST_ID_HEADER,
|
|
4
|
+
RuntimeError,
|
|
5
|
+
addBreadcrumb,
|
|
6
|
+
breadcrumbEventAttributes,
|
|
7
|
+
captureExceptionAsLog,
|
|
8
|
+
compileIgnore,
|
|
9
|
+
configureRuntimeLogger,
|
|
10
|
+
consoleSink,
|
|
11
|
+
createRequestState,
|
|
12
|
+
createRuntimeLogger,
|
|
13
|
+
currentOperation,
|
|
14
|
+
defineRuntimeErrors,
|
|
15
|
+
emitSummary,
|
|
16
|
+
errorAttributes,
|
|
17
|
+
errorCodeOf,
|
|
18
|
+
errorInternal,
|
|
19
|
+
fileSink,
|
|
20
|
+
fingerprintEventAttributes,
|
|
21
|
+
fingerprintValues,
|
|
22
|
+
flushRuntimeLogs,
|
|
23
|
+
isError,
|
|
24
|
+
isRuntimeErrorLike,
|
|
25
|
+
local,
|
|
26
|
+
makeRedactor,
|
|
27
|
+
normalizeRoutePath,
|
|
28
|
+
noteOperationError,
|
|
29
|
+
notifyException,
|
|
30
|
+
otlpSink,
|
|
31
|
+
parseUserAgent,
|
|
32
|
+
recordOperationAiUsage,
|
|
33
|
+
redactAttributes,
|
|
34
|
+
redactText,
|
|
35
|
+
reportExplicitFailure,
|
|
36
|
+
requestOutcome,
|
|
37
|
+
resolveRequestId,
|
|
38
|
+
responseStatusOf,
|
|
39
|
+
runInBackground,
|
|
40
|
+
runtimeContext,
|
|
41
|
+
runtimeLogStats,
|
|
42
|
+
runtimeLogger,
|
|
43
|
+
sanitizeBreadcrumbs,
|
|
44
|
+
shutdownRuntimeLogger,
|
|
45
|
+
splitFingerprint,
|
|
46
|
+
toClientError,
|
|
47
|
+
withRuntimeOperation
|
|
48
|
+
} from "./chunk-FBKYOHU2.js";
|
|
49
|
+
import {
|
|
50
|
+
runDoctor
|
|
51
|
+
} from "./chunk-Y4T7SQMO.js";
|
|
52
|
+
import {
|
|
53
|
+
COMPAT_MANIFEST,
|
|
54
|
+
CountingExporter,
|
|
55
|
+
INGESTER_VERSION_HEADER,
|
|
56
|
+
SDK_IDENTITY,
|
|
57
|
+
configureCompatCheck,
|
|
58
|
+
debugLog,
|
|
59
|
+
ensureCompatConfigured,
|
|
60
|
+
evaluateCompat,
|
|
61
|
+
featuresForBrowserEvents,
|
|
62
|
+
fetchIngesterCompat,
|
|
63
|
+
installAutterAutoFlush,
|
|
64
|
+
isDebugEnabled,
|
|
65
|
+
noteCompatFeature,
|
|
66
|
+
noteRelayRejection,
|
|
67
|
+
observeIngesterResponse,
|
|
68
|
+
registerFlushTarget,
|
|
69
|
+
setDebugMode,
|
|
70
|
+
telemetryStats,
|
|
71
|
+
unregisterFlushTargets
|
|
72
|
+
} from "./chunk-OY2HTUWY.js";
|
|
73
|
+
|
|
1
74
|
// src/relay.ts
|
|
2
75
|
var IpWindow = class {
|
|
3
76
|
constructor(limitPerMinute) {
|
|
@@ -17,8 +90,8 @@ var IpWindow = class {
|
|
|
17
90
|
return entry.count <= this.limitPerMinute;
|
|
18
91
|
}
|
|
19
92
|
};
|
|
20
|
-
function firstForwardedFor(
|
|
21
|
-
const raw = Array.isArray(
|
|
93
|
+
function firstForwardedFor(value) {
|
|
94
|
+
const raw = Array.isArray(value) ? value[0] : value;
|
|
22
95
|
return raw ? (raw.split(",")[0] ?? "").trim() : "";
|
|
23
96
|
}
|
|
24
97
|
var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
|
|
@@ -36,7 +109,142 @@ var EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
|
36
109
|
"timing"
|
|
37
110
|
]);
|
|
38
111
|
var SEVERITIES = /* @__PURE__ */ new Set(["fatal", "error", "warning", "info"]);
|
|
39
|
-
|
|
112
|
+
var TRACE_ID = /^(?!0{32})[0-9a-f]{32}$/;
|
|
113
|
+
var CONTEXT_MAX_DEPTH = 6;
|
|
114
|
+
var CONTEXT_MAX_NODES = 256;
|
|
115
|
+
var CONTEXT_MAX_STRING = 4e3;
|
|
116
|
+
var CONTEXT_MAX_ARRAY = 100;
|
|
117
|
+
var CONTEXT_MAX_KEYS = 100;
|
|
118
|
+
function byteLength(text) {
|
|
119
|
+
return new TextEncoder().encode(text).length;
|
|
120
|
+
}
|
|
121
|
+
function boundContext(value) {
|
|
122
|
+
let nodes = 0;
|
|
123
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
124
|
+
const walk = (v, depth) => {
|
|
125
|
+
if (v === null) return null;
|
|
126
|
+
const t = typeof v;
|
|
127
|
+
if (t === "string") return v.slice(0, CONTEXT_MAX_STRING);
|
|
128
|
+
if (t === "number" || t === "boolean") return v;
|
|
129
|
+
if (t !== "object") return void 0;
|
|
130
|
+
if (depth >= CONTEXT_MAX_DEPTH || nodes >= CONTEXT_MAX_NODES) return void 0;
|
|
131
|
+
const obj = v;
|
|
132
|
+
if (seen.has(obj)) return void 0;
|
|
133
|
+
seen.add(obj);
|
|
134
|
+
let isArr = false;
|
|
135
|
+
try {
|
|
136
|
+
isArr = Array.isArray(v);
|
|
137
|
+
} catch {
|
|
138
|
+
return void 0;
|
|
139
|
+
}
|
|
140
|
+
if (isArr) {
|
|
141
|
+
const arr = v;
|
|
142
|
+
const out2 = [];
|
|
143
|
+
let len = 0;
|
|
144
|
+
try {
|
|
145
|
+
len = arr.length;
|
|
146
|
+
} catch {
|
|
147
|
+
return out2;
|
|
148
|
+
}
|
|
149
|
+
for (let i = 0; i < len && i < CONTEXT_MAX_ARRAY; i++) {
|
|
150
|
+
if (nodes >= CONTEXT_MAX_NODES) break;
|
|
151
|
+
nodes++;
|
|
152
|
+
let el;
|
|
153
|
+
try {
|
|
154
|
+
el = walk(arr[i], depth + 1);
|
|
155
|
+
} catch {
|
|
156
|
+
el = void 0;
|
|
157
|
+
}
|
|
158
|
+
if (el !== void 0) out2.push(el);
|
|
159
|
+
}
|
|
160
|
+
return out2;
|
|
161
|
+
}
|
|
162
|
+
let keys;
|
|
163
|
+
try {
|
|
164
|
+
keys = Object.keys(obj);
|
|
165
|
+
} catch {
|
|
166
|
+
return void 0;
|
|
167
|
+
}
|
|
168
|
+
const out = {};
|
|
169
|
+
for (let i = 0; i < keys.length && i < CONTEXT_MAX_KEYS; i++) {
|
|
170
|
+
if (nodes >= CONTEXT_MAX_NODES) break;
|
|
171
|
+
const key = keys[i];
|
|
172
|
+
if (key === void 0) continue;
|
|
173
|
+
nodes++;
|
|
174
|
+
let child;
|
|
175
|
+
try {
|
|
176
|
+
child = walk(obj[key], depth + 1);
|
|
177
|
+
} catch {
|
|
178
|
+
child = void 0;
|
|
179
|
+
}
|
|
180
|
+
if (child !== void 0) out[key] = child;
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
};
|
|
184
|
+
let result;
|
|
185
|
+
try {
|
|
186
|
+
result = walk(value, 0);
|
|
187
|
+
} catch {
|
|
188
|
+
result = void 0;
|
|
189
|
+
}
|
|
190
|
+
return result && typeof result === "object" ? result : {};
|
|
191
|
+
}
|
|
192
|
+
async function readBodyBounded(request, maxBody) {
|
|
193
|
+
const body = request.body;
|
|
194
|
+
if (!body) {
|
|
195
|
+
const text = await request.text();
|
|
196
|
+
return byteLength(text) > maxBody ? { tooLarge: true } : { tooLarge: false, text };
|
|
197
|
+
}
|
|
198
|
+
const reader = body.getReader();
|
|
199
|
+
const chunks = [];
|
|
200
|
+
let total = 0;
|
|
201
|
+
for (; ; ) {
|
|
202
|
+
const { done, value } = await reader.read();
|
|
203
|
+
if (done) break;
|
|
204
|
+
if (!value) continue;
|
|
205
|
+
total += value.byteLength;
|
|
206
|
+
if (total > maxBody) {
|
|
207
|
+
void Promise.resolve().then(() => reader.cancel()).catch(() => {
|
|
208
|
+
});
|
|
209
|
+
return { tooLarge: true };
|
|
210
|
+
}
|
|
211
|
+
chunks.push(value);
|
|
212
|
+
}
|
|
213
|
+
const buf = new Uint8Array(total);
|
|
214
|
+
let offset = 0;
|
|
215
|
+
for (const chunk of chunks) {
|
|
216
|
+
buf.set(chunk, offset);
|
|
217
|
+
offset += chunk.byteLength;
|
|
218
|
+
}
|
|
219
|
+
return { tooLarge: false, text: new TextDecoder().decode(buf) };
|
|
220
|
+
}
|
|
221
|
+
function relayContext(raw, redactor) {
|
|
222
|
+
const bounded = boundContext(raw);
|
|
223
|
+
return Array.isArray(bounded) ? {} : { context: redactor(bounded) };
|
|
224
|
+
}
|
|
225
|
+
function relayBreadcrumbs(raw, redactor) {
|
|
226
|
+
const breadcrumbs = sanitizeBreadcrumbs(raw, redactor);
|
|
227
|
+
return breadcrumbs ? { breadcrumbs } : {};
|
|
228
|
+
}
|
|
229
|
+
var DEBUG_ID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
230
|
+
function relayDebugIds(raw, redactor) {
|
|
231
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
|
|
232
|
+
const debugIds = {};
|
|
233
|
+
let count = 0;
|
|
234
|
+
for (const [url, id] of Object.entries(raw)) {
|
|
235
|
+
if (count >= 50) break;
|
|
236
|
+
if (typeof id !== "string" || !DEBUG_ID_RE.test(id) || url.length > 1e3) continue;
|
|
237
|
+
debugIds[redactor.text(url.split(/[?#]/)[0])] = id.toLowerCase();
|
|
238
|
+
count++;
|
|
239
|
+
}
|
|
240
|
+
return count ? { debugIds } : {};
|
|
241
|
+
}
|
|
242
|
+
function relayFingerprint(raw, redactor) {
|
|
243
|
+
const values = fingerprintValues(raw, redactor.text);
|
|
244
|
+
return values ? { fingerprint: Array.isArray(raw) ? values : values[0] } : {};
|
|
245
|
+
}
|
|
246
|
+
var defaultRelayRedactor = makeRedactor(true);
|
|
247
|
+
function sanitizeBrowserPayload(raw, redactor = defaultRelayRedactor) {
|
|
40
248
|
if (typeof raw !== "object" || raw === null) return null;
|
|
41
249
|
const p = raw;
|
|
42
250
|
if (p.version !== 1) return null;
|
|
@@ -53,16 +261,20 @@ function sanitizeBrowserPayload(raw) {
|
|
|
53
261
|
type: e.type,
|
|
54
262
|
timestamp: e.timestamp,
|
|
55
263
|
...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) } : {},
|
|
264
|
+
message: typeof e.message === "string" ? redactor.text(e.message.slice(0, 4512)).slice(0, 4e3) : "",
|
|
265
|
+
...typeof e.name === "string" ? { name: redactor.text(e.name.slice(0, 712)).slice(0, 200) } : {},
|
|
58
266
|
...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) } : {},
|
|
267
|
+
...typeof e.stack === "string" ? { stack: redactor.text(e.stack.slice(0, 32512)).slice(0, 32e3) } : {},
|
|
60
268
|
...typeof e.errorType === "string" ? { errorType: e.errorType.slice(0, 200) } : {},
|
|
61
269
|
...typeof e.filename === "string" ? { filename: e.filename.split("?")[0].slice(0, 1e3) } : {},
|
|
62
270
|
...typeof e.line === "number" ? { line: e.line } : {},
|
|
63
271
|
...typeof e.column === "number" ? { column: e.column } : {},
|
|
64
272
|
...typeof e.route === "string" ? { route: e.route.split("?")[0].slice(0, 1e3) } : {},
|
|
65
|
-
...typeof e.context === "object" && e.context !== null ?
|
|
273
|
+
...typeof e.context === "object" && e.context !== null ? relayContext(e.context, redactor) : {},
|
|
274
|
+
...relayBreadcrumbs(e.breadcrumbs, redactor),
|
|
275
|
+
...relayDebugIds(e.debugIds, redactor),
|
|
276
|
+
...relayFingerprint(e.fingerprint, redactor),
|
|
277
|
+
...typeof e.traceId === "string" && TRACE_ID.test(e.traceId) ? { traceId: e.traceId } : {}
|
|
66
278
|
});
|
|
67
279
|
}
|
|
68
280
|
return {
|
|
@@ -71,11 +283,16 @@ function sanitizeBrowserPayload(raw) {
|
|
|
71
283
|
service: p.service.slice(0, 200),
|
|
72
284
|
environment: p.environment.slice(0, 100),
|
|
73
285
|
...typeof p.release === "string" ? { release: p.release.slice(0, 200) } : {},
|
|
286
|
+
// @autter/runtime-browser version, for the ingester's compat records.
|
|
287
|
+
...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
288
|
events
|
|
75
289
|
};
|
|
76
290
|
}
|
|
77
291
|
function forward(payload, opts) {
|
|
78
|
-
const
|
|
292
|
+
const endpoint = (opts.endpoint ?? DEFAULT_ENDPOINT).replace(/\/$/, "");
|
|
293
|
+
const url = `${endpoint}/v1/browser`;
|
|
294
|
+
const compat = opts.compatCheck !== false;
|
|
295
|
+
if (compat) ensureCompatConfigured(endpoint);
|
|
79
296
|
void fetch(url, {
|
|
80
297
|
method: "POST",
|
|
81
298
|
headers: {
|
|
@@ -84,6 +301,17 @@ function forward(payload, opts) {
|
|
|
84
301
|
},
|
|
85
302
|
body: JSON.stringify(payload),
|
|
86
303
|
signal: AbortSignal.timeout(1e4)
|
|
304
|
+
}).then((response) => {
|
|
305
|
+
if (!compat) return;
|
|
306
|
+
const types = new Set(
|
|
307
|
+
(payload.events ?? []).map((e) => String(e.type))
|
|
308
|
+
);
|
|
309
|
+
observeIngesterResponse({ status: response.status, headers: response.headers });
|
|
310
|
+
for (const feature of featuresForBrowserEvents(types)) noteCompatFeature(feature);
|
|
311
|
+
if ((payload.events ?? []).some((e) => e.traceId)) {
|
|
312
|
+
noteCompatFeature("browser_trace_propagation");
|
|
313
|
+
}
|
|
314
|
+
noteRelayRejection(types, response.status, response.headers);
|
|
87
315
|
}).catch((err) => {
|
|
88
316
|
(opts.onError ?? ((e) => console.warn("autter relay forward failed:", e)))(
|
|
89
317
|
err
|
|
@@ -92,6 +320,7 @@ function forward(payload, opts) {
|
|
|
92
320
|
}
|
|
93
321
|
function createBrowserRelayFetchHandler(opts) {
|
|
94
322
|
const maxBody = opts.maxBodyBytes ?? DEFAULT_MAX_BODY;
|
|
323
|
+
const redactor = opts.redact === void 0 || opts.redact === true ? defaultRelayRedactor : makeRedactor(opts.redact);
|
|
95
324
|
const limiter = opts.perIpRateLimit === false ? null : new IpWindow(opts.perIpRateLimit ?? 120);
|
|
96
325
|
return async (request) => {
|
|
97
326
|
if (request.method !== "POST") {
|
|
@@ -105,21 +334,28 @@ function createBrowserRelayFetchHandler(opts) {
|
|
|
105
334
|
});
|
|
106
335
|
}
|
|
107
336
|
}
|
|
108
|
-
|
|
109
|
-
|
|
337
|
+
let bounded;
|
|
338
|
+
try {
|
|
339
|
+
bounded = await readBodyBounded(request, maxBody);
|
|
340
|
+
} catch {
|
|
341
|
+
return new Response(JSON.stringify({ error: "invalid json" }), {
|
|
342
|
+
status: 400
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
if (bounded.tooLarge) {
|
|
110
346
|
return new Response(JSON.stringify({ error: "payload too large" }), {
|
|
111
347
|
status: 413
|
|
112
348
|
});
|
|
113
349
|
}
|
|
114
350
|
let raw;
|
|
115
351
|
try {
|
|
116
|
-
raw = JSON.parse(text);
|
|
352
|
+
raw = JSON.parse(bounded.text);
|
|
117
353
|
} catch {
|
|
118
354
|
return new Response(JSON.stringify({ error: "invalid json" }), {
|
|
119
355
|
status: 400
|
|
120
356
|
});
|
|
121
357
|
}
|
|
122
|
-
const payload = sanitizeBrowserPayload(raw);
|
|
358
|
+
const payload = sanitizeBrowserPayload(raw, redactor);
|
|
123
359
|
if (!payload) {
|
|
124
360
|
return new Response(JSON.stringify({ error: "invalid payload" }), {
|
|
125
361
|
status: 400
|
|
@@ -131,6 +367,7 @@ function createBrowserRelayFetchHandler(opts) {
|
|
|
131
367
|
}
|
|
132
368
|
function createBrowserRelayHandler(opts) {
|
|
133
369
|
const maxBody = opts.maxBodyBytes ?? DEFAULT_MAX_BODY;
|
|
370
|
+
const redactor = opts.redact === void 0 || opts.redact === true ? defaultRelayRedactor : makeRedactor(opts.redact);
|
|
134
371
|
const limiter = opts.perIpRateLimit === false ? null : new IpWindow(opts.perIpRateLimit ?? 120);
|
|
135
372
|
function respond(res, status, body) {
|
|
136
373
|
res.statusCode = status;
|
|
@@ -142,7 +379,7 @@ function createBrowserRelayHandler(opts) {
|
|
|
142
379
|
}
|
|
143
380
|
}
|
|
144
381
|
function handleParsed(raw, res) {
|
|
145
|
-
const payload = sanitizeBrowserPayload(raw);
|
|
382
|
+
const payload = sanitizeBrowserPayload(raw, redactor);
|
|
146
383
|
if (!payload) {
|
|
147
384
|
respond(res, 400, { error: "invalid payload" });
|
|
148
385
|
return;
|
|
@@ -204,22 +441,22 @@ function createBrowserRelayHandler(opts) {
|
|
|
204
441
|
|
|
205
442
|
// src/server.ts
|
|
206
443
|
import { ServerResponse } from "http";
|
|
207
|
-
import { randomUUID
|
|
444
|
+
import { randomUUID } from "crypto";
|
|
208
445
|
import {
|
|
209
|
-
context,
|
|
446
|
+
context as context2,
|
|
210
447
|
trace as trace2,
|
|
211
448
|
SpanKind,
|
|
212
449
|
SpanStatusCode,
|
|
213
450
|
TraceFlags
|
|
214
451
|
} from "@opentelemetry/api";
|
|
215
|
-
import { getRPCMetadata, RPCType } from "@opentelemetry/core";
|
|
452
|
+
import { getRPCMetadata as getRPCMetadata2, RPCType as RPCType2 } from "@opentelemetry/core";
|
|
216
453
|
import {
|
|
217
454
|
AggregationTemporalityPreference,
|
|
218
455
|
OTLPMetricExporter
|
|
219
456
|
} from "@opentelemetry/exporter-metrics-otlp-http";
|
|
220
457
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
221
458
|
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
|
222
|
-
import { Resource } from "@opentelemetry/resources";
|
|
459
|
+
import { Resource as Resource2 } from "@opentelemetry/resources";
|
|
223
460
|
import { ExplicitBucketHistogramAggregation, PeriodicExportingMetricReader, View } from "@opentelemetry/sdk-metrics";
|
|
224
461
|
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
225
462
|
import {
|
|
@@ -235,50 +472,83 @@ import {
|
|
|
235
472
|
ATTR_SERVICE_VERSION
|
|
236
473
|
} from "@opentelemetry/semantic-conventions";
|
|
237
474
|
|
|
238
|
-
// src/
|
|
475
|
+
// src/redact-exporter.ts
|
|
239
476
|
import { ExportResultCode } from "@opentelemetry/core";
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
477
|
+
import { Resource } from "@opentelemetry/resources";
|
|
478
|
+
import {
|
|
479
|
+
AggregationTemporality
|
|
480
|
+
} from "@opentelemetry/sdk-metrics";
|
|
481
|
+
var scrubbedResources = /* @__PURE__ */ new WeakMap();
|
|
482
|
+
function redactResource(resource, redactor) {
|
|
483
|
+
const cached = scrubbedResources.get(resource);
|
|
484
|
+
if (cached) return cached;
|
|
485
|
+
const scrubbed = new Resource(redactor.exported(resource.attributes));
|
|
486
|
+
scrubbedResources.set(resource, scrubbed);
|
|
487
|
+
return scrubbed;
|
|
488
|
+
}
|
|
489
|
+
function redactSpan(span, redactor) {
|
|
490
|
+
const status = span.status.message ? { ...span.status, message: redactor.text(span.status.message) } : span.status;
|
|
491
|
+
return {
|
|
492
|
+
name: redactor.text(span.name),
|
|
493
|
+
kind: span.kind,
|
|
494
|
+
spanContext: () => span.spanContext(),
|
|
495
|
+
parentSpanId: span.parentSpanId,
|
|
496
|
+
startTime: span.startTime,
|
|
497
|
+
endTime: span.endTime,
|
|
498
|
+
status,
|
|
499
|
+
attributes: redactor.exported(span.attributes),
|
|
500
|
+
links: span.links.map(
|
|
501
|
+
(link) => link.attributes ? { ...link, attributes: redactor.exported(link.attributes) } : link
|
|
502
|
+
),
|
|
503
|
+
events: span.events.map((event) => ({
|
|
504
|
+
...event,
|
|
505
|
+
name: redactor.text(event.name),
|
|
506
|
+
...event.attributes ? { attributes: redactor.exported(event.attributes) } : {}
|
|
507
|
+
})),
|
|
508
|
+
duration: span.duration,
|
|
509
|
+
ended: span.ended,
|
|
510
|
+
resource: redactResource(span.resource, redactor),
|
|
511
|
+
instrumentationLibrary: span.instrumentationLibrary,
|
|
512
|
+
droppedAttributesCount: span.droppedAttributesCount,
|
|
513
|
+
droppedEventsCount: span.droppedEventsCount,
|
|
514
|
+
droppedLinksCount: span.droppedLinksCount
|
|
515
|
+
};
|
|
251
516
|
}
|
|
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) {
|
|
517
|
+
var RedactingSpanExporter = class {
|
|
518
|
+
constructor(inner, redactor) {
|
|
272
519
|
this.inner = inner;
|
|
520
|
+
this.redactor = redactor;
|
|
273
521
|
}
|
|
274
522
|
inner;
|
|
523
|
+
redactor;
|
|
275
524
|
export(spans, resultCallback) {
|
|
276
|
-
this.
|
|
277
|
-
|
|
278
|
-
|
|
525
|
+
const redactor = this.redactor();
|
|
526
|
+
let scrubbed = spans;
|
|
527
|
+
let failed = 0;
|
|
528
|
+
if (redactor.enabled) {
|
|
529
|
+
scrubbed = [];
|
|
530
|
+
for (const span of spans) {
|
|
531
|
+
try {
|
|
532
|
+
scrubbed.push(redactSpan(span, redactor));
|
|
533
|
+
} catch {
|
|
534
|
+
failed++;
|
|
535
|
+
}
|
|
279
536
|
}
|
|
280
|
-
|
|
281
|
-
|
|
537
|
+
}
|
|
538
|
+
if (!failed) {
|
|
539
|
+
this.inner.export(scrubbed, resultCallback);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const error = new Error(`autter-runtime: ${failed} span(s) could not be redacted and were not exported`);
|
|
543
|
+
const fail = () => resultCallback({ code: ExportResultCode.FAILED, error });
|
|
544
|
+
if (!scrubbed.length) {
|
|
545
|
+
fail();
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
this.inner.export(
|
|
549
|
+
scrubbed,
|
|
550
|
+
(result) => result.code === ExportResultCode.SUCCESS ? fail() : resultCallback(result)
|
|
551
|
+
);
|
|
282
552
|
}
|
|
283
553
|
shutdown() {
|
|
284
554
|
return this.inner.shutdown();
|
|
@@ -287,394 +557,38 @@ var CountingExporter = class {
|
|
|
287
557
|
return this.inner.forceFlush?.() ?? Promise.resolve();
|
|
288
558
|
}
|
|
289
559
|
};
|
|
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
|
-
}
|
|
560
|
+
var RedactingMetricExporter = class {
|
|
561
|
+
constructor(inner, redactor) {
|
|
562
|
+
this.inner = inner;
|
|
563
|
+
this.redactor = redactor;
|
|
476
564
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
565
|
+
inner;
|
|
566
|
+
redactor;
|
|
567
|
+
export(metrics2, resultCallback) {
|
|
568
|
+
const redactor = this.redactor();
|
|
569
|
+
if (!redactor.enabled) {
|
|
570
|
+
this.inner.export(metrics2, resultCallback);
|
|
571
|
+
return;
|
|
482
572
|
}
|
|
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;
|
|
573
|
+
let resource;
|
|
544
574
|
try {
|
|
545
|
-
|
|
546
|
-
} catch {
|
|
547
|
-
|
|
548
|
-
|
|
575
|
+
resource = redactResource(metrics2.resource, redactor);
|
|
576
|
+
} catch (error) {
|
|
577
|
+
resultCallback({ code: ExportResultCode.FAILED, error });
|
|
578
|
+
return;
|
|
549
579
|
}
|
|
550
|
-
|
|
580
|
+
this.inner.export({ ...metrics2, resource }, resultCallback);
|
|
551
581
|
}
|
|
552
|
-
|
|
553
|
-
|
|
582
|
+
forceFlush() {
|
|
583
|
+
return this.inner.forceFlush();
|
|
554
584
|
}
|
|
555
|
-
|
|
556
|
-
|
|
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);
|
|
662
|
-
}
|
|
663
|
-
} catch {
|
|
664
|
-
truncated = true;
|
|
585
|
+
shutdown() {
|
|
586
|
+
return this.inner.shutdown();
|
|
665
587
|
}
|
|
666
|
-
|
|
667
|
-
|
|
588
|
+
selectAggregationTemporality(instrumentType) {
|
|
589
|
+
return this.inner.selectAggregationTemporality ? this.inner.selectAggregationTemporality(instrumentType) : AggregationTemporality.CUMULATIVE;
|
|
668
590
|
}
|
|
669
|
-
|
|
670
|
-
}
|
|
671
|
-
function makeRedactor(options) {
|
|
672
|
-
if (options === false) {
|
|
673
|
-
return (attributes) => ({ ...attributes ?? {} });
|
|
674
|
-
}
|
|
675
|
-
const r = compile(options === true ? void 0 : options);
|
|
676
|
-
return (attributes) => redactWith(attributes, r);
|
|
677
|
-
}
|
|
591
|
+
};
|
|
678
592
|
|
|
679
593
|
// src/memory.ts
|
|
680
594
|
import { PerformanceObserver } from "perf_hooks";
|
|
@@ -721,388 +635,544 @@ function startMemoryMetrics() {
|
|
|
721
635
|
};
|
|
722
636
|
}
|
|
723
637
|
|
|
724
|
-
// src/
|
|
725
|
-
import {
|
|
726
|
-
import {
|
|
727
|
-
import {
|
|
728
|
-
var
|
|
729
|
-
var
|
|
730
|
-
var
|
|
731
|
-
var
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
var
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
638
|
+
// src/requests.ts
|
|
639
|
+
import { AsyncResource } from "async_hooks";
|
|
640
|
+
import { context, trace } from "@opentelemetry/api";
|
|
641
|
+
import { getRPCMetadata, RPCType } from "@opentelemetry/core";
|
|
642
|
+
var REQUEST_STATE = /* @__PURE__ */ Symbol.for("autter.runtime.request");
|
|
643
|
+
var BOUND = /* @__PURE__ */ Symbol.for("autter.runtime.bound");
|
|
644
|
+
var CAPTURED = /* @__PURE__ */ new WeakSet();
|
|
645
|
+
var capturer = () => {
|
|
646
|
+
};
|
|
647
|
+
function setRequestExceptionCapturer(fn) {
|
|
648
|
+
capturer = fn;
|
|
649
|
+
}
|
|
650
|
+
var annotator = () => {
|
|
651
|
+
};
|
|
652
|
+
function setRequestSpanAnnotator(fn) {
|
|
653
|
+
annotator = fn;
|
|
654
|
+
}
|
|
655
|
+
function markCaptured(error) {
|
|
656
|
+
if (error && typeof error === "object") CAPTURED.add(error);
|
|
657
|
+
}
|
|
658
|
+
function expressRouteOf(req) {
|
|
659
|
+
const props = req;
|
|
660
|
+
const path = props?.route?.path;
|
|
661
|
+
if (typeof path !== "string" || path === "") return null;
|
|
662
|
+
const base = typeof props?.baseUrl === "string" ? props.baseUrl : "";
|
|
663
|
+
const route = base + path;
|
|
664
|
+
return route.startsWith("/") ? route : null;
|
|
665
|
+
}
|
|
666
|
+
function defaultShouldCapture(error) {
|
|
667
|
+
return error instanceof RuntimeError || errorCodeOf(error) !== void 0 || responseStatusOf(error) >= 500;
|
|
668
|
+
}
|
|
669
|
+
function onRequestSpan(fn) {
|
|
670
|
+
const active2 = context.active();
|
|
671
|
+
const metadata = getRPCMetadata(active2);
|
|
672
|
+
const span = metadata?.type === RPCType.HTTP ? metadata.span : void 0;
|
|
673
|
+
if (!span || !span.isRecording() || trace.getSpan(active2) === span) {
|
|
674
|
+
fn();
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
context.with(trace.setSpan(active2, span), fn);
|
|
678
|
+
}
|
|
679
|
+
function captureAtBoundary(error, state, rule = void 0, attributes) {
|
|
680
|
+
let wanted;
|
|
681
|
+
try {
|
|
682
|
+
wanted = typeof rule === "function" ? rule(error) : rule === void 0 ? defaultShouldCapture(error) : rule;
|
|
683
|
+
} catch {
|
|
684
|
+
wanted = false;
|
|
685
|
+
}
|
|
686
|
+
if (!wanted) return;
|
|
687
|
+
if (error && typeof error === "object" && CAPTURED.has(error)) return;
|
|
688
|
+
markCaptured(error);
|
|
689
|
+
try {
|
|
690
|
+
const run = () => onRequestSpan(() => capturer(error, attributes));
|
|
691
|
+
if (state && !state.sealed) local.run(state, run);
|
|
692
|
+
else run();
|
|
693
|
+
} catch {
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
function requestStateOf(req) {
|
|
697
|
+
if (!req || typeof req !== "object") return void 0;
|
|
698
|
+
return req[REQUEST_STATE];
|
|
699
|
+
}
|
|
700
|
+
function captureRequestError(error, attributes) {
|
|
701
|
+
if (error && typeof error === "object" && CAPTURED.has(error)) {
|
|
702
|
+
if (!attributes) return;
|
|
703
|
+
try {
|
|
704
|
+
onRequestSpan(() => annotator(attributes));
|
|
705
|
+
} catch {
|
|
763
706
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
captureAtBoundary(error, currentOperation(), true, attributes);
|
|
710
|
+
}
|
|
711
|
+
function bindEmitter(emitter, state) {
|
|
712
|
+
const target = emitter;
|
|
713
|
+
if (!target || typeof target !== "object" || target[BOUND]) return;
|
|
714
|
+
target[BOUND] = true;
|
|
715
|
+
const wrappers = /* @__PURE__ */ new WeakMap();
|
|
716
|
+
const wrap = (listener) => {
|
|
717
|
+
if (typeof listener !== "function") return listener;
|
|
718
|
+
let wrapped = wrappers.get(listener);
|
|
719
|
+
if (!wrapped) {
|
|
720
|
+
wrapped = function(...args) {
|
|
721
|
+
return local.run(
|
|
722
|
+
state,
|
|
723
|
+
() => listener.apply(this, args)
|
|
724
|
+
);
|
|
725
|
+
};
|
|
726
|
+
wrappers.set(listener, wrapped);
|
|
774
727
|
}
|
|
775
|
-
return
|
|
728
|
+
return wrapped;
|
|
776
729
|
};
|
|
777
|
-
const
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
)
|
|
794
|
-
|
|
730
|
+
for (const method of [
|
|
731
|
+
"on",
|
|
732
|
+
"addListener",
|
|
733
|
+
"prependListener",
|
|
734
|
+
"once",
|
|
735
|
+
"prependOnceListener"
|
|
736
|
+
]) {
|
|
737
|
+
const original = target[method];
|
|
738
|
+
if (typeof original !== "function") continue;
|
|
739
|
+
target[method] = function(event, listener) {
|
|
740
|
+
return original.call(this, event, wrap(listener));
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
for (const method of ["removeListener", "off"]) {
|
|
744
|
+
const original = target[method];
|
|
745
|
+
if (typeof original !== "function") continue;
|
|
746
|
+
target[method] = function(event, listener) {
|
|
747
|
+
const wrapped = typeof listener === "function" ? wrappers.get(listener) ?? listener : listener;
|
|
748
|
+
return original.call(this, event, wrapped);
|
|
749
|
+
};
|
|
750
|
+
}
|
|
795
751
|
}
|
|
796
|
-
function
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
752
|
+
function exposeHeaderValue(current, header) {
|
|
753
|
+
const text = Array.isArray(current) ? current.join(", ") : String(current ?? "");
|
|
754
|
+
const names = text.split(",").map((item) => item.trim().toLowerCase()).filter(Boolean);
|
|
755
|
+
if (names.includes("*") || names.includes(header)) return null;
|
|
756
|
+
return names.length ? `${text}, ${header}` : header;
|
|
757
|
+
}
|
|
758
|
+
function exposeOnCors(res, header) {
|
|
759
|
+
const original = res.writeHead;
|
|
760
|
+
res.writeHead = function(...args) {
|
|
761
|
+
try {
|
|
762
|
+
const inline = args.slice(1).find(
|
|
763
|
+
(arg) => !!arg && typeof arg === "object" && !Array.isArray(arg)
|
|
764
|
+
);
|
|
765
|
+
const inlineKey = (name) => inline ? Object.keys(inline).find((key) => key.toLowerCase() === name) : void 0;
|
|
766
|
+
const cors = res.getHeader("access-control-allow-origin") !== void 0 || inlineKey("access-control-allow-origin") !== void 0;
|
|
767
|
+
if (cors) {
|
|
768
|
+
const key = inlineKey("access-control-expose-headers");
|
|
769
|
+
const value = exposeHeaderValue(
|
|
770
|
+
key && inline ? inline[key] : res.getHeader("access-control-expose-headers"),
|
|
771
|
+
header
|
|
772
|
+
);
|
|
773
|
+
if (value && key && inline) inline[key] = value;
|
|
774
|
+
else if (value) res.setHeader("access-control-expose-headers", value);
|
|
775
|
+
}
|
|
776
|
+
} catch {
|
|
802
777
|
}
|
|
803
|
-
return
|
|
778
|
+
return original.apply(this, args);
|
|
804
779
|
};
|
|
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
780
|
}
|
|
815
|
-
function
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
781
|
+
function pathOf(req) {
|
|
782
|
+
const raw = req.originalUrl ?? req.url ?? "/";
|
|
783
|
+
return raw.split(/[?#]/)[0] || "/";
|
|
784
|
+
}
|
|
785
|
+
function finishRequest(state, input) {
|
|
786
|
+
if (state.sealed) return;
|
|
787
|
+
state.name = (input.name ?? `${input.method} ${input.route}`).slice(0, 200);
|
|
788
|
+
state.outcome = requestOutcome({
|
|
789
|
+
...state.explicitOutcome ? { explicit: state.outcome } : {},
|
|
790
|
+
...state.error !== void 0 ? { error: state.error } : {},
|
|
791
|
+
...state.errorThrown ? { thrown: true } : {},
|
|
792
|
+
status: input.status,
|
|
793
|
+
aborted: input.aborted
|
|
794
|
+
});
|
|
795
|
+
const span = trace.getActiveSpan();
|
|
796
|
+
if (span?.isRecording()) span.setAttribute("autter.operation.outcome", state.outcome);
|
|
797
|
+
if (input.span) {
|
|
798
|
+
const remote = trace.wrapSpanContext(input.span);
|
|
799
|
+
context.with(
|
|
800
|
+
trace.setSpan(context.active(), remote),
|
|
801
|
+
() => reportExplicitFailure(state)
|
|
824
802
|
);
|
|
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) };
|
|
803
|
+
} else reportExplicitFailure(state);
|
|
804
|
+
emitSummary(state, {
|
|
805
|
+
http: {
|
|
806
|
+
"http.request.method": input.method,
|
|
807
|
+
"http.route": input.route,
|
|
808
|
+
"http.response.status_code": input.status,
|
|
809
|
+
"autter.request.aborted": input.aborted
|
|
810
|
+
},
|
|
811
|
+
...input.span ? { span: input.span } : {}
|
|
812
|
+
});
|
|
840
813
|
}
|
|
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."))
|
|
814
|
+
function beginNodeRequest(req, res, options, route) {
|
|
815
|
+
const header = (options.requestIdHeader ?? DEFAULT_REQUEST_ID_HEADER).toLowerCase();
|
|
816
|
+
const requestId = resolveRequestId(
|
|
817
|
+
options.trustRequestId === false ? void 0 : req.headers[header]
|
|
850
818
|
);
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
819
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
820
|
+
const path = pathOf(req);
|
|
821
|
+
const state = createRequestState({
|
|
822
|
+
name: `${method} ${normalizeRoutePath(path)}`,
|
|
823
|
+
requestId,
|
|
824
|
+
req,
|
|
825
|
+
res
|
|
857
826
|
});
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
827
|
+
req[REQUEST_STATE] = state;
|
|
828
|
+
try {
|
|
829
|
+
if (!res.headersSent) res.setHeader(header, requestId);
|
|
830
|
+
exposeOnCors(res, header);
|
|
831
|
+
} catch {
|
|
832
|
+
}
|
|
833
|
+
const span = trace.getActiveSpan();
|
|
834
|
+
if (span?.isRecording())
|
|
835
|
+
span.setAttributes({
|
|
836
|
+
"autter.request.id": requestId,
|
|
837
|
+
"autter.operation.kind": "request"
|
|
838
|
+
});
|
|
839
|
+
const spanContext = span?.spanContext();
|
|
840
|
+
bindEmitter(req, state);
|
|
841
|
+
bindEmitter(res, state);
|
|
842
|
+
let finished = false;
|
|
843
|
+
const done = () => {
|
|
844
|
+
finishRequest(state, {
|
|
845
|
+
method,
|
|
846
|
+
route: route?.() ?? expressRouteOf(req) ?? normalizeRoutePath(path),
|
|
847
|
+
status: res.statusCode,
|
|
848
|
+
aborted: !finished,
|
|
849
|
+
...spanContext ? { span: spanContext } : {}
|
|
850
|
+
});
|
|
851
|
+
};
|
|
852
|
+
res.once("finish", () => {
|
|
853
|
+
finished = true;
|
|
854
|
+
done();
|
|
855
|
+
});
|
|
856
|
+
res.once("close", done);
|
|
857
|
+
return state;
|
|
858
|
+
}
|
|
859
|
+
function autterRequests(options = {}) {
|
|
860
|
+
const ignored = compileIgnore(options.ignore);
|
|
861
|
+
return function autterRequestsMiddleware(req, res, next) {
|
|
862
|
+
if (req[REQUEST_STATE] || ignored(pathOf(req))) {
|
|
863
|
+
next();
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const state = beginNodeRequest(req, res, options);
|
|
867
|
+
local.run(state, () => next());
|
|
868
868
|
};
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
869
|
+
}
|
|
870
|
+
function autterErrorResponse(options = {}) {
|
|
871
|
+
return function autterErrorResponseMiddleware(error, req, res, next) {
|
|
872
|
+
const state = req[REQUEST_STATE];
|
|
873
|
+
if (state) noteOperationError(error, true, state);
|
|
874
|
+
captureAtBoundary(error, state, options.capture);
|
|
875
|
+
if (res.headersSent) {
|
|
876
|
+
next(error);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
const header = (options.requestIdHeader ?? DEFAULT_REQUEST_ID_HEADER).toLowerCase();
|
|
880
|
+
const inbound = req.headers?.[header];
|
|
881
|
+
const requestId = state?.requestId ?? (typeof inbound === "string" ? inbound.slice(0, 128) : void 0);
|
|
882
|
+
res.statusCode = responseStatusOf(error);
|
|
883
|
+
res.setHeader("content-type", "application/json; charset=utf-8");
|
|
884
|
+
res.end(JSON.stringify(toClientError(error, requestId)));
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
var RESOURCE = /* @__PURE__ */ Symbol.for("autter.runtime.resource");
|
|
888
|
+
function autterFastify(fastify, options, done) {
|
|
889
|
+
const opts = options ?? {};
|
|
890
|
+
const header = (opts.requestIdHeader ?? DEFAULT_REQUEST_ID_HEADER).toLowerCase();
|
|
891
|
+
const ignored = compileIgnore(opts.ignore);
|
|
892
|
+
const stateOf = (request) => request[REQUEST_STATE];
|
|
893
|
+
fastify.addHook("onRequest", ((request, reply, next) => {
|
|
894
|
+
if (ignored(request.url) || request.raw[REQUEST_STATE]) {
|
|
895
|
+
next();
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const requestId = resolveRequestId(
|
|
899
|
+
opts.trustRequestId === false ? void 0 : request.headers[header]
|
|
872
900
|
);
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
901
|
+
const method = String(request.method ?? "GET").toUpperCase();
|
|
902
|
+
const state = createRequestState({
|
|
903
|
+
name: `${method} ${normalizeRoutePath(request.url)}`,
|
|
904
|
+
requestId,
|
|
905
|
+
req: request.raw,
|
|
906
|
+
res: reply.raw
|
|
907
|
+
});
|
|
908
|
+
request[REQUEST_STATE] = state;
|
|
909
|
+
request.raw[REQUEST_STATE] = state;
|
|
910
|
+
reply.header(header, requestId);
|
|
911
|
+
const span = trace.getActiveSpan();
|
|
912
|
+
if (span?.isRecording())
|
|
913
|
+
span.setAttributes({
|
|
914
|
+
"autter.request.id": requestId,
|
|
915
|
+
"autter.operation.kind": "request"
|
|
916
|
+
});
|
|
917
|
+
const spanContext = span?.spanContext();
|
|
918
|
+
bindEmitter(request.raw, state);
|
|
919
|
+
bindEmitter(reply.raw, state);
|
|
920
|
+
let finished = false;
|
|
921
|
+
const finish = (aborted) => finishRequest(state, {
|
|
922
|
+
method,
|
|
923
|
+
route: request.routeOptions?.url ?? request.routerPath ?? normalizeRoutePath(request.url),
|
|
924
|
+
status: reply.raw.statusCode || reply.statusCode,
|
|
925
|
+
aborted,
|
|
926
|
+
...spanContext ? { span: spanContext } : {}
|
|
927
|
+
});
|
|
928
|
+
reply.raw.once("finish", () => {
|
|
929
|
+
finished = true;
|
|
930
|
+
finish(false);
|
|
931
|
+
});
|
|
932
|
+
reply.raw.once("close", () => finish(!finished));
|
|
933
|
+
local.run(state, () => {
|
|
934
|
+
request[RESOURCE] = new AsyncResource("autter-request");
|
|
935
|
+
next();
|
|
936
|
+
});
|
|
937
|
+
}));
|
|
938
|
+
fastify.addHook("preHandler", ((request, _reply, next) => {
|
|
939
|
+
const resource = request[RESOURCE];
|
|
940
|
+
if (resource) resource.runInAsyncScope(() => next());
|
|
941
|
+
else next();
|
|
942
|
+
}));
|
|
943
|
+
fastify.addHook("onSend", ((request, reply, payload, next) => {
|
|
944
|
+
try {
|
|
945
|
+
if (stateOf(request) && reply.getHeader("access-control-allow-origin") !== void 0) {
|
|
946
|
+
const value = exposeHeaderValue(reply.getHeader("access-control-expose-headers"), header);
|
|
947
|
+
if (value) reply.header("access-control-expose-headers", value);
|
|
948
|
+
}
|
|
949
|
+
} catch {
|
|
950
|
+
}
|
|
951
|
+
next(null, payload);
|
|
952
|
+
}));
|
|
953
|
+
fastify.addHook("onError", ((request, _reply, error, next) => {
|
|
954
|
+
const state = stateOf(request);
|
|
955
|
+
if (state) noteOperationError(error, true, state);
|
|
956
|
+
captureAtBoundary(error, state);
|
|
957
|
+
next();
|
|
958
|
+
}));
|
|
959
|
+
done();
|
|
960
|
+
}
|
|
961
|
+
autterFastify[/* @__PURE__ */ Symbol.for("skip-override")] = true;
|
|
962
|
+
autterFastify[/* @__PURE__ */ Symbol.for("fastify.display-name")] = "autter-runtime";
|
|
963
|
+
function withHeaders(response, header, requestId) {
|
|
964
|
+
const apply = (target) => {
|
|
965
|
+
target.headers.set(header, requestId);
|
|
966
|
+
if (target.headers.get("access-control-allow-origin") !== null) {
|
|
967
|
+
const value = exposeHeaderValue(
|
|
968
|
+
target.headers.get("access-control-expose-headers") ?? "",
|
|
969
|
+
header
|
|
970
|
+
);
|
|
971
|
+
if (value) target.headers.set("access-control-expose-headers", value);
|
|
972
|
+
}
|
|
973
|
+
return target;
|
|
974
|
+
};
|
|
975
|
+
try {
|
|
976
|
+
return apply(response);
|
|
977
|
+
} catch {
|
|
978
|
+
try {
|
|
979
|
+
return apply(new Response(response.body, response));
|
|
980
|
+
} catch {
|
|
981
|
+
return response;
|
|
982
|
+
}
|
|
877
983
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
984
|
+
}
|
|
985
|
+
function withRuntimeRequest(handler, options = {}) {
|
|
986
|
+
const header = (options.requestIdHeader ?? DEFAULT_REQUEST_ID_HEADER).toLowerCase();
|
|
987
|
+
const ignored = compileIgnore(options.ignore);
|
|
988
|
+
return async (request, ...rest) => {
|
|
989
|
+
let pathname = "/";
|
|
990
|
+
try {
|
|
991
|
+
pathname = new URL(request.url, "http://localhost").pathname;
|
|
992
|
+
} catch {
|
|
993
|
+
}
|
|
994
|
+
if (ignored(pathname)) return handler(request, ...rest);
|
|
995
|
+
const requestId = resolveRequestId(
|
|
996
|
+
options.trustRequestId === false ? void 0 : request.headers.get(header)
|
|
883
997
|
);
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
return { buffered: queue.length + inFlight, dropped };
|
|
897
|
-
}
|
|
898
|
-
async function flushRuntimeLogs() {
|
|
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
|
-
]
|
|
998
|
+
const method = String(request.method ?? "GET").toUpperCase();
|
|
999
|
+
const route = options.route ?? normalizeRoutePath(pathname);
|
|
1000
|
+
const state = createRequestState({
|
|
1001
|
+
name: `${method} ${options.name ?? route}`,
|
|
1002
|
+
requestId,
|
|
1003
|
+
req: request
|
|
1004
|
+
});
|
|
1005
|
+
const span = trace.getActiveSpan();
|
|
1006
|
+
if (span?.isRecording())
|
|
1007
|
+
span.setAttributes({
|
|
1008
|
+
"autter.request.id": requestId,
|
|
1009
|
+
"autter.operation.kind": "request"
|
|
939
1010
|
});
|
|
940
|
-
|
|
941
|
-
|
|
1011
|
+
const spanContext = span?.spanContext();
|
|
1012
|
+
let status = 500;
|
|
1013
|
+
try {
|
|
1014
|
+
const response = await local.run(state, async () => {
|
|
942
1015
|
try {
|
|
943
|
-
|
|
944
|
-
throw new Error("Runtime log flush exceeded 10 seconds");
|
|
945
|
-
const response = await fetch(`${activeConfig.endpoint}/v1/logs`, {
|
|
946
|
-
method: "POST",
|
|
947
|
-
headers: {
|
|
948
|
-
"content-type": "application/json",
|
|
949
|
-
authorization: `Bearer ${activeConfig.apiKey}`
|
|
950
|
-
},
|
|
951
|
-
body,
|
|
952
|
-
signal: AbortSignal.timeout(
|
|
953
|
-
Math.max(1, Math.min(3e3, deadline - Date.now()))
|
|
954
|
-
)
|
|
955
|
-
});
|
|
956
|
-
if (!response.ok)
|
|
957
|
-
throw new Error(`Runtime log export failed (${response.status})`);
|
|
958
|
-
failure = void 0;
|
|
959
|
-
break;
|
|
1016
|
+
return await handler(request, ...rest);
|
|
960
1017
|
} catch (error) {
|
|
961
|
-
|
|
1018
|
+
noteOperationError(error, true, state);
|
|
1019
|
+
captureAtBoundary(error, state, true);
|
|
1020
|
+
throw error;
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
status = response.status;
|
|
1024
|
+
return withHeaders(response, header, requestId);
|
|
1025
|
+
} finally {
|
|
1026
|
+
const aborted = request.signal?.aborted === true;
|
|
1027
|
+
finishRequest(state, {
|
|
1028
|
+
method,
|
|
1029
|
+
route,
|
|
1030
|
+
name: `${method} ${options.name ?? route}`,
|
|
1031
|
+
status,
|
|
1032
|
+
aborted,
|
|
1033
|
+
...spanContext ? { span: spanContext } : {}
|
|
1034
|
+
});
|
|
1035
|
+
if (options.waitUntil) {
|
|
1036
|
+
try {
|
|
1037
|
+
options.waitUntil(flushRuntimeLogs().catch(() => {
|
|
1038
|
+
}));
|
|
1039
|
+
} catch {
|
|
962
1040
|
}
|
|
963
1041
|
}
|
|
964
|
-
inFlight = 0;
|
|
965
|
-
if (failure) {
|
|
966
|
-
failureStreak++;
|
|
967
|
-
queue = [...batch, ...queue];
|
|
968
|
-
queueBytes += inFlightBytes;
|
|
969
|
-
inFlightBytes = 0;
|
|
970
|
-
throw failure;
|
|
971
|
-
}
|
|
972
|
-
inFlightBytes = 0;
|
|
973
|
-
failureStreak = 0;
|
|
974
1042
|
}
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
return
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
function startHookRequest(res) {
|
|
1046
|
+
const req = res.req;
|
|
1047
|
+
if (!req || req[REQUEST_STATE]) return;
|
|
1048
|
+
const state = beginNodeRequest(req, res, {});
|
|
1049
|
+
local.enterWith(state);
|
|
980
1050
|
}
|
|
981
|
-
|
|
982
|
-
|
|
1051
|
+
|
|
1052
|
+
// src/unhandled-rejections.ts
|
|
1053
|
+
var MODES = /* @__PURE__ */ new Set([
|
|
1054
|
+
"throw",
|
|
1055
|
+
"strict",
|
|
1056
|
+
"warn",
|
|
1057
|
+
"warn-with-error-code",
|
|
1058
|
+
"none"
|
|
1059
|
+
]);
|
|
1060
|
+
var LISTENER_MARK = /* @__PURE__ */ Symbol.for("autter.runtime.unhandledRejection.listener");
|
|
1061
|
+
var HANDLED_KEY = /* @__PURE__ */ Symbol.for("autter.runtime.unhandledRejection.handled");
|
|
1062
|
+
function handledPromises() {
|
|
1063
|
+
const registry = globalThis;
|
|
1064
|
+
let set = registry[HANDLED_KEY];
|
|
1065
|
+
if (!set) {
|
|
1066
|
+
set = /* @__PURE__ */ new WeakSet();
|
|
1067
|
+
registry[HANDLED_KEY] = set;
|
|
1068
|
+
}
|
|
1069
|
+
return set;
|
|
1070
|
+
}
|
|
1071
|
+
var recorded = /* @__PURE__ */ new WeakSet();
|
|
1072
|
+
function markRecorded(reason) {
|
|
1073
|
+
if (!reason || typeof reason !== "object") return;
|
|
1074
|
+
recorded.add(reason);
|
|
1075
|
+
setImmediate(() => recorded.delete(reason)).unref?.();
|
|
1076
|
+
}
|
|
1077
|
+
function recordedAsRejection(error) {
|
|
1078
|
+
return !!error && typeof error === "object" && recorded.has(error);
|
|
1079
|
+
}
|
|
1080
|
+
function splitOptions(value) {
|
|
1081
|
+
return value ? value.split(/\s+/).filter(Boolean) : [];
|
|
1082
|
+
}
|
|
1083
|
+
function unhandledRejectionsMode(execArgv = process.execArgv, nodeOptions = process.env.NODE_OPTIONS) {
|
|
1084
|
+
const args = [...splitOptions(nodeOptions), ...execArgv];
|
|
1085
|
+
let mode = "throw";
|
|
1086
|
+
for (let i = 0; i < args.length; i++) {
|
|
1087
|
+
const arg = args[i] ?? "";
|
|
1088
|
+
if (!arg.startsWith("--")) continue;
|
|
1089
|
+
const eq = arg.indexOf("=");
|
|
1090
|
+
const name = (eq < 0 ? arg : arg.slice(0, eq)).replace(/_/g, "-");
|
|
1091
|
+
if (name !== "--unhandled-rejections") continue;
|
|
1092
|
+
const value = eq < 0 ? args[++i] : arg.slice(eq + 1);
|
|
1093
|
+
if (value && MODES.has(value))
|
|
1094
|
+
mode = value;
|
|
1095
|
+
}
|
|
1096
|
+
return mode;
|
|
1097
|
+
}
|
|
1098
|
+
function isErrorLike(value) {
|
|
983
1099
|
try {
|
|
984
|
-
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1100
|
+
return typeof value === "object" && value !== null && Object.prototype.hasOwnProperty.call(value, "stack");
|
|
1101
|
+
} catch {
|
|
1102
|
+
return false;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
function describeReason(reason) {
|
|
1106
|
+
if (typeof reason !== "object" && typeof reason !== "function" || reason === null) {
|
|
1107
|
+
try {
|
|
1108
|
+
return String(reason);
|
|
1109
|
+
} catch {
|
|
1110
|
+
return typeof reason;
|
|
991
1111
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1112
|
+
}
|
|
1113
|
+
try {
|
|
1114
|
+
const proto = Object.getPrototypeOf(reason);
|
|
1115
|
+
const ctor = proto?.constructor;
|
|
1116
|
+
const name = typeof ctor === "function" ? ctor.name : "";
|
|
1117
|
+
return name ? `#<${name}>` : "[object Object]";
|
|
1118
|
+
} catch {
|
|
1119
|
+
return "[object Object]";
|
|
997
1120
|
}
|
|
998
1121
|
}
|
|
999
|
-
function
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
...error instanceof Error ? {
|
|
1009
|
-
"exception.type": error.name,
|
|
1010
|
-
"exception.stacktrace": error.stack ?? ""
|
|
1011
|
-
} : {}
|
|
1012
|
-
})
|
|
1013
|
-
};
|
|
1122
|
+
function toUncaught(reason, from) {
|
|
1123
|
+
if (isErrorLike(reason)) return reason;
|
|
1124
|
+
const error = new Error(
|
|
1125
|
+
`This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "${describeReason(reason)}".`
|
|
1126
|
+
);
|
|
1127
|
+
error.name = "UnhandledPromiseRejection";
|
|
1128
|
+
error.code = "ERR_UNHANDLED_REJECTION";
|
|
1129
|
+
Error.captureStackTrace?.(error, from);
|
|
1130
|
+
return error;
|
|
1014
1131
|
}
|
|
1015
|
-
var
|
|
1016
|
-
function
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1132
|
+
var warningId = 0;
|
|
1133
|
+
function emitNodeWarning(reason) {
|
|
1134
|
+
try {
|
|
1135
|
+
const text = isErrorLike(reason) ? String(reason.stack) : describeReason(reason);
|
|
1136
|
+
process.emitWarning(text, "UnhandledPromiseRejectionWarning");
|
|
1137
|
+
process.emitWarning(
|
|
1138
|
+
`Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag \`--unhandled-rejections=strict\` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: ${++warningId})`,
|
|
1139
|
+
"UnhandledPromiseRejectionWarning"
|
|
1140
|
+
);
|
|
1141
|
+
} catch {
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
function installUnhandledRejectionCapture(capture, mode = unhandledRejectionsMode()) {
|
|
1145
|
+
if (mode === "strict") return () => {
|
|
1029
1146
|
};
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1147
|
+
const listener = (reason, promise) => {
|
|
1148
|
+
try {
|
|
1149
|
+
if (promise && typeof promise === "object") {
|
|
1150
|
+
const handled = handledPromises();
|
|
1151
|
+
if (handled.has(promise)) return;
|
|
1152
|
+
handled.add(promise);
|
|
1036
1153
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
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
|
-
});
|
|
1154
|
+
const alone = process.listeners("unhandledRejection").every((fn) => fn[LISTENER_MARK] === true);
|
|
1155
|
+
if (alone && mode === "throw") {
|
|
1156
|
+
const error = toUncaught(reason, listener);
|
|
1157
|
+
process.nextTick(() => {
|
|
1158
|
+
throw error;
|
|
1159
|
+
});
|
|
1160
|
+
return;
|
|
1060
1161
|
}
|
|
1162
|
+
if (alone && mode === "warn-with-error-code") {
|
|
1163
|
+
emitNodeWarning(reason);
|
|
1164
|
+
process.exitCode = 1;
|
|
1165
|
+
}
|
|
1166
|
+
markRecorded(reason);
|
|
1167
|
+
capture(reason);
|
|
1168
|
+
} catch {
|
|
1061
1169
|
}
|
|
1062
1170
|
};
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
state.outcome = "failed";
|
|
1069
|
-
thrown = error;
|
|
1070
|
-
throw error;
|
|
1071
|
-
} finally {
|
|
1072
|
-
trace.getActiveSpan()?.setAttributes({
|
|
1073
|
-
...traceAttributes(state),
|
|
1074
|
-
"autter.operation.outcome": state.outcome
|
|
1075
|
-
});
|
|
1076
|
-
if (state.outcome === "failed" && !thrown)
|
|
1077
|
-
config?.reportOutcome(
|
|
1078
|
-
state.name,
|
|
1079
|
-
state.message ?? "Operation failed",
|
|
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
|
|
1100
|
-
}
|
|
1101
|
-
);
|
|
1102
|
-
state.sealed = true;
|
|
1103
|
-
}
|
|
1104
|
-
});
|
|
1105
|
-
return config ? config.run(state.name, run, traceAttributes(state)) : run();
|
|
1171
|
+
listener[LISTENER_MARK] = true;
|
|
1172
|
+
process.on("unhandledRejection", listener);
|
|
1173
|
+
return () => {
|
|
1174
|
+
process.off("unhandledRejection", listener);
|
|
1175
|
+
};
|
|
1106
1176
|
}
|
|
1107
1177
|
|
|
1108
1178
|
// src/server.ts
|
|
@@ -1326,36 +1396,43 @@ async function runLlmSpan(tracer, info, fn) {
|
|
|
1326
1396
|
kind: SpanKind.CLIENT,
|
|
1327
1397
|
attributes: llmBaseAttributes(info)
|
|
1328
1398
|
});
|
|
1329
|
-
const
|
|
1399
|
+
const usage = {};
|
|
1400
|
+
let responseModel;
|
|
1401
|
+
const setResponseModel = (model) => {
|
|
1402
|
+
responseModel = model;
|
|
1403
|
+
span.setAttribute("gen_ai.response.model", model);
|
|
1404
|
+
};
|
|
1330
1405
|
const handle = {
|
|
1331
|
-
setUsage: (
|
|
1332
|
-
|
|
1406
|
+
setUsage: (next) => {
|
|
1407
|
+
Object.assign(usage, next);
|
|
1408
|
+
span.setAttributes(usageAttributes(next));
|
|
1409
|
+
},
|
|
1410
|
+
setCost: (usd) => {
|
|
1411
|
+
usage.costUsd = usd;
|
|
1412
|
+
span.setAttribute("autter.llm.cost_usd", usd);
|
|
1413
|
+
},
|
|
1333
1414
|
setResponseModel,
|
|
1334
1415
|
setModel: setResponseModel,
|
|
1335
|
-
setAttributes: (attributes) => span.setAttributes(attributes)
|
|
1416
|
+
setAttributes: (attributes) => span.setAttributes(activeRedactor(attributes))
|
|
1336
1417
|
};
|
|
1337
1418
|
try {
|
|
1338
|
-
const result = await
|
|
1339
|
-
trace2.setSpan(
|
|
1419
|
+
const result = await context2.with(
|
|
1420
|
+
trace2.setSpan(context2.active(), span),
|
|
1340
1421
|
() => fn(handle)
|
|
1341
1422
|
);
|
|
1342
1423
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
1343
1424
|
return result;
|
|
1344
1425
|
} 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
|
-
});
|
|
1426
|
+
recordError(span, err);
|
|
1427
|
+
if (isError(err)) span.setAttribute("error.type", err.name);
|
|
1353
1428
|
throw err;
|
|
1354
1429
|
} finally {
|
|
1355
1430
|
span.end();
|
|
1431
|
+
recordOperationAiUsage({ ...usage, model: responseModel ?? info.model });
|
|
1356
1432
|
}
|
|
1357
1433
|
}
|
|
1358
1434
|
function recordLlmCall(tracer, call) {
|
|
1435
|
+
recordOperationAiUsage({ ...call, model: call.model });
|
|
1359
1436
|
const durationMs = Math.max(0, call.durationMs ?? 0);
|
|
1360
1437
|
const endTime = /* @__PURE__ */ new Date();
|
|
1361
1438
|
const startTime = new Date(endTime.getTime() - durationMs);
|
|
@@ -1368,51 +1445,54 @@ function recordLlmCall(tracer, call) {
|
|
|
1368
1445
|
}
|
|
1369
1446
|
);
|
|
1370
1447
|
if (call.error !== void 0 && call.error !== null) {
|
|
1371
|
-
|
|
1372
|
-
|
|
1448
|
+
recordError(span, call.error);
|
|
1449
|
+
if (isError(call.error)) {
|
|
1373
1450
|
span.setAttribute("error.type", call.error.name);
|
|
1374
1451
|
}
|
|
1375
|
-
span.setStatus({
|
|
1376
|
-
code: SpanStatusCode.ERROR,
|
|
1377
|
-
message: call.error instanceof Error ? call.error.message : String(call.error)
|
|
1378
|
-
});
|
|
1379
1452
|
} else {
|
|
1380
1453
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
1381
1454
|
}
|
|
1382
1455
|
span.end(endTime);
|
|
1383
1456
|
}
|
|
1384
|
-
async function runWithSpan(tracer, name, fn, attributes) {
|
|
1385
|
-
const span = tracer.startSpan(name, {
|
|
1457
|
+
async function runWithSpan(tracer, name, fn, attributes, links) {
|
|
1458
|
+
const span = tracer.startSpan(name, {
|
|
1459
|
+
attributes,
|
|
1460
|
+
...links?.length ? { links } : {}
|
|
1461
|
+
});
|
|
1386
1462
|
try {
|
|
1387
|
-
const result = await
|
|
1388
|
-
trace2.setSpan(
|
|
1463
|
+
const result = await context2.with(
|
|
1464
|
+
trace2.setSpan(context2.active(), span),
|
|
1389
1465
|
fn
|
|
1390
1466
|
);
|
|
1391
1467
|
span.setStatus({ code: SpanStatusCode.OK });
|
|
1392
1468
|
return result;
|
|
1393
1469
|
} catch (err) {
|
|
1394
|
-
|
|
1395
|
-
span.setStatus({
|
|
1396
|
-
code: SpanStatusCode.ERROR,
|
|
1397
|
-
message: err instanceof Error ? err.message : String(err)
|
|
1398
|
-
});
|
|
1470
|
+
recordError(span, err);
|
|
1399
1471
|
throw err;
|
|
1400
1472
|
} finally {
|
|
1401
1473
|
span.end();
|
|
1402
1474
|
}
|
|
1403
1475
|
}
|
|
1404
|
-
function
|
|
1405
|
-
const
|
|
1406
|
-
const
|
|
1407
|
-
if (
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1476
|
+
function recordError(span, err) {
|
|
1477
|
+
const isErr = isError(err);
|
|
1478
|
+
const message = activeRedactor.text(isErr ? err.message : String(err));
|
|
1479
|
+
if (isErr) {
|
|
1480
|
+
span.addEvent("exception", {
|
|
1481
|
+
"exception.type": err.name,
|
|
1482
|
+
"exception.message": message,
|
|
1483
|
+
...err.stack ? { "exception.stacktrace": activeRedactor.text(err.stack) } : {},
|
|
1484
|
+
...breadcrumbEventAttributes(activeRedactor.text, err)
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
1488
|
+
}
|
|
1489
|
+
function redactWithActive(attributes) {
|
|
1490
|
+
return activeRedactor(attributes);
|
|
1411
1491
|
}
|
|
1412
1492
|
function captureExpressRoute(_span, response) {
|
|
1413
1493
|
if (!(response instanceof ServerResponse)) return;
|
|
1414
|
-
const rpcMetadata =
|
|
1415
|
-
if (rpcMetadata?.type !==
|
|
1494
|
+
const rpcMetadata = getRPCMetadata2(context2.active());
|
|
1495
|
+
if (rpcMetadata?.type !== RPCType2.HTTP) return;
|
|
1416
1496
|
const setRoute = () => {
|
|
1417
1497
|
if (rpcMetadata.route) return;
|
|
1418
1498
|
const route = expressRouteOf(response.req);
|
|
@@ -1420,6 +1500,21 @@ function captureExpressRoute(_span, response) {
|
|
|
1420
1500
|
};
|
|
1421
1501
|
response.prependListener("finish", setRoute);
|
|
1422
1502
|
response.prependListener("close", setRoute);
|
|
1503
|
+
if (hookRequests) startHookRequest(response);
|
|
1504
|
+
}
|
|
1505
|
+
var hookRequests = false;
|
|
1506
|
+
function structuredErrorAttributes(error) {
|
|
1507
|
+
const attrs = { ...errorAttributes(error, { causes: false }) };
|
|
1508
|
+
const internal = errorInternal(error);
|
|
1509
|
+
if (internal) {
|
|
1510
|
+
try {
|
|
1511
|
+
attrs["autter.error.internal"] = JSON.stringify(
|
|
1512
|
+
activeRedactor(internal)
|
|
1513
|
+
).slice(0, 4e3);
|
|
1514
|
+
} catch {
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
return attrs;
|
|
1423
1518
|
}
|
|
1424
1519
|
var active = null;
|
|
1425
1520
|
var activeAlwaysOnProvider = null;
|
|
@@ -1431,22 +1526,41 @@ function initAutterServer(options) {
|
|
|
1431
1526
|
""
|
|
1432
1527
|
);
|
|
1433
1528
|
const headers = { authorization: `Bearer ${options.apiKey}` };
|
|
1529
|
+
const traceExporter = () => new CountingExporter(
|
|
1530
|
+
new RedactingSpanExporter(
|
|
1531
|
+
new OTLPTraceExporter({ url: `${endpoint}/v1/traces`, headers }),
|
|
1532
|
+
() => activeRedactor
|
|
1533
|
+
)
|
|
1534
|
+
);
|
|
1434
1535
|
const environment = options.environment ?? process.env.NODE_ENV ?? "production";
|
|
1435
1536
|
if (isDebugEnabled() || options.debug === true) setDebugMode(true);
|
|
1436
1537
|
debugLog(`initialising service=${options.service} endpoint=${endpoint}`);
|
|
1437
1538
|
activeRedactor = makeRedactor(options.redactAttributes ?? true);
|
|
1438
|
-
const
|
|
1539
|
+
const distro = options.distro ?? SDK_IDENTITY;
|
|
1540
|
+
configureCompatCheck({ endpoint, enabled: options.compatCheck !== false, sdk: distro });
|
|
1541
|
+
const resource = new Resource2({
|
|
1439
1542
|
[ATTR_SERVICE_NAME]: options.service,
|
|
1440
|
-
"service.instance.id": (options.instanceId || process.env.AUTTER_RUNTIME_INSTANCE_ID ||
|
|
1543
|
+
"service.instance.id": (options.instanceId || process.env.AUTTER_RUNTIME_INSTANCE_ID || randomUUID()).slice(0, 128),
|
|
1441
1544
|
...options.release ? { [ATTR_SERVICE_VERSION]: options.release } : {},
|
|
1442
1545
|
"deployment.environment": environment,
|
|
1546
|
+
// OTel semconv for SDK distributions: the ingester records which
|
|
1547
|
+
// Autter SDK version each service runs (version compatibility checks).
|
|
1548
|
+
"telemetry.distro.name": distro.name,
|
|
1549
|
+
"telemetry.distro.version": distro.version,
|
|
1443
1550
|
// Tells the ingester request metrics arrive on the metrics pipe, so
|
|
1444
1551
|
// it must not also fold our server spans into usage rollups (that
|
|
1445
1552
|
// would double-count every sampled request).
|
|
1446
1553
|
"autter.metrics_wired": true
|
|
1447
1554
|
});
|
|
1555
|
+
const rootSampler = new TraceIdRatioBasedSampler(options.traceSampleRate ?? 0.01);
|
|
1448
1556
|
const headSampler = new ParentBasedSampler({
|
|
1449
|
-
root:
|
|
1557
|
+
root: rootSampler,
|
|
1558
|
+
// Browsers (runtime-browser's traceparent) send unsampled parents: they
|
|
1559
|
+
// make no sampling decision. Apply this service's own rate to such
|
|
1560
|
+
// requests instead of never head-sampling them. The ratio sampler is
|
|
1561
|
+
// deterministic by trace id, so services sharing a rate still agree
|
|
1562
|
+
// with an upstream service that chose not to sample.
|
|
1563
|
+
remoteParentNotSampled: rootSampler
|
|
1450
1564
|
});
|
|
1451
1565
|
const retainOnError = options.retainTracesOnError !== false;
|
|
1452
1566
|
const slowThresholdMs = Number.isFinite(options.retainTracesAboveMs) ? Math.max(0, options.retainTracesAboveMs) : 0;
|
|
@@ -1454,30 +1568,24 @@ function initAutterServer(options) {
|
|
|
1454
1568
|
let sampler = retainTraces ? new RecordUnsampledSampler(headSampler) : headSampler;
|
|
1455
1569
|
if (options.llmTracing !== false) sampler = new LlmAwareSampler(sampler);
|
|
1456
1570
|
const errorTraceBuffer = retainTraces ? new ErrorTraceRetentionProcessor(
|
|
1457
|
-
new BatchSpanProcessor(
|
|
1458
|
-
new CountingExporter(
|
|
1459
|
-
new OTLPTraceExporter({ url: `${endpoint}/v1/traces`, headers })
|
|
1460
|
-
),
|
|
1461
|
-
{ scheduledDelayMillis: 2e3 }
|
|
1462
|
-
),
|
|
1571
|
+
new BatchSpanProcessor(traceExporter(), { scheduledDelayMillis: 2e3 }),
|
|
1463
1572
|
retainOnError,
|
|
1464
1573
|
slowThresholdMs
|
|
1465
1574
|
) : null;
|
|
1466
|
-
const mainSpanProcessor = new BatchSpanProcessor(
|
|
1467
|
-
new CountingExporter(
|
|
1468
|
-
new OTLPTraceExporter({ url: `${endpoint}/v1/traces`, headers })
|
|
1469
|
-
)
|
|
1470
|
-
);
|
|
1575
|
+
const mainSpanProcessor = new BatchSpanProcessor(traceExporter());
|
|
1471
1576
|
const metricReader = new PeriodicExportingMetricReader({
|
|
1472
|
-
exporter: new
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1577
|
+
exporter: new RedactingMetricExporter(
|
|
1578
|
+
new OTLPMetricExporter({
|
|
1579
|
+
url: `${endpoint}/v1/metrics`,
|
|
1580
|
+
headers,
|
|
1581
|
+
// Deltas, not lifetime totals: the ingester SUMs data points
|
|
1582
|
+
// into runtime_metrics_1m, and the default (cumulative)
|
|
1583
|
+
// temporality would re-count every past request on each
|
|
1584
|
+
// 60 s export.
|
|
1585
|
+
temporalityPreference: AggregationTemporalityPreference.DELTA
|
|
1586
|
+
}),
|
|
1587
|
+
() => activeRedactor
|
|
1588
|
+
),
|
|
1481
1589
|
exportIntervalMillis: options.metricIntervalMs ?? 6e4
|
|
1482
1590
|
});
|
|
1483
1591
|
const sdk = new NodeSDK({
|
|
@@ -1503,16 +1611,14 @@ function initAutterServer(options) {
|
|
|
1503
1611
|
});
|
|
1504
1612
|
sdk.start();
|
|
1505
1613
|
const stopMemoryMetrics = options.memoryMetrics === false ? null : startMemoryMetrics();
|
|
1614
|
+
noteCompatFeature("endpoint_latency");
|
|
1615
|
+
if (stopMemoryMetrics) noteCompatFeature("memory_metrics");
|
|
1616
|
+
if (options.llmTracing !== false) noteCompatFeature("llm_calls");
|
|
1506
1617
|
const alwaysOnProvider = new BasicTracerProvider({
|
|
1507
1618
|
resource,
|
|
1508
1619
|
sampler: new AlwaysOnSampler(),
|
|
1509
1620
|
spanProcessors: [
|
|
1510
|
-
new BatchSpanProcessor(
|
|
1511
|
-
new CountingExporter(
|
|
1512
|
-
new OTLPTraceExporter({ url: `${endpoint}/v1/traces`, headers })
|
|
1513
|
-
),
|
|
1514
|
-
{ scheduledDelayMillis: 2e3 }
|
|
1515
|
-
)
|
|
1621
|
+
new BatchSpanProcessor(traceExporter(), { scheduledDelayMillis: 2e3 })
|
|
1516
1622
|
]
|
|
1517
1623
|
});
|
|
1518
1624
|
const errorTracer = alwaysOnProvider.getTracer("autter-errors");
|
|
@@ -1521,53 +1627,73 @@ function initAutterServer(options) {
|
|
|
1521
1627
|
function captureException2(error, attributes) {
|
|
1522
1628
|
errorTraceBuffer?.retainActiveTrace();
|
|
1523
1629
|
telemetryStats.markCaptured();
|
|
1524
|
-
const
|
|
1525
|
-
const message =
|
|
1526
|
-
const
|
|
1630
|
+
const isErr = isError(error);
|
|
1631
|
+
const message = activeRedactor.text(isErr ? error.message : String(error));
|
|
1632
|
+
const stackText = isErr && error.stack ? activeRedactor.text(error.stack) : "";
|
|
1633
|
+
const split = splitFingerprint(attributes);
|
|
1634
|
+
const fingerprint = fingerprintEventAttributes(split.fingerprint, activeRedactor.text);
|
|
1635
|
+
if (fingerprint["autter.fingerprint"]) noteCompatFeature("fingerprint_override");
|
|
1636
|
+
const redacted = activeRedactor(split.attributes);
|
|
1527
1637
|
const activeSpan = trace2.getActiveSpan();
|
|
1528
1638
|
const reuseActive = activeSpan?.isRecording() === true;
|
|
1529
|
-
const span = reuseActive ? activeSpan : errorTracer.startSpan(
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1639
|
+
const span = reuseActive ? activeSpan : errorTracer.startSpan(isErr ? error.name : "Error");
|
|
1640
|
+
const structured = structuredErrorAttributes(error);
|
|
1641
|
+
const causes = errorAttributes(error);
|
|
1642
|
+
const declared = {
|
|
1643
|
+
...Object.fromEntries(Object.entries(causes).filter(([key]) => key !== "autter.error.internal")),
|
|
1644
|
+
...fingerprint
|
|
1645
|
+
};
|
|
1646
|
+
const requestId = currentOperation()?.requestId;
|
|
1647
|
+
span.setAttributes({
|
|
1648
|
+
"autter.severity": "error",
|
|
1649
|
+
...redacted,
|
|
1650
|
+
...structured,
|
|
1651
|
+
...requestId ? { "autter.request.id": requestId } : {}
|
|
1652
|
+
});
|
|
1653
|
+
markCaptured(error);
|
|
1654
|
+
noteOperationError(error);
|
|
1655
|
+
notifyException(error, { ...redacted, ...fingerprint });
|
|
1656
|
+
if (isErr && stackText) {
|
|
1657
|
+
span.addEvent("exception", {
|
|
1658
|
+
"exception.type": error.name,
|
|
1659
|
+
"exception.message": message,
|
|
1660
|
+
"exception.stacktrace": stackText,
|
|
1661
|
+
...redacted["autter.handled"] === true ? { "autter.handled": true, "autter.sampled": redacted["autter.sampled"] === true } : {},
|
|
1662
|
+
...declared,
|
|
1663
|
+
...breadcrumbEventAttributes(activeRedactor.text, error)
|
|
1664
|
+
});
|
|
1543
1665
|
} else {
|
|
1544
1666
|
const stack = new Error().stack?.split("\n").filter((line, i) => i === 0 || !line.includes("captureException")).join("\n");
|
|
1545
1667
|
span.addEvent("exception", {
|
|
1546
|
-
"exception.type":
|
|
1668
|
+
"exception.type": isErr ? error.name : "Error",
|
|
1547
1669
|
"exception.message": message,
|
|
1548
|
-
...stack ? { "exception.stacktrace": stack } : {},
|
|
1549
|
-
...redacted["autter.handled"] === true ? { "autter.handled": true, "autter.sampled": redacted["autter.sampled"] === true } : {}
|
|
1670
|
+
...stack ? { "exception.stacktrace": activeRedactor.text(stack) } : {},
|
|
1671
|
+
...redacted["autter.handled"] === true ? { "autter.handled": true, "autter.sampled": redacted["autter.sampled"] === true } : {},
|
|
1672
|
+
...declared,
|
|
1673
|
+
...breadcrumbEventAttributes(activeRedactor.text, error)
|
|
1550
1674
|
});
|
|
1551
1675
|
}
|
|
1552
1676
|
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
1553
1677
|
if (!reuseActive) span.end();
|
|
1554
1678
|
}
|
|
1555
|
-
function captureMessage2(message,
|
|
1556
|
-
if (
|
|
1679
|
+
function captureMessage2(message, severity = "warning", attributes) {
|
|
1680
|
+
if (severity === "error" || severity === "fatal") {
|
|
1557
1681
|
errorTraceBuffer?.retainActiveTrace();
|
|
1558
1682
|
}
|
|
1559
1683
|
telemetryStats.markCaptured();
|
|
1560
1684
|
const stack = new Error().stack?.split("\n").filter((line, i) => i === 0 || !line.includes("captureMessage")).join("\n");
|
|
1685
|
+
const safeMessage = activeRedactor.text(message);
|
|
1561
1686
|
const span = errorTracer.startSpan("Message", {
|
|
1562
|
-
attributes: { "autter.severity":
|
|
1687
|
+
attributes: { "autter.severity": severity, ...activeRedactor(attributes) }
|
|
1563
1688
|
});
|
|
1564
1689
|
span.addEvent("exception", {
|
|
1565
1690
|
"exception.type": "Message",
|
|
1566
|
-
"exception.message":
|
|
1567
|
-
...stack ? { "exception.stacktrace": stack } : {},
|
|
1568
|
-
"autter.severity":
|
|
1691
|
+
"exception.message": safeMessage,
|
|
1692
|
+
...stack ? { "exception.stacktrace": activeRedactor.text(stack) } : {},
|
|
1693
|
+
"autter.severity": severity,
|
|
1694
|
+
...breadcrumbEventAttributes(activeRedactor.text)
|
|
1569
1695
|
});
|
|
1570
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
1696
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
|
|
1571
1697
|
span.end();
|
|
1572
1698
|
}
|
|
1573
1699
|
function reportOutcome2(name, message, attributes) {
|
|
@@ -1593,18 +1719,29 @@ function initAutterServer(options) {
|
|
|
1593
1719
|
apiKey: options.apiKey,
|
|
1594
1720
|
service: options.service,
|
|
1595
1721
|
environment,
|
|
1722
|
+
distro,
|
|
1596
1723
|
release: options.release,
|
|
1597
1724
|
options: options.logging,
|
|
1598
1725
|
redact: activeRedactor,
|
|
1599
|
-
run: (name, fn, attributes) => runWithSpan(processTracer, name, fn, activeRedactor(attributes)),
|
|
1726
|
+
run: (name, fn, attributes, links) => runWithSpan(processTracer, name, fn, activeRedactor(attributes), links),
|
|
1600
1727
|
reportOutcome: reportOutcome2
|
|
1601
1728
|
});
|
|
1729
|
+
hookRequests = options.logging?.requests === true;
|
|
1730
|
+
let onCrash = null;
|
|
1731
|
+
let uninstallRejections = null;
|
|
1602
1732
|
if (options.captureGlobalErrors !== false) {
|
|
1603
|
-
|
|
1733
|
+
onCrash = (err) => {
|
|
1734
|
+
if (recordedAsRejection(err)) return;
|
|
1604
1735
|
captureException2(err, { "autter.unhandled": true });
|
|
1605
1736
|
if (options.autoFlush === false) void Promise.resolve(flushTarget.forceFlush()).catch(() => {
|
|
1606
1737
|
});
|
|
1607
|
-
}
|
|
1738
|
+
};
|
|
1739
|
+
process.on("uncaughtExceptionMonitor", onCrash);
|
|
1740
|
+
if (options.captureUnhandledRejections !== false) {
|
|
1741
|
+
uninstallRejections = installUnhandledRejectionCapture(
|
|
1742
|
+
(reason) => captureException2(reason, { "autter.unhandled_rejection": true })
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1608
1745
|
}
|
|
1609
1746
|
const flushTarget = {
|
|
1610
1747
|
forceFlush: async () => {
|
|
@@ -1637,6 +1774,9 @@ function initAutterServer(options) {
|
|
|
1637
1774
|
trackLlmCall: (call) => recordLlmCall(llmTracer, call),
|
|
1638
1775
|
shutdown: async () => {
|
|
1639
1776
|
const logShutdown = await Promise.allSettled([shutdownRuntimeLogger()]);
|
|
1777
|
+
hookRequests = false;
|
|
1778
|
+
if (onCrash) process.off("uncaughtExceptionMonitor", onCrash);
|
|
1779
|
+
uninstallRejections?.();
|
|
1640
1780
|
stopMemoryMetrics?.();
|
|
1641
1781
|
active = null;
|
|
1642
1782
|
activeAlwaysOnProvider = null;
|
|
@@ -1657,17 +1797,41 @@ function captureException(error, attributes) {
|
|
|
1657
1797
|
active.captureException(error, attributes);
|
|
1658
1798
|
return;
|
|
1659
1799
|
}
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1800
|
+
if (loggerOnlyCapture) {
|
|
1801
|
+
loggerOnlyCapture(error, attributes);
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
markCaptured(error);
|
|
1805
|
+
noteOperationError(error);
|
|
1806
|
+
const split = splitFingerprint(attributes);
|
|
1807
|
+
const fingerprint = fingerprintEventAttributes(split.fingerprint, activeRedactor.text);
|
|
1808
|
+
notifyException(error, { ...split.attributes, ...fingerprint });
|
|
1809
|
+
const span = trace2.getTracer("autter-errors").startSpan(isError(error) ? error.name : "Error", {
|
|
1810
|
+
attributes: { ...activeRedactor(split.attributes), ...fingerprint }
|
|
1665
1811
|
});
|
|
1812
|
+
recordError(span, error);
|
|
1666
1813
|
span.end();
|
|
1667
1814
|
}
|
|
1815
|
+
var loggerOnlyCapture = null;
|
|
1816
|
+
function setLoggerOnlyCapture(fn) {
|
|
1817
|
+
loggerOnlyCapture = fn;
|
|
1818
|
+
}
|
|
1819
|
+
function serverActive() {
|
|
1820
|
+
return active !== null;
|
|
1821
|
+
}
|
|
1822
|
+
setRequestExceptionCapturer((error, attributes) => captureException(error, attributes));
|
|
1823
|
+
setRequestSpanAnnotator((attributes) => {
|
|
1824
|
+
const span = trace2.getActiveSpan();
|
|
1825
|
+
if (span?.isRecording()) span.setAttributes(activeRedactor(splitFingerprint(attributes).attributes));
|
|
1826
|
+
});
|
|
1668
1827
|
function withProcessSpan(name, fn, attributes) {
|
|
1669
1828
|
if (active) return active.withProcessSpan(name, fn, attributes);
|
|
1670
|
-
return runWithSpan(
|
|
1829
|
+
return runWithSpan(
|
|
1830
|
+
trace2.getTracer("autter-processes"),
|
|
1831
|
+
name,
|
|
1832
|
+
fn,
|
|
1833
|
+
activeRedactor(attributes)
|
|
1834
|
+
);
|
|
1671
1835
|
}
|
|
1672
1836
|
function withLlmCall(info, fn) {
|
|
1673
1837
|
if (active) return active.withLlmCall(info, fn);
|
|
@@ -1680,20 +1844,22 @@ function trackLlmCall(call) {
|
|
|
1680
1844
|
}
|
|
1681
1845
|
recordLlmCall(trace2.getTracer("autter-llm"), call);
|
|
1682
1846
|
}
|
|
1683
|
-
function captureMessage(message,
|
|
1847
|
+
function captureMessage(message, severity = "warning", attributes) {
|
|
1684
1848
|
if (active) {
|
|
1685
|
-
active.captureMessage(message,
|
|
1849
|
+
active.captureMessage(message, severity, attributes);
|
|
1686
1850
|
return;
|
|
1687
1851
|
}
|
|
1852
|
+
const safeMessage = activeRedactor.text(message);
|
|
1688
1853
|
const span = trace2.getTracer("autter-errors").startSpan("Message", {
|
|
1689
|
-
attributes: { "autter.severity":
|
|
1854
|
+
attributes: { "autter.severity": severity, ...activeRedactor(attributes) }
|
|
1690
1855
|
});
|
|
1691
1856
|
span.addEvent("exception", {
|
|
1692
1857
|
"exception.type": "Message",
|
|
1693
|
-
"exception.message":
|
|
1694
|
-
"autter.severity":
|
|
1858
|
+
"exception.message": safeMessage,
|
|
1859
|
+
"autter.severity": severity,
|
|
1860
|
+
...breadcrumbEventAttributes(activeRedactor.text)
|
|
1695
1861
|
});
|
|
1696
|
-
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
1862
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
|
|
1697
1863
|
span.end();
|
|
1698
1864
|
}
|
|
1699
1865
|
function reportOutcome(name, message, attributes) {
|
|
@@ -1715,7 +1881,7 @@ function makeSafeCapture(options) {
|
|
|
1715
1881
|
const clean = (attributes) => attributes ? { ...redactAttributes(attributes, options) } : void 0;
|
|
1716
1882
|
return {
|
|
1717
1883
|
captureException: (error, attributes) => captureException(error, clean(attributes)),
|
|
1718
|
-
captureMessage: (message,
|
|
1884
|
+
captureMessage: (message, severity, attributes) => captureMessage(message, severity ?? "warning", clean(attributes))
|
|
1719
1885
|
};
|
|
1720
1886
|
}
|
|
1721
1887
|
function alwaysOnTracer(name) {
|
|
@@ -1787,15 +1953,26 @@ function extractUsage(result) {
|
|
|
1787
1953
|
const r = result;
|
|
1788
1954
|
const usage = r?.usage ?? r?.message?.usage;
|
|
1789
1955
|
const meta = r?.usageMetadata;
|
|
1956
|
+
const details = usage?.prompt_tokens_details;
|
|
1957
|
+
const cached = usage?.cache_read_input_tokens ?? (typeof details?.cached_tokens === "number" ? details.cached_tokens : void 0) ?? meta?.cachedContentTokenCount;
|
|
1790
1958
|
const input = usage?.prompt_tokens ?? usage?.input_tokens ?? meta?.promptTokenCount ?? void 0;
|
|
1791
1959
|
const output = usage?.completion_tokens ?? usage?.output_tokens ?? meta?.candidatesTokenCount ?? void 0;
|
|
1792
1960
|
const responseModel = r?.model ?? r?.modelVersion;
|
|
1793
1961
|
return {
|
|
1794
1962
|
inputTokens: typeof input === "number" ? input : void 0,
|
|
1795
1963
|
outputTokens: typeof output === "number" ? output : void 0,
|
|
1964
|
+
cacheReadTokens: typeof cached === "number" ? cached : void 0,
|
|
1796
1965
|
responseModel: typeof responseModel === "string" ? responseModel : void 0
|
|
1797
1966
|
};
|
|
1798
1967
|
}
|
|
1968
|
+
function rollUp(model, usage) {
|
|
1969
|
+
recordOperationAiUsage({
|
|
1970
|
+
model: usage.responseModel ?? model,
|
|
1971
|
+
...usage.inputTokens !== void 0 ? { inputTokens: usage.inputTokens } : {},
|
|
1972
|
+
...usage.outputTokens !== void 0 ? { outputTokens: usage.outputTokens } : {},
|
|
1973
|
+
...usage.cacheReadTokens !== void 0 ? { cacheReadTokens: usage.cacheReadTokens } : {}
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1799
1976
|
function applyUsage(span, usage) {
|
|
1800
1977
|
if (usage.inputTokens !== void 0) {
|
|
1801
1978
|
span.setAttribute("gen_ai.usage.input_tokens", usage.inputTokens);
|
|
@@ -1808,14 +1985,10 @@ function applyUsage(span, usage) {
|
|
|
1808
1985
|
}
|
|
1809
1986
|
}
|
|
1810
1987
|
function endWithError(span, err) {
|
|
1811
|
-
|
|
1812
|
-
span.setStatus({
|
|
1813
|
-
code: SpanStatusCode2.ERROR,
|
|
1814
|
-
message: err instanceof Error ? err.message : String(err)
|
|
1815
|
-
});
|
|
1988
|
+
recordError(span, err);
|
|
1816
1989
|
span.end();
|
|
1817
1990
|
}
|
|
1818
|
-
function wrapStream(stream, span) {
|
|
1991
|
+
function wrapStream(stream, span, model) {
|
|
1819
1992
|
let ended = false;
|
|
1820
1993
|
const seen = {
|
|
1821
1994
|
inputTokens: void 0,
|
|
@@ -1826,6 +1999,7 @@ function wrapStream(stream, span) {
|
|
|
1826
1999
|
if (ended) return;
|
|
1827
2000
|
ended = true;
|
|
1828
2001
|
applyUsage(span, seen);
|
|
2002
|
+
rollUp(model, seen);
|
|
1829
2003
|
span.setAttribute("autter.llm.streamed", true);
|
|
1830
2004
|
if (err !== void 0) {
|
|
1831
2005
|
endWithError(span, err);
|
|
@@ -1847,6 +2021,7 @@ function wrapStream(stream, span) {
|
|
|
1847
2021
|
const usage = extractUsage(step.value);
|
|
1848
2022
|
if (usage.inputTokens !== void 0) seen.inputTokens = usage.inputTokens;
|
|
1849
2023
|
if (usage.outputTokens !== void 0) seen.outputTokens = usage.outputTokens;
|
|
2024
|
+
if (usage.cacheReadTokens !== void 0) seen.cacheReadTokens = usage.cacheReadTokens;
|
|
1850
2025
|
if (usage.responseModel) seen.responseModel = usage.responseModel;
|
|
1851
2026
|
} else {
|
|
1852
2027
|
finish();
|
|
@@ -1857,9 +2032,9 @@ function wrapStream(stream, span) {
|
|
|
1857
2032
|
throw err;
|
|
1858
2033
|
}
|
|
1859
2034
|
},
|
|
1860
|
-
async return(
|
|
2035
|
+
async return(value2) {
|
|
1861
2036
|
finish();
|
|
1862
|
-
return inner.return ? inner.return(
|
|
2037
|
+
return inner.return ? inner.return(value2) : { done: true, value: void 0 };
|
|
1863
2038
|
},
|
|
1864
2039
|
async throw(err) {
|
|
1865
2040
|
finish(err);
|
|
@@ -1869,8 +2044,8 @@ function wrapStream(stream, span) {
|
|
|
1869
2044
|
};
|
|
1870
2045
|
};
|
|
1871
2046
|
}
|
|
1872
|
-
const
|
|
1873
|
-
return typeof
|
|
2047
|
+
const value = Reflect.get(target, prop, receiver);
|
|
2048
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
1874
2049
|
}
|
|
1875
2050
|
});
|
|
1876
2051
|
}
|
|
@@ -1892,7 +2067,7 @@ function instrumentLlmClient(client, options) {
|
|
|
1892
2067
|
"gen_ai.system": provider,
|
|
1893
2068
|
"gen_ai.request.model": request.model,
|
|
1894
2069
|
...userId ? { "autter.user_id": userId } : {},
|
|
1895
|
-
...options?.attributes
|
|
2070
|
+
...redactWithActive(options?.attributes)
|
|
1896
2071
|
}
|
|
1897
2072
|
});
|
|
1898
2073
|
let result;
|
|
@@ -1904,9 +2079,11 @@ function instrumentLlmClient(client, options) {
|
|
|
1904
2079
|
}
|
|
1905
2080
|
if (!(result instanceof Promise)) {
|
|
1906
2081
|
if (result && typeof result === "object" && Symbol.asyncIterator in result) {
|
|
1907
|
-
return wrapStream(result, span);
|
|
2082
|
+
return wrapStream(result, span, request.model);
|
|
1908
2083
|
}
|
|
1909
|
-
|
|
2084
|
+
const usage = extractUsage(result);
|
|
2085
|
+
applyUsage(span, usage);
|
|
2086
|
+
rollUp(request.model, usage);
|
|
1910
2087
|
span.setStatus({ code: SpanStatusCode2.OK });
|
|
1911
2088
|
span.end();
|
|
1912
2089
|
return result;
|
|
@@ -1914,9 +2091,11 @@ function instrumentLlmClient(client, options) {
|
|
|
1914
2091
|
return result.then(
|
|
1915
2092
|
(resolved) => {
|
|
1916
2093
|
if (resolved && typeof resolved === "object" && Symbol.asyncIterator in resolved) {
|
|
1917
|
-
return wrapStream(resolved, span);
|
|
2094
|
+
return wrapStream(resolved, span, request.model);
|
|
1918
2095
|
}
|
|
1919
|
-
|
|
2096
|
+
const usage = extractUsage(resolved);
|
|
2097
|
+
applyUsage(span, usage);
|
|
2098
|
+
rollUp(request.model, usage);
|
|
1920
2099
|
span.setStatus({ code: SpanStatusCode2.OK });
|
|
1921
2100
|
span.end();
|
|
1922
2101
|
return resolved;
|
|
@@ -1932,30 +2111,30 @@ function instrumentLlmClient(client, options) {
|
|
|
1932
2111
|
if (cached) return cached;
|
|
1933
2112
|
const proxy = new Proxy(target, {
|
|
1934
2113
|
get(t, prop, receiver) {
|
|
1935
|
-
const
|
|
1936
|
-
if (typeof prop === "symbol") return
|
|
2114
|
+
const value = Reflect.get(t, prop, receiver);
|
|
2115
|
+
if (typeof prop === "symbol") return value;
|
|
1937
2116
|
const propPath = path ? `${path}.${prop}` : prop;
|
|
1938
|
-
if (typeof
|
|
2117
|
+
if (typeof value === "function") {
|
|
1939
2118
|
if (CALL_METHODS.has(prop)) {
|
|
1940
2119
|
return (...args) => {
|
|
1941
2120
|
const first = args[0];
|
|
1942
2121
|
if (first && typeof first === "object" && typeof first.model === "string") {
|
|
1943
2122
|
return traceCall(
|
|
1944
|
-
|
|
2123
|
+
value,
|
|
1945
2124
|
t,
|
|
1946
2125
|
propPath,
|
|
1947
2126
|
args
|
|
1948
2127
|
);
|
|
1949
2128
|
}
|
|
1950
|
-
return
|
|
2129
|
+
return value.apply(t, args);
|
|
1951
2130
|
};
|
|
1952
2131
|
}
|
|
1953
|
-
return
|
|
2132
|
+
return value.bind(t);
|
|
1954
2133
|
}
|
|
1955
|
-
if (
|
|
1956
|
-
return deepProxy(
|
|
2134
|
+
if (value !== null && typeof value === "object" && !(value instanceof Promise)) {
|
|
2135
|
+
return deepProxy(value, propPath);
|
|
1957
2136
|
}
|
|
1958
|
-
return
|
|
2137
|
+
return value;
|
|
1959
2138
|
}
|
|
1960
2139
|
});
|
|
1961
2140
|
proxied.set(target, proxy);
|
|
@@ -2007,26 +2186,300 @@ ${frames.join("\n")}`;
|
|
|
2007
2186
|
});
|
|
2008
2187
|
} };
|
|
2009
2188
|
}
|
|
2189
|
+
|
|
2190
|
+
// src/framework-errors.ts
|
|
2191
|
+
function recordBoundaryError(error, req, rule) {
|
|
2192
|
+
try {
|
|
2193
|
+
const state = requestStateOf(req);
|
|
2194
|
+
if (state) noteOperationError(error, false, state);
|
|
2195
|
+
captureAtBoundary(error, state, rule);
|
|
2196
|
+
} catch {
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
function autterErrorHandler(options = {}) {
|
|
2200
|
+
return function autterErrorHandlerMiddleware(error, req, _res, next) {
|
|
2201
|
+
recordBoundaryError(error, req, options.capture);
|
|
2202
|
+
next(error);
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
function autterFastifyErrors(fastify, options, done) {
|
|
2206
|
+
const rule = options?.capture;
|
|
2207
|
+
fastify.addHook("onError", ((request, _reply, error, next) => {
|
|
2208
|
+
recordBoundaryError(error, request?.raw, rule);
|
|
2209
|
+
next();
|
|
2210
|
+
}));
|
|
2211
|
+
done?.();
|
|
2212
|
+
}
|
|
2213
|
+
autterFastifyErrors[/* @__PURE__ */ Symbol.for("skip-override")] = true;
|
|
2214
|
+
autterFastifyErrors[/* @__PURE__ */ Symbol.for("fastify.display-name")] = "autter-runtime-errors";
|
|
2215
|
+
function autterKoa(options = {}) {
|
|
2216
|
+
return async function autterKoaMiddleware(ctx, next) {
|
|
2217
|
+
try {
|
|
2218
|
+
await next();
|
|
2219
|
+
} catch (error) {
|
|
2220
|
+
recordBoundaryError(error, ctx?.req, options.capture);
|
|
2221
|
+
throw error;
|
|
2222
|
+
}
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
// src/logging-only.ts
|
|
2227
|
+
import {
|
|
2228
|
+
SpanStatusCode as SpanStatusCode3,
|
|
2229
|
+
trace as trace3
|
|
2230
|
+
} from "@opentelemetry/api";
|
|
2231
|
+
var activeLogging = null;
|
|
2232
|
+
function initAutterLogging(options) {
|
|
2233
|
+
if (activeLogging) return activeLogging;
|
|
2234
|
+
if (serverActive()) {
|
|
2235
|
+
console.warn(
|
|
2236
|
+
"[autter-runtime] initAutterLogging() ignored: initAutterServer() is already active and includes logging"
|
|
2237
|
+
);
|
|
2238
|
+
return {
|
|
2239
|
+
captureException,
|
|
2240
|
+
flush: flushRuntimeLogs,
|
|
2241
|
+
shutdown: async () => {
|
|
2242
|
+
}
|
|
2243
|
+
};
|
|
2244
|
+
}
|
|
2245
|
+
if (isDebugEnabled() || options.debug === true) setDebugMode(true);
|
|
2246
|
+
const endpoint = (options.endpoint ?? "https://otlp.autter.dev").replace(/\/$/, "");
|
|
2247
|
+
const environment = options.environment ?? process.env.NODE_ENV ?? "production";
|
|
2248
|
+
const redact = makeRedactor(options.redactAttributes ?? true);
|
|
2249
|
+
const mode = options.exceptions ?? "auto";
|
|
2250
|
+
debugLog(`initialising logger-only service=${options.service} endpoint=${endpoint}`);
|
|
2251
|
+
function captureException2(error, attributes) {
|
|
2252
|
+
markCaptured(error);
|
|
2253
|
+
const split = splitFingerprint(attributes);
|
|
2254
|
+
const fingerprint = fingerprintEventAttributes(split.fingerprint, redact.text);
|
|
2255
|
+
const redacted = redact(split.attributes);
|
|
2256
|
+
const span = trace3.getActiveSpan();
|
|
2257
|
+
if (mode === "auto" && span?.isRecording()) {
|
|
2258
|
+
noteOperationError(error);
|
|
2259
|
+
notifyException(error, { ...redacted, ...fingerprint });
|
|
2260
|
+
span.setAttributes({ "autter.severity": "error", ...redacted, ...structuredErrorAttributes(error) });
|
|
2261
|
+
const declared = { ...errorAttributes(error), ...fingerprint };
|
|
2262
|
+
if (isError(error)) {
|
|
2263
|
+
span.addEvent("exception", {
|
|
2264
|
+
"exception.type": error.name,
|
|
2265
|
+
"exception.message": error.message,
|
|
2266
|
+
...error.stack ? { "exception.stacktrace": error.stack } : {},
|
|
2267
|
+
...declared
|
|
2268
|
+
});
|
|
2269
|
+
} else {
|
|
2270
|
+
span.addEvent("exception", {
|
|
2271
|
+
"exception.type": "Error",
|
|
2272
|
+
"exception.message": String(error),
|
|
2273
|
+
...declared
|
|
2274
|
+
});
|
|
2275
|
+
}
|
|
2276
|
+
span.setStatus({
|
|
2277
|
+
code: SpanStatusCode3.ERROR,
|
|
2278
|
+
message: isError(error) ? error.message : String(error)
|
|
2279
|
+
});
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
notifyException(error, { ...redacted, ...fingerprint });
|
|
2283
|
+
captureExceptionAsLog(error, { ...redacted, ...fingerprint });
|
|
2284
|
+
}
|
|
2285
|
+
const captured = /* @__PURE__ */ new WeakSet();
|
|
2286
|
+
configureRuntimeLogger({
|
|
2287
|
+
endpoint,
|
|
2288
|
+
apiKey: options.apiKey ?? "",
|
|
2289
|
+
service: options.service,
|
|
2290
|
+
environment,
|
|
2291
|
+
...options.release ? { release: options.release } : {},
|
|
2292
|
+
...options.logging ? { options: options.logging } : {},
|
|
2293
|
+
redact,
|
|
2294
|
+
reportOutcome: (name, message, attributes) => {
|
|
2295
|
+
runtimeLogger.error(message, {
|
|
2296
|
+
...attributes,
|
|
2297
|
+
"autter.outcome.name": name.slice(0, 200),
|
|
2298
|
+
"autter.outcome.message": message.slice(0, 1e3)
|
|
2299
|
+
});
|
|
2300
|
+
},
|
|
2301
|
+
captureThrown: (error) => {
|
|
2302
|
+
if (error && typeof error === "object") {
|
|
2303
|
+
if (captured.has(error)) return;
|
|
2304
|
+
captured.add(error);
|
|
2305
|
+
}
|
|
2306
|
+
captureException2(error);
|
|
2307
|
+
}
|
|
2308
|
+
});
|
|
2309
|
+
setLoggerOnlyCapture(captureException2);
|
|
2310
|
+
const onCrash = (error) => {
|
|
2311
|
+
if (recordedAsRejection(error)) return;
|
|
2312
|
+
captureException2(error, { "autter.unhandled": true });
|
|
2313
|
+
};
|
|
2314
|
+
let uninstallRejections = null;
|
|
2315
|
+
if (options.captureGlobalErrors !== false) {
|
|
2316
|
+
process.on("uncaughtExceptionMonitor", onCrash);
|
|
2317
|
+
if (options.captureUnhandledRejections !== false)
|
|
2318
|
+
uninstallRejections = installUnhandledRejectionCapture(
|
|
2319
|
+
(reason) => captureException2(reason, { "autter.unhandled_rejection": true })
|
|
2320
|
+
);
|
|
2321
|
+
}
|
|
2322
|
+
registerFlushTarget("logging", { forceFlush: () => flushRuntimeLogs() });
|
|
2323
|
+
let autoFlushHandle = options.autoFlush === false ? null : installAutterAutoFlush();
|
|
2324
|
+
const handle = {
|
|
2325
|
+
captureException: captureException2,
|
|
2326
|
+
flush: flushRuntimeLogs,
|
|
2327
|
+
shutdown: async () => {
|
|
2328
|
+
process.off("uncaughtExceptionMonitor", onCrash);
|
|
2329
|
+
uninstallRejections?.();
|
|
2330
|
+
uninstallRejections = null;
|
|
2331
|
+
setLoggerOnlyCapture(null);
|
|
2332
|
+
autoFlushHandle?.dispose();
|
|
2333
|
+
autoFlushHandle = null;
|
|
2334
|
+
unregisterFlushTargets();
|
|
2335
|
+
activeLogging = null;
|
|
2336
|
+
await shutdownRuntimeLogger();
|
|
2337
|
+
}
|
|
2338
|
+
};
|
|
2339
|
+
activeLogging = handle;
|
|
2340
|
+
return handle;
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
// src/enrichers.ts
|
|
2344
|
+
function headerOf(req, name) {
|
|
2345
|
+
const headers = req?.headers;
|
|
2346
|
+
if (!headers) return void 0;
|
|
2347
|
+
if (typeof headers.get === "function") {
|
|
2348
|
+
return headers.get(name) ?? void 0;
|
|
2349
|
+
}
|
|
2350
|
+
const value = headers[name.toLowerCase()];
|
|
2351
|
+
if (Array.isArray(value)) return value[0] === void 0 ? void 0 : String(value[0]);
|
|
2352
|
+
return value === void 0 || value === null ? void 0 : String(value);
|
|
2353
|
+
}
|
|
2354
|
+
function enrichUserAgent() {
|
|
2355
|
+
return (event, { req }) => {
|
|
2356
|
+
const ua = headerOf(req, "user-agent");
|
|
2357
|
+
if (!ua) return;
|
|
2358
|
+
const info = parseUserAgent(ua);
|
|
2359
|
+
if (info.browser) event.attributes["user_agent.browser"] = info.browser;
|
|
2360
|
+
if (info.os) event.attributes["user_agent.os"] = info.os;
|
|
2361
|
+
event.attributes["user_agent.device"] = info.device;
|
|
2362
|
+
event.attributes["user_agent.bot"] = info.bot;
|
|
2363
|
+
};
|
|
2364
|
+
}
|
|
2365
|
+
function enrichRequestSize() {
|
|
2366
|
+
return (event, { req, res }) => {
|
|
2367
|
+
const request = Number(headerOf(req, "content-length"));
|
|
2368
|
+
if (Number.isFinite(request) && request >= 0)
|
|
2369
|
+
event.attributes["http.request.body.size"] = request;
|
|
2370
|
+
const raw = res?.getHeader?.(
|
|
2371
|
+
"content-length"
|
|
2372
|
+
);
|
|
2373
|
+
const response = Number(raw);
|
|
2374
|
+
if (raw !== void 0 && Number.isFinite(response) && response >= 0)
|
|
2375
|
+
event.attributes["http.response.body.size"] = response;
|
|
2376
|
+
};
|
|
2377
|
+
}
|
|
2378
|
+
var COUNTRY_HEADERS = [
|
|
2379
|
+
"cf-ipcountry",
|
|
2380
|
+
"x-vercel-ip-country",
|
|
2381
|
+
"cloudfront-viewer-country",
|
|
2382
|
+
"x-country-code",
|
|
2383
|
+
"fastly-geo-country-code"
|
|
2384
|
+
];
|
|
2385
|
+
function enrichEdgeGeo() {
|
|
2386
|
+
return (event, { req }) => {
|
|
2387
|
+
for (const name of COUNTRY_HEADERS) {
|
|
2388
|
+
const value = headerOf(req, name)?.trim().toUpperCase();
|
|
2389
|
+
if (value && /^[A-Z]{2}$/.test(value) && value !== "XX" && value !== "T1") {
|
|
2390
|
+
event.attributes["geo.country.iso_code"] = value;
|
|
2391
|
+
return;
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
var REGION_VARS = [
|
|
2397
|
+
"AUTTER_REGION",
|
|
2398
|
+
"AWS_REGION",
|
|
2399
|
+
"VERCEL_REGION",
|
|
2400
|
+
"FLY_REGION",
|
|
2401
|
+
"RAILWAY_REPLICA_REGION",
|
|
2402
|
+
"GOOGLE_CLOUD_REGION",
|
|
2403
|
+
"CLOUD_RUN_REGION",
|
|
2404
|
+
"RENDER_REGION",
|
|
2405
|
+
"AZURE_REGION"
|
|
2406
|
+
];
|
|
2407
|
+
var COMMIT_VARS = [
|
|
2408
|
+
"AUTTER_COMMIT",
|
|
2409
|
+
"GIT_SHA",
|
|
2410
|
+
"GIT_COMMIT",
|
|
2411
|
+
"COMMIT_SHA",
|
|
2412
|
+
"SOURCE_COMMIT",
|
|
2413
|
+
"VERCEL_GIT_COMMIT_SHA",
|
|
2414
|
+
"GITHUB_SHA",
|
|
2415
|
+
"RENDER_GIT_COMMIT",
|
|
2416
|
+
"RAILWAY_GIT_COMMIT_SHA",
|
|
2417
|
+
"CF_PAGES_COMMIT_SHA",
|
|
2418
|
+
"HEROKU_SLUG_COMMIT",
|
|
2419
|
+
"SOURCE_VERSION"
|
|
2420
|
+
];
|
|
2421
|
+
function enrichDeployment(env = process.env) {
|
|
2422
|
+
const pick = (names) => names.map((name) => env[name]?.trim()).find((value) => value);
|
|
2423
|
+
const region = pick(REGION_VARS)?.slice(0, 64);
|
|
2424
|
+
const commit = pick(COMMIT_VARS)?.slice(0, 64);
|
|
2425
|
+
return (event) => {
|
|
2426
|
+
if (region) event.attributes["deployment.region"] = region;
|
|
2427
|
+
if (commit) event.attributes["deployment.commit"] = commit;
|
|
2428
|
+
};
|
|
2429
|
+
}
|
|
2010
2430
|
export {
|
|
2431
|
+
CODE_PATTERN,
|
|
2432
|
+
COMPAT_MANIFEST,
|
|
2433
|
+
INGESTER_VERSION_HEADER,
|
|
2434
|
+
RuntimeError,
|
|
2435
|
+
SDK_IDENTITY,
|
|
2436
|
+
addBreadcrumb,
|
|
2437
|
+
autterErrorHandler,
|
|
2438
|
+
autterErrorResponse,
|
|
2439
|
+
autterFastify,
|
|
2440
|
+
autterFastifyErrors,
|
|
2441
|
+
autterKoa,
|
|
2442
|
+
autterRequests,
|
|
2011
2443
|
captureException,
|
|
2012
2444
|
captureMessage,
|
|
2445
|
+
captureRequestError,
|
|
2446
|
+
consoleSink,
|
|
2013
2447
|
createBrowserRelayFetchHandler,
|
|
2014
2448
|
createBrowserRelayHandler,
|
|
2015
2449
|
createRuntimeLogger,
|
|
2450
|
+
defineRuntimeErrors,
|
|
2016
2451
|
emitLlmSelftestTrace,
|
|
2452
|
+
enrichDeployment,
|
|
2453
|
+
enrichEdgeGeo,
|
|
2454
|
+
enrichRequestSize,
|
|
2455
|
+
enrichUserAgent,
|
|
2456
|
+
errorAttributes,
|
|
2457
|
+
evaluateCompat,
|
|
2458
|
+
fetchIngesterCompat,
|
|
2459
|
+
fileSink,
|
|
2017
2460
|
flushRuntimeLogs,
|
|
2461
|
+
initAutterLogging,
|
|
2018
2462
|
initAutterServer,
|
|
2019
2463
|
installAutterAutoFlush,
|
|
2020
2464
|
instrumentLlmClient,
|
|
2465
|
+
isRuntimeErrorLike,
|
|
2021
2466
|
makeSafeCapture,
|
|
2467
|
+
otlpSink,
|
|
2022
2468
|
redactAttributes,
|
|
2469
|
+
redactText,
|
|
2023
2470
|
reportOutcome,
|
|
2471
|
+
runDoctor,
|
|
2472
|
+
runInBackground,
|
|
2473
|
+
runtimeContext,
|
|
2024
2474
|
runtimeLogStats,
|
|
2025
2475
|
runtimeLogger,
|
|
2026
2476
|
sanitizeBrowserPayload,
|
|
2027
2477
|
startCaughtExceptionSampler,
|
|
2478
|
+
toClientError,
|
|
2028
2479
|
trackLlmCall,
|
|
2480
|
+
unhandledRejectionsMode,
|
|
2029
2481
|
withLlmCall,
|
|
2030
2482
|
withProcessSpan,
|
|
2031
|
-
withRuntimeOperation
|
|
2483
|
+
withRuntimeOperation,
|
|
2484
|
+
withRuntimeRequest
|
|
2032
2485
|
};
|