@autter/runtime-node 1.9.1 → 1.10.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/dist/index.js CHANGED
@@ -51,15 +51,17 @@ import {
51
51
  stopLoggedErrorCapture,
52
52
  toClientError,
53
53
  withRuntimeOperation
54
- } from "./chunk-D3PJ7TQ4.js";
54
+ } from "./chunk-V65UY6LU.js";
55
55
  import {
56
- runDoctor
57
- } from "./chunk-FYMEBULO.js";
56
+ runDoctor,
57
+ runVerify
58
+ } from "./chunk-3UODRQBD.js";
58
59
  import {
59
60
  COMPAT_MANIFEST,
60
61
  CountingExporter,
61
62
  INGESTER_VERSION_HEADER,
62
63
  SDK_IDENTITY,
64
+ authHeaders,
63
65
  configureCompatCheck,
64
66
  debugLog,
65
67
  ensureCompatConfigured,
@@ -69,14 +71,18 @@ import {
69
71
  installAutterAutoFlush,
70
72
  isDebugEnabled,
71
73
  noteCompatFeature,
74
+ noteIngestRefusal,
72
75
  noteRelayRejection,
73
76
  observeIngesterResponse,
74
77
  registerFlushTarget,
78
+ resolveIngestKey,
75
79
  setDebugMode,
76
80
  sharedSlot,
77
81
  telemetryStats,
78
- unregisterFlushTargets
79
- } from "./chunk-VIP2EL47.js";
82
+ unregisterFlushTargets,
83
+ warnKeyKind,
84
+ warnMissingKey
85
+ } from "./chunk-KWAJIELR.js";
80
86
 
81
87
  // src/relay.ts
82
88
  var IpWindow = class {
@@ -358,18 +364,53 @@ function relayFingerprint(raw, redactor) {
358
364
  }
359
365
  var defaultRelayRedactor = makeRedactor(true);
360
366
  function sanitizeBrowserPayload(raw, redactor = defaultRelayRedactor) {
361
- if (typeof raw !== "object" || raw === null) return null;
367
+ const checked = checkBrowserPayload(raw, redactor);
368
+ return "payload" in checked ? checked.payload : null;
369
+ }
370
+ var MAX_RELAY_ISSUES = 5;
371
+ function describe(value) {
372
+ if (value === null) return "null";
373
+ if (Array.isArray(value)) return "an array";
374
+ if (typeof value === "string") return value ? `"${value.slice(0, 40)}"` : "an empty string";
375
+ return typeof value === "undefined" ? "missing" : `a ${typeof value}`;
376
+ }
377
+ function checkBrowserPayload(raw, redactor = defaultRelayRedactor) {
378
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
379
+ return { issues: [{ path: [], message: `expected a JSON object, got ${describe(raw)}` }] };
380
+ }
362
381
  const p = raw;
363
- if (p.version !== 1) return null;
364
- if (typeof p.service !== "string" || !p.service) return null;
365
- if (typeof p.environment !== "string" || !p.environment) return null;
366
- if (!Array.isArray(p.events) || p.events.length > MAX_EVENTS) return null;
382
+ const issues = [];
383
+ const issue = (path, message) => {
384
+ if (issues.length < MAX_RELAY_ISSUES) issues.push({ path, message });
385
+ };
386
+ if (p.version !== 1) issue(["version"], `expected 1, got ${describe(p.version)}`);
387
+ if (typeof p.service !== "string" || !p.service) issue(["service"], `expected a non-empty string, got ${describe(p.service)}`);
388
+ if (typeof p.environment !== "string" || !p.environment) {
389
+ issue(["environment"], `expected a non-empty string, got ${describe(p.environment)}`);
390
+ }
391
+ if (!Array.isArray(p.events)) issue(["events"], `expected an array, got ${describe(p.events)}`);
392
+ else if (p.events.length > MAX_EVENTS) issue(["events"], `at most ${MAX_EVENTS} events per batch, got ${p.events.length}`);
393
+ if (Array.isArray(p.events) && p.events.length <= MAX_EVENTS) {
394
+ p.events.forEach((rawEvent, index) => {
395
+ if (typeof rawEvent !== "object" || rawEvent === null) {
396
+ issue(["events", index], `expected an object, got ${describe(rawEvent)}`);
397
+ return;
398
+ }
399
+ const e = rawEvent;
400
+ if (typeof e.type !== "string" || !EVENT_TYPES.has(e.type)) {
401
+ issue(["events", index, "type"], `expected one of ${[...EVENT_TYPES].join(", ")}, got ${describe(e.type)}`);
402
+ }
403
+ if (typeof e.timestamp !== "string") {
404
+ issue(["events", index, "timestamp"], `expected an ISO 8601 string, got ${describe(e.timestamp)}`);
405
+ }
406
+ });
407
+ }
408
+ if (issues.length) return { issues };
409
+ const service = p.service;
410
+ const environment = p.environment;
367
411
  const events = [];
368
412
  for (const rawEvent of p.events) {
369
- if (typeof rawEvent !== "object" || rawEvent === null) return null;
370
413
  const e = rawEvent;
371
- if (typeof e.type !== "string" || !EVENT_TYPES.has(e.type)) return null;
372
- if (typeof e.timestamp !== "string") return null;
373
414
  events.push({
374
415
  type: e.type,
375
416
  timestamp: e.timestamp,
@@ -390,20 +431,24 @@ function sanitizeBrowserPayload(raw, redactor = defaultRelayRedactor) {
390
431
  ...typeof e.traceId === "string" && TRACE_ID.test(e.traceId) ? { traceId: e.traceId } : {}
391
432
  });
392
433
  }
393
- return {
434
+ return { payload: {
394
435
  version: 1,
395
436
  ...typeof p.sessionId === "string" ? { sessionId: p.sessionId.slice(0, 100) } : {},
396
- service: p.service.slice(0, 200),
397
- environment: p.environment.slice(0, 100),
437
+ service: service.slice(0, 200),
438
+ environment: environment.slice(0, 100),
398
439
  ...typeof p.release === "string" ? { release: p.release.slice(0, 200) } : {},
399
440
  // @autter/runtime-browser version, for the ingester's compat records.
400
441
  ...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 } : {},
401
442
  events
402
- };
443
+ } };
403
444
  }
