@autter/runtime-node 1.8.0 → 1.9.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.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { IncomingMessage, ServerResponse } from 'node:http';
2
- import { b as RedactOptions, c as Redactor$1, d as RuntimeLogLevel, e as RuntimeOperationKind, f as RuntimeSink, F as FileSinkOptions, g as RuntimeLogContext, a as RuntimeOutcome, h as RuntimeCarrier, I as IgnoreErrorPattern, B as BreadcrumbInput } from './sinks-CIdlmYwy.cjs';
3
- export { i as Breadcrumb, j as BreadcrumbLevel, k as BreadcrumbType, l as CODE_PATTERN, n as ClientErrorBody, o as ConsoleSinkOptions, O as OtlpSinkOptions, p as RuntimeError, q as RuntimeErrorCatalog, r as RuntimeErrorDefinition, s as RuntimeErrorExtras, t as RuntimeErrorFactory, u as RuntimeErrorLike, v as RuntimeErrorOptions, R as RuntimeEvent, w as RuntimeLogContextValue, x as RuntimeSinkContext, y as consoleSink, z as defineRuntimeErrors, A as errorAttributes, D as fileSink, E as isRuntimeErrorLike, G as otlpSink, H as redactAttributes, J as redactText, K as toClientError } from './sinks-CIdlmYwy.cjs';
2
+ import { b as RedactOptions, c as Redactor$1, d as RuntimeLogLevel, e as RuntimeOperationKind, f as RuntimeSink, F as FileSinkOptions, g as RuntimeLogContext, a as RuntimeOutcome, h as RuntimeCarrier, I as IgnoreErrorPattern, i as CaptureLoggedErrorsOption, B as BreadcrumbInput } from './sinks-CLtQFPnn.cjs';
3
+ export { j as Breadcrumb, k as BreadcrumbLevel, l as BreadcrumbType, n as CODE_PATTERN, o as ClientErrorBody, p as ConsoleSinkOptions, L as LoggedErrorSource, O as OtlpSinkOptions, q as RuntimeError, r as RuntimeErrorCatalog, s as RuntimeErrorDefinition, t as RuntimeErrorExtras, u as RuntimeErrorFactory, v as RuntimeErrorLike, w as RuntimeErrorOptions, R as RuntimeEvent, x as RuntimeLogContextValue, y as RuntimeSinkContext, z as consoleSink, A as defineRuntimeErrors, D as errorAttributes, E as fileSink, G as isRuntimeErrorLike, H as otlpSink, J as redactAttributes, K as redactText, N as toClientError } from './sinks-CLtQFPnn.cjs';
4
4
  import { Attributes } from '@opentelemetry/api';
5
5
 
6
6
  /**
@@ -23,6 +23,37 @@ interface RelayOptions {
23
23
  * `false` to disable (e.g. when a WAF already rate-limits).
24
24
  */
25
25
  perIpRateLimit?: number | false;
26
+ /**
27
+ * How the per-client rate limit finds the visitor's address. A caller can
28
+ * write anything into `X-Forwarded-For`, so the relay never takes its
29
+ * first (leftmost) entry unless told to.
30
+ *
31
+ * - `"auto"` (default): works behind any load balancer, CDN or platform
32
+ * proxy without setup. Proxies append the address they saw to the right
33
+ * of the header (or overwrite it), so the relay reads it right to left
34
+ * and takes the first public address, skipping private and loopback
35
+ * hops. The Node handler only does this when the connection itself came
36
+ * from a private address (a proxy on your network); a public peer is the
37
+ * visitor and its header is ignored. On Cloudflare Workers it uses
38
+ * `CF-Connecting-IP`.
39
+ * - a number `n`: exactly `n` proxies you control sit in front; use the
40
+ * entry `n` from the right (Express `trust proxy` semantics). Use `1`
41
+ * for a Node origin behind Cloudflare with no load balancer.
42
+ * - `true`: every proxy in front is yours and strips client values; use
43
+ * the leftmost entry.
44
+ * - `false`: never read the header. The Node handler keys the socket peer;
45
+ * the fetch handler keys one shared bucket for the route.
46
+ *
47
+ * Strings from environment variables are read too (`"auto"`, `"true"`,
48
+ * `"false"`, `"0"`, `"2"`); anything else falls back to `"auto"`.
49
+ */
50
+ trustProxy?: boolean | "auto" | number | string;
51
+ /**
52
+ * Return the visitor's address yourself (Deno's `info.remoteAddr`, Bun's
53
+ * `server.requestIP`, a header your edge sets). Wins over `trustProxy`
54
+ * when it returns a non-empty string.
55
+ */
56
+ clientIp?: (request: Request | IncomingMessage) => string | null | undefined;
26
57
  /** Called when the async forward fails (default: console.warn). */
27
58
  onError?: (err: unknown) => void;
28
59
  /**
@@ -167,6 +198,19 @@ declare const runtimeContext: {
167
198
  warn(message: string, attributes?: RuntimeLogContext): void;
168
199
  /** Log an error and attach it (code/why/fix) to the current summary. */
169
200
  error(error: unknown, attributes?: RuntimeLogContext): void;
201
+ /**
202
+ * The app caught `error` and chose to continue on a fallback (RF-02):
203
+ *
204
+ * } catch (err) {
205
+ * runtimeContext.degraded(err, "pricing service down, using cached prices");
206
+ * }
207
+ *
208
+ * Recorded as an occurrence at `warning` severity, marked
209
+ * `autter.handling = "deliberate"` with the reason (grouped by its code
210
+ * when the error has one), never an incident. The current request or
211
+ * operation ends "degraded" unless it failed or set its outcome.
212
+ */
213
+ degraded(error: unknown, reason: string, attributes?: RuntimeLogContext): void;
170
214
  /** Current operation id. */
171
215
  readonly id: string | undefined;
172
216
  /** Current request id (inherited by child operations and forks). */
