@diveinto/obs 1.0.5 → 1.1.1

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/otel.js CHANGED
@@ -1,26 +1,26 @@
1
1
  /**
2
- * Optional OpenTelemetry export. Off unless OTEL_EXPORTER_OTLP_ENDPOINT is set.
2
+ * OpenTelemetry: the trace engine, always; OTLP export, when an endpoint is configured.
3
3
  *
4
- * The first-party stdout/file logs are always the source of truth and work with
5
- * no collector. This module is the bolt-on that ships spans to an OTLP backend
6
- * (any OTLP-compatible collector). When ENABLED, OTel becomes the trace engine:
7
- * - `@vercel/otel` registers the SDK + OTLP exporter (reading the standard
8
- * OTEL_* env: endpoint, headers, protocol, resource attributes).
9
- * - withOtelServerSpan() opens a SERVER span per request, CONTINUING an inbound
10
- * W3C traceparent, so a trace started upstream continues here.
11
- * - withOtelSpan() opens child spans for the domain story (order.place, ...).
12
- * - context.ts reads the ACTIVE OTel span's ids, so every log line carries the
13
- * same trace_id/span_id the backend shows, and traceHeaders() injects that
14
- * same trace id on outbound calls (one trace across every hop).
4
+ * The first-party stdout and file records are the source of truth and need no collector. The
5
+ * tracer runs whether or not anything is exported (otel-start.ts registers it either way), so a
6
+ * span's ids are the same with export on and off, and the files hold the span tree either way
7
+ * (spans.ts writes each span's record when it ends):
8
+ * - withOtelServerSpan() makes the request's SERVER span, continuing an inbound W3C
9
+ * traceparent, or names and annotates the one the framework already opened.
10
+ * - withOtelSpan() opens a span for a unit of work.
11
+ * - context.ts reads the active span's ids, so every record carries the ids the backend shows,
12
+ * and traceHeaders() sends them on outbound calls (one trace across every hop).
13
+ * - makeOtelLogSink() exports records, with their own ids, when export is on.
15
14
  *
16
- * @opentelemetry/api is imported at module top (it is a tiny, side-effect-free
17
- * facade); the heavy SDK (@vercel/otel) is imported lazily only when an endpoint
18
- * is configured. This file is Node-only (reached via setup/instrumentation) and
19
- * must never be imported by the Edge middleware.
15
+ * @opentelemetry/api is imported at module top (a small, side-effect-free facade); the SDK is
16
+ * imported lazily by otel-start.ts. Without a registered provider (a test, the edge runtime) the
17
+ * API's spans do not record and the AsyncLocalStorage ids in context.ts stand in. This file is
18
+ * Node-only and must never be imported by the Edge middleware.
20
19
  */
21
- import { context as otelContext, propagation, SpanKind, SpanStatusCode, trace, } from "@opentelemetry/api";
20
+ import { context as otelContext, propagation, ROOT_CONTEXT, SpanKind, SpanStatusCode, trace, TraceFlags, } from "@opentelemetry/api";
22
21
  import { logs, SeverityNumber } from "@opentelemetry/api-logs";
23
22
  import { obsConfig } from "./config.js";
23
+ import { exportable } from "./spans.js";
24
24
  import { LEVELS } from "./types.js";
25
25
  // The tracer and OTLP logger name is the service's own, set by configure().
26
26
  // It was a hardcoded constant in all four copies, which is exactly the kind
@@ -28,13 +28,13 @@ import { LEVELS } from "./types.js";
28
28
  function otelName() {
29
29
  return obsConfig().service;
30
30
  }
