@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
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const MAX_ISSUE_BREADCRUMBS = 64;
|
|
4
|
+
const MAX_EXCEPTION_TYPE_LENGTH = 256;
|
|
5
|
+
const MAX_MECHANISM_TYPE_LENGTH = 64;
|
|
6
|
+
const MAX_BREADCRUMB_NAME_LENGTH = 64;
|
|
7
|
+
const MAX_BREADCRUMB_MESSAGE_LENGTH = 512;
|
|
8
|
+
const MAX_BREADCRUMB_DATA_FIELDS = 8;
|
|
9
|
+
const MAX_BREADCRUMB_DATA_STRING_LENGTH = 256;
|
|
10
|
+
const MACHINE_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_.:-]{0,63}$/u;
|
|
11
|
+
const DATA_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_.-]{0,63}$/u;
|
|
12
|
+
const BREADCRUMB_LEVEL_ALIASES = new Map([
|
|
13
|
+
["trace", "debug"],
|
|
14
|
+
["debug", "debug"],
|
|
15
|
+
["info", "info"],
|
|
16
|
+
["log", "info"],
|
|
17
|
+
["warn", "warning"],
|
|
18
|
+
["warning", "warning"],
|
|
19
|
+
["error", "error"],
|
|
20
|
+
["fatal", "critical"],
|
|
21
|
+
["critical", "critical"]
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
function buildIssueDiagnosticsHelpers({ SdkError, requireTimestamp }) {
|
|
25
|
+
function validationError(message) {
|
|
26
|
+
return new SdkError("validation_error", message);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function validateIssueException(exception) {
|
|
30
|
+
if (exception === undefined) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
requireObject("issue exception", exception);
|
|
34
|
+
rejectUnknownKeys("issue exception", exception, new Set(["type", "mechanism"]));
|
|
35
|
+
const type = boundedText("issue exception type", exception.type, MAX_EXCEPTION_TYPE_LENGTH, {
|
|
36
|
+
rejectLocationText: true
|
|
37
|
+
});
|
|
38
|
+
const mechanism = validateIssueExceptionMechanism(exception.mechanism);
|
|
39
|
+
return {
|
|
40
|
+
type,
|
|
41
|
+
...(mechanism === undefined ? {} : { mechanism })
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function validateIssueExceptionMechanism(mechanism) {
|
|
46
|
+
if (mechanism === undefined) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
requireObject("issue exception mechanism", mechanism);
|
|
50
|
+
rejectUnknownKeys(
|
|
51
|
+
"issue exception mechanism",
|
|
52
|
+
mechanism,
|
|
53
|
+
new Set(["type", "handled"])
|
|
54
|
+
);
|
|
55
|
+
if (typeof mechanism.type !== "string" || !MACHINE_NAME_PATTERN.test(mechanism.type)) {
|
|
56
|
+
throw validationError(
|
|
57
|
+
`issue exception mechanism type must match ${MACHINE_NAME_PATTERN}`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (Array.from(mechanism.type).length > MAX_MECHANISM_TYPE_LENGTH) {
|
|
61
|
+
throw validationError(
|
|
62
|
+
`issue exception mechanism type must be at most ${MAX_MECHANISM_TYPE_LENGTH} characters`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
if (typeof mechanism.handled !== "boolean") {
|
|
66
|
+
throw validationError("issue exception mechanism handled must be a boolean");
|
|
67
|
+
}
|
|
68
|
+
return { type: mechanism.type, handled: mechanism.handled };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function createIssueException(type, mechanism, handled) {
|
|
72
|
+
return validateIssueException({
|
|
73
|
+
type,
|
|
74
|
+
mechanism: { type: mechanism, handled }
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function validateIssueBreadcrumb(breadcrumb, defaultTimestamp) {
|
|
79
|
+
requireObject("issue breadcrumb", breadcrumb);
|
|
80
|
+
rejectUnknownKeys(
|
|
81
|
+
"issue breadcrumb",
|
|
82
|
+
breadcrumb,
|
|
83
|
+
new Set(["timestamp", "type", "category", "level", "message", "data"])
|
|
84
|
+
);
|
|
85
|
+
const timestamp = breadcrumb.timestamp ?? defaultTimestamp;
|
|
86
|
+
requireTimestamp(timestamp);
|
|
87
|
+
if (typeof breadcrumb.category !== "string" || !MACHINE_NAME_PATTERN.test(breadcrumb.category)) {
|
|
88
|
+
throw validationError(`issue breadcrumb category must match ${MACHINE_NAME_PATTERN}`);
|
|
89
|
+
}
|
|
90
|
+
const type = optionalMachineName("issue breadcrumb type", breadcrumb.type);
|
|
91
|
+
const level = optionalBreadcrumbLevel(breadcrumb.level);
|
|
92
|
+
const message = breadcrumb.message === undefined
|
|
93
|
+
? undefined
|
|
94
|
+
: boundedText(
|
|
95
|
+
"issue breadcrumb message",
|
|
96
|
+
breadcrumb.message,
|
|
97
|
+
MAX_BREADCRUMB_MESSAGE_LENGTH
|
|
98
|
+
);
|
|
99
|
+
const data = validateBreadcrumbData(breadcrumb.data);
|
|
100
|
+
return {
|
|
101
|
+
timestamp,
|
|
102
|
+
...(type === undefined ? {} : { type }),
|
|
103
|
+
category: breadcrumb.category,
|
|
104
|
+
...(level === undefined ? {} : { level }),
|
|
105
|
+
...(message === undefined ? {} : { message }),
|
|
106
|
+
...(data === undefined ? {} : { data })
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function validateIssueBreadcrumbs(breadcrumbs) {
|
|
111
|
+
if (breadcrumbs === undefined) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
if (!Array.isArray(breadcrumbs) || breadcrumbs.length < 1 || breadcrumbs.length > MAX_ISSUE_BREADCRUMBS) {
|
|
115
|
+
throw validationError(
|
|
116
|
+
`issue breadcrumbs must contain 1-${MAX_ISSUE_BREADCRUMBS} entries`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return breadcrumbs.map((breadcrumb) => validateIssueBreadcrumb(breadcrumb));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function validateIssueDiagnostics(attributes) {
|
|
123
|
+
const exception = validateIssueException(attributes.exception);
|
|
124
|
+
const breadcrumbs = validateIssueBreadcrumbs(attributes.breadcrumbs);
|
|
125
|
+
if (
|
|
126
|
+
attributes.breadcrumbsTruncated !== undefined
|
|
127
|
+
&& typeof attributes.breadcrumbsTruncated !== "boolean"
|
|
128
|
+
) {
|
|
129
|
+
throw validationError("issue breadcrumbsTruncated must be a boolean");
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
...(exception === undefined ? {} : { exception }),
|
|
133
|
+
...(breadcrumbs === undefined ? {} : { breadcrumbs }),
|
|
134
|
+
...(attributes.breadcrumbsTruncated === true ? { breadcrumbsTruncated: true } : {})
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function cloneIssueDiagnostics(attributes) {
|
|
139
|
+
const diagnostics = {};
|
|
140
|
+
if (attributes.exception !== undefined) {
|
|
141
|
+
diagnostics.exception = {
|
|
142
|
+
...attributes.exception,
|
|
143
|
+
...(attributes.exception.mechanism === undefined
|
|
144
|
+
? {}
|
|
145
|
+
: { mechanism: { ...attributes.exception.mechanism } })
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (Array.isArray(attributes.breadcrumbs)) {
|
|
149
|
+
diagnostics.breadcrumbs = attributes.breadcrumbs.map((breadcrumb) => ({
|
|
150
|
+
...breadcrumb,
|
|
151
|
+
...(breadcrumb.data === undefined ? {} : { data: { ...breadcrumb.data } })
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
if (attributes.breadcrumbsTruncated === true) {
|
|
155
|
+
diagnostics.breadcrumbsTruncated = true;
|
|
156
|
+
}
|
|
157
|
+
return diagnostics;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function validateBreadcrumbData(data) {
|
|
161
|
+
if (data === undefined) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
requireObject("issue breadcrumb data", data);
|
|
165
|
+
const entries = Object.entries(data);
|
|
166
|
+
if (entries.length > MAX_BREADCRUMB_DATA_FIELDS) {
|
|
167
|
+
throw validationError(
|
|
168
|
+
`issue breadcrumb data must contain at most ${MAX_BREADCRUMB_DATA_FIELDS} fields`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
const validated = {};
|
|
172
|
+
for (const [key, value] of entries) {
|
|
173
|
+
if (!DATA_KEY_PATTERN.test(key)) {
|
|
174
|
+
throw validationError(`issue breadcrumb data key must match ${DATA_KEY_PATTERN}`);
|
|
175
|
+
}
|
|
176
|
+
if (typeof value === "string") {
|
|
177
|
+
validated[key] = boundedText(
|
|
178
|
+
`issue breadcrumb data value for ${key}`,
|
|
179
|
+
value,
|
|
180
|
+
MAX_BREADCRUMB_DATA_STRING_LENGTH
|
|
181
|
+
);
|
|
182
|
+
} else if (typeof value === "number") {
|
|
183
|
+
if (!Number.isFinite(value)) {
|
|
184
|
+
throw validationError(`issue breadcrumb data value for ${key} must be finite`);
|
|
185
|
+
}
|
|
186
|
+
validated[key] = value;
|
|
187
|
+
} else if (typeof value === "boolean" || value === null) {
|
|
188
|
+
validated[key] = value;
|
|
189
|
+
} else {
|
|
190
|
+
throw validationError(
|
|
191
|
+
`issue breadcrumb data value for ${key} must be a string, number, boolean, or null`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return validated;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function optionalMachineName(label, value) {
|
|
199
|
+
if (value === undefined) {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
if (typeof value !== "string" || !MACHINE_NAME_PATTERN.test(value)) {
|
|
203
|
+
throw validationError(`${label} must match ${MACHINE_NAME_PATTERN}`);
|
|
204
|
+
}
|
|
205
|
+
if (Array.from(value).length > MAX_BREADCRUMB_NAME_LENGTH) {
|
|
206
|
+
throw validationError(`${label} must be at most ${MAX_BREADCRUMB_NAME_LENGTH} characters`);
|
|
207
|
+
}
|
|
208
|
+
return value;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function optionalBreadcrumbLevel(value) {
|
|
212
|
+
if (value === undefined) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
const normalized = typeof value === "string" ? BREADCRUMB_LEVEL_ALIASES.get(value) : undefined;
|
|
216
|
+
if (normalized === undefined) {
|
|
217
|
+
throw validationError(
|
|
218
|
+
`issue breadcrumb level must be one of: ${Array.from(BREADCRUMB_LEVEL_ALIASES.keys()).join(", ")}`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
return normalized;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function boundedText(label, value, maxLength, { rejectLocationText = false } = {}) {
|
|
225
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
226
|
+
throw validationError(`${label} must be non-empty`);
|
|
227
|
+
}
|
|
228
|
+
if (
|
|
229
|
+
Array.from(value).length > maxLength
|
|
230
|
+
|| hasControlCharacter(value)
|
|
231
|
+
|| (rejectLocationText && /[?#]/u.test(value))
|
|
232
|
+
) {
|
|
233
|
+
throw validationError(`${label} is invalid or exceeds ${maxLength} characters`);
|
|
234
|
+
}
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function requireObject(label, value) {
|
|
239
|
+
if (!value || Array.isArray(value) || typeof value !== "object") {
|
|
240
|
+
throw validationError(`${label} must be an object`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function rejectUnknownKeys(label, value, allowed) {
|
|
245
|
+
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
246
|
+
if (unknown.length > 0) {
|
|
247
|
+
throw validationError(`${label} has unsupported fields: ${unknown.sort().join(", ")}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function hasControlCharacter(value) {
|
|
252
|
+
return Array.from(value).some((character) => {
|
|
253
|
+
const code = character.codePointAt(0);
|
|
254
|
+
return code !== undefined && (code <= 31 || (code >= 127 && code <= 159));
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
MAX_ISSUE_BREADCRUMBS,
|
|
260
|
+
cloneIssueDiagnostics,
|
|
261
|
+
createIssueException,
|
|
262
|
+
validateIssueBreadcrumb,
|
|
263
|
+
validateIssueDiagnostics
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = { buildIssueDiagnosticsHelpers };
|
package/issue-stack.cjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const MAX_ISSUE_STACK_FRAMES = 32;
|
|
4
|
+
const MAX_ISSUE_STACK_FUNCTION_LENGTH = 256;
|
|
5
|
+
const MAX_ISSUE_STACK_MODULE_LENGTH = 512;
|
|
4
6
|
const SAFE_DEBUG_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
5
7
|
const LOCAL_ABSOLUTE_PATH_PATTERN = /^(?:\/(?:Users|home|private|tmp|var|Volumes)\/|[A-Za-z]:[\\/])/u;
|
|
6
8
|
|
|
@@ -54,7 +56,31 @@ function buildIssueStackHelpers({ SdkError }) {
|
|
|
54
56
|
if (debugId === null) {
|
|
55
57
|
throw new SdkError("validation_error", "issue stack frame debugId is invalid");
|
|
56
58
|
}
|
|
57
|
-
|
|
59
|
+
const functionName = optionalFrameIdentity(frame.function, MAX_ISSUE_STACK_FUNCTION_LENGTH);
|
|
60
|
+
if (functionName === null) {
|
|
61
|
+
throw new SdkError("validation_error", "issue stack frame function is invalid");
|
|
62
|
+
}
|
|
63
|
+
const moduleName = optionalFrameIdentity(frame.module, MAX_ISSUE_STACK_MODULE_LENGTH, true);
|
|
64
|
+
if (moduleName === null) {
|
|
65
|
+
throw new SdkError("validation_error", "issue stack frame module is invalid");
|
|
66
|
+
}
|
|
67
|
+
const inApp = frame.inApp === undefined
|
|
68
|
+
? undefined
|
|
69
|
+
: typeof frame.inApp === "boolean"
|
|
70
|
+
? frame.inApp
|
|
71
|
+
: null;
|
|
72
|
+
if (inApp === null) {
|
|
73
|
+
throw new SdkError("validation_error", "issue stack frame inApp is invalid");
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
filename,
|
|
77
|
+
line,
|
|
78
|
+
column,
|
|
79
|
+
...(functionName ? { function: functionName } : {}),
|
|
80
|
+
...(moduleName ? { module: moduleName } : {}),
|
|
81
|
+
...(inApp !== undefined ? { inApp } : {}),
|
|
82
|
+
...(debugId ? { debugId } : {})
|
|
83
|
+
};
|
|
58
84
|
});
|
|
59
85
|
}
|
|
60
86
|
|
|
@@ -67,13 +93,18 @@ function parseJavaScriptStackFrame(rawLine) {
|
|
|
67
93
|
return null;
|
|
68
94
|
}
|
|
69
95
|
let location = line;
|
|
96
|
+
let functionName;
|
|
70
97
|
if (location.startsWith("at ")) {
|
|
71
98
|
location = location.slice(3).trim();
|
|
72
99
|
if (location.endsWith(")") && location.includes("(")) {
|
|
73
|
-
|
|
100
|
+
const marker = location.lastIndexOf("(");
|
|
101
|
+
functionName = generatedFrameFunction(location.slice(0, marker));
|
|
102
|
+
location = location.slice(marker + 1, -1);
|
|
74
103
|
}
|
|
75
104
|
} else if (location.includes("@")) {
|
|
76
|
-
|
|
105
|
+
const marker = location.lastIndexOf("@");
|
|
106
|
+
functionName = generatedFrameFunction(location.slice(0, marker));
|
|
107
|
+
location = location.slice(marker + 1);
|
|
77
108
|
}
|
|
78
109
|
const parts = location.split(":");
|
|
79
110
|
if (parts.length < 3) {
|
|
@@ -85,7 +116,42 @@ function parseJavaScriptStackFrame(rawLine) {
|
|
|
85
116
|
if (!filename || lineNumber === null || column === null) {
|
|
86
117
|
return null;
|
|
87
118
|
}
|
|
88
|
-
return {
|
|
119
|
+
return {
|
|
120
|
+
filename,
|
|
121
|
+
line: lineNumber,
|
|
122
|
+
column,
|
|
123
|
+
...(functionName ? { function: functionName } : {})
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function optionalFrameIdentity(value, maxLength, rejectLocationText = false) {
|
|
128
|
+
if (value === undefined) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
if (typeof value !== "string") {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
const identity = value.trim();
|
|
135
|
+
if (!identity
|
|
136
|
+
|| Array.from(identity).length > maxLength
|
|
137
|
+
|| hasControlCharacter(identity)
|
|
138
|
+
|| (rejectLocationText && (identity.includes("?") || identity.includes("#")))) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
return identity;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function generatedFrameFunction(value) {
|
|
145
|
+
const functionName = optionalFrameIdentity(value, MAX_ISSUE_STACK_FUNCTION_LENGTH);
|
|
146
|
+
if (!functionName
|
|
147
|
+
|| functionName.includes("@")
|
|
148
|
+
|| functionName.includes("/")
|
|
149
|
+
|| functionName.includes("\\")
|
|
150
|
+
|| functionName.includes("?")
|
|
151
|
+
|| functionName.includes("#")) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
return functionName;
|
|
89
155
|
}
|
|
90
156
|
|
|
91
157
|
function positiveIntegerFromText(value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logbrew/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Public LogBrew JavaScript SDK for building, validating, and flushing event batches.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.cjs",
|
|
@@ -43,12 +43,14 @@
|
|
|
43
43
|
"index.js",
|
|
44
44
|
"index.d.ts",
|
|
45
45
|
"index.d.cts",
|
|
46
|
+
"issue-diagnostics.cjs",
|
|
46
47
|
"issue-stack.cjs",
|
|
47
48
|
"log-context.cjs",
|
|
48
49
|
"opentelemetry.cjs",
|
|
49
50
|
"react-native.js",
|
|
50
51
|
"react-native.d.ts",
|
|
51
52
|
"support-ticket.cjs",
|
|
53
|
+
"telemetry-context.cjs",
|
|
52
54
|
"trace-context.cjs",
|
|
53
55
|
"winston.cjs",
|
|
54
56
|
"release-artifacts-common.js",
|
package/react-native.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export type {
|
|
|
40
40
|
PinoDestinationConfig,
|
|
41
41
|
PinoDestinationHandle,
|
|
42
42
|
PinoLogRecord,
|
|
43
|
+
ProductAnalyticsKind,
|
|
43
44
|
ProductActionInput,
|
|
44
45
|
ReleaseAttributes,
|
|
45
46
|
Severity,
|
|
@@ -65,6 +66,8 @@ export type {
|
|
|
65
66
|
} from "./index.js";
|
|
66
67
|
|
|
67
68
|
export {
|
|
69
|
+
PRODUCT_ANALYTICS_KINDS,
|
|
70
|
+
PRODUCT_ANALYTICS_SCHEMA_VERSION,
|
|
68
71
|
createBaggage,
|
|
69
72
|
createIssueAttributesFromError,
|
|
70
73
|
createLogBrewOpenTelemetrySpanExporter,
|