@logbrew/sdk 0.1.34 → 0.1.36
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 +26 -8
- package/core.cjs +24 -4
- package/index.d.ts +6 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,9 +100,9 @@ npx logbrew-release-artifacts upload-js \
|
|
|
100
100
|
--allow-hosted
|
|
101
101
|
```
|
|
102
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.
|
|
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. Issues show symbolicated frames after LogBrew processes the matching release artifacts.
|
|
104
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. The helper also follows `Error.cause` and `AggregateError.errors` into a bounded parent-first exception graph. Automatic messages are marked redacted, every node reports whether frames were captured, truncated, or unavailable, and unsafe accessors, cycles, or the eight-node cap mark the graph truncated instead of inventing evidence. React, browser, Node, Next.js, and React Native helpers reuse this same core projection. See the shared [exception-chain contract](../../
|
|
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. The helper also follows `Error.cause` and `AggregateError.errors` into a bounded parent-first exception graph. Automatic messages are marked redacted, every node reports whether frames were captured, truncated, or unavailable, and unsafe accessors, cycles, or the eight-node cap mark the graph truncated instead of inventing evidence. React, browser, Node, Next.js, and React Native helpers reuse this same core projection. See the shared [exception-chain contract](../../README.md#exception-chains). A bundle prepared by the Vite plugin registers its Debug ID automatically; use `debugIdMap` only for a different app-owned build flow:
|
|
106
106
|
|
|
107
107
|
```js
|
|
108
108
|
import { createIssueAttributesFromError, LogBrewClient } from "@logbrew/sdk";
|
|
@@ -252,6 +252,24 @@ const response = await client.shutdown(transport);
|
|
|
252
252
|
console.error(JSON.stringify({ ok: true, status: response.statusCode, attempts: response.attempts, events: 6 }));
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
+
## Read Events Back
|
|
256
|
+
|
|
257
|
+
A successful send shows that LogBrew accepted the request. It does not show
|
|
258
|
+
that you can read the event. After your app sends one event, read it
|
|
259
|
+
back with a project read key:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
logbrew projects keys create "<project_id>" --kind read \
|
|
263
|
+
--ingest-key-file "$HOME/.logbrew/setup-check.read" --json
|
|
264
|
+
export LOGBREW_TOKEN="$(< "$HOME/.logbrew/setup-check.read")"
|
|
265
|
+
logbrew read logs --since 1h --json
|
|
266
|
+
logbrew issues --since 1h --json
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
A read key reads 1 project and cannot send telemetry. Run `unset LOGBREW_TOKEN`
|
|
270
|
+
when you finish. [Read your events back](https://docs.logbrew.co/guides/verify-setup)
|
|
271
|
+
covers key kinds, empty results, and how to revoke the key.
|
|
272
|
+
|
|
255
273
|
## Explicit Metrics
|
|
256
274
|
|
|
257
275
|
Use `client.metric()` when application code already knows the measurement name, value, unit, and aggregation shape. Metrics are queued like other events and are not collected automatically.
|
|
@@ -434,7 +452,7 @@ These examples use `@opentelemetry/sdk-trace` 3. With `@opentelemetry/sdk-trace-
|
|
|
434
452
|
|
|
435
453
|
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.
|
|
436
454
|
|
|
437
|
-
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](../../
|
|
455
|
+
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](../../README.md#what-you-can-capture).
|
|
438
456
|
|
|
439
457
|
## Event Filtering
|
|
440
458
|
|
|
@@ -481,7 +499,7 @@ console.log(JSON.stringify(health));
|
|
|
481
499
|
await client.shutdown();
|
|
482
500
|
```
|
|
483
501
|
|
|
484
|
-
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,
|
|
502
|
+
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. A `429` that carries a `Retry-After` delay or `code: "rate_limited"` waits that long, from 1 second to 1 hour plus up to 10 percent jitter, then resends the same batch on its own. Authentication failures, a `429` with `code: "usage_limit_exceeded"` or with neither signal, 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.
|
|
485
503
|
|
|
486
504
|
`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.
|
|
487
505
|
|
|
@@ -518,11 +536,11 @@ Flush requests are also bounded. Core and Node default to 100 events and 256 KiB
|
|
|
518
536
|
|
|
519
537
|
`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.
|
|
520
538
|
|
|
521
|
-
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.
|
|
539
|
+
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. Transports can also return `errorCode` from the `429` body. A `429` with `retryAfterMs` or `errorCode: "rate_limited"` raises a retryable error and automatic delivery waits it out; `errorCode: "usage_limit_exceeded"` or a `429` with neither signal pauses automatic delivery, like a `401`. 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.
|
|
522
540
|
|
|
523
541
|
## Support Ticket Drafts
|
|
524
542
|
|
|
525
|
-
Use `createSupportTicketDraft()`
|
|
543
|
+
Use `createSupportTicketDraft()` to build the JSON body for a LogBrew support ticket. The helper validates the source and category, converts JavaScript camelCase inputs to the ticket fields, and redacts token-like diagnostics before returning the object.
|
|
526
544
|
|
|
527
545
|
```js
|
|
528
546
|
import { createSupportTicketDraft } from "@logbrew/sdk";
|
|
@@ -552,7 +570,7 @@ const draft = createSupportTicketDraft({
|
|
|
552
570
|
console.log(JSON.stringify(draft, null, 2));
|
|
553
571
|
```
|
|
554
572
|
|
|
555
|
-
|
|
573
|
+
The helper sends nothing. To open the ticket, send it to `POST /api/support/tickets` or run `logbrew support create`. See [Report a problem](https://docs.logbrew.co/guides/report-a-problem). Diagnostics are bounded to JSON-like values; auth values, cookies, tokens, local paths, URL origins, hidden payloads, and unsupported objects are redacted or omitted.
|
|
556
574
|
|
|
557
575
|
## Agent-Readable Timelines
|
|
558
576
|
|
|
@@ -595,7 +613,7 @@ client.action("evt_payment_api", new Date().toISOString(), createNetworkMileston
|
|
|
595
613
|
|
|
596
614
|
Timeline helpers keep only primitive metadata, strip query strings and hashes from route templates, normalize HTTP methods, infer failed network milestones from status codes `400` and above, and serialize through the existing `action` event type. Product actions also carry the reserved version-1 `interaction` analytics classification; network milestones do not. Keep metadata low-cardinality, such as `sessionId`, `traceId`, `routeTemplate`, `method`, `statusCode`, `durationMs`, `screen`, `funnel`, and `step`.
|
|
597
615
|
|
|
598
|
-
`PRODUCT_ANALYTICS_SCHEMA_VERSION` and `PRODUCT_ANALYTICS_KINDS` expose the classification values supported by the installed package. The reserved analytics fields cannot be replaced through caller metadata. See the repository [product analytics capture contract](../../
|
|
616
|
+
`PRODUCT_ANALYTICS_SCHEMA_VERSION` and `PRODUCT_ANALYTICS_KINDS` expose the classification values supported by the installed package. The reserved analytics fields cannot be replaced through caller metadata. See the repository [product analytics capture contract](../../README.md#product-analytics-capture) before constructing those fields directly.
|
|
599
617
|
|
|
600
618
|
The packaged `agent-timeline` example shows a two-event checkout timeline that an AI assistant can inspect without session replay or payload capture. It combines product action metadata, network milestone metadata, explicit `traceparent` propagation, and a drop-only `eventFilter` that removes low-value info logs:
|
|
601
619
|
|
package/core.cjs
CHANGED
|
@@ -91,6 +91,9 @@ const DEFAULT_MAX_BATCH_BYTES = 256 * 1024;
|
|
|
91
91
|
// One second matches the OpenTelemetry log processor default, so a lone event becomes readable quickly.
|
|
92
92
|
const DEFAULT_DELIVERY_INTERVAL_MS = 1000;
|
|
93
93
|
const MAX_DELIVERY_INTERVAL_MS = 60 * 1000;
|
|
94
|
+
// A rate-limited batch waits from 1 second to 1 hour, plus up to 10 percent jitter, before it is sent again.
|
|
95
|
+
const MIN_RATE_LIMIT_WAIT_MS = 1000;
|
|
96
|
+
const MAX_RATE_LIMIT_WAIT_MS = 60 * 60 * 1000;
|
|
94
97
|
const DEFAULT_DELIVERY_QUEUE_THRESHOLD = 50;
|
|
95
98
|
const MAX_CLIENT_REPORT_REASONS = 16;
|
|
96
99
|
const MAX_CLIENT_REPORT_COUNT = 1_000_000_000;
|
|
@@ -208,9 +211,12 @@ class RecordingTransport {
|
|
|
208
211
|
}
|
|
209
212
|
|
|
210
213
|
const retryAfterMs = retryAfterMsOrUndefined(next.retryAfterMs);
|
|
211
|
-
return
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
return {
|
|
215
|
+
statusCode: next.statusCode,
|
|
216
|
+
attempts: 1,
|
|
217
|
+
...(retryAfterMs === undefined ? {} : { retryAfterMs }),
|
|
218
|
+
...(typeof next.errorCode === "string" ? { errorCode: next.errorCode } : {})
|
|
219
|
+
};
|
|
214
220
|
}
|
|
215
221
|
}
|
|
216
222
|
|
|
@@ -1101,6 +1107,13 @@ class LogBrewClient {
|
|
|
1101
1107
|
return;
|
|
1102
1108
|
}
|
|
1103
1109
|
if (error instanceof SdkError && error.code === "rate_limited") {
|
|
1110
|
+
if (error.retryable === true) {
|
|
1111
|
+
this.automaticPauseReason = "none";
|
|
1112
|
+
this.retryDelayMs = rateLimitWaitMs(
|
|
1113
|
+
error.retryAfterMs ?? automaticRetryDelayMs(this.deliveryIntervalMs, this.consecutiveDeliveryFailures)
|
|
1114
|
+
);
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1104
1117
|
this.automaticPauseReason = "rate_limit";
|
|
1105
1118
|
return;
|
|
1106
1119
|
}
|
|
@@ -1151,7 +1164,9 @@ class LogBrewClient {
|
|
|
1151
1164
|
}
|
|
1152
1165
|
if (response.statusCode === 429) {
|
|
1153
1166
|
throw new SdkError("rate_limited", "transport rate limited the batch", {
|
|
1154
|
-
retryAfterMs: response.retryAfterMs
|
|
1167
|
+
retryAfterMs: response.retryAfterMs,
|
|
1168
|
+
retryable: response.errorCode !== "usage_limit_exceeded"
|
|
1169
|
+
&& (retryAfterMsOrUndefined(response.retryAfterMs) !== undefined || response.errorCode === "rate_limited")
|
|
1155
1170
|
});
|
|
1156
1171
|
}
|
|
1157
1172
|
if (response.statusCode >= 200 && response.statusCode < 300) {
|
|
@@ -1191,6 +1206,11 @@ function automaticRetryDelayMs(deliveryIntervalMs, consecutiveFailures) {
|
|
|
1191
1206
|
return minimumDelay + Math.floor(Math.random() * (maximumDelay - minimumDelay + 1));
|
|
1192
1207
|
}
|
|
1193
1208
|
|
|
1209
|
+
function rateLimitWaitMs(delayMs) {
|
|
1210
|
+
const bounded = Math.min(MAX_RATE_LIMIT_WAIT_MS, Math.max(MIN_RATE_LIMIT_WAIT_MS, delayMs));
|
|
1211
|
+
return Math.min(MAX_RATE_LIMIT_WAIT_MS, bounded + Math.floor(Math.random() * (Math.floor(bounded / 10) + 1)));
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1194
1214
|
function statusClass(statusCode) {
|
|
1195
1215
|
if (statusCode >= 200 && statusCode < 300) {
|
|
1196
1216
|
return "success";
|
package/index.d.ts
CHANGED
|
@@ -643,10 +643,10 @@ export type TimelineAttributesOptions = {
|
|
|
643
643
|
metadata?: Metadata;
|
|
644
644
|
};
|
|
645
645
|
|
|
646
|
-
/**
|
|
646
|
+
/** Support-ticket sources accepted by explicit draft helpers. */
|
|
647
647
|
export type SupportTicketSource = "cli" | "sdk" | "website" | "docs" | "mobile";
|
|
648
648
|
|
|
649
|
-
/**
|
|
649
|
+
/** Support-ticket categories accepted by explicit draft helpers. */
|
|
650
650
|
export type SupportTicketCategory =
|
|
651
651
|
| "sdk_install_failure"
|
|
652
652
|
| "ingest_failure"
|
|
@@ -686,7 +686,7 @@ export type SupportTicketDraftInput = {
|
|
|
686
686
|
diagnostics?: Record<string, unknown>;
|
|
687
687
|
};
|
|
688
688
|
|
|
689
|
-
/**
|
|
689
|
+
/** Support-ticket create body built locally. It sends nothing. */
|
|
690
690
|
export type SupportTicketDraft = {
|
|
691
691
|
source: SupportTicketSource;
|
|
692
692
|
category: SupportTicketCategory;
|
|
@@ -775,6 +775,8 @@ export type TransportResponse = {
|
|
|
775
775
|
batches?: number;
|
|
776
776
|
/** Optional retry delay from a rate-limit response, in milliseconds. */
|
|
777
777
|
retryAfterMs?: number;
|
|
778
|
+
/** Optional `code` from a 429 response body, such as `rate_limited` or `usage_limit_exceeded`. */
|
|
779
|
+
errorCode?: string;
|
|
778
780
|
};
|
|
779
781
|
|
|
780
782
|
/** Minimal transport interface accepted by flush and shutdown operations. */
|
|
@@ -844,7 +846,7 @@ export declare class TransportError extends Error {
|
|
|
844
846
|
|
|
845
847
|
/** Scripted transport for previewing, accepting, or failing queued event flushes. */
|
|
846
848
|
export declare class RecordingTransport {
|
|
847
|
-
constructor(scriptedResponses?: Array<{ statusCode: number; retryAfterMs?: number } | Error>);
|
|
849
|
+
constructor(scriptedResponses?: Array<{ statusCode: number; retryAfterMs?: number; errorCode?: string } | Error>);
|
|
848
850
|
/** Every request body sent through this transport instance. */
|
|
849
851
|
sentBodies: string[];
|
|
850
852
|
/** Create a transport that accepts queued flushes with a 202 response. */
|