@@ -268,6 +312,20 @@ interface AutterServerOptions {
268
312
  * Default true; false turns both off.
269
313
  */
270
314
  captureGlobalErrors?: boolean;
315
+ /**
316
+ * Errors the app caught and logged count (RF-01): an `Error` passed to
317
+ * `console.error`, pino, winston, bunyan or `runtimeLogger.error` becomes
318
+ * an occurrence at `error` severity, marked `autter.handled` /
319
+ * `autter.handling: "caught"`, with the log line as context
320
+ * (`autter.log.source`, `autter.log.message`). Log lines without an error
321
+ * object are untouched; an error also passed to `captureException` in
322
+ * the same tick is reported once; a burst limit per error type and throw
323
+ * site keeps ten at once, then one every ten seconds (the count left out
324
+ * is reported). It never fails a request on its own. `false` turns it
325
+ * off; an object turns single loggers off, e.g. `{ console: false }`.
326
+ * Default true.
327
+ */
328
+ captureLoggedErrors?: CaptureLoggedErrorsOption;
271
329
  /**
272
330
  * Record unhandled promise rejections as errors (severity "error",
273
331
  * `autter.unhandled_rejection: true`). Never changes whether the process
@@ -617,6 +675,18 @@ interface RuntimeRequestOptions {
617
675
  ignore?: string[];
618
676
  /** Receives the log flush promise after the response (serverless `waitUntil`). */
619
677
  waitUntil?: (promise: Promise<unknown>) => void;
678
+ /**
679
+ * Report thrown errors (default true): `false`, or a predicate per error.
680
+ * An error it turns down is rethrown untouched and does not fail the
681
+ * summary (Next.js `redirect()` / `notFound()` signals).
682
+ */
683
+ capture?: boolean | ((error: unknown) => boolean);
684
+ /**
685
+ * Answer a thrown error with `toClientError` JSON and the error's own
686
+ * 4xx/5xx status (else 500) instead of rethrowing it, so a thrown 4xx is
687
+ * recorded with its stack and still answered as a 4xx. Default false.
688
+ */
689
+ errorResponse?: boolean | ((error: unknown) => boolean);
620
690
  }
621
691
  /**
622
692
  * Report an error that reached a request boundary — a framework error hook,
@@ -806,6 +876,8 @@ interface AutterLoggingOptions {
806
876
  sampleRate?: number;
807
877
  /** Same semantics as initAutterServer: errors never reported (type or message). */
808
878
  ignoreErrors?: IgnoreErrorPattern[];
879
+ /** Same semantics as initAutterServer: errors logged through console.error, pino, winston, bunyan or runtimeLogger.error are occurrences. Default true. */
880
+ captureLoggedErrors?: CaptureLoggedErrorsOption;
809
881
  }
