@logbrew/sdk 0.1.29 → 0.1.31

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
@@ -456,7 +456,7 @@ Prefer removing sensitive values at the source before calling LogBrew. `eventFil
456
456
 
457
457
  ## Automatic Delivery
458
458
 
459
- 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.
459
+ When a client owns a `transport`, it automatically sends queued work 1 second after the first queued event or when 50 events are waiting, whichever happens first, so a single event is readable in about a second while bursts still share one request. The one-shot timer starts only after capture, later events do not restart it, 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.
460
460
 
461
461
  ```js
462
462
  const transport = RecordingTransport.alwaysAccept();
@@ -465,7 +465,7 @@ const client = LogBrewClient.create({
465
465
  sdkName: "checkout-api",
466
466
  sdkVersion: "1.0.0",
467
467
  transport,
468
- deliveryIntervalMs: 5000,
468
+ deliveryIntervalMs: 1000,
469
469
  deliveryQueueThreshold: 50
470
470
  });
471
471
 
package/core.cjs CHANGED
@@ -88,7 +88,8 @@ const DEFAULT_MAX_QUEUE_SIZE = 1000;
88
88
  const DEFAULT_MAX_QUEUE_BYTES = 4 * 1024 * 1024;
89
89
  const DEFAULT_MAX_BATCH_EVENTS = 100;
90
90
  const DEFAULT_MAX_BATCH_BYTES = 256 * 1024;
91
- const DEFAULT_DELIVERY_INTERVAL_MS = 5000;
91
+ // One second matches the OpenTelemetry log processor default, so a lone event becomes readable quickly.
92
+ const DEFAULT_DELIVERY_INTERVAL_MS = 1000;
92
93
  const MAX_DELIVERY_INTERVAL_MS = 60 * 1000;
93
94
  const DEFAULT_DELIVERY_QUEUE_THRESHOLD = 50;
94
95
  const MAX_CLIENT_REPORT_REASONS = 16;
@@ -96,6 +97,8 @@ const MAX_CLIENT_REPORT_COUNT = 1_000_000_000;
96
97
  const DELIVERY_HEALTH_SCHEMA_VERSION = 1;
97
98
  const EVENT_QUEUE_FACTORY = Symbol.for("@logbrew/sdk.eventQueueFactory");
98
99
  const CONTEXT_PROVIDER = Symbol.for("@logbrew/sdk.contextProvider");
100
+ // Runtime packages supply request-scoped breadcrumbs through this seam; core merges them into issues.
101
+ const BREADCRUMB_PROVIDER = Symbol.for("@logbrew/sdk.breadcrumbProvider");
99
102
  const MAX_ERROR_CAUSES = 5;
