@tallyrow/safesignal 1.2.0 → 1.3.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
CHANGED
|
@@ -194,6 +194,36 @@ getRootLogger().info('payment.authorized', { amount: 4200 });
|
|
|
194
194
|
context-merge precedence (root → logger chain → `correlation()`); host and
|
|
195
195
|
federated modules each contribute without per-`Logger` cost.
|
|
196
196
|
|
|
197
|
+
### Tag the delivery request with `traceparent`
|
|
198
|
+
|
|
199
|
+
Beyond the per-`LogRecord` trace fields above, the `./transport-otlp` transport
|
|
200
|
+
can also set a W3C `traceparent` (and `tracestate`) **request header** on the
|
|
201
|
+
delivery request itself, so a backend or collector can join the ingest request
|
|
202
|
+
to its trace. It is **off by default** — opt in per transport:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
const transport = createOtlpTransport({
|
|
206
|
+
endpoint: 'https://otlp.example.com/v1/logs',
|
|
207
|
+
headers: { authorization: `Bearer ${token}` }, // sent only on the wire
|
|
208
|
+
injectTraceparent: true, // ← opt in
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
A delivery request carries the header **only when every event in the flushed
|
|
213
|
+
batch shares one valid trace context** (the common case for a burst of logs in
|
|
214
|
+
one span); a mixed-trace, trace-less, or empty batch sends no header — never an
|
|
215
|
+
arbitrary "representative" one. `tracestate` rides along only when it is
|
|
216
|
+
identical across the batch (and within the 512-char bound).
|
|
217
|
+
|
|
218
|
+
- **Carry-only / fail-safe**: built from the events' existing `context.trace`;
|
|
219
|
+
no ids are minted, header construction never throws into a logging call or
|
|
220
|
+
blocks delivery, and the event payload is byte-identical either way.
|
|
221
|
+
- **Secure**: the header carries only trace identifiers + bounded `tracestate`;
|
|
222
|
+
it never overwrites, duplicates, or exposes your auth/secret `headers`
|
|
223
|
+
(a consumer-supplied `traceparent` wins). Only `./transport-otlp` supports it
|
|
224
|
+
— `navigator.sendBeacon` cannot set custom request headers, so
|
|
225
|
+
`./transport-beacon` is out of scope.
|
|
226
|
+
|
|
197
227
|
## Level configuration
|
|
198
228
|
|
|
199
229
|
In `production`, `debug` and `info` are dropped by default. Raise the
|
|
@@ -381,8 +411,6 @@ Reference docs and design history:
|
|
|
381
411
|
|
|
382
412
|
The following are forward-looking items (not shipping today):
|
|
383
413
|
|
|
384
|
-
- **Trace-context propagation** — W3C Trace Context (`traceparent`,
|
|
385
|
-
`tracestate`) for correlating frontend logs with backend traces.
|
|
386
414
|
- **OTLP/HTTP+protobuf encoding** for the
|
|
387
415
|
[`./transport-otlp`](#ship-logs-to-otlp--transport-otlp-subpath)
|
|
388
416
|
subpath — the subpath ships **JSON** today behind an internal
|
package/dist/transport-otlp.cjs
CHANGED
|
@@ -313,6 +313,76 @@ function pushException(out, event) {
|
|
|
313
313
|
}
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
// src/transport-otlp/traceparent-header.ts
|
|
317
|
+
var MAX_TRACESTATE_LEN = 512;
|
|
318
|
+
var TRACE_ID_RE = /^[0-9a-f]{32}$/;
|
|
319
|
+
var SPAN_ID_RE = /^[0-9a-f]{16}$/;
|
|
320
|
+
var ALL_ZERO_TRACE_ID = "0".repeat(32);
|
|
321
|
+
var ALL_ZERO_SPAN_ID = "0".repeat(16);
|
|
322
|
+
var NONE = {
|
|
323
|
+
key: "none",
|
|
324
|
+
traceparent: null,
|
|
325
|
+
traceState: null
|
|
326
|
+
};
|
|
327
|
+
function hasValidIds(trace) {
|
|
328
|
+
return typeof trace.traceId === "string" && TRACE_ID_RE.test(trace.traceId) && trace.traceId !== ALL_ZERO_TRACE_ID && typeof trace.spanId === "string" && SPAN_ID_RE.test(trace.spanId) && trace.spanId !== ALL_ZERO_SPAN_ID;
|
|
329
|
+
}
|
|
330
|
+
function flagsHex(traceFlags) {
|
|
331
|
+
const n = typeof traceFlags === "number" && Number.isInteger(traceFlags) && traceFlags >= 0 && traceFlags <= 255 ? traceFlags : 0;
|
|
332
|
+
return n.toString(16).padStart(2, "0");
|
|
333
|
+
}
|
|
334
|
+
function resolve(event) {
|
|
335
|
+
const trace = event.context.trace;
|
|
336
|
+
if (trace === void 0 || !hasValidIds(trace)) {
|
|
337
|
+
return NONE;
|
|
338
|
+
}
|
|
339
|
+
const traceparent = `00-${trace.traceId}-${trace.spanId}-${flagsHex(
|
|
340
|
+
trace.traceFlags
|
|
341
|
+
)}`;
|
|
342
|
+
const traceState = typeof trace.traceState === "string" && trace.traceState.length > 0 && trace.traceState.length <= MAX_TRACESTATE_LEN ? trace.traceState : null;
|
|
343
|
+
return { key: traceparent, traceparent, traceState };
|
|
344
|
+
}
|
|
345
|
+
function decideBatchTraceparent(events) {
|
|
346
|
+
if (events.length === 0) {
|
|
347
|
+
return { inject: false };
|
|
348
|
+
}
|
|
349
|
+
const first = resolve(events[0]);
|
|
350
|
+
if (first.key === "none") {
|
|
351
|
+
return { inject: false };
|
|
352
|
+
}
|
|
353
|
+
let tracestateUniform = true;
|
|
354
|
+
for (let i = 1; i < events.length; i += 1) {
|
|
355
|
+
const r = resolve(events[i]);
|
|
356
|
+
if (r.key !== first.key) {
|
|
357
|
+
return { inject: false };
|
|
358
|
+
}
|
|
359
|
+
if (r.traceState !== first.traceState) {
|
|
360
|
+
tracestateUniform = false;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const traceparent = first.traceparent;
|
|
364
|
+
if (tracestateUniform && first.traceState !== null) {
|
|
365
|
+
return { inject: true, traceparent, tracestate: first.traceState };
|
|
366
|
+
}
|
|
367
|
+
return { inject: true, traceparent };
|
|
368
|
+
}
|
|
369
|
+
function buildRequestHeaders(base, events, enabled) {
|
|
370
|
+
if (!enabled) {
|
|
371
|
+
return base;
|
|
372
|
+
}
|
|
373
|
+
const decision = decideBatchTraceparent(events);
|
|
374
|
+
if (!decision.inject) {
|
|
375
|
+
return base;
|
|
376
|
+
}
|
|
377
|
+
const injected = {
|
|
378
|
+
traceparent: decision.traceparent
|
|
379
|
+
};
|
|
380
|
+
if (decision.tracestate !== void 0) {
|
|
381
|
+
injected.tracestate = decision.tracestate;
|
|
382
|
+
}
|
|
383
|
+
return { ...injected, ...base };
|
|
384
|
+
}
|
|
385
|
+
|
|
316
386
|
// src/transport-otlp/otlp-transport.ts
|
|
317
387
|
var DEFAULTS = {
|
|
318
388
|
maxBatchSize: 20,
|
|
@@ -326,6 +396,9 @@ function validateOptions(options) {
|
|
|
326
396
|
throw new TypeError("otlp transport: options must be a non-null object");
|
|
327
397
|
}
|
|
328
398
|
const { headers, batching, maxBufferedEvents, maxRecordBytes } = options;
|
|
399
|
+
if (options.injectTraceparent !== void 0 && typeof options.injectTraceparent !== "boolean") {
|
|
400
|
+
throw new TypeError("otlp transport: injectTraceparent must be a boolean");
|
|
401
|
+
}
|
|
329
402
|
if (headers !== void 0) {
|
|
330
403
|
if (typeof headers !== "object" || headers === null) {
|
|
331
404
|
throw new TypeError("otlp transport: headers must be an object");
|
|
@@ -373,6 +446,7 @@ function createOtlpTransport(options) {
|
|
|
373
446
|
onInternalError: options.onInternalError ?? (() => void 0),
|
|
374
447
|
maxBufferedEvents: options.maxBufferedEvents ?? DEFAULTS.maxBufferedEvents,
|
|
375
448
|
maxRecordBytes: options.maxRecordBytes ?? DEFAULTS.maxRecordBytes,
|
|
449
|
+
injectTraceparent: options.injectTraceparent ?? false,
|
|
376
450
|
notified: freshNotifiedLedger(),
|
|
377
451
|
// Placeholder; real batcher assigned below once the flush closure exists.
|
|
378
452
|
batcher: void 0,
|
|
@@ -461,12 +535,18 @@ async function flushBatch(state, events) {
|
|
|
461
535
|
);
|
|
462
536
|
return;
|
|
463
537
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
538
|
+
let headers = state.headers;
|
|
539
|
+
try {
|
|
540
|
+
headers = buildRequestHeaders(
|
|
467
541
|
state.headers,
|
|
468
|
-
|
|
542
|
+
events,
|
|
543
|
+
state.injectTraceparent
|
|
469
544
|
);
|
|
545
|
+
} catch {
|
|
546
|
+
headers = state.headers;
|
|
547
|
+
}
|
|
548
|
+
const promise = (async () => {
|
|
549
|
+
const result = await deliver(state.endpoint, headers, body);
|
|
470
550
|
mapResult(state, result);
|
|
471
551
|
})().catch(() => {
|
|
472
552
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/transport-otlp/batcher.ts","../src/transport-otlp/delivery.ts","../src/transport-otlp/endpoint-validation.ts","../src/transport-otlp/errors.ts","../src/transport-otlp/attributes.ts","../src/transport-otlp/resource.ts","../src/transport-otlp/otlp-serializer.ts","../src/transport-otlp/otlp-transport.ts"],"names":[],"mappings":"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","file":"transport-otlp.cjs","sourcesContent":["/**\n * Bounded batch buffer for the OTLP transport.\n *\n * A parallel copy of the `./transport-beacon` batcher state machine (the\n * boundary rule forbids importing across subpaths — TO-7). Behaviour\n * (research D7, data-model § Batcher):\n *\n * - `push(event)` appends to the in-memory buffer.\n * - First event in an empty batch with `maxBatchAgeMs` set arms a\n * one-shot age timer.\n * - Reaching `maxBatchSize` flushes synchronously at end of `push`.\n * - `flush()` drains the pending buffer through the consumer callback.\n * Empty buffer → no-op.\n * - `shutdown()` cancels the timer and inhibits further callbacks.\n *\n * Buffer is copied + cleared BEFORE the callback runs, so a re-entrant\n * `push` during the callback starts a fresh batch. A throwing callback is\n * swallowed (the batcher never throws from push/flush/shutdown). The hard\n * `maxBufferedEvents` cap is enforced by the transport BEFORE `push`, so\n * the batcher itself stays simple.\n */\n\nimport type { LogEvent } from '../api/types.js';\n\nexport interface BatcherOptions {\n maxBatchSize: number;\n maxBatchAgeMs?: number;\n flush: (events: LogEvent[]) => void;\n}\n\nexport interface Batcher {\n push(event: LogEvent): void;\n flush(): void;\n shutdown(): void;\n /** Current pending count — used by the transport's buffer-cap guard. */\n size(): number;\n}\n\nexport function createBatcher(opts: BatcherOptions): Batcher {\n const buffer: LogEvent[] = [];\n let maxAgeTimer: ReturnType<typeof setTimeout> | null = null;\n let flushCallback: ((events: LogEvent[]) => void) | null = opts.flush;\n\n const clearTimer = (): void => {\n if (maxAgeTimer !== null) {\n clearTimeout(maxAgeTimer);\n maxAgeTimer = null;\n }\n };\n\n const armTimer = (): void => {\n if (opts.maxBatchAgeMs === undefined) return;\n maxAgeTimer = setTimeout(() => {\n maxAgeTimer = null;\n doFlush();\n }, opts.maxBatchAgeMs);\n };\n\n const doFlush = (): void => {\n if (buffer.length === 0) return;\n if (flushCallback === null) {\n buffer.length = 0;\n clearTimer();\n return;\n }\n const events = buffer.slice();\n buffer.length = 0;\n clearTimer();\n try {\n flushCallback(events);\n } catch {\n // Swallow: the events were copied out; the consumer callback chose\n // to throw. Single-flush-attempt model (no re-push, no retry).\n }\n };\n\n return {\n push(event: LogEvent): void {\n if (flushCallback === null) return;\n buffer.push(event);\n if (buffer.length === 1 && opts.maxBatchAgeMs !== undefined) {\n armTimer();\n }\n if (buffer.length >= opts.maxBatchSize) {\n doFlush();\n }\n },\n flush(): void {\n doFlush();\n },\n shutdown(): void {\n clearTimer();\n buffer.length = 0;\n flushCallback = null;\n },\n size(): number {\n return buffer.length;\n },\n };\n}\n","/**\n * Delivery primitive for the OTLP transport: POST the OTLP/HTTP+JSON body\n * via `fetch` with `keepalive: true` (research D6, TO-2).\n *\n * `navigator.sendBeacon` is deliberately NOT used — it cannot set the\n * custom auth + `Content-Type` headers OTLP backends require (FR-004).\n * `keepalive` preserves best-effort delivery during page unload.\n *\n * `deliver(...)` NEVER throws or rejects: every outcome (2xx, 2xx with an\n * OTLP partial-success rejection, non-2xx, network reject, missing\n * `fetch`) is reduced to a `DeliveryResult` for the caller to map onto a\n * rate-limited notice. There is no retry (research D7).\n *\n * Boundary discipline (TO-7): zero `src/` imports; zero vendor imports.\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md` OP-1;\n * `contracts/transport-otlp-public-api.md` TO-2/TO-4.\n */\n\n/** Outcome of a single delivery attempt. Never an exception. */\nexport type DeliveryResult =\n | { kind: 'delivered' }\n | { kind: 'unavailable' }\n | { kind: 'send_failed'; detail: string; cause?: unknown }\n | { kind: 'partial_rejection'; rejected: number };\n\n/**\n * POST `body` to `endpoint` with `keepalive: true`, merging the caller's\n * static `headers` (e.g. auth) over the mandatory `content-type`.\n * `credentials: 'same-origin'` keeps cookies from leaking cross-origin by\n * default (Principle IV); auth travels only in the explicit headers.\n */\nexport async function deliver(\n endpoint: string,\n headers: Readonly<Record<string, string>>,\n body: string,\n): Promise<DeliveryResult> {\n const fetchFn = (globalThis as { fetch?: typeof fetch }).fetch;\n if (typeof fetchFn !== 'function') {\n return { kind: 'unavailable' };\n }\n\n let response: Response;\n try {\n response = await fetchFn(endpoint, {\n method: 'POST',\n body,\n headers: { 'content-type': 'application/json', ...headers },\n keepalive: true,\n credentials: 'same-origin',\n });\n } catch (cause) {\n return { kind: 'send_failed', detail: 'fetch rejected', cause };\n }\n\n if (!response.ok) {\n return { kind: 'send_failed', detail: `HTTP ${response.status}` };\n }\n\n // 2xx — check for an OTLP partial-success rejection in the body. A\n // missing / non-JSON / unexpected body is treated as full success.\n const rejected = await readRejectedCount(response);\n if (rejected > 0) {\n return { kind: 'partial_rejection', rejected };\n }\n return { kind: 'delivered' };\n}\n\nasync function readRejectedCount(response: Response): Promise<number> {\n try {\n if (typeof response.json !== 'function') return 0;\n const parsed = (await response.json()) as unknown;\n if (typeof parsed !== 'object' || parsed === null) return 0;\n const partial = (parsed as { partialSuccess?: unknown }).partialSuccess;\n if (typeof partial !== 'object' || partial === null) return 0;\n const raw = (partial as { rejectedLogRecords?: unknown })\n .rejectedLogRecords;\n // OTLP encodes int64 as a string in JSON, but tolerate a number too.\n const n = typeof raw === 'string' ? Number(raw) : raw;\n return typeof n === 'number' && Number.isFinite(n) && n > 0 ? n : 0;\n } catch {\n // A body that cannot be read/parsed does not turn a 2xx into a failure.\n return 0;\n }\n}\n","/**\n * Construction-time endpoint validation for `createOtlpTransport`.\n *\n * Locked behaviour (mirrors `./transport-beacon`, TO-5 / research D8):\n * - HTTPS endpoints always pass.\n * - HTTP endpoints pass IFF `allowInsecureLoopback === true` AND the\n * parsed URL's hostname is in `{ localhost, 127.0.0.1, [::1] }`.\n * - Every other case throws a typed error at construction time, before\n * any logger derives the runtime and before any listener is attached.\n *\n * Pure and side-effect-free: parses via `new URL(...)` and inspects the\n * result. MUST NOT read ambient state (no `process.env`, no\n * `window.location`) and MUST NOT read `allowInsecureLoopback` from\n * anywhere except the argument the caller passed.\n *\n * The consumer supplies the FULL OTLP logs URL (e.g.\n * `https://otlp.example.com/v1/logs`); the transport appends nothing.\n *\n * Specs: `specs/007-transport-otlp/contracts/transport-otlp-public-api.md`\n * TO-5; `data-model.md` § validation rules.\n */\n\n/**\n * Hostnames permitted under `allowInsecureLoopback: true`. WHATWG URL\n * normalises `http://[::1]` to hostname `[::1]` (with brackets).\n */\nconst LOOPBACK_HOSTS: ReadonlySet<string> = new Set([\n 'localhost',\n '127.0.0.1',\n '[::1]',\n]);\n\n/**\n * Validate a consumer-supplied `endpoint`. Returns the parsed `URL` on\n * success; throws on every violation. The thrown error's `.message` names\n * the violated constraint and the offending endpoint string.\n */\nexport function validateEndpoint(\n endpoint: unknown,\n allowInsecureLoopback: boolean,\n): URL {\n if (typeof endpoint !== 'string') {\n throw new TypeError(\n `otlp transport: endpoint must be a string, got ${typeName(endpoint)}`,\n );\n }\n\n let parsed: URL;\n try {\n parsed = new URL(endpoint);\n } catch {\n throw new TypeError(`otlp transport: invalid endpoint URL: '${endpoint}'`);\n }\n\n if (parsed.protocol === 'https:') {\n return parsed;\n }\n\n if (parsed.protocol === 'http:') {\n if (!allowInsecureLoopback) {\n throw new Error(\n `otlp transport refuses non-HTTPS endpoint '${endpoint}'`,\n );\n }\n if (!LOOPBACK_HOSTS.has(parsed.hostname)) {\n throw new Error(\n `otlp transport: allowInsecureLoopback permits only ` +\n `localhost / 127.0.0.1 / [::1]; got '${parsed.hostname}' ` +\n `in '${endpoint}'`,\n );\n }\n return parsed;\n }\n\n throw new Error(\n `otlp transport refuses non-HTTPS endpoint '${endpoint}' ` +\n `(scheme '${parsed.protocol}' is not permitted)`,\n );\n}\n\nfunction typeName(value: unknown): string {\n if (value === null) return 'null';\n return typeof value;\n}\n","/**\n * Subpath-owned diagnostic error class + rate-limited notice helper for\n * the OTLP transport.\n *\n * Drop/diagnostic notices fired by the OTLP transport are `OtlpError`\n * instances (a subclass of `Error`) owned by this subpath — by-convention\n * compatible with the core's `PackageError` shape (`.code`,\n * `.transportName`, optional `.cause`) but with NO runtime import from\n * `src/internal/**` (TO-7). This module is INTERNAL to the subpath;\n * `src/transport-otlp/index.ts` does not re-export it. The\n * `onInternalError` hook receives the instance typed as `Error`.\n *\n * **Security (FR-009 / TO-6)**: notice messages MUST NEVER include any\n * configured request-header value. The helpers here only ever build\n * messages from the failure code, the transport name, and an optional\n * non-secret detail string supplied by the caller. Callers MUST NOT pass\n * header values into `detail`.\n *\n * Specs: `specs/007-transport-otlp/data-model.md` § OtlpFailureCode;\n * `contracts/transport-otlp-public-api.md` TO-4/TO-6.\n */\n\n/**\n * Documented `OtlpError.code` values — one per failure class. Surfaced to\n * the consumer as `err.code` on the `Error`-shaped `onInternalError`\n * argument. Each is rate-limited to one notice per class per transport\n * instance per session.\n */\nexport type OtlpFailureCode =\n | 'oversized_event'\n | 'buffer_overflow'\n | 'delivery_unavailable'\n | 'send_failed'\n | 'partial_rejection'\n | 'serialize_failed'\n | 'shutdown_failed';\n\n/**\n * Subclass of `Error` carrying a discriminating `.code`, the originating\n * transport's `.transportName`, and an optional `.cause` chain. `.name`\n * is `'OtlpError'`. `.cause` follows the ES2022 convention (absent when\n * not provided rather than `undefined`).\n */\nexport class OtlpError extends Error {\n readonly code: OtlpFailureCode;\n readonly transportName: string;\n declare readonly cause?: unknown;\n\n constructor(\n code: OtlpFailureCode,\n transportName: string,\n message: string,\n cause?: unknown,\n ) {\n super(message);\n this.name = 'OtlpError';\n this.code = code;\n this.transportName = transportName;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', {\n value: cause,\n enumerable: true,\n writable: false,\n configurable: false,\n });\n }\n }\n}\n\n/** Type guard for `OtlpError` instances. */\nexport function isOtlpError(value: unknown): value is OtlpError {\n return value instanceof OtlpError;\n}\n\n/**\n * Minimal shape the `notifyOnce` helper needs: the transport's name, the\n * consumer error sink, and a per-code \"already notified\" ledger. The\n * concrete `OtlpTransportState` (data-model) satisfies this.\n */\nexport interface NotifyContext {\n readonly name: string;\n readonly onInternalError: (err: Error) => void;\n readonly notified: Record<OtlpFailureCode, boolean>;\n}\n\n/**\n * Emit at most ONE diagnostic notice per failure `code` per context\n * (instance) per session (FR-010). Subsequent calls with the same code are\n * silently suppressed. The `onInternalError` callback is invoked inside a\n * try/catch so a throwing consumer handler can never propagate back into\n * the transport's hot path (Principle II).\n *\n * `detail` is an optional NON-SECRET human string (e.g. an HTTP status).\n * Callers MUST NOT pass any configured header/secret value here.\n */\nexport function notifyOnce(\n ctx: NotifyContext,\n code: OtlpFailureCode,\n message: string,\n cause?: unknown,\n): void {\n if (ctx.notified[code]) return;\n ctx.notified[code] = true;\n const err = new OtlpError(\n code,\n ctx.name,\n `otlp transport '${ctx.name}': ${message}`,\n cause,\n );\n try {\n ctx.onInternalError(err);\n } catch {\n // A throwing consumer error handler must never reach the caller.\n }\n}\n\n/** Build the per-instance \"already notified\" ledger with all flags false. */\nexport function freshNotifiedLedger(): Record<OtlpFailureCode, boolean> {\n return {\n oversized_event: false,\n buffer_overflow: false,\n delivery_unavailable: false,\n send_failed: false,\n partial_rejection: false,\n serialize_failed: false,\n shutdown_failed: false,\n };\n}\n","/**\n * Pure `AttributeValue` → OTLP `AnyValue` encoder + the shared OTLP-JSON\n * value types used across the subpath.\n *\n * The OTLP/HTTP+JSON value model is hand-encoded here with ZERO runtime\n * dependencies and no `@opentelemetry/*` import (research D1, TO-7). The\n * `AttributeValue` union (string | number | boolean | null | array |\n * object) maps 1:1 onto OTLP `AnyValue` (OP-5).\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md` OP-5.\n */\n\nimport type { AttributeValue } from '../api/types.js';\n\n/**\n * OTLP `AnyValue` (JSON encoding). `null` → `{}` (an unset value) since\n * OTLP has no explicit null. A non-finite or otherwise non-integer number\n * is a `doubleValue`; an integer-safe number is an `intValue` (string, per\n * the uint64/sint64-as-string rule).\n */\nexport type AnyValue =\n | { stringValue: string }\n | { boolValue: boolean }\n | { intValue: string }\n | { doubleValue: number }\n | { arrayValue: { values: AnyValue[] } }\n | { kvlistValue: { values: KeyValue[] } }\n // biome-ignore lint/complexity/noBannedTypes: OTLP represents an unset/null value as an empty AnyValue object.\n | {};\n\n/** OTLP `KeyValue`. */\nexport interface KeyValue {\n key: string;\n value: AnyValue;\n}\n\n/** Encode a single sanitized `AttributeValue` as an OTLP `AnyValue`. */\nexport function toAnyValue(value: AttributeValue): AnyValue {\n if (value === null) {\n return {};\n }\n switch (typeof value) {\n case 'string':\n return { stringValue: value };\n case 'boolean':\n return { boolValue: value };\n case 'number':\n return Number.isInteger(value)\n ? { intValue: String(value) }\n : { doubleValue: value };\n default:\n break;\n }\n if (Array.isArray(value)) {\n return { arrayValue: { values: value.map(toAnyValue) } };\n }\n // Remaining case: a plain `{ [key: string]: AttributeValue }` object.\n return { kvlistValue: { values: toKeyValues(value) } };\n}\n\n/**\n * Encode a record of sanitized attributes as an OTLP `KeyValue[]`,\n * optionally prefixing each key (used to namespace merged context\n * attributes under `context.` — OP-4).\n */\nexport function toKeyValues(\n record: Readonly<Record<string, AttributeValue>>,\n keyPrefix = '',\n): KeyValue[] {\n const out: KeyValue[] = [];\n for (const key of Object.keys(record)) {\n const value = record[key];\n if (value === undefined) continue;\n out.push({ key: keyPrefix + key, value: toAnyValue(value) });\n }\n return out;\n}\n","/**\n * Map SafeSignal runtime-global identity to an OTLP `Resource` (OP-2 / D3).\n *\n * The `Resource` carries only identity that is constant across a batch\n * from one configured transport — `service.name`, `service.version`,\n * `deployment.environment` (standard OTel semantic-convention attributes\n * every OTLP backend understands). The federated `module.*` identity is\n * **per-logger** (it can differ between events in the same batch via\n * `withContext`), so it is attributed per-`LogRecord` (see\n * `otlp-serializer.ts`), not on the shared Resource — preserving correct\n * origin attribution (Principle VI).\n *\n * Only present fields are emitted (no empty/`null` keys). Pure and\n * dependency-free.\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md` OP-2.\n */\n\nimport type { LogContext } from '../api/types.js';\n\nimport type { KeyValue } from './attributes.js';\n\nexport interface OtlpResource {\n attributes: KeyValue[];\n}\n\n/**\n * Build the OTLP `Resource` for a batch from a representative context\n * (the batch's first event). `service.*` / `deployment.environment` are\n * runtime-global, so any event in the batch is representative.\n */\nexport function buildResource(context: LogContext): OtlpResource {\n const attributes: KeyValue[] = [];\n\n const push = (key: string, value: string | undefined): void => {\n if (typeof value === 'string' && value.length > 0) {\n attributes.push({ key, value: { stringValue: value } });\n }\n };\n\n push('service.name', context.application?.name);\n push('service.version', context.application?.version);\n push('deployment.environment', context.environment);\n\n return { attributes };\n}\n","/**\n * Pure `LogEvent[]` → OTLP/HTTP+JSON logs payload serializer (OP-1..OP-6).\n *\n * This is the heart of the subpath. It hand-builds the OTLP logs JSON\n * shape with ZERO runtime dependencies and NO `@opentelemetry/*` import\n * (research D1, TO-7): severity numbers are literal constants matching the\n * OTel `SeverityNumber` ranges (and the in-repo\n * `src/internal/telemetry/otel/mapping.ts` table), reused conceptually but\n * never imported.\n *\n * **Encoding seam (FR-015)**: `serializeBatch(...)` builds the encoder-\n * neutral `OtlpLogsRequest` object; `encode(...)` turns it into the wire\n * body. Today the only encoder is JSON; a future protobuf encoder slots in\n * behind `encode(...)` without changing the object model or the public\n * surface.\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md`.\n */\n\nimport type { LogContext, LogEvent, LogLevel } from '../api/types.js';\n\nimport { type AnyValue, type KeyValue, toKeyValues } from './attributes.js';\nimport { buildResource } from './resource.js';\n\n/** Constant instrumentation-scope name for every emitted ScopeLogs. */\nexport const SCOPE_NAME = '@tallyrow/safesignal';\n\n/**\n * OTLP `SeverityNumber` base value per SafeSignal level (OP-3 / D2). These\n * are the canonical OTLP range bases (DEBUG 5, INFO 9, WARN 13, ERROR 17)\n * and match `LEVEL_TO_SEVERITY` in the internal OTel seam.\n */\nconst LEVEL_TO_SEVERITY_NUMBER: Readonly<Record<LogLevel, number>> = {\n debug: 5,\n info: 9,\n warn: 13,\n error: 17,\n};\n\nconst LEVEL_TO_SEVERITY_TEXT: Readonly<Record<LogLevel, string>> = {\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n};\n\n// ---------------------------------------------------------------------------\n// OTLP wire shapes (JSON encoding)\n// ---------------------------------------------------------------------------\n\nexport interface OtlpLogRecord {\n timeUnixNano: string;\n observedTimeUnixNano: string;\n severityNumber: number;\n severityText: string;\n body: AnyValue;\n attributes: KeyValue[];\n /** W3C trace correlation — present only when `event.context.trace` is set. */\n traceId?: string;\n spanId?: string;\n flags?: number;\n}\n\nexport interface OtlpScopeLogs {\n scope: { name: string };\n logRecords: OtlpLogRecord[];\n}\n\nexport interface OtlpResourceLogs {\n resource: { attributes: KeyValue[] };\n scopeLogs: OtlpScopeLogs[];\n}\n\nexport interface OtlpLogsRequest {\n resourceLogs: OtlpResourceLogs[];\n}\n\n// ---------------------------------------------------------------------------\n// Mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Convert one `LogEvent` to an OTLP `LogRecord`. Never mutates the input\n * (T-S4). Per-record attributes are: event attributes, then merged context\n * attributes under a `context.` prefix, then per-logger `module.*`\n * identity, then `exception.*` when an error is present (OP-4).\n *\n * `fallbackTimeMs` is a single resolved time used when `event.timestamp`\n * is unparseable, so the mapping never throws on a bad timestamp (OP-3).\n */\nexport function toLogRecord(\n event: LogEvent,\n fallbackTimeMs: number,\n): OtlpLogRecord {\n const ms = toEpochMs(event.timestamp, fallbackTimeMs);\n const nano = String(ms * 1_000_000);\n\n const attributes: KeyValue[] = toKeyValues(event.attributes);\n\n const context = event.context;\n if (context.attributes !== undefined) {\n attributes.push(...toKeyValues(context.attributes, 'context.'));\n }\n pushModuleIdentity(attributes, context);\n pushException(attributes, event);\n\n const record: OtlpLogRecord = {\n timeUnixNano: nano,\n observedTimeUnixNano: nano,\n severityNumber: LEVEL_TO_SEVERITY_NUMBER[event.level],\n severityText: LEVEL_TO_SEVERITY_TEXT[event.level],\n body: { stringValue: event.message },\n attributes,\n };\n\n // W3C trace correlation → OTLP standard top-level fields (OP/OT contracts).\n // The structured ids are already lowercase-hex (validated upstream), so they\n // are the OTLP/JSON encoding as-is — no base64, no @opentelemetry import.\n const trace = context.trace;\n if (trace !== undefined) {\n record.traceId = trace.traceId;\n record.spanId = trace.spanId;\n if (trace.traceFlags !== undefined) {\n record.flags = trace.traceFlags;\n }\n }\n\n return record;\n}\n\n/**\n * Build the encoder-neutral OTLP logs request object for a batch. The\n * Resource is derived from the first event's runtime-global identity; if\n * the batch is empty, an empty Resource is used.\n */\nexport function serializeBatch(\n batch: ReadonlyArray<LogEvent>,\n fallbackTimeMs: number,\n): OtlpLogsRequest {\n const first = batch[0];\n const resource = buildResource(first ? first.context : ({} as LogContext));\n const logRecords = batch.map((e) => toLogRecord(e, fallbackTimeMs));\n return {\n resourceLogs: [\n {\n resource,\n scopeLogs: [{ scope: { name: SCOPE_NAME }, logRecords }],\n },\n ],\n };\n}\n\n/**\n * Encoding seam (FR-015). Turns the request object into the wire body.\n * The only encoding in this feature is JSON; protobuf is a roadmap\n * follow-up that slots in here without touching callers.\n */\nexport function encode(request: OtlpLogsRequest): string {\n return JSON.stringify(request);\n}\n\n/**\n * Convenience: build + JSON-encode a batch in one call. Pure; never\n * mutates inputs.\n */\nexport function serializeOtlpJson(\n batch: ReadonlyArray<LogEvent>,\n fallbackTimeMs: number,\n): string {\n return encode(serializeBatch(batch, fallbackTimeMs));\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction toEpochMs(iso: string, fallbackMs: number): number {\n const parsed = Date.parse(iso);\n return Number.isFinite(parsed) ? parsed : fallbackMs;\n}\n\nfunction pushModuleIdentity(out: KeyValue[], context: LogContext): void {\n const mod = context.module;\n if (mod === undefined) return;\n if (typeof mod.name === 'string' && mod.name.length > 0) {\n out.push({ key: 'module.name', value: { stringValue: mod.name } });\n }\n if (typeof mod.version === 'string' && mod.version.length > 0) {\n out.push({ key: 'module.version', value: { stringValue: mod.version } });\n }\n}\n\nfunction pushException(out: KeyValue[], event: LogEvent): void {\n const err = event.error;\n if (err === undefined) return;\n out.push({ key: 'exception.type', value: { stringValue: err.name } });\n out.push({ key: 'exception.message', value: { stringValue: err.message } });\n if (typeof err.stack === 'string' && err.stack.length > 0) {\n out.push({\n key: 'exception.stacktrace',\n value: { stringValue: err.stack },\n });\n }\n}\n","/**\n * `createOtlpTransport` — factory for the `./transport-otlp` subpath.\n *\n * Composes the subpath primitives into a `Transport` that delivers\n * fully-processed `LogEvent`s to an OTLP logs backend as OTLP/HTTP+JSON,\n * batched, fire-and-forget (no retry), fail-closed.\n *\n * Delivery policy (research D6/D7, contracts TO-2..TO-8):\n *\n * send(event)\n * ├── if shutdownComplete: no-op\n * ├── lazily install the pagehide best-effort flush (first send)\n * ├── if serialized record > maxRecordBytes → oversized_event drop\n * ├── if buffered >= maxBufferedEvents → buffer_overflow drop\n * └── batcher.push(event) // flush on size / age\n *\n * flush(batch) // batcher callback\n * ├── serialize(batch) (fail-closed: serialize_failed → drop)\n * └── deliver(endpoint, headers, body) // fetch keepalive, never throws\n * ├── delivered ─────────────────── done\n * ├── unavailable ───────────────── delivery_unavailable notice\n * ├── send_failed ───────────────── send_failed notice (+cause)\n * └── partial_rejection ─────────── partial_rejection notice\n *\n * Every notice is rate-limited to one per failure class per instance per\n * session and NEVER carries a configured header/secret value (FR-009).\n * `send`/`flush`/`shutdown` NEVER throw or reject to the caller; only\n * construction-time validation throws, at the consumer's call site.\n *\n * Boundary discipline (TO-7): the only `src/` import is a type-only import\n * from `'../api/types.js'`. No `@opentelemetry/*` and no\n * `../internal/telemetry/otel/` import — the payload is hand-serialized.\n *\n * Specs: `specs/007-transport-otlp/contracts/*`, `data-model.md`.\n */\n\nimport type { LogEvent, Transport } from '../api/types.js';\n\nimport { type Batcher, createBatcher } from './batcher.js';\nimport { type DeliveryResult, deliver } from './delivery.js';\nimport { validateEndpoint } from './endpoint-validation.js';\nimport {\n freshNotifiedLedger,\n type NotifyContext,\n notifyOnce,\n type OtlpFailureCode,\n} from './errors.js';\nimport { encode, serializeBatch, toLogRecord } from './otlp-serializer.js';\n\n// ---------------------------------------------------------------------------\n// Public options shape (data-model.md § OtlpTransportOptions)\n// ---------------------------------------------------------------------------\n\nexport interface OtlpTransportOptions {\n /** Full OTLP logs endpoint URL (e.g. `https://otlp.example.com/v1/logs`). */\n endpoint: string;\n /** Static request headers (e.g. auth). Sent only on the wire. */\n headers?: Record<string, string>;\n /** Batch flush triggers. */\n batching?: {\n maxBatchSize: number;\n maxBatchAgeMs?: number;\n };\n /** Hard cap on buffered events; over-cap events are dropped. Default 1000. */\n maxBufferedEvents?: number;\n /** Per-record size guard in bytes; larger records are dropped. Default 64 KiB. */\n maxRecordBytes?: number;\n /** Stable diagnostic identifier (`Transport.name`). Default `'otlp'`. */\n name?: string;\n /** Permit `http://` localhost/127.0.0.1/[::1] only. Default false. */\n allowInsecureLoopback?: boolean;\n /** Receives rate-limited diagnostic notices. Never carries header values. */\n onInternalError?: (err: Error) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal per-instance state (data-model.md § OtlpTransportState)\n// ---------------------------------------------------------------------------\n\ninterface OtlpTransportState extends NotifyContext {\n readonly endpoint: string;\n readonly headers: Readonly<Record<string, string>>;\n readonly name: string;\n readonly onInternalError: (err: Error) => void;\n readonly maxBufferedEvents: number;\n readonly maxRecordBytes: number;\n readonly notified: Record<OtlpFailureCode, boolean>;\n batcher: Batcher;\n pagehideInstalled: boolean;\n pagehideUninstall: (() => void) | null;\n shutdownComplete: boolean;\n inFlight: Set<Promise<void>>;\n /**\n * Events accepted but not yet delivered (buffered in the batcher + in\n * flight). The true memory bound in this no-retry design: incremented on\n * accept, decremented when a batch's delivery settles. Capped at\n * `maxBufferedEvents` so a slow/failing backend cannot grow memory\n * unboundedly.\n */\n pending: number;\n}\n\nconst DEFAULTS = {\n maxBatchSize: 20,\n maxBatchAgeMs: 5000,\n maxBufferedEvents: 1000,\n maxRecordBytes: 65536,\n name: 'otlp',\n} as const;\n\n// ---------------------------------------------------------------------------\n// Construction-time validation (TO-2)\n// ---------------------------------------------------------------------------\n\nfunction validateOptions(options: OtlpTransportOptions): void {\n if (typeof options !== 'object' || options === null) {\n throw new TypeError('otlp transport: options must be a non-null object');\n }\n const { headers, batching, maxBufferedEvents, maxRecordBytes } = options;\n\n if (headers !== undefined) {\n if (typeof headers !== 'object' || headers === null) {\n throw new TypeError('otlp transport: headers must be an object');\n }\n for (const key of Object.keys(headers)) {\n if (typeof headers[key] !== 'string') {\n throw new TypeError(\n `otlp transport: header '${key}' must be a string value`,\n );\n }\n }\n }\n\n const maxBatchSize = batching?.maxBatchSize ?? DEFAULTS.maxBatchSize;\n if (!Number.isInteger(maxBatchSize) || maxBatchSize < 1) {\n throw new RangeError(\n 'otlp transport: batching.maxBatchSize must be an integer >= 1',\n );\n }\n const cap = maxBufferedEvents ?? DEFAULTS.maxBufferedEvents;\n if (!Number.isInteger(cap) || cap < maxBatchSize) {\n throw new RangeError(\n 'otlp transport: maxBufferedEvents must be an integer >= maxBatchSize',\n );\n }\n const recBytes = maxRecordBytes ?? DEFAULTS.maxRecordBytes;\n if (!Number.isInteger(recBytes) || recBytes < 1) {\n throw new RangeError(\n 'otlp transport: maxRecordBytes must be an integer >= 1',\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\nexport function createOtlpTransport(options: OtlpTransportOptions): Transport {\n validateOptions(options);\n const allowInsecureLoopback = options.allowInsecureLoopback ?? false;\n // Throws at the consumer call site on a bad endpoint (off the hot path).\n validateEndpoint(options.endpoint, allowInsecureLoopback);\n\n const name = options.name ?? DEFAULTS.name;\n const maxBatchSize = options.batching?.maxBatchSize ?? DEFAULTS.maxBatchSize;\n const maxBatchAgeMs =\n options.batching?.maxBatchAgeMs ?? DEFAULTS.maxBatchAgeMs;\n\n const state: OtlpTransportState = {\n endpoint: options.endpoint,\n // Copy + freeze the headers so later consumer mutation cannot change\n // what we send, and so nothing outside delivery can read them.\n headers: Object.freeze({ ...(options.headers ?? {}) }),\n name,\n onInternalError: options.onInternalError ?? (() => undefined),\n maxBufferedEvents: options.maxBufferedEvents ?? DEFAULTS.maxBufferedEvents,\n maxRecordBytes: options.maxRecordBytes ?? DEFAULTS.maxRecordBytes,\n notified: freshNotifiedLedger(),\n // Placeholder; real batcher assigned below once the flush closure exists.\n batcher: undefined as unknown as Batcher,\n pagehideInstalled: false,\n pagehideUninstall: null,\n shutdownComplete: false,\n inFlight: new Set<Promise<void>>(),\n pending: 0,\n };\n\n state.batcher = createBatcher({\n maxBatchSize,\n maxBatchAgeMs,\n flush: (events) => {\n void flushBatch(state, events);\n },\n });\n\n return {\n name,\n send(event: LogEvent): void {\n if (state.shutdownComplete) return;\n ensurePagehide(state);\n\n // Per-record size guard (oversized_event) — measure the serialized\n // OTLP LogRecord, drop if it exceeds the budget. Never throws.\n try {\n const record = toLogRecord(event, Date.now());\n if (byteLength(JSON.stringify(record)) > state.maxRecordBytes) {\n notifyOnce(\n state,\n 'oversized_event',\n `dropped an event whose serialized record exceeds ${state.maxRecordBytes} bytes`,\n );\n return;\n }\n } catch (cause) {\n notifyOnce(\n state,\n 'serialize_failed',\n 'dropped an event that failed to serialize',\n cause,\n );\n return;\n }\n\n // Hard memory cap on undelivered (buffered + in-flight) events.\n if (state.pending >= state.maxBufferedEvents) {\n notifyOnce(\n state,\n 'buffer_overflow',\n `${state.maxBufferedEvents} events undelivered; dropping event`,\n );\n return;\n }\n state.pending += 1;\n state.batcher.push(event);\n },\n async flush(): Promise<void> {\n state.batcher.flush();\n await settleInFlight(state);\n },\n async shutdown(): Promise<void> {\n if (state.shutdownComplete) {\n await settleInFlight(state);\n return;\n }\n state.shutdownComplete = true;\n try {\n state.batcher.flush();\n await settleInFlight(state);\n } catch (cause) {\n notifyOnce(state, 'shutdown_failed', 'shutdown flush failed', cause);\n } finally {\n teardownPagehide(state);\n state.batcher.shutdown();\n }\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Batch delivery (never throws)\n// ---------------------------------------------------------------------------\n\nasync function flushBatch(\n state: OtlpTransportState,\n events: LogEvent[],\n): Promise<void> {\n const count = events.length;\n let body: string;\n try {\n body = encode(serializeBatch(events, Date.now()));\n } catch (cause) {\n // Fail-closed: drop the batch, but still release its pending budget.\n state.pending = Math.max(0, state.pending - count);\n notifyOnce(\n state,\n 'serialize_failed',\n 'dropped a batch that failed to serialize',\n cause,\n );\n return;\n }\n\n const promise = (async (): Promise<void> => {\n const result: DeliveryResult = await deliver(\n state.endpoint,\n state.headers,\n body,\n );\n mapResult(state, result);\n })().catch(() => {\n // Defensive: deliver() is contracted not to reject, but never let an\n // unexpected rejection escape into an unhandled rejection.\n });\n\n state.inFlight.add(promise);\n void promise.finally(() => {\n state.inFlight.delete(promise);\n // Release this batch's pending budget once delivery settles.\n state.pending = Math.max(0, state.pending - count);\n });\n}\n\nfunction mapResult(state: OtlpTransportState, result: DeliveryResult): void {\n switch (result.kind) {\n case 'delivered':\n return;\n case 'unavailable':\n notifyOnce(\n state,\n 'delivery_unavailable',\n 'fetch is unavailable; dropping batch',\n );\n return;\n case 'send_failed':\n notifyOnce(\n state,\n 'send_failed',\n `delivery failed (${result.detail})`,\n result.cause,\n );\n return;\n case 'partial_rejection':\n notifyOnce(\n state,\n 'partial_rejection',\n `backend rejected ${result.rejected} record(s)`,\n );\n return;\n }\n}\n\nasync function settleInFlight(state: OtlpTransportState): Promise<void> {\n // Snapshot: new deliveries triggered during await are not awaited here\n // (flush() is a point-in-time drain), matching beacon's flush semantics.\n await Promise.all([...state.inFlight]);\n}\n\n// ---------------------------------------------------------------------------\n// Lazy pagehide best-effort flush (Principle VII — nothing at Logger create)\n// ---------------------------------------------------------------------------\n\nfunction ensurePagehide(state: OtlpTransportState): void {\n if (state.pagehideInstalled) return;\n const target = globalThis as {\n addEventListener?: typeof globalThis.addEventListener;\n removeEventListener?: typeof globalThis.removeEventListener;\n };\n state.pagehideInstalled = true;\n if (typeof target.addEventListener !== 'function') {\n state.pagehideUninstall = null;\n return;\n }\n const handler = (): void => {\n // Best-effort: drain via the keepalive fetch path. Never blocks unload.\n state.batcher.flush();\n };\n target.addEventListener('pagehide', handler);\n state.pagehideUninstall = (): void => {\n if (typeof target.removeEventListener === 'function') {\n target.removeEventListener('pagehide', handler);\n }\n };\n}\n\nfunction teardownPagehide(state: OtlpTransportState): void {\n if (state.pagehideUninstall !== null) {\n state.pagehideUninstall();\n state.pagehideUninstall = null;\n }\n state.pagehideInstalled = false;\n}\n\nfunction byteLength(s: string): number {\n return new TextEncoder().encode(s).length;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/transport-otlp/batcher.ts","../src/transport-otlp/delivery.ts","../src/transport-otlp/endpoint-validation.ts","../src/transport-otlp/errors.ts","../src/transport-otlp/attributes.ts","../src/transport-otlp/resource.ts","../src/transport-otlp/otlp-serializer.ts","../src/transport-otlp/traceparent-header.ts","../src/transport-otlp/otlp-transport.ts"],"names":[],"mappings":"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","file":"transport-otlp.cjs","sourcesContent":["/**\n * Bounded batch buffer for the OTLP transport.\n *\n * A parallel copy of the `./transport-beacon` batcher state machine (the\n * boundary rule forbids importing across subpaths — TO-7). Behaviour\n * (research D7, data-model § Batcher):\n *\n * - `push(event)` appends to the in-memory buffer.\n * - First event in an empty batch with `maxBatchAgeMs` set arms a\n * one-shot age timer.\n * - Reaching `maxBatchSize` flushes synchronously at end of `push`.\n * - `flush()` drains the pending buffer through the consumer callback.\n * Empty buffer → no-op.\n * - `shutdown()` cancels the timer and inhibits further callbacks.\n *\n * Buffer is copied + cleared BEFORE the callback runs, so a re-entrant\n * `push` during the callback starts a fresh batch. A throwing callback is\n * swallowed (the batcher never throws from push/flush/shutdown). The hard\n * `maxBufferedEvents` cap is enforced by the transport BEFORE `push`, so\n * the batcher itself stays simple.\n */\n\nimport type { LogEvent } from '../api/types.js';\n\nexport interface BatcherOptions {\n maxBatchSize: number;\n maxBatchAgeMs?: number;\n flush: (events: LogEvent[]) => void;\n}\n\nexport interface Batcher {\n push(event: LogEvent): void;\n flush(): void;\n shutdown(): void;\n /** Current pending count — used by the transport's buffer-cap guard. */\n size(): number;\n}\n\nexport function createBatcher(opts: BatcherOptions): Batcher {\n const buffer: LogEvent[] = [];\n let maxAgeTimer: ReturnType<typeof setTimeout> | null = null;\n let flushCallback: ((events: LogEvent[]) => void) | null = opts.flush;\n\n const clearTimer = (): void => {\n if (maxAgeTimer !== null) {\n clearTimeout(maxAgeTimer);\n maxAgeTimer = null;\n }\n };\n\n const armTimer = (): void => {\n if (opts.maxBatchAgeMs === undefined) return;\n maxAgeTimer = setTimeout(() => {\n maxAgeTimer = null;\n doFlush();\n }, opts.maxBatchAgeMs);\n };\n\n const doFlush = (): void => {\n if (buffer.length === 0) return;\n if (flushCallback === null) {\n buffer.length = 0;\n clearTimer();\n return;\n }\n const events = buffer.slice();\n buffer.length = 0;\n clearTimer();\n try {\n flushCallback(events);\n } catch {\n // Swallow: the events were copied out; the consumer callback chose\n // to throw. Single-flush-attempt model (no re-push, no retry).\n }\n };\n\n return {\n push(event: LogEvent): void {\n if (flushCallback === null) return;\n buffer.push(event);\n if (buffer.length === 1 && opts.maxBatchAgeMs !== undefined) {\n armTimer();\n }\n if (buffer.length >= opts.maxBatchSize) {\n doFlush();\n }\n },\n flush(): void {\n doFlush();\n },\n shutdown(): void {\n clearTimer();\n buffer.length = 0;\n flushCallback = null;\n },\n size(): number {\n return buffer.length;\n },\n };\n}\n","/**\n * Delivery primitive for the OTLP transport: POST the OTLP/HTTP+JSON body\n * via `fetch` with `keepalive: true` (research D6, TO-2).\n *\n * `navigator.sendBeacon` is deliberately NOT used — it cannot set the\n * custom auth + `Content-Type` headers OTLP backends require (FR-004).\n * `keepalive` preserves best-effort delivery during page unload.\n *\n * `deliver(...)` NEVER throws or rejects: every outcome (2xx, 2xx with an\n * OTLP partial-success rejection, non-2xx, network reject, missing\n * `fetch`) is reduced to a `DeliveryResult` for the caller to map onto a\n * rate-limited notice. There is no retry (research D7).\n *\n * Boundary discipline (TO-7): zero `src/` imports; zero vendor imports.\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md` OP-1;\n * `contracts/transport-otlp-public-api.md` TO-2/TO-4.\n */\n\n/** Outcome of a single delivery attempt. Never an exception. */\nexport type DeliveryResult =\n | { kind: 'delivered' }\n | { kind: 'unavailable' }\n | { kind: 'send_failed'; detail: string; cause?: unknown }\n | { kind: 'partial_rejection'; rejected: number };\n\n/**\n * POST `body` to `endpoint` with `keepalive: true`, merging the caller's\n * static `headers` (e.g. auth) over the mandatory `content-type`.\n * `credentials: 'same-origin'` keeps cookies from leaking cross-origin by\n * default (Principle IV); auth travels only in the explicit headers.\n */\nexport async function deliver(\n endpoint: string,\n headers: Readonly<Record<string, string>>,\n body: string,\n): Promise<DeliveryResult> {\n const fetchFn = (globalThis as { fetch?: typeof fetch }).fetch;\n if (typeof fetchFn !== 'function') {\n return { kind: 'unavailable' };\n }\n\n let response: Response;\n try {\n response = await fetchFn(endpoint, {\n method: 'POST',\n body,\n headers: { 'content-type': 'application/json', ...headers },\n keepalive: true,\n credentials: 'same-origin',\n });\n } catch (cause) {\n return { kind: 'send_failed', detail: 'fetch rejected', cause };\n }\n\n if (!response.ok) {\n return { kind: 'send_failed', detail: `HTTP ${response.status}` };\n }\n\n // 2xx — check for an OTLP partial-success rejection in the body. A\n // missing / non-JSON / unexpected body is treated as full success.\n const rejected = await readRejectedCount(response);\n if (rejected > 0) {\n return { kind: 'partial_rejection', rejected };\n }\n return { kind: 'delivered' };\n}\n\nasync function readRejectedCount(response: Response): Promise<number> {\n try {\n if (typeof response.json !== 'function') return 0;\n const parsed = (await response.json()) as unknown;\n if (typeof parsed !== 'object' || parsed === null) return 0;\n const partial = (parsed as { partialSuccess?: unknown }).partialSuccess;\n if (typeof partial !== 'object' || partial === null) return 0;\n const raw = (partial as { rejectedLogRecords?: unknown })\n .rejectedLogRecords;\n // OTLP encodes int64 as a string in JSON, but tolerate a number too.\n const n = typeof raw === 'string' ? Number(raw) : raw;\n return typeof n === 'number' && Number.isFinite(n) && n > 0 ? n : 0;\n } catch {\n // A body that cannot be read/parsed does not turn a 2xx into a failure.\n return 0;\n }\n}\n","/**\n * Construction-time endpoint validation for `createOtlpTransport`.\n *\n * Locked behaviour (mirrors `./transport-beacon`, TO-5 / research D8):\n * - HTTPS endpoints always pass.\n * - HTTP endpoints pass IFF `allowInsecureLoopback === true` AND the\n * parsed URL's hostname is in `{ localhost, 127.0.0.1, [::1] }`.\n * - Every other case throws a typed error at construction time, before\n * any logger derives the runtime and before any listener is attached.\n *\n * Pure and side-effect-free: parses via `new URL(...)` and inspects the\n * result. MUST NOT read ambient state (no `process.env`, no\n * `window.location`) and MUST NOT read `allowInsecureLoopback` from\n * anywhere except the argument the caller passed.\n *\n * The consumer supplies the FULL OTLP logs URL (e.g.\n * `https://otlp.example.com/v1/logs`); the transport appends nothing.\n *\n * Specs: `specs/007-transport-otlp/contracts/transport-otlp-public-api.md`\n * TO-5; `data-model.md` § validation rules.\n */\n\n/**\n * Hostnames permitted under `allowInsecureLoopback: true`. WHATWG URL\n * normalises `http://[::1]` to hostname `[::1]` (with brackets).\n */\nconst LOOPBACK_HOSTS: ReadonlySet<string> = new Set([\n 'localhost',\n '127.0.0.1',\n '[::1]',\n]);\n\n/**\n * Validate a consumer-supplied `endpoint`. Returns the parsed `URL` on\n * success; throws on every violation. The thrown error's `.message` names\n * the violated constraint and the offending endpoint string.\n */\nexport function validateEndpoint(\n endpoint: unknown,\n allowInsecureLoopback: boolean,\n): URL {\n if (typeof endpoint !== 'string') {\n throw new TypeError(\n `otlp transport: endpoint must be a string, got ${typeName(endpoint)}`,\n );\n }\n\n let parsed: URL;\n try {\n parsed = new URL(endpoint);\n } catch {\n throw new TypeError(`otlp transport: invalid endpoint URL: '${endpoint}'`);\n }\n\n if (parsed.protocol === 'https:') {\n return parsed;\n }\n\n if (parsed.protocol === 'http:') {\n if (!allowInsecureLoopback) {\n throw new Error(\n `otlp transport refuses non-HTTPS endpoint '${endpoint}'`,\n );\n }\n if (!LOOPBACK_HOSTS.has(parsed.hostname)) {\n throw new Error(\n `otlp transport: allowInsecureLoopback permits only ` +\n `localhost / 127.0.0.1 / [::1]; got '${parsed.hostname}' ` +\n `in '${endpoint}'`,\n );\n }\n return parsed;\n }\n\n throw new Error(\n `otlp transport refuses non-HTTPS endpoint '${endpoint}' ` +\n `(scheme '${parsed.protocol}' is not permitted)`,\n );\n}\n\nfunction typeName(value: unknown): string {\n if (value === null) return 'null';\n return typeof value;\n}\n","/**\n * Subpath-owned diagnostic error class + rate-limited notice helper for\n * the OTLP transport.\n *\n * Drop/diagnostic notices fired by the OTLP transport are `OtlpError`\n * instances (a subclass of `Error`) owned by this subpath — by-convention\n * compatible with the core's `PackageError` shape (`.code`,\n * `.transportName`, optional `.cause`) but with NO runtime import from\n * `src/internal/**` (TO-7). This module is INTERNAL to the subpath;\n * `src/transport-otlp/index.ts` does not re-export it. The\n * `onInternalError` hook receives the instance typed as `Error`.\n *\n * **Security (FR-009 / TO-6)**: notice messages MUST NEVER include any\n * configured request-header value. The helpers here only ever build\n * messages from the failure code, the transport name, and an optional\n * non-secret detail string supplied by the caller. Callers MUST NOT pass\n * header values into `detail`.\n *\n * Specs: `specs/007-transport-otlp/data-model.md` § OtlpFailureCode;\n * `contracts/transport-otlp-public-api.md` TO-4/TO-6.\n */\n\n/**\n * Documented `OtlpError.code` values — one per failure class. Surfaced to\n * the consumer as `err.code` on the `Error`-shaped `onInternalError`\n * argument. Each is rate-limited to one notice per class per transport\n * instance per session.\n */\nexport type OtlpFailureCode =\n | 'oversized_event'\n | 'buffer_overflow'\n | 'delivery_unavailable'\n | 'send_failed'\n | 'partial_rejection'\n | 'serialize_failed'\n | 'shutdown_failed';\n\n/**\n * Subclass of `Error` carrying a discriminating `.code`, the originating\n * transport's `.transportName`, and an optional `.cause` chain. `.name`\n * is `'OtlpError'`. `.cause` follows the ES2022 convention (absent when\n * not provided rather than `undefined`).\n */\nexport class OtlpError extends Error {\n readonly code: OtlpFailureCode;\n readonly transportName: string;\n declare readonly cause?: unknown;\n\n constructor(\n code: OtlpFailureCode,\n transportName: string,\n message: string,\n cause?: unknown,\n ) {\n super(message);\n this.name = 'OtlpError';\n this.code = code;\n this.transportName = transportName;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', {\n value: cause,\n enumerable: true,\n writable: false,\n configurable: false,\n });\n }\n }\n}\n\n/** Type guard for `OtlpError` instances. */\nexport function isOtlpError(value: unknown): value is OtlpError {\n return value instanceof OtlpError;\n}\n\n/**\n * Minimal shape the `notifyOnce` helper needs: the transport's name, the\n * consumer error sink, and a per-code \"already notified\" ledger. The\n * concrete `OtlpTransportState` (data-model) satisfies this.\n */\nexport interface NotifyContext {\n readonly name: string;\n readonly onInternalError: (err: Error) => void;\n readonly notified: Record<OtlpFailureCode, boolean>;\n}\n\n/**\n * Emit at most ONE diagnostic notice per failure `code` per context\n * (instance) per session (FR-010). Subsequent calls with the same code are\n * silently suppressed. The `onInternalError` callback is invoked inside a\n * try/catch so a throwing consumer handler can never propagate back into\n * the transport's hot path (Principle II).\n *\n * `detail` is an optional NON-SECRET human string (e.g. an HTTP status).\n * Callers MUST NOT pass any configured header/secret value here.\n */\nexport function notifyOnce(\n ctx: NotifyContext,\n code: OtlpFailureCode,\n message: string,\n cause?: unknown,\n): void {\n if (ctx.notified[code]) return;\n ctx.notified[code] = true;\n const err = new OtlpError(\n code,\n ctx.name,\n `otlp transport '${ctx.name}': ${message}`,\n cause,\n );\n try {\n ctx.onInternalError(err);\n } catch {\n // A throwing consumer error handler must never reach the caller.\n }\n}\n\n/** Build the per-instance \"already notified\" ledger with all flags false. */\nexport function freshNotifiedLedger(): Record<OtlpFailureCode, boolean> {\n return {\n oversized_event: false,\n buffer_overflow: false,\n delivery_unavailable: false,\n send_failed: false,\n partial_rejection: false,\n serialize_failed: false,\n shutdown_failed: false,\n };\n}\n","/**\n * Pure `AttributeValue` → OTLP `AnyValue` encoder + the shared OTLP-JSON\n * value types used across the subpath.\n *\n * The OTLP/HTTP+JSON value model is hand-encoded here with ZERO runtime\n * dependencies and no `@opentelemetry/*` import (research D1, TO-7). The\n * `AttributeValue` union (string | number | boolean | null | array |\n * object) maps 1:1 onto OTLP `AnyValue` (OP-5).\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md` OP-5.\n */\n\nimport type { AttributeValue } from '../api/types.js';\n\n/**\n * OTLP `AnyValue` (JSON encoding). `null` → `{}` (an unset value) since\n * OTLP has no explicit null. A non-finite or otherwise non-integer number\n * is a `doubleValue`; an integer-safe number is an `intValue` (string, per\n * the uint64/sint64-as-string rule).\n */\nexport type AnyValue =\n | { stringValue: string }\n | { boolValue: boolean }\n | { intValue: string }\n | { doubleValue: number }\n | { arrayValue: { values: AnyValue[] } }\n | { kvlistValue: { values: KeyValue[] } }\n // biome-ignore lint/complexity/noBannedTypes: OTLP represents an unset/null value as an empty AnyValue object.\n | {};\n\n/** OTLP `KeyValue`. */\nexport interface KeyValue {\n key: string;\n value: AnyValue;\n}\n\n/** Encode a single sanitized `AttributeValue` as an OTLP `AnyValue`. */\nexport function toAnyValue(value: AttributeValue): AnyValue {\n if (value === null) {\n return {};\n }\n switch (typeof value) {\n case 'string':\n return { stringValue: value };\n case 'boolean':\n return { boolValue: value };\n case 'number':\n return Number.isInteger(value)\n ? { intValue: String(value) }\n : { doubleValue: value };\n default:\n break;\n }\n if (Array.isArray(value)) {\n return { arrayValue: { values: value.map(toAnyValue) } };\n }\n // Remaining case: a plain `{ [key: string]: AttributeValue }` object.\n return { kvlistValue: { values: toKeyValues(value) } };\n}\n\n/**\n * Encode a record of sanitized attributes as an OTLP `KeyValue[]`,\n * optionally prefixing each key (used to namespace merged context\n * attributes under `context.` — OP-4).\n */\nexport function toKeyValues(\n record: Readonly<Record<string, AttributeValue>>,\n keyPrefix = '',\n): KeyValue[] {\n const out: KeyValue[] = [];\n for (const key of Object.keys(record)) {\n const value = record[key];\n if (value === undefined) continue;\n out.push({ key: keyPrefix + key, value: toAnyValue(value) });\n }\n return out;\n}\n","/**\n * Map SafeSignal runtime-global identity to an OTLP `Resource` (OP-2 / D3).\n *\n * The `Resource` carries only identity that is constant across a batch\n * from one configured transport — `service.name`, `service.version`,\n * `deployment.environment` (standard OTel semantic-convention attributes\n * every OTLP backend understands). The federated `module.*` identity is\n * **per-logger** (it can differ between events in the same batch via\n * `withContext`), so it is attributed per-`LogRecord` (see\n * `otlp-serializer.ts`), not on the shared Resource — preserving correct\n * origin attribution (Principle VI).\n *\n * Only present fields are emitted (no empty/`null` keys). Pure and\n * dependency-free.\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md` OP-2.\n */\n\nimport type { LogContext } from '../api/types.js';\n\nimport type { KeyValue } from './attributes.js';\n\nexport interface OtlpResource {\n attributes: KeyValue[];\n}\n\n/**\n * Build the OTLP `Resource` for a batch from a representative context\n * (the batch's first event). `service.*` / `deployment.environment` are\n * runtime-global, so any event in the batch is representative.\n */\nexport function buildResource(context: LogContext): OtlpResource {\n const attributes: KeyValue[] = [];\n\n const push = (key: string, value: string | undefined): void => {\n if (typeof value === 'string' && value.length > 0) {\n attributes.push({ key, value: { stringValue: value } });\n }\n };\n\n push('service.name', context.application?.name);\n push('service.version', context.application?.version);\n push('deployment.environment', context.environment);\n\n return { attributes };\n}\n","/**\n * Pure `LogEvent[]` → OTLP/HTTP+JSON logs payload serializer (OP-1..OP-6).\n *\n * This is the heart of the subpath. It hand-builds the OTLP logs JSON\n * shape with ZERO runtime dependencies and NO `@opentelemetry/*` import\n * (research D1, TO-7): severity numbers are literal constants matching the\n * OTel `SeverityNumber` ranges (and the in-repo\n * `src/internal/telemetry/otel/mapping.ts` table), reused conceptually but\n * never imported.\n *\n * **Encoding seam (FR-015)**: `serializeBatch(...)` builds the encoder-\n * neutral `OtlpLogsRequest` object; `encode(...)` turns it into the wire\n * body. Today the only encoder is JSON; a future protobuf encoder slots in\n * behind `encode(...)` without changing the object model or the public\n * surface.\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md`.\n */\n\nimport type { LogContext, LogEvent, LogLevel } from '../api/types.js';\n\nimport { type AnyValue, type KeyValue, toKeyValues } from './attributes.js';\nimport { buildResource } from './resource.js';\n\n/** Constant instrumentation-scope name for every emitted ScopeLogs. */\nexport const SCOPE_NAME = '@tallyrow/safesignal';\n\n/**\n * OTLP `SeverityNumber` base value per SafeSignal level (OP-3 / D2). These\n * are the canonical OTLP range bases (DEBUG 5, INFO 9, WARN 13, ERROR 17)\n * and match `LEVEL_TO_SEVERITY` in the internal OTel seam.\n */\nconst LEVEL_TO_SEVERITY_NUMBER: Readonly<Record<LogLevel, number>> = {\n debug: 5,\n info: 9,\n warn: 13,\n error: 17,\n};\n\nconst LEVEL_TO_SEVERITY_TEXT: Readonly<Record<LogLevel, string>> = {\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n};\n\n// ---------------------------------------------------------------------------\n// OTLP wire shapes (JSON encoding)\n// ---------------------------------------------------------------------------\n\nexport interface OtlpLogRecord {\n timeUnixNano: string;\n observedTimeUnixNano: string;\n severityNumber: number;\n severityText: string;\n body: AnyValue;\n attributes: KeyValue[];\n /** W3C trace correlation — present only when `event.context.trace` is set. */\n traceId?: string;\n spanId?: string;\n flags?: number;\n}\n\nexport interface OtlpScopeLogs {\n scope: { name: string };\n logRecords: OtlpLogRecord[];\n}\n\nexport interface OtlpResourceLogs {\n resource: { attributes: KeyValue[] };\n scopeLogs: OtlpScopeLogs[];\n}\n\nexport interface OtlpLogsRequest {\n resourceLogs: OtlpResourceLogs[];\n}\n\n// ---------------------------------------------------------------------------\n// Mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Convert one `LogEvent` to an OTLP `LogRecord`. Never mutates the input\n * (T-S4). Per-record attributes are: event attributes, then merged context\n * attributes under a `context.` prefix, then per-logger `module.*`\n * identity, then `exception.*` when an error is present (OP-4).\n *\n * `fallbackTimeMs` is a single resolved time used when `event.timestamp`\n * is unparseable, so the mapping never throws on a bad timestamp (OP-3).\n */\nexport function toLogRecord(\n event: LogEvent,\n fallbackTimeMs: number,\n): OtlpLogRecord {\n const ms = toEpochMs(event.timestamp, fallbackTimeMs);\n const nano = String(ms * 1_000_000);\n\n const attributes: KeyValue[] = toKeyValues(event.attributes);\n\n const context = event.context;\n if (context.attributes !== undefined) {\n attributes.push(...toKeyValues(context.attributes, 'context.'));\n }\n pushModuleIdentity(attributes, context);\n pushException(attributes, event);\n\n const record: OtlpLogRecord = {\n timeUnixNano: nano,\n observedTimeUnixNano: nano,\n severityNumber: LEVEL_TO_SEVERITY_NUMBER[event.level],\n severityText: LEVEL_TO_SEVERITY_TEXT[event.level],\n body: { stringValue: event.message },\n attributes,\n };\n\n // W3C trace correlation → OTLP standard top-level fields (OP/OT contracts).\n // The structured ids are already lowercase-hex (validated upstream), so they\n // are the OTLP/JSON encoding as-is — no base64, no @opentelemetry import.\n const trace = context.trace;\n if (trace !== undefined) {\n record.traceId = trace.traceId;\n record.spanId = trace.spanId;\n if (trace.traceFlags !== undefined) {\n record.flags = trace.traceFlags;\n }\n }\n\n return record;\n}\n\n/**\n * Build the encoder-neutral OTLP logs request object for a batch. The\n * Resource is derived from the first event's runtime-global identity; if\n * the batch is empty, an empty Resource is used.\n */\nexport function serializeBatch(\n batch: ReadonlyArray<LogEvent>,\n fallbackTimeMs: number,\n): OtlpLogsRequest {\n const first = batch[0];\n const resource = buildResource(first ? first.context : ({} as LogContext));\n const logRecords = batch.map((e) => toLogRecord(e, fallbackTimeMs));\n return {\n resourceLogs: [\n {\n resource,\n scopeLogs: [{ scope: { name: SCOPE_NAME }, logRecords }],\n },\n ],\n };\n}\n\n/**\n * Encoding seam (FR-015). Turns the request object into the wire body.\n * The only encoding in this feature is JSON; protobuf is a roadmap\n * follow-up that slots in here without touching callers.\n */\nexport function encode(request: OtlpLogsRequest): string {\n return JSON.stringify(request);\n}\n\n/**\n * Convenience: build + JSON-encode a batch in one call. Pure; never\n * mutates inputs.\n */\nexport function serializeOtlpJson(\n batch: ReadonlyArray<LogEvent>,\n fallbackTimeMs: number,\n): string {\n return encode(serializeBatch(batch, fallbackTimeMs));\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction toEpochMs(iso: string, fallbackMs: number): number {\n const parsed = Date.parse(iso);\n return Number.isFinite(parsed) ? parsed : fallbackMs;\n}\n\nfunction pushModuleIdentity(out: KeyValue[], context: LogContext): void {\n const mod = context.module;\n if (mod === undefined) return;\n if (typeof mod.name === 'string' && mod.name.length > 0) {\n out.push({ key: 'module.name', value: { stringValue: mod.name } });\n }\n if (typeof mod.version === 'string' && mod.version.length > 0) {\n out.push({ key: 'module.version', value: { stringValue: mod.version } });\n }\n}\n\nfunction pushException(out: KeyValue[], event: LogEvent): void {\n const err = event.error;\n if (err === undefined) return;\n out.push({ key: 'exception.type', value: { stringValue: err.name } });\n out.push({ key: 'exception.message', value: { stringValue: err.message } });\n if (typeof err.stack === 'string' && err.stack.length > 0) {\n out.push({\n key: 'exception.stacktrace',\n value: { stringValue: err.stack },\n });\n }\n}\n","/**\n * Outbound W3C `traceparent` header injection for the `./transport-otlp`\n * delivery path (Feature 009).\n *\n * When `injectTraceparent` is enabled, a delivery request whose batch all\n * belongs to ONE valid trace gets a standard W3C `traceparent` (and, when\n * uniform, `tracestate`) request header so the ingest request is joinable to\n * its trace. SafeSignal is **carry-only**: this reads the already-normalized\n * `event.context.trace` (Feature 008 validates it once per emit, before any\n * transport sees the event) and never mints ids.\n *\n * Policy (homogeneous-only, fail-closed — contracts TI-3..TI-6, research D3/D4):\n * - Per-event key = `none` when `context.trace` is absent OR structurally\n * invalid (defensive guard for events that reach the transport without\n * passing emit-time normalization), else the full `traceparent` string\n * (so differing flags ⇒ differing keys ⇒ not homogeneous).\n * - Inject `traceparent` iff the batch is non-empty AND every key is the\n * same non-`none` value. Empty / trace-less / heterogeneous ⇒ no header.\n * - Inject `tracestate` iff `traceparent` is injected AND every event shares\n * the same defined `traceState` within `MAX_TRACESTATE_LEN`; else omit it\n * while keeping `traceparent` (optional part dropped, valid ids kept).\n * - Consumer `options.headers` win on any collision (spread last), so the\n * injected header can never overwrite/expose an auth/secret value.\n *\n * Boundary discipline (TO-7): the only import is a **type-only** import from\n * `../api/types.js`. No `../trace/`, no `@opentelemetry/*`. Pure; never throws.\n *\n * Specs: `specs/009-traceparent-injection/contracts/traceparent-injection.md`.\n */\n\nimport type { LogEvent, TraceContext } from '../api/types.js';\n\n/** Max `tracestate` length (W3C caps the header at 512 chars), mirrored here. */\nconst MAX_TRACESTATE_LEN = 512;\n\nconst TRACE_ID_RE = /^[0-9a-f]{32}$/;\nconst SPAN_ID_RE = /^[0-9a-f]{16}$/;\nconst ALL_ZERO_TRACE_ID = '0'.repeat(32);\nconst ALL_ZERO_SPAN_ID = '0'.repeat(16);\n\n/** Outcome of evaluating one delivery batch. */\nexport type BatchTraceparentDecision =\n | { inject: false }\n | { inject: true; traceparent: string; tracestate?: string };\n\ninterface ResolvedTrace {\n /** `'none'`, or the `traceparent` string (which also uniquely keys the trace). */\n readonly key: string;\n /** The `traceparent` string when valid, else `null`. */\n readonly traceparent: string | null;\n /** A bounded, non-empty `traceState`, else `null`. */\n readonly traceState: string | null;\n}\n\nconst NONE: ResolvedTrace = {\n key: 'none',\n traceparent: null,\n traceState: null,\n};\n\nfunction hasValidIds(trace: TraceContext): boolean {\n return (\n typeof trace.traceId === 'string' &&\n TRACE_ID_RE.test(trace.traceId) &&\n trace.traceId !== ALL_ZERO_TRACE_ID &&\n typeof trace.spanId === 'string' &&\n SPAN_ID_RE.test(trace.spanId) &&\n trace.spanId !== ALL_ZERO_SPAN_ID\n );\n}\n\nfunction flagsHex(traceFlags: number | undefined): string {\n const n =\n typeof traceFlags === 'number' &&\n Number.isInteger(traceFlags) &&\n traceFlags >= 0 &&\n traceFlags <= 255\n ? traceFlags\n : 0;\n return n.toString(16).padStart(2, '0');\n}\n\nfunction resolve(event: LogEvent): ResolvedTrace {\n const trace = event.context.trace;\n if (trace === undefined || !hasValidIds(trace)) {\n return NONE;\n }\n const traceparent = `00-${trace.traceId}-${trace.spanId}-${flagsHex(\n trace.traceFlags,\n )}`;\n const traceState =\n typeof trace.traceState === 'string' &&\n trace.traceState.length > 0 &&\n trace.traceState.length <= MAX_TRACESTATE_LEN\n ? trace.traceState\n : null;\n return { key: traceparent, traceparent, traceState };\n}\n\n/**\n * Decide whether a delivery batch warrants a `traceparent`/`tracestate`\n * header. Pure; never throws.\n */\nexport function decideBatchTraceparent(\n events: ReadonlyArray<LogEvent>,\n): BatchTraceparentDecision {\n if (events.length === 0) {\n return { inject: false };\n }\n const first = resolve(events[0] as LogEvent);\n if (first.key === 'none') {\n return { inject: false };\n }\n\n let tracestateUniform = true;\n for (let i = 1; i < events.length; i += 1) {\n const r = resolve(events[i] as LogEvent);\n if (r.key !== first.key) {\n return { inject: false }; // heterogeneous (or one is `none`)\n }\n if (r.traceState !== first.traceState) {\n tracestateUniform = false;\n }\n }\n\n const traceparent = first.traceparent as string;\n if (tracestateUniform && first.traceState !== null) {\n return { inject: true, traceparent, tracestate: first.traceState };\n }\n return { inject: true, traceparent };\n}\n\n/**\n * Build the per-request header map. When injection is disabled or the batch is\n * not homogeneous, returns the SAME `base` reference (no allocation,\n * byte-identical request). Otherwise returns a new map with `traceparent`\n * (and `tracestate`) UNDER the consumer `base` headers, so `base` always wins\n * on collision (TI-6). Never mutates `base`; never throws.\n */\nexport function buildRequestHeaders(\n base: Readonly<Record<string, string>>,\n events: ReadonlyArray<LogEvent>,\n enabled: boolean,\n): Readonly<Record<string, string>> {\n if (!enabled) {\n return base;\n }\n const decision = decideBatchTraceparent(events);\n if (!decision.inject) {\n return base;\n }\n const injected: Record<string, string> = {\n traceparent: decision.traceparent,\n };\n if (decision.tracestate !== undefined) {\n injected.tracestate = decision.tracestate;\n }\n return { ...injected, ...base };\n}\n","/**\n * `createOtlpTransport` — factory for the `./transport-otlp` subpath.\n *\n * Composes the subpath primitives into a `Transport` that delivers\n * fully-processed `LogEvent`s to an OTLP logs backend as OTLP/HTTP+JSON,\n * batched, fire-and-forget (no retry), fail-closed.\n *\n * Delivery policy (research D6/D7, contracts TO-2..TO-8):\n *\n * send(event)\n * ├── if shutdownComplete: no-op\n * ├── lazily install the pagehide best-effort flush (first send)\n * ├── if serialized record > maxRecordBytes → oversized_event drop\n * ├── if buffered >= maxBufferedEvents → buffer_overflow drop\n * └── batcher.push(event) // flush on size / age\n *\n * flush(batch) // batcher callback\n * ├── serialize(batch) (fail-closed: serialize_failed → drop)\n * └── deliver(endpoint, headers, body) // fetch keepalive, never throws\n * ├── delivered ─────────────────── done\n * ├── unavailable ───────────────── delivery_unavailable notice\n * ├── send_failed ───────────────── send_failed notice (+cause)\n * └── partial_rejection ─────────── partial_rejection notice\n *\n * Every notice is rate-limited to one per failure class per instance per\n * session and NEVER carries a configured header/secret value (FR-009).\n * `send`/`flush`/`shutdown` NEVER throw or reject to the caller; only\n * construction-time validation throws, at the consumer's call site.\n *\n * Boundary discipline (TO-7): the only `src/` import is a type-only import\n * from `'../api/types.js'`. No `@opentelemetry/*` and no\n * `../internal/telemetry/otel/` import — the payload is hand-serialized.\n *\n * Specs: `specs/007-transport-otlp/contracts/*`, `data-model.md`.\n */\n\nimport type { LogEvent, Transport } from '../api/types.js';\n\nimport { type Batcher, createBatcher } from './batcher.js';\nimport { type DeliveryResult, deliver } from './delivery.js';\nimport { validateEndpoint } from './endpoint-validation.js';\nimport {\n freshNotifiedLedger,\n type NotifyContext,\n notifyOnce,\n type OtlpFailureCode,\n} from './errors.js';\nimport { encode, serializeBatch, toLogRecord } from './otlp-serializer.js';\nimport { buildRequestHeaders } from './traceparent-header.js';\n\n// ---------------------------------------------------------------------------\n// Public options shape (data-model.md § OtlpTransportOptions)\n// ---------------------------------------------------------------------------\n\nexport interface OtlpTransportOptions {\n /** Full OTLP logs endpoint URL (e.g. `https://otlp.example.com/v1/logs`). */\n endpoint: string;\n /** Static request headers (e.g. auth). Sent only on the wire. */\n headers?: Record<string, string>;\n /** Batch flush triggers. */\n batching?: {\n maxBatchSize: number;\n maxBatchAgeMs?: number;\n };\n /** Hard cap on buffered events; over-cap events are dropped. Default 1000. */\n maxBufferedEvents?: number;\n /** Per-record size guard in bytes; larger records are dropped. Default 64 KiB. */\n maxRecordBytes?: number;\n /** Stable diagnostic identifier (`Transport.name`). Default `'otlp'`. */\n name?: string;\n /** Permit `http://` localhost/127.0.0.1/[::1] only. Default false. */\n allowInsecureLoopback?: boolean;\n /**\n * When `true`, set a W3C `traceparent` (and `tracestate`) request header on a\n * delivery request whose batch all shares one valid trace context. Off by\n * default; homogeneous-only and fail-closed — a mixed, trace-less, or empty\n * batch sets no header, and the event payload is unchanged either way.\n * Carry-only: built from each event's existing `context.trace`; no ids are\n * minted. See `specs/009-traceparent-injection/`.\n */\n injectTraceparent?: boolean;\n /** Receives rate-limited diagnostic notices. Never carries header values. */\n onInternalError?: (err: Error) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal per-instance state (data-model.md § OtlpTransportState)\n// ---------------------------------------------------------------------------\n\ninterface OtlpTransportState extends NotifyContext {\n readonly endpoint: string;\n readonly headers: Readonly<Record<string, string>>;\n readonly name: string;\n readonly onInternalError: (err: Error) => void;\n readonly maxBufferedEvents: number;\n readonly maxRecordBytes: number;\n readonly injectTraceparent: boolean;\n readonly notified: Record<OtlpFailureCode, boolean>;\n batcher: Batcher;\n pagehideInstalled: boolean;\n pagehideUninstall: (() => void) | null;\n shutdownComplete: boolean;\n inFlight: Set<Promise<void>>;\n /**\n * Events accepted but not yet delivered (buffered in the batcher + in\n * flight). The true memory bound in this no-retry design: incremented on\n * accept, decremented when a batch's delivery settles. Capped at\n * `maxBufferedEvents` so a slow/failing backend cannot grow memory\n * unboundedly.\n */\n pending: number;\n}\n\nconst DEFAULTS = {\n maxBatchSize: 20,\n maxBatchAgeMs: 5000,\n maxBufferedEvents: 1000,\n maxRecordBytes: 65536,\n name: 'otlp',\n} as const;\n\n// ---------------------------------------------------------------------------\n// Construction-time validation (TO-2)\n// ---------------------------------------------------------------------------\n\nfunction validateOptions(options: OtlpTransportOptions): void {\n if (typeof options !== 'object' || options === null) {\n throw new TypeError('otlp transport: options must be a non-null object');\n }\n const { headers, batching, maxBufferedEvents, maxRecordBytes } = options;\n\n if (\n options.injectTraceparent !== undefined &&\n typeof options.injectTraceparent !== 'boolean'\n ) {\n throw new TypeError('otlp transport: injectTraceparent must be a boolean');\n }\n\n if (headers !== undefined) {\n if (typeof headers !== 'object' || headers === null) {\n throw new TypeError('otlp transport: headers must be an object');\n }\n for (const key of Object.keys(headers)) {\n if (typeof headers[key] !== 'string') {\n throw new TypeError(\n `otlp transport: header '${key}' must be a string value`,\n );\n }\n }\n }\n\n const maxBatchSize = batching?.maxBatchSize ?? DEFAULTS.maxBatchSize;\n if (!Number.isInteger(maxBatchSize) || maxBatchSize < 1) {\n throw new RangeError(\n 'otlp transport: batching.maxBatchSize must be an integer >= 1',\n );\n }\n const cap = maxBufferedEvents ?? DEFAULTS.maxBufferedEvents;\n if (!Number.isInteger(cap) || cap < maxBatchSize) {\n throw new RangeError(\n 'otlp transport: maxBufferedEvents must be an integer >= maxBatchSize',\n );\n }\n const recBytes = maxRecordBytes ?? DEFAULTS.maxRecordBytes;\n if (!Number.isInteger(recBytes) || recBytes < 1) {\n throw new RangeError(\n 'otlp transport: maxRecordBytes must be an integer >= 1',\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\nexport function createOtlpTransport(options: OtlpTransportOptions): Transport {\n validateOptions(options);\n const allowInsecureLoopback = options.allowInsecureLoopback ?? false;\n // Throws at the consumer call site on a bad endpoint (off the hot path).\n validateEndpoint(options.endpoint, allowInsecureLoopback);\n\n const name = options.name ?? DEFAULTS.name;\n const maxBatchSize = options.batching?.maxBatchSize ?? DEFAULTS.maxBatchSize;\n const maxBatchAgeMs =\n options.batching?.maxBatchAgeMs ?? DEFAULTS.maxBatchAgeMs;\n\n const state: OtlpTransportState = {\n endpoint: options.endpoint,\n // Copy + freeze the headers so later consumer mutation cannot change\n // what we send, and so nothing outside delivery can read them.\n headers: Object.freeze({ ...(options.headers ?? {}) }),\n name,\n onInternalError: options.onInternalError ?? (() => undefined),\n maxBufferedEvents: options.maxBufferedEvents ?? DEFAULTS.maxBufferedEvents,\n maxRecordBytes: options.maxRecordBytes ?? DEFAULTS.maxRecordBytes,\n injectTraceparent: options.injectTraceparent ?? false,\n notified: freshNotifiedLedger(),\n // Placeholder; real batcher assigned below once the flush closure exists.\n batcher: undefined as unknown as Batcher,\n pagehideInstalled: false,\n pagehideUninstall: null,\n shutdownComplete: false,\n inFlight: new Set<Promise<void>>(),\n pending: 0,\n };\n\n state.batcher = createBatcher({\n maxBatchSize,\n maxBatchAgeMs,\n flush: (events) => {\n void flushBatch(state, events);\n },\n });\n\n return {\n name,\n send(event: LogEvent): void {\n if (state.shutdownComplete) return;\n ensurePagehide(state);\n\n // Per-record size guard (oversized_event) — measure the serialized\n // OTLP LogRecord, drop if it exceeds the budget. Never throws.\n try {\n const record = toLogRecord(event, Date.now());\n if (byteLength(JSON.stringify(record)) > state.maxRecordBytes) {\n notifyOnce(\n state,\n 'oversized_event',\n `dropped an event whose serialized record exceeds ${state.maxRecordBytes} bytes`,\n );\n return;\n }\n } catch (cause) {\n notifyOnce(\n state,\n 'serialize_failed',\n 'dropped an event that failed to serialize',\n cause,\n );\n return;\n }\n\n // Hard memory cap on undelivered (buffered + in-flight) events.\n if (state.pending >= state.maxBufferedEvents) {\n notifyOnce(\n state,\n 'buffer_overflow',\n `${state.maxBufferedEvents} events undelivered; dropping event`,\n );\n return;\n }\n state.pending += 1;\n state.batcher.push(event);\n },\n async flush(): Promise<void> {\n state.batcher.flush();\n await settleInFlight(state);\n },\n async shutdown(): Promise<void> {\n if (state.shutdownComplete) {\n await settleInFlight(state);\n return;\n }\n state.shutdownComplete = true;\n try {\n state.batcher.flush();\n await settleInFlight(state);\n } catch (cause) {\n notifyOnce(state, 'shutdown_failed', 'shutdown flush failed', cause);\n } finally {\n teardownPagehide(state);\n state.batcher.shutdown();\n }\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Batch delivery (never throws)\n// ---------------------------------------------------------------------------\n\nasync function flushBatch(\n state: OtlpTransportState,\n events: LogEvent[],\n): Promise<void> {\n const count = events.length;\n let body: string;\n try {\n body = encode(serializeBatch(events, Date.now()));\n } catch (cause) {\n // Fail-closed: drop the batch, but still release its pending budget.\n state.pending = Math.max(0, state.pending - count);\n notifyOnce(\n state,\n 'serialize_failed',\n 'dropped a batch that failed to serialize',\n cause,\n );\n return;\n }\n\n // Per-request header map. Best-effort `traceparent` injection (Feature 009)\n // never blocks delivery: any failure falls back to the plain configured\n // headers. Returns the same `state.headers` reference when disabled or the\n // batch is not homogeneous, so the request stays byte-identical.\n let headers: Readonly<Record<string, string>> = state.headers;\n try {\n headers = buildRequestHeaders(\n state.headers,\n events,\n state.injectTraceparent,\n );\n } catch {\n headers = state.headers;\n }\n\n const promise = (async (): Promise<void> => {\n const result: DeliveryResult = await deliver(state.endpoint, headers, body);\n mapResult(state, result);\n })().catch(() => {\n // Defensive: deliver() is contracted not to reject, but never let an\n // unexpected rejection escape into an unhandled rejection.\n });\n\n state.inFlight.add(promise);\n void promise.finally(() => {\n state.inFlight.delete(promise);\n // Release this batch's pending budget once delivery settles.\n state.pending = Math.max(0, state.pending - count);\n });\n}\n\nfunction mapResult(state: OtlpTransportState, result: DeliveryResult): void {\n switch (result.kind) {\n case 'delivered':\n return;\n case 'unavailable':\n notifyOnce(\n state,\n 'delivery_unavailable',\n 'fetch is unavailable; dropping batch',\n );\n return;\n case 'send_failed':\n notifyOnce(\n state,\n 'send_failed',\n `delivery failed (${result.detail})`,\n result.cause,\n );\n return;\n case 'partial_rejection':\n notifyOnce(\n state,\n 'partial_rejection',\n `backend rejected ${result.rejected} record(s)`,\n );\n return;\n }\n}\n\nasync function settleInFlight(state: OtlpTransportState): Promise<void> {\n // Snapshot: new deliveries triggered during await are not awaited here\n // (flush() is a point-in-time drain), matching beacon's flush semantics.\n await Promise.all([...state.inFlight]);\n}\n\n// ---------------------------------------------------------------------------\n// Lazy pagehide best-effort flush (Principle VII — nothing at Logger create)\n// ---------------------------------------------------------------------------\n\nfunction ensurePagehide(state: OtlpTransportState): void {\n if (state.pagehideInstalled) return;\n const target = globalThis as {\n addEventListener?: typeof globalThis.addEventListener;\n removeEventListener?: typeof globalThis.removeEventListener;\n };\n state.pagehideInstalled = true;\n if (typeof target.addEventListener !== 'function') {\n state.pagehideUninstall = null;\n return;\n }\n const handler = (): void => {\n // Best-effort: drain via the keepalive fetch path. Never blocks unload.\n state.batcher.flush();\n };\n target.addEventListener('pagehide', handler);\n state.pagehideUninstall = (): void => {\n if (typeof target.removeEventListener === 'function') {\n target.removeEventListener('pagehide', handler);\n }\n };\n}\n\nfunction teardownPagehide(state: OtlpTransportState): void {\n if (state.pagehideUninstall !== null) {\n state.pagehideUninstall();\n state.pagehideUninstall = null;\n }\n state.pagehideInstalled = false;\n}\n\nfunction byteLength(s: string): number {\n return new TextEncoder().encode(s).length;\n}\n"]}
|
|
@@ -54,6 +54,15 @@ interface OtlpTransportOptions {
|
|
|
54
54
|
name?: string;
|
|
55
55
|
/** Permit `http://` localhost/127.0.0.1/[::1] only. Default false. */
|
|
56
56
|
allowInsecureLoopback?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* When `true`, set a W3C `traceparent` (and `tracestate`) request header on a
|
|
59
|
+
* delivery request whose batch all shares one valid trace context. Off by
|
|
60
|
+
* default; homogeneous-only and fail-closed — a mixed, trace-less, or empty
|
|
61
|
+
* batch sets no header, and the event payload is unchanged either way.
|
|
62
|
+
* Carry-only: built from each event's existing `context.trace`; no ids are
|
|
63
|
+
* minted. See `specs/009-traceparent-injection/`.
|
|
64
|
+
*/
|
|
65
|
+
injectTraceparent?: boolean;
|
|
57
66
|
/** Receives rate-limited diagnostic notices. Never carries header values. */
|
|
58
67
|
onInternalError?: (err: Error) => void;
|
|
59
68
|
}
|
package/dist/transport-otlp.d.ts
CHANGED
|
@@ -54,6 +54,15 @@ interface OtlpTransportOptions {
|
|
|
54
54
|
name?: string;
|
|
55
55
|
/** Permit `http://` localhost/127.0.0.1/[::1] only. Default false. */
|
|
56
56
|
allowInsecureLoopback?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* When `true`, set a W3C `traceparent` (and `tracestate`) request header on a
|
|
59
|
+
* delivery request whose batch all shares one valid trace context. Off by
|
|
60
|
+
* default; homogeneous-only and fail-closed — a mixed, trace-less, or empty
|
|
61
|
+
* batch sets no header, and the event payload is unchanged either way.
|
|
62
|
+
* Carry-only: built from each event's existing `context.trace`; no ids are
|
|
63
|
+
* minted. See `specs/009-traceparent-injection/`.
|
|
64
|
+
*/
|
|
65
|
+
injectTraceparent?: boolean;
|
|
57
66
|
/** Receives rate-limited diagnostic notices. Never carries header values. */
|
|
58
67
|
onInternalError?: (err: Error) => void;
|
|
59
68
|
}
|
package/dist/transport-otlp.mjs
CHANGED
|
@@ -311,6 +311,76 @@ function pushException(out, event) {
|
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
+
// src/transport-otlp/traceparent-header.ts
|
|
315
|
+
var MAX_TRACESTATE_LEN = 512;
|
|
316
|
+
var TRACE_ID_RE = /^[0-9a-f]{32}$/;
|
|
317
|
+
var SPAN_ID_RE = /^[0-9a-f]{16}$/;
|
|
318
|
+
var ALL_ZERO_TRACE_ID = "0".repeat(32);
|
|
319
|
+
var ALL_ZERO_SPAN_ID = "0".repeat(16);
|
|
320
|
+
var NONE = {
|
|
321
|
+
key: "none",
|
|
322
|
+
traceparent: null,
|
|
323
|
+
traceState: null
|
|
324
|
+
};
|
|
325
|
+
function hasValidIds(trace) {
|
|
326
|
+
return typeof trace.traceId === "string" && TRACE_ID_RE.test(trace.traceId) && trace.traceId !== ALL_ZERO_TRACE_ID && typeof trace.spanId === "string" && SPAN_ID_RE.test(trace.spanId) && trace.spanId !== ALL_ZERO_SPAN_ID;
|
|
327
|
+
}
|
|
328
|
+
function flagsHex(traceFlags) {
|
|
329
|
+
const n = typeof traceFlags === "number" && Number.isInteger(traceFlags) && traceFlags >= 0 && traceFlags <= 255 ? traceFlags : 0;
|
|
330
|
+
return n.toString(16).padStart(2, "0");
|
|
331
|
+
}
|
|
332
|
+
function resolve(event) {
|
|
333
|
+
const trace = event.context.trace;
|
|
334
|
+
if (trace === void 0 || !hasValidIds(trace)) {
|
|
335
|
+
return NONE;
|
|
336
|
+
}
|
|
337
|
+
const traceparent = `00-${trace.traceId}-${trace.spanId}-${flagsHex(
|
|
338
|
+
trace.traceFlags
|
|
339
|
+
)}`;
|
|
340
|
+
const traceState = typeof trace.traceState === "string" && trace.traceState.length > 0 && trace.traceState.length <= MAX_TRACESTATE_LEN ? trace.traceState : null;
|
|
341
|
+
return { key: traceparent, traceparent, traceState };
|
|
342
|
+
}
|
|
343
|
+
function decideBatchTraceparent(events) {
|
|
344
|
+
if (events.length === 0) {
|
|
345
|
+
return { inject: false };
|
|
346
|
+
}
|
|
347
|
+
const first = resolve(events[0]);
|
|
348
|
+
if (first.key === "none") {
|
|
349
|
+
return { inject: false };
|
|
350
|
+
}
|
|
351
|
+
let tracestateUniform = true;
|
|
352
|
+
for (let i = 1; i < events.length; i += 1) {
|
|
353
|
+
const r = resolve(events[i]);
|
|
354
|
+
if (r.key !== first.key) {
|
|
355
|
+
return { inject: false };
|
|
356
|
+
}
|
|
357
|
+
if (r.traceState !== first.traceState) {
|
|
358
|
+
tracestateUniform = false;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const traceparent = first.traceparent;
|
|
362
|
+
if (tracestateUniform && first.traceState !== null) {
|
|
363
|
+
return { inject: true, traceparent, tracestate: first.traceState };
|
|
364
|
+
}
|
|
365
|
+
return { inject: true, traceparent };
|
|
366
|
+
}
|
|
367
|
+
function buildRequestHeaders(base, events, enabled) {
|
|
368
|
+
if (!enabled) {
|
|
369
|
+
return base;
|
|
370
|
+
}
|
|
371
|
+
const decision = decideBatchTraceparent(events);
|
|
372
|
+
if (!decision.inject) {
|
|
373
|
+
return base;
|
|
374
|
+
}
|
|
375
|
+
const injected = {
|
|
376
|
+
traceparent: decision.traceparent
|
|
377
|
+
};
|
|
378
|
+
if (decision.tracestate !== void 0) {
|
|
379
|
+
injected.tracestate = decision.tracestate;
|
|
380
|
+
}
|
|
381
|
+
return { ...injected, ...base };
|
|
382
|
+
}
|
|
383
|
+
|
|
314
384
|
// src/transport-otlp/otlp-transport.ts
|
|
315
385
|
var DEFAULTS = {
|
|
316
386
|
maxBatchSize: 20,
|
|
@@ -324,6 +394,9 @@ function validateOptions(options) {
|
|
|
324
394
|
throw new TypeError("otlp transport: options must be a non-null object");
|
|
325
395
|
}
|
|
326
396
|
const { headers, batching, maxBufferedEvents, maxRecordBytes } = options;
|
|
397
|
+
if (options.injectTraceparent !== void 0 && typeof options.injectTraceparent !== "boolean") {
|
|
398
|
+
throw new TypeError("otlp transport: injectTraceparent must be a boolean");
|
|
399
|
+
}
|
|
327
400
|
if (headers !== void 0) {
|
|
328
401
|
if (typeof headers !== "object" || headers === null) {
|
|
329
402
|
throw new TypeError("otlp transport: headers must be an object");
|
|
@@ -371,6 +444,7 @@ function createOtlpTransport(options) {
|
|
|
371
444
|
onInternalError: options.onInternalError ?? (() => void 0),
|
|
372
445
|
maxBufferedEvents: options.maxBufferedEvents ?? DEFAULTS.maxBufferedEvents,
|
|
373
446
|
maxRecordBytes: options.maxRecordBytes ?? DEFAULTS.maxRecordBytes,
|
|
447
|
+
injectTraceparent: options.injectTraceparent ?? false,
|
|
374
448
|
notified: freshNotifiedLedger(),
|
|
375
449
|
// Placeholder; real batcher assigned below once the flush closure exists.
|
|
376
450
|
batcher: void 0,
|
|
@@ -459,12 +533,18 @@ async function flushBatch(state, events) {
|
|
|
459
533
|
);
|
|
460
534
|
return;
|
|
461
535
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
536
|
+
let headers = state.headers;
|
|
537
|
+
try {
|
|
538
|
+
headers = buildRequestHeaders(
|
|
465
539
|
state.headers,
|
|
466
|
-
|
|
540
|
+
events,
|
|
541
|
+
state.injectTraceparent
|
|
467
542
|
);
|
|
543
|
+
} catch {
|
|
544
|
+
headers = state.headers;
|
|
545
|
+
}
|
|
546
|
+
const promise = (async () => {
|
|
547
|
+
const result = await deliver(state.endpoint, headers, body);
|
|
468
548
|
mapResult(state, result);
|
|
469
549
|
})().catch(() => {
|
|
470
550
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/transport-otlp/batcher.ts","../src/transport-otlp/delivery.ts","../src/transport-otlp/endpoint-validation.ts","../src/transport-otlp/errors.ts","../src/transport-otlp/attributes.ts","../src/transport-otlp/resource.ts","../src/transport-otlp/otlp-serializer.ts","../src/transport-otlp/otlp-transport.ts"],"names":[],"mappings":"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","file":"transport-otlp.mjs","sourcesContent":["/**\n * Bounded batch buffer for the OTLP transport.\n *\n * A parallel copy of the `./transport-beacon` batcher state machine (the\n * boundary rule forbids importing across subpaths — TO-7). Behaviour\n * (research D7, data-model § Batcher):\n *\n * - `push(event)` appends to the in-memory buffer.\n * - First event in an empty batch with `maxBatchAgeMs` set arms a\n * one-shot age timer.\n * - Reaching `maxBatchSize` flushes synchronously at end of `push`.\n * - `flush()` drains the pending buffer through the consumer callback.\n * Empty buffer → no-op.\n * - `shutdown()` cancels the timer and inhibits further callbacks.\n *\n * Buffer is copied + cleared BEFORE the callback runs, so a re-entrant\n * `push` during the callback starts a fresh batch. A throwing callback is\n * swallowed (the batcher never throws from push/flush/shutdown). The hard\n * `maxBufferedEvents` cap is enforced by the transport BEFORE `push`, so\n * the batcher itself stays simple.\n */\n\nimport type { LogEvent } from '../api/types.js';\n\nexport interface BatcherOptions {\n maxBatchSize: number;\n maxBatchAgeMs?: number;\n flush: (events: LogEvent[]) => void;\n}\n\nexport interface Batcher {\n push(event: LogEvent): void;\n flush(): void;\n shutdown(): void;\n /** Current pending count — used by the transport's buffer-cap guard. */\n size(): number;\n}\n\nexport function createBatcher(opts: BatcherOptions): Batcher {\n const buffer: LogEvent[] = [];\n let maxAgeTimer: ReturnType<typeof setTimeout> | null = null;\n let flushCallback: ((events: LogEvent[]) => void) | null = opts.flush;\n\n const clearTimer = (): void => {\n if (maxAgeTimer !== null) {\n clearTimeout(maxAgeTimer);\n maxAgeTimer = null;\n }\n };\n\n const armTimer = (): void => {\n if (opts.maxBatchAgeMs === undefined) return;\n maxAgeTimer = setTimeout(() => {\n maxAgeTimer = null;\n doFlush();\n }, opts.maxBatchAgeMs);\n };\n\n const doFlush = (): void => {\n if (buffer.length === 0) return;\n if (flushCallback === null) {\n buffer.length = 0;\n clearTimer();\n return;\n }\n const events = buffer.slice();\n buffer.length = 0;\n clearTimer();\n try {\n flushCallback(events);\n } catch {\n // Swallow: the events were copied out; the consumer callback chose\n // to throw. Single-flush-attempt model (no re-push, no retry).\n }\n };\n\n return {\n push(event: LogEvent): void {\n if (flushCallback === null) return;\n buffer.push(event);\n if (buffer.length === 1 && opts.maxBatchAgeMs !== undefined) {\n armTimer();\n }\n if (buffer.length >= opts.maxBatchSize) {\n doFlush();\n }\n },\n flush(): void {\n doFlush();\n },\n shutdown(): void {\n clearTimer();\n buffer.length = 0;\n flushCallback = null;\n },\n size(): number {\n return buffer.length;\n },\n };\n}\n","/**\n * Delivery primitive for the OTLP transport: POST the OTLP/HTTP+JSON body\n * via `fetch` with `keepalive: true` (research D6, TO-2).\n *\n * `navigator.sendBeacon` is deliberately NOT used — it cannot set the\n * custom auth + `Content-Type` headers OTLP backends require (FR-004).\n * `keepalive` preserves best-effort delivery during page unload.\n *\n * `deliver(...)` NEVER throws or rejects: every outcome (2xx, 2xx with an\n * OTLP partial-success rejection, non-2xx, network reject, missing\n * `fetch`) is reduced to a `DeliveryResult` for the caller to map onto a\n * rate-limited notice. There is no retry (research D7).\n *\n * Boundary discipline (TO-7): zero `src/` imports; zero vendor imports.\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md` OP-1;\n * `contracts/transport-otlp-public-api.md` TO-2/TO-4.\n */\n\n/** Outcome of a single delivery attempt. Never an exception. */\nexport type DeliveryResult =\n | { kind: 'delivered' }\n | { kind: 'unavailable' }\n | { kind: 'send_failed'; detail: string; cause?: unknown }\n | { kind: 'partial_rejection'; rejected: number };\n\n/**\n * POST `body` to `endpoint` with `keepalive: true`, merging the caller's\n * static `headers` (e.g. auth) over the mandatory `content-type`.\n * `credentials: 'same-origin'` keeps cookies from leaking cross-origin by\n * default (Principle IV); auth travels only in the explicit headers.\n */\nexport async function deliver(\n endpoint: string,\n headers: Readonly<Record<string, string>>,\n body: string,\n): Promise<DeliveryResult> {\n const fetchFn = (globalThis as { fetch?: typeof fetch }).fetch;\n if (typeof fetchFn !== 'function') {\n return { kind: 'unavailable' };\n }\n\n let response: Response;\n try {\n response = await fetchFn(endpoint, {\n method: 'POST',\n body,\n headers: { 'content-type': 'application/json', ...headers },\n keepalive: true,\n credentials: 'same-origin',\n });\n } catch (cause) {\n return { kind: 'send_failed', detail: 'fetch rejected', cause };\n }\n\n if (!response.ok) {\n return { kind: 'send_failed', detail: `HTTP ${response.status}` };\n }\n\n // 2xx — check for an OTLP partial-success rejection in the body. A\n // missing / non-JSON / unexpected body is treated as full success.\n const rejected = await readRejectedCount(response);\n if (rejected > 0) {\n return { kind: 'partial_rejection', rejected };\n }\n return { kind: 'delivered' };\n}\n\nasync function readRejectedCount(response: Response): Promise<number> {\n try {\n if (typeof response.json !== 'function') return 0;\n const parsed = (await response.json()) as unknown;\n if (typeof parsed !== 'object' || parsed === null) return 0;\n const partial = (parsed as { partialSuccess?: unknown }).partialSuccess;\n if (typeof partial !== 'object' || partial === null) return 0;\n const raw = (partial as { rejectedLogRecords?: unknown })\n .rejectedLogRecords;\n // OTLP encodes int64 as a string in JSON, but tolerate a number too.\n const n = typeof raw === 'string' ? Number(raw) : raw;\n return typeof n === 'number' && Number.isFinite(n) && n > 0 ? n : 0;\n } catch {\n // A body that cannot be read/parsed does not turn a 2xx into a failure.\n return 0;\n }\n}\n","/**\n * Construction-time endpoint validation for `createOtlpTransport`.\n *\n * Locked behaviour (mirrors `./transport-beacon`, TO-5 / research D8):\n * - HTTPS endpoints always pass.\n * - HTTP endpoints pass IFF `allowInsecureLoopback === true` AND the\n * parsed URL's hostname is in `{ localhost, 127.0.0.1, [::1] }`.\n * - Every other case throws a typed error at construction time, before\n * any logger derives the runtime and before any listener is attached.\n *\n * Pure and side-effect-free: parses via `new URL(...)` and inspects the\n * result. MUST NOT read ambient state (no `process.env`, no\n * `window.location`) and MUST NOT read `allowInsecureLoopback` from\n * anywhere except the argument the caller passed.\n *\n * The consumer supplies the FULL OTLP logs URL (e.g.\n * `https://otlp.example.com/v1/logs`); the transport appends nothing.\n *\n * Specs: `specs/007-transport-otlp/contracts/transport-otlp-public-api.md`\n * TO-5; `data-model.md` § validation rules.\n */\n\n/**\n * Hostnames permitted under `allowInsecureLoopback: true`. WHATWG URL\n * normalises `http://[::1]` to hostname `[::1]` (with brackets).\n */\nconst LOOPBACK_HOSTS: ReadonlySet<string> = new Set([\n 'localhost',\n '127.0.0.1',\n '[::1]',\n]);\n\n/**\n * Validate a consumer-supplied `endpoint`. Returns the parsed `URL` on\n * success; throws on every violation. The thrown error's `.message` names\n * the violated constraint and the offending endpoint string.\n */\nexport function validateEndpoint(\n endpoint: unknown,\n allowInsecureLoopback: boolean,\n): URL {\n if (typeof endpoint !== 'string') {\n throw new TypeError(\n `otlp transport: endpoint must be a string, got ${typeName(endpoint)}`,\n );\n }\n\n let parsed: URL;\n try {\n parsed = new URL(endpoint);\n } catch {\n throw new TypeError(`otlp transport: invalid endpoint URL: '${endpoint}'`);\n }\n\n if (parsed.protocol === 'https:') {\n return parsed;\n }\n\n if (parsed.protocol === 'http:') {\n if (!allowInsecureLoopback) {\n throw new Error(\n `otlp transport refuses non-HTTPS endpoint '${endpoint}'`,\n );\n }\n if (!LOOPBACK_HOSTS.has(parsed.hostname)) {\n throw new Error(\n `otlp transport: allowInsecureLoopback permits only ` +\n `localhost / 127.0.0.1 / [::1]; got '${parsed.hostname}' ` +\n `in '${endpoint}'`,\n );\n }\n return parsed;\n }\n\n throw new Error(\n `otlp transport refuses non-HTTPS endpoint '${endpoint}' ` +\n `(scheme '${parsed.protocol}' is not permitted)`,\n );\n}\n\nfunction typeName(value: unknown): string {\n if (value === null) return 'null';\n return typeof value;\n}\n","/**\n * Subpath-owned diagnostic error class + rate-limited notice helper for\n * the OTLP transport.\n *\n * Drop/diagnostic notices fired by the OTLP transport are `OtlpError`\n * instances (a subclass of `Error`) owned by this subpath — by-convention\n * compatible with the core's `PackageError` shape (`.code`,\n * `.transportName`, optional `.cause`) but with NO runtime import from\n * `src/internal/**` (TO-7). This module is INTERNAL to the subpath;\n * `src/transport-otlp/index.ts` does not re-export it. The\n * `onInternalError` hook receives the instance typed as `Error`.\n *\n * **Security (FR-009 / TO-6)**: notice messages MUST NEVER include any\n * configured request-header value. The helpers here only ever build\n * messages from the failure code, the transport name, and an optional\n * non-secret detail string supplied by the caller. Callers MUST NOT pass\n * header values into `detail`.\n *\n * Specs: `specs/007-transport-otlp/data-model.md` § OtlpFailureCode;\n * `contracts/transport-otlp-public-api.md` TO-4/TO-6.\n */\n\n/**\n * Documented `OtlpError.code` values — one per failure class. Surfaced to\n * the consumer as `err.code` on the `Error`-shaped `onInternalError`\n * argument. Each is rate-limited to one notice per class per transport\n * instance per session.\n */\nexport type OtlpFailureCode =\n | 'oversized_event'\n | 'buffer_overflow'\n | 'delivery_unavailable'\n | 'send_failed'\n | 'partial_rejection'\n | 'serialize_failed'\n | 'shutdown_failed';\n\n/**\n * Subclass of `Error` carrying a discriminating `.code`, the originating\n * transport's `.transportName`, and an optional `.cause` chain. `.name`\n * is `'OtlpError'`. `.cause` follows the ES2022 convention (absent when\n * not provided rather than `undefined`).\n */\nexport class OtlpError extends Error {\n readonly code: OtlpFailureCode;\n readonly transportName: string;\n declare readonly cause?: unknown;\n\n constructor(\n code: OtlpFailureCode,\n transportName: string,\n message: string,\n cause?: unknown,\n ) {\n super(message);\n this.name = 'OtlpError';\n this.code = code;\n this.transportName = transportName;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', {\n value: cause,\n enumerable: true,\n writable: false,\n configurable: false,\n });\n }\n }\n}\n\n/** Type guard for `OtlpError` instances. */\nexport function isOtlpError(value: unknown): value is OtlpError {\n return value instanceof OtlpError;\n}\n\n/**\n * Minimal shape the `notifyOnce` helper needs: the transport's name, the\n * consumer error sink, and a per-code \"already notified\" ledger. The\n * concrete `OtlpTransportState` (data-model) satisfies this.\n */\nexport interface NotifyContext {\n readonly name: string;\n readonly onInternalError: (err: Error) => void;\n readonly notified: Record<OtlpFailureCode, boolean>;\n}\n\n/**\n * Emit at most ONE diagnostic notice per failure `code` per context\n * (instance) per session (FR-010). Subsequent calls with the same code are\n * silently suppressed. The `onInternalError` callback is invoked inside a\n * try/catch so a throwing consumer handler can never propagate back into\n * the transport's hot path (Principle II).\n *\n * `detail` is an optional NON-SECRET human string (e.g. an HTTP status).\n * Callers MUST NOT pass any configured header/secret value here.\n */\nexport function notifyOnce(\n ctx: NotifyContext,\n code: OtlpFailureCode,\n message: string,\n cause?: unknown,\n): void {\n if (ctx.notified[code]) return;\n ctx.notified[code] = true;\n const err = new OtlpError(\n code,\n ctx.name,\n `otlp transport '${ctx.name}': ${message}`,\n cause,\n );\n try {\n ctx.onInternalError(err);\n } catch {\n // A throwing consumer error handler must never reach the caller.\n }\n}\n\n/** Build the per-instance \"already notified\" ledger with all flags false. */\nexport function freshNotifiedLedger(): Record<OtlpFailureCode, boolean> {\n return {\n oversized_event: false,\n buffer_overflow: false,\n delivery_unavailable: false,\n send_failed: false,\n partial_rejection: false,\n serialize_failed: false,\n shutdown_failed: false,\n };\n}\n","/**\n * Pure `AttributeValue` → OTLP `AnyValue` encoder + the shared OTLP-JSON\n * value types used across the subpath.\n *\n * The OTLP/HTTP+JSON value model is hand-encoded here with ZERO runtime\n * dependencies and no `@opentelemetry/*` import (research D1, TO-7). The\n * `AttributeValue` union (string | number | boolean | null | array |\n * object) maps 1:1 onto OTLP `AnyValue` (OP-5).\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md` OP-5.\n */\n\nimport type { AttributeValue } from '../api/types.js';\n\n/**\n * OTLP `AnyValue` (JSON encoding). `null` → `{}` (an unset value) since\n * OTLP has no explicit null. A non-finite or otherwise non-integer number\n * is a `doubleValue`; an integer-safe number is an `intValue` (string, per\n * the uint64/sint64-as-string rule).\n */\nexport type AnyValue =\n | { stringValue: string }\n | { boolValue: boolean }\n | { intValue: string }\n | { doubleValue: number }\n | { arrayValue: { values: AnyValue[] } }\n | { kvlistValue: { values: KeyValue[] } }\n // biome-ignore lint/complexity/noBannedTypes: OTLP represents an unset/null value as an empty AnyValue object.\n | {};\n\n/** OTLP `KeyValue`. */\nexport interface KeyValue {\n key: string;\n value: AnyValue;\n}\n\n/** Encode a single sanitized `AttributeValue` as an OTLP `AnyValue`. */\nexport function toAnyValue(value: AttributeValue): AnyValue {\n if (value === null) {\n return {};\n }\n switch (typeof value) {\n case 'string':\n return { stringValue: value };\n case 'boolean':\n return { boolValue: value };\n case 'number':\n return Number.isInteger(value)\n ? { intValue: String(value) }\n : { doubleValue: value };\n default:\n break;\n }\n if (Array.isArray(value)) {\n return { arrayValue: { values: value.map(toAnyValue) } };\n }\n // Remaining case: a plain `{ [key: string]: AttributeValue }` object.\n return { kvlistValue: { values: toKeyValues(value) } };\n}\n\n/**\n * Encode a record of sanitized attributes as an OTLP `KeyValue[]`,\n * optionally prefixing each key (used to namespace merged context\n * attributes under `context.` — OP-4).\n */\nexport function toKeyValues(\n record: Readonly<Record<string, AttributeValue>>,\n keyPrefix = '',\n): KeyValue[] {\n const out: KeyValue[] = [];\n for (const key of Object.keys(record)) {\n const value = record[key];\n if (value === undefined) continue;\n out.push({ key: keyPrefix + key, value: toAnyValue(value) });\n }\n return out;\n}\n","/**\n * Map SafeSignal runtime-global identity to an OTLP `Resource` (OP-2 / D3).\n *\n * The `Resource` carries only identity that is constant across a batch\n * from one configured transport — `service.name`, `service.version`,\n * `deployment.environment` (standard OTel semantic-convention attributes\n * every OTLP backend understands). The federated `module.*` identity is\n * **per-logger** (it can differ between events in the same batch via\n * `withContext`), so it is attributed per-`LogRecord` (see\n * `otlp-serializer.ts`), not on the shared Resource — preserving correct\n * origin attribution (Principle VI).\n *\n * Only present fields are emitted (no empty/`null` keys). Pure and\n * dependency-free.\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md` OP-2.\n */\n\nimport type { LogContext } from '../api/types.js';\n\nimport type { KeyValue } from './attributes.js';\n\nexport interface OtlpResource {\n attributes: KeyValue[];\n}\n\n/**\n * Build the OTLP `Resource` for a batch from a representative context\n * (the batch's first event). `service.*` / `deployment.environment` are\n * runtime-global, so any event in the batch is representative.\n */\nexport function buildResource(context: LogContext): OtlpResource {\n const attributes: KeyValue[] = [];\n\n const push = (key: string, value: string | undefined): void => {\n if (typeof value === 'string' && value.length > 0) {\n attributes.push({ key, value: { stringValue: value } });\n }\n };\n\n push('service.name', context.application?.name);\n push('service.version', context.application?.version);\n push('deployment.environment', context.environment);\n\n return { attributes };\n}\n","/**\n * Pure `LogEvent[]` → OTLP/HTTP+JSON logs payload serializer (OP-1..OP-6).\n *\n * This is the heart of the subpath. It hand-builds the OTLP logs JSON\n * shape with ZERO runtime dependencies and NO `@opentelemetry/*` import\n * (research D1, TO-7): severity numbers are literal constants matching the\n * OTel `SeverityNumber` ranges (and the in-repo\n * `src/internal/telemetry/otel/mapping.ts` table), reused conceptually but\n * never imported.\n *\n * **Encoding seam (FR-015)**: `serializeBatch(...)` builds the encoder-\n * neutral `OtlpLogsRequest` object; `encode(...)` turns it into the wire\n * body. Today the only encoder is JSON; a future protobuf encoder slots in\n * behind `encode(...)` without changing the object model or the public\n * surface.\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md`.\n */\n\nimport type { LogContext, LogEvent, LogLevel } from '../api/types.js';\n\nimport { type AnyValue, type KeyValue, toKeyValues } from './attributes.js';\nimport { buildResource } from './resource.js';\n\n/** Constant instrumentation-scope name for every emitted ScopeLogs. */\nexport const SCOPE_NAME = '@tallyrow/safesignal';\n\n/**\n * OTLP `SeverityNumber` base value per SafeSignal level (OP-3 / D2). These\n * are the canonical OTLP range bases (DEBUG 5, INFO 9, WARN 13, ERROR 17)\n * and match `LEVEL_TO_SEVERITY` in the internal OTel seam.\n */\nconst LEVEL_TO_SEVERITY_NUMBER: Readonly<Record<LogLevel, number>> = {\n debug: 5,\n info: 9,\n warn: 13,\n error: 17,\n};\n\nconst LEVEL_TO_SEVERITY_TEXT: Readonly<Record<LogLevel, string>> = {\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n};\n\n// ---------------------------------------------------------------------------\n// OTLP wire shapes (JSON encoding)\n// ---------------------------------------------------------------------------\n\nexport interface OtlpLogRecord {\n timeUnixNano: string;\n observedTimeUnixNano: string;\n severityNumber: number;\n severityText: string;\n body: AnyValue;\n attributes: KeyValue[];\n /** W3C trace correlation — present only when `event.context.trace` is set. */\n traceId?: string;\n spanId?: string;\n flags?: number;\n}\n\nexport interface OtlpScopeLogs {\n scope: { name: string };\n logRecords: OtlpLogRecord[];\n}\n\nexport interface OtlpResourceLogs {\n resource: { attributes: KeyValue[] };\n scopeLogs: OtlpScopeLogs[];\n}\n\nexport interface OtlpLogsRequest {\n resourceLogs: OtlpResourceLogs[];\n}\n\n// ---------------------------------------------------------------------------\n// Mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Convert one `LogEvent` to an OTLP `LogRecord`. Never mutates the input\n * (T-S4). Per-record attributes are: event attributes, then merged context\n * attributes under a `context.` prefix, then per-logger `module.*`\n * identity, then `exception.*` when an error is present (OP-4).\n *\n * `fallbackTimeMs` is a single resolved time used when `event.timestamp`\n * is unparseable, so the mapping never throws on a bad timestamp (OP-3).\n */\nexport function toLogRecord(\n event: LogEvent,\n fallbackTimeMs: number,\n): OtlpLogRecord {\n const ms = toEpochMs(event.timestamp, fallbackTimeMs);\n const nano = String(ms * 1_000_000);\n\n const attributes: KeyValue[] = toKeyValues(event.attributes);\n\n const context = event.context;\n if (context.attributes !== undefined) {\n attributes.push(...toKeyValues(context.attributes, 'context.'));\n }\n pushModuleIdentity(attributes, context);\n pushException(attributes, event);\n\n const record: OtlpLogRecord = {\n timeUnixNano: nano,\n observedTimeUnixNano: nano,\n severityNumber: LEVEL_TO_SEVERITY_NUMBER[event.level],\n severityText: LEVEL_TO_SEVERITY_TEXT[event.level],\n body: { stringValue: event.message },\n attributes,\n };\n\n // W3C trace correlation → OTLP standard top-level fields (OP/OT contracts).\n // The structured ids are already lowercase-hex (validated upstream), so they\n // are the OTLP/JSON encoding as-is — no base64, no @opentelemetry import.\n const trace = context.trace;\n if (trace !== undefined) {\n record.traceId = trace.traceId;\n record.spanId = trace.spanId;\n if (trace.traceFlags !== undefined) {\n record.flags = trace.traceFlags;\n }\n }\n\n return record;\n}\n\n/**\n * Build the encoder-neutral OTLP logs request object for a batch. The\n * Resource is derived from the first event's runtime-global identity; if\n * the batch is empty, an empty Resource is used.\n */\nexport function serializeBatch(\n batch: ReadonlyArray<LogEvent>,\n fallbackTimeMs: number,\n): OtlpLogsRequest {\n const first = batch[0];\n const resource = buildResource(first ? first.context : ({} as LogContext));\n const logRecords = batch.map((e) => toLogRecord(e, fallbackTimeMs));\n return {\n resourceLogs: [\n {\n resource,\n scopeLogs: [{ scope: { name: SCOPE_NAME }, logRecords }],\n },\n ],\n };\n}\n\n/**\n * Encoding seam (FR-015). Turns the request object into the wire body.\n * The only encoding in this feature is JSON; protobuf is a roadmap\n * follow-up that slots in here without touching callers.\n */\nexport function encode(request: OtlpLogsRequest): string {\n return JSON.stringify(request);\n}\n\n/**\n * Convenience: build + JSON-encode a batch in one call. Pure; never\n * mutates inputs.\n */\nexport function serializeOtlpJson(\n batch: ReadonlyArray<LogEvent>,\n fallbackTimeMs: number,\n): string {\n return encode(serializeBatch(batch, fallbackTimeMs));\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction toEpochMs(iso: string, fallbackMs: number): number {\n const parsed = Date.parse(iso);\n return Number.isFinite(parsed) ? parsed : fallbackMs;\n}\n\nfunction pushModuleIdentity(out: KeyValue[], context: LogContext): void {\n const mod = context.module;\n if (mod === undefined) return;\n if (typeof mod.name === 'string' && mod.name.length > 0) {\n out.push({ key: 'module.name', value: { stringValue: mod.name } });\n }\n if (typeof mod.version === 'string' && mod.version.length > 0) {\n out.push({ key: 'module.version', value: { stringValue: mod.version } });\n }\n}\n\nfunction pushException(out: KeyValue[], event: LogEvent): void {\n const err = event.error;\n if (err === undefined) return;\n out.push({ key: 'exception.type', value: { stringValue: err.name } });\n out.push({ key: 'exception.message', value: { stringValue: err.message } });\n if (typeof err.stack === 'string' && err.stack.length > 0) {\n out.push({\n key: 'exception.stacktrace',\n value: { stringValue: err.stack },\n });\n }\n}\n","/**\n * `createOtlpTransport` — factory for the `./transport-otlp` subpath.\n *\n * Composes the subpath primitives into a `Transport` that delivers\n * fully-processed `LogEvent`s to an OTLP logs backend as OTLP/HTTP+JSON,\n * batched, fire-and-forget (no retry), fail-closed.\n *\n * Delivery policy (research D6/D7, contracts TO-2..TO-8):\n *\n * send(event)\n * ├── if shutdownComplete: no-op\n * ├── lazily install the pagehide best-effort flush (first send)\n * ├── if serialized record > maxRecordBytes → oversized_event drop\n * ├── if buffered >= maxBufferedEvents → buffer_overflow drop\n * └── batcher.push(event) // flush on size / age\n *\n * flush(batch) // batcher callback\n * ├── serialize(batch) (fail-closed: serialize_failed → drop)\n * └── deliver(endpoint, headers, body) // fetch keepalive, never throws\n * ├── delivered ─────────────────── done\n * ├── unavailable ───────────────── delivery_unavailable notice\n * ├── send_failed ───────────────── send_failed notice (+cause)\n * └── partial_rejection ─────────── partial_rejection notice\n *\n * Every notice is rate-limited to one per failure class per instance per\n * session and NEVER carries a configured header/secret value (FR-009).\n * `send`/`flush`/`shutdown` NEVER throw or reject to the caller; only\n * construction-time validation throws, at the consumer's call site.\n *\n * Boundary discipline (TO-7): the only `src/` import is a type-only import\n * from `'../api/types.js'`. No `@opentelemetry/*` and no\n * `../internal/telemetry/otel/` import — the payload is hand-serialized.\n *\n * Specs: `specs/007-transport-otlp/contracts/*`, `data-model.md`.\n */\n\nimport type { LogEvent, Transport } from '../api/types.js';\n\nimport { type Batcher, createBatcher } from './batcher.js';\nimport { type DeliveryResult, deliver } from './delivery.js';\nimport { validateEndpoint } from './endpoint-validation.js';\nimport {\n freshNotifiedLedger,\n type NotifyContext,\n notifyOnce,\n type OtlpFailureCode,\n} from './errors.js';\nimport { encode, serializeBatch, toLogRecord } from './otlp-serializer.js';\n\n// ---------------------------------------------------------------------------\n// Public options shape (data-model.md § OtlpTransportOptions)\n// ---------------------------------------------------------------------------\n\nexport interface OtlpTransportOptions {\n /** Full OTLP logs endpoint URL (e.g. `https://otlp.example.com/v1/logs`). */\n endpoint: string;\n /** Static request headers (e.g. auth). Sent only on the wire. */\n headers?: Record<string, string>;\n /** Batch flush triggers. */\n batching?: {\n maxBatchSize: number;\n maxBatchAgeMs?: number;\n };\n /** Hard cap on buffered events; over-cap events are dropped. Default 1000. */\n maxBufferedEvents?: number;\n /** Per-record size guard in bytes; larger records are dropped. Default 64 KiB. */\n maxRecordBytes?: number;\n /** Stable diagnostic identifier (`Transport.name`). Default `'otlp'`. */\n name?: string;\n /** Permit `http://` localhost/127.0.0.1/[::1] only. Default false. */\n allowInsecureLoopback?: boolean;\n /** Receives rate-limited diagnostic notices. Never carries header values. */\n onInternalError?: (err: Error) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal per-instance state (data-model.md § OtlpTransportState)\n// ---------------------------------------------------------------------------\n\ninterface OtlpTransportState extends NotifyContext {\n readonly endpoint: string;\n readonly headers: Readonly<Record<string, string>>;\n readonly name: string;\n readonly onInternalError: (err: Error) => void;\n readonly maxBufferedEvents: number;\n readonly maxRecordBytes: number;\n readonly notified: Record<OtlpFailureCode, boolean>;\n batcher: Batcher;\n pagehideInstalled: boolean;\n pagehideUninstall: (() => void) | null;\n shutdownComplete: boolean;\n inFlight: Set<Promise<void>>;\n /**\n * Events accepted but not yet delivered (buffered in the batcher + in\n * flight). The true memory bound in this no-retry design: incremented on\n * accept, decremented when a batch's delivery settles. Capped at\n * `maxBufferedEvents` so a slow/failing backend cannot grow memory\n * unboundedly.\n */\n pending: number;\n}\n\nconst DEFAULTS = {\n maxBatchSize: 20,\n maxBatchAgeMs: 5000,\n maxBufferedEvents: 1000,\n maxRecordBytes: 65536,\n name: 'otlp',\n} as const;\n\n// ---------------------------------------------------------------------------\n// Construction-time validation (TO-2)\n// ---------------------------------------------------------------------------\n\nfunction validateOptions(options: OtlpTransportOptions): void {\n if (typeof options !== 'object' || options === null) {\n throw new TypeError('otlp transport: options must be a non-null object');\n }\n const { headers, batching, maxBufferedEvents, maxRecordBytes } = options;\n\n if (headers !== undefined) {\n if (typeof headers !== 'object' || headers === null) {\n throw new TypeError('otlp transport: headers must be an object');\n }\n for (const key of Object.keys(headers)) {\n if (typeof headers[key] !== 'string') {\n throw new TypeError(\n `otlp transport: header '${key}' must be a string value`,\n );\n }\n }\n }\n\n const maxBatchSize = batching?.maxBatchSize ?? DEFAULTS.maxBatchSize;\n if (!Number.isInteger(maxBatchSize) || maxBatchSize < 1) {\n throw new RangeError(\n 'otlp transport: batching.maxBatchSize must be an integer >= 1',\n );\n }\n const cap = maxBufferedEvents ?? DEFAULTS.maxBufferedEvents;\n if (!Number.isInteger(cap) || cap < maxBatchSize) {\n throw new RangeError(\n 'otlp transport: maxBufferedEvents must be an integer >= maxBatchSize',\n );\n }\n const recBytes = maxRecordBytes ?? DEFAULTS.maxRecordBytes;\n if (!Number.isInteger(recBytes) || recBytes < 1) {\n throw new RangeError(\n 'otlp transport: maxRecordBytes must be an integer >= 1',\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\nexport function createOtlpTransport(options: OtlpTransportOptions): Transport {\n validateOptions(options);\n const allowInsecureLoopback = options.allowInsecureLoopback ?? false;\n // Throws at the consumer call site on a bad endpoint (off the hot path).\n validateEndpoint(options.endpoint, allowInsecureLoopback);\n\n const name = options.name ?? DEFAULTS.name;\n const maxBatchSize = options.batching?.maxBatchSize ?? DEFAULTS.maxBatchSize;\n const maxBatchAgeMs =\n options.batching?.maxBatchAgeMs ?? DEFAULTS.maxBatchAgeMs;\n\n const state: OtlpTransportState = {\n endpoint: options.endpoint,\n // Copy + freeze the headers so later consumer mutation cannot change\n // what we send, and so nothing outside delivery can read them.\n headers: Object.freeze({ ...(options.headers ?? {}) }),\n name,\n onInternalError: options.onInternalError ?? (() => undefined),\n maxBufferedEvents: options.maxBufferedEvents ?? DEFAULTS.maxBufferedEvents,\n maxRecordBytes: options.maxRecordBytes ?? DEFAULTS.maxRecordBytes,\n notified: freshNotifiedLedger(),\n // Placeholder; real batcher assigned below once the flush closure exists.\n batcher: undefined as unknown as Batcher,\n pagehideInstalled: false,\n pagehideUninstall: null,\n shutdownComplete: false,\n inFlight: new Set<Promise<void>>(),\n pending: 0,\n };\n\n state.batcher = createBatcher({\n maxBatchSize,\n maxBatchAgeMs,\n flush: (events) => {\n void flushBatch(state, events);\n },\n });\n\n return {\n name,\n send(event: LogEvent): void {\n if (state.shutdownComplete) return;\n ensurePagehide(state);\n\n // Per-record size guard (oversized_event) — measure the serialized\n // OTLP LogRecord, drop if it exceeds the budget. Never throws.\n try {\n const record = toLogRecord(event, Date.now());\n if (byteLength(JSON.stringify(record)) > state.maxRecordBytes) {\n notifyOnce(\n state,\n 'oversized_event',\n `dropped an event whose serialized record exceeds ${state.maxRecordBytes} bytes`,\n );\n return;\n }\n } catch (cause) {\n notifyOnce(\n state,\n 'serialize_failed',\n 'dropped an event that failed to serialize',\n cause,\n );\n return;\n }\n\n // Hard memory cap on undelivered (buffered + in-flight) events.\n if (state.pending >= state.maxBufferedEvents) {\n notifyOnce(\n state,\n 'buffer_overflow',\n `${state.maxBufferedEvents} events undelivered; dropping event`,\n );\n return;\n }\n state.pending += 1;\n state.batcher.push(event);\n },\n async flush(): Promise<void> {\n state.batcher.flush();\n await settleInFlight(state);\n },\n async shutdown(): Promise<void> {\n if (state.shutdownComplete) {\n await settleInFlight(state);\n return;\n }\n state.shutdownComplete = true;\n try {\n state.batcher.flush();\n await settleInFlight(state);\n } catch (cause) {\n notifyOnce(state, 'shutdown_failed', 'shutdown flush failed', cause);\n } finally {\n teardownPagehide(state);\n state.batcher.shutdown();\n }\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Batch delivery (never throws)\n// ---------------------------------------------------------------------------\n\nasync function flushBatch(\n state: OtlpTransportState,\n events: LogEvent[],\n): Promise<void> {\n const count = events.length;\n let body: string;\n try {\n body = encode(serializeBatch(events, Date.now()));\n } catch (cause) {\n // Fail-closed: drop the batch, but still release its pending budget.\n state.pending = Math.max(0, state.pending - count);\n notifyOnce(\n state,\n 'serialize_failed',\n 'dropped a batch that failed to serialize',\n cause,\n );\n return;\n }\n\n const promise = (async (): Promise<void> => {\n const result: DeliveryResult = await deliver(\n state.endpoint,\n state.headers,\n body,\n );\n mapResult(state, result);\n })().catch(() => {\n // Defensive: deliver() is contracted not to reject, but never let an\n // unexpected rejection escape into an unhandled rejection.\n });\n\n state.inFlight.add(promise);\n void promise.finally(() => {\n state.inFlight.delete(promise);\n // Release this batch's pending budget once delivery settles.\n state.pending = Math.max(0, state.pending - count);\n });\n}\n\nfunction mapResult(state: OtlpTransportState, result: DeliveryResult): void {\n switch (result.kind) {\n case 'delivered':\n return;\n case 'unavailable':\n notifyOnce(\n state,\n 'delivery_unavailable',\n 'fetch is unavailable; dropping batch',\n );\n return;\n case 'send_failed':\n notifyOnce(\n state,\n 'send_failed',\n `delivery failed (${result.detail})`,\n result.cause,\n );\n return;\n case 'partial_rejection':\n notifyOnce(\n state,\n 'partial_rejection',\n `backend rejected ${result.rejected} record(s)`,\n );\n return;\n }\n}\n\nasync function settleInFlight(state: OtlpTransportState): Promise<void> {\n // Snapshot: new deliveries triggered during await are not awaited here\n // (flush() is a point-in-time drain), matching beacon's flush semantics.\n await Promise.all([...state.inFlight]);\n}\n\n// ---------------------------------------------------------------------------\n// Lazy pagehide best-effort flush (Principle VII — nothing at Logger create)\n// ---------------------------------------------------------------------------\n\nfunction ensurePagehide(state: OtlpTransportState): void {\n if (state.pagehideInstalled) return;\n const target = globalThis as {\n addEventListener?: typeof globalThis.addEventListener;\n removeEventListener?: typeof globalThis.removeEventListener;\n };\n state.pagehideInstalled = true;\n if (typeof target.addEventListener !== 'function') {\n state.pagehideUninstall = null;\n return;\n }\n const handler = (): void => {\n // Best-effort: drain via the keepalive fetch path. Never blocks unload.\n state.batcher.flush();\n };\n target.addEventListener('pagehide', handler);\n state.pagehideUninstall = (): void => {\n if (typeof target.removeEventListener === 'function') {\n target.removeEventListener('pagehide', handler);\n }\n };\n}\n\nfunction teardownPagehide(state: OtlpTransportState): void {\n if (state.pagehideUninstall !== null) {\n state.pagehideUninstall();\n state.pagehideUninstall = null;\n }\n state.pagehideInstalled = false;\n}\n\nfunction byteLength(s: string): number {\n return new TextEncoder().encode(s).length;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/transport-otlp/batcher.ts","../src/transport-otlp/delivery.ts","../src/transport-otlp/endpoint-validation.ts","../src/transport-otlp/errors.ts","../src/transport-otlp/attributes.ts","../src/transport-otlp/resource.ts","../src/transport-otlp/otlp-serializer.ts","../src/transport-otlp/traceparent-header.ts","../src/transport-otlp/otlp-transport.ts"],"names":[],"mappings":"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","file":"transport-otlp.mjs","sourcesContent":["/**\n * Bounded batch buffer for the OTLP transport.\n *\n * A parallel copy of the `./transport-beacon` batcher state machine (the\n * boundary rule forbids importing across subpaths — TO-7). Behaviour\n * (research D7, data-model § Batcher):\n *\n * - `push(event)` appends to the in-memory buffer.\n * - First event in an empty batch with `maxBatchAgeMs` set arms a\n * one-shot age timer.\n * - Reaching `maxBatchSize` flushes synchronously at end of `push`.\n * - `flush()` drains the pending buffer through the consumer callback.\n * Empty buffer → no-op.\n * - `shutdown()` cancels the timer and inhibits further callbacks.\n *\n * Buffer is copied + cleared BEFORE the callback runs, so a re-entrant\n * `push` during the callback starts a fresh batch. A throwing callback is\n * swallowed (the batcher never throws from push/flush/shutdown). The hard\n * `maxBufferedEvents` cap is enforced by the transport BEFORE `push`, so\n * the batcher itself stays simple.\n */\n\nimport type { LogEvent } from '../api/types.js';\n\nexport interface BatcherOptions {\n maxBatchSize: number;\n maxBatchAgeMs?: number;\n flush: (events: LogEvent[]) => void;\n}\n\nexport interface Batcher {\n push(event: LogEvent): void;\n flush(): void;\n shutdown(): void;\n /** Current pending count — used by the transport's buffer-cap guard. */\n size(): number;\n}\n\nexport function createBatcher(opts: BatcherOptions): Batcher {\n const buffer: LogEvent[] = [];\n let maxAgeTimer: ReturnType<typeof setTimeout> | null = null;\n let flushCallback: ((events: LogEvent[]) => void) | null = opts.flush;\n\n const clearTimer = (): void => {\n if (maxAgeTimer !== null) {\n clearTimeout(maxAgeTimer);\n maxAgeTimer = null;\n }\n };\n\n const armTimer = (): void => {\n if (opts.maxBatchAgeMs === undefined) return;\n maxAgeTimer = setTimeout(() => {\n maxAgeTimer = null;\n doFlush();\n }, opts.maxBatchAgeMs);\n };\n\n const doFlush = (): void => {\n if (buffer.length === 0) return;\n if (flushCallback === null) {\n buffer.length = 0;\n clearTimer();\n return;\n }\n const events = buffer.slice();\n buffer.length = 0;\n clearTimer();\n try {\n flushCallback(events);\n } catch {\n // Swallow: the events were copied out; the consumer callback chose\n // to throw. Single-flush-attempt model (no re-push, no retry).\n }\n };\n\n return {\n push(event: LogEvent): void {\n if (flushCallback === null) return;\n buffer.push(event);\n if (buffer.length === 1 && opts.maxBatchAgeMs !== undefined) {\n armTimer();\n }\n if (buffer.length >= opts.maxBatchSize) {\n doFlush();\n }\n },\n flush(): void {\n doFlush();\n },\n shutdown(): void {\n clearTimer();\n buffer.length = 0;\n flushCallback = null;\n },\n size(): number {\n return buffer.length;\n },\n };\n}\n","/**\n * Delivery primitive for the OTLP transport: POST the OTLP/HTTP+JSON body\n * via `fetch` with `keepalive: true` (research D6, TO-2).\n *\n * `navigator.sendBeacon` is deliberately NOT used — it cannot set the\n * custom auth + `Content-Type` headers OTLP backends require (FR-004).\n * `keepalive` preserves best-effort delivery during page unload.\n *\n * `deliver(...)` NEVER throws or rejects: every outcome (2xx, 2xx with an\n * OTLP partial-success rejection, non-2xx, network reject, missing\n * `fetch`) is reduced to a `DeliveryResult` for the caller to map onto a\n * rate-limited notice. There is no retry (research D7).\n *\n * Boundary discipline (TO-7): zero `src/` imports; zero vendor imports.\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md` OP-1;\n * `contracts/transport-otlp-public-api.md` TO-2/TO-4.\n */\n\n/** Outcome of a single delivery attempt. Never an exception. */\nexport type DeliveryResult =\n | { kind: 'delivered' }\n | { kind: 'unavailable' }\n | { kind: 'send_failed'; detail: string; cause?: unknown }\n | { kind: 'partial_rejection'; rejected: number };\n\n/**\n * POST `body` to `endpoint` with `keepalive: true`, merging the caller's\n * static `headers` (e.g. auth) over the mandatory `content-type`.\n * `credentials: 'same-origin'` keeps cookies from leaking cross-origin by\n * default (Principle IV); auth travels only in the explicit headers.\n */\nexport async function deliver(\n endpoint: string,\n headers: Readonly<Record<string, string>>,\n body: string,\n): Promise<DeliveryResult> {\n const fetchFn = (globalThis as { fetch?: typeof fetch }).fetch;\n if (typeof fetchFn !== 'function') {\n return { kind: 'unavailable' };\n }\n\n let response: Response;\n try {\n response = await fetchFn(endpoint, {\n method: 'POST',\n body,\n headers: { 'content-type': 'application/json', ...headers },\n keepalive: true,\n credentials: 'same-origin',\n });\n } catch (cause) {\n return { kind: 'send_failed', detail: 'fetch rejected', cause };\n }\n\n if (!response.ok) {\n return { kind: 'send_failed', detail: `HTTP ${response.status}` };\n }\n\n // 2xx — check for an OTLP partial-success rejection in the body. A\n // missing / non-JSON / unexpected body is treated as full success.\n const rejected = await readRejectedCount(response);\n if (rejected > 0) {\n return { kind: 'partial_rejection', rejected };\n }\n return { kind: 'delivered' };\n}\n\nasync function readRejectedCount(response: Response): Promise<number> {\n try {\n if (typeof response.json !== 'function') return 0;\n const parsed = (await response.json()) as unknown;\n if (typeof parsed !== 'object' || parsed === null) return 0;\n const partial = (parsed as { partialSuccess?: unknown }).partialSuccess;\n if (typeof partial !== 'object' || partial === null) return 0;\n const raw = (partial as { rejectedLogRecords?: unknown })\n .rejectedLogRecords;\n // OTLP encodes int64 as a string in JSON, but tolerate a number too.\n const n = typeof raw === 'string' ? Number(raw) : raw;\n return typeof n === 'number' && Number.isFinite(n) && n > 0 ? n : 0;\n } catch {\n // A body that cannot be read/parsed does not turn a 2xx into a failure.\n return 0;\n }\n}\n","/**\n * Construction-time endpoint validation for `createOtlpTransport`.\n *\n * Locked behaviour (mirrors `./transport-beacon`, TO-5 / research D8):\n * - HTTPS endpoints always pass.\n * - HTTP endpoints pass IFF `allowInsecureLoopback === true` AND the\n * parsed URL's hostname is in `{ localhost, 127.0.0.1, [::1] }`.\n * - Every other case throws a typed error at construction time, before\n * any logger derives the runtime and before any listener is attached.\n *\n * Pure and side-effect-free: parses via `new URL(...)` and inspects the\n * result. MUST NOT read ambient state (no `process.env`, no\n * `window.location`) and MUST NOT read `allowInsecureLoopback` from\n * anywhere except the argument the caller passed.\n *\n * The consumer supplies the FULL OTLP logs URL (e.g.\n * `https://otlp.example.com/v1/logs`); the transport appends nothing.\n *\n * Specs: `specs/007-transport-otlp/contracts/transport-otlp-public-api.md`\n * TO-5; `data-model.md` § validation rules.\n */\n\n/**\n * Hostnames permitted under `allowInsecureLoopback: true`. WHATWG URL\n * normalises `http://[::1]` to hostname `[::1]` (with brackets).\n */\nconst LOOPBACK_HOSTS: ReadonlySet<string> = new Set([\n 'localhost',\n '127.0.0.1',\n '[::1]',\n]);\n\n/**\n * Validate a consumer-supplied `endpoint`. Returns the parsed `URL` on\n * success; throws on every violation. The thrown error's `.message` names\n * the violated constraint and the offending endpoint string.\n */\nexport function validateEndpoint(\n endpoint: unknown,\n allowInsecureLoopback: boolean,\n): URL {\n if (typeof endpoint !== 'string') {\n throw new TypeError(\n `otlp transport: endpoint must be a string, got ${typeName(endpoint)}`,\n );\n }\n\n let parsed: URL;\n try {\n parsed = new URL(endpoint);\n } catch {\n throw new TypeError(`otlp transport: invalid endpoint URL: '${endpoint}'`);\n }\n\n if (parsed.protocol === 'https:') {\n return parsed;\n }\n\n if (parsed.protocol === 'http:') {\n if (!allowInsecureLoopback) {\n throw new Error(\n `otlp transport refuses non-HTTPS endpoint '${endpoint}'`,\n );\n }\n if (!LOOPBACK_HOSTS.has(parsed.hostname)) {\n throw new Error(\n `otlp transport: allowInsecureLoopback permits only ` +\n `localhost / 127.0.0.1 / [::1]; got '${parsed.hostname}' ` +\n `in '${endpoint}'`,\n );\n }\n return parsed;\n }\n\n throw new Error(\n `otlp transport refuses non-HTTPS endpoint '${endpoint}' ` +\n `(scheme '${parsed.protocol}' is not permitted)`,\n );\n}\n\nfunction typeName(value: unknown): string {\n if (value === null) return 'null';\n return typeof value;\n}\n","/**\n * Subpath-owned diagnostic error class + rate-limited notice helper for\n * the OTLP transport.\n *\n * Drop/diagnostic notices fired by the OTLP transport are `OtlpError`\n * instances (a subclass of `Error`) owned by this subpath — by-convention\n * compatible with the core's `PackageError` shape (`.code`,\n * `.transportName`, optional `.cause`) but with NO runtime import from\n * `src/internal/**` (TO-7). This module is INTERNAL to the subpath;\n * `src/transport-otlp/index.ts` does not re-export it. The\n * `onInternalError` hook receives the instance typed as `Error`.\n *\n * **Security (FR-009 / TO-6)**: notice messages MUST NEVER include any\n * configured request-header value. The helpers here only ever build\n * messages from the failure code, the transport name, and an optional\n * non-secret detail string supplied by the caller. Callers MUST NOT pass\n * header values into `detail`.\n *\n * Specs: `specs/007-transport-otlp/data-model.md` § OtlpFailureCode;\n * `contracts/transport-otlp-public-api.md` TO-4/TO-6.\n */\n\n/**\n * Documented `OtlpError.code` values — one per failure class. Surfaced to\n * the consumer as `err.code` on the `Error`-shaped `onInternalError`\n * argument. Each is rate-limited to one notice per class per transport\n * instance per session.\n */\nexport type OtlpFailureCode =\n | 'oversized_event'\n | 'buffer_overflow'\n | 'delivery_unavailable'\n | 'send_failed'\n | 'partial_rejection'\n | 'serialize_failed'\n | 'shutdown_failed';\n\n/**\n * Subclass of `Error` carrying a discriminating `.code`, the originating\n * transport's `.transportName`, and an optional `.cause` chain. `.name`\n * is `'OtlpError'`. `.cause` follows the ES2022 convention (absent when\n * not provided rather than `undefined`).\n */\nexport class OtlpError extends Error {\n readonly code: OtlpFailureCode;\n readonly transportName: string;\n declare readonly cause?: unknown;\n\n constructor(\n code: OtlpFailureCode,\n transportName: string,\n message: string,\n cause?: unknown,\n ) {\n super(message);\n this.name = 'OtlpError';\n this.code = code;\n this.transportName = transportName;\n if (cause !== undefined) {\n Object.defineProperty(this, 'cause', {\n value: cause,\n enumerable: true,\n writable: false,\n configurable: false,\n });\n }\n }\n}\n\n/** Type guard for `OtlpError` instances. */\nexport function isOtlpError(value: unknown): value is OtlpError {\n return value instanceof OtlpError;\n}\n\n/**\n * Minimal shape the `notifyOnce` helper needs: the transport's name, the\n * consumer error sink, and a per-code \"already notified\" ledger. The\n * concrete `OtlpTransportState` (data-model) satisfies this.\n */\nexport interface NotifyContext {\n readonly name: string;\n readonly onInternalError: (err: Error) => void;\n readonly notified: Record<OtlpFailureCode, boolean>;\n}\n\n/**\n * Emit at most ONE diagnostic notice per failure `code` per context\n * (instance) per session (FR-010). Subsequent calls with the same code are\n * silently suppressed. The `onInternalError` callback is invoked inside a\n * try/catch so a throwing consumer handler can never propagate back into\n * the transport's hot path (Principle II).\n *\n * `detail` is an optional NON-SECRET human string (e.g. an HTTP status).\n * Callers MUST NOT pass any configured header/secret value here.\n */\nexport function notifyOnce(\n ctx: NotifyContext,\n code: OtlpFailureCode,\n message: string,\n cause?: unknown,\n): void {\n if (ctx.notified[code]) return;\n ctx.notified[code] = true;\n const err = new OtlpError(\n code,\n ctx.name,\n `otlp transport '${ctx.name}': ${message}`,\n cause,\n );\n try {\n ctx.onInternalError(err);\n } catch {\n // A throwing consumer error handler must never reach the caller.\n }\n}\n\n/** Build the per-instance \"already notified\" ledger with all flags false. */\nexport function freshNotifiedLedger(): Record<OtlpFailureCode, boolean> {\n return {\n oversized_event: false,\n buffer_overflow: false,\n delivery_unavailable: false,\n send_failed: false,\n partial_rejection: false,\n serialize_failed: false,\n shutdown_failed: false,\n };\n}\n","/**\n * Pure `AttributeValue` → OTLP `AnyValue` encoder + the shared OTLP-JSON\n * value types used across the subpath.\n *\n * The OTLP/HTTP+JSON value model is hand-encoded here with ZERO runtime\n * dependencies and no `@opentelemetry/*` import (research D1, TO-7). The\n * `AttributeValue` union (string | number | boolean | null | array |\n * object) maps 1:1 onto OTLP `AnyValue` (OP-5).\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md` OP-5.\n */\n\nimport type { AttributeValue } from '../api/types.js';\n\n/**\n * OTLP `AnyValue` (JSON encoding). `null` → `{}` (an unset value) since\n * OTLP has no explicit null. A non-finite or otherwise non-integer number\n * is a `doubleValue`; an integer-safe number is an `intValue` (string, per\n * the uint64/sint64-as-string rule).\n */\nexport type AnyValue =\n | { stringValue: string }\n | { boolValue: boolean }\n | { intValue: string }\n | { doubleValue: number }\n | { arrayValue: { values: AnyValue[] } }\n | { kvlistValue: { values: KeyValue[] } }\n // biome-ignore lint/complexity/noBannedTypes: OTLP represents an unset/null value as an empty AnyValue object.\n | {};\n\n/** OTLP `KeyValue`. */\nexport interface KeyValue {\n key: string;\n value: AnyValue;\n}\n\n/** Encode a single sanitized `AttributeValue` as an OTLP `AnyValue`. */\nexport function toAnyValue(value: AttributeValue): AnyValue {\n if (value === null) {\n return {};\n }\n switch (typeof value) {\n case 'string':\n return { stringValue: value };\n case 'boolean':\n return { boolValue: value };\n case 'number':\n return Number.isInteger(value)\n ? { intValue: String(value) }\n : { doubleValue: value };\n default:\n break;\n }\n if (Array.isArray(value)) {\n return { arrayValue: { values: value.map(toAnyValue) } };\n }\n // Remaining case: a plain `{ [key: string]: AttributeValue }` object.\n return { kvlistValue: { values: toKeyValues(value) } };\n}\n\n/**\n * Encode a record of sanitized attributes as an OTLP `KeyValue[]`,\n * optionally prefixing each key (used to namespace merged context\n * attributes under `context.` — OP-4).\n */\nexport function toKeyValues(\n record: Readonly<Record<string, AttributeValue>>,\n keyPrefix = '',\n): KeyValue[] {\n const out: KeyValue[] = [];\n for (const key of Object.keys(record)) {\n const value = record[key];\n if (value === undefined) continue;\n out.push({ key: keyPrefix + key, value: toAnyValue(value) });\n }\n return out;\n}\n","/**\n * Map SafeSignal runtime-global identity to an OTLP `Resource` (OP-2 / D3).\n *\n * The `Resource` carries only identity that is constant across a batch\n * from one configured transport — `service.name`, `service.version`,\n * `deployment.environment` (standard OTel semantic-convention attributes\n * every OTLP backend understands). The federated `module.*` identity is\n * **per-logger** (it can differ between events in the same batch via\n * `withContext`), so it is attributed per-`LogRecord` (see\n * `otlp-serializer.ts`), not on the shared Resource — preserving correct\n * origin attribution (Principle VI).\n *\n * Only present fields are emitted (no empty/`null` keys). Pure and\n * dependency-free.\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md` OP-2.\n */\n\nimport type { LogContext } from '../api/types.js';\n\nimport type { KeyValue } from './attributes.js';\n\nexport interface OtlpResource {\n attributes: KeyValue[];\n}\n\n/**\n * Build the OTLP `Resource` for a batch from a representative context\n * (the batch's first event). `service.*` / `deployment.environment` are\n * runtime-global, so any event in the batch is representative.\n */\nexport function buildResource(context: LogContext): OtlpResource {\n const attributes: KeyValue[] = [];\n\n const push = (key: string, value: string | undefined): void => {\n if (typeof value === 'string' && value.length > 0) {\n attributes.push({ key, value: { stringValue: value } });\n }\n };\n\n push('service.name', context.application?.name);\n push('service.version', context.application?.version);\n push('deployment.environment', context.environment);\n\n return { attributes };\n}\n","/**\n * Pure `LogEvent[]` → OTLP/HTTP+JSON logs payload serializer (OP-1..OP-6).\n *\n * This is the heart of the subpath. It hand-builds the OTLP logs JSON\n * shape with ZERO runtime dependencies and NO `@opentelemetry/*` import\n * (research D1, TO-7): severity numbers are literal constants matching the\n * OTel `SeverityNumber` ranges (and the in-repo\n * `src/internal/telemetry/otel/mapping.ts` table), reused conceptually but\n * never imported.\n *\n * **Encoding seam (FR-015)**: `serializeBatch(...)` builds the encoder-\n * neutral `OtlpLogsRequest` object; `encode(...)` turns it into the wire\n * body. Today the only encoder is JSON; a future protobuf encoder slots in\n * behind `encode(...)` without changing the object model or the public\n * surface.\n *\n * Specs: `specs/007-transport-otlp/contracts/otlp-payload.md`.\n */\n\nimport type { LogContext, LogEvent, LogLevel } from '../api/types.js';\n\nimport { type AnyValue, type KeyValue, toKeyValues } from './attributes.js';\nimport { buildResource } from './resource.js';\n\n/** Constant instrumentation-scope name for every emitted ScopeLogs. */\nexport const SCOPE_NAME = '@tallyrow/safesignal';\n\n/**\n * OTLP `SeverityNumber` base value per SafeSignal level (OP-3 / D2). These\n * are the canonical OTLP range bases (DEBUG 5, INFO 9, WARN 13, ERROR 17)\n * and match `LEVEL_TO_SEVERITY` in the internal OTel seam.\n */\nconst LEVEL_TO_SEVERITY_NUMBER: Readonly<Record<LogLevel, number>> = {\n debug: 5,\n info: 9,\n warn: 13,\n error: 17,\n};\n\nconst LEVEL_TO_SEVERITY_TEXT: Readonly<Record<LogLevel, string>> = {\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n};\n\n// ---------------------------------------------------------------------------\n// OTLP wire shapes (JSON encoding)\n// ---------------------------------------------------------------------------\n\nexport interface OtlpLogRecord {\n timeUnixNano: string;\n observedTimeUnixNano: string;\n severityNumber: number;\n severityText: string;\n body: AnyValue;\n attributes: KeyValue[];\n /** W3C trace correlation — present only when `event.context.trace` is set. */\n traceId?: string;\n spanId?: string;\n flags?: number;\n}\n\nexport interface OtlpScopeLogs {\n scope: { name: string };\n logRecords: OtlpLogRecord[];\n}\n\nexport interface OtlpResourceLogs {\n resource: { attributes: KeyValue[] };\n scopeLogs: OtlpScopeLogs[];\n}\n\nexport interface OtlpLogsRequest {\n resourceLogs: OtlpResourceLogs[];\n}\n\n// ---------------------------------------------------------------------------\n// Mapping\n// ---------------------------------------------------------------------------\n\n/**\n * Convert one `LogEvent` to an OTLP `LogRecord`. Never mutates the input\n * (T-S4). Per-record attributes are: event attributes, then merged context\n * attributes under a `context.` prefix, then per-logger `module.*`\n * identity, then `exception.*` when an error is present (OP-4).\n *\n * `fallbackTimeMs` is a single resolved time used when `event.timestamp`\n * is unparseable, so the mapping never throws on a bad timestamp (OP-3).\n */\nexport function toLogRecord(\n event: LogEvent,\n fallbackTimeMs: number,\n): OtlpLogRecord {\n const ms = toEpochMs(event.timestamp, fallbackTimeMs);\n const nano = String(ms * 1_000_000);\n\n const attributes: KeyValue[] = toKeyValues(event.attributes);\n\n const context = event.context;\n if (context.attributes !== undefined) {\n attributes.push(...toKeyValues(context.attributes, 'context.'));\n }\n pushModuleIdentity(attributes, context);\n pushException(attributes, event);\n\n const record: OtlpLogRecord = {\n timeUnixNano: nano,\n observedTimeUnixNano: nano,\n severityNumber: LEVEL_TO_SEVERITY_NUMBER[event.level],\n severityText: LEVEL_TO_SEVERITY_TEXT[event.level],\n body: { stringValue: event.message },\n attributes,\n };\n\n // W3C trace correlation → OTLP standard top-level fields (OP/OT contracts).\n // The structured ids are already lowercase-hex (validated upstream), so they\n // are the OTLP/JSON encoding as-is — no base64, no @opentelemetry import.\n const trace = context.trace;\n if (trace !== undefined) {\n record.traceId = trace.traceId;\n record.spanId = trace.spanId;\n if (trace.traceFlags !== undefined) {\n record.flags = trace.traceFlags;\n }\n }\n\n return record;\n}\n\n/**\n * Build the encoder-neutral OTLP logs request object for a batch. The\n * Resource is derived from the first event's runtime-global identity; if\n * the batch is empty, an empty Resource is used.\n */\nexport function serializeBatch(\n batch: ReadonlyArray<LogEvent>,\n fallbackTimeMs: number,\n): OtlpLogsRequest {\n const first = batch[0];\n const resource = buildResource(first ? first.context : ({} as LogContext));\n const logRecords = batch.map((e) => toLogRecord(e, fallbackTimeMs));\n return {\n resourceLogs: [\n {\n resource,\n scopeLogs: [{ scope: { name: SCOPE_NAME }, logRecords }],\n },\n ],\n };\n}\n\n/**\n * Encoding seam (FR-015). Turns the request object into the wire body.\n * The only encoding in this feature is JSON; protobuf is a roadmap\n * follow-up that slots in here without touching callers.\n */\nexport function encode(request: OtlpLogsRequest): string {\n return JSON.stringify(request);\n}\n\n/**\n * Convenience: build + JSON-encode a batch in one call. Pure; never\n * mutates inputs.\n */\nexport function serializeOtlpJson(\n batch: ReadonlyArray<LogEvent>,\n fallbackTimeMs: number,\n): string {\n return encode(serializeBatch(batch, fallbackTimeMs));\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction toEpochMs(iso: string, fallbackMs: number): number {\n const parsed = Date.parse(iso);\n return Number.isFinite(parsed) ? parsed : fallbackMs;\n}\n\nfunction pushModuleIdentity(out: KeyValue[], context: LogContext): void {\n const mod = context.module;\n if (mod === undefined) return;\n if (typeof mod.name === 'string' && mod.name.length > 0) {\n out.push({ key: 'module.name', value: { stringValue: mod.name } });\n }\n if (typeof mod.version === 'string' && mod.version.length > 0) {\n out.push({ key: 'module.version', value: { stringValue: mod.version } });\n }\n}\n\nfunction pushException(out: KeyValue[], event: LogEvent): void {\n const err = event.error;\n if (err === undefined) return;\n out.push({ key: 'exception.type', value: { stringValue: err.name } });\n out.push({ key: 'exception.message', value: { stringValue: err.message } });\n if (typeof err.stack === 'string' && err.stack.length > 0) {\n out.push({\n key: 'exception.stacktrace',\n value: { stringValue: err.stack },\n });\n }\n}\n","/**\n * Outbound W3C `traceparent` header injection for the `./transport-otlp`\n * delivery path (Feature 009).\n *\n * When `injectTraceparent` is enabled, a delivery request whose batch all\n * belongs to ONE valid trace gets a standard W3C `traceparent` (and, when\n * uniform, `tracestate`) request header so the ingest request is joinable to\n * its trace. SafeSignal is **carry-only**: this reads the already-normalized\n * `event.context.trace` (Feature 008 validates it once per emit, before any\n * transport sees the event) and never mints ids.\n *\n * Policy (homogeneous-only, fail-closed — contracts TI-3..TI-6, research D3/D4):\n * - Per-event key = `none` when `context.trace` is absent OR structurally\n * invalid (defensive guard for events that reach the transport without\n * passing emit-time normalization), else the full `traceparent` string\n * (so differing flags ⇒ differing keys ⇒ not homogeneous).\n * - Inject `traceparent` iff the batch is non-empty AND every key is the\n * same non-`none` value. Empty / trace-less / heterogeneous ⇒ no header.\n * - Inject `tracestate` iff `traceparent` is injected AND every event shares\n * the same defined `traceState` within `MAX_TRACESTATE_LEN`; else omit it\n * while keeping `traceparent` (optional part dropped, valid ids kept).\n * - Consumer `options.headers` win on any collision (spread last), so the\n * injected header can never overwrite/expose an auth/secret value.\n *\n * Boundary discipline (TO-7): the only import is a **type-only** import from\n * `../api/types.js`. No `../trace/`, no `@opentelemetry/*`. Pure; never throws.\n *\n * Specs: `specs/009-traceparent-injection/contracts/traceparent-injection.md`.\n */\n\nimport type { LogEvent, TraceContext } from '../api/types.js';\n\n/** Max `tracestate` length (W3C caps the header at 512 chars), mirrored here. */\nconst MAX_TRACESTATE_LEN = 512;\n\nconst TRACE_ID_RE = /^[0-9a-f]{32}$/;\nconst SPAN_ID_RE = /^[0-9a-f]{16}$/;\nconst ALL_ZERO_TRACE_ID = '0'.repeat(32);\nconst ALL_ZERO_SPAN_ID = '0'.repeat(16);\n\n/** Outcome of evaluating one delivery batch. */\nexport type BatchTraceparentDecision =\n | { inject: false }\n | { inject: true; traceparent: string; tracestate?: string };\n\ninterface ResolvedTrace {\n /** `'none'`, or the `traceparent` string (which also uniquely keys the trace). */\n readonly key: string;\n /** The `traceparent` string when valid, else `null`. */\n readonly traceparent: string | null;\n /** A bounded, non-empty `traceState`, else `null`. */\n readonly traceState: string | null;\n}\n\nconst NONE: ResolvedTrace = {\n key: 'none',\n traceparent: null,\n traceState: null,\n};\n\nfunction hasValidIds(trace: TraceContext): boolean {\n return (\n typeof trace.traceId === 'string' &&\n TRACE_ID_RE.test(trace.traceId) &&\n trace.traceId !== ALL_ZERO_TRACE_ID &&\n typeof trace.spanId === 'string' &&\n SPAN_ID_RE.test(trace.spanId) &&\n trace.spanId !== ALL_ZERO_SPAN_ID\n );\n}\n\nfunction flagsHex(traceFlags: number | undefined): string {\n const n =\n typeof traceFlags === 'number' &&\n Number.isInteger(traceFlags) &&\n traceFlags >= 0 &&\n traceFlags <= 255\n ? traceFlags\n : 0;\n return n.toString(16).padStart(2, '0');\n}\n\nfunction resolve(event: LogEvent): ResolvedTrace {\n const trace = event.context.trace;\n if (trace === undefined || !hasValidIds(trace)) {\n return NONE;\n }\n const traceparent = `00-${trace.traceId}-${trace.spanId}-${flagsHex(\n trace.traceFlags,\n )}`;\n const traceState =\n typeof trace.traceState === 'string' &&\n trace.traceState.length > 0 &&\n trace.traceState.length <= MAX_TRACESTATE_LEN\n ? trace.traceState\n : null;\n return { key: traceparent, traceparent, traceState };\n}\n\n/**\n * Decide whether a delivery batch warrants a `traceparent`/`tracestate`\n * header. Pure; never throws.\n */\nexport function decideBatchTraceparent(\n events: ReadonlyArray<LogEvent>,\n): BatchTraceparentDecision {\n if (events.length === 0) {\n return { inject: false };\n }\n const first = resolve(events[0] as LogEvent);\n if (first.key === 'none') {\n return { inject: false };\n }\n\n let tracestateUniform = true;\n for (let i = 1; i < events.length; i += 1) {\n const r = resolve(events[i] as LogEvent);\n if (r.key !== first.key) {\n return { inject: false }; // heterogeneous (or one is `none`)\n }\n if (r.traceState !== first.traceState) {\n tracestateUniform = false;\n }\n }\n\n const traceparent = first.traceparent as string;\n if (tracestateUniform && first.traceState !== null) {\n return { inject: true, traceparent, tracestate: first.traceState };\n }\n return { inject: true, traceparent };\n}\n\n/**\n * Build the per-request header map. When injection is disabled or the batch is\n * not homogeneous, returns the SAME `base` reference (no allocation,\n * byte-identical request). Otherwise returns a new map with `traceparent`\n * (and `tracestate`) UNDER the consumer `base` headers, so `base` always wins\n * on collision (TI-6). Never mutates `base`; never throws.\n */\nexport function buildRequestHeaders(\n base: Readonly<Record<string, string>>,\n events: ReadonlyArray<LogEvent>,\n enabled: boolean,\n): Readonly<Record<string, string>> {\n if (!enabled) {\n return base;\n }\n const decision = decideBatchTraceparent(events);\n if (!decision.inject) {\n return base;\n }\n const injected: Record<string, string> = {\n traceparent: decision.traceparent,\n };\n if (decision.tracestate !== undefined) {\n injected.tracestate = decision.tracestate;\n }\n return { ...injected, ...base };\n}\n","/**\n * `createOtlpTransport` — factory for the `./transport-otlp` subpath.\n *\n * Composes the subpath primitives into a `Transport` that delivers\n * fully-processed `LogEvent`s to an OTLP logs backend as OTLP/HTTP+JSON,\n * batched, fire-and-forget (no retry), fail-closed.\n *\n * Delivery policy (research D6/D7, contracts TO-2..TO-8):\n *\n * send(event)\n * ├── if shutdownComplete: no-op\n * ├── lazily install the pagehide best-effort flush (first send)\n * ├── if serialized record > maxRecordBytes → oversized_event drop\n * ├── if buffered >= maxBufferedEvents → buffer_overflow drop\n * └── batcher.push(event) // flush on size / age\n *\n * flush(batch) // batcher callback\n * ├── serialize(batch) (fail-closed: serialize_failed → drop)\n * └── deliver(endpoint, headers, body) // fetch keepalive, never throws\n * ├── delivered ─────────────────── done\n * ├── unavailable ───────────────── delivery_unavailable notice\n * ├── send_failed ───────────────── send_failed notice (+cause)\n * └── partial_rejection ─────────── partial_rejection notice\n *\n * Every notice is rate-limited to one per failure class per instance per\n * session and NEVER carries a configured header/secret value (FR-009).\n * `send`/`flush`/`shutdown` NEVER throw or reject to the caller; only\n * construction-time validation throws, at the consumer's call site.\n *\n * Boundary discipline (TO-7): the only `src/` import is a type-only import\n * from `'../api/types.js'`. No `@opentelemetry/*` and no\n * `../internal/telemetry/otel/` import — the payload is hand-serialized.\n *\n * Specs: `specs/007-transport-otlp/contracts/*`, `data-model.md`.\n */\n\nimport type { LogEvent, Transport } from '../api/types.js';\n\nimport { type Batcher, createBatcher } from './batcher.js';\nimport { type DeliveryResult, deliver } from './delivery.js';\nimport { validateEndpoint } from './endpoint-validation.js';\nimport {\n freshNotifiedLedger,\n type NotifyContext,\n notifyOnce,\n type OtlpFailureCode,\n} from './errors.js';\nimport { encode, serializeBatch, toLogRecord } from './otlp-serializer.js';\nimport { buildRequestHeaders } from './traceparent-header.js';\n\n// ---------------------------------------------------------------------------\n// Public options shape (data-model.md § OtlpTransportOptions)\n// ---------------------------------------------------------------------------\n\nexport interface OtlpTransportOptions {\n /** Full OTLP logs endpoint URL (e.g. `https://otlp.example.com/v1/logs`). */\n endpoint: string;\n /** Static request headers (e.g. auth). Sent only on the wire. */\n headers?: Record<string, string>;\n /** Batch flush triggers. */\n batching?: {\n maxBatchSize: number;\n maxBatchAgeMs?: number;\n };\n /** Hard cap on buffered events; over-cap events are dropped. Default 1000. */\n maxBufferedEvents?: number;\n /** Per-record size guard in bytes; larger records are dropped. Default 64 KiB. */\n maxRecordBytes?: number;\n /** Stable diagnostic identifier (`Transport.name`). Default `'otlp'`. */\n name?: string;\n /** Permit `http://` localhost/127.0.0.1/[::1] only. Default false. */\n allowInsecureLoopback?: boolean;\n /**\n * When `true`, set a W3C `traceparent` (and `tracestate`) request header on a\n * delivery request whose batch all shares one valid trace context. Off by\n * default; homogeneous-only and fail-closed — a mixed, trace-less, or empty\n * batch sets no header, and the event payload is unchanged either way.\n * Carry-only: built from each event's existing `context.trace`; no ids are\n * minted. See `specs/009-traceparent-injection/`.\n */\n injectTraceparent?: boolean;\n /** Receives rate-limited diagnostic notices. Never carries header values. */\n onInternalError?: (err: Error) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal per-instance state (data-model.md § OtlpTransportState)\n// ---------------------------------------------------------------------------\n\ninterface OtlpTransportState extends NotifyContext {\n readonly endpoint: string;\n readonly headers: Readonly<Record<string, string>>;\n readonly name: string;\n readonly onInternalError: (err: Error) => void;\n readonly maxBufferedEvents: number;\n readonly maxRecordBytes: number;\n readonly injectTraceparent: boolean;\n readonly notified: Record<OtlpFailureCode, boolean>;\n batcher: Batcher;\n pagehideInstalled: boolean;\n pagehideUninstall: (() => void) | null;\n shutdownComplete: boolean;\n inFlight: Set<Promise<void>>;\n /**\n * Events accepted but not yet delivered (buffered in the batcher + in\n * flight). The true memory bound in this no-retry design: incremented on\n * accept, decremented when a batch's delivery settles. Capped at\n * `maxBufferedEvents` so a slow/failing backend cannot grow memory\n * unboundedly.\n */\n pending: number;\n}\n\nconst DEFAULTS = {\n maxBatchSize: 20,\n maxBatchAgeMs: 5000,\n maxBufferedEvents: 1000,\n maxRecordBytes: 65536,\n name: 'otlp',\n} as const;\n\n// ---------------------------------------------------------------------------\n// Construction-time validation (TO-2)\n// ---------------------------------------------------------------------------\n\nfunction validateOptions(options: OtlpTransportOptions): void {\n if (typeof options !== 'object' || options === null) {\n throw new TypeError('otlp transport: options must be a non-null object');\n }\n const { headers, batching, maxBufferedEvents, maxRecordBytes } = options;\n\n if (\n options.injectTraceparent !== undefined &&\n typeof options.injectTraceparent !== 'boolean'\n ) {\n throw new TypeError('otlp transport: injectTraceparent must be a boolean');\n }\n\n if (headers !== undefined) {\n if (typeof headers !== 'object' || headers === null) {\n throw new TypeError('otlp transport: headers must be an object');\n }\n for (const key of Object.keys(headers)) {\n if (typeof headers[key] !== 'string') {\n throw new TypeError(\n `otlp transport: header '${key}' must be a string value`,\n );\n }\n }\n }\n\n const maxBatchSize = batching?.maxBatchSize ?? DEFAULTS.maxBatchSize;\n if (!Number.isInteger(maxBatchSize) || maxBatchSize < 1) {\n throw new RangeError(\n 'otlp transport: batching.maxBatchSize must be an integer >= 1',\n );\n }\n const cap = maxBufferedEvents ?? DEFAULTS.maxBufferedEvents;\n if (!Number.isInteger(cap) || cap < maxBatchSize) {\n throw new RangeError(\n 'otlp transport: maxBufferedEvents must be an integer >= maxBatchSize',\n );\n }\n const recBytes = maxRecordBytes ?? DEFAULTS.maxRecordBytes;\n if (!Number.isInteger(recBytes) || recBytes < 1) {\n throw new RangeError(\n 'otlp transport: maxRecordBytes must be an integer >= 1',\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\nexport function createOtlpTransport(options: OtlpTransportOptions): Transport {\n validateOptions(options);\n const allowInsecureLoopback = options.allowInsecureLoopback ?? false;\n // Throws at the consumer call site on a bad endpoint (off the hot path).\n validateEndpoint(options.endpoint, allowInsecureLoopback);\n\n const name = options.name ?? DEFAULTS.name;\n const maxBatchSize = options.batching?.maxBatchSize ?? DEFAULTS.maxBatchSize;\n const maxBatchAgeMs =\n options.batching?.maxBatchAgeMs ?? DEFAULTS.maxBatchAgeMs;\n\n const state: OtlpTransportState = {\n endpoint: options.endpoint,\n // Copy + freeze the headers so later consumer mutation cannot change\n // what we send, and so nothing outside delivery can read them.\n headers: Object.freeze({ ...(options.headers ?? {}) }),\n name,\n onInternalError: options.onInternalError ?? (() => undefined),\n maxBufferedEvents: options.maxBufferedEvents ?? DEFAULTS.maxBufferedEvents,\n maxRecordBytes: options.maxRecordBytes ?? DEFAULTS.maxRecordBytes,\n injectTraceparent: options.injectTraceparent ?? false,\n notified: freshNotifiedLedger(),\n // Placeholder; real batcher assigned below once the flush closure exists.\n batcher: undefined as unknown as Batcher,\n pagehideInstalled: false,\n pagehideUninstall: null,\n shutdownComplete: false,\n inFlight: new Set<Promise<void>>(),\n pending: 0,\n };\n\n state.batcher = createBatcher({\n maxBatchSize,\n maxBatchAgeMs,\n flush: (events) => {\n void flushBatch(state, events);\n },\n });\n\n return {\n name,\n send(event: LogEvent): void {\n if (state.shutdownComplete) return;\n ensurePagehide(state);\n\n // Per-record size guard (oversized_event) — measure the serialized\n // OTLP LogRecord, drop if it exceeds the budget. Never throws.\n try {\n const record = toLogRecord(event, Date.now());\n if (byteLength(JSON.stringify(record)) > state.maxRecordBytes) {\n notifyOnce(\n state,\n 'oversized_event',\n `dropped an event whose serialized record exceeds ${state.maxRecordBytes} bytes`,\n );\n return;\n }\n } catch (cause) {\n notifyOnce(\n state,\n 'serialize_failed',\n 'dropped an event that failed to serialize',\n cause,\n );\n return;\n }\n\n // Hard memory cap on undelivered (buffered + in-flight) events.\n if (state.pending >= state.maxBufferedEvents) {\n notifyOnce(\n state,\n 'buffer_overflow',\n `${state.maxBufferedEvents} events undelivered; dropping event`,\n );\n return;\n }\n state.pending += 1;\n state.batcher.push(event);\n },\n async flush(): Promise<void> {\n state.batcher.flush();\n await settleInFlight(state);\n },\n async shutdown(): Promise<void> {\n if (state.shutdownComplete) {\n await settleInFlight(state);\n return;\n }\n state.shutdownComplete = true;\n try {\n state.batcher.flush();\n await settleInFlight(state);\n } catch (cause) {\n notifyOnce(state, 'shutdown_failed', 'shutdown flush failed', cause);\n } finally {\n teardownPagehide(state);\n state.batcher.shutdown();\n }\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Batch delivery (never throws)\n// ---------------------------------------------------------------------------\n\nasync function flushBatch(\n state: OtlpTransportState,\n events: LogEvent[],\n): Promise<void> {\n const count = events.length;\n let body: string;\n try {\n body = encode(serializeBatch(events, Date.now()));\n } catch (cause) {\n // Fail-closed: drop the batch, but still release its pending budget.\n state.pending = Math.max(0, state.pending - count);\n notifyOnce(\n state,\n 'serialize_failed',\n 'dropped a batch that failed to serialize',\n cause,\n );\n return;\n }\n\n // Per-request header map. Best-effort `traceparent` injection (Feature 009)\n // never blocks delivery: any failure falls back to the plain configured\n // headers. Returns the same `state.headers` reference when disabled or the\n // batch is not homogeneous, so the request stays byte-identical.\n let headers: Readonly<Record<string, string>> = state.headers;\n try {\n headers = buildRequestHeaders(\n state.headers,\n events,\n state.injectTraceparent,\n );\n } catch {\n headers = state.headers;\n }\n\n const promise = (async (): Promise<void> => {\n const result: DeliveryResult = await deliver(state.endpoint, headers, body);\n mapResult(state, result);\n })().catch(() => {\n // Defensive: deliver() is contracted not to reject, but never let an\n // unexpected rejection escape into an unhandled rejection.\n });\n\n state.inFlight.add(promise);\n void promise.finally(() => {\n state.inFlight.delete(promise);\n // Release this batch's pending budget once delivery settles.\n state.pending = Math.max(0, state.pending - count);\n });\n}\n\nfunction mapResult(state: OtlpTransportState, result: DeliveryResult): void {\n switch (result.kind) {\n case 'delivered':\n return;\n case 'unavailable':\n notifyOnce(\n state,\n 'delivery_unavailable',\n 'fetch is unavailable; dropping batch',\n );\n return;\n case 'send_failed':\n notifyOnce(\n state,\n 'send_failed',\n `delivery failed (${result.detail})`,\n result.cause,\n );\n return;\n case 'partial_rejection':\n notifyOnce(\n state,\n 'partial_rejection',\n `backend rejected ${result.rejected} record(s)`,\n );\n return;\n }\n}\n\nasync function settleInFlight(state: OtlpTransportState): Promise<void> {\n // Snapshot: new deliveries triggered during await are not awaited here\n // (flush() is a point-in-time drain), matching beacon's flush semantics.\n await Promise.all([...state.inFlight]);\n}\n\n// ---------------------------------------------------------------------------\n// Lazy pagehide best-effort flush (Principle VII — nothing at Logger create)\n// ---------------------------------------------------------------------------\n\nfunction ensurePagehide(state: OtlpTransportState): void {\n if (state.pagehideInstalled) return;\n const target = globalThis as {\n addEventListener?: typeof globalThis.addEventListener;\n removeEventListener?: typeof globalThis.removeEventListener;\n };\n state.pagehideInstalled = true;\n if (typeof target.addEventListener !== 'function') {\n state.pagehideUninstall = null;\n return;\n }\n const handler = (): void => {\n // Best-effort: drain via the keepalive fetch path. Never blocks unload.\n state.batcher.flush();\n };\n target.addEventListener('pagehide', handler);\n state.pagehideUninstall = (): void => {\n if (typeof target.removeEventListener === 'function') {\n target.removeEventListener('pagehide', handler);\n }\n };\n}\n\nfunction teardownPagehide(state: OtlpTransportState): void {\n if (state.pagehideUninstall !== null) {\n state.pagehideUninstall();\n state.pagehideUninstall = null;\n }\n state.pagehideInstalled = false;\n}\n\nfunction byteLength(s: string): number {\n return new TextEncoder().encode(s).length;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tallyrow/safesignal",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -22,8 +22,12 @@
|
|
|
22
22
|
],
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
|
-
"url": "git+https://
|
|
25
|
+
"url": "git+https://github.com/TallyRow/safesignal.git"
|
|
26
26
|
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/TallyRow/safesignal/issues"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/TallyRow/safesignal#readme",
|
|
27
31
|
"type": "module",
|
|
28
32
|
"sideEffects": false,
|
|
29
33
|
"files": [
|