31
+ /**
32
+ * Whether OTLP export is configured: what the log sink and the metrics read. Spans never ask; the
33
+ * tracer runs either way. Env-based (process-global) on purpose: Next can run instrumentation.ts in
34
+ * a different module context than the route handlers, so a per-module flag set by startOtel would
35
+ * read false in handlers.
36
+ */
31
37
  export function otelEnabled() {
32
- // Env-based (process-global) on purpose: Next can run instrumentation.ts in a
33
- // different module context than the route handlers, so a per-module `enabled`
34
- // flag set by startOtel would read false in handlers. registerOTel sets the
35
- // OTel provider on globalThis (shared across contexts), so if the endpoint is
36
- // configured we use the global OTel API everywhere; it degrades cleanly (no-op
37
- // span -> currentIds null -> contextvar fallback) if the SDK did not start.
38
38
  return !!process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
39
39
  }
40
40
  // stdlib level name -> OTel severity number.
@@ -58,10 +58,14 @@ const SEVERITY_TEXT = {
58
58
  CRITICAL: "FATAL",
59
59
  };
60
60
  /**
61
- * A Sink that ships each obs record to the OTLP backend as a log, correlated to
62
- * the active span (emit() reads the current context, and our records are emitted
63
- * inside the request/domain span). Returns null when export is off. The internal
64
- * export fetch does not pass back through our sinks, so there is no feedback loop.
61
+ * A Sink that ships each obs record to the OTLP backend as a log carrying the record's own trace
62
+ * and span ids, not only an active span's: a record written for a span after it ended, or in work
63
+ * continued from a stored traceparent, still sits in its trace. Returns null when export is off.
64
+ *
65
+ * Two kinds of record stay in the files: a span's own record (the span itself is exported) and one
66
+ * marked `obs.local` (an export failure, which would otherwise travel over the failing exporter).
67
+ * What does leave passes `exportable`, and the message passes the service's `exportScrub`. The
68
+ * internal export fetch does not pass back through our sinks, so there is no feedback loop.
65
69
  */
