@logbrew/sdk 0.1.35 → 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 +8 -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";
|
|
@@ -452,7 +452,7 @@ These examples use `@opentelemetry/sdk-trace` 3. With `@opentelemetry/sdk-trace-
|
|
|
452
452
|
|
|
453
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.
|
|
454
454
|
|
|
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](../../
|
|
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).
|
|
456
456
|
|
|
457
457
|
## Event Filtering
|
|
458
458
|
|
|
@@ -499,7 +499,7 @@ console.log(JSON.stringify(health));
|
|
|
499
499
|
await client.shutdown();
|
|
500
500
|
```
|
|
501
501
|
|
|
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. 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.
|
|
503
503
|
|
|
504
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.
|
|
505
505
|
|
|
@@ -536,11 +536,11 @@ Flush requests are also bounded. Core and Node default to 100 events and 256 KiB
|
|
|
536
536
|
|
|
537
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.
|
|
538
538
|
|
|
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.
|
|
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.
|
|
540
540
|
|
|
541
541
|
## Support Ticket Drafts
|
|
542
542
|
|
|
543
|
-
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.
|
|
544
544
|
|
|
545
545
|
```js
|
|
546
546
|
import { createSupportTicketDraft } from "@logbrew/sdk";
|
|
@@ -570,7 +570,7 @@ const draft = createSupportTicketDraft({
|
|
|
570
570
|
console.log(JSON.stringify(draft, null, 2));
|
|
571
571
|
```
|
|
572
572
|
|
|
573
|
-
|
|
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.
|
|
574
574
|
|
|
575
575
|
## Agent-Readable Timelines
|
|
576
576
|
|
|
@@ -613,7 +613,7 @@ client.action("evt_payment_api", new Date().toISOString(), createNetworkMileston
|
|
|
613
613
|
|
|
614
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`.
|
|
615
615
|
|
|
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](../../
|
|
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.
|
|
617
617
|
|
|
618
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:
|
|
619
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. */
|