@autter/otlp-ingester 1.6.1 → 1.8.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.
Files changed (50) hide show
  1. package/README.md +31 -5
  2. package/dist/auth.d.ts +16 -1
  3. package/dist/auth.js +34 -10
  4. package/dist/clickhouse.d.ts +3 -0
  5. package/dist/clickhouse.js +38 -1
  6. package/dist/compat-manifest.json +37 -0
  7. package/dist/config.d.ts +23 -2
  8. package/dist/config.js +33 -8
  9. package/dist/context.d.ts +5 -0
  10. package/dist/context.js +49 -10
  11. package/dist/index.d.ts +2 -1
  12. package/dist/index.js +8 -2
  13. package/dist/ingest-drops.d.ts +308 -0
  14. package/dist/ingest-drops.js +448 -0
  15. package/dist/log-promotion.d.ts +4 -3
  16. package/dist/log-promotion.js +5 -5
  17. package/dist/logs.d.ts +16 -1
  18. package/dist/logs.js +17 -3
  19. package/dist/memory.d.ts +9 -1
  20. package/dist/memory.js +28 -7
  21. package/dist/migrations.d.ts +8 -0
  22. package/dist/migrations.js +29 -1
  23. package/dist/normalize-browser.d.ts +53 -25
  24. package/dist/normalize-browser.js +56 -10
  25. package/dist/normalize-otlp.d.ts +6 -0
  26. package/dist/normalize-otlp.js +275 -108
  27. package/dist/otlp-proto.d.ts +14 -0
  28. package/dist/otlp-proto.js +25 -0
  29. package/dist/profiles.d.ts +4 -0
  30. package/dist/profiles.js +6 -2
  31. package/dist/request-spans.d.ts +45 -0
  32. package/dist/request-spans.js +91 -0
  33. package/dist/sdk-versions.d.ts +39 -2
  34. package/dist/sdk-versions.js +49 -8
  35. package/dist/server.d.ts +10 -0
  36. package/dist/server.js +345 -105
  37. package/dist/sink.d.ts +14 -0
  38. package/dist/sink.js +15 -0
  39. package/dist/source-maps-lookup.d.ts +6 -3
  40. package/dist/source-maps-lookup.js +35 -13
  41. package/dist/source-maps.d.ts +6 -2
  42. package/dist/source-maps.js +12 -4
  43. package/dist/stack-frames.d.ts +5 -2
  44. package/dist/stack-frames.js +55 -9
  45. package/dist/symbolicate.d.ts +13 -0
  46. package/dist/symbolicate.js +22 -0
  47. package/dist/trace-dedupe.d.ts +98 -0
  48. package/dist/trace-dedupe.js +385 -0
  49. package/dist/types.d.ts +5 -1
  50. package/package.json +1 -1
@@ -37,7 +37,8 @@ import { profileTableDDL } from "./profiles.js";
37
37
  import { SOURCE_MAP_DEBUG_COLUMNS, SOURCE_MAP_DEBUG_ID_INDEX, sourceMapTableDDL, } from "./source-maps.js";
38
38
  import { LOG_REQUEST_COLUMNS, LOG_REQUEST_ID_INDEX, logTableDDL, requestRollupTableDDL, requestRollupViewDDL, } from "./logs.js";
39
39
  import { memoryTableDDL, platformEventTableDDL } from "./memory.js";
40
- import { ingesterInfoTableDDL, sdkVersionTableDDL } from "./sdk-versions.js";
40
+ import { ingesterInfoTableDDL, SDK_VERSION_VIA_COLUMN, sdkVersionTableDDL } from "./sdk-versions.js";
41
+ import { ingestDropsTableDDL } from "./ingest-drops.js";
41
42
  /** Migration 0013 columns — shared with the baseline DDL in clickhouse.ts. */
