@logbrew/sdk 0.1.6 → 0.1.8
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 +64 -3
- package/core.cjs +169 -17
- package/index.d.cts +124 -0
- package/index.d.ts +124 -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
|
|
|
@@ -192,6 +250,7 @@ Use `client.metric()` when application code already knows the measurement name,
|
|
|
192
250
|
```js
|
|
193
251
|
client.metric("evt_metric_001", "2026-06-02T10:00:06Z", {
|
|
194
252
|
name: "checkout.requests",
|
|
253
|
+
description: "Number of checkout requests accepted by the application.",
|
|
195
254
|
kind: "counter",
|
|
196
255
|
value: 42,
|
|
197
256
|
unit: "{request}",
|
|
@@ -200,7 +259,7 @@ client.metric("evt_metric_001", "2026-06-02T10:00:06Z", {
|
|
|
200
259
|
});
|
|
201
260
|
```
|
|
202
261
|
|
|
203
|
-
Metric `kind` must be `counter`, `gauge`, or `histogram`. Counters and histograms must be non-negative and use `delta` or `cumulative` temporality; gauges use `instant` temporality and may be negative. Keep metric metadata primitive and low-cardinality, such as service, region, or route template.
|
|
262
|
+
Metric `kind` must be `counter`, `gauge`, or `histogram`. Counters and histograms must be non-negative and use `delta` or `cumulative` temporality; gauges use `instant` temporality and may be negative. An optional `description` gives humans and investigation tools stable meaning for the measurement; keep it generic, single-line, between 1 and 1,024 Unicode characters, and free of identifiers, personal data, or changing values. It is not a query dimension. Keep metric metadata primitive and low-cardinality, such as service, region, or route template.
|
|
204
263
|
|
|
205
264
|
## W3C Trace Context
|
|
206
265
|
|
|
@@ -519,7 +578,9 @@ client.action("evt_payment_api", new Date().toISOString(), createNetworkMileston
|
|
|
519
578
|
}));
|
|
520
579
|
```
|
|
521
580
|
|
|
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`.
|
|
581
|
+
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`.
|
|
582
|
+
|
|
583
|
+
`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
584
|
|
|
524
585
|
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
586
|
|
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,7 +19,11 @@ 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"]);
|
|
26
|
+
const MAX_METRIC_DESCRIPTION_LENGTH = 1024;
|
|
21
27
|
const NON_NEGATIVE_METRIC_KINDS = new Set(["counter", "histogram"]);
|
|
22
28
|
const METRIC_TEMPORALITIES_BY_KIND = new Map([
|
|
23
29
|
["counter", new Set(["delta", "cumulative"])],
|
|
@@ -124,6 +130,18 @@ const {
|
|
|
124
130
|
} = buildLogContextHelpers({ SdkError });
|
|
125
131
|
|
|
126
132
|
const { javascriptStackFrames, validateIssueStackFrames } = buildIssueStackHelpers({ SdkError });
|
|
133
|
+
const {
|
|
134
|
+
MAX_ISSUE_BREADCRUMBS,
|
|
135
|
+
cloneIssueDiagnostics,
|
|
136
|
+
createIssueException,
|
|
137
|
+
validateIssueBreadcrumb,
|
|
138
|
+
validateIssueDiagnostics
|
|
139
|
+
} = buildIssueDiagnosticsHelpers({ SdkError, requireTimestamp });
|
|
140
|
+
const {
|
|
141
|
+
cloneTelemetryContext,
|
|
142
|
+
mergeTelemetryContexts,
|
|
143
|
+
validateTelemetryContext
|
|
144
|
+
} = buildTelemetryContextHelpers({ SdkError });
|
|
127
145
|
|
|
128
146
|
class TransportError extends Error {
|
|
129
147
|
constructor(code, message, retryable = false) {
|
|
@@ -176,6 +194,7 @@ class LogBrewClient {
|
|
|
176
194
|
apiKey,
|
|
177
195
|
sdkName,
|
|
178
196
|
sdkVersion,
|
|
197
|
+
context,
|
|
179
198
|
maxRetries = 2,
|
|
180
199
|
eventFilter,
|
|
181
200
|
maxQueueSize = DEFAULT_MAX_QUEUE_SIZE,
|
|
@@ -193,6 +212,7 @@ class LogBrewClient {
|
|
|
193
212
|
requireNonEmpty("apiKey", apiKey);
|
|
194
213
|
requireNonEmpty("sdkName", sdkName);
|
|
195
214
|
requireNonEmpty("sdkVersion", sdkVersion);
|
|
215
|
+
const clientContext = validateTelemetryContext(context, "client telemetry context");
|
|
196
216
|
requireNonNegativeInteger("maxRetries", maxRetries);
|
|
197
217
|
if (eventFilter !== undefined && typeof eventFilter !== "function") {
|
|
198
218
|
throw new SdkError("validation_error", "eventFilter must be a function");
|
|
@@ -240,6 +260,7 @@ class LogBrewClient {
|
|
|
240
260
|
maxQueueBytes,
|
|
241
261
|
maxQueueSize,
|
|
242
262
|
onEventDropped,
|
|
263
|
+
context: clientContext,
|
|
243
264
|
sdk: {
|
|
244
265
|
name: sdkName,
|
|
245
266
|
language: "javascript",
|
|
@@ -260,6 +281,7 @@ class LogBrewClient {
|
|
|
260
281
|
maxQueueBytes,
|
|
261
282
|
maxQueueSize,
|
|
262
283
|
onEventDropped,
|
|
284
|
+
context,
|
|
263
285
|
eventStore,
|
|
264
286
|
eventQueueFactory,
|
|
265
287
|
transport,
|
|
@@ -277,6 +299,7 @@ class LogBrewClient {
|
|
|
277
299
|
this.maxQueueBytes = maxQueueBytes;
|
|
278
300
|
this.maxQueueSize = maxQueueSize;
|
|
279
301
|
this.onEventDropped = onEventDropped;
|
|
302
|
+
this.context = cloneTelemetryContext(context);
|
|
280
303
|
this.transport = transport;
|
|
281
304
|
this.sdk = sdk;
|
|
282
305
|
this.maxRetries = maxRetries;
|
|
@@ -351,6 +374,8 @@ class LogBrewClient {
|
|
|
351
374
|
this.lastAcceptedAtUnixMs = 0;
|
|
352
375
|
this.lastDroppedAtUnixMs = 0;
|
|
353
376
|
this.failedBatch = undefined;
|
|
377
|
+
this.issueBreadcrumbs = [];
|
|
378
|
+
this.issueBreadcrumbsTruncated = false;
|
|
354
379
|
this.#scheduleAutomaticDelivery();
|
|
355
380
|
}
|
|
356
381
|
|
|
@@ -454,6 +479,28 @@ class LogBrewClient {
|
|
|
454
479
|
return purgedEvents;
|
|
455
480
|
}
|
|
456
481
|
|
|
482
|
+
addBreadcrumb(breadcrumb, timestamp = new Date().toISOString()) {
|
|
483
|
+
if (this.closed) {
|
|
484
|
+
throw new SdkError("shutdown_error", "client is already shut down");
|
|
485
|
+
}
|
|
486
|
+
if (this.closing) {
|
|
487
|
+
throw new SdkError("shutdown_error", "client is shutting down");
|
|
488
|
+
}
|
|
489
|
+
const validated = validateIssueBreadcrumb(breadcrumb, timestamp);
|
|
490
|
+
if (this.issueBreadcrumbs.length === MAX_ISSUE_BREADCRUMBS) {
|
|
491
|
+
this.issueBreadcrumbs.shift();
|
|
492
|
+
this.issueBreadcrumbsTruncated = true;
|
|
493
|
+
}
|
|
494
|
+
this.issueBreadcrumbs.push(validated);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
clearBreadcrumbs() {
|
|
498
|
+
const cleared = this.issueBreadcrumbs.length;
|
|
499
|
+
this.issueBreadcrumbs.splice(0, cleared);
|
|
500
|
+
this.issueBreadcrumbsTruncated = false;
|
|
501
|
+
return cleared;
|
|
502
|
+
}
|
|
503
|
+
|
|
457
504
|
release(id, timestamp, attributes) {
|
|
458
505
|
this.#pushEvent("release", id, timestamp, validateRelease(attributes));
|
|
459
506
|
}
|
|
@@ -463,7 +510,18 @@ class LogBrewClient {
|
|
|
463
510
|
}
|
|
464
511
|
|
|
465
512
|
issue(id, timestamp, attributes) {
|
|
466
|
-
|
|
513
|
+
const withBreadcrumbs = attributes.breadcrumbs !== undefined || this.issueBreadcrumbs.length === 0
|
|
514
|
+
? attributes
|
|
515
|
+
: {
|
|
516
|
+
...attributes,
|
|
517
|
+
breadcrumbs: this.issueBreadcrumbs,
|
|
518
|
+
...(
|
|
519
|
+
attributes.breadcrumbsTruncated === true || this.issueBreadcrumbsTruncated
|
|
520
|
+
? { breadcrumbsTruncated: true }
|
|
521
|
+
: {}
|
|
522
|
+
)
|
|
523
|
+
};
|
|
524
|
+
this.#pushEvent("issue", id, timestamp, validateIssue(withBreadcrumbs));
|
|
467
525
|
}
|
|
468
526
|
|
|
469
527
|
log(id, timestamp, attributes) {
|
|
@@ -561,7 +619,11 @@ class LogBrewClient {
|
|
|
561
619
|
}
|
|
562
620
|
requireNonEmpty("event id", id);
|
|
563
621
|
requireTimestamp(timestamp);
|
|
564
|
-
const
|
|
622
|
+
const context = mergeTelemetryContexts(this.context, attributes.context);
|
|
623
|
+
const eventAttributes = context === undefined
|
|
624
|
+
? attributes
|
|
625
|
+
: { ...attributes, context };
|
|
626
|
+
const event = { type: eventType, id, timestamp, attributes: eventAttributes };
|
|
565
627
|
if (this.eventFilter && this.eventFilter(cloneEvent(event)) === false) {
|
|
566
628
|
return;
|
|
567
629
|
}
|
|
@@ -1330,6 +1392,11 @@ function createIssueAttributesFromError(error, options = {}) {
|
|
|
1330
1392
|
title: stringOrUndefined(options.title) ?? details.name,
|
|
1331
1393
|
level: normalizeSeverity("issue level", options.level ?? "error"),
|
|
1332
1394
|
...(stringOrUndefined(options.message) ? { message: options.message } : details.message ? { message: details.message } : {}),
|
|
1395
|
+
exception: createIssueException(
|
|
1396
|
+
boundedIssueExceptionType(details.name),
|
|
1397
|
+
stringOrUndefined(options.mechanism) ?? "javascript.error",
|
|
1398
|
+
options.handled === undefined ? true : options.handled
|
|
1399
|
+
),
|
|
1333
1400
|
...(stackFrames.length > 0 ? { stackFrames } : {}),
|
|
1334
1401
|
metadata: compactMetadata(metadata)
|
|
1335
1402
|
};
|
|
@@ -1355,6 +1422,23 @@ function errorDetails(error) {
|
|
|
1355
1422
|
return { name: "Error" };
|
|
1356
1423
|
}
|
|
1357
1424
|
|
|
1425
|
+
function boundedIssueExceptionType(value) {
|
|
1426
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
1427
|
+
const characters = Array.from(normalized);
|
|
1428
|
+
if (
|
|
1429
|
+
normalized === ""
|
|
1430
|
+
|| characters.length > 256
|
|
1431
|
+
|| /[?#]/u.test(normalized)
|
|
1432
|
+
|| characters.some((character) => {
|
|
1433
|
+
const code = character.codePointAt(0);
|
|
1434
|
+
return code !== undefined && (code <= 31 || (code >= 127 && code <= 159));
|
|
1435
|
+
})
|
|
1436
|
+
) {
|
|
1437
|
+
return "Error";
|
|
1438
|
+
}
|
|
1439
|
+
return normalized;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1358
1442
|
function issueGroupingMetadata(source, details, frame, fingerprint) {
|
|
1359
1443
|
const groupingKey = frame
|
|
1360
1444
|
? `${source}:${details.name}:${frame.filename}`
|
|
@@ -1505,6 +1589,7 @@ function logbrewLevelFromConsoleMethod(method) {
|
|
|
1505
1589
|
|
|
1506
1590
|
function createProductActionAttributes(action, options = {}) {
|
|
1507
1591
|
const details = productActionDetails(action);
|
|
1592
|
+
const routeTemplate = sanitizeRouteTemplate(details.routeTemplate);
|
|
1508
1593
|
return {
|
|
1509
1594
|
name: details.name,
|
|
1510
1595
|
status: details.status,
|
|
@@ -1512,12 +1597,13 @@ function createProductActionAttributes(action, options = {}) {
|
|
|
1512
1597
|
source: "product.action",
|
|
1513
1598
|
...compactMetadata(options.metadata),
|
|
1514
1599
|
...compactMetadata(details.metadata),
|
|
1515
|
-
routeTemplate
|
|
1600
|
+
routeTemplate,
|
|
1516
1601
|
sessionId: stringOrUndefined(details.sessionId),
|
|
1517
1602
|
traceId: stringOrUndefined(details.traceId),
|
|
1518
1603
|
screen: stringOrUndefined(details.screen),
|
|
1519
1604
|
funnel: stringOrUndefined(details.funnel),
|
|
1520
|
-
step: stringOrUndefined(details.step)
|
|
1605
|
+
step: stringOrUndefined(details.step),
|
|
1606
|
+
...productAnalyticsMetadata("interaction", routeTemplate || details.screen)
|
|
1521
1607
|
})
|
|
1522
1608
|
};
|
|
1523
1609
|
}
|
|
@@ -2002,7 +2088,7 @@ function cloneSpanLinks(links) {
|
|
|
2002
2088
|
}
|
|
2003
2089
|
|
|
2004
2090
|
function cloneEvent(event) {
|
|
2005
|
-
const attributes = { ...event.attributes };
|
|
2091
|
+
const attributes = { ...event.attributes, ...cloneIssueDiagnostics(event.attributes) };
|
|
2006
2092
|
if (event.attributes.metadata !== undefined) {
|
|
2007
2093
|
attributes.metadata = { ...event.attributes.metadata };
|
|
2008
2094
|
}
|
|
@@ -2012,6 +2098,9 @@ function cloneEvent(event) {
|
|
|
2012
2098
|
if (Array.isArray(event.attributes.links)) {
|
|
2013
2099
|
attributes.links = cloneSpanLinks(event.attributes.links);
|
|
2014
2100
|
}
|
|
2101
|
+
if (event.attributes.context !== undefined) {
|
|
2102
|
+
attributes.context = cloneTelemetryContext(event.attributes.context);
|
|
2103
|
+
}
|
|
2015
2104
|
return { ...event, attributes };
|
|
2016
2105
|
}
|
|
2017
2106
|
|
|
@@ -2024,7 +2113,7 @@ function validateRelease(attributes) {
|
|
|
2024
2113
|
version: attributes.version,
|
|
2025
2114
|
...(attributes.commit ? { commit: attributes.commit } : {}),
|
|
2026
2115
|
...(attributes.notes !== undefined ? { notes: attributes.notes } : {})
|
|
2027
|
-
}, attributes.metadata);
|
|
2116
|
+
}, attributes.metadata, attributes.context);
|
|
2028
2117
|
}
|
|
2029
2118
|
|
|
2030
2119
|
function validateEnvironment(attributes) {
|
|
@@ -2032,7 +2121,7 @@ function validateEnvironment(attributes) {
|
|
|
2032
2121
|
return withMetadata({
|
|
2033
2122
|
name: attributes.name,
|
|
2034
2123
|
...(attributes.region !== undefined ? { region: attributes.region } : {})
|
|
2035
|
-
}, attributes.metadata);
|
|
2124
|
+
}, attributes.metadata, attributes.context);
|
|
2036
2125
|
}
|
|
2037
2126
|
|
|
2038
2127
|
function validateIssue(attributes) {
|
|
@@ -2043,8 +2132,9 @@ function validateIssue(attributes) {
|
|
|
2043
2132
|
title: attributes.title,
|
|
2044
2133
|
level,
|
|
2045
2134
|
...(attributes.message !== undefined ? { message: attributes.message } : {}),
|
|
2046
|
-
...(stackFrames !== undefined ? { stackFrames } : {})
|
|
2047
|
-
|
|
2135
|
+
...(stackFrames !== undefined ? { stackFrames } : {}),
|
|
2136
|
+
...validateIssueDiagnostics(attributes)
|
|
2137
|
+
}, attributes.metadata, attributes.context);
|
|
2048
2138
|
}
|
|
2049
2139
|
|
|
2050
2140
|
function validateLog(attributes) {
|
|
@@ -2054,7 +2144,7 @@ function validateLog(attributes) {
|
|
|
2054
2144
|
message: attributes.message,
|
|
2055
2145
|
level,
|
|
2056
2146
|
...(attributes.logger !== undefined ? { logger: attributes.logger } : {})
|
|
2057
|
-
}, attributes.metadata);
|
|
2147
|
+
}, attributes.metadata, attributes.context);
|
|
2058
2148
|
}
|
|
2059
2149
|
|
|
2060
2150
|
function normalizeSeverity(label, value) {
|
|
@@ -2086,7 +2176,7 @@ function validateSpan(attributes) {
|
|
|
2086
2176
|
...(attributes.durationMs !== undefined ? { durationMs: attributes.durationMs } : {}),
|
|
2087
2177
|
...(events !== undefined ? { events } : {}),
|
|
2088
2178
|
...(links !== undefined ? { links } : {})
|
|
2089
|
-
}, attributes.metadata);
|
|
2179
|
+
}, attributes.metadata, attributes.context);
|
|
2090
2180
|
}
|
|
2091
2181
|
|
|
2092
2182
|
function validateSpanEvents(events) {
|
|
@@ -2169,7 +2259,7 @@ function validateAction(attributes) {
|
|
|
2169
2259
|
return withMetadata({
|
|
2170
2260
|
name: attributes.name,
|
|
2171
2261
|
status: attributes.status
|
|
2172
|
-
}, attributes.metadata);
|
|
2262
|
+
}, attributes.metadata, attributes.context);
|
|
2173
2263
|
}
|
|
2174
2264
|
|
|
2175
2265
|
function validateMetric(attributes) {
|
|
@@ -2183,14 +2273,43 @@ function validateMetric(attributes) {
|
|
|
2183
2273
|
if (NON_NEGATIVE_METRIC_KINDS.has(attributes.kind) && attributes.value < 0) {
|
|
2184
2274
|
throw new SdkError("validation_error", `metric ${attributes.kind} value must be non-negative`);
|
|
2185
2275
|
}
|
|
2276
|
+
const description = metricDescriptionOrUndefined(attributes.description);
|
|
2186
2277
|
|
|
2187
2278
|
return withMetadata({
|
|
2188
2279
|
name: attributes.name,
|
|
2280
|
+
...(description === undefined ? {} : { description }),
|
|
2189
2281
|
kind: attributes.kind,
|
|
2190
2282
|
value: attributes.value,
|
|
2191
2283
|
unit: attributes.unit,
|
|
2192
2284
|
temporality: attributes.temporality
|
|
2193
|
-
}, attributes.metadata);
|
|
2285
|
+
}, attributes.metadata, attributes.context);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
function metricDescriptionOrUndefined(value) {
|
|
2289
|
+
if (value === undefined) {
|
|
2290
|
+
return undefined;
|
|
2291
|
+
}
|
|
2292
|
+
const characters = typeof value === "string" ? Array.from(value) : [];
|
|
2293
|
+
if (
|
|
2294
|
+
typeof value !== "string"
|
|
2295
|
+
|| value.trim() === ""
|
|
2296
|
+
|| characters.length > MAX_METRIC_DESCRIPTION_LENGTH
|
|
2297
|
+
|| characters.some((character) => {
|
|
2298
|
+
const code = character.codePointAt(0);
|
|
2299
|
+
return code !== undefined && (
|
|
2300
|
+
code <= 31
|
|
2301
|
+
|| (code >= 127 && code <= 159)
|
|
2302
|
+
|| code === 0x2028
|
|
2303
|
+
|| code === 0x2029
|
|
2304
|
+
);
|
|
2305
|
+
})
|
|
2306
|
+
) {
|
|
2307
|
+
throw new SdkError(
|
|
2308
|
+
"validation_error",
|
|
2309
|
+
`metric description must be a non-blank string of at most ${MAX_METRIC_DESCRIPTION_LENGTH} non-control characters`
|
|
2310
|
+
);
|
|
2311
|
+
}
|
|
2312
|
+
return value.trim();
|
|
2194
2313
|
}
|
|
2195
2314
|
|
|
2196
2315
|
function productActionDetails(action) {
|
|
@@ -2269,6 +2388,34 @@ function sanitizeRouteTemplate(routeTemplate) {
|
|
|
2269
2388
|
}
|
|
2270
2389
|
}
|
|
2271
2390
|
|
|
2391
|
+
function productAnalyticsMetadata(kind, surface) {
|
|
2392
|
+
const normalizedSurface = boundedProductAnalyticsSurface(surface);
|
|
2393
|
+
return {
|
|
2394
|
+
analyticsSchemaVersion: PRODUCT_ANALYTICS_SCHEMA_VERSION,
|
|
2395
|
+
analyticsKind: kind,
|
|
2396
|
+
analyticsSurface: normalizedSurface
|
|
2397
|
+
};
|
|
2398
|
+
}
|
|
2399
|
+
|
|
2400
|
+
function boundedProductAnalyticsSurface(surface) {
|
|
2401
|
+
if (typeof surface !== "string") {
|
|
2402
|
+
return undefined;
|
|
2403
|
+
}
|
|
2404
|
+
const normalized = surface.trim();
|
|
2405
|
+
const characters = Array.from(normalized);
|
|
2406
|
+
if (
|
|
2407
|
+
normalized === ""
|
|
2408
|
+
|| characters.length > MAX_PRODUCT_ANALYTICS_SURFACE_LENGTH
|
|
2409
|
+
|| characters.some((character) => {
|
|
2410
|
+
const code = character.codePointAt(0);
|
|
2411
|
+
return code !== undefined && (code <= 31 || (code >= 127 && code <= 159));
|
|
2412
|
+
})
|
|
2413
|
+
) {
|
|
2414
|
+
return undefined;
|
|
2415
|
+
}
|
|
2416
|
+
return normalized;
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2272
2419
|
function normalizeHttpMethod(method) {
|
|
2273
2420
|
const value = method === undefined ? "GET" : method;
|
|
2274
2421
|
if (typeof value !== "string" || value.trim() === "") {
|
|
@@ -2312,11 +2459,14 @@ function stringOrUndefined(value) {
|
|
|
2312
2459
|
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
2313
2460
|
}
|
|
2314
2461
|
|
|
2315
|
-
function withMetadata(attributes, metadata) {
|
|
2462
|
+
function withMetadata(attributes, metadata, context) {
|
|
2316
2463
|
const safeMetadata = cloneMetadata(metadata);
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2464
|
+
const safeContext = validateTelemetryContext(context);
|
|
2465
|
+
return {
|
|
2466
|
+
...attributes,
|
|
2467
|
+
...(safeMetadata === undefined ? {} : { metadata: safeMetadata }),
|
|
2468
|
+
...(safeContext === undefined ? {} : { context: safeContext })
|
|
2469
|
+
};
|
|
2320
2470
|
}
|
|
2321
2471
|
|
|
2322
2472
|
function normalizeConsoleLevels(levels) {
|
|
@@ -2367,6 +2517,8 @@ function formatConsoleArgument(value, includeErrorStack) {
|
|
|
2367
2517
|
}
|
|
2368
2518
|
|
|
2369
2519
|
module.exports = {
|
|
2520
|
+
PRODUCT_ANALYTICS_KINDS,
|
|
2521
|
+
PRODUCT_ANALYTICS_SCHEMA_VERSION,
|
|
2370
2522
|
createBaggage,
|
|
2371
2523
|
createIssueAttributesFromError,
|
|
2372
2524
|
createNetworkMilestoneAttributes,
|