@logbrew/sdk 0.1.5 → 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 +63 -3
- package/core.cjs +200 -18
- 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
|
|
|
@@ -585,7 +645,7 @@ logger.error(new Error("payment failed"), "checkout failed");
|
|
|
585
645
|
await destination.flush();
|
|
586
646
|
```
|
|
587
647
|
|
|
588
|
-
The Pino adapter reads JSON log lines, maps Pino `trace`/`debug` to LogBrew `info`, `warn` to `warning`, `error` to `error`, and `fatal` to `critical`, captures primitive Pino fields as `context.*`, captures serialized error name/message, skips noisy runtime defaults, and omits stack text unless `includeErrorStack: true` is set. It does not patch Pino or replace application logger ownership.
|
|
648
|
+
The Pino adapter reads JSON log lines, maps Pino `trace`/`debug` to LogBrew `info`, `warn` to `warning`, `error` to `error`, and `fatal` to `critical`, captures safe primitive Pino fields as `context.*`, captures serialized error name/message, skips noisy runtime defaults, and omits stack text unless `includeErrorStack: true` is set. Credential, authorization, cookie, body, payload, query, raw-URL, propagation-header, local-path, and stack fields are excluded even when they are primitive. It does not patch Pino or replace application logger ownership. The log message itself is telemetry, so retain normal Pino redaction and keep secrets or user-entered text out of messages.
|
|
589
649
|
|
|
590
650
|
When the app also uses a LogBrew Node or framework request helper, pass `traceProvider: getActiveLogBrewTrace` from `@logbrew/node` to add the current active `traceId`, `spanId`, optional `parentSpanId`, and `sampled` flag to each captured log. The provider is called per record, invalid or missing contexts are ignored, and no raw propagation headers, request data, payloads, or stack traces are captured.
|
|
591
651
|
|
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([
|
|
@@ -28,6 +33,44 @@ const CONSOLE_METHODS = new Set(["debug", "info", "log", "warn", "error"]);
|
|
|
28
33
|
const DEFAULT_CONSOLE_LEVELS = ["debug", "info", "log", "warn", "error"];
|
|
29
34
|
const PINO_HOST_FIELD = ["host", "name"].join("");
|
|
30
35
|
const PINO_RESERVED_FIELDS = new Set(["level", "time", "timestamp", "msg", "message", "err", "error", "pid", PINO_HOST_FIELD, "v"]);
|
|
36
|
+
const PINO_SENSITIVE_CONTEXT_FIELDS = new Set([
|
|
37
|
+
"apikey",
|
|
38
|
+
"authorization",
|
|
39
|
+
"baggage",
|
|
40
|
+
"body",
|
|
41
|
+
"cookie",
|
|
42
|
+
"credentials",
|
|
43
|
+
"cwd",
|
|
44
|
+
"directory",
|
|
45
|
+
"dirname",
|
|
46
|
+
"errorstack",
|
|
47
|
+
"file",
|
|
48
|
+
"filename",
|
|
49
|
+
"filepath",
|
|
50
|
+
"headers",
|
|
51
|
+
"href",
|
|
52
|
+
"key",
|
|
53
|
+
"password",
|
|
54
|
+
"payload",
|
|
55
|
+
"proxyauthorization",
|
|
56
|
+
"query",
|
|
57
|
+
"querystring",
|
|
58
|
+
"requestbody",
|
|
59
|
+
"requestheaders",
|
|
60
|
+
"requesturl",
|
|
61
|
+
"responsebody",
|
|
62
|
+
"responseheaders",
|
|
63
|
+
"responseurl",
|
|
64
|
+
"search",
|
|
65
|
+
"secret",
|
|
66
|
+
"setcookie",
|
|
67
|
+
"stack",
|
|
68
|
+
"token",
|
|
69
|
+
"traceparent",
|
|
70
|
+
"tracestate",
|
|
71
|
+
"uri",
|
|
72
|
+
"url"
|
|
73
|
+
]);
|
|
31
74
|
const TRACEPARENT_PATTERN = /^([0-9a-fA-F]{2})-([0-9a-fA-F]{32})-([0-9a-fA-F]{16})-([0-9a-fA-F]{2})$/u;
|
|
32
75
|
const ZERO_TRACE_ID = "00000000000000000000000000000000";
|
|
33
76
|
const ZERO_SPAN_ID = "0000000000000000";
|
|
@@ -86,6 +129,18 @@ const {
|
|
|
86
129
|
} = buildLogContextHelpers({ SdkError });
|
|
87
130
|
|
|
88
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 });
|
|
89
144
|
|
|
90
145
|
class TransportError extends Error {
|
|
91
146
|
constructor(code, message, retryable = false) {
|
|
@@ -138,6 +193,7 @@ class LogBrewClient {
|
|
|
138
193
|
apiKey,
|
|
139
194
|
sdkName,
|
|
140
195
|
sdkVersion,
|
|
196
|
+
context,
|
|
141
197
|
maxRetries = 2,
|
|
142
198
|
eventFilter,
|
|
143
199
|
maxQueueSize = DEFAULT_MAX_QUEUE_SIZE,
|
|
@@ -155,6 +211,7 @@ class LogBrewClient {
|
|
|
155
211
|
requireNonEmpty("apiKey", apiKey);
|
|
156
212
|
requireNonEmpty("sdkName", sdkName);
|
|
157
213
|
requireNonEmpty("sdkVersion", sdkVersion);
|
|
214
|
+
const clientContext = validateTelemetryContext(context, "client telemetry context");
|
|
158
215
|
requireNonNegativeInteger("maxRetries", maxRetries);
|
|
159
216
|
if (eventFilter !== undefined && typeof eventFilter !== "function") {
|
|
160
217
|
throw new SdkError("validation_error", "eventFilter must be a function");
|
|
@@ -202,6 +259,7 @@ class LogBrewClient {
|
|
|
202
259
|
maxQueueBytes,
|
|
203
260
|
maxQueueSize,
|
|
204
261
|
onEventDropped,
|
|
262
|
+
context: clientContext,
|
|
205
263
|
sdk: {
|
|
206
264
|
name: sdkName,
|
|
207
265
|
language: "javascript",
|
|
@@ -222,6 +280,7 @@ class LogBrewClient {
|
|
|
222
280
|
maxQueueBytes,
|
|
223
281
|
maxQueueSize,
|
|
224
282
|
onEventDropped,
|
|
283
|
+
context,
|
|
225
284
|
eventStore,
|
|
226
285
|
eventQueueFactory,
|
|
227
286
|
transport,
|
|
@@ -239,6 +298,7 @@ class LogBrewClient {
|
|
|
239
298
|
this.maxQueueBytes = maxQueueBytes;
|
|
240
299
|
this.maxQueueSize = maxQueueSize;
|
|
241
300
|
this.onEventDropped = onEventDropped;
|
|
301
|
+
this.context = cloneTelemetryContext(context);
|
|
242
302
|
this.transport = transport;
|
|
243
303
|
this.sdk = sdk;
|
|
244
304
|
this.maxRetries = maxRetries;
|
|
@@ -313,6 +373,8 @@ class LogBrewClient {
|
|
|
313
373
|
this.lastAcceptedAtUnixMs = 0;
|
|
314
374
|
this.lastDroppedAtUnixMs = 0;
|
|
315
375
|
this.failedBatch = undefined;
|
|
376
|
+
this.issueBreadcrumbs = [];
|
|
377
|
+
this.issueBreadcrumbsTruncated = false;
|
|
316
378
|
this.#scheduleAutomaticDelivery();
|
|
317
379
|
}
|
|
318
380
|
|
|
@@ -416,6 +478,28 @@ class LogBrewClient {
|
|
|
416
478
|
return purgedEvents;
|
|
417
479
|
}
|
|
418
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
|
+
|
|
419
503
|
release(id, timestamp, attributes) {
|
|
420
504
|
this.#pushEvent("release", id, timestamp, validateRelease(attributes));
|
|
421
505
|
}
|
|
@@ -425,7 +509,18 @@ class LogBrewClient {
|
|
|
425
509
|
}
|
|
426
510
|
|
|
427
511
|
issue(id, timestamp, attributes) {
|
|
428
|
-
|
|
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));
|
|
429
524
|
}
|
|
430
525
|
|
|
431
526
|
log(id, timestamp, attributes) {
|
|
@@ -523,7 +618,11 @@ class LogBrewClient {
|
|
|
523
618
|
}
|
|
524
619
|
requireNonEmpty("event id", id);
|
|
525
620
|
requireTimestamp(timestamp);
|
|
526
|
-
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 };
|
|
527
626
|
if (this.eventFilter && this.eventFilter(cloneEvent(event)) === false) {
|
|
528
627
|
return;
|
|
529
628
|
}
|
|
@@ -1292,6 +1391,11 @@ function createIssueAttributesFromError(error, options = {}) {
|
|
|
1292
1391
|
title: stringOrUndefined(options.title) ?? details.name,
|
|
1293
1392
|
level: normalizeSeverity("issue level", options.level ?? "error"),
|
|
1294
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
|
+
),
|
|
1295
1399
|
...(stackFrames.length > 0 ? { stackFrames } : {}),
|
|
1296
1400
|
metadata: compactMetadata(metadata)
|
|
1297
1401
|
};
|
|
@@ -1317,6 +1421,23 @@ function errorDetails(error) {
|
|
|
1317
1421
|
return { name: "Error" };
|
|
1318
1422
|
}
|
|
1319
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
|
+
|
|
1320
1441
|
function issueGroupingMetadata(source, details, frame, fingerprint) {
|
|
1321
1442
|
const groupingKey = frame
|
|
1322
1443
|
? `${source}:${details.name}:${frame.filename}`
|
|
@@ -1467,6 +1588,7 @@ function logbrewLevelFromConsoleMethod(method) {
|
|
|
1467
1588
|
|
|
1468
1589
|
function createProductActionAttributes(action, options = {}) {
|
|
1469
1590
|
const details = productActionDetails(action);
|
|
1591
|
+
const routeTemplate = sanitizeRouteTemplate(details.routeTemplate);
|
|
1470
1592
|
return {
|
|
1471
1593
|
name: details.name,
|
|
1472
1594
|
status: details.status,
|
|
@@ -1474,12 +1596,13 @@ function createProductActionAttributes(action, options = {}) {
|
|
|
1474
1596
|
source: "product.action",
|
|
1475
1597
|
...compactMetadata(options.metadata),
|
|
1476
1598
|
...compactMetadata(details.metadata),
|
|
1477
|
-
routeTemplate
|
|
1599
|
+
routeTemplate,
|
|
1478
1600
|
sessionId: stringOrUndefined(details.sessionId),
|
|
1479
1601
|
traceId: stringOrUndefined(details.traceId),
|
|
1480
1602
|
screen: stringOrUndefined(details.screen),
|
|
1481
1603
|
funnel: stringOrUndefined(details.funnel),
|
|
1482
|
-
step: stringOrUndefined(details.step)
|
|
1604
|
+
step: stringOrUndefined(details.step),
|
|
1605
|
+
...productAnalyticsMetadata("interaction", routeTemplate || details.screen)
|
|
1483
1606
|
})
|
|
1484
1607
|
};
|
|
1485
1608
|
}
|
|
@@ -1798,13 +1921,35 @@ function pinoMessage(record) {
|
|
|
1798
1921
|
function pinoContextMetadata(record) {
|
|
1799
1922
|
const metadata = {};
|
|
1800
1923
|
for (const [key, value] of Object.entries(record)) {
|
|
1801
|
-
if (!PINO_RESERVED_FIELDS.has(key) && isMetadataValue(value)) {
|
|
1924
|
+
if (!PINO_RESERVED_FIELDS.has(key) && !isSensitivePinoContextField(key) && isMetadataValue(value)) {
|
|
1802
1925
|
metadata[`context.${key}`] = value;
|
|
1803
1926
|
}
|
|
1804
1927
|
}
|
|
1805
1928
|
return metadata;
|
|
1806
1929
|
}
|
|
1807
1930
|
|
|
1931
|
+
function isSensitivePinoContextField(key) {
|
|
1932
|
+
const normalized = key.toLowerCase().replace(/[^a-z0-9]+/gu, "");
|
|
1933
|
+
return (
|
|
1934
|
+
PINO_SENSITIVE_CONTEXT_FIELDS.has(normalized)
|
|
1935
|
+
|| normalized.endsWith("accesskey")
|
|
1936
|
+
|| normalized.endsWith("apikey")
|
|
1937
|
+
|| normalized.endsWith("authorization")
|
|
1938
|
+
|| normalized.endsWith("body")
|
|
1939
|
+
|| normalized.endsWith("cookie")
|
|
1940
|
+
|| normalized.endsWith("credentials")
|
|
1941
|
+
|| normalized.endsWith("headers")
|
|
1942
|
+
|| normalized.endsWith("password")
|
|
1943
|
+
|| normalized.endsWith("payload")
|
|
1944
|
+
|| normalized.endsWith("privatekey")
|
|
1945
|
+
|| normalized.endsWith("query")
|
|
1946
|
+
|| normalized.endsWith("secret")
|
|
1947
|
+
|| normalized.endsWith("stack")
|
|
1948
|
+
|| normalized.endsWith("token")
|
|
1949
|
+
|| normalized.endsWith("url")
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1808
1953
|
function addPinoErrorMetadata(metadata, error, includeErrorStack) {
|
|
1809
1954
|
if (!error) {
|
|
1810
1955
|
return;
|
|
@@ -1942,7 +2087,7 @@ function cloneSpanLinks(links) {
|
|
|
1942
2087
|
}
|
|
1943
2088
|
|
|
1944
2089
|
function cloneEvent(event) {
|
|
1945
|
-
const attributes = { ...event.attributes };
|
|
2090
|
+
const attributes = { ...event.attributes, ...cloneIssueDiagnostics(event.attributes) };
|
|
1946
2091
|
if (event.attributes.metadata !== undefined) {
|
|
1947
2092
|
attributes.metadata = { ...event.attributes.metadata };
|
|
1948
2093
|
}
|
|
@@ -1952,6 +2097,9 @@ function cloneEvent(event) {
|
|
|
1952
2097
|
if (Array.isArray(event.attributes.links)) {
|
|
1953
2098
|
attributes.links = cloneSpanLinks(event.attributes.links);
|
|
1954
2099
|
}
|
|
2100
|
+
if (event.attributes.context !== undefined) {
|
|
2101
|
+
attributes.context = cloneTelemetryContext(event.attributes.context);
|
|
2102
|
+
}
|
|
1955
2103
|
return { ...event, attributes };
|
|
1956
2104
|
}
|
|
1957
2105
|
|
|
@@ -1964,7 +2112,7 @@ function validateRelease(attributes) {
|
|
|
1964
2112
|
version: attributes.version,
|
|
1965
2113
|
...(attributes.commit ? { commit: attributes.commit } : {}),
|
|
1966
2114
|
...(attributes.notes !== undefined ? { notes: attributes.notes } : {})
|
|
1967
|
-
}, attributes.metadata);
|
|
2115
|
+
}, attributes.metadata, attributes.context);
|
|
1968
2116
|
}
|
|
1969
2117
|
|
|
1970
2118
|
function validateEnvironment(attributes) {
|
|
@@ -1972,7 +2120,7 @@ function validateEnvironment(attributes) {
|
|
|
1972
2120
|
return withMetadata({
|
|
1973
2121
|
name: attributes.name,
|
|
1974
2122
|
...(attributes.region !== undefined ? { region: attributes.region } : {})
|
|
1975
|
-
}, attributes.metadata);
|
|
2123
|
+
}, attributes.metadata, attributes.context);
|
|
1976
2124
|
}
|
|
1977
2125
|
|
|
1978
2126
|
function validateIssue(attributes) {
|
|
@@ -1983,8 +2131,9 @@ function validateIssue(attributes) {
|
|
|
1983
2131
|
title: attributes.title,
|
|
1984
2132
|
level,
|
|
1985
2133
|
...(attributes.message !== undefined ? { message: attributes.message } : {}),
|
|
1986
|
-
...(stackFrames !== undefined ? { stackFrames } : {})
|
|
1987
|
-
|
|
2134
|
+
...(stackFrames !== undefined ? { stackFrames } : {}),
|
|
2135
|
+
...validateIssueDiagnostics(attributes)
|
|
2136
|
+
}, attributes.metadata, attributes.context);
|
|
1988
2137
|
}
|
|
1989
2138
|
|
|
1990
2139
|
function validateLog(attributes) {
|
|
@@ -1994,7 +2143,7 @@ function validateLog(attributes) {
|
|
|
1994
2143
|
message: attributes.message,
|
|
1995
2144
|
level,
|
|
1996
2145
|
...(attributes.logger !== undefined ? { logger: attributes.logger } : {})
|
|
1997
|
-
}, attributes.metadata);
|
|
2146
|
+
}, attributes.metadata, attributes.context);
|
|
1998
2147
|
}
|
|
1999
2148
|
|
|
2000
2149
|
function normalizeSeverity(label, value) {
|
|
@@ -2026,7 +2175,7 @@ function validateSpan(attributes) {
|
|
|
2026
2175
|
...(attributes.durationMs !== undefined ? { durationMs: attributes.durationMs } : {}),
|
|
2027
2176
|
...(events !== undefined ? { events } : {}),
|
|
2028
2177
|
...(links !== undefined ? { links } : {})
|
|
2029
|
-
}, attributes.metadata);
|
|
2178
|
+
}, attributes.metadata, attributes.context);
|
|
2030
2179
|
}
|
|
2031
2180
|
|
|
2032
2181
|
function validateSpanEvents(events) {
|
|
@@ -2109,7 +2258,7 @@ function validateAction(attributes) {
|
|
|
2109
2258
|
return withMetadata({
|
|
2110
2259
|
name: attributes.name,
|
|
2111
2260
|
status: attributes.status
|
|
2112
|
-
}, attributes.metadata);
|
|
2261
|
+
}, attributes.metadata, attributes.context);
|
|
2113
2262
|
}
|
|
2114
2263
|
|
|
2115
2264
|
function validateMetric(attributes) {
|
|
@@ -2130,7 +2279,7 @@ function validateMetric(attributes) {
|
|
|
2130
2279
|
value: attributes.value,
|
|
2131
2280
|
unit: attributes.unit,
|
|
2132
2281
|
temporality: attributes.temporality
|
|
2133
|
-
}, attributes.metadata);
|
|
2282
|
+
}, attributes.metadata, attributes.context);
|
|
2134
2283
|
}
|
|
2135
2284
|
|
|
2136
2285
|
function productActionDetails(action) {
|
|
@@ -2209,6 +2358,34 @@ function sanitizeRouteTemplate(routeTemplate) {
|
|
|
2209
2358
|
}
|
|
2210
2359
|
}
|
|
2211
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
|
+
|
|
2212
2389
|
function normalizeHttpMethod(method) {
|
|
2213
2390
|
const value = method === undefined ? "GET" : method;
|
|
2214
2391
|
if (typeof value !== "string" || value.trim() === "") {
|
|
@@ -2252,11 +2429,14 @@ function stringOrUndefined(value) {
|
|
|
2252
2429
|
return typeof value === "string" && value.trim() !== "" ? value : undefined;
|
|
2253
2430
|
}
|
|
2254
2431
|
|
|
2255
|
-
function withMetadata(attributes, metadata) {
|
|
2432
|
+
function withMetadata(attributes, metadata, context) {
|
|
2256
2433
|
const safeMetadata = cloneMetadata(metadata);
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2434
|
+
const safeContext = validateTelemetryContext(context);
|
|
2435
|
+
return {
|
|
2436
|
+
...attributes,
|
|
2437
|
+
...(safeMetadata === undefined ? {} : { metadata: safeMetadata }),
|
|
2438
|
+
...(safeContext === undefined ? {} : { context: safeContext })
|
|
2439
|
+
};
|
|
2260
2440
|
}
|
|
2261
2441
|
|
|
2262
2442
|
function normalizeConsoleLevels(levels) {
|
|
@@ -2307,6 +2487,8 @@ function formatConsoleArgument(value, includeErrorStack) {
|
|
|
2307
2487
|
}
|
|
2308
2488
|
|
|
2309
2489
|
module.exports = {
|
|
2490
|
+
PRODUCT_ANALYTICS_KINDS,
|
|
2491
|
+
PRODUCT_ANALYTICS_SCHEMA_VERSION,
|
|
2310
2492
|
createBaggage,
|
|
2311
2493
|
createIssueAttributesFromError,
|
|
2312
2494
|
createNetworkMilestoneAttributes,
|