42
43
  export const OCCURRENCE_CODE_COLUMNS = [
43
44
  "error_code String DEFAULT ''",
@@ -62,6 +63,17 @@ export const OCCURRENCE_GROUPING_COLUMNS = [
62
63
  "frames String DEFAULT '' CODEC(ZSTD(3))",
63
64
  "fingerprints Array(String) DEFAULT []",
64
65
  ];
66
+ /**
67
+ * Migration 0020 columns — shared with the baseline DDL in clickhouse.ts.
68
+ * In-app browser JS frames a source map resolved / left unresolved at
69
+ * ingest (symbolicate.ts inAppFrameResolution); 0 and 0 for server
70
+ * occurrences, browser ones with no in-app JS frame, and rows written
71
+ * before 0020.
72
+ */
73
+ export const OCCURRENCE_FRAME_RESOLUTION_COLUMNS = [
74
+ "in_app_frames_resolved UInt16 DEFAULT 0",
75
+ "in_app_frames_unresolved UInt16 DEFAULT 0",
76
+ ];
65
77
  export const MIGRATIONS = [
66
78
  // 0001 intentionally reserved as the baseline marker: databases created
67
79
  // before the migration runner existed record it as applied without
@@ -202,6 +214,22 @@ export const MIGRATIONS = [
202
214
  `ALTER TABLE {db}.runtime_source_maps ADD ${SOURCE_MAP_DEBUG_ID_INDEX.replace("INDEX ", "INDEX IF NOT EXISTS ")}`,
203
215
  ],
204
216
  },
217
+ // Rejections and drops per tenant, minute, route and reason (EI-27):
218
+ // what the ingester refused or did not store, so a repository's missing
219
+ // telemetry has a reason (ingest-drops.ts).
220
+ { id: "0018-runtime-ingest-drops", statements: [ingestDropsTableDDL("{db}")] },
221
+ // How each service's telemetry arrived: an OpenTelemetry Collector, an
222
+ // SDK exporter directly, or the browser relay (sdk-versions.ts).
223
+ {
224
+ id: "0019-sdk-versions-via",
225
+ statements: [`ALTER TABLE {db}.runtime_sdk_versions ADD COLUMN IF NOT EXISTS ${SDK_VERSION_VIA_COLUMN}`],
226
+ },
227
+ // Resolved and unresolved in-app browser frames per occurrence, after
228
+ // symbolication — the share the EI-14 source map rule reads.
229
+ {
230
+ id: "0020-occurrence-frame-resolution",
231
+ statements: OCCURRENCE_FRAME_RESOLUTION_COLUMNS.map((column) => `ALTER TABLE {db}.runtime_error_occurrences ADD COLUMN IF NOT EXISTS ${column}`),
232
+ },
205
233
  ];
206
234
  /** The tracking table itself — created by the runner before anything else. */