810
882
  interface AutterLogging {
811
883
  captureException(error: unknown, attributes?: Attributes): void;
@@ -988,4 +1060,4 @@ declare function runDoctor(options?: DoctorOptions): Promise<DoctorReport>;
988
1060
  */
989
1061
  declare function addBreadcrumb(input: BreadcrumbInput): void;
990
1062
 
991
- export { type AutoFlushHandle, type AutoFlushOptions, type AutterErrorHandlerOptions, type AutterErrorResponseOptions, type AutterLogging, type AutterLoggingOptions, type AutterRequestsOptions, type AutterServer, type AutterServerOptions, type AutterSeverity, BreadcrumbInput, COMPAT_MANIFEST, type CompatIssue, type DoctorOptions, type DoctorReport, FileSinkOptions, type FlushTarget, INGESTER_VERSION_HEADER, type IngesterCompatInfo, type InstrumentLlmOptions, type LlmCallHandle, type LlmCallInfo, type LlmUsage, RedactOptions, type RelayOptions, RuntimeCarrier, type RuntimeContextHandle, type RuntimeEnrichContext, type RuntimeEnrichEvent, type RuntimeEnricher, RuntimeLogContext, RuntimeLogLevel, type RuntimeLoggingOptions, type RuntimeOperation, RuntimeOperationKind, type RuntimeOperationOptions, RuntimeOutcome, type RuntimeRequestOptions, RuntimeSink, SDK_IDENTITY, type SafeCapture, type SdkIdentity, type TrackedLlmCall, type UnhandledRejectionsMode, addBreadcrumb, autterErrorHandler, autterErrorResponse, autterFastify, autterFastifyErrors, autterKoa, autterRequests, captureException, captureMessage, captureRequestError, createBrowserRelayFetchHandler, createBrowserRelayHandler, createRuntimeLogger, emitLlmSelftestTrace, enrichDeployment, enrichEdgeGeo, enrichRequestSize, enrichUserAgent, evaluateCompat, fetchIngesterCompat, flushRuntimeLogs, initAutterLogging, initAutterServer, installAutterAutoFlush, instrumentLlmClient, makeSafeCapture, reportOutcome, runDoctor, runInBackground, runtimeContext, runtimeLogStats, runtimeLogger, sanitizeBrowserPayload, startCaughtExceptionSampler, trackLlmCall, unhandledRejectionsMode, withLlmCall, withProcessSpan, withRuntimeOperation, withRuntimeRequest };
1063
+ export { type AutoFlushHandle, type AutoFlushOptions, type AutterErrorHandlerOptions, type AutterErrorResponseOptions, type AutterLogging, type AutterLoggingOptions, type AutterRequestsOptions, type AutterServer, type AutterServerOptions, type AutterSeverity, BreadcrumbInput, COMPAT_MANIFEST, CaptureLoggedErrorsOption, type CompatIssue, type DoctorOptions, type DoctorReport, FileSinkOptions, type FlushTarget, INGESTER_VERSION_HEADER, type IngesterCompatInfo, type InstrumentLlmOptions, type LlmCallHandle, type LlmCallInfo, type LlmUsage, RedactOptions, type RelayOptions, RuntimeCarrier, type RuntimeContextHandle, type RuntimeEnrichContext, type RuntimeEnrichEvent, type RuntimeEnricher, RuntimeLogContext, RuntimeLogLevel, type RuntimeLoggingOptions, type RuntimeOperation, RuntimeOperationKind, type RuntimeOperationOptions, RuntimeOutcome, type RuntimeRequestOptions, RuntimeSink, SDK_IDENTITY, type SafeCapture, type SdkIdentity, type TrackedLlmCall, type UnhandledRejectionsMode, addBreadcrumb, autterErrorHandler, autterErrorResponse, autterFastify, autterFastifyErrors, autterKoa, autterRequests, captureException, captureMessage, captureRequestError, createBrowserRelayFetchHandler, createBrowserRelayHandler, createRuntimeLogger, emitLlmSelftestTrace, enrichDeployment, enrichEdgeGeo, enrichRequestSize, enrichUserAgent, evaluateCompat, fetchIngesterCompat, flushRuntimeLogs, initAutterLogging, initAutterServer, installAutterAutoFlush, instrumentLlmClient, makeSafeCapture, reportOutcome, runDoctor, runInBackground, runtimeContext, runtimeLogStats, runtimeLogger, sanitizeBrowserPayload, startCaughtExceptionSampler, trackLlmCall, unhandledRejectionsMode, withLlmCall, withProcessSpan, withRuntimeOperation, withRuntimeRequest };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { IncomingMessage, ServerResponse } from 'node:http';
2
- import { b as RedactOptions, c as Redactor$1, d as RuntimeLogLevel, e as RuntimeOperationKind, f as RuntimeSink, F as FileSinkOptions, g as RuntimeLogContext, a as RuntimeOutcome, h as RuntimeCarrier, I as IgnoreErrorPattern, B as BreadcrumbInput } from './sinks-CIdlmYwy.js';
3
- export { i as Breadcrumb, j as BreadcrumbLevel, k as BreadcrumbType, l as CODE_PATTERN, n as ClientErrorBody, o as ConsoleSinkOptions, O as OtlpSinkOptions, p as RuntimeError, q as RuntimeErrorCatalog, r as RuntimeErrorDefinition, s as RuntimeErrorExtras, t as RuntimeErrorFactory, u as RuntimeErrorLike, v as RuntimeErrorOptions, R as RuntimeEvent, w as RuntimeLogContextValue, x as RuntimeSinkContext, y as consoleSink, z as defineRuntimeErrors, A as errorAttributes, D as fileSink, E as isRuntimeErrorLike, G as otlpSink, H as redactAttributes, J as redactText, K as toClientError } from './sinks-CIdlmYwy.js';
2
+ import { b as RedactOptions, c as Redactor$1, d as RuntimeLogLevel, e as RuntimeOperationKind, f as RuntimeSink, F as FileSinkOptions, g as RuntimeLogContext, a as RuntimeOutcome, h as RuntimeCarrier, I as IgnoreErrorPattern, i as CaptureLoggedErrorsOption, B as BreadcrumbInput } from './sinks-CLtQFPnn.js';
3
+ export { j as Breadcrumb, k as BreadcrumbLevel, l as BreadcrumbType, n as CODE_PATTERN, o as ClientErrorBody, p as ConsoleSinkOptions, L as LoggedErrorSource, O as OtlpSinkOptions, q as RuntimeError, r as RuntimeErrorCatalog, s as RuntimeErrorDefinition, t as RuntimeErrorExtras, u as RuntimeErrorFactory, v as RuntimeErrorLike, w as RuntimeErrorOptions, R as RuntimeEvent, x as RuntimeLogContextValue, y as RuntimeSinkContext, z as consoleSink, A as defineRuntimeErrors, D as errorAttributes, E as fileSink, G as isRuntimeErrorLike, H as otlpSink, J as redactAttributes, K as redactText, N as toClientError } from './sinks-CLtQFPnn.js';
4
4
  import { Attributes } from '@opentelemetry/api';
5
5
 
6
6
  /**
@@ -23,6 +23,37 @@ interface RelayOptions {
23
23
  * `false` to disable (e.g. when a WAF already rate-limits).
24
24
  */
25
25
  perIpRateLimit?: number | false;
26
+ /**
27
+ * How the per-client rate limit finds the visitor's address. A caller can
28
+ * write anything into `X-Forwarded-For`, so the relay never takes its
29
+ * first (leftmost) entry unless told to.
30
+ *
31
+ * - `"auto"` (default): works behind any load balancer, CDN or platform
32
+ * proxy without setup. Proxies append the address they saw to the right
33
+ * of the header (or overwrite it), so the relay reads it right to left
34
+ * and takes the first public address, skipping private and loopback
35
+ * hops. The Node handler only does this when the connection itself came
36
+ * from a private address (a proxy on your network); a public peer is the
37
+ * visitor and its header is ignored. On Cloudflare Workers it uses
38
+ * `CF-Connecting-IP`.
39
+ * - a number `n`: exactly `n` proxies you control sit in front; use the
40
+ * entry `n` from the right (Express `trust proxy` semantics). Use `1`
41
+ * for a Node origin behind Cloudflare with no load balancer.
42
+ * - `true`: every proxy in front is yours and strips client values; use
43
+ * the leftmost entry.
44
+ * - `false`: never read the header. The Node handler keys the socket peer;
45
+ * the fetch handler keys one shared bucket for the route.
46
+ *
47
+ * Strings from environment variables are read too (`"auto"`, `"true"`,
48
+ * `"false"`, `"0"`, `"2"`); anything else falls back to `"auto"`.
49
+ */
50
+ trustProxy?: boolean | "auto" | number | string;
51
+ /**
52
+ * Return the visitor's address yourself (Deno's `info.remoteAddr`, Bun's
53
+ * `server.requestIP`, a header your edge sets). Wins over `trustProxy`
54
+ * when it returns a non-empty string.
55
+ */
56
+ clientIp?: (request: Request | IncomingMessage) => string | null | undefined;
26
57
  /** Called when the async forward fails (default: console.warn). */
27
58
  onError?: (err: unknown) => void;
28
59
  /**
@@ -167,6 +198,19 @@ declare const runtimeContext: {
167
198
  warn(message: string, attributes?: RuntimeLogContext): void;
168
199
  /** Log an error and attach it (code/why/fix) to the current summary. */
169
200
  error(error: unknown, attributes?: RuntimeLogContext): void;
201
+ /**
202
+ * The app caught `error` and chose to continue on a fallback (RF-02):
203
+ *
204
+ * } catch (err) {
205
+ * runtimeContext.degraded(err, "pricing service down, using cached prices");
206
+ * }
207
+ *
208
+ * Recorded as an occurrence at `warning` severity, marked
209
+ * `autter.handling = "deliberate"` with the reason (grouped by its code
210
+ * when the error has one), never an incident. The current request or
211
+ * operation ends "degraded" unless it failed or set its outcome.
212
+ */
213
+ degraded(error: unknown, reason: string, attributes?: RuntimeLogContext): void;
170
214
  /** Current operation id. */
171
215
  readonly id: string | undefined;
172
216
  /** Current request id (inherited by child operations and forks). */
@@ -268,6 +312,20 @@ interface AutterServerOptions {
268
312
  * Default true; false turns both off.
269
313
  */
270
314
  captureGlobalErrors?: boolean;
315
+ /**
316
+ * Errors the app caught and logged count (RF-01): an `Error` passed to
317
+ * `console.error`, pino, winston, bunyan or `runtimeLogger.error` becomes
318
+ * an occurrence at `error` severity, marked `autter.handled` /
319
+ * `autter.handling: "caught"`, with the log line as context
320
+ * (`autter.log.source`, `autter.log.message`). Log lines without an error
321
+ * object are untouched; an error also passed to `captureException` in
322
+ * the same tick is reported once; a burst limit per error type and throw
323
+ * site keeps ten at once, then one every ten seconds (the count left out
324
+ * is reported). It never fails a request on its own. `false` turns it
325
+ * off; an object turns single loggers off, e.g. `{ console: false }`.
326
+ * Default true.
327
+ */
328
+ captureLoggedErrors?: CaptureLoggedErrorsOption;
271
329
  /**
272
330
  * Record unhandled promise rejections as errors (severity "error",
273
331
  * `autter.unhandled_rejection: true`). Never changes whether the process
@@ -617,6 +675,18 @@ interface RuntimeRequestOptions {
617
675
  ignore?: string[];
618
676
  /** Receives the log flush promise after the response (serverless `waitUntil`). */
619
677
  waitUntil?: (promise: Promise<unknown>) => void;
678
+ /**
679
+ * Report thrown errors (default true): `false`, or a predicate per error.
680
+ * An error it turns down is rethrown untouched and does not fail the
681
+ * summary (Next.js `redirect()` / `notFound()` signals).
682
+ */
683
+ capture?: boolean | ((error: unknown) => boolean);
684
+ /**
685
+ * Answer a thrown error with `toClientError` JSON and the error's own
686
+ * 4xx/5xx status (else 500) instead of rethrowing it, so a thrown 4xx is
687
+ * recorded with its stack and still answered as a 4xx. Default false.
688
+ */
689
+ errorResponse?: boolean | ((error: unknown) => boolean);
620
690
  }
621
691
  /**
622
692
  * Report an error that reached a request boundary — a framework error hook,
@@ -806,6 +876,8 @@ interface AutterLoggingOptions {
806
876
  sampleRate?: number;
807
877
  /** Same semantics as initAutterServer: errors never reported (type or message). */
808
878
  ignoreErrors?: IgnoreErrorPattern[];
879
+ /** Same semantics as initAutterServer: errors logged through console.error, pino, winston, bunyan or runtimeLogger.error are occurrences. Default true. */
880
+ captureLoggedErrors?: CaptureLoggedErrorsOption;
809
881
  }
