@aaac/observability 0.1.9 → 0.1.11
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/{chunk-GRL7RHMS.js → chunk-VRQYMG5M.js} +85 -8
- package/dist/chunk-VRQYMG5M.js.map +1 -0
- package/dist/cli/index.js +182 -10
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +70 -2
- package/dist/index.js +5 -1
- package/package.json +1 -1
- package/dist/chunk-GRL7RHMS.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
DEFAULT_DB_PATH,
|
|
4
|
+
OtelEmitter,
|
|
4
5
|
SqliteQueryAdapter,
|
|
5
6
|
createPipeline,
|
|
6
7
|
emitHumanInstruction,
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
generateId,
|
|
11
12
|
isoToUnixNano,
|
|
12
13
|
loadEventMappingConfig
|
|
13
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-VRQYMG5M.js";
|
|
14
15
|
|
|
15
16
|
// src/cli/index.ts
|
|
16
17
|
import { readFileSync } from "fs";
|
|
@@ -121,6 +122,57 @@ command_sets:
|
|
|
121
122
|
fallback:
|
|
122
123
|
type: boolean
|
|
123
124
|
|
|
125
|
+
backfill:
|
|
126
|
+
summary: Re-emit historical spans from SQLite to an OTLP backend
|
|
127
|
+
description: |
|
|
128
|
+
Reads closed/instant spans from the local SQLite database and emits them
|
|
129
|
+
via OtelEmitter to an OTLP-compatible backend.
|
|
130
|
+
|
|
131
|
+
IMPORTANT \u2014 idempotency: The OTel SDK does not support specifying custom
|
|
132
|
+
spanIds, so each backfill invocation creates NEW spans in the backend.
|
|
133
|
+
Repeated invocations will produce DUPLICATE data. Use --from/--to to
|
|
134
|
+
control scope. See #125 \xA77 for full idempotency guidance.
|
|
135
|
+
options:
|
|
136
|
+
- name: db
|
|
137
|
+
schema: { type: string }
|
|
138
|
+
description: Path to observability SQLite database (overrides default)
|
|
139
|
+
- name: endpoint
|
|
140
|
+
schema: { type: string }
|
|
141
|
+
description: "OTLP HTTP endpoint URL (fallback: OTEL_EXPORTER_OTLP_ENDPOINT)"
|
|
142
|
+
- name: service-name
|
|
143
|
+
schema: { type: string }
|
|
144
|
+
description: "OTEL service name reported to backend (default: @aaac/observability)"
|
|
145
|
+
- name: from
|
|
146
|
+
schema: { type: string }
|
|
147
|
+
description: Backfill spans starting at or after this ISO 8601 time
|
|
148
|
+
- name: to
|
|
149
|
+
schema: { type: string }
|
|
150
|
+
description: Backfill spans starting at or before this ISO 8601 time
|
|
151
|
+
- name: event-type
|
|
152
|
+
schema: { type: string }
|
|
153
|
+
description: "Filter by event type (e.g. agent.session)"
|
|
154
|
+
- name: dry-run
|
|
155
|
+
schema: { type: boolean }
|
|
156
|
+
description: Print span count without emitting to OTLP backend
|
|
157
|
+
exits:
|
|
158
|
+
'0':
|
|
159
|
+
description: Backfill completed
|
|
160
|
+
stdout:
|
|
161
|
+
format: json
|
|
162
|
+
schema:
|
|
163
|
+
type: object
|
|
164
|
+
properties:
|
|
165
|
+
spansFound:
|
|
166
|
+
type: integer
|
|
167
|
+
spansEmitted:
|
|
168
|
+
type: integer
|
|
169
|
+
dryRun:
|
|
170
|
+
type: boolean
|
|
171
|
+
'1':
|
|
172
|
+
description: Backfill failed (missing endpoint, DB error, etc.)
|
|
173
|
+
stderr:
|
|
174
|
+
format: text
|
|
175
|
+
|
|
124
176
|
query:
|
|
125
177
|
summary: Query the observability store via QueryAdapter
|
|
126
178
|
options:
|
|
@@ -170,7 +222,7 @@ command_sets:
|
|
|
170
222
|
stderr:
|
|
171
223
|
format: text
|
|
172
224
|
`;
|
|
173
|
-
var CONTRACT_JSON_STR = '{\n "cli_contracts": "0.1.0",\n "info": {\n "title": "AaaC Observability CLI",\n "version": "0.1.0",\n "description": "aaac-observ \u2014 external event registration and query for @aaac/observability"\n },\n "command_sets": {\n "aaac-observ": {\n "summary": "aaac-observ \u2014 register external events and query the observability store",\n "executable": "aaac-observ",\n "commands": {\n "record": {\n "summary": "Register an external event into the observability pipeline (thin entrypoint for EventCollector.registerExternalEvent)",\n "options": [\n {\n "name": "event-type",\n "aliases": [\n "t"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Event type (e.g. promotion.commit, process.edit, agent.session)"\n },\n {\n "name": "lifecycle",\n "aliases": [\n "l"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "open",\n "close",\n "event",\n "instant"\n ]\n },\n "description": "Event lifecycle phase: open | close | event | instant"\n },\n {\n "name": "span-id",\n "aliases": [\n "s"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Span ID (auto-generated if omitted)"\n },\n {\n "name": "parent-span-id",\n "schema": {\n "type": "string"\n },\n "description": "Parent span ID for hierarchy"\n },\n {\n "name": "session-id",\n "schema": {\n "type": "string"\n },\n "description": "Session ID to associate with the event"\n },\n {\n "name": "trace-id",\n "schema": {\n "type": "string"\n },\n "description": "Trace ID for distributed tracing"\n },\n {\n "name": "source",\n "schema": {\n "type": "string"\n },\n "description": "Event source identifier (default: external)"\n },\n {\n "name": "attr",\n "schema": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "description": "Attribute as key=value (repeatable, e.g. --attr git.commit=abc123)"\n },\n {\n "name": "link",\n "schema": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "description": "Cross-axis link as targetSpanId:linkType[:targetTraceId] (repeatable)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Event registered successfully",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object",\n "properties": {\n "eventId": {\n "type": "string"\n },\n "spanId": {\n "type": "string"\n }\n }\n }\n }\n },\n "1": {\n "description": "Registration failed (validation error or write error)",\n "stderr": {\n "format": "text"\n }\n }\n }\n },\n "record-hook": {\n "summary": "Record a Cursor/git hook event \u2014 parses stdin JSON, resolves event_mapping into 3-axis spans/links, injects session_id, and emits human-events (fail-open)",\n "arguments": [\n {\n "name": "hook-name",\n "index": 0,\n "required": true,\n "schema": {\n "type": "string"\n },\n "description": "Cursor/git hook name (e.g. afterFileEdit, subagentStart, beforeShellExecution)"\n }\n ],\n "options": [\n {\n "name": "mapping-config",\n "schema": {\n "type": "string"\n },\n "description": "Path to event-mapping.json (default: .agent-logs/config/event-mapping.json)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Hook recorded (or no-op when stdin/mapping unavailable) \u2014 always fail-open",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object",\n "properties": {\n "recorded": {\n "type": "integer"\n },\n "fallback": {\n "type": "boolean"\n }\n }\n }\n }\n }\n }\n },\n "query": {\n "summary": "Query the observability store via QueryAdapter",\n "options": [\n {\n "name": "kind",\n "aliases": [\n "k"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "trace",\n "span",\n "search",\n "links"\n ]\n },\n "description": "Query kind: trace | span | search | links"\n },\n {\n "name": "trace-id",\n "schema": {\n "type": "string"\n },\n "description": "Trace ID for trace queries (--kind trace)"\n },\n {\n "name": "span-id",\n "aliases": [\n "s"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Span ID for span/links queries (--kind span or --kind links)"\n },\n {\n "name": "event-type",\n "schema": {\n "type": "string"\n },\n "description": "Event type filter for search queries (--kind search)"\n },\n {\n "name": "task-id",\n "schema": {\n "type": "string"\n },\n "description": "Task ID filter for search queries (--kind search)"\n },\n {\n "name": "from",\n "schema": {\n "type": "string"\n },\n "description": "Start time in ISO 8601 format for search queries (--kind search)"\n },\n {\n "name": "to",\n "schema": {\n "type": "string"\n },\n "description": "End time in ISO 8601 format for search queries (--kind search)"\n },\n {\n "name": "direction",\n "aliases": [\n "d"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "forward",\n "reverse",\n "both"\n ]\n },\n "description": "Link traversal direction for links queries (default: both)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Query succeeded \u2014 results as JSON array",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object"\n }\n }\n },\n "1": {\n "description": "Query failed",\n "stderr": {\n "format": "text"\n }\n }\n }\n }\n }\n }\n }\n}';
|
|
225
|
+
var CONTRACT_JSON_STR = '{\n "cli_contracts": "0.1.0",\n "info": {\n "title": "AaaC Observability CLI",\n "version": "0.1.0",\n "description": "aaac-observ \u2014 external event registration and query for @aaac/observability"\n },\n "command_sets": {\n "aaac-observ": {\n "summary": "aaac-observ \u2014 register external events and query the observability store",\n "executable": "aaac-observ",\n "commands": {\n "record": {\n "summary": "Register an external event into the observability pipeline (thin entrypoint for EventCollector.registerExternalEvent)",\n "options": [\n {\n "name": "event-type",\n "aliases": [\n "t"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Event type (e.g. promotion.commit, process.edit, agent.session)"\n },\n {\n "name": "lifecycle",\n "aliases": [\n "l"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "open",\n "close",\n "event",\n "instant"\n ]\n },\n "description": "Event lifecycle phase: open | close | event | instant"\n },\n {\n "name": "span-id",\n "aliases": [\n "s"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Span ID (auto-generated if omitted)"\n },\n {\n "name": "parent-span-id",\n "schema": {\n "type": "string"\n },\n "description": "Parent span ID for hierarchy"\n },\n {\n "name": "session-id",\n "schema": {\n "type": "string"\n },\n "description": "Session ID to associate with the event"\n },\n {\n "name": "trace-id",\n "schema": {\n "type": "string"\n },\n "description": "Trace ID for distributed tracing"\n },\n {\n "name": "source",\n "schema": {\n "type": "string"\n },\n "description": "Event source identifier (default: external)"\n },\n {\n "name": "attr",\n "schema": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "description": "Attribute as key=value (repeatable, e.g. --attr git.commit=abc123)"\n },\n {\n "name": "link",\n "schema": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "description": "Cross-axis link as targetSpanId:linkType[:targetTraceId] (repeatable)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Event registered successfully",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object",\n "properties": {\n "eventId": {\n "type": "string"\n },\n "spanId": {\n "type": "string"\n }\n }\n }\n }\n },\n "1": {\n "description": "Registration failed (validation error or write error)",\n "stderr": {\n "format": "text"\n }\n }\n }\n },\n "record-hook": {\n "summary": "Record a Cursor/git hook event \u2014 parses stdin JSON, resolves event_mapping into 3-axis spans/links, injects session_id, and emits human-events (fail-open)",\n "arguments": [\n {\n "name": "hook-name",\n "index": 0,\n "required": true,\n "schema": {\n "type": "string"\n },\n "description": "Cursor/git hook name (e.g. afterFileEdit, subagentStart, beforeShellExecution)"\n }\n ],\n "options": [\n {\n "name": "mapping-config",\n "schema": {\n "type": "string"\n },\n "description": "Path to event-mapping.json (default: .agent-logs/config/event-mapping.json)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Hook recorded (or no-op when stdin/mapping unavailable) \u2014 always fail-open",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object",\n "properties": {\n "recorded": {\n "type": "integer"\n },\n "fallback": {\n "type": "boolean"\n }\n }\n }\n }\n }\n }\n },\n "backfill": {\n "summary": "Re-emit historical spans from SQLite to an OTLP backend",\n "description": "Reads closed/instant spans from the local SQLite database and emits them\\nvia OtelEmitter to an OTLP-compatible backend.\\n\\nIMPORTANT \u2014 idempotency: The OTel SDK does not support specifying custom\\nspanIds, so each backfill invocation creates NEW spans in the backend.\\nRepeated invocations will produce DUPLICATE data. Use --from/--to to\\ncontrol scope. See #125 \xA77 for full idempotency guidance.\\n",\n "options": [\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n },\n {\n "name": "endpoint",\n "schema": {\n "type": "string"\n },\n "description": "OTLP HTTP endpoint URL (fallback: OTEL_EXPORTER_OTLP_ENDPOINT)"\n },\n {\n "name": "service-name",\n "schema": {\n "type": "string"\n },\n "description": "OTEL service name reported to backend (default: @aaac/observability)"\n },\n {\n "name": "from",\n "schema": {\n "type": "string"\n },\n "description": "Backfill spans starting at or after this ISO 8601 time"\n },\n {\n "name": "to",\n "schema": {\n "type": "string"\n },\n "description": "Backfill spans starting at or before this ISO 8601 time"\n },\n {\n "name": "event-type",\n "schema": {\n "type": "string"\n },\n "description": "Filter by event type (e.g. agent.session)"\n },\n {\n "name": "dry-run",\n "schema": {\n "type": "boolean"\n },\n "description": "Print span count without emitting to OTLP backend"\n }\n ],\n "exits": {\n "0": {\n "description": "Backfill completed",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object",\n "properties": {\n "spansFound": {\n "type": "integer"\n },\n "spansEmitted": {\n "type": "integer"\n },\n "dryRun": {\n "type": "boolean"\n }\n }\n }\n }\n },\n "1": {\n "description": "Backfill failed (missing endpoint, DB error, etc.)",\n "stderr": {\n "format": "text"\n }\n }\n }\n },\n "query": {\n "summary": "Query the observability store via QueryAdapter",\n "options": [\n {\n "name": "kind",\n "aliases": [\n "k"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "trace",\n "span",\n "search",\n "links"\n ]\n },\n "description": "Query kind: trace | span | search | links"\n },\n {\n "name": "trace-id",\n "schema": {\n "type": "string"\n },\n "description": "Trace ID for trace queries (--kind trace)"\n },\n {\n "name": "span-id",\n "aliases": [\n "s"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Span ID for span/links queries (--kind span or --kind links)"\n },\n {\n "name": "event-type",\n "schema": {\n "type": "string"\n },\n "description": "Event type filter for search queries (--kind search)"\n },\n {\n "name": "task-id",\n "schema": {\n "type": "string"\n },\n "description": "Task ID filter for search queries (--kind search)"\n },\n {\n "name": "from",\n "schema": {\n "type": "string"\n },\n "description": "Start time in ISO 8601 format for search queries (--kind search)"\n },\n {\n "name": "to",\n "schema": {\n "type": "string"\n },\n "description": "End time in ISO 8601 format for search queries (--kind search)"\n },\n {\n "name": "direction",\n "aliases": [\n "d"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "forward",\n "reverse",\n "both"\n ]\n },\n "description": "Link traversal direction for links queries (default: both)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Query succeeded \u2014 results as JSON array",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object"\n }\n }\n },\n "1": {\n "description": "Query failed",\n "stderr": {\n "format": "text"\n }\n }\n }\n }\n }\n }\n }\n}';
|
|
174
226
|
|
|
175
227
|
// src/generated/program.ts
|
|
176
228
|
function createProgram(handlers2, version) {
|
|
@@ -182,6 +234,9 @@ function createProgram(handlers2, version) {
|
|
|
182
234
|
program2.command("record-hook").description("Record a Cursor/git hook event \u2014 parses stdin JSON, resolves event_mapping into 3-axis spans/links, injects session_id, and emits human-events (fail-open)").argument("<hook-name>", "Cursor/git hook name (e.g. afterFileEdit, subagentStart, beforeShellExecution)").option("--mapping-config <value>", "Path to event-mapping.json (default: .agent-logs/config/event-mapping.json)").option("--db <value>", "Path to observability SQLite database (overrides default)").action(async (hookName, opts, cmd) => {
|
|
183
235
|
await handlers2.recordHook(hookName, opts, cmd.optsWithGlobals());
|
|
184
236
|
});
|
|
237
|
+
program2.command("backfill").description("Re-emit historical spans from SQLite to an OTLP backend").option("--db <value>", "Path to observability SQLite database (overrides default)").option("--endpoint <value>", "OTLP HTTP endpoint URL (fallback: OTEL_EXPORTER_OTLP_ENDPOINT)").option("--service-name <value>", "OTEL service name reported to backend (default: @aaac/observability)").option("--from <value>", "Backfill spans starting at or after this ISO 8601 time").option("--to <value>", "Backfill spans starting at or before this ISO 8601 time").option("--event-type <value>", "Filter by event type (e.g. agent.session)").option("--dry-run", "Print span count without emitting to OTLP backend").action(async (opts, cmd) => {
|
|
238
|
+
await handlers2.backfill(opts, cmd.optsWithGlobals());
|
|
239
|
+
});
|
|
185
240
|
program2.command("query").description("Query the observability store via QueryAdapter").option("-k, --kind <value>", "Query kind: trace | span | search | links").option("--trace-id <value>", "Trace ID for trace queries (--kind trace)").option("-s, --span-id <value>", "Span ID for span/links queries (--kind span or --kind links)").option("--event-type <value>", "Event type filter for search queries (--kind search)").option("--task-id <value>", "Task ID filter for search queries (--kind search)").option("--from <value>", "Start time in ISO 8601 format for search queries (--kind search)").option("--to <value>", "End time in ISO 8601 format for search queries (--kind search)").option("-d, --direction <value>", "Link traversal direction for links queries (default: both)").option("--db <value>", "Path to observability SQLite database (overrides default)").action(async (opts, cmd) => {
|
|
186
241
|
await handlers2.query(opts, cmd.optsWithGlobals());
|
|
187
242
|
});
|
|
@@ -202,7 +257,7 @@ function createProgram(handlers2, version) {
|
|
|
202
257
|
type: "cli-contracts/extract",
|
|
203
258
|
extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
204
259
|
specVersion: doc.cli_contracts ?? "0.1.0",
|
|
205
|
-
commands: ["aaac-observ.record", "aaac-observ.record-hook", "aaac-observ.query"]
|
|
260
|
+
commands: ["aaac-observ.record", "aaac-observ.record-hook", "aaac-observ.backfill", "aaac-observ.query"]
|
|
206
261
|
};
|
|
207
262
|
}
|
|
208
263
|
Object.assign(out, doc);
|
|
@@ -219,7 +274,7 @@ function createProgram(handlers2, version) {
|
|
|
219
274
|
yamlLines.push("extractedAt: " + (/* @__PURE__ */ new Date()).toISOString());
|
|
220
275
|
yamlLines.push("spec_version: " + (doc.cli_contracts ?? "0.1.0"));
|
|
221
276
|
yamlLines.push("commands:");
|
|
222
|
-
for (const id of ["aaac-observ.record", "aaac-observ.record-hook", "aaac-observ.query"]) {
|
|
277
|
+
for (const id of ["aaac-observ.record", "aaac-observ.record-hook", "aaac-observ.backfill", "aaac-observ.query"]) {
|
|
223
278
|
yamlLines.push(" - " + id);
|
|
224
279
|
}
|
|
225
280
|
}
|
|
@@ -332,10 +387,14 @@ async function handleRecord(options, _parentOpts) {
|
|
|
332
387
|
afterCorrelate: (event) => {
|
|
333
388
|
capturedEventId = event.id;
|
|
334
389
|
return event;
|
|
335
|
-
}
|
|
336
|
-
//
|
|
337
|
-
//
|
|
338
|
-
|
|
390
|
+
}
|
|
391
|
+
// Enable the default enrichment rules (R1–R5). This is the path the git
|
|
392
|
+
// post-commit hook uses to record promotion.commit (close) with
|
|
393
|
+
// committed_files; R5 (cross-axis) must run here to add the
|
|
394
|
+
// materializes_as_commit link to task spans whose modified files intersect
|
|
395
|
+
// the commit. R3/R4 accumulation persists in the SQLite-backed cache of the
|
|
396
|
+
// same db file, so R5 sees it across the process boundary (#143 / #140).
|
|
397
|
+
// Enricher is fail-open, so a rule error never blocks recording.
|
|
339
398
|
});
|
|
340
399
|
try {
|
|
341
400
|
collector.registerExternalEvent({
|
|
@@ -520,7 +579,7 @@ async function runRecordHook(args) {
|
|
|
520
579
|
const rule = config?.mappings?.[hook];
|
|
521
580
|
let recorded = 0;
|
|
522
581
|
let fallback = false;
|
|
523
|
-
const { collector, sink } = createPipeline({ dbPath
|
|
582
|
+
const { collector, sink } = createPipeline({ dbPath });
|
|
524
583
|
try {
|
|
525
584
|
if (rule) {
|
|
526
585
|
recorded += emitMappedSpans(collector, hook, rule, hookInput, sessionId);
|
|
@@ -636,11 +695,124 @@ async function handleQuery(options, _parentOpts) {
|
|
|
636
695
|
adapter.close();
|
|
637
696
|
}
|
|
638
697
|
|
|
698
|
+
// src/cli/backfill.ts
|
|
699
|
+
function spanRecordToMappedSpan(span, links) {
|
|
700
|
+
const start = span.startTime ?? 0n;
|
|
701
|
+
const end = span.endTime ?? start;
|
|
702
|
+
const mappedLinks = links.map((l) => ({
|
|
703
|
+
targetSpanId: l.targetSpanId,
|
|
704
|
+
targetTraceId: l.targetTraceId ?? void 0,
|
|
705
|
+
attributes: {
|
|
706
|
+
link_type: l.linkType,
|
|
707
|
+
...l.attributes ?? {}
|
|
708
|
+
}
|
|
709
|
+
}));
|
|
710
|
+
return {
|
|
711
|
+
spanId: span.spanId,
|
|
712
|
+
traceId: span.traceId ?? "",
|
|
713
|
+
parentSpanId: span.parentSpanId ?? void 0,
|
|
714
|
+
name: span.eventType,
|
|
715
|
+
startTimeUnixNano: start,
|
|
716
|
+
endTimeUnixNano: end,
|
|
717
|
+
attributes: span.attributes,
|
|
718
|
+
spanEvents: [],
|
|
719
|
+
// span events are not stored in the spans table
|
|
720
|
+
links: mappedLinks
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
async function handleBackfill(options, _parentOpts) {
|
|
724
|
+
const dbPath = options.db ?? DEFAULT_DB_PATH;
|
|
725
|
+
const dryRun = options.dryRun ?? false;
|
|
726
|
+
const endpoint = options.endpoint ?? process.env["OTEL_EXPORTER_OTLP_ENDPOINT"];
|
|
727
|
+
if (!dryRun && !endpoint) {
|
|
728
|
+
process.stderr.write(
|
|
729
|
+
"Error: --endpoint is required (or set OTEL_EXPORTER_OTLP_ENDPOINT) unless --dry-run\n"
|
|
730
|
+
);
|
|
731
|
+
process.exit(1);
|
|
732
|
+
}
|
|
733
|
+
let fromNano;
|
|
734
|
+
let toNano;
|
|
735
|
+
if (options.from) {
|
|
736
|
+
if (Number.isNaN(Date.parse(options.from))) {
|
|
737
|
+
process.stderr.write(
|
|
738
|
+
`Error: invalid --from value "${options.from}" \u2014 expected ISO 8601
|
|
739
|
+
`
|
|
740
|
+
);
|
|
741
|
+
process.exit(1);
|
|
742
|
+
}
|
|
743
|
+
fromNano = isoToUnixNano(options.from);
|
|
744
|
+
}
|
|
745
|
+
if (options.to) {
|
|
746
|
+
if (Number.isNaN(Date.parse(options.to))) {
|
|
747
|
+
process.stderr.write(
|
|
748
|
+
`Error: invalid --to value "${options.to}" \u2014 expected ISO 8601
|
|
749
|
+
`
|
|
750
|
+
);
|
|
751
|
+
process.exit(1);
|
|
752
|
+
}
|
|
753
|
+
toNano = isoToUnixNano(options.to);
|
|
754
|
+
}
|
|
755
|
+
const adapter = new SqliteQueryAdapter(dbPath);
|
|
756
|
+
let spans;
|
|
757
|
+
try {
|
|
758
|
+
spans = adapter.querySpans({
|
|
759
|
+
...options.eventType ? { eventType: options.eventType } : {},
|
|
760
|
+
...fromNano !== void 0 ? { fromTimeUnixNano: fromNano } : {},
|
|
761
|
+
...toNano !== void 0 ? { toTimeUnixNano: toNano } : {}
|
|
762
|
+
});
|
|
763
|
+
spans = spans.filter((s) => s.status === "closed" || s.status === "instant");
|
|
764
|
+
} catch (err) {
|
|
765
|
+
process.stderr.write(
|
|
766
|
+
`Error: failed to query SQLite \u2014 ${err instanceof Error ? err.message : String(err)}
|
|
767
|
+
`
|
|
768
|
+
);
|
|
769
|
+
adapter.close();
|
|
770
|
+
process.exit(1);
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
const spansFound = spans.length;
|
|
774
|
+
if (dryRun) {
|
|
775
|
+
adapter.close();
|
|
776
|
+
const result2 = { spansFound, spansEmitted: 0, dryRun: true };
|
|
777
|
+
process.stdout.write(JSON.stringify(result2) + "\n");
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
const mappedSpans = [];
|
|
781
|
+
for (const span of spans) {
|
|
782
|
+
let links = [];
|
|
783
|
+
try {
|
|
784
|
+
links = adapter.getLinks(span.spanId, "forward");
|
|
785
|
+
} catch {
|
|
786
|
+
}
|
|
787
|
+
mappedSpans.push(spanRecordToMappedSpan(span, links));
|
|
788
|
+
}
|
|
789
|
+
adapter.close();
|
|
790
|
+
const emitter = new OtelEmitter({
|
|
791
|
+
endpoint,
|
|
792
|
+
serviceName: options.serviceName
|
|
793
|
+
});
|
|
794
|
+
let spansEmitted = 0;
|
|
795
|
+
for (const mapped of mappedSpans) {
|
|
796
|
+
try {
|
|
797
|
+
emitter.emit(mapped);
|
|
798
|
+
spansEmitted++;
|
|
799
|
+
} catch {
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
await Promise.race([
|
|
803
|
+
emitter.shutdown(),
|
|
804
|
+
new Promise((resolve2) => setTimeout(resolve2, 2e3))
|
|
805
|
+
]);
|
|
806
|
+
const result = { spansFound, spansEmitted, dryRun: false };
|
|
807
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
808
|
+
}
|
|
809
|
+
|
|
639
810
|
// src/cli/handlers.ts
|
|
640
811
|
var handlers = {
|
|
641
812
|
record: handleRecord,
|
|
642
813
|
recordHook: handleRecordHook,
|
|
643
|
-
query: handleQuery
|
|
814
|
+
query: handleQuery,
|
|
815
|
+
backfill: handleBackfill
|
|
644
816
|
};
|
|
645
817
|
|
|
646
818
|
// src/cli/index.ts
|