@logbrew/sdk 0.1.6 → 0.1.7

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
@@ -134,7 +134,65 @@ try {
134
134
  }
135
135
  ```
136
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`.
137
+ The helper records the error name/message, a typed exception with capture mechanism and handled state, and up to 32 ordered generated `stackFrames`, with query strings, hashes, and local absolute prefixes removed. Each frame carries filename, positive line/column, a conservatively parsed function name when the runtime provides one, and an optional matched Debug ID. Applications that create frames directly may also provide bounded `function`, `module`, and `inApp` identity. 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
+
139
+ Use `addBreadcrumb()` for explicit navigation, state, action, or network steps that should appear on later issues. The client keeps only the most recent 64 entries and marks an issue with `breadcrumbsTruncated: true` after older entries are evicted. `clearBreadcrumbs()` removes the current history.
140
+
141
+ ```js
142
+ client.addBreadcrumb({
143
+ category: "checkout",
144
+ type: "user",
145
+ level: "info",
146
+ message: "Submitted checkout",
147
+ data: { attempt: 2 }
148
+ });
149
+ ```
150
+
151
+ Breadcrumb timestamps default to the current time. Each entry accepts only fixed keys, a bounded message, and at most eight flat primitive data fields. Nested objects, arrays, invalid timestamps, control characters, and oversized values are rejected. Do not include authentication material, user-entered text, raw identifiers, full URLs, query strings, headers, or request/response bodies. The SDK does not capture console output, DOM text, or network payloads to build breadcrumbs.
152
+
153
+ ## Shared Telemetry Context
154
+
155
+ Give issues, logs, spans, actions, metrics, releases, and environments the same bounded identity and correlation data with the versioned `context` option. The JavaScript framework clients accept the same option, so middleware-generated and app-created events share one resource, deployment, session, subject, trace, and tag vocabulary.
156
+
157
+ ```js
158
+ const client = LogBrewClient.create({
159
+ apiKey: "LOGBREW_API_KEY",
160
+ sdkName: "checkout-web",
161
+ sdkVersion: "1.0.0",
162
+ context: {
163
+ schemaVersion: 1,
164
+ resource: {
165
+ service: { name: "checkout-web", version: "1.0.0" },
166
+ deployment: { environment: "production", release: "web@1.0.0" },
167
+ runtime: { name: "browser" },
168
+ framework: { name: "react", version: "19" }
169
+ },
170
+ session: { id: "session_01" },
171
+ subject: { id: "subject_01", kind: "anonymous" },
172
+ tags: { plan: "team", region: "eu" }
173
+ }
174
+ });
175
+
176
+ client.action("evt_checkout_started", new Date().toISOString(), {
177
+ name: "checkout.started",
178
+ status: "success",
179
+ context: {
180
+ schemaVersion: 1,
181
+ trace: {
182
+ traceId: "4bf92f3577b34da6a3ce929d0e0e4736",
183
+ spanId: "b7ad6b7169203331",
184
+ sampled: true
185
+ },
186
+ tags: { funnel: "checkout" }
187
+ }
188
+ });
189
+ ```
190
+
191
+ Client context is copied at creation and merged into every event. Event context can replace the current trace, session, or subject and can add or override individual resource fields and tags. Inputs are validated before queueing: unknown fields, all-zero trace identifiers, empty sections, oversized values, and more than 32 tags are rejected.
192
+
193
+ For span events, the span's required top-level `traceId`, `spanId`, and optional `parentSpanId` remain the canonical span identity. Shared `context.trace` is intended to correlate issues, logs, actions, metrics, releases, and environments that do not already carry a required span identity.
194
+
195
+ `subject.id` and `session.id` are app-owned correlation identifiers, not profile fields. Use opaque identifiers and never put names, email addresses, IP addresses, authentication values, free-form user input, or other personal data in context or tags. The SDK does not discover a user, device, location, session, or product interaction automatically. Capture only the fields your application has deliberately approved, rotate anonymous identifiers according to your privacy policy, and keep tags low-cardinality.
138
196
 
139
197
  ## Example
140
198
 