810
882
  interface AutterLogging {
811
883
  captureException(error: unknown, attributes?: Attributes): void;
@@ -988,4 +1060,4 @@ declare function runDoctor(options?: DoctorOptions): Promise<DoctorReport>;
988
1060
  */
989
1061
  declare function addBreadcrumb(input: BreadcrumbInput): void;
990
1062
 
991
- export { type AutoFlushHandle, type AutoFlushOptions, type AutterErrorHandlerOptions, type AutterErrorResponseOptions, type AutterLogging, type AutterLoggingOptions, type AutterRequestsOptions, type AutterServer, type AutterServerOptions, type AutterSeverity, BreadcrumbInput, COMPAT_MANIFEST, type CompatIssue, type DoctorOptions, type DoctorReport, FileSinkOptions, type FlushTarget, INGESTER_VERSION_HEADER, type IngesterCompatInfo, type InstrumentLlmOptions, type LlmCallHandle, type LlmCallInfo, type LlmUsage, RedactOptions, type RelayOptions, RuntimeCarrier, type RuntimeContextHandle, type RuntimeEnrichContext, type RuntimeEnrichEvent, type RuntimeEnricher, RuntimeLogContext, RuntimeLogLevel, type RuntimeLoggingOptions, type RuntimeOperation, RuntimeOperationKind, type RuntimeOperationOptions, RuntimeOutcome, type RuntimeRequestOptions, RuntimeSink, SDK_IDENTITY, type SafeCapture, type SdkIdentity, type TrackedLlmCall, type UnhandledRejectionsMode, addBreadcrumb, autterErrorHandler, autterErrorResponse, autterFastify, autterFastifyErrors, autterKoa, autterRequests, captureException, captureMessage, captureRequestError, createBrowserRelayFetchHandler, createBrowserRelayHandler, createRuntimeLogger, emitLlmSelftestTrace, enrichDeployment, enrichEdgeGeo, enrichRequestSize, enrichUserAgent, evaluateCompat, fetchIngesterCompat, flushRuntimeLogs, initAutterLogging, initAutterServer, installAutterAutoFlush, instrumentLlmClient, makeSafeCapture, reportOutcome, runDoctor, runInBackground, runtimeContext, runtimeLogStats, runtimeLogger, sanitizeBrowserPayload, startCaughtExceptionSampler, trackLlmCall, unhandledRejectionsMode, withLlmCall, withProcessSpan, withRuntimeOperation, withRuntimeRequest };
1063
+ export { type AutoFlushHandle, type AutoFlushOptions, type AutterErrorHandlerOptions, type AutterErrorResponseOptions, type AutterLogging, type AutterLoggingOptions, type AutterRequestsOptions, type AutterServer, type AutterServerOptions, type AutterSeverity, BreadcrumbInput, COMPAT_MANIFEST, CaptureLoggedErrorsOption, type CompatIssue, type DoctorOptions, type DoctorReport, FileSinkOptions, type FlushTarget, INGESTER_VERSION_HEADER, type IngesterCompatInfo, type InstrumentLlmOptions, type LlmCallHandle, type LlmCallInfo, type LlmUsage, RedactOptions, type RelayOptions, RuntimeCarrier, type RuntimeContextHandle, type RuntimeEnrichContext, type RuntimeEnrichEvent, type RuntimeEnricher, RuntimeLogContext, RuntimeLogLevel, type RuntimeLoggingOptions, type RuntimeOperation, RuntimeOperationKind, type RuntimeOperationOptions, RuntimeOutcome, type RuntimeRequestOptions, RuntimeSink, SDK_IDENTITY, type SafeCapture, type SdkIdentity, type TrackedLlmCall, type UnhandledRejectionsMode, addBreadcrumb, autterErrorHandler, autterErrorResponse, autterFastify, autterFastifyErrors, autterKoa, autterRequests, captureException, captureMessage, captureRequestError, createBrowserRelayFetchHandler, createBrowserRelayHandler, createRuntimeLogger, emitLlmSelftestTrace, enrichDeployment, enrichEdgeGeo, enrichRequestSize, enrichUserAgent, evaluateCompat, fetchIngesterCompat, flushRuntimeLogs, initAutterLogging, initAutterServer, installAutterAutoFlush, instrumentLlmClient, makeSafeCapture, reportOutcome, runDoctor, runInBackground, runtimeContext, runtimeLogStats, runtimeLogger, sanitizeBrowserPayload, startCaughtExceptionSampler, trackLlmCall, unhandledRejectionsMode, withLlmCall, withProcessSpan, withRuntimeOperation, withRuntimeRequest };
package/dist/index.js CHANGED
@@ -21,10 +21,12 @@ import {
21
21
  fingerprintEventAttributes,
22
22
  fingerprintValues,
23
23
  flushRuntimeLogs,
24
+ isCaptured,
24
25
  isError,
25
26
  isRuntimeErrorLike,
26
27
  local,
27
28
  makeRedactor,
29
+ markCaptured,
28
30
  normalizeRoutePath,
29
31
  noteOperationError,
30
32
  notifyException,
@@ -42,14 +44,17 @@ import {
42
44
  runtimeLogStats,
43
45
  runtimeLogger,
44
46
  sanitizeBreadcrumbs,
47
+ setHandledCapture,
45
48
  shutdownRuntimeLogger,
46
49
  splitFingerprint,
50
+ startLoggedErrorCapture,
51
+ stopLoggedErrorCapture,
47
52
  toClientError,
48
53
  withRuntimeOperation
49
- } from "./chunk-QKY2OQSO.js";
54
+ } from "./chunk-HHKQ3UCH.js";
50
55
  import {
51
56
  runDoctor
52
- } from "./chunk-UC3QH4PC.js";
57
+ } from "./chunk-JSGGUWRD.js";
53
58
  import {
54
59
  COMPAT_MANIFEST,
55
60
  CountingExporter,
@@ -71,7 +76,7 @@ import {
71
76
  sharedSlot,
72
77
  telemetryStats,
73
78
  unregisterFlushTargets
74
- } from "./chunk-B5ZMOCEM.js";
79
+ } from "./chunk-ZKM76SDP.js";
75
80
 
