@logbrew/sdk 0.1.36 → 0.1.38
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/core.cjs +44 -213
- package/issue-breadcrumbs.cjs +143 -0
- package/issue-diagnostics.cjs +94 -582
- package/issue-evidence.cjs +256 -0
- package/issue-exception.cjs +263 -0
- package/issue-stack-frames.cjs +220 -0
- package/issue-stack-text.cjs +101 -0
- package/issue-stack.cjs +188 -225
- package/issue-text.cjs +72 -0
- package/log-context.cjs +87 -67
- package/package.json +11 -1
- package/sdk-error.cjs +36 -0
- package/support-ticket.cjs +232 -122
- package/telemetry-context.cjs +343 -251
- package/trace-context.cjs +376 -179
- package/transport-error.cjs +23 -0
- package/validation.cjs +279 -0
- package/winston.cjs +6 -7
package/core.cjs
CHANGED
|
@@ -1,10 +1,48 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
const {
|
|
2
|
+
MAX_ISSUE_BREADCRUMBS,
|
|
3
|
+
cloneIssueDiagnostics,
|
|
4
|
+
createIssueException,
|
|
5
|
+
validateIssueBreadcrumb,
|
|
6
|
+
validateIssueDiagnostics,
|
|
7
|
+
validateIssueEvidence
|
|
8
|
+
} = require("./issue-diagnostics.cjs");
|
|
9
|
+
const { javascriptStackEvidence, validateIssueStackFrames, validateNativeStackFrames } = require("./issue-stack.cjs");
|
|
10
|
+
const {
|
|
11
|
+
compactMetadata,
|
|
12
|
+
isMetadataValue,
|
|
13
|
+
normalizeLogTraceContext,
|
|
14
|
+
traceFromProvider,
|
|
15
|
+
traceMetadataFromLogContext
|
|
16
|
+
} = require("./log-context.cjs");
|
|
5
17
|
const { buildOpenTelemetryHelpers } = require("./opentelemetry.cjs");
|
|
6
|
-
const {
|
|
7
|
-
|
|
18
|
+
const {
|
|
19
|
+
cloneTelemetryContext,
|
|
20
|
+
mergeTelemetryContexts,
|
|
21
|
+
validateTelemetryContext
|
|
22
|
+
} = require("./telemetry-context.cjs");
|
|
23
|
+
const { SdkError, retryAfterMsOrUndefined } = require("./sdk-error.cjs");
|
|
24
|
+
const { createSupportTicketDraft } = require("./support-ticket.cjs");
|
|
25
|
+
const { TransportError } = require("./transport-error.cjs");
|
|
26
|
+
const {
|
|
27
|
+
createBaggage,
|
|
28
|
+
createTraceContextHeaders,
|
|
29
|
+
createTraceparent,
|
|
30
|
+
createTraceparentHeaders,
|
|
31
|
+
createTracestate,
|
|
32
|
+
parseBaggage,
|
|
33
|
+
parseTraceparent,
|
|
34
|
+
parseTracestate
|
|
35
|
+
} = require("./trace-context.cjs");
|
|
36
|
+
const {
|
|
37
|
+
requireAllowedValue,
|
|
38
|
+
requireFiniteNumber,
|
|
39
|
+
requireNonEmpty,
|
|
40
|
+
requireNonNegativeInteger,
|
|
41
|
+
requirePositiveInteger,
|
|
42
|
+
requireSpanId,
|
|
43
|
+
requireTimestamp,
|
|
44
|
+
requireTraceId
|
|
45
|
+
} = require("./validation.cjs");
|
|
8
46
|
|
|
9
47
|
const SEVERITY_ALIASES = new Map([
|
|
10
48
|
["trace", "info"],
|
|
@@ -80,10 +118,6 @@ const PINO_SENSITIVE_CONTEXT_FIELDS = new Set([
|
|
|
80
118
|
"uri",
|
|
81
119
|
"url"
|
|
82
120
|
]);
|
|
83
|
-
const TRACEPARENT_PATTERN = /^([0-9a-fA-F]{2})-([0-9a-fA-F]{32})-([0-9a-fA-F]{16})-([0-9a-fA-F]{2})$/u;
|
|
84
|
-
const ZERO_TRACE_ID = "00000000000000000000000000000000";
|
|
85
|
-
const ZERO_SPAN_ID = "0000000000000000";
|
|
86
|
-
const RFC3339_TIMESTAMP_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d{1,9})?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/u;
|
|
87
121
|
const DEFAULT_MAX_QUEUE_SIZE = 1000;
|
|
88
122
|
const DEFAULT_MAX_QUEUE_BYTES = 4 * 1024 * 1024;
|
|
89
123
|
const DEFAULT_MAX_BATCH_EVENTS = 100;
|
|
@@ -129,60 +163,7 @@ const EVENT_VALIDATORS = new Map([
|
|
|
129
163
|
["action", validateAction],
|
|
130
164
|
["metric", validateMetric]
|
|
131
165
|
]);
|
|
132
|
-
class SdkError extends Error {
|
|
133
|
-
constructor(code, message, details = {}) {
|
|
134
|
-
super(message);
|
|
135
|
-
this.name = "SdkError";
|
|
136
|
-
this.code = code;
|
|
137
|
-
const retryAfterMs = retryAfterMsOrUndefined(details?.retryAfterMs);
|
|
138
|
-
if (retryAfterMs !== undefined) {
|
|
139
|
-
this.retryAfterMs = retryAfterMs;
|
|
140
|
-
}
|
|
141
|
-
if (details?.retryable === true) {
|
|
142
|
-
this.retryable = true;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
166
|
|
|
147
|
-
const {
|
|
148
|
-
compactMetadata,
|
|
149
|
-
isMetadataValue,
|
|
150
|
-
normalizeLogTraceContext,
|
|
151
|
-
traceFromProvider,
|
|
152
|
-
traceMetadataFromLogContext
|
|
153
|
-
} = buildLogContextHelpers({ SdkError });
|
|
154
|
-
|
|
155
|
-
const {
|
|
156
|
-
javascriptStackEvidence,
|
|
157
|
-
validateIssueStackFrames,
|
|
158
|
-
validateNativeStackFrames
|
|
159
|
-
} = buildIssueStackHelpers({ SdkError });
|
|
160
|
-
const {
|
|
161
|
-
MAX_ISSUE_BREADCRUMBS,
|
|
162
|
-
cloneIssueDiagnostics,
|
|
163
|
-
createIssueException,
|
|
164
|
-
validateIssueBreadcrumb,
|
|
165
|
-
validateIssueDiagnostics,
|
|
166
|
-
validateIssueEvidence
|
|
167
|
-
} = buildIssueDiagnosticsHelpers({ SdkError, requireTimestamp, validateIssueStackFrames });
|
|
168
|
-
const {
|
|
169
|
-
cloneTelemetryContext,
|
|
170
|
-
mergeTelemetryContexts,
|
|
171
|
-
validateTelemetryContext
|
|
172
|
-
} = buildTelemetryContextHelpers({ SdkError });
|
|
173
|
-
|
|
174
|
-
class TransportError extends Error {
|
|
175
|
-
constructor(code, message, retryable = false) {
|
|
176
|
-
super(message);
|
|
177
|
-
this.name = "TransportError";
|
|
178
|
-
this.code = code;
|
|
179
|
-
this.retryable = retryable;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
static network(message) {
|
|
183
|
-
return new TransportError("network_failure", message, true);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
167
|
|
|
187
168
|
class RecordingTransport {
|
|
188
169
|
constructor(scriptedResponses = [{ statusCode: 202 }]) {
|
|
@@ -2086,68 +2067,6 @@ function createNetworkMilestoneAttributes(request, options = {}) {
|
|
|
2086
2067
|
};
|
|
2087
2068
|
}
|
|
2088
2069
|
|
|
2089
|
-
function parseTraceparent(traceparent) {
|
|
2090
|
-
if (typeof traceparent !== "string" || traceparent.trim() === "") {
|
|
2091
|
-
throw new SdkError("validation_error", "traceparent must be non-empty");
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
const match = TRACEPARENT_PATTERN.exec(traceparent.trim());
|
|
2095
|
-
if (!match) {
|
|
2096
|
-
throw new SdkError("validation_error", "traceparent must use W3C version-traceId-parentSpanId-traceFlags format");
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
const version = match[1].toLowerCase();
|
|
2100
|
-
const traceId = match[2].toLowerCase();
|
|
2101
|
-
const parentSpanId = match[3].toLowerCase();
|
|
2102
|
-
const traceFlags = match[4].toLowerCase();
|
|
2103
|
-
if (version === "ff") {
|
|
2104
|
-
throw new SdkError("validation_error", "traceparent version ff is not allowed");
|
|
2105
|
-
}
|
|
2106
|
-
if (traceId === ZERO_TRACE_ID) {
|
|
2107
|
-
throw new SdkError("validation_error", "traceparent traceId must not be all zeros");
|
|
2108
|
-
}
|
|
2109
|
-
if (parentSpanId === ZERO_SPAN_ID) {
|
|
2110
|
-
throw new SdkError("validation_error", "traceparent parentSpanId must not be all zeros");
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
return {
|
|
2114
|
-
version,
|
|
2115
|
-
traceId,
|
|
2116
|
-
parentSpanId,
|
|
2117
|
-
traceFlags,
|
|
2118
|
-
sampled: (Number.parseInt(traceFlags, 16) & 1) === 1
|
|
2119
|
-
};
|
|
2120
|
-
}
|
|
2121
|
-
|
|
2122
|
-
function createTraceparent({ traceId, spanId, traceFlags = "01" }) {
|
|
2123
|
-
requireTraceId(traceId);
|
|
2124
|
-
requireSpanId("spanId", spanId);
|
|
2125
|
-
requireTraceFlags(traceFlags);
|
|
2126
|
-
return `00-${traceId.toLowerCase()}-${spanId.toLowerCase()}-${traceFlags.toLowerCase()}`;
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
function createTraceparentHeaders(input) {
|
|
2130
|
-
return { traceparent: createTraceparent(input) };
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2133
|
-
const {
|
|
2134
|
-
createBaggage,
|
|
2135
|
-
createTraceContextHeaders,
|
|
2136
|
-
createTracestate,
|
|
2137
|
-
parseBaggage,
|
|
2138
|
-
parseTracestate
|
|
2139
|
-
} = buildTraceContextHelpers({
|
|
2140
|
-
SdkError,
|
|
2141
|
-
createTraceparent
|
|
2142
|
-
});
|
|
2143
|
-
|
|
2144
|
-
const createSupportTicketDraft = buildCreateSupportTicketDraft({
|
|
2145
|
-
SdkError,
|
|
2146
|
-
requireAllowedValue,
|
|
2147
|
-
requireNonEmpty,
|
|
2148
|
-
requireTraceId
|
|
2149
|
-
});
|
|
2150
|
-
|
|
2151
2070
|
const {
|
|
2152
2071
|
createLogBrewOpenTelemetrySpanExporter,
|
|
2153
2072
|
createLogBrewOpenTelemetrySpanProcessor,
|
|
@@ -2444,94 +2363,6 @@ function addPinoErrorMetadata(metadata, error, includeErrorStack) {
|
|
|
2444
2363
|
}
|
|
2445
2364
|
}
|
|
2446
2365
|
|
|
2447
|
-
function requireNonEmpty(label, value) {
|
|
2448
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
2449
|
-
throw new SdkError("validation_error", `${label} must be non-empty`);
|
|
2450
|
-
}
|
|
2451
|
-
}
|
|
2452
|
-
|
|
2453
|
-
function requireAllowedValue(label, value, allowedValues) {
|
|
2454
|
-
requireNonEmpty(label, value);
|
|
2455
|
-
if (!allowedValues.has(value)) {
|
|
2456
|
-
throw new SdkError(
|
|
2457
|
-
"validation_error",
|
|
2458
|
-
`${label} must be one of: ${Array.from(allowedValues).join(", ")}`
|
|
2459
|
-
);
|
|
2460
|
-
}
|
|
2461
|
-
}
|
|
2462
|
-
|
|
2463
|
-
function requireFiniteNumber(label, value) {
|
|
2464
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
2465
|
-
throw new SdkError("validation_error", `${label} must be a finite number`);
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2468
|
-
|
|
2469
|
-
function requirePositiveInteger(label, value) {
|
|
2470
|
-
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
2471
|
-
throw new SdkError("validation_error", `${label} must be a positive integer`);
|
|
2472
|
-
}
|
|
2473
|
-
}
|
|
2474
|
-
|
|
2475
|
-
function requireNonNegativeInteger(label, value) {
|
|
2476
|
-
if (!Number.isSafeInteger(value) || value < 0) {
|
|
2477
|
-
throw new SdkError("validation_error", `${label} must be a non-negative integer`);
|
|
2478
|
-
}
|
|
2479
|
-
}
|
|
2480
|
-
|
|
2481
|
-
function retryAfterMsOrUndefined(value) {
|
|
2482
|
-
if (value === undefined) {
|
|
2483
|
-
return undefined;
|
|
2484
|
-
}
|
|
2485
|
-
return Number.isSafeInteger(value) && value >= 0 ? value : undefined;
|
|
2486
|
-
}
|
|
2487
|
-
|
|
2488
|
-
function requireTraceId(traceId) {
|
|
2489
|
-
if (typeof traceId !== "string" || !/^[0-9a-fA-F]{32}$/u.test(traceId)) {
|
|
2490
|
-
throw new SdkError("validation_error", "traceId must be 32 lowercase or uppercase hex characters");
|
|
2491
|
-
}
|
|
2492
|
-
if (traceId.toLowerCase() === ZERO_TRACE_ID) {
|
|
2493
|
-
throw new SdkError("validation_error", "traceId must not be all zeros");
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
|
|
2497
|
-
function requireSpanId(label, spanId) {
|
|
2498
|
-
if (typeof spanId !== "string" || !/^[0-9a-fA-F]{16}$/u.test(spanId)) {
|
|
2499
|
-
throw new SdkError("validation_error", `${label} must be 16 lowercase or uppercase hex characters`);
|
|
2500
|
-
}
|
|
2501
|
-
if (spanId.toLowerCase() === ZERO_SPAN_ID) {
|
|
2502
|
-
throw new SdkError("validation_error", `${label} must not be all zeros`);
|
|
2503
|
-
}
|
|
2504
|
-
}
|
|
2505
|
-
|
|
2506
|
-
function requireTraceFlags(traceFlags) {
|
|
2507
|
-
if (typeof traceFlags !== "string" || !/^[0-9a-fA-F]{2}$/u.test(traceFlags)) {
|
|
2508
|
-
throw new SdkError("validation_error", "traceFlags must be 2 lowercase or uppercase hex characters");
|
|
2509
|
-
}
|
|
2510
|
-
}
|
|
2511
|
-
|
|
2512
|
-
function requireTimestamp(timestamp) {
|
|
2513
|
-
requireNonEmpty("timestamp", timestamp);
|
|
2514
|
-
const timePortion = timestamp.split("T")[1];
|
|
2515
|
-
if (!timestamp.endsWith("Z")
|
|
2516
|
-
&& !(timePortion && (timePortion.includes("+") || /.+-.+/.test(timePortion)))) {
|
|
2517
|
-
throw new SdkError(
|
|
2518
|
-
"validation_error",
|
|
2519
|
-
`timestamp must include a timezone offset: ${timestamp}`
|
|
2520
|
-
);
|
|
2521
|
-
}
|
|
2522
|
-
const match = timestamp.match(RFC3339_TIMESTAMP_PATTERN);
|
|
2523
|
-
const calendar = new Date(0);
|
|
2524
|
-
calendar.setUTCHours(0, 0, 0, 0);
|
|
2525
|
-
calendar.setUTCFullYear(Number(match?.[1]), Number(match?.[2]) - 1, Number(match?.[3]));
|
|
2526
|
-
if (!match
|
|
2527
|
-
|| Number(match[1]) === 0
|
|
2528
|
-
|| calendar.getUTCFullYear() !== Number(match[1])
|
|
2529
|
-
|| calendar.getUTCMonth() !== Number(match[2]) - 1
|
|
2530
|
-
|| calendar.getUTCDate() !== Number(match[3])) {
|
|
2531
|
-
throw new SdkError("validation_error", `invalid timestamp: ${timestamp}`);
|
|
2532
|
-
}
|
|
2533
|
-
}
|
|
2534
|
-
|
|
2535
2366
|
function cloneMetadata(metadata) {
|
|
2536
2367
|
if (metadata === undefined) {
|
|
2537
2368
|
return undefined;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { SdkError } = require("./sdk-error.cjs");
|
|
4
|
+
const { boundedText, optionalText, requireMachineName } = require("./issue-text.cjs");
|
|
5
|
+
const { optionalEntry, rejectUnknownKeys, requireObject, requireTimestamp } = require("./validation.cjs");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @import { IssueBreadcrumb, IssueBreadcrumbDataValue, IssueBreadcrumbLevel } from "./index.cjs"
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const MAX_ISSUE_BREADCRUMBS = 64;
|
|
12
|
+
const MIN_ISSUE_BREADCRUMBS = 1;
|
|
13
|
+
const MAX_BREADCRUMB_MESSAGE_LENGTH = 512;
|
|
14
|
+
const MAX_BREADCRUMB_DATA_FIELDS = 8;
|
|
15
|
+
const MAX_BREADCRUMB_DATA_STRING_LENGTH = 256;
|
|
16
|
+
const DATA_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/u;
|
|
17
|
+
const BREADCRUMB_KEYS = new Set(["timestamp", "type", "category", "level", "message", "data"]);
|
|
18
|
+
/** @type {ReadonlyMap<string, IssueBreadcrumbLevel>} */
|
|
19
|
+
const BREADCRUMB_LEVEL_ALIASES = new Map([
|
|
20
|
+
["trace", "debug"],
|
|
21
|
+
["debug", "debug"],
|
|
22
|
+
["info", "info"],
|
|
23
|
+
["log", "info"],
|
|
24
|
+
["warn", "warning"],
|
|
25
|
+
["warning", "warning"],
|
|
26
|
+
["error", "error"],
|
|
27
|
+
["fatal", "critical"],
|
|
28
|
+
["critical", "critical"]
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {unknown} value
|
|
33
|
+
* @returns {IssueBreadcrumbLevel | undefined}
|
|
34
|
+
*/
|
|
35
|
+
function optionalBreadcrumbLevel(value) {
|
|
36
|
+
if (value === undefined) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const normalized = typeof value === "string" ? BREADCRUMB_LEVEL_ALIASES.get(value) : undefined;
|
|
40
|
+
if (normalized === undefined) {
|
|
41
|
+
throw new SdkError(
|
|
42
|
+
"validation_error",
|
|
43
|
+
`issue breadcrumb level must be one of: ${[...BREADCRUMB_LEVEL_ALIASES.keys()].join(", ")}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return normalized;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {string} key
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
function requireDataKey(key) {
|
|
54
|
+
if (!DATA_KEY_PATTERN.test(key)) {
|
|
55
|
+
throw new SdkError("validation_error", `issue breadcrumb data key must match ${DATA_KEY_PATTERN}`);
|
|
56
|
+
}
|
|
57
|
+
return key;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @param {string} key
|
|
62
|
+
* @param {unknown} value
|
|
63
|
+
* @returns {IssueBreadcrumbDataValue}
|
|
64
|
+
*/
|
|
65
|
+
function breadcrumbDataValue(key, value) {
|
|
66
|
+
if (typeof value === "string") {
|
|
67
|
+
return boundedText(`issue breadcrumb data value for ${key}`, value, MAX_BREADCRUMB_DATA_STRING_LENGTH);
|
|
68
|
+
}
|
|
69
|
+
if (typeof value === "number") {
|
|
70
|
+
if (!Number.isFinite(value)) {
|
|
71
|
+
throw new SdkError("validation_error", `issue breadcrumb data value for ${key} must be finite`);
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
if (typeof value === "boolean" || value === null) {
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
throw new SdkError(
|
|
79
|
+
"validation_error",
|
|
80
|
+
`issue breadcrumb data value for ${key} must be a string, number, boolean, or null`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @param {unknown} data
|
|
86
|
+
* @returns {Record<string, IssueBreadcrumbDataValue> | undefined}
|
|
87
|
+
*/
|
|
88
|
+
function validateBreadcrumbData(data) {
|
|
89
|
+
if (data === undefined) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
/** @type {readonly (readonly [string, unknown])[]} */
|
|
93
|
+
const entries = Object.entries(requireObject("issue breadcrumb data", data));
|
|
94
|
+
if (entries.length > MAX_BREADCRUMB_DATA_FIELDS) {
|
|
95
|
+
throw new SdkError(
|
|
96
|
+
"validation_error",
|
|
97
|
+
`issue breadcrumb data must contain at most ${MAX_BREADCRUMB_DATA_FIELDS} fields`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return Object.fromEntries(entries.map(([key, value]) => [requireDataKey(key), breadcrumbDataValue(key, value)]));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {unknown} breadcrumb
|
|
105
|
+
* @param {unknown} [defaultTimestamp]
|
|
106
|
+
* @returns {IssueBreadcrumb}
|
|
107
|
+
*/
|
|
108
|
+
function validateIssueBreadcrumb(breadcrumb, defaultTimestamp) {
|
|
109
|
+
const source = requireObject("issue breadcrumb", breadcrumb);
|
|
110
|
+
rejectUnknownKeys("issue breadcrumb", source, BREADCRUMB_KEYS);
|
|
111
|
+
const timestamp = requireTimestamp(source["timestamp"] ?? defaultTimestamp);
|
|
112
|
+
const category = requireMachineName("issue breadcrumb category", source["category"]);
|
|
113
|
+
const type = source["type"] === undefined ? undefined : requireMachineName("issue breadcrumb type", source["type"]);
|
|
114
|
+
const level = optionalBreadcrumbLevel(source["level"]);
|
|
115
|
+
const message = optionalText("issue breadcrumb message", source["message"], MAX_BREADCRUMB_MESSAGE_LENGTH);
|
|
116
|
+
const data = validateBreadcrumbData(source["data"]);
|
|
117
|
+
return {
|
|
118
|
+
timestamp,
|
|
119
|
+
...optionalEntry("type", type),
|
|
120
|
+
category,
|
|
121
|
+
...optionalEntry("level", level),
|
|
122
|
+
...optionalEntry("message", message),
|
|
123
|
+
...optionalEntry("data", data)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @param {unknown} breadcrumbs
|
|
129
|
+
* @returns {IssueBreadcrumb[] | undefined}
|
|
130
|
+
*/
|
|
131
|
+
function validateIssueBreadcrumbs(breadcrumbs) {
|
|
132
|
+
if (breadcrumbs === undefined) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
if (!Array.isArray(breadcrumbs) || breadcrumbs.length < MIN_ISSUE_BREADCRUMBS || breadcrumbs.length > MAX_ISSUE_BREADCRUMBS) {
|
|
136
|
+
throw new SdkError("validation_error", `issue breadcrumbs must contain 1-${MAX_ISSUE_BREADCRUMBS} entries`);
|
|
137
|
+
}
|
|
138
|
+
/** @type {readonly unknown[]} */
|
|
139
|
+
const list = breadcrumbs;
|
|
140
|
+
return list.map((breadcrumb) => validateIssueBreadcrumb(breadcrumb));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = { MAX_ISSUE_BREADCRUMBS, validateIssueBreadcrumb, validateIssueBreadcrumbs };
|