@diveinto/obs 1.0.5 → 1.1.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/README.md +47 -10
- package/dist/api.d.ts +2 -0
- package/dist/api.js +5 -5
- package/dist/config.d.ts +13 -1
- package/dist/config.js +2 -0
- package/dist/http.js +7 -8
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/otel-start.d.ts +14 -3
- package/dist/otel-start.js +66 -13
- package/dist/otel.d.ts +30 -10
- package/dist/otel.js +104 -45
- package/dist/record.d.ts +5 -0
- package/dist/record.js +9 -2
- package/dist/setup.js +1 -1
- package/dist/sinks.d.ts +1 -1
- package/dist/sinks.js +7 -6
- package/dist/spans.d.ts +161 -0
- package/dist/spans.js +387 -0
- package/dist/types.js +1 -1
- package/package.json +11 -6
package/dist/otel.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* OpenTelemetry: the trace engine, always; OTLP export, when an endpoint is configured.
|
|
3
3
|
*
|
|
4
|
-
* The first-party stdout
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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 (
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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:
|
|
56
|
-
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
|
|
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
|
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
|
|
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
|
|
32
|
-
*
|
|
33
|
-
* a fresh file; the other writer's fd still
|
|
34
|
-
* it
|
|
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
|
|
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
|
|
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
|
package/dist/spans.d.ts
ADDED
|
@@ -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 {};
|