@logbrew/sdk 0.1.3 → 0.1.4

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
@@ -1,5 +1,9 @@
1
1
  # @logbrew/sdk
2
2
 
3
+ <p align="center">
4
+ <img src="https://raw.githubusercontent.com/LogBrewCo/sdk/main/assets/brand/logbrew-logo-transparent-512.png" alt="LogBrew logo" width="96" height="96">
5
+ </p>
6
+
3
7
  Public JavaScript SDK for creating LogBrew event batches, validating them locally, and flushing them through a transport.
4
8
 
5
9
  ## Install
@@ -11,7 +15,7 @@ pnpm add @logbrew/sdk
11
15
 
12
16
  The package supports both ESM `import` and CommonJS `require`.
13
17
  The shipped package also includes `.d.ts` and `.d.cts` declarations so ESM and CommonJS TypeScript consumers can install it directly without a separate build step.
14
- The package ships copyable examples under `node_modules/@logbrew/sdk/examples/`. Use the fake `LOGBREW_API_KEY` placeholder in docs, keep the real key in your app configuration, and call `previewJson()` when you want to inspect queued JSON before sending. Type declarations document payload shapes such as `ReleaseAttributes`, `SpanAttributes`, `MetricAttributes`, transport responses, SDK errors, lifecycle helpers, W3C trace helpers, product timeline helpers, console capture, Pino destination, and Winston transport APIs.
18
+ The package ships copyable examples under `node_modules/@logbrew/sdk/examples/`. Use the fake `LOGBREW_API_KEY` placeholder in docs, keep the real key in your app configuration, and call `previewJson()` when you want to inspect queued JSON before sending. Type declarations document payload shapes such as `ReleaseAttributes`, `SpanAttributes`, `MetricAttributes`, transport responses, SDK errors, lifecycle helpers, W3C trace helpers, support-ticket draft helpers, product timeline helpers, console capture, Pino destination, and Winston transport APIs.
15
19
 
16
20
  After install, discover and run the packaged examples:
17
21
 
@@ -21,6 +25,117 @@ node node_modules/@logbrew/sdk/examples/index.mjs agent-timeline
21
25
  npm --prefix node_modules/@logbrew/sdk/examples run agent-timeline