445
+ var NO_KEY_BODY = {
446
+ error: "relay has no ingest key",
447
+ fix: "set AUTTER_RUNTIME_KEY (a server key, autter_rt_\u2026) where the relay runs"
448
+ };
404
449
  var FORWARD_ATTEMPTS = 3;
405
450
  var FORWARD_MAX_WAIT_MS = 1e4;
406
- function forward(payload, opts) {
451
+ function forward(payload, opts, apiKey) {
407
452
  const endpoint = (opts.endpoint ?? DEFAULT_ENDPOINT).replace(/\/$/, "");
408
453
  const url = `${endpoint}/v1/browser`;
409
454
  const compat = opts.compatCheck !== false;
@@ -416,7 +461,7 @@ function forward(payload, opts) {
416
461
  method: "POST",
417
462
  headers: {
418
463
  "content-type": "application/json",
419
- authorization: `Bearer ${opts.apiKey}`
464
+ ...authHeaders(apiKey)
420
465
  },
421
466
  body,
422
467
  signal: AbortSignal.timeout(1e4)
@@ -433,6 +478,15 @@ function forward(payload, opts) {
433
478
  await sleep(seconds > 0 ? Math.min(seconds * 1e3, FORWARD_MAX_WAIT_MS) : 1e3 * 2 ** (n - 1));
434
479
  return attempt(n + 1);
435
480
  }
481
+ if (!response.ok) {
482
+ noteIngestRefusal({
483
+ status: response.status,
484
+ route: "/v1/browser",
485
+ endpoint,
486
+ body: await response.clone().text().catch(() => ""),
487
+ via: "relay"
488
+ });
489
+ }
436
490
  if (!compat) return;
437
491
  const types = new Set(
438
492
  (payload.events ?? []).map((e) => String(e.type))
@@ -457,10 +511,19 @@ function createBrowserRelayFetchHandler(opts) {
457
511
  const maxBody = opts.maxBodyBytes ?? DEFAULT_MAX_BODY;
458
512
  const redactor = opts.redact === void 0 || opts.redact === true ? defaultRelayRedactor : makeRedactor(opts.redact);
459
513
  const limiter = opts.perIpRateLimit === false ? null : new IpWindow(opts.perIpRateLimit ?? 120);
514
+ const apiKey = resolveIngestKey(opts.apiKey);
515
+ if (!apiKey) warnMissingKey("relay");
516
+ else warnKeyKind(apiKey);
460
517
  return async (request) => {
461
518
  if (request.method !== "POST") {
462
519
  return new Response(null, { status: 405 });
463
520
  }
521
+ if (!apiKey) {
522
+ return new Response(JSON.stringify(NO_KEY_BODY), {
523
+ status: 500,
524
+ headers: { "content-type": "application/json" }
525
+ });
526
+ }
464
527
  if (limiter) {
465
528
  const ip = fetchClientKey(request, opts);
466
529
  if (!limiter.allow(ip)) {
@@ -490,13 +553,14 @@ function createBrowserRelayFetchHandler(opts) {
490
553
  status: 400
491
554
  });
492
555
  }
493
- const payload = sanitizeBrowserPayload(raw, redactor);
494
- if (!payload) {
495
- return new Response(JSON.stringify({ error: "invalid payload" }), {
496
- status: 400
556
+ const checked = checkBrowserPayload(raw, redactor);
557
+ if (!("payload" in checked)) {
558
+ return new Response(JSON.stringify({ error: "invalid payload", issues: checked.issues }), {
559
+ status: 400,
560
+ headers: { "content-type": "application/json" }
497
561
  });
498
562
  }
499
- forward(payload, opts);
563
+ forward(checked.payload, opts, apiKey);
500
564
  return new Response(null, { status: 202 });
501
565
  };
502
566
  }
@@ -513,13 +577,16 @@ function createBrowserRelayHandler(opts) {
513
577
  res.end();
514
578
  }
515
579
  }
580
+ const apiKey = resolveIngestKey(opts.apiKey);
581
+ if (!apiKey) warnMissingKey("relay");
582
+ else warnKeyKind(apiKey);
516
583
  function handleParsed(raw, res) {
517
- const payload = sanitizeBrowserPayload(raw, redactor);
518
- if (!payload) {
519
- respond(res, 400, { error: "invalid payload" });
584
+ const checked = checkBrowserPayload(raw, redactor);
585
+ if (!("payload" in checked)) {
586
+ respond(res, 400, { error: "invalid payload", issues: checked.issues });
520
587
  return;
521
588
  }
522
- forward(payload, opts);
589
+ forward(checked.payload, opts, apiKey);
523
590
  respond(res, 202);
524
591
  }
525
592
  return (req, res) => {
@@ -527,6 +594,10 @@ function createBrowserRelayHandler(opts) {
527
594
  respond(res, 405);
528
595
  return;
529
596
  }
597
+ if (!apiKey) {
598
+ respond(res, 500, NO_KEY_BODY);
599
+ return;
600
+ }
530
601
  if (limiter) {
531
602
  const ip = nodeClientKey(req, opts);
532
603
  if (!limiter.allow(ip)) {
@@ -576,13 +647,13 @@ function createBrowserRelayHandler(opts) {
576
647
 
577
648
  // src/server.ts
578
649
  import { errorMonitor } from "events";
579
- import { createRequire } from "module";
580
- import { join } from "path";
650
+ import { createRequire as createRequire2 } from "module";
651
+ import { join as join2 } from "path";
581
652
  import { ServerResponse } from "http";
582
653
  import { randomUUID } from "crypto";
583
654
  import {
584
- context as context2,
585
- trace as trace2,
655
+ context as context3,
656
+ trace as trace3,
586
657
  SpanKind,
587
658
  SpanStatusCode,
588
659
  TraceFlags
@@ -828,6 +899,73 @@ var RedactingMetricExporter = class {
828
899
  }
829
900
  };
830
901
 
902
+ // src/ingest-exporters.ts
903
+ import { ExportResultCode as ExportResultCode2 } from "@opentelemetry/core";
904
+ import {
905
+ AggregationTemporality as AggregationTemporality2
906
+ } from "@opentelemetry/sdk-metrics";
907
+ function refusalOf(result) {
908
+ if (result.code === ExportResultCode2.SUCCESS) return null;
909
+ const error = result.error;
910
+ return typeof error?.code === "number" ? { status: error.code, body: error.data } : null;
911
+ }
912
+ var NO_KEY = {
913
+ code: ExportResultCode2.FAILED,
914
+ error: new Error("no Autter ingest key: not sent (set AUTTER_RUNTIME_KEY)")
915
+ };
916
+ function observe(endpoint, route, callback) {
917
+ return (result) => {
918
+ const refusal = refusalOf(result);
919
+ if (refusal) noteIngestRefusal({ ...refusal, endpoint, route });
920
+ callback(result);
921
+ };
922
+ }
923
+ var IngestSpanExporter = class {
924
+ constructor(inner, target) {
925
+ this.inner = inner;
926
+ this.target = target;
927
+ }
928
+ inner;
929
+ target;
930
+ export(spans, resultCallback) {
931
+ if (!this.target.hasKey) {
932
+ resultCallback(NO_KEY);
933
+ return;
934
+ }
935
+ this.inner.export(spans, observe(this.target.endpoint, "/v1/traces", resultCallback));
936
+ }
937
+ shutdown() {
938
+ return this.inner.shutdown();
939
+ }
940
+ forceFlush() {
941
+ return this.inner.forceFlush?.() ?? Promise.resolve();
942
+ }
943
+ };
944
+ var IngestMetricExporter = class {
945
+ constructor(inner, target) {
946
+ this.inner = inner;
947
+ this.target = target;
948
+ }
949
+ inner;
950
+ target;
951
+ export(metrics2, resultCallback) {
952
+ if (!this.target.hasKey) {
953
+ resultCallback(NO_KEY);
954
+ return;
955
+ }
956
+ this.inner.export(metrics2, observe(this.target.endpoint, "/v1/metrics", resultCallback));
957
+ }
958
+ forceFlush() {
959
+ return this.inner.forceFlush();
960
+ }
961
+ shutdown() {
962
+ return this.inner.shutdown();
963
+ }
964
+ selectAggregationTemporality(instrumentType) {
965
+ return this.inner.selectAggregationTemporality ? this.inner.selectAggregationTemporality(instrumentType) : AggregationTemporality2.CUMULATIVE;
966
+ }
967
+ };
968
+
831
969
  // src/memory.ts
832
970
  import { PerformanceObserver } from "perf_hooks";
833
971
  import { getHeapStatistics } from "v8";
@@ -936,6 +1074,27 @@ function captureAtBoundary(error, state, rule = void 0, attributes) {
936
1074
  } catch {
937
1075
  }
938
1076
  }
1077
+ function requestScopeAttributes() {
1078
+ try {
1079
+ const out = {};
1080
+ const state = local.getStore();
1081
+ const active = context.active();
1082
+ const metadata = getRPCMetadata(active);
1083
+ const http = metadata?.type === RPCType.HTTP ? metadata : void 0;
1084
+ const spanAttributes = http?.span?.attributes;
1085
+ const req = state?.req;
1086
+ const nodeRequest = !!req && typeof req === "object" && typeof req.url === "string" && req.url.startsWith("/");
1087
+ const requestId = state?.requestId ?? spanAttributes?.["autter.request.id"];
1088
+ if (typeof requestId === "string" && requestId) out["autter.request.id"] = requestId;
1089
+ const route = http?.route ?? spanAttributes?.["http.route"] ?? (nodeRequest ? expressRouteOf(req) ?? normalizeRoutePath(pathOf(req)) : void 0);
1090
+ if (typeof route === "string" && route) out["http.route"] = route;
1091
+ const method = (nodeRequest ? req.method : void 0) ?? spanAttributes?.["http.request.method"] ?? spanAttributes?.["http.method"];
1092
+ if (typeof method === "string" && method) out["http.request.method"] = method.toUpperCase();
1093
+ return Object.keys(out).length > 0 ? out : void 0;
1094
+ } catch {
1095
+ return void 0;
1096
+ }
1097
+ }
939
1098
  function requestStateOf(req) {
940
1099
  if (!req || typeof req !== "object") return void 0;
941
1100
  return req[REQUEST_STATE];
@@ -1425,6 +1584,173 @@ function installUnhandledRejectionCapture(capture, mode = unhandledRejectionsMod
1425
1584
  };
1426
1585
  }
1427
1586
 
1587
+ // src/express-errors.ts
1588
+ import { createRequire } from "module";
1589
+ import { join } from "path";
1590
+ import { context as context2, trace as trace2 } from "@opentelemetry/api";
1591
+ import { Hook } from "require-in-the-middle";
1592
+ var PATCHED = /* @__PURE__ */ Symbol.for("autter.runtime.express.layer");
1593
+ var WRAPPED = /* @__PURE__ */ Symbol.for("autter.runtime.express.next");
1594
+ var ERRORS = /* @__PURE__ */ Symbol.for("autter.runtime.express.errors");
1595
+ var MAX_ERRORS = 8;
1596
+ function noteExpressError(req, error) {
1597
+ if (error === void 0 || error === null || error === false) return;
1598
+ if (error === "route" || error === "router") return;
1599
+ if (!req || typeof req !== "object") return;
1600
+ try {
1601
+ const target = req;
1602
+ let list = target[ERRORS];
1603
+ if (!list) {
1604
+ list = [];
1605
+ target[ERRORS] = list;
1606
+ }
1607
+ if (list.length < MAX_ERRORS && !list.includes(error)) list.push(error);
1608
+ } catch {
1609
+ }
1610
+ }
1611
+ function wrapNext(req, next) {
1612
+ if (typeof next !== "function") return next;
1613
+ const fn = next;
1614
+ const existing = fn[WRAPPED];
1615
+ if (existing) return existing;
1616
+ const wrapped = function autterExpressNext(...args) {
1617
+ if (args.length > 0) noteExpressError(req, args[0]);
1618
+ return fn.apply(this, args);
1619
+ };
1620
+ try {
1621
+ fn[WRAPPED] = wrapped;
1622
+ } catch {
1623
+ }
1624
+ return wrapped;
1625
+ }
1626
+ function patchLayerPrototype(proto) {
1627
+ if (!proto || typeof proto !== "object") return false;
1628
+ const target = proto;
1629
+ if (target[PATCHED]) return true;
1630
+ const requestName = typeof target.handleRequest === "function" ? "handleRequest" : typeof target.handle_request === "function" ? "handle_request" : null;
1631
+ const errorName = typeof target.handleError === "function" ? "handleError" : typeof target.handle_error === "function" ? "handle_error" : null;
1632
+ if (!requestName || !errorName) return false;
1633
+ const handleRequest = target[requestName];
1634
+ const handleError = target[errorName];
1635
+ target[requestName] = function autterHandleRequest(req, res, next) {
1636
+ let wrapped = next;
1637
+ try {
1638
+ wrapped = wrapNext(req, next);
1639
+ } catch {
1640
+ wrapped = next;
1641
+ }
1642
+ return handleRequest.call(this, req, res, wrapped);
1643
+ };
1644
+ target[errorName] = function autterHandleError(error, req, res, next) {
1645
+ let wrapped = next;
1646
+ try {
1647
+ noteExpressError(req, error);
1648
+ wrapped = wrapNext(req, next);
1649
+ } catch {
1650
+ wrapped = next;
1651
+ }
1652
+ return handleError.call(this, error, req, res, wrapped);
1653
+ };
1654
+ try {
1655
+ Object.defineProperty(target, PATCHED, { value: true });
1656
+ } catch {
1657
+ target[PATCHED] = true;
1658
+ }
1659
+ return true;
1660
+ }
1661
+ var LAYER_FILES = ["express/lib/router/layer.js", "router/lib/layer.js"];
1662
+ function patchLayerModule(exports) {
1663
+ try {
1664
+ const Layer = exports;
1665
+ if (typeof Layer === "function" && patchLayerPrototype(Layer.prototype))
1666
+ debugLog("express: error capture installed");
1667
+ } catch {
1668
+ }
1669
+ }
1670
+ function scanModuleCache() {
1671
+ try {
1672
+ const cache = createRequire(join(process.cwd(), "autter-runtime-express.cjs")).cache;
1673
+ for (const [key, entry] of Object.entries(cache)) {
1674
+ const path = key.replace(/\\/g, "/");
1675
+ if (LAYER_FILES.some((file) => path.endsWith(`/node_modules/${file}`)))
1676
+ patchLayerModule(entry?.exports);
1677
+ }
1678
+ } catch {
1679
+ }
1680
+ }
1681
+ var installed = false;
1682
+ function installExpressErrorCapture() {
1683
+ if (installed) return;
1684
+ installed = true;
1685
+ scanModuleCache();
1686
+ try {
1687
+ new Hook(["express", "router"], { internals: true }, (exports, name) => {
1688
+ const file = name.replace(/\\/g, "/");
1689
+ if (LAYER_FILES.includes(file)) patchLayerModule(exports);
1690
+ return exports;
1691
+ });
1692
+ } catch {
1693
+ }
1694
+ }
1695
+ function patchFromRequest(req) {
1696
+ try {
1697
+ const app = req?.app;
1698
+ if (!app || typeof app !== "function" && typeof app !== "object") return;
1699
+ const router = app._router ?? app.router;
1700
+ const layer = router?.stack?.[0];
1701
+ if (layer && typeof layer === "object" && patchLayerPrototype(Object.getPrototypeOf(layer)))
1702
+ debugLog("express: error capture installed (from a request)");
1703
+ } catch {
1704
+ }
1705
+ }
1706
+ function declaredStatus(error) {
1707
+ if (!error || typeof error !== "object") return void 0;
1708
+ try {
1709
+ const value = error.status ?? error.statusCode;
1710
+ const status = typeof value === "string" ? Number(value) : value;
1711
+ return typeof status === "number" && Number.isInteger(status) && status >= 400 && status < 600 ? status : void 0;
1712
+ } catch {
1713
+ return void 0;
1714
+ }
1715
+ }
1716
+ function watchExpressResponse(span, res, enabled) {
1717
+ const req = res.req;
1718
+ if (!req) return;
1719
+ const ctx = context2.active();
1720
+ let done = false;
1721
+ const settle = (finished) => {
1722
+ if (done) return;
1723
+ done = true;
1724
+ try {
1725
+ const errors = req[ERRORS];
1726
+ if (!errors?.length) {
1727
+ patchFromRequest(req);
1728
+ return;
1729
+ }
1730
+ req[ERRORS] = void 0;
1731
+ if (!enabled()) return;
1732
+ if (finished && !(res.statusCode >= 500)) return;
1733
+ if (errors.some((error2) => isCaptured(error2))) return;
1734
+ const error = errors.find((candidate) => {
1735
+ const status = declaredStatus(candidate);
1736
+ return status === void 0 || status >= 500;
1737
+ });
1738
+ if (error === void 0) return;
1739
+ const state = requestStateOf(req);
1740
+ if (state && !state.sealed) noteOperationError(error, false, state);
1741
+ const run = () => captureAtBoundary(error, state, true);
1742
+ if (span.isRecording()) context2.with(trace2.setSpan(ctx, span), run);
1743
+ else context2.with(ctx, run);
1744
+ } catch {
1745
+ }
1746
+ };
1747
+ try {
1748
+ res.prependListener("finish", () => settle(true));
1749
+ res.prependListener("close", () => settle(false));
1750
+ } catch {
1751
+ }
1752
+ }
1753
+
1428
1754
  // src/route-label.ts
1429
1755
  import { getRPCMetadata as getRPCMetadata2, RPCType as RPCType2 } from "@opentelemetry/core";
1430
1756
  var ours = /* @__PURE__ */ new WeakMap();
@@ -1596,7 +1922,7 @@ var ErrorTraceRetentionProcessor = class {
1596
1922
  }
1597
1923
  /** Promote the active trace, if any — called on captured errors. */
1598
1924
  retainActiveTrace() {
1599
- const active = trace2.getActiveSpan()?.spanContext();
1925
+ const active = trace3.getActiveSpan()?.spanContext();
1600
1926
  if (!active || (active.traceFlags & TraceFlags.SAMPLED) !== 0) return;
1601
1927
  const entry = this.entryFor(active.traceId);
1602
1928
  if (entry.retained) return;
@@ -1709,8 +2035,8 @@ async function runLlmSpan(tracer, info, fn) {
1709
2035
  setAttributes: (attributes) => span.setAttributes(S.redactor(attributes))
1710
2036
  };
1711
2037
  try {
1712
- const result = await context2.with(
1713
- trace2.setSpan(context2.active(), span),
2038
+ const result = await context3.with(
2039
+ trace3.setSpan(context3.active(), span),
1714
2040
  () => fn(handle)
1715
2041
  );
1716
2042
  span.setStatus({ code: SpanStatusCode.OK });
@@ -1753,8 +2079,8 @@ async function runWithSpan(tracer, name, fn, attributes, links) {
1753
2079
  ...links?.length ? { links } : {}
1754
2080
  });
1755
2081
  try {
1756
- const result = await context2.with(
1757
- trace2.setSpan(context2.active(), span),
2082
+ const result = await context3.with(
2083
+ trace3.setSpan(context3.active(), span),
1758
2084
  fn
1759
2085
  );
1760
2086
  span.setStatus({ code: SpanStatusCode.OK });
@@ -1784,7 +2110,7 @@ function redactWithActive(attributes) {
1784
2110
  }
1785
2111
  function patchLoadedHttpModules() {
1786
2112
  try {
1787
- const load = createRequire(join(process.cwd(), "autter-runtime-http.cjs"));
2113
+ const load = createRequire2(join2(process.cwd(), "autter-runtime-http.cjs"));
1788
2114
  load("http");
1789
2115
  load("https");
1790
2116
  } catch {
@@ -1793,7 +2119,7 @@ function patchLoadedHttpModules() {
1793
2119
  var METRIC_ROUTE_ATTR = "autter.metric_has_route";
1794
2120
  function captureRequestRoute(span, response) {
1795
2121
  if (!(response instanceof ServerResponse)) return;
1796
- const rpcMetadata = getRPCMetadata3(context2.active());
2122
+ const rpcMetadata = getRPCMetadata3(context3.active());
1797
2123
  if (rpcMetadata?.type !== RPCType3.HTTP) return;
1798
2124
  let failed = false;
1799
2125
  response.on(errorMonitor, () => {
@@ -1809,6 +2135,7 @@ function captureRequestRoute(span, response) {
1809
2135
  setRoute();
1810
2136
  if (rpcMetadata.route && !failed) span.setAttribute(METRIC_ROUTE_ATTR, true);
1811
2137
  });
2138
+ if (S.expressErrors) watchExpressResponse(span, response, () => S.expressErrors === true);
1812
2139
  if (S.hookRequests) startHookRequest(response);
1813
2140
  }
1814
2141
  function structuredErrorAttributes(error) {
@@ -1830,12 +2157,14 @@ function initAutterServer(options) {
1830
2157
  /\/$/,
1831
2158
  ""
1832
2159
  );
1833
- const headers = { authorization: `Bearer ${options.apiKey}` };
2160
+ const apiKey = resolveIngestKey(options.apiKey);
2161
+ const headers = authHeaders(apiKey);
2162
+ const ingestTarget = { endpoint, hasKey: apiKey !== null };
1834
2163
  const redactor = makeRedactor(options.redactAttributes ?? true);
1835
2164
  const queryPolicy = makeUrlQueryPolicy(options.keepQueryParams);
1836
2165
  const traceExporter = () => new CountingExporter(
1837
2166
  new RedactingSpanExporter(
1838
- new OTLPTraceExporter({ url: `${endpoint}/v1/traces`, headers }),
2167
+ new IngestSpanExporter(new OTLPTraceExporter({ url: `${endpoint}/v1/traces`, headers }), ingestTarget),
1839
2168
  () => redactor,
1840
2169
  queryPolicy
1841
2170
  )
@@ -1843,6 +2172,8 @@ function initAutterServer(options) {
1843
2172
  const environment = options.environment ?? process.env.NODE_ENV ?? "production";
1844
2173
  if (isDebugEnabled() || options.debug === true) setDebugMode(true);
1845
2174
  debugLog(`initialising service=${options.service} endpoint=${endpoint}`);
2175
+ if (apiKey === null) warnMissingKey("server", options.service);
2176
+ else warnKeyKind(apiKey);
1846
2177
  S.redactor = redactor;
1847
2178
  const distro = options.distro ?? SDK_IDENTITY;
1848
2179
  configureCompatCheck({ endpoint, enabled: options.compatCheck !== false, sdk: distro });
@@ -1891,7 +2222,7 @@ function initAutterServer(options) {
1891
2222
  const mainSpanProcessor = new BatchSpanProcessor(traceExporter());
1892
2223
  const metricReader = new PeriodicExportingMetricReader({
1893
2224
  exporter: new RedactingMetricExporter(
1894
- new OTLPMetricExporter({
2225
+ new IngestMetricExporter(new OTLPMetricExporter({
1895
2226
  url: `${endpoint}/v1/metrics`,
1896
2227
  headers,
1897
2228
  // Deltas, not lifetime totals: the ingester SUMs data points
@@ -1899,7 +2230,7 @@ function initAutterServer(options) {
1899
2230
  // temporality would re-count every past request on each
1900
2231
  // 60 s export.
1901
2232
  temporalityPreference: AggregationTemporalityPreference.DELTA
1902
- }),
2233
+ }), ingestTarget),
1903
2234
  () => redactor
1904
2235
  ),
1905
2236
  exportIntervalMillis: options.metricIntervalMs ?? 6e4
@@ -1961,9 +2292,11 @@ function initAutterServer(options) {
1961
2292
  const fingerprint = fingerprintEventAttributes(split.fingerprint, redactor.text);
1962
2293
  if (fingerprint["autter.fingerprint"]) noteCompatFeature("fingerprint_override");
1963
2294
  const redacted = redactor(split.attributes);
1964
- const activeSpan = trace2.getActiveSpan();
1965
- const reuseActive = activeSpan?.isRecording() === true;
2295
+ const crash = attributes?.["autter.unhandled"] === true;
2296
+ const activeSpan = trace3.getActiveSpan();
2297
+ const reuseActive = !crash && activeSpan?.isRecording() === true;
1966
2298
  const span = reuseActive ? activeSpan : errorTracer.startSpan(isErr ? error.name : "Error");
2299
+ const scope = reuseActive ? void 0 : requestScopeAttributes();
1967
2300
  const structured = structuredErrorAttributes(error);
1968
2301
  const causes = errorAttributes(error);
1969
2302
  const declared = {
@@ -1972,6 +2305,7 @@ function initAutterServer(options) {
1972
2305
  };
1973
2306
  const requestId = currentOperation()?.requestId;
1974
2307
  span.setAttributes({
2308
+ ...scope,
1975
2309
  "autter.severity": "error",
1976
2310
  ...redacted,
1977
2311
  ...structured,
@@ -2028,7 +2362,7 @@ function initAutterServer(options) {
2028
2362
  if (!errorFilter({ type: "OutcomeFailure", message: `${name}: ${message}` })) return;
2029
2363
  errorTraceBuffer?.retainActiveTrace();
2030
2364
  telemetryStats.markCaptured();
2031
- const activeSpan = trace2.getActiveSpan();
2365
+ const activeSpan = trace3.getActiveSpan();
2032
2366
  const reuseActive = activeSpan?.isRecording() === true;
2033
2367
  const span = reuseActive ? activeSpan : errorTracer.startSpan("OutcomeFailure");
2034
2368
  const safeOutcome = redactor({ "autter.outcome.name": name.slice(0, 200), "autter.outcome.message": message.slice(0, 1e3) });
@@ -2057,7 +2391,7 @@ function initAutterServer(options) {
2057
2391
  const split = splitFingerprint(attributes);
2058
2392
  const fingerprint = fingerprintEventAttributes(split.fingerprint, redactor.text);
2059
2393
  const redacted = redactor(split.attributes);
2060
- const activeSpan = trace2.getActiveSpan();
2394
+ const activeSpan = trace3.getActiveSpan();
2061
2395
  const reuseActive = activeSpan?.isRecording() === true;
2062
2396
  const span = reuseActive ? activeSpan : errorTracer.startSpan(isErr ? error.name : "Error");
2063
2397
  markCaptured(error);
@@ -2076,7 +2410,7 @@ function initAutterServer(options) {
2076
2410
  }
2077
2411
  configureRuntimeLogger({
2078
2412
  endpoint,
2079
- apiKey: options.apiKey,
2413
+ apiKey: apiKey ?? "",
2080
2414
  service: options.service,
2081
2415
  environment,
2082
2416
  distro,
@@ -2087,6 +2421,8 @@ function initAutterServer(options) {
2087
2421
  reportOutcome: reportOutcome2
2088
2422
  });
2089
2423
  S.hookRequests = options.logging?.requests === true;
2424
+ S.expressErrors = options.captureExpressErrors !== false;
2425
+ if (S.expressErrors) installExpressErrorCapture();
2090
2426
  S.handled = captureHandled;
2091
2427
  startLoggedErrorCapture(options.captureLoggedErrors);
2092
2428
  let onCrash = null;
@@ -2145,6 +2481,7 @@ function initAutterServer(options) {
2145
2481
  S.alwaysOnProvider = null;
2146
2482
  S.handled = null;
2147
2483
  S.hookRequests = false;
2484
+ S.expressErrors = false;
2148
2485
  S.redactor = makeRedactor(true);
2149
2486
  }
2150
2487
  autoFlushHandle?.dispose();
@@ -2173,7 +2510,7 @@ function captureException(error, attributes) {
2173
2510
  const split = splitFingerprint(attributes);
2174
2511
  const fingerprint = fingerprintEventAttributes(split.fingerprint, S.redactor.text);
2175
2512
  notifyException(error, { ...split.attributes, ...fingerprint });
2176
- const span = trace2.getTracer("autter-errors").startSpan(isError(error) ? error.name : "Error", {
2513
+ const span = trace3.getTracer("autter-errors").startSpan(isError(error) ? error.name : "Error", {
2177
2514
  attributes: { ...S.redactor(split.attributes), ...fingerprint }
2178
2515
  });
2179
2516
  recordError(span, error);
@@ -2195,7 +2532,7 @@ function captureHandledError(error, attributes, severity) {
2195
2532
  markCaptured(error);
2196
2533
  const split = splitFingerprint(attributes);
2197
2534
  notifyException(error, { ...split.attributes, "autter.severity": severity });
2198
- const span = trace2.getTracer("autter-errors").startSpan(isError(error) ? error.name : "Error");
2535
+ const span = trace3.getTracer("autter-errors").startSpan(isError(error) ? error.name : "Error");
2199
2536
  span.addEvent("exception", {
2200
2537
  ...S.redactor(split.attributes),
2201
2538
  "exception.type": isError(error) ? error.name : "Error",
@@ -2213,13 +2550,13 @@ function serverActive() {
2213
2550
  setRequestExceptionCapturer((error, attributes) => captureException(error, attributes));
2214
2551
  setHandledCapture(captureHandledError);
2215
2552
  setRequestSpanAnnotator((attributes) => {
2216
- const span = trace2.getActiveSpan();
2553
+ const span = trace3.getActiveSpan();
2217
2554
  if (span?.isRecording()) span.setAttributes(S.redactor(splitFingerprint(attributes).attributes));
2218
2555
  });
2219
2556
  function withProcessSpan(name, fn, attributes) {
2220
2557
  if (S.active) return S.active.withProcessSpan(name, fn, attributes);
2221
2558
  return runWithSpan(
2222
- trace2.getTracer("autter-processes"),
2559
+ trace3.getTracer("autter-processes"),
2223
2560
  name,
2224
2561
  fn,
2225
2562
  S.redactor(attributes)
@@ -2227,14 +2564,14 @@ function withProcessSpan(name, fn, attributes) {
2227
2564
  }
2228
2565
  function withLlmCall(info, fn) {
2229
2566
  if (S.active) return S.active.withLlmCall(info, fn);
2230
- return runLlmSpan(trace2.getTracer("autter-llm"), info, fn);
2567
+ return runLlmSpan(trace3.getTracer("autter-llm"), info, fn);
2231
2568
  }
2232
2569
  function trackLlmCall(call) {
2233
2570
  if (S.active) {
2234
2571
  S.active.trackLlmCall(call);
2235
2572
  return;
2236
2573
  }
2237
- recordLlmCall(trace2.getTracer("autter-llm"), call);
2574
+ recordLlmCall(trace3.getTracer("autter-llm"), call);
2238
2575
  }
2239
2576
  function captureMessage(message, severity = "warning", attributes) {
2240
2577
  if (S.active) {
@@ -2242,7 +2579,7 @@ function captureMessage(message, severity = "warning", attributes) {
2242
2579
  return;
2243
2580
  }
2244
2581
  const safeMessage = S.redactor.text(message);
2245
- const span = trace2.getTracer("autter-errors").startSpan("Message", {
2582
+ const span = trace3.getTracer("autter-errors").startSpan("Message", {
2246
2583
  attributes: { "autter.severity": severity, ...S.redactor(attributes) }
2247
2584
  });
2248
2585
  span.addEvent("exception", {
@@ -2256,7 +2593,7 @@ function captureMessage(message, severity = "warning", attributes) {
2256
2593
  }
2257
2594
  function reportOutcome(name, message, attributes) {
2258
2595
  if (S.active) return S.active.reportOutcome(name, message, attributes);
2259
- const span = trace2.getTracer("autter-outcomes").startSpan("OutcomeFailure");
2596
+ const span = trace3.getTracer("autter-outcomes").startSpan("OutcomeFailure");
2260
2597
  const safeOutcome = redactAttributes({ "autter.outcome.name": name.slice(0, 200), "autter.outcome.message": message.slice(0, 1e3) });
2261
2598
  const safeName = String(safeOutcome["autter.outcome.name"] ?? "operation");
2262
2599
  const safeMessage = String(safeOutcome["autter.outcome.message"] ?? "failed");
@@ -2277,7 +2614,7 @@ function makeSafeCapture(options) {
2277
2614
  };
2278
2615
  }
2279
2616
  function alwaysOnTracer(name) {
2280
- return S.alwaysOnProvider ? S.alwaysOnProvider.getTracer(name) : trace2.getTracer(name);
2617
+ return S.alwaysOnProvider ? S.alwaysOnProvider.getTracer(name) : trace3.getTracer(name);
2281
2618
  }
2282
2619
  function autterLlmTracer() {
2283
2620
  return alwaysOnTracer("autter-llm");
@@ -2297,7 +2634,7 @@ async function emitLlmSelftestTrace() {
2297
2634
  attributes: { "autter.selftest": true }
2298
2635
  },
2299
2636
  (llm) => {
2300
- traceId = trace2.getActiveSpan()?.spanContext().traceId ?? "";
2637
+ traceId = trace3.getActiveSpan()?.spanContext().traceId ?? "";
2301
2638
  llm.setUsage({ inputTokens: 1, outputTokens: 1 });
2302
2639
  llm.setCost(0);
2303
2640
  }
@@ -2618,7 +2955,7 @@ function autterKoa(options = {}) {
2618
2955
  // src/logging-only.ts
2619
2956
  import {
2620
2957
  SpanStatusCode as SpanStatusCode3,
2621
- trace as trace3
2958
+ trace as trace4
2622
2959
  } from "@opentelemetry/api";
2623
2960
  var L = sharedSlot("logging", () => ({ active: null }));
2624
2961
  function initAutterLogging(options) {
@@ -2653,7 +2990,7 @@ function initAutterLogging(options) {
2653
2990
  const split = splitFingerprint(attributes);
2654
2991
  const fingerprint = fingerprintEventAttributes(split.fingerprint, redact.text);
2655
2992
  const redacted = redact(split.attributes);
2656
- const span = trace3.getActiveSpan();
2993
+ const span = trace4.getActiveSpan();
2657
2994
  if (mode === "auto" && span?.isRecording()) {
2658
2995
  noteOperationError(error);
2659
2996
  notifyException(error, { ...redacted, ...fingerprint });
@@ -2694,7 +3031,7 @@ function initAutterLogging(options) {
2694
3031
  const fingerprint = fingerprintEventAttributes(split.fingerprint, redact.text);
2695
3032
  const redacted = redact(split.attributes);
2696
3033
  notifyException(error, { ...redacted, ...fingerprint, "autter.severity": severity });
2697
- const span = trace3.getActiveSpan();
3034
+ const span = trace4.getActiveSpan();
2698
3035
  if (mode === "auto" && span?.isRecording()) {
2699
3036
  span.addEvent("exception", {
2700
3037
  ...redacted,
@@ -2712,7 +3049,9 @@ function initAutterLogging(options) {
2712
3049
  const captured = /* @__PURE__ */ new WeakSet();
2713
3050
  configureRuntimeLogger({
2714
3051
  endpoint,
2715
- apiKey: options.apiKey ?? "",
3052
+ // Normalised ("undefined"/empty is no key), no env fallback: without
3053
+ // an apiKey this mode logs locally only, by design.
3054
+ apiKey: resolveIngestKey(options.apiKey, {}) ?? "",
2716
3055
  service: options.service,
2717
3056
  environment,
2718
3057
  ...options.release ? { release: options.release } : {},
@@ -2872,6 +3211,7 @@ export {
2872
3211
  captureException,
2873
3212
  captureMessage,
2874
3213
  captureRequestError,
3214
+ checkBrowserPayload,
2875
3215
  consoleSink,
2876
3216
  createBrowserRelayFetchHandler,
2877
3217
  createBrowserRelayHandler,
@@ -2899,6 +3239,7 @@ export {
2899
3239
  reportOutcome,
2900
3240
  runDoctor,
2901
3241
  runInBackground,
3242
+ runVerify,
2902
3243
  runtimeContext,
2903
3244
  runtimeLogStats,
2904
3245
  runtimeLogger,