@@ -519,7 +577,9 @@ client.action("evt_payment_api", new Date().toISOString(), createNetworkMileston
519
577
  }));
520
578
  ```
521
579
 
522
- 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. Keep metadata low-cardinality, such as `sessionId`, `traceId`, `routeTemplate`, `method`, `statusCode`, `durationMs`, `screen`, `funnel`, and `step`.
580
+ 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`.
581
+
582
+ `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](../../docs/product-analytics-contract.md) before constructing those fields directly.
523
583
 
524
584
  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:
525
585
 
package/core.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  const { buildCreateSupportTicketDraft } = require("./support-ticket.cjs");
2
+ const { buildIssueDiagnosticsHelpers } = require("./issue-diagnostics.cjs");
2
3
  const { buildIssueStackHelpers } = require("./issue-stack.cjs");
3
4
  const { buildLogContextHelpers } = require("./log-context.cjs");
4
5
  const { buildOpenTelemetryHelpers } = require("./opentelemetry.cjs");
6
+ const { buildTelemetryContextHelpers } = require("./telemetry-context.cjs");
5
7
  const { buildTraceContextHelpers } = require("./trace-context.cjs");
6
8
 
7
9
  const SEVERITY_ALIASES = new Map([
@@ -17,6 +19,9 @@ const SEVERITY_ALIASES = new Map([
17
19
  const SEVERITY_VALUES = new Set(SEVERITY_ALIASES.keys());
18
20
  const SPAN_STATUSES = new Set(["ok", "error"]);
19
21
  const ACTION_STATUSES = new Set(["queued", "running", "success", "failure"]);
22
+ const PRODUCT_ANALYTICS_SCHEMA_VERSION = 1;
23
+ const PRODUCT_ANALYTICS_KINDS = Object.freeze(["page_view", "screen_view", "interaction"]);
24
+ const MAX_PRODUCT_ANALYTICS_SURFACE_LENGTH = 256;
20
25
  const METRIC_KINDS = new Set(["counter", "gauge", "histogram"]);
21
26
  const NON_NEGATIVE_METRIC_KINDS = new Set(["counter", "histogram"]);
22
27
  const METRIC_TEMPORALITIES_BY_KIND = new Map([
@@ -124,6 +129,18 @@ const {
124
129
  } = buildLogContextHelpers({ SdkError });
125
130
 
126
131
  const { javascriptStackFrames, validateIssueStackFrames } = buildIssueStackHelpers({ SdkError });
132
+ const {
133
+ MAX_ISSUE_BREADCRUMBS,
134
+ cloneIssueDiagnostics,
135
+ createIssueException,
136
+ validateIssueBreadcrumb,
137
+ validateIssueDiagnostics
138
+ } = buildIssueDiagnosticsHelpers({ SdkError, requireTimestamp });
139
+ const {
140
+ cloneTelemetryContext,
141
+ mergeTelemetryContexts,
142
+ validateTelemetryContext
143
+ } = buildTelemetryContextHelpers({ SdkError });
127
144
 
128
145
  class TransportError extends Error {
129
146
  constructor(code, message, retryable = false) {
@@ -176,6 +193,7 @@ class LogBrewClient {
176
193
  apiKey,
177
194
  sdkName,
178
195
  sdkVersion,
196
+ context,
179
197
  maxRetries = 2,
180
198
  eventFilter,
181
199
  maxQueueSize = DEFAULT_MAX_QUEUE_SIZE,
@@ -193,6 +211,7 @@ class LogBrewClient {
193
211
  requireNonEmpty("apiKey", apiKey);
194
212
  requireNonEmpty("sdkName", sdkName);
195
213
  requireNonEmpty("sdkVersion", sdkVersion);
214
+ const clientContext = validateTelemetryContext(context, "client telemetry context");
196
215
  requireNonNegativeInteger("maxRetries", maxRetries);
197
216
  if (eventFilter !== undefined && typeof eventFilter !== "function") {
198
217
  throw new SdkError("validation_error", "eventFilter must be a function");
@@ -240,6 +259,7 @@ class LogBrewClient {
240
259
  maxQueueBytes,
241
260
  maxQueueSize,
242
261
  onEventDropped,
262
+ context: clientContext,
243
263
  sdk: {
244
264
  name: sdkName,
245
265
  language: "javascript",
@@ -260,6 +280,7 @@ class LogBrewClient {
260
280
  maxQueueBytes,
261
281
  maxQueueSize,
262
282
  onEventDropped,
283
+ context,
263
284
  eventStore,
264
285
  eventQueueFactory,
265
286
  transport,
@@ -277,6 +298,7 @@ class LogBrewClient {
277
298
  this.maxQueueBytes = maxQueueBytes;
278
299
  this.maxQueueSize = maxQueueSize;
279
300
  this.onEventDropped = onEventDropped;
301
+ this.context = cloneTelemetryContext(context);
280
302
  this.transport = transport;
281
303
  this.sdk = sdk;
282
304
  this.maxRetries = maxRetries;
@@ -351,6 +373,8 @@ class LogBrewClient {
351
373
  this.lastAcceptedAtUnixMs = 0;
352
374
  this.lastDroppedAtUnixMs = 0;
353
375
  this.failedBatch = undefined;
376
+ this.issueBreadcrumbs = [];
377
+ this.issueBreadcrumbsTruncated = false;
354
378
  this.#scheduleAutomaticDelivery();
355
379
  }
356
380
 
@@ -454,6 +478,28 @@ class LogBrewClient {
454
478
  return purgedEvents;
455
479
  }
456
480
 
481
+ addBreadcrumb(breadcrumb, timestamp = new Date().toISOString()) {
482
+ if (this.closed) {
483
+ throw new SdkError("shutdown_error", "client is already shut down");
484
+ }
485
+ if (this.closing) {
486
+ throw new SdkError("shutdown_error", "client is shutting down");
487
+ }
488
+ const validated = validateIssueBreadcrumb(breadcrumb, timestamp);
489
+ if (this.issueBreadcrumbs.length === MAX_ISSUE_BREADCRUMBS) {
490
+ this.issueBreadcrumbs.shift();
491
+ this.issueBreadcrumbsTruncated = true;
492
+ }
493
+ this.issueBreadcrumbs.push(validated);
494
+ }
495
+
496
+ clearBreadcrumbs() {
497
+ const cleared = this.issueBreadcrumbs.length;
498
+ this.issueBreadcrumbs.splice(0, cleared);
499
+ this.issueBreadcrumbsTruncated = false;
500
+ return cleared;
501
+ }
502
+
457
503
  release(id, timestamp, attributes) {
458
504
  this.#pushEvent("release", id, timestamp, validateRelease(attributes));
459
505
  }
@@ -463,7 +509,18 @@ class LogBrewClient {
463
509
  }
464
510
 
465
511
  issue(id, timestamp, attributes) {
466
- this.#pushEvent("issue", id, timestamp, validateIssue(attributes));
512
+ const withBreadcrumbs = attributes.breadcrumbs !== undefined || this.issueBreadcrumbs.length === 0
513
+ ? attributes
514
+ : {
515
+ ...attributes,
516
+ breadcrumbs: this.issueBreadcrumbs,
517
+ ...(
518
+ attributes.breadcrumbsTruncated === true || this.issueBreadcrumbsTruncated
519
+ ? { breadcrumbsTruncated: true }
520
+ : {}
521
+ )
522
+ };
523
+ this.#pushEvent("issue", id, timestamp, validateIssue(withBreadcrumbs));
467
524
  }
468
525
 
469
526
  log(id, timestamp, attributes) {
@@ -561,7 +618,11 @@ class LogBrewClient {
561
618
  }
562
619
  requireNonEmpty("event id", id);
563
620
  requireTimestamp(timestamp);
564
- const event = { type: eventType, id, timestamp, attributes };
621
+ const context = mergeTelemetryContexts(this.context, attributes.context);
622
+ const eventAttributes = context === undefined
623
+ ? attributes
624
+ : { ...attributes, context };
625
+ const event = { type: eventType, id, timestamp, attributes: eventAttributes };
565
626
  if (this.eventFilter && this.eventFilter(cloneEvent(event)) === false) {
566
627
  return;
567
628
  }
@@ -1330,6 +1391,11 @@ function createIssueAttributesFromError(error, options = {}) {
1330
1391
  title: stringOrUndefined(options.title) ?? details.name,
1331
1392
  level: normalizeSeverity("issue level", options.level ?? "error"),
1332
1393
  ...(stringOrUndefined(options.message) ? { message: options.message } : details.message ? { message: details.message } : {}),
1394
+ exception: createIssueException(
1395
+ boundedIssueExceptionType(details.name),
1396
+ stringOrUndefined(options.mechanism) ?? "javascript.error",
1397
+ options.handled === undefined ? true : options.handled
1398
+ ),
1333
1399
  ...(stackFrames.length > 0 ? { stackFrames } : {}),
1334
1400
  metadata: compactMetadata(metadata)
1335
1401
  };
@@ -1355,6 +1421,23 @@ function errorDetails(error) {
1355
1421
  return { name: "Error" };
1356
1422
  }
1357
1423
 
1424
+ function boundedIssueExceptionType(value) {
1425
+ const normalized = typeof value === "string" ? value.trim() : "";
1426
+ const characters = Array.from(normalized);
1427
+ if (
1428
+ normalized === ""
1429
+ || characters.length > 256
1430
+ || /[?#]/u.test(normalized)
1431
+ || characters.some((character) => {
1432
+ const code = character.codePointAt(0);
1433
+ return code !== undefined && (code <= 31 || (code >= 127 && code <= 159));
1434
+ })
1435
+ ) {
1436
+ return "Error";
1437
+ }
1438
+ return normalized;
1439
+ }
1440
+
1358
1441
  function issueGroupingMetadata(source, details, frame, fingerprint) {
1359
1442
  const groupingKey = frame
1360
1443
  ? `${source}:${details.name}:${frame.filename}`
@@ -1505,6 +1588,7 @@ function logbrewLevelFromConsoleMethod(method) {
1505
1588
 
1506
1589
  function createProductActionAttributes(action, options = {}) {
1507
1590
  const details = productActionDetails(action);
1591
+ const routeTemplate = sanitizeRouteTemplate(details.routeTemplate);
1508
1592
  return {
1509
1593
  name: details.name,
1510
1594
  status: details.status,
@@ -1512,12 +1596,13 @@ function createProductActionAttributes(action, options = {}) {
1512
1596
  source: "product.action",
1513
1597
  ...compactMetadata(options.metadata),
1514
1598
  ...compactMetadata(details.metadata),
1515
- routeTemplate: sanitizeRouteTemplate(details.routeTemplate),
1599
+ routeTemplate,
1516
1600
  sessionId: stringOrUndefined(details.sessionId),
1517
1601
  traceId: stringOrUndefined(details.traceId),
1518
1602
  screen: stringOrUndefined(details.screen),
1519
1603
  funnel: stringOrUndefined(details.funnel),
1520
- step: stringOrUndefined(details.step)
1604
+ step: stringOrUndefined(details.step),
1605
+ ...productAnalyticsMetadata("interaction", routeTemplate || details.screen)
1521
1606
  })
1522
1607
  };
1523
1608
  }
@@ -2002,7 +2087,7 @@ function cloneSpanLinks(links) {
2002
2087
  }
2003
2088
 
2004
2089
  function cloneEvent(event) {
2005
- const attributes = { ...event.attributes };
2090
+ const attributes = { ...event.attributes, ...cloneIssueDiagnostics(event.attributes) };
2006
2091
  if (event.attributes.metadata !== undefined) {
2007
2092
  attributes.metadata = { ...event.attributes.metadata };
2008
2093
  }
@@ -2012,6 +2097,9 @@ function cloneEvent(event) {
2012
2097
  if (Array.isArray(event.attributes.links)) {
2013
2098
  attributes.links = cloneSpanLinks(event.attributes.links);
2014
2099
  }
2100
+ if (event.attributes.context !== undefined) {
2101
+ attributes.context = cloneTelemetryContext(event.attributes.context);
2102
+ }
2015
2103
  return { ...event, attributes };
2016
2104
  }
2017
2105
 
@@ -2024,7 +2112,7 @@ function validateRelease(attributes) {
2024
2112
  version: attributes.version,
2025
2113
  ...(attributes.commit ? { commit: attributes.commit } : {}),
2026
2114
  ...(attributes.notes !== undefined ? { notes: attributes.notes } : {})
2027
- }, attributes.metadata);
2115
+ }, attributes.metadata, attributes.context);
2028
2116
  }
2029
2117
 
2030
2118
  function validateEnvironment(attributes) {
@@ -2032,7 +2120,7 @@ function validateEnvironment(attributes) {
2032
2120
  return withMetadata({
2033
2121
  name: attributes.name,
2034
2122
  ...(attributes.region !== undefined ? { region: attributes.region } : {})
2035
- }, attributes.metadata);
2123
+ }, attributes.metadata, attributes.context);
2036
2124
  }
2037
2125
 
2038
2126
  function validateIssue(attributes) {
@@ -2043,8 +2131,9 @@ function validateIssue(attributes) {
2043
2131
  title: attributes.title,
2044
2132
  level,
2045
2133
  ...(attributes.message !== undefined ? { message: attributes.message } : {}),
2046
- ...(stackFrames !== undefined ? { stackFrames } : {})
2047
- }, attributes.metadata);
2134
+ ...(stackFrames !== undefined ? { stackFrames } : {}),
2135
+ ...validateIssueDiagnostics(attributes)
2136
+ }, attributes.metadata, attributes.context);
2048
2137
  }
2049
2138
 
2050
2139
  function validateLog(attributes) {
@@ -2054,7 +2143,7 @@ function validateLog(attributes) {
2054
2143
  message: attributes.message,
2055
2144
  level,
2056
2145
  ...(attributes.logger !== undefined ? { logger: attributes.logger } : {})
2057
- }, attributes.metadata);
2146
+ }, attributes.metadata, attributes.context);
2058
2147
  }
2059
2148
 
2060
2149
  function normalizeSeverity(label, value) {
@@ -2086,7 +2175,7 @@ function validateSpan(attributes) {
2086
2175
  ...(attributes.durationMs !== undefined ? { durationMs: attributes.durationMs } : {}),
2087
2176
  ...(events !== undefined ? { events } : {}),
2088
2177
  ...(links !== undefined ? { links } : {})
2089
- }, attributes.metadata);
2178
+ }, attributes.metadata, attributes.context);
2090
2179
  }
2091
2180
 
2092
2181
  function validateSpanEvents(events) {
@@ -2169,7 +2258,7 @@ function validateAction(attributes) {
2169
2258
  return withMetadata({
2170
2259
  name: attributes.name,
2171
2260
  status: attributes.status
2172
- }, attributes.metadata);
2261
+ }, attributes.metadata, attributes.context);
2173
2262
  }
2174
2263
 
2175
2264
  function validateMetric(attributes) {
@@ -2190,7 +2279,7 @@ function validateMetric(attributes) {
2190
2279
  value: attributes.value,
2191
2280
  unit: attributes.unit,
2192
2281
  temporality: attributes.temporality
2193
- }, attributes.metadata);
2282
+ }, attributes.metadata, attributes.context);
2194
2283
  }
2195
2284
 
2196
2285
  function productActionDetails(action) {
@@ -2269,6 +2358,34 @@ function sanitizeRouteTemplate(routeTemplate) {
2269
2358
  }
2270
2359
  }
2271
2360
 
2361
+ function productAnalyticsMetadata(kind, surface) {
2362
+ const normalizedSurface = boundedProductAnalyticsSurface(surface);
2363
+ return {
2364
+ analyticsSchemaVersion: PRODUCT_ANALYTICS_SCHEMA_VERSION,
2365
+ analyticsKind: kind,
2366
+ analyticsSurface: normalizedSurface
2367
+ };
2368
+ }
2369
+
2370
+ function boundedProductAnalyticsSurface(surface) {
2371
+ if (typeof surface !== "string") {
2372
+ return undefined;
2373
+ }
2374
+ const normalized = surface.trim();
2375
+ const characters = Array.from(normalized);
2376
+ if (
2377
+ normalized === ""
2378
+ || characters.length > MAX_PRODUCT_ANALYTICS_SURFACE_LENGTH
2379
+ || characters.some((character) => {
2380
+ const code = character.codePointAt(0);
2381
+ return code !== undefined && (code <= 31 || (code >= 127 && code <= 159));
2382
+ })
2383
+ ) {
2384
+ return undefined;
2385
+ }
2386
+ return normalized;
2387
+ }
2388
+
2272
2389
  function normalizeHttpMethod(method) {
2273
2390
  const value = method === undefined ? "GET" : method;
2274
2391
  if (typeof value !== "string" || value.trim() === "") {
@@ -2312,11 +2429,14 @@ function stringOrUndefined(value) {
2312
2429
  return typeof value === "string" && value.trim() !== "" ? value : undefined;
2313
2430
  }
2314
2431
 
2315
- function withMetadata(attributes, metadata) {
2432
+ function withMetadata(attributes, metadata, context) {
2316
2433
  const safeMetadata = cloneMetadata(metadata);
2317
- return safeMetadata === undefined
2318
- ? attributes
2319
- : { ...attributes, metadata: safeMetadata };
2434
+ const safeContext = validateTelemetryContext(context);
2435
+ return {
2436
+ ...attributes,
2437
+ ...(safeMetadata === undefined ? {} : { metadata: safeMetadata }),
2438
+ ...(safeContext === undefined ? {} : { context: safeContext })
2439
+ };
2320
2440
  }
2321
2441
 
2322
2442
  function normalizeConsoleLevels(levels) {
@@ -2367,6 +2487,8 @@ function formatConsoleArgument(value, includeErrorStack) {
2367
2487
  }
2368
2488
 
2369
2489
  module.exports = {
2490
+ PRODUCT_ANALYTICS_KINDS,
2491
+ PRODUCT_ANALYTICS_SCHEMA_VERSION,
2370
2492
  createBaggage,
2371
2493
  createIssueAttributesFromError,
2372
2494
  createNetworkMilestoneAttributes,
package/index.d.cts CHANGED
@@ -2,6 +2,48 @@
2
2
  export type MetadataValue = string | number | boolean | null;
3
3
  /** Structured metadata map shared by public LogBrew event attribute types. */
4
4
  export type Metadata = Record<string, MetadataValue>;
5
+ /** Bounded service, runtime, or framework identity shared by telemetry signals. */
6
+ export type TelemetryNamedVersion = {
7
+ name: string;
8
+ version?: string;
9
+ };
10
+ /** Privacy-bounded resource identity shared by telemetry signals. */
11
+ export type TelemetryResource = {
12
+ service?: TelemetryNamedVersion;
13
+ deployment?: { environment?: string; release?: string };
14
+ runtime?: TelemetryNamedVersion;
15
+ framework?: TelemetryNamedVersion;
16
+ operatingSystem?: TelemetryNamedVersion & { build?: string };
17
+ device?: { family?: string; model?: string; architecture?: string };
18
+ application?: { name?: string; version?: string; build?: string };
19
+ };
20
+ /** W3C-compatible correlation identity shared by non-span telemetry. */
21
+ export type TelemetryTraceContext = {
22
+ traceId: string;
23
+ spanId?: string;
24
+ parentSpanId?: string;
25
+ sampled?: boolean;
26
+ };
27
+ /** Opaque application session identity. */
28
+ export type TelemetrySessionContext = {
29
+ id: string;
30
+ previousId?: string;
31
+ };
32
+ /** Explicit app-owned subject identity; do not send names, email addresses, or IP addresses. */
33
+ export type TelemetrySubjectContext = {
34
+ id: string;
35
+ kind: "anonymous" | "user";
36
+ };
37
+ /** Versioned shared context available on every LogBrew event type. */
38
+ export type TelemetryContext = {
39
+ schemaVersion: 1;
40
+ resource?: TelemetryResource;
41
+ trace?: TelemetryTraceContext;
42
+ session?: TelemetrySessionContext;
43
+ subject?: TelemetrySubjectContext;
44
+ /** Up to 32 low-cardinality string dimensions with safe machine keys. */
45
+ tags?: Record<string, string>;
46
+ };
5
47
  /** Canonical user-facing severity categories accepted by LogBrew. */
6
48
  export type Severity = "info" | "warning" | "error" | "critical";
7
49
  /** Runtime-level aliases accepted for compatibility and normalized before send. */
@@ -211,6 +253,7 @@ export type ReleaseAttributes = {
211
253
  commit?: string;
212
254
  notes?: string;
213
255
  metadata?: Metadata;
256
+ context?: TelemetryContext;
214
257
  };
215
258
 
216
259
  /** Public environment event attributes. */
@@ -218,6 +261,7 @@ export type EnvironmentAttributes = {
218
261
  name: string;
219
262
  region?: string;
220
263
  metadata?: Metadata;
264
+ context?: TelemetryContext;
221
265
  };
222
266
 
223
267
  /** Privacy-bounded generated JavaScript frame attached to an issue. */
@@ -228,18 +272,71 @@ export type IssueStackFrame = {
228
272
  line: number;
229
273
  /** One-based generated source column. */
230
274
  column: number;
275
+ /** Optional bounded function or method identity. */
276
+ function?: string;
277
+ /** Optional bounded module, package, or namespace identity. */
278
+ module?: string;
279
+ /** Whether application code classified this frame as app-owned. */
280
+ inApp?: boolean;
231
281
  /** Optional release-artifact Debug ID matched to this generated file. */
232
282
  debugId?: string;
233
283
  };
234
284
 
285
+ /** Runtime path that captured an exception and whether it escaped that path. */
286
+ export type IssueExceptionMechanism = {
287
+ /** Stable low-cardinality capture mechanism, such as `react.error_boundary`. */
288
+ type: string;
289
+ /** False when the exception escaped the capture boundary. */
290
+ handled: boolean;
291
+ };
292
+
293
+ /** Structured exception identity attached to an issue. */
294
+ export type IssueException = {
295
+ /** Bounded runtime exception class or error type. */
296
+ type: string;
297
+ /** Capture mechanism when the SDK can determine it. */
298
+ mechanism?: IssueExceptionMechanism;
299
+ };
300
+
301
+ export type IssueBreadcrumbLevel = "debug" | "info" | "warning" | "error" | "critical";
302
+ export type IssueBreadcrumbLevelInput = IssueBreadcrumbLevel | "trace" | "log" | "warn" | "fatal";
303
+ export type IssueBreadcrumbDataValue = string | number | boolean | null;
304
+
305
+ /** One privacy-bounded step that happened before an issue. */
306
+ export type IssueBreadcrumb = {
307
+ /** RFC 3339 timestamp with an explicit timezone. */
308
+ timestamp: string;
309
+ /** Optional stable breadcrumb kind, such as `navigation` or `http`. */
310
+ type?: string;
311
+ /** Required low-cardinality source category. */
312
+ category: string;
313
+ level?: IssueBreadcrumbLevel;
314
+ /** Optional bounded display-safe description. */
315
+ message?: string;
316
+ /** At most eight flat primitive fields. Never include authentication material or raw request data. */
317
+ data?: Record<string, IssueBreadcrumbDataValue>;
318
+ };
319
+
320
+ /** Input accepted by `addBreadcrumb`; the client supplies the current timestamp when omitted. */
321
+ export type IssueBreadcrumbInput = Omit<IssueBreadcrumb, "timestamp" | "level"> & {
322
+ timestamp?: string;
323
+ level?: IssueBreadcrumbLevelInput;
324
+ };
325
+
235
326
  /** Public issue event attributes. */
236
327
  export type IssueAttributes = {
237
328
  title: string;
238
329
  level: SeverityInput;
239
330
  message?: string;
331
+ exception?: IssueException;
240
332
  /** Ordered privacy-bounded generated frames, capped at 32. */
241
333
  stackFrames?: IssueStackFrame[];
334
+ /** Oldest-to-newest issue history, capped at the most recent 64 entries. */
335
+ breadcrumbs?: IssueBreadcrumb[];
336
+ /** True when older or invalid history was omitted before capture. */
337
+ breadcrumbsTruncated?: boolean;
242
338
  metadata?: Metadata;
339
+ context?: TelemetryContext;
243
340
  };
244
341
 
245
342
  /** Options for creating privacy-bounded issue attributes from a JavaScript error. */
@@ -248,6 +345,10 @@ export type JavaScriptErrorIssueOptions = {
248
345
  level?: SeverityInput;
249
346
  message?: string;
250
347
  metadata?: Metadata;
348
+ /** Stable low-cardinality capture mechanism. Defaults to `javascript.error`. */
349
+ mechanism?: string;
350
+ /** Whether the error was handled by the capture boundary. Defaults to true. */
351
+ handled?: boolean;
251
352
  /** Metadata source label. Defaults to `javascript.error`. */
252
353
  source?: string;
253
354
  /** Active trace copied into primitive correlation metadata. */
@@ -276,6 +377,7 @@ export type LogAttributes = {
276
377
  level: SeverityInput;
277
378
  logger?: string;
278
379
  metadata?: Metadata;
380
+ context?: TelemetryContext;
279
381
  };
280
382
 
281
383
  /** Console method names supported by the opt-in console capture helper. */
@@ -406,6 +508,7 @@ export type SpanAttributes = {
406
508
  events?: SpanEventSummary[];
407
509
  links?: SpanLinkSummary[];
408
510
  metadata?: Metadata;
511
+ context?: TelemetryContext;
409
512
  };
410
513
 
411
514
  /** Public action event attributes. */
@@ -413,8 +516,18 @@ export type ActionAttributes = {
413
516
  name: string;
414
517
  status: "queued" | "running" | "success" | "failure";
415
518
  metadata?: Metadata;
519
+ context?: TelemetryContext;
416
520
  };
417
521
 
522
+ /** Stable product-analytics event categories carried in reserved action metadata. */
523
+ export type ProductAnalyticsKind = "page_view" | "screen_view" | "interaction";
524
+
525
+ /** Current version of the reserved product-analytics metadata vocabulary. */
526
+ export declare const PRODUCT_ANALYTICS_SCHEMA_VERSION: 1;
527
+
528
+ /** Product-analytics categories understood by this SDK version. */
529
+ export declare const PRODUCT_ANALYTICS_KINDS: readonly ProductAnalyticsKind[];
530
+
418
531
  /** App-owned product step input for agent-readable action timelines. */
419
532
  export type ProductActionInput = string | {
420
533
  name: string;
@@ -515,6 +628,7 @@ export type MetricAttributes = {
515
628
  unit: string;
516
629
  temporality: "delta" | "cumulative";
517
630
  metadata?: Metadata;
631
+ context?: TelemetryContext;
518
632
  } | {
519
633
  name: string;
520
634
  kind: "gauge";
@@ -656,6 +770,8 @@ export declare class LogBrewClient {
656
770
  apiKey: string;
657
771
  sdkName: string;
658
772
  sdkVersion: string;
773
+ /** Versioned context merged into every captured event; event context can override dynamic fields. */
774
+ context?: TelemetryContext;
659
775
  /** Retry attempts after the first send. Must be a non-negative integer; defaults to 2. */
660
776
  maxRetries?: number;
661
777
  eventFilter?: EventFilter;
@@ -690,6 +806,10 @@ export declare class LogBrewClient {
690
806
  previewJson(): string;
691
807
  /** Purge queued events from memory and persistence while no delivery operation is active. */
692
808
  purgePendingEvents(): number;
809
+ /** Add one explicit privacy-bounded breadcrumb to the client's 64-entry issue history. */
810
+ addBreadcrumb(breadcrumb: IssueBreadcrumbInput, timestamp?: string): void;
811
+ /** Clear the current issue breadcrumb history and return the number removed. */
812
+ clearBreadcrumbs(): number;
693
813
  release(id: string, timestamp: string, attributes: ReleaseAttributes): void;
694
814
  environment(id: string, timestamp: string, attributes: EnvironmentAttributes): void;
695
815
  issue(id: string, timestamp: string, attributes: IssueAttributes): void;