100
103
  const BUILTIN_ERROR_NAMES = new Set([
101
104
  "AggregateError",
@@ -215,6 +218,7 @@ class LogBrewClient {
215
218
  sdkVersion,
216
219
  context,
217
220
  [CONTEXT_PROVIDER]: contextProvider,
221
+ [BREADCRUMB_PROVIDER]: breadcrumbProvider,
218
222
  maxRetries = 2,
219
223
  eventFilter,
220
224
  maxQueueSize = DEFAULT_MAX_QUEUE_SIZE,
@@ -236,6 +240,9 @@ class LogBrewClient {
236
240
  if (contextProvider !== undefined && typeof contextProvider !== "function") {
237
241
  throw new SdkError("validation_error", "context provider must be a function");
238
242
  }
243
+ if (breadcrumbProvider !== undefined && typeof breadcrumbProvider !== "function") {
244
+ throw new SdkError("validation_error", "breadcrumb provider must be a function");
245
+ }
239
246
  requireNonNegativeInteger("maxRetries", maxRetries);
240
247
  if (eventFilter !== undefined && typeof eventFilter !== "function") {
241
248
  throw new SdkError("validation_error", "eventFilter must be a function");
@@ -285,6 +292,7 @@ class LogBrewClient {
285
292
  onEventDropped,
286
293
  context: clientContext,
287
294
  contextProvider,
295
+ breadcrumbProvider,
288
296
  sdk: {
289
297
  name: sdkName,
290
298
  language: "javascript",
@@ -307,6 +315,7 @@ class LogBrewClient {
307
315
  onEventDropped,
308
316
  context,
309
317
  contextProvider,
318
+ breadcrumbProvider,
310
319
  eventStore,
311
320
  eventQueueFactory,
312
321
  transport,
@@ -326,6 +335,7 @@ class LogBrewClient {
326
335
  this.onEventDropped = onEventDropped;
327
336
  this.context = cloneTelemetryContext(context);
328
337
  this.contextProvider = contextProvider;
338
+ this.breadcrumbProvider = breadcrumbProvider;
329
339
  this.transport = transport;
330
340
  this.sdk = sdk;
331
341
  this.maxRetries = maxRetries;
@@ -631,20 +641,46 @@ class LogBrewClient {
631
641
  }
632
642
 
633
643
  #pushIssue(id, timestamp, attributes) {
634
- const withBreadcrumbs = attributes.breadcrumbs !== undefined || this.issueBreadcrumbs.length === 0
644
+ if (attributes.breadcrumbs !== undefined) {
645
+ return this.#pushEvent("issue", id, timestamp, validateIssue(attributes));
646
+ }
647
+ const provided = this.#providedBreadcrumbs();
648
+ const merged = provided.breadcrumbs.length === 0
649
+ ? this.issueBreadcrumbs
650
+ : [...this.issueBreadcrumbs, ...provided.breadcrumbs]
651
+ .sort((left, right) => Date.parse(left.timestamp) - Date.parse(right.timestamp));
652
+ const breadcrumbs = merged.slice(-MAX_ISSUE_BREADCRUMBS);
653
+ const truncated = attributes.breadcrumbsTruncated === true || this.issueBreadcrumbsTruncated
654
+ || provided.truncated || merged.length > breadcrumbs.length;
655
+ const withBreadcrumbs = breadcrumbs.length === 0
635
656
  ? attributes
636
- : {
637
- ...attributes,
638
- breadcrumbs: this.issueBreadcrumbs,
639
- ...(
640
- attributes.breadcrumbsTruncated === true || this.issueBreadcrumbsTruncated
641
- ? { breadcrumbsTruncated: true }
642
- : {}
643
- )
644
- };
657
+ : { ...attributes, breadcrumbs, ...(truncated ? { breadcrumbsTruncated: true } : {}) };
645
658
  return this.#pushEvent("issue", id, timestamp, validateIssue(withBreadcrumbs));
646
659
  }
647
660
 
661
+ // Provider failures and invalid entries never cost the issue itself.
662
+ #providedBreadcrumbs() {
663
+ let provided;
664
+ try {
665
+ provided = this.breadcrumbProvider?.();
666
+ } catch {
667
+ return { breadcrumbs: [], truncated: false };
668
+ }
669
+ if (!provided || !Array.isArray(provided.breadcrumbs)) {
670
+ return { breadcrumbs: [], truncated: false };
671
+ }
672
+ const breadcrumbs = [];
673
+ let truncated = provided.truncated === true || provided.breadcrumbs.length > MAX_ISSUE_BREADCRUMBS;
674
+ for (const breadcrumb of provided.breadcrumbs.slice(-MAX_ISSUE_BREADCRUMBS)) {
675
+ try {
676
+ breadcrumbs.push(validateIssueBreadcrumb(breadcrumb));
677
+ } catch {
678
+ truncated = true;
679
+ }
680
+ }
681
+ return { breadcrumbs, truncated };
682
+ }
683
+
648
684
  #pushEvent(eventType, id, timestamp, attributes) {
649
685
  if (this.closed) {
650
686
  throw new SdkError("shutdown_error", "client is already shut down");
package/index.d.ts CHANGED
@@ -886,7 +886,7 @@ export declare class LogBrewClient {
886
886
  transport?: Transport;
887
887
  /** Enable interval and queue-threshold delivery. Defaults to true when transport is supplied. */
888
888
  automaticDelivery?: boolean;
889
- /** One-shot delivery interval in milliseconds. Defaults to 5000 and must not exceed 60000. */
889
+ /** One-shot delivery interval in milliseconds. Defaults to 1000 and must not exceed 60000. */
890
890
  deliveryIntervalMs?: number;
891
891
  /** Queue count that triggers delivery without waiting for the interval. Defaults to min(50, maxQueueSize). */
892
892
  deliveryQueueThreshold?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logbrew/sdk",
3
- "version": "0.1.29",
3
+ "version": "0.1.31",
4
4
  "description": "Public LogBrew JavaScript SDK for building, validating, and flushing event batches.",
5
5
  "type": "module",
6
6
  "main": "./index.cjs",