22
26
  ```
23
27
 
28
+ For Vite apps, add the build-time release-artifact plugin to `vite.config.js`. It enables hidden source maps when your config has not chosen a source-map mode, injects matching Debug IDs after the build, strips embedded source text and local source prefixes, writes a privacy-bounded manifest, and can upload the prepared artifacts before the build completes:
29
+
30
+ ```js
31
+ import { createLogBrewViteReleaseArtifactsPlugin } from "@logbrew/sdk/vite-release-artifacts";
32
+
33
+ export default {
34
+ plugins: [
35
+ createLogBrewViteReleaseArtifactsPlugin({
36
+ release: "web@1.2.3",
37
+ environment: "production",
38
+ service: "checkout-web",
39
+ projectId: "550e8400-e29b-41d4-a716-446655440000",
40
+ minifiedPathPrefix: "https://cdn.example/assets",
41
+ upload: {
42
+ endpoint: "https://api.logbrew.com/api/release-artifacts",
43
+ allowHostedUpload: true,
44
+ maxRetries: 2
45
+ }
46
+ })
47
+ ]
48
+ };
49
+ ```
50
+
51
+ Set `LOGBREW_RELEASE_ARTIFACT_TOKEN` in the build environment to a dedicated release-artifact token. Use `tokenEnv` when your CI uses a different environment variable name, or `dryRun: true` to prepare the complete build output without a network request. The plugin runs only during Vite builds, keeps upload disabled when `upload` is omitted, and fails the build when preparation or upload cannot complete safely. It never uses normal SDK ingest keys or account/session API values.
52
+
53
+ The package also ships the dependency-free `logbrew-release-artifacts` command for JavaScript source-map preparation and upload. Use it after your frontend build to inject matching Debug IDs, strip embedded source text by default, and create a privacy-bounded manifest that can be inspected before upload:
54
+
55
+ ```bash
56
+ npx logbrew-release-artifacts prepare-js \
57
+ --build-dir dist \
58
+ --strip-sources-content \
59
+ --strip-source-prefix "$PWD" \
60
+ --write
61
+
62
+ npx logbrew-release-artifacts manifest-js \
63
+ --build-dir dist \
64
+ --project-id 550e8400-e29b-41d4-a716-446655440000 \
65
+ --release web@1.2.3 \
66
+ --environment production \
67
+ --service checkout-web \
68
+ --minified-path-prefix https://cdn.example/assets \
69
+ > logbrew-release-artifacts.json
70
+
71
+ npx logbrew-release-artifacts symbolicate-js \
72
+ --build-dir dist \
73
+ --manifest logbrew-release-artifacts.json \
74
+ --stack-frame "at checkout (https://cdn.example/assets/app.js:1:1)"
75
+
76
+ npx logbrew-release-artifacts symbolicate-js \
77
+ --build-dir dist \
78
+ --manifest logbrew-release-artifacts.json \
79
+ --issue-event ./captured-logbrew-issue.json \
80
+ --source-root "$PWD" \
81
+ --context-lines 1
82
+
83
+ npx logbrew-release-artifacts upload-js \
84
+ --build-dir dist \
85
+ --manifest logbrew-release-artifacts.json \
86
+ --endpoint http://127.0.0.1:4319/release-artifacts \
87
+ --dry-run
88
+ ```
89
+
90
+ The `symbolicate-js` command resolves either one generated stack frame or a captured LogBrew issue event with `attributes.metadata.releaseArtifactDebugId`, `releaseArtifactCodeFile`, `errorFrameLine`, and `errorFrameColumn` through the prepared manifest. Use it to catch bad path prefixes, mismatched release/environment/service values, embedded source content, and local source-path leaks before deploy. Add `--source-root` only when you want the local report to include bounded app-owned source context; the source lines stay in the local command output and are not added to runtime SDK events or upload payloads. The `upload-js` command revalidates the manifest and can post the manifest/minified/source-map parts to a local loopback intake. For a hosted release-artifact endpoint, opt in explicitly and keep the release-artifact auth value in an environment variable:
91
+
92
+ ```bash
93
+ export LOGBREW_RELEASE_ARTIFACT_AUTH="<release-artifact-auth>"
94
+
95
+ npx logbrew-release-artifacts upload-js \
96
+ --build-dir dist \
97
+ --manifest logbrew-release-artifacts.json \
98
+ --endpoint https://api.logbrew.com/api/release-artifacts \
99
+ --token-env LOGBREW_RELEASE_ARTIFACT_AUTH \
100
+ --allow-hosted
101
+ ```
102
+
103
+ Non-loopback endpoints require `--allow-hosted`, a UUID `projectId` created by `manifest-js --project-id`, HTTPS, and no embedded auth values, query strings, or fragments. Local loopback preparation remains valid without a project ID. The upload command never uses normal SDK ingest keys or account/session API auth values. Full backend-symbolicated issue support is separate from artifact upload until your project has completed hosted symbolication for its release.
104
+
105
+ When you capture a JavaScript error, use `createIssueAttributesFromError()` to keep error metadata structured and source-map-friendly without sending raw stack text by default. Pass a Debug ID map from your app-owned build setup when you want the issue event to carry release-artifact metadata:
106
+
107
+ ```js
108
+ import { createIssueAttributesFromError, LogBrewClient } from "@logbrew/sdk";
109
+
110
+ const client = LogBrewClient.create({
111
+ apiKey: "LOGBREW_API_KEY",
112
+ sdkName: "checkout-web",
113
+ sdkVersion: "1.0.0"
114
+ });
115
+
116
+ try {
117
+ checkout();
118
+ } catch (error) {
119
+ client.issue(
120
+ "evt_checkout_error",
121
+ new Date().toISOString(),
122
+ createIssueAttributesFromError(error, {
123
+ release: "web@1.2.3",
124
+ environment: "production",
125
+ service: "checkout-web",
126
+ runtime: "browser",
127
+ fingerprint: "checkout-runtime-error",
128
+ debugIdMap: {
129
+ "https://cdn.example/assets/app.js": "11111111-2222-4333-8444-555555555555"
130
+ },
131
+ metadata: { routeTemplate: "/checkout" }
132
+ })
133
+ );
134
+ }
135
+ ```
136
+
137
+ The helper records the error name/message and up to 32 ordered generated `stackFrames`, with query strings, hashes, and local absolute prefixes removed. Each frame carries only filename, positive line/column, and an optional matched Debug ID. Existing first-frame metadata remains available for compatible grouping and tooling. The helper also emits an `issueGroupingKey` based on source, error type, and the sanitized first frame, plus an optional app-owned `issueFingerprint` when you pass a stable, safe, low-cardinality `fingerprint`. Nested `Error.cause` chains and `AggregateError.errors` are summarized as bounded cause counts, types, and sources without copying nested messages or stacks. Raw stack text is included only with `includeErrorStack: true`.
138
+
24
139
  ## Example
25
140
 
26
141
  ```js
