@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 +62 -2
- package/core.cjs +139 -17
- package/index.d.cts +120 -0
- package/index.d.ts +120 -0
- package/index.js +2 -0
- package/issue-diagnostics.cjs +267 -0
- package/issue-stack.cjs +70 -4
- package/package.json +3 -1
- package/react-native.d.ts +3 -0
- package/react-native.js +2 -0
- package/telemetry-context.cjs +298 -0
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
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;
|