66
70
  export function makeOtelLogSink() {
67
71
  if (!otelEnabled())
@@ -71,21 +75,42 @@ export function makeOtelLogSink() {
71
75
  name: "otlp",
72
76
  minLevel: LEVELS.DEBUG,
73
77
  write: (rec) => {
78
+ if (rec.event === "span.end" || rec.fields["obs.local"] === true)
79
+ return;
74
80
  const attributes = { "logger.name": rec.logger };
75
81
  if (rec.event)
76
82
  attributes.event = rec.event;
77
83
  for (const src of [rec.baggage, rec.fields]) {
78
- for (const [k, v] of Object.entries(src))
79
- attributes[k] = otelAttr(v);
84
+ for (const [k, v] of Object.entries(src)) {
85
+ if (exportable(k))
86
+ attributes[k] = otelAttr(v);
87
+ }
80
88
  }
89
+ const scrub = obsConfig().exportScrub;
90
+ // An error's type and message ride as attributes, as they end the record in the files; the
91
+ // message passes the scrub the body does, since an error can quote what it failed on.
92
+ if (rec.error) {
93
+ attributes["error.type"] = rec.error.type;
94
+ attributes["error.message"] = scrub ? scrub(rec.error.message) : rec.error.message;
95
+ }
96
+ const ids = rec.trace;
97
+ const context = ids.traceId.length === 32 && ids.spanId.length === 16
98
+ ? trace.setSpanContext(ROOT_CONTEXT, {
99
+ traceId: ids.traceId,
100
+ spanId: ids.spanId,
101
+ traceFlags: TraceFlags.SAMPLED,
102
+ isRemote: false,
103
+ })
104
+ : undefined;
81
105
  logger.emit({
106
+ context,
82
107
  severityNumber: SEVERITY[rec.level] ?? SeverityNumber.INFO,
83
108
  severityText: SEVERITY_TEXT[rec.level] ?? rec.level,
84
109
  // Body is the human message only - the event key already rides as its own
85
110
  // attribute, so prefixing it here is redundant and diverges from the
86
111
  // agent, whose OTLP body is the bare message. Fall back to the event when
87
112
  // there is no message (mirrors the agent's `message or event`).
88
- body: rec.msg || rec.event || "",
113
+ body: scrub ? scrub(rec.msg || rec.event || "") : rec.msg || rec.event || "",
89
114
  attributes,
90
115
  });
91
116
  },
@@ -96,11 +121,13 @@ export function makeOtelLogSink() {
96
121
  * Includes the span's PARENT id (same OTel id-space as spanId) so log records
97
122
  * can reconstruct the tree, not just correlate by trace_id. */
98
123
  export function currentIds() {
99
- if (!otelEnabled())
100
- return null;
101
124
  const span = trace.getActiveSpan();
102
- const sc = span?.spanContext();
103
- if (!sc || !sc.traceId || sc.traceId === "0".repeat(32))
125
+ // A span that does not record (no provider registered: a test, the edge runtime) carries its
126
+ // parent's ids, not ids of its own, so the AsyncLocalStorage ids stand in.
127
+ if (!span?.isRecording())
128
+ return null;
129
+ const sc = span.spanContext();
130
+ if (!sc.traceId || sc.traceId === "0".repeat(32))
104
131
  return null;
105
132
  // Parent isn't on the public Span API - read it defensively off the concrete
106
133
  // SDK span (ReadableSpan). SDK 2.x exposes parentSpanContext.spanId; SDK 1.x /
@@ -122,13 +149,25 @@ function setAttrs(span, attrs) {
122
149
  span.setAttribute(k, otelAttr(v));
123
150
  }
124
151
  /**
125
- * Open a SERVER span continuing the inbound W3C traceparent (extracted from the
126
- * request headers), so this service's span nests under the caller's trace. A
127
- * no-op passthrough when export is off.
152
+ * The request's SERVER span. When the framework has already opened one for this request (Next.js
153
+ * does, continuing an inbound traceparent itself), that span is named `name` and given `attrs`,
154
+ * and `fn` runs in it: one SERVER span per request, never two siblings. Otherwise a SERVER span is
155
+ * opened here, continuing the inbound W3C traceparent extracted from the headers.
128
156
  */
129
157
  export async function withOtelServerSpan(name, headers, attrs, fn) {
130
- if (!otelEnabled())
131
- return fn();
158
+ const active = trace.getActiveSpan();
159
+ if (active?.isRecording() && active.kind === SpanKind.SERVER) {
160
+ active.updateName(name);
161
+ setAttrs(active, attrs);
162
+ try {
163
+ return await fn();
164
+ }
165
+ catch (e) {
166
+ active.recordException(e);
167
+ active.setStatus({ code: SpanStatusCode.ERROR });
168
+ throw e;
169
+ }
170
+ }
132
171
  const carrier = {};
133
172
  headers.forEach((v, k) => {
134
173
  carrier[k] = v;
@@ -150,13 +189,17 @@ export async function withOtelServerSpan(name, headers, attrs, fn) {
150
189
  }
151
190
  }));
152
191
  }
153
- /** Open a child span around a unit of work (domain story / outbound). A no-op
154
- * passthrough when export is off. */
155
- export async function withOtelSpan(name, attrs, fn) {
156
- if (!otelEnabled())
157
- return fn();
192
+ const SPAN_KINDS = {
193
+ internal: SpanKind.INTERNAL,
194
+ server: SpanKind.SERVER,
195
+ client: SpanKind.CLIENT,
196
+ producer: SpanKind.PRODUCER,
197
+ consumer: SpanKind.CONSUMER,
198
+ };
199
+ /** Open a child span around a unit of work: internal by default, `kind` for a call out or a message. */
200
+ export async function withOtelSpan(name, attrs, fn, kind = "internal") {
158
201
  const tracer = trace.getTracer(otelName());
159
- return tracer.startActiveSpan(name, async (span) => {
202
+ return tracer.startActiveSpan(name, { kind: SPAN_KINDS[kind] }, async (span) => {
160
203
  setAttrs(span, attrs);
161
204
  try {
162
205
  const out = await fn();
@@ -171,3 +214,19 @@ export async function withOtelSpan(name, attrs, fn) {
171
214
  }
172
215
  });
173
216
  }
217
+ /**
218
+ * Open a span that starts a trace of its own, whatever span is in force: background work, a server
219
+ * action. `seed` continues a trace whose ids arrived another way (a stored or forwarded context):
220
+ * its trace id and, as the parent, its span id.
221
+ */
222
+ export async function withOtelRootSpan(name, attrs, fn, seed, kind = "internal") {
223
+ const parent = seed?.traceId && /^[0-9a-f]{32}$/.test(seed.traceId) && seed.parentSpanId && /^[0-9a-f]{16}$/.test(seed.parentSpanId)
224
+ ? trace.setSpanContext(ROOT_CONTEXT, {
225
+ traceId: seed.traceId,
226
+ spanId: seed.parentSpanId,
227
+ traceFlags: TraceFlags.SAMPLED,
228
+ isRemote: true,
229
+ })
230
+ : ROOT_CONTEXT;
231
+ return otelContext.with(parent, () => withOtelSpan(name, attrs, fn, kind));
232
+ }
package/dist/record.d.ts CHANGED
@@ -5,6 +5,11 @@ export declare function normalizeLevel(level: LevelInput | undefined): LevelName
5
5
  export declare function toErrorInfo(err: unknown): ErrorInfo | undefined;
6
6
  /** Build a record from the current trace context and emit it to the sinks. */
7
7
  export declare function emit(level: LevelName, logger: string, event: string | undefined, msg: string, fields?: Fields, error?: unknown): void;
8
+ /**
9
+ * Emit a record whose ids are given rather than read from the current context: a span's own
10
+ * record, written when the span ends, wherever that happens.
11
+ */
12
+ export declare function emitWith(ids: ObsRecord["trace"], baggage: Fields, level: LevelName, logger: string, event: string | undefined, msg: string, fields?: Fields, error?: unknown): void;
8
13
  /**
9
14
  * Test seam: capture records instead of emitting them.
10
15
  *
package/dist/record.js CHANGED
@@ -46,14 +46,21 @@ let testRecorder = null;
46
46
  /** Build a record from the current trace context and emit it to the sinks. */
47
47
  export function emit(level, logger, event, msg, fields, error) {
48
48
  const ctx = current();
49
+ emitWith({ traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId }, ctx.baggage, level, logger, event, msg, fields, error);
50
+ }
51
+ /**
52
+ * Emit a record whose ids are given rather than read from the current context: a span's own
53
+ * record, written when the span ends, wherever that happens.
54
+ */
55
+ export function emitWith(ids, baggage, level, logger, event, msg, fields, error) {
49
56
  const rec = {
50
57
  tsMs: Date.now(),
51
58
  level,
52
59
  logger,
53
60
  event,
54
61
  msg: msg ?? "",
55
- trace: { traceId: ctx.traceId, spanId: ctx.spanId, parentSpanId: ctx.parentSpanId },
56
- baggage: ctx.baggage,
62
+ trace: ids,
63
+ baggage,
57
64
  fields: fields ?? {},
58
65
  error: toErrorInfo(error),
59
66
  };
package/dist/setup.js CHANGED
@@ -12,7 +12,7 @@
12
12
  * filesystem is ephemeral or read-only.
13
13
  *
14
14
  * Env. `<PREFIX>` is the service's own `envPrefix`; the rest are generic and
15
- * shared, so a fleet-wide change is one value everywhere:
15
+ * shared, so a change across every service is one value everywhere:
16
16
  * LOG_LEVEL trace|debug|info|warn|error|fatal (default info)
17
17
  * LOG_FORMAT stdout renderer: pretty (default) | json
18
18
  * <PREFIX>_LOG_TEXT_FILE pretty file: 1 (default path), 0/off, or a path
@@ -29,6 +29,10 @@ import { renderJson, renderPretty } from "./render.js";
29
29
  import { configureSinks, makeFileSink, makeStdoutSink, setSinkInitializer } from "./sinks.js";
30
30
  import { LEVELS } from "./types.js";
31
31
  let configured = false;
32
+ // Whether the sinks were set up before configure() ran, and so hold stdout alone. A record written
33
+ // that early (a heartbeat from startOtel, which a service may call before createObs) must not cost
34
+ // the service its files: the next setupLogging() once configured sets them up again, whole.
35
+ let setUpUnconfigured = false;
32
36
  // Register lazy self-init: the first log emitted in any module context (Next
33
37
  // can isolate the instrumentation hook from the route handlers) runs
34
38
  // setupLogging for THAT context, so request and domain logs are never
@@ -115,11 +119,13 @@ function fileSetting(raw) {
115
119
  /** Test seam: let a test re-run setupLogging() with a different environment. */
116
120
  export function resetLoggingForTests() {
117
121
  configured = false;
122
+ setUpUnconfigured = false;
118
123
  }
119
124
  export function setupLogging() {
120
- if (configured)
125
+ if (configured && !(setUpUnconfigured && isConfigured()))
121
126
  return;
122
127
  configured = true;
128
+ setUpUnconfigured = !isConfigured();
123
129
  const level = resolveLevel();
124
130
  const stdoutFormat = (process.env.LOG_FORMAT || "pretty").trim().toLowerCase() === "json" ? "json" : "pretty";
125
131
  // stdout is always on; its renderer follows LOG_FORMAT.
package/dist/sinks.d.ts CHANGED
@@ -64,7 +64,7 @@ export declare class RotatingFileWriter {
64
64
  *
65
65
  * `renameSync` overwrites without a word, so rotating twice onto the same
66
66
  * name destroys the first file. That is not theoretical: it took a whole
67
- * day of the commander's records on r2d2 on 2026-09-22, when two writers
67
+ * day of one service's records in production on 2026-09-22, when two writers
68
68
  * in one process both rolled to `.2026-09-21`. The writers are shared now
69
69
  * and should not collide, but a second process (a restart overlapping its
70
70
  * predecessor) can still reach this line, and losing a day of logs to a
package/dist/sinks.js CHANGED
@@ -28,10 +28,10 @@ import { LEVELS } from "./types.js";
28
28
  * a SECOND set of sinks for that context - a second RotatingFileWriter on
29
29
  * the same path, with its own fd and its own rotation bookkeeping.
30
30
  *
31
- * What that does on a real host (r2d2, 2026-09-22): at the day boundary one
32
- * writer renames `commander.jsonl` to `commander.jsonl.2026-09-21` and opens
33
- * a fresh file; the other writer's fd still points at the renamed inode, so
34
- * it goes on appending there. The file an operator greps stops advancing
31
+ * What that did in production (2026-09-22): at the day boundary one writer
32
+ * renamed `service.jsonl` to `service.jsonl.2026-09-21` and opened
33
+ * a fresh file; the other writer's fd still pointed at the renamed inode, so
34
+ * it went on appending there. The file an operator greps stops advancing
35
35
  * while the service is plainly still logging, and the rotated file fills
36
36
  * with records from the WRONG day. Worse, `renameSync` overwrites, so the
37
37
  * second rotation destroyed the real previous day.
@@ -172,7 +172,8 @@ export class RotatingFileWriter {
172
172
  return "";
173
173
  const iso = new Date().toISOString();
174
174
  // "hourly" -> YYYY-MM-DDTHH ; "daily" (default) -> YYYY-MM-DD. UTC, matching
175
- // the agent's utc=True so rolled files are named consistently fleet-wide.
175
+ // the Python implementation's utc=True, so rolled files are named the same way
176
+ // by every service that writes this record.
176
177
  return this.rotation === "hourly" ? iso.slice(0, 13) : iso.slice(0, 10);
177
178
  }
178
179
  isSize() {
@@ -228,7 +229,7 @@ export class RotatingFileWriter {
228
229
  *
229
230
  * `renameSync` overwrites without a word, so rotating twice onto the same
230
231
  * name destroys the first file. That is not theoretical: it took a whole
231
- * day of the commander's records on r2d2 on 2026-09-22, when two writers
232
+ * day of one service's records in production on 2026-09-22, when two writers
232
233
  * in one process both rolled to `.2026-09-21`. The writers are shared now
233
234
  * and should not collide, but a second process (a restart overlapping its
234
235
  * predecessor) can still reach this line, and losing a day of logs to a
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Spans in the files, export that can be seen, and context for work that outlives its request.
3
+ *
4
+ * Three rules shape this module:
5
+ *
6
+ * - Every span the service cares about is written to its own files when it ends, as one
7
+ * record (`event: span.end`) carrying the span's own ids, name, kind, status and duration.
8
+ * A reader that has only the files (a log tool on the machine, an operator over ssh) can then
9
+ * draw the same span tree an OTLP backend shows; without it, a span that logged nothing is
10
+ * invisible, and every child of it reads as an orphan.
11
+ * - Nothing personal or secret leaves the process. What is exported passes `exportable`; the
12
+ * files keep everything, because they stay on the machine.
13
+ * - Export is seen, not assumed. Counters track what was sent and what failed, a periodic
14
+ * `obs.export` record says so, and a failure is logged to the files at most once a minute,
15
+ * never over the exporter that is failing.
16
+ *
17
+ * The span tree's ids come from the tracer whether or not anything is exported (see
18
+ * otel-start.ts), so the files look the same with export on and off.
19
+ */
20
+ import { SpanKind, SpanStatusCode, type Attributes, type Link, type SpanContext } from "@opentelemetry/api";
21
+ import type { Fields } from "./types.js";
22
+ /** The instrumentation scope of the spans this library opens itself. */
23
+ export declare const LIBRARY_SCOPE = "@diveinto/obs";
24
+ type HrTime = [number, number];
25
+ /** What this module reads of an ended SDK span (sdk-trace-base's ReadableSpan, 1.x and 2.x). */
26
+ export type EndedSpan = {
27
+ name: string;
28
+ kind: SpanKind;
29
+ spanContext(): SpanContext;
30
+ parentSpanContext?: {
31
+ spanId?: string;
32
+ };
33
+ parentSpanId?: string;
34
+ startTime: HrTime;
35
+ endTime: HrTime;
36
+ duration: HrTime;
37
+ status: {
38
+ code: SpanStatusCode;
39
+ message?: string;
40
+ };
41
+ attributes: Attributes;
42
+ links: Link[];
43
+ events: {
44
+ name: string;
45
+ attributes?: Attributes;
46
+ time: HrTime;
47
+ droppedAttributesCount?: number;
48
+ }[];
49
+ resource: unknown;
50
+ instrumentationScope?: {
51
+ name: string;
52
+ version?: string;
53
+ };
54
+ instrumentationLibrary?: {
55
+ name: string;
56
+ version?: string;
57
+ };
58
+ ended?: boolean;
59
+ droppedAttributesCount?: number;
60
+ droppedEventsCount?: number;
61
+ droppedLinksCount?: number;
62
+ };
63
+ /** The result an exporter reports: code 0 is success. */
64
+ export type ExportResult = {
65
+ code: number;
66
+ error?: Error;
67
+ };
68
+ /** A span exporter, as the SDK's batch processor calls one. */
69
+ export type SpanExporterLike = {
70
+ export(spans: EndedSpan[], done: (r: ExportResult) => void): void;
71
+ shutdown(): Promise<void>;
72
+ forceFlush?(): Promise<void>;
73
+ };
74
+ /** A log record exporter, as the SDK's batch processor calls one. */
75
+ export type LogExporterLike = {
76
+ export(records: unknown[], done: (r: ExportResult) => void): void;
77
+ shutdown(): Promise<void>;
78
+ forceFlush?(): Promise<void>;
79
+ };
80
+ /**
81
+ * Whether an attribute or field may be exported: not a credential, an address, a user agent, a
82
+ * query, a full URL or a personal identifier, and not one the service named in `exportDenyKeys`.
83
+ */
84
+ export declare function exportable(key: string): boolean;
85
+ /** What this process wrote and sent, since it started. */
86
+ export type ExportCounters = {
87
+ spansWritten: number;
88
+ spansExported: number;
89
+ spansFailed: number;
90
+ logsExported: number;
91
+ logsFailed: number;
92
+ };
93
+ /** The counters as they stand, a copy. */
94
+ export declare function exportCounters(): ExportCounters;
95
+ /**
96
+ * Write an `obs.export` record now and then every `everyMs`: whether export is on, and the
97
+ * counters. A component configured to export that has exported nothing in an hour shows it here.
98
+ * The timer never holds the process open.
99
+ */
100
+ export declare function startExportHeartbeat(exportOn: boolean, everyMs?: number): void;
101
+ /** Test seam: stop the heartbeat and zero the counters. */
102
+ export declare function resetExportForTests(): void;
103
+ /**
104
+ * A span exporter that sends only what may leave the process (`exportable`), counts what it sent
105
+ * and what failed, and notes a failure in the files. Never throws into the SDK.
106
+ */
107
+ export declare class GuardedSpanExporter implements SpanExporterLike {
108
+ private readonly inner;
109
+ constructor(inner: SpanExporterLike);
110
+ export(spans: EndedSpan[], done: (r: ExportResult) => void): void;
111
+ shutdown(): Promise<void>;
112
+ forceFlush(): Promise<void>;
113
+ }
114
+ /** A log exporter that counts what it sent and what failed, as GuardedSpanExporter does. */
115
+ export declare class GuardedLogExporter implements LogExporterLike {
116
+ private readonly inner;
117
+ constructor(inner: LogExporterLike);
118
+ export(records: unknown[], done: (r: ExportResult) => void): void;
119
+ shutdown(): Promise<void>;
120
+ forceFlush(): Promise<void>;
121
+ }
122
+ /** A W3C traceparent for a span context, always marked sampled: every trace is kept. */
123
+ export declare function traceparentOf(sc: {
124
+ traceId: string;
125
+ spanId: string;
126
+ }): string;
127
+ /**
128
+ * A span processor that writes one `span.end` record per span it keeps (see `writtenToFile`), with
129
+ * the span's own trace, span and parent ids, so the record sits in the tree where the span does.
130
+ * The record's fields: `span.name`, `span.kind`, `span.status`, `dur_ms`, `span.scope`, the span's
131
+ * links as traceparents (`span.links`), an error's type and message, and up to twenty of its
132
+ * attributes. Errors are ERROR records, so a level filter finds a failed span.
133
+ */
134
+ export declare class FileSpanProcessor {
135
+ onStart(): void;
136
+ onEnd(span: EndedSpan): void;
137
+ forceFlush(): Promise<void>;
138
+ shutdown(): Promise<void>;
139
+ }
140
+ /**
141
+ * The span context a W3C traceparent names, or null for one that is missing, malformed, too long,
142
+ * or all zeros. A stored or annotated traceparent is untrusted input: it never fails the work that
143
+ * reads it, it only fails to be a parent.
144
+ */
145
+ export declare function spanContextFromTraceparent(value: string | null | undefined): SpanContext | null;
146
+ /** The traceparent of the span in force, to store on a row or an object for later work; "" outside one. */
147
+ export declare function currentTraceparent(): string;
148
+ export type SpanKindName = "internal" | "server" | "client" | "producer" | "consumer";
149
+ /**
150
+ * Run work that another process asked for, or that a request asked for and is done later, as a
151
+ * span of its own: a child of the stored or annotated `parent` when there is one, else a new trace;
152
+ * linked to every traceparent in `links` (the other requests a batch serves, the operation an
153
+ * object was made for). A parent that is missing or malformed starts a new trace and never fails
154
+ * the work. An error is recorded on the span and thrown on.
155
+ */
156
+ export declare function continueFrom<T>(parent: string | null | undefined, name: string, fn: () => Promise<T> | T, opts?: {
157
+ kind?: SpanKindName;
158
+ links?: (string | null | undefined)[];
159
+ attributes?: Fields;
160
+ }): Promise<T>;
161
+ export {};