207
235
  export function migrationsTableDDL(db) {
@@ -1,13 +1,9 @@
1
1
  import { z } from "zod";
2
2
  import { type RuntimeMetricPoint, type RuntimeOccurrenceInput } from "./types.js";
3
+ /** Events past this in one payload are dropped and counted (events_over_limit). */
4
+ export declare const MAX_BROWSER_EVENTS = 50;
5
+ /** A whole payload, every event valid: what normalizeBrowserPayload takes. */
3
6
  export declare const browserPayloadSchema: z.ZodObject<{
4
- version: z.ZodLiteral<1>;
5
- sessionId: z.ZodOptional<z.ZodString>;
6
- service: z.ZodString;
7
- environment: z.ZodString;
8
- release: z.ZodOptional<z.ZodString>;
9
- /** @autter/runtime-browser version (1.4+); recorded for compatibility checks. */
10
- sdk: z.ZodCatch<z.ZodOptional<z.ZodString>>;
11
7
  events: z.ZodArray<z.ZodObject<{
12
8
  id: z.ZodOptional<z.ZodString>;
13
9
  type: z.ZodEnum<["exception", "unhandled_rejection", "message", "session_start", "track_event", "outcome", "request_failure", "csp_violation", "timing"]>;
@@ -52,64 +48,71 @@ export declare const browserPayloadSchema: z.ZodObject<{
52
48
  */
53
49
  traceId: z.ZodCatch<z.ZodOptional<z.ZodString>>;
54
50
  }, "strip", z.ZodTypeAny, {
51
+ type: "request_failure" | "timing" | "outcome" | "csp_violation" | "message" | "exception" | "unhandled_rejection" | "session_start" | "track_event";
55
52
  message: string;
56
- type: "message" | "exception" | "request_failure" | "timing" | "outcome" | "csp_violation" | "unhandled_rejection" | "session_start" | "track_event";
57
53
  timestamp: string;
54
+ breadcrumbs?: unknown[] | undefined;
55
+ route?: string | undefined;
58
56
  severity?: "info" | "warning" | "error" | "fatal" | undefined;
59
57
  errorType?: string | undefined;
60
58
  stack?: string | undefined;
61
- route?: string | undefined;
62
59
  traceId?: string | undefined;
63
- breadcrumbs?: unknown[] | undefined;
64
60
  debugIds?: Record<string, unknown> | undefined;
65
61
  durationMs?: number | undefined;
66
62
  filename?: string | undefined;
63
+ name?: string | undefined;
67
64
  column?: number | undefined;
68
65
  line?: number | undefined;
69
- name?: string | undefined;
70
66
  id?: string | undefined;
71
67
  context?: Record<string, unknown> | undefined;
72
68
  fingerprint?: string | string[] | undefined;
73
69
  }, {
74
- type: "message" | "exception" | "request_failure" | "timing" | "outcome" | "csp_violation" | "unhandled_rejection" | "session_start" | "track_event";
70
+ type: "request_failure" | "timing" | "outcome" | "csp_violation" | "message" | "exception" | "unhandled_rejection" | "session_start" | "track_event";
75
71
  timestamp: string;
72
+ breadcrumbs?: unknown;
73
+ route?: string | undefined;
76
74
  message?: string | undefined;
77
75
  severity?: "info" | "warning" | "error" | "fatal" | undefined;
78
76
  errorType?: string | undefined;
79
77
  stack?: string | undefined;
80
- route?: string | undefined;
81
78
  traceId?: unknown;
82
- breadcrumbs?: unknown;
83
79
  debugIds?: unknown;
84
80
  durationMs?: number | undefined;
85
81
  filename?: string | undefined;
82
+ name?: string | undefined;
86
83
  column?: number | undefined;
87
84
  line?: number | undefined;
88
- name?: string | undefined;
89
85
  id?: string | undefined;
90
86
  context?: Record<string, unknown> | undefined;
91
87
  fingerprint?: unknown;
92
88
  }>, "many">;
89
+ version: z.ZodLiteral<1>;
90
+ sessionId: z.ZodOptional<z.ZodString>;
91
+ service: z.ZodString;
92
+ environment: z.ZodString;
93
+ release: z.ZodOptional<z.ZodString>;
94
+ /** @autter/runtime-browser version (1.4+); recorded for compatibility checks. */
95
+ sdk: z.ZodCatch<z.ZodOptional<z.ZodString>>;
93
96
  }, "strip", z.ZodTypeAny, {
97
+ version: 1;
94
98
  service: string;
95
99
  environment: string;
96
- version: 1;
97
100
  events: {
101
+ type: "request_failure" | "timing" | "outcome" | "csp_violation" | "message" | "exception" | "unhandled_rejection" | "session_start" | "track_event";
98
102
  message: string;
99
- type: "message" | "exception" | "request_failure" | "timing" | "outcome" | "csp_violation" | "unhandled_rejection" | "session_start" | "track_event";
100
103
  timestamp: string;
104
+ breadcrumbs?: unknown[] | undefined;
105
+ route?: string | undefined;
101
106
  severity?: "info" | "warning" | "error" | "fatal" | undefined;
102
107
  errorType?: string | undefined;
103
108
  stack?: string | undefined;
104
- route?: string | undefined;
105
109
  traceId?: string | undefined;
106
- breadcrumbs?: unknown[] | undefined;
107
110
  debugIds?: Record<string, unknown> | undefined;
108
111
  durationMs?: number | undefined;
109
112
  filename?: string | undefined;
113
+ name?: string | undefined;
110
114
  column?: number | undefined;
111
115
  line?: number | undefined;
112
- name?: string | undefined;
113
116
  id?: string | undefined;
114
117
  context?: Record<string, unknown> | undefined;
115
118
  fingerprint?: string | string[] | undefined;
@@ -118,25 +121,25 @@ export declare const browserPayloadSchema: z.ZodObject<{
118
121
  sessionId?: string | undefined;
119
122
  sdk?: string | undefined;
120
123
  }, {
124
+ version: 1;
121
125
  service: string;
122
126
  environment: string;
123
- version: 1;
124
127
  events: {
125
- type: "message" | "exception" | "request_failure" | "timing" | "outcome" | "csp_violation" | "unhandled_rejection" | "session_start" | "track_event";
128
+ type: "request_failure" | "timing" | "outcome" | "csp_violation" | "message" | "exception" | "unhandled_rejection" | "session_start" | "track_event";
126
129
  timestamp: string;
130
+ breadcrumbs?: unknown;
131
+ route?: string | undefined;
127
132
  message?: string | undefined;
128
133
  severity?: "info" | "warning" | "error" | "fatal" | undefined;
129
134
  errorType?: string | undefined;
130
135
  stack?: string | undefined;
131
- route?: string | undefined;
132
136
  traceId?: unknown;
133
- breadcrumbs?: unknown;
134
137
  debugIds?: unknown;
135
138
  durationMs?: number | undefined;
136
139
  filename?: string | undefined;
140
+ name?: string | undefined;
137
141
  column?: number | undefined;
138
142
  line?: number | undefined;
139
- name?: string | undefined;
140
143
  id?: string | undefined;
141
144
  context?: Record<string, unknown> | undefined;
142
145
  fingerprint?: unknown;
@@ -146,6 +149,31 @@ export declare const browserPayloadSchema: z.ZodObject<{
146
149
  sdk?: unknown;
147
150
  }>;
148
151
  export type BrowserPayload = z.infer<typeof browserPayloadSchema>;
152
+ export interface ParsedBrowserPayload {
153
+ /** The envelope with its valid events (at most MAX_BROWSER_EVENTS). */
154
+ payload: BrowserPayload;
155
+ /** Events dropped because they failed validation (invalid_event). */
156
+ invalidEvents: number;
157
+ /** Events past MAX_BROWSER_EVENTS, not looked at (events_over_limit). */
158
+ overLimitEvents: number;
159
+ /** The first validation issues of the dropped events; `path` starts with ["events", index]. */
160
+ issues: z.ZodIssue[];
161
+ }
162
+ /**
163
+ * Validate a browser payload event by event (1.8.0). The envelope (version,
164
+ * service, environment) must be valid or the payload is refused; an event
165
+ * that fails validation — a type this ingester doesn't know, a bad
166
+ * timestamp, an oversized field — is dropped on its own and counted, so it
167
+ * no longer costs the errors sent in the same beacon. Events past
168
+ * MAX_BROWSER_EVENTS are dropped and counted unread.
169
+ */
170
+ export declare function parseBrowserPayload(body: unknown): {
171
+ success: true;
172
+ data: ParsedBrowserPayload;
173
+ } | {
174
+ success: false;
175
+ issues: z.ZodIssue[];
176
+ };
149
177
  export interface NormalizedBrowser {
150
178
  occurrences: RuntimeOccurrenceInput[];
151
179
  metricPoints: RuntimeMetricPoint[];
@@ -67,7 +67,9 @@ const browserEventSchema = z.object({
67
67
  */
68
68
  traceId: z.string().regex(/^(?!0{32})[0-9a-f]{32}$/).optional().catch(undefined),
69
69
  });
70
- export const browserPayloadSchema = z.object({
70
+ /** Events past this in one payload are dropped and counted (events_over_limit). */
71
+ export const MAX_BROWSER_EVENTS = 50;
72
+ const browserEnvelope = {
71
73
  version: z.literal(1),
72
74
  sessionId: z.string().max(100).optional(),
73
75
  service: z.string().min(1).max(200),
@@ -75,8 +77,53 @@ export const browserPayloadSchema = z.object({
75
77
  release: z.string().max(200).optional(),
76
78
  /** @autter/runtime-browser version (1.4+); recorded for compatibility checks. */
77
79
  sdk: z.string().regex(/^\d{1,6}\.\d{1,6}\.\d{1,6}[0-9A-Za-z.+-]{0,20}$/).optional().catch(undefined),
78
- events: z.array(browserEventSchema).max(50),
80
+ };
81
+ /** A whole payload, every event valid: what normalizeBrowserPayload takes. */
82
+ export const browserPayloadSchema = z.object({
83
+ ...browserEnvelope,
84
+ events: z.array(browserEventSchema).max(MAX_BROWSER_EVENTS),
79
85
  });
86
+ const browserEnvelopeSchema = z.object({ ...browserEnvelope, events: z.array(z.unknown()) });
87
+ /**
88
+ * Validate a browser payload event by event (1.8.0). The envelope (version,
89
+ * service, environment) must be valid or the payload is refused; an event
90
+ * that fails validation — a type this ingester doesn't know, a bad
91
+ * timestamp, an oversized field — is dropped on its own and counted, so it
92
+ * no longer costs the errors sent in the same beacon. Events past
93
+ * MAX_BROWSER_EVENTS are dropped and counted unread.
94
+ */
95
+ export function parseBrowserPayload(body) {
96
+ const envelope = browserEnvelopeSchema.safeParse(body);
97
+ if (!envelope.success)
98
+ return { success: false, issues: envelope.error.issues.slice(0, 5) };
99
+ const raw = envelope.data.events;
100
+ const considered = raw.slice(0, MAX_BROWSER_EVENTS);
101
+ const events = [];
102
+ const issues = [];
103
+ let invalidEvents = 0;
104
+ considered.forEach((candidate, index) => {
105
+ const parsed = browserEventSchema.safeParse(candidate);
106
+ if (parsed.success) {
107
+ events.push(parsed.data);
108
+ return;
109
+ }
110
+ invalidEvents += 1;
111
+ for (const issue of parsed.error.issues) {
112
+ if (issues.length >= 5)
113
+ break;
114
+ issues.push({ ...issue, path: ["events", index, ...issue.path] });
115
+ }
116
+ });
117
+ return {
118
+ success: true,
119
+ data: {
120
+ payload: { ...envelope.data, events },
121
+ invalidEvents,
122
+ overLimitEvents: raw.length - considered.length,
123
+ issues,
124
+ },
125
+ };
126
+ }
80
127
  const TYPE_TO_ERROR_TYPE = {
81
128
  exception: "Error",
82
129
  unhandled_rejection: "UnhandledRejection",
@@ -154,14 +201,13 @@ export function normalizeBrowserPayload(payload) {
154
201
  continue;
155
202
  }
156
203
  const severity = asSeverity(event.severity, TYPE_TO_SEVERITY[event.type] ?? "error");
157
- // The browser has no request stream, so error/message events are the
158
- // only usage signal for many pages — count each as an event (and, for
159
- // fatal/error severity, an error event). Without this, a page that
160
- // only reports errors groups issues whose dashboards all read 0.
161
- bumpRollup(event.route ? (event.route.split("?")[0] ?? "") : "", occurredAt, {
162
- requestCount: 1,
163
- errorCount: severity === "fatal" || severity === "error" ? 1 : 0,
164
- });
204
+ // A browser error belongs to no server request: it counts in
205
+ // `error_count` only (fatal/error severity), never `request_count`,
206
+ // so a page that only reports errors still shows them without
207
+ // inventing requests. Warnings and info messages count nothing.
208
+ if (severity === "fatal" || severity === "error") {
209
+ bumpRollup(event.route ? (event.route.split("?")[0] ?? "") : "", occurredAt, { errorCount: 1 });
210
+ }
165
211
  occurrences.push({
166
212
  source: "browser",
167
213
  severity,
@@ -78,7 +78,13 @@ export interface OtlpMetricsRequest {
78
78
  }>;
79
79
  }
80
80
  export interface NormalizedTraces {
81
+ /** One per thrown error: duplicate recordings across a trace's spans are
82
+ * merged and redundant status-only occurrences dropped (trace-dedupe.ts). */
81
83
  occurrences: RuntimeOccurrenceInput[];
84
+ /** Each occurrence's position among every signal the batch recorded,
85
+ * before deduplication: the batch index its deterministic id is derived
86
+ * from, so a merge or a drop never shifts another occurrence's id. */
87
+ occurrenceBatchIndexes: number[];
82
88
  spans: RuntimeSpanRow[];
83
89
  metricPoints: RuntimeMetricPoint[];
84
90
  llmCalls: RuntimeLlmCall[];