@@ -93,6 +208,7 @@ Use `parseTraceparent()`, `createTraceparent()`, and `spanAttributesFromTracepar
93
208
 
94
209
  ```js
95
210
  import {
211
+ createTraceContextHeaders,
96
212
  createTraceparentHeaders,
97
213
  LogBrewClient,
98
214
  RecordingTransport,
@@ -111,6 +227,13 @@ const span = spanAttributesFromTraceparent(incomingTraceparent, {
111
227
  spanId: "b7ad6b7169203331",
112
228
  status: "ok",
113
229
  durationMs: 18.4,
230
+ events: [{ name: "cache.lookup", metadata: { hit: false, system: "redis" } }],
231
+ links: [{
232
+ traceId: "11111111111111111111111111111111",
233
+ spanId: "2222222222222222",
234
+ sampled: true,
235
+ metadata: { relation: "batch_item" }
236
+ }],
114
237
  metadata: { service: "checkout" }
115
238
  });
116
239
  client.span("evt_checkout_span", "2026-06-02T10:00:04Z", span);
@@ -123,10 +246,123 @@ await fetch("https://example.invalid/payments", {
123
246
  })
124
247
  });
125
248
 
249
+ await fetch("https://example.invalid/fulfillment", {
250
+ headers: createTraceContextHeaders({
251
+ traceId: span.traceId,
252
+ spanId: span.spanId,
253
+ traceFlags: "01",
254
+ tracestate: [{ key: "rojo", value: "00f067aa0ba902b7" }],
255
+ baggage: [{ key: "release", value: "checkout@1.2.3" }]
256
+ })
257
+ });
258
+
126
259
  await client.flush(RecordingTransport.alwaysAccept());