76
81
  // src/relay.ts
77
82
  var IpWindow = class {
@@ -96,6 +101,112 @@ function firstForwardedFor(value) {
96
101
  const raw = Array.isArray(value) ? value[0] : value;
97
102
  return raw ? (raw.split(",")[0] ?? "").trim() : "";
98
103
  }
104
+ var TRUST_AUTO = { mode: "auto" };
105
+ var warnedTrustProxy = false;
106
+ function resolveTrustProxy(value) {
107
+ if (value === void 0 || value === null || value === "auto") return TRUST_AUTO;
108
+ if (value === true) return { mode: "all" };
109
+ if (value === false || value === 0) return { mode: "never" };
110
+ if (typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 100) {
111
+ return { mode: "hops", hops: value };
112
+ }
113
+ if (typeof value === "string") {
114
+ const v = value.trim().toLowerCase();
115
+ if (v === "" || v === "auto") return TRUST_AUTO;
116
+ if (v === "false" || v === "0" || v === "no" || v === "off") return { mode: "never" };
117
+ if (v === "true" || v === "yes" || v === "on") return { mode: "all" };
118
+ if (/^[1-9][0-9]?$/.test(v)) return { mode: "hops", hops: Number(v) };
119
+ }
120
+ if (!warnedTrustProxy) {
121
+ warnedTrustProxy = true;
122
+ console.warn(
123
+ `[autter-runtime] relay: trustProxy ${JSON.stringify(value)} is not recognised; using "auto".`
124
+ );
125
+ }
126
+ return TRUST_AUTO;
127
+ }
128
+ var IPV4_RE = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/;
129
+ function normalizeAddress(entry) {
130
+ if (!entry) return null;
131
+ let a = entry.trim();
132
+ if (!a || a.length > 64) return null;
133
+ const bracketed = /^\[([0-9A-Fa-f:.]+)\](?::\d{1,5})?$/.exec(a);
134
+ if (bracketed) a = bracketed[1] ?? "";
135
+ else if (/^[\d.]+:\d{1,5}$/.test(a)) a = a.slice(0, a.lastIndexOf(":"));
136
+ a = a.toLowerCase();
137
+ if (a.startsWith("::ffff:") && IPV4_RE.test(a.slice(7))) a = a.slice(7);
138
+ if (IPV4_RE.test(a)) return a;
139
+ if (/^[0-9a-f:]+(:(\d{1,3}\.){3}\d{1,3})?$/.test(a) && a.includes(":") && a.split(":").length <= 9) {
140
+ return a;
141
+ }
142
+ return null;
143
+ }
144
+ function isPrivateAddress(a) {
145
+ if (IPV4_RE.test(a)) {
146
+ const [x, y] = a.split(".").map(Number);
147
+ return x === 10 || x === 127 || x === 0 || x === 169 && y === 254 || x === 172 && y >= 16 && y <= 31 || x === 192 && y === 168 || x === 100 && y >= 64 && y <= 127;
148
+ }
149
+ return a === "::1" || a === "::" || /^f[cd]/.test(a) || /^fe[89ab]/.test(a);
150
+ }
151
+ function forwardedEntries(value) {
152
+ const raw = Array.isArray(value) ? value.join(",") : value;
153
+ if (!raw) return [];
154
+ const out = [];
155
+ for (const part of raw.split(",")) {
156
+ const a = normalizeAddress(part);
157
+ if (a) out.push(a);
158
+ }
159
+ return out;
160
+ }
161
+ function rightmostPublic(entries) {
162
+ for (let i = entries.length - 1; i >= 0; i--) {
163
+ const a = entries[i];
164
+ if (a && !isPrivateAddress(a)) return a;
165
+ }
166
+ return null;
167
+ }
168
+ function fromCustom(opts, request) {
169
+ if (!opts.clientIp) return null;
170
+ try {
171
+ const v = opts.clientIp(request);
172
+ return typeof v === "string" && v.trim() ? v.trim().slice(0, 64) : null;
173
+ } catch {
174
+ return null;
175
+ }
176
+ }
177
+ function fetchClientKey(request, opts) {
178
+ const custom = fromCustom(opts, request);
179
+ if (custom) return custom;
180
+ const trust = resolveTrustProxy(opts.trustProxy);
181
+ if (trust.mode === "never") return "shared";
182
+ const header = request.headers.get("x-forwarded-for");
183
+ if (trust.mode === "all") return firstForwardedFor(header) || "shared";
184
+ const entries = forwardedEntries(header);
185
+ if (trust.mode === "hops") {
186
+ return entries[Math.max(0, entries.length - trust.hops)] ?? "shared";
187
+ }
188
+ const cf = request.cf;
189
+ if (cf !== null && typeof cf === "object") {
190
+ const viaCf = normalizeAddress(request.headers.get("cf-connecting-ip"));
191
+ if (viaCf) return viaCf;
192
+ }
193
+ return rightmostPublic(entries) ?? entries[entries.length - 1] ?? "shared";
194
+ }
195
+ function nodeClientKey(req, opts) {
196
+ const custom = fromCustom(opts, req);
197
+ if (custom) return custom;
198
+ const trust = resolveTrustProxy(opts.trustProxy);
199
+ const peer = normalizeAddress(req.socket?.remoteAddress);
200
+ if (trust.mode === "never") return peer ?? "unknown";
201
+ const header = req.headers["x-forwarded-for"];
202
+ if (trust.mode === "all") return firstForwardedFor(header) || peer || "unknown";
203
+ const entries = forwardedEntries(header);
204
+ if (trust.mode === "hops") {
205
+ return entries[Math.max(0, entries.length - trust.hops)] ?? peer ?? "unknown";
206
+ }
207
+ if (peer && !isPrivateAddress(peer)) return peer;
208
+ return rightmostPublic(entries) ?? peer ?? entries[entries.length - 1] ?? "unknown";
209
+ }
99
210
  var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
100
211
  var DEFAULT_MAX_BODY = 64 * 1024;
101
212
  var MAX_EVENTS = 50;
@@ -329,7 +440,7 @@ function createBrowserRelayFetchHandler(opts) {
329
440
  return new Response(null, { status: 405 });
330
441
  }
331
442
  if (limiter) {
332
- const ip = firstForwardedFor(request.headers.get("x-forwarded-for")) || "unknown";
443
+ const ip = fetchClientKey(request, opts);
333
444
  if (!limiter.allow(ip)) {
334
445
  return new Response(JSON.stringify({ error: "rate limit exceeded" }), {
335
446
  status: 429
@@ -395,7 +506,7 @@ function createBrowserRelayHandler(opts) {
395
506
  return;
396
507
  }
397
508
  if (limiter) {
398
- const ip = firstForwardedFor(req.headers["x-forwarded-for"]) || req.socket?.remoteAddress || "unknown";
509
+ const ip = nodeClientKey(req, opts);
399
510
  if (!limiter.allow(ip)) {
400
511
  respond(res, 429, { error: "rate limit exceeded" });
401
512
  return;
@@ -746,7 +857,6 @@ import { context, trace } from "@opentelemetry/api";
746
857
  import { getRPCMetadata, RPCType } from "@opentelemetry/core";
747
858
  var REQUEST_STATE = /* @__PURE__ */ Symbol.for("autter.runtime.request");
748
859
  var BOUND = /* @__PURE__ */ Symbol.for("autter.runtime.bound");
749
- var CAPTURED = sharedSlot("requests", () => ({ captured: /* @__PURE__ */ new WeakSet() })).captured;
750
860
  var capturer = () => {
751
861
  };
752
862
  function setRequestExceptionCapturer(fn) {
@@ -757,8 +867,14 @@ var annotator = () => {
757
867
  function setRequestSpanAnnotator(fn) {
758
868
  annotator = fn;
759
869
  }
760
- function markCaptured(error) {
761
- if (error && typeof error === "object") CAPTURED.add(error);
870
+ function decide(rule, error, fallback) {
871
+ if (rule === void 0) return fallback;
872
+ if (typeof rule !== "function") return rule;
873
+ try {
874
+ return rule(error) === true;
875
+ } catch {
876
+ return false;
877
+ }
762
878
  }
763
879
  function expressRouteOf(req) {
764
880
  const props = req;
@@ -789,7 +905,7 @@ function captureAtBoundary(error, state, rule = void 0, attributes) {
789
905
  wanted = false;
790
906
  }
791
907
  if (!wanted) return;
792
- if (error && typeof error === "object" && CAPTURED.has(error)) return;
908
+ if (isCaptured(error)) return;
793
909
  markCaptured(error);
794
910
  try {
795
911
  const run = () => onRequestSpan(() => capturer(error, attributes));
@@ -803,7 +919,7 @@ function requestStateOf(req) {
803
919
  return req[REQUEST_STATE];
804
920
  }
805
921
  function captureRequestError(error, attributes) {
806
- if (error && typeof error === "object" && CAPTURED.has(error)) {
922
+ if (isCaptured(error)) {
807
923
  if (!attributes) return;
808
924
  try {
809
925
  onRequestSpan(() => annotator(attributes));
@@ -895,7 +1011,8 @@ function finishRequest(state, input) {
895
1011
  ...state.error !== void 0 ? { error: state.error } : {},
896
1012
  ...state.errorThrown ? { thrown: true } : {},
897
1013
  status: input.status,
898
- aborted: input.aborted
1014
+ aborted: input.aborted,
1015
+ ...state.degraded ? { degraded: true } : {}
899
1016
  });
900
1017
  const span = trace.getActiveSpan();
901
1018
  if (span?.isRecording()) span.setAttribute("autter.operation.outcome", state.outcome);
@@ -1120,9 +1237,15 @@ function withRuntimeRequest(handler, options = {}) {
1120
1237
  try {
1121
1238
  return await handler(request, ...rest);
1122
1239
  } catch (error) {
1123
- noteOperationError(error, true, state);
1124
- captureAtBoundary(error, state, true);
1125
- throw error;
1240
+ if (decide(options.capture, error, true)) {
1241
+ noteOperationError(error, true, state);
1242
+ captureAtBoundary(error, state, true);
1243
+ }
1244
+ if (!decide(options.errorResponse, error, false)) throw error;
1245
+ return new Response(JSON.stringify(toClientError(error, requestId)), {
1246
+ status: responseStatusOf(error),
1247
+ headers: { "content-type": "application/json; charset=utf-8" }
1248
+ });
1126
1249
  }
1127
1250
  });
1128
1251
  status = response.status;
@@ -1320,6 +1443,8 @@ var S = sharedSlot("server", () => ({
1320
1443
  alwaysOnProvider: null,
1321
1444
  redactor: makeRedactor(true),
1322
1445
  loggerOnlyCapture: null,
1446
+ handled: null,
1447
+ loggerOnlyHandled: null,
1323
1448
  hookRequests: false
1324
1449
  }));
1325
1450
  var LLM_MARKER_ATTRIBUTES = [
@@ -1896,6 +2021,37 @@ function initAutterServer(options) {
1896
2021
  span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
1897
2022
  if (!reuseActive) span.end();
1898
2023
  }
2024
+ function captureHandled(error, attributes, severity) {
2025
+ const isErr = isError(error);
2026
+ if (!errorFilter({
2027
+ type: isErr ? String(error.name ?? "") : "Error",
2028
+ message: isErr ? String(error.message ?? "") : String(error),
2029
+ severity
2030
+ })) return;
2031
+ if (severity === "error") errorTraceBuffer?.retainActiveTrace();
2032
+ telemetryStats.markCaptured();
2033
+ const message = redactor.text(isErr ? error.message : String(error));
2034
+ const stack = isErr && error.stack ? error.stack : new Error().stack?.split("\n").filter((line, i) => i === 0 || !/captureHandled|report(?:Degraded|LoggedError)/.test(line)).join("\n");
2035
+ const split = splitFingerprint(attributes);
2036
+ const fingerprint = fingerprintEventAttributes(split.fingerprint, redactor.text);
2037
+ const redacted = redactor(split.attributes);
2038
+ const activeSpan = trace2.getActiveSpan();
2039
+ const reuseActive = activeSpan?.isRecording() === true;
2040
+ const span = reuseActive ? activeSpan : errorTracer.startSpan(isErr ? error.name : "Error");
2041
+ markCaptured(error);
2042
+ notifyException(error, { ...redacted, ...fingerprint, "autter.severity": severity });
2043
+ span.addEvent("exception", {
2044
+ ...redacted,
2045
+ "exception.type": isErr ? error.name : "Error",
2046
+ "exception.message": message,
2047
+ ...stack ? { "exception.stacktrace": redactor.text(stack) } : {},
2048
+ "autter.severity": severity,
2049
+ ...errorAttributes(error),
2050
+ ...fingerprint,
2051
+ ...breadcrumbEventAttributes(redactor.text, error)
2052
+ });
2053
+ if (!reuseActive) span.end();
2054
+ }
1899
2055
  configureRuntimeLogger({
1900
2056
  endpoint,
1901
2057
  apiKey: options.apiKey,
@@ -1909,6 +2065,8 @@ function initAutterServer(options) {
1909
2065
  reportOutcome: reportOutcome2
1910
2066
  });
1911
2067
  S.hookRequests = options.logging?.requests === true;
2068
+ S.handled = captureHandled;
2069
+ startLoggedErrorCapture(options.captureLoggedErrors);
1912
2070
  let onCrash = null;
1913
2071
  let uninstallRejections = null;
1914
2072
  if (options.captureGlobalErrors !== false) {
@@ -1955,6 +2113,7 @@ function initAutterServer(options) {
1955
2113
  withLlmCall: (info, fn) => runLlmSpan(llmTracer, info, fn),
1956
2114
  trackLlmCall: (call) => recordLlmCall(llmTracer, call),
1957
2115
  shutdown: async () => {
2116
+ if (S.active === server) stopLoggedErrorCapture();
1958
2117
  const logShutdown = await Promise.allSettled([shutdownRuntimeLogger()]);
1959
2118
  if (onCrash) process.off("uncaughtExceptionMonitor", onCrash);
1960
2119
  uninstallRejections?.();
@@ -1962,6 +2121,7 @@ function initAutterServer(options) {
1962
2121
  if (S.active === server) {
1963
2122
  S.active = null;
1964
2123
  S.alwaysOnProvider = null;
2124
+ S.handled = null;
1965
2125
  S.hookRequests = false;
1966
2126
  S.redactor = makeRedactor(true);
1967
2127
  }
@@ -1997,13 +2157,39 @@ function captureException(error, attributes) {
1997
2157
  recordError(span, error);
1998
2158
  span.end();
1999
2159
  }
2000
- function setLoggerOnlyCapture(fn) {
2160
+ function setLoggerOnlyCapture(fn, handled = null) {
2001
2161
  S.loggerOnlyCapture = fn;
2162
+ S.loggerOnlyHandled = handled;
2163
+ }
2164
+ function captureHandledError(error, attributes, severity) {
2165
+ if (S.active) {
2166
+ S.handled?.(error, attributes, severity);
2167
+ return;
2168
+ }
2169
+ if (S.loggerOnlyHandled) {
2170
+ S.loggerOnlyHandled(error, attributes, severity);
2171
+ return;
2172
+ }
2173
+ markCaptured(error);
2174
+ const split = splitFingerprint(attributes);
2175
+ notifyException(error, { ...split.attributes, "autter.severity": severity });
2176
+ const span = trace2.getTracer("autter-errors").startSpan(isError(error) ? error.name : "Error");
2177
+ span.addEvent("exception", {
2178
+ ...S.redactor(split.attributes),
2179
+ "exception.type": isError(error) ? error.name : "Error",
2180
+ "exception.message": S.redactor.text(isError(error) ? error.message : String(error)),
2181
+ ...isError(error) && error.stack ? { "exception.stacktrace": S.redactor.text(error.stack) } : {},
2182
+ "autter.severity": severity,
2183
+ ...errorAttributes(error),
2184
+ ...fingerprintEventAttributes(split.fingerprint, S.redactor.text)
2185
+ });
2186
+ span.end();
2002
2187
  }
2003
2188
  function serverActive() {
2004
2189
  return S.active !== null;
2005
2190
  }
2006
2191
  setRequestExceptionCapturer((error, attributes) => captureException(error, attributes));
2192
+ setHandledCapture(captureHandledError);
2007
2193
  setRequestSpanAnnotator((attributes) => {
2008
2194
  const span = trace2.getActiveSpan();
2009
2195
  if (span?.isRecording()) span.setAttributes(S.redactor(splitFingerprint(attributes).attributes));
@@ -2474,6 +2660,33 @@ function initAutterLogging(options) {
2474
2660
  notifyException(error, { ...redacted, ...fingerprint });
2475
2661
  captureExceptionAsLog(error, { ...redacted, ...fingerprint });
2476
2662
  }
2663
+ function captureHandled(error, attributes, severity) {
2664
+ markCaptured(error);
2665
+ const isErrValue = isError(error);
2666
+ if (!errorFilter({
2667
+ type: isErrValue ? String(error.name ?? "") : "Error",
2668
+ message: isErrValue ? String(error.message ?? "") : String(error),
2669
+ severity
2670
+ })) return;
2671
+ const split = splitFingerprint(attributes);
2672
+ const fingerprint = fingerprintEventAttributes(split.fingerprint, redact.text);
2673
+ const redacted = redact(split.attributes);
2674
+ notifyException(error, { ...redacted, ...fingerprint, "autter.severity": severity });
2675
+ const span = trace3.getActiveSpan();
2676
+ if (mode === "auto" && span?.isRecording()) {
2677
+ span.addEvent("exception", {
2678
+ ...redacted,
2679
+ "exception.type": isErrValue ? error.name : "Error",
2680
+ "exception.message": redact.text(isErrValue ? error.message : String(error)),
2681
+ ...isErrValue && error.stack ? { "exception.stacktrace": redact.text(error.stack) } : {},
2682
+ "autter.severity": severity,
2683
+ ...errorAttributes(error),
2684
+ ...fingerprint
2685
+ });
2686
+ return;
2687
+ }
2688
+ captureExceptionAsLog(error, { ...redacted, ...fingerprint }, severity);
2689
+ }
2477
2690
  const captured = /* @__PURE__ */ new WeakSet();
2478
2691
  configureRuntimeLogger({
2479
2692
  endpoint,
@@ -2498,7 +2711,8 @@ function initAutterLogging(options) {
2498
2711
  captureException2(error);
2499
2712
  }
2500
2713
  });
2501
- setLoggerOnlyCapture(captureException2);
2714
+ setLoggerOnlyCapture(captureException2, captureHandled);
2715
+ startLoggedErrorCapture(options.captureLoggedErrors);
2502
2716
  const onCrash = (error) => {
2503
2717
  if (recordedAsRejection(error)) return;
2504
2718
  captureException2(error, { "autter.unhandled": true });
@@ -2517,6 +2731,7 @@ function initAutterLogging(options) {
2517
2731
  captureException: captureException2,
2518
2732
  flush: flushRuntimeLogs,
2519
2733
  shutdown: async () => {
2734
+ if (L.active === handle) stopLoggedErrorCapture();
2520
2735
  process.off("uncaughtExceptionMonitor", onCrash);
2521
2736
  uninstallRejections?.();
2522
2737
  uninstallRejections = null;