127
260
  ```
128
261
 
129
- The helpers validate the W3C `version-traceId-parentSpanId-traceFlags` shape, reject all-zero trace/span ids, normalize valid ids to lowercase, expose the sampled flag from `traceFlags`, and keep span metadata primitive-only. `createTraceparentHeaders()` returns an explicit outbound carrier with only `traceparent`. The helpers do not install OpenTelemetry or patch HTTP clients; use them when you need explicit interop in code you own.
262
+ The helpers validate the W3C `version-traceId-parentSpanId-traceFlags` shape, reject all-zero trace/span ids, normalize valid ids to lowercase, expose the sampled flag from `traceFlags`, and keep span metadata primitive-only. Optional span `events` record up to eight low-cardinality milestones with optional timestamps and primitive metadata only. Optional span `links` record up to eight privacy-bounded references to related trace/span IDs for batch, fan-out, queue, or retry workflows. `createTraceparentHeaders()` returns an explicit outbound carrier with only `traceparent`. `parseTracestate()`, `createTracestate()`, `parseBaggage()`, `createBaggage()`, and `createTraceContextHeaders()` add opt-in W3C `tracestate` and `baggage` propagation with bounded entry counts and header sizes. The helpers do not install OpenTelemetry, patch HTTP clients, infer baggage/tracestate automatically, or capture payloads; use them when you need explicit interop in code you own.
263
+
264
+ If your app already installs OpenTelemetry JS, use `logbrewTraceContextFromCurrentOpenTelemetrySpan()` to copy the current active OTel span into a LogBrew child trace before recording logs, spans, or actions:
265
+
266
+ ```js
267
+ import {
268
+ LogBrewClient,
269
+ logbrewTraceContextFromCurrentOpenTelemetrySpan
270
+ } from "@logbrew/sdk";
271
+
272
+ const client = LogBrewClient.create({
273
+ apiKey: "LOGBREW_API_KEY",
274
+ sdkName: "checkout-web",
275
+ sdkVersion: "1.0.0"
276
+ });
277
+
278
+ const trace = logbrewTraceContextFromCurrentOpenTelemetrySpan();
279
+ if (trace) {
280
+ client.log("evt_checkout_log", "2026-06-02T10:00:03Z", {
281
+ message: "checkout step rendered",
282
+ level: "info",
283
+ logger: "checkout",
284
+ metadata: {
285
+ release: "checkout@1.2.3",
286
+ environment: "production",
287
+ traceId: trace.traceId,
288
+ spanId: trace.spanId,
289
+ parentSpanId: trace.parentSpanId,
290
+ sampled: trace.sampled
291
+ }
292
+ });
293
+ }
294
+ ```
295
+
296
+ The OpenTelemetry helpers also accept explicit `spanContext` and `span` objects through `logbrewTraceContextFromOpenTelemetrySpanContext()` and `logbrewTraceContextFromOpenTelemetrySpan()`. They duck-type the public OTel shape, return `null` when OpenTelemetry is absent or invalid, create a fresh LogBrew child span ID by default, and copy only valid trace ID, parent span ID, and sampled state.
297
+
298
+ If your app already owns an OpenTelemetry provider, `createLogBrewOpenTelemetrySpanProcessor()` can convert ended OTel `ReadableSpan` objects into queued LogBrew spans without making LogBrew own your provider, exporter, or instrumentation setup:
299
+
300
+ ```js
301
+ import { SpanKind } from "@opentelemetry/api";
302
+ import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
303
+ import {
304
+ createLogBrewOpenTelemetrySpanProcessor,
305
+ LogBrewClient,
306
+ RecordingTransport
307
+ } from "@logbrew/sdk";
308
+
309
+ const client = LogBrewClient.create({
310
+ apiKey: "LOGBREW_API_KEY",
311
+ sdkName: "checkout-api",
312
+ sdkVersion: "1.0.0"
313
+ });
314
+ const processor = createLogBrewOpenTelemetrySpanProcessor({
315
+ client,
316
+ transport: RecordingTransport.alwaysAccept(),
317
+ eventAttributeKeys: ["cache.hit"],
318
+ includeTraceSummary: true,
319
+ linkAttributeKeys: ["messaging.operation.name"],
320
+ metadata: { release: "checkout@1.2.3", environment: "production" }
321
+ });
322
+ const provider = new BasicTracerProvider({ spanProcessors: [processor] });
323
+ const tracer = provider.getTracer("checkout-api");
324
+
325
+ const span = tracer.startSpan("GET /orders/:id", {
326
+ kind: SpanKind.CLIENT,
327
+ attributes: {
328
+ "http.request.method": "GET",
329
+ "http.response.status_code": 200,
330
+ "http.route": "/orders/:id"
331
+ }
332
+ });
333
+ span.addEvent("cache.lookup", { "cache.hit": false });
334
+ span.end();
335
+
336
+ await processor.forceFlush();
337
+ ```
338
+
339
+ If your OpenTelemetry setup already uses standard processors such as `SimpleSpanProcessor` or `BatchSpanProcessor`, use `createLogBrewOpenTelemetrySpanExporter()` instead:
340
+
341
+ ```js
342
+ import { BasicTracerProvider, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
343
+ import {
344
+ createLogBrewOpenTelemetrySpanExporter,
345
+ LogBrewClient,
346
+ RecordingTransport
347
+ } from "@logbrew/sdk";
348
+
349
+ const client = LogBrewClient.create({
350
+ apiKey: "LOGBREW_API_KEY",
351
+ sdkName: "checkout-api",
352
+ sdkVersion: "1.0.0"
353
+ });
354
+ const exporter = createLogBrewOpenTelemetrySpanExporter({
355
+ client,
356
+ transport: RecordingTransport.alwaysAccept(),
357
+ includeTraceSummary: true,
358
+ metadata: { release: "checkout@1.2.3", environment: "production" }
359
+ });
360
+ const provider = new BasicTracerProvider({
361
+ spanProcessors: [new SimpleSpanProcessor(exporter)]
362
+ });
363
+ ```
364
+
365
+ The OTel processor and exporter follow normal OTel sampled-span behavior by default and summarize up to eight span events and eight links. Set `includeTraceSummary: true` when you also want one synthetic `opentelemetry.trace:<root-name>` span per trace on processor `forceFlush()`/`shutdown()` or exporter `export()`/`forceFlush()`/`shutdown()`; the summary carries the trace ID, span count, error count, root span ID/name/kind, duration, and safe service/environment/route metadata so a batch reads like one request or transaction. They copy only a small safe default set such as service, environment, route, method, status code, span kind, instrumentation scope, exception type, exception event count, escaped exception count, and dropped-count metadata. Exception counts scan the readable span's events, while emitted exception type lists stay bounded. Escaped exception events on unset-status spans mark the LogBrew span as `error`; explicit OTel `OK` status stays `ok`. Additional event/link/span/resource attributes require explicit allowlists, and high-risk keys such as full URLs, headers, query strings, payloads, cookies, private auth values, DB statements, exception messages, and stacks stay blocked. Concurrent flush calls use one active send and one coalesced trailing drain: later spans leave the queue before their caller completes, while a failed active send consumes no extra retry budget. These helpers do not add an OpenTelemetry dependency, patch clients, own tracer providers, serialize baggage or tracestate, copy raw propagation headers, or capture request/response bodies.
130
366
 
131
367
  LogBrew severity categories are `info`, `warning`, `error`, and `critical`. The JavaScript SDK accepts common runtime aliases such as `trace`, `debug`, `warn`, and `fatal` for compatibility, then serializes canonical values before queued events are sent. The shared mapping is documented in the [LogBrew severity contract](../../docs/severity-contract.md).
132
368
 
@@ -150,6 +386,100 @@ const client = LogBrewClient.create({
150
386
 
151
387
  Prefer removing sensitive values at the source before calling LogBrew. `eventFilter` is intentionally drop-only: it avoids broad mutable event processing, global scopes, and hidden context that can make observability payloads harder to reason about.
152
388
 
389
+ ## Automatic Delivery
390
+
391
+ When a client owns a `transport`, it automatically sends queued work after 5 seconds or when 50 events are waiting, whichever happens first. The one-shot timer starts only after capture, is `unref()`'d on Node.js, and is cancelled by manual flush, purge, or shutdown. Automatic sends reuse the same serialized flush path, immutable retry body, accepted-prefix acknowledgement, and persistent queue as manual calls. They do not install process, signal, page, or exit hooks.
392
+
393
+ ```js
394
+ const transport = RecordingTransport.alwaysAccept();
395
+ const client = LogBrewClient.create({
396
+ apiKey: "LOGBREW_API_KEY",
397
+ sdkName: "checkout-api",
398
+ sdkVersion: "1.0.0",
399
+ transport,
400
+ deliveryIntervalMs: 5000,
401
+ deliveryQueueThreshold: 50
402
+ });
403
+
404
+ client.log("evt_checkout_ready", new Date().toISOString(), {
405
+ level: "info",
406
+ message: "checkout ready"
407
+ });
408
+
409
+ const health = client.deliveryHealth();
410
+ console.log(JSON.stringify(health));
411
+ await client.shutdown();
412
+ ```
413
+
414
+ Set `automaticDelivery: false` for explicit manual-only operation; the owned transport still lets you call `flush()` and `shutdown()` without passing it again. Lower-level clients without an owned transport remain manual and continue to accept `flush(transport)` and `shutdown(transport)`. Retryable automatic failures use equal-jitter exponential backoff capped at 60 seconds. Authentication, rate-limit, and other non-retryable failures pause automatic sends while retaining the exact failed batch; an explicit successful `flush()` with the owned transport clears the pause.
415
+
416
+ `deliveryHealth()` returns a frozen, JSON-serializable schema. It reports lifecycle and delivery states, memory or persistent storage, current and startup-hydrated queue counts/bytes, in-flight and coalesced work, client-lifetime accepted totals, fixed drop reasons, bounded retry state, transport status classes, and monotonic-within-client transition timestamps. Counters saturate instead of overflowing. The snapshot never includes event content or IDs, messages or attributes, authentication material, transport destinations or headers, filesystem paths, arbitrary metadata, numeric HTTP status, response text, or raw errors.
417
+
418
+ ## Queue Bounds
419
+
420
+ `LogBrewClient` keeps a count-and-byte-bounded in-memory queue so heavy logging bursts cannot grow without limit before the next flush. The defaults are 1000 events and 4 MiB of compact serialized event data. `pendingEvents()` and `pendingBytes()` expose the retained amount without exposing event content.
421
+
422
+ ```js
423
+ const client = LogBrewClient.create({
424
+ apiKey: "LOGBREW_API_KEY",
425
+ sdkName: "checkout-api",
426
+ sdkVersion: "1.0.0",
427
+ maxQueueSize: 500,
428
+ maxQueueBytes: 2 * 1024 * 1024,
429
+ maxBatchEvents: 100,
430
+ maxBatchBytes: 256 * 1024,
431
+ onEventDropped(drop) {
432
+ console.warn("LogBrew dropped telemetry", drop.reason, drop.eventType);
433
+ }
434
+ });
435
+ ```
436
+
437
+ When either queue limit is full, LogBrew drops the incoming event and keeps earlier context unchanged. `queue_overflow` means the event-count limit was reached, `queue_bytes_overflow` means the compact event-byte limit was reached, and `event_too_large` means one event could not fit a request body by itself. Drop callbacks are advisory and must not interrupt application logging. The default queue remains memory-only; automatic delivery reduces normal flush work, while the app still calls `shutdown()` at its owned graceful-lifecycle boundary.
438
+
439
+ Server runtimes can supply an explicit synchronous `eventStore` when they need restart recovery. The core loads and revalidates compact records during client creation, persists each accepted event before adding it to memory, persists an accepted prefix before removing it from memory, and closes the store only after successful shutdown. `purgePendingEvents()` clears both layers only while no flush or shutdown is queued or active. Implementations must provide synchronous `load`, `append`, `acknowledge`, `purge`, and `close` methods; use the encrypted `persistentQueue` adapter in `@logbrew/node` instead of writing a filesystem adapter from scratch.
440
+
441
+ Flush requests are also bounded. Core and Node default to 100 events and 256 KiB of exact UTF-8 JSON per request. The browser factory defaults to 64 KiB so its normal request body cannot exceed the existing keepalive transport ceiling. A flush sends one queue snapshot in order, splitting it on either limit. The response reports total `attempts` and acknowledged `batches`. Existing clients that only set `maxQueueSize` keep working; the byte and batch settings are additive and are also accepted by the Node and browser client factories.
442
+
443
+ ## Flush Failures
444
+
445
+ `flush()` calls are serialized so concurrent callers cannot send the same queue prefix. Each call owns the events present when its turn starts. Events captured while its transport is awaiting remain queued for the next flush. A 2xx response removes only that acknowledged prefix; if a later batch fails, the failed batch and every later event remain in their original order. Retries reuse the exact same request body.
446
+
447
+ A `401` transport response raises `SdkError` with code `unauthenticated`; a `429` response raises code `rate_limited` and includes `retryAfterMs` when the transport exposes a `Retry-After` delay. Those outcomes pause automatic delivery because authentication and account usage are app/service-owned recovery states, not SDK retry loops. HTTP `408`, `5xx`, and retryable `TransportError` exhaustion retain the failed bytes and schedule bounded equal-jitter backoff. `maxRetries` is the number of immediate retries after the first attempt and must be a non-negative integer. `shutdown()` cancels automatic scheduling, rejects new capture while it drains once, closes only after success, and reopens the intact unsent remainder after failure. LogBrew does not derive account usage locally, sleep inside transport retries, or drop queued events on rate limits.
448
+
449
+ ## Support Ticket Drafts
450
+
451
+ Use `createSupportTicketDraft()` when a developer or support agent needs a local JSON payload for the planned LogBrew support-ticket API. The helper validates the public source/category contract, converts JavaScript camelCase inputs to the planned backend create payload fields, and redacts token-like diagnostics before returning the object.
452
+
453
+ ```js
454
+ import { createSupportTicketDraft } from "@logbrew/sdk";
455
+
456
+ const draft = createSupportTicketDraft({
457
+ source: "sdk",
458
+ category: "ingest_failure",
459
+ title: "Telemetry flush failed",
460
+ description: "Flush returned usage_limit_exceeded",
461
+ projectId: "proj_123",
462
+ environment: "production",
463
+ runtime: "node@22",
464
+ framework: "express",
465
+ sdkPackage: "@logbrew/sdk",
466
+ sdkVersion: "0.1.3",
467
+ release: "checkout@1.2.3",
468
+ traceId: "4bf92f3577b34da6a3ce929d0e0e4736",
469
+ eventId: "evt_checkout_flush",
470
+ diagnostics: {
471
+ attemptCount: 2,
472
+ retryable: false,
473
+ endpoint: "https://api.example/ingest?debug=true",
474
+ apiKey: "redacted by helper"
475
+ }
476
+ });
477
+
478
+ console.log(JSON.stringify(draft, null, 2));
479
+ ```
480
+
481
+ This helper does not send data, open support tickets, call `POST /api/support/tickets`, use account/session API credentials, or infer backend usage/quota state. Support routes are backend-owned and should only be called by an explicit user or agent action after backend reports deployed support-ticket routes. Diagnostics are bounded to JSON-like values; auth values, cookies, tokens, local paths, URL origins, hidden payloads, and unsupported objects are redacted or omitted.
482
+
153
483
  ## Agent-Readable Timelines
154
484
 
155
485
  Use `createProductActionAttributes()` and `createNetworkMilestoneAttributes()` when a service already knows important product steps or API milestones. The helpers create normal `action` event attributes with primitive metadata that can be analyzed across many sessions without visual replay, global HTTP patching, payload capture, or header capture.
@@ -257,6 +587,8 @@ await destination.flush();
257
587
 
258
588
  The Pino adapter reads JSON log lines, maps Pino `trace`/`debug` to LogBrew `info`, `warn` to `warning`, `error` to `error`, and `fatal` to `critical`, captures primitive Pino fields as `context.*`, captures serialized error name/message, skips noisy runtime defaults, and omits stack text unless `includeErrorStack: true` is set. It does not patch Pino or replace application logger ownership.
259
589
 
590
+ When the app also uses a LogBrew Node or framework request helper, pass `traceProvider: getActiveLogBrewTrace` from `@logbrew/node` to add the current active `traceId`, `spanId`, optional `parentSpanId`, and `sampled` flag to each captured log. The provider is called per record, invalid or missing contexts are ignored, and no raw propagation headers, request data, payloads, or stack traces are captured.
591
+
260
592
  ## Winston Transport
261
593
 
262
594
  If a Node app already uses Winston, add the dependency-free LogBrew transport to the app-owned logger:
@@ -291,4 +623,6 @@ await logbrewTransport.flush();
291
623
 
292
624
  The Winston adapter receives Winston `info` objects, maps `debug`/`silly` to LogBrew `info`, `warn` to `warning`, `error` to `error`, `fatal`/`critical` to `critical`, and other common Winston levels to `info`. It captures primitive info fields as `context.*`, captures nested `err`/`error` objects or formatted error stack name/message, omits stack text unless `includeErrorStack: true` is set, and exposes `onError` for capture failures. It does not mutate Winston globals or replace the app's logger.
293
625
 
626
+ Use the same `traceProvider: getActiveLogBrewTrace` option with LogBrew Node/framework helpers when you want Winston logs to carry the active request trace. The adapter only copies normalized W3C-shaped IDs and sampled state; it does not patch Winston globally or serialize arbitrary active context.
627
+
294
628
  Use a clearly fake placeholder like `LOGBREW_API_KEY` in examples. Call `flush` or `shutdown` to send queued events through a transport, and use `previewJson()` when you want a stable local JSON preview before sending anything.
@@ -9,7 +9,7 @@ try {
9
9
  }
10
10
  }
11
11
 
12
- const { createTraceparentHeaders, LogBrewClient, RecordingTransport } = sdk;
12
+ const { createTraceContextHeaders, createTraceparentHeaders, LogBrewClient, RecordingTransport } = sdk;
13
13
 
14
14
  const outgoingHeaders = createTraceparentHeaders({
15
15
  traceId: "4bf92f3577b34da6a3ce929d0e0e4736",
@@ -19,6 +19,20 @@ const outgoingHeaders = createTraceparentHeaders({
19
19
  if (outgoingHeaders.traceparent !== "00-4bf92f3577b34da6a3ce929d0e0e4736-b7ad6b7169203331-01") {
20
20
  throw new Error("createTraceparentHeaders produced an unexpected carrier");
21
21
  }
22
+ const outgoingContextHeaders = createTraceContextHeaders({
23
+ traceId: "4bf92f3577b34da6a3ce929d0e0e4736",
24
+ spanId: "b7ad6b7169203331",
25
+ traceFlags: "01",
26
+ tracestate: [{ key: "rojo", value: "00f067aa0ba902b7" }],
27
+ baggage: [{ key: "release", value: "checkout@1.2.3" }]
28
+ });
29
+ if (
30
+ outgoingContextHeaders.traceparent !== outgoingHeaders.traceparent
31
+ || outgoingContextHeaders.tracestate !== "rojo=00f067aa0ba902b7"
32
+ || outgoingContextHeaders.baggage !== "release=checkout%401.2.3"
33
+ ) {
34
+ throw new Error("createTraceContextHeaders produced an unexpected carrier");
35
+ }
22
36
 
23
37
  const client = LogBrewClient.create({
24
38
  apiKey: "LOGBREW_API_KEY",
@@ -5,7 +5,7 @@ const sdk = await import("@logbrew/sdk").catch(async (error) => {
5
5
  throw error;
6
6
  });
7
7
 
8
- const { createTraceparentHeaders, LogBrewClient, RecordingTransport } = sdk;
8
+ const { createTraceContextHeaders, createTraceparentHeaders, LogBrewClient, RecordingTransport } = sdk;
9
9
 
10
10
  const outgoingHeaders = createTraceparentHeaders({
11
11
  traceId: "4bf92f3577b34da6a3ce929d0e0e4736",
@@ -15,6 +15,20 @@ const outgoingHeaders = createTraceparentHeaders({
15
15
  if (outgoingHeaders.traceparent !== "00-4bf92f3577b34da6a3ce929d0e0e4736-b7ad6b7169203331-01") {
16
16
  throw new Error("createTraceparentHeaders produced an unexpected carrier");
17
17
  }
18
+ const outgoingContextHeaders = createTraceContextHeaders({
19
+ traceId: "4bf92f3577b34da6a3ce929d0e0e4736",
20
+ spanId: "b7ad6b7169203331",
21
+ traceFlags: "01",
22
+ tracestate: [{ key: "rojo", value: "00f067aa0ba902b7" }],
23
+ baggage: [{ key: "release", value: "checkout@1.2.3" }]
24
+ });
25
+ if (
26
+ outgoingContextHeaders.traceparent !== outgoingHeaders.traceparent
27
+ || outgoingContextHeaders.tracestate !== "rojo=00f067aa0ba902b7"
28
+ || outgoingContextHeaders.baggage !== "release=checkout%401.2.3"
29
+ ) {
30
+ throw new Error("createTraceContextHeaders produced an unexpected carrier");
31
+ }
18
32
 
19
33
  const client = LogBrewClient.create({
20
34
  apiKey: "LOGBREW_API_KEY",