@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/validation.cjs
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
const { SdkError } = require("./sdk-error.cjs");
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @template Value
|
|
5
|
+
* @typedef {Value extends readonly (infer Item)[]
|
|
6
|
+
* ? readonly DeepReadonly<Item>[]
|
|
7
|
+
* : Value extends object ? { readonly [Key in keyof Value]: DeepReadonly<Value[Key]> } : Value} DeepReadonly
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const ZERO_TRACE_ID = "00000000000000000000000000000000";
|
|
11
|
+
const ZERO_SPAN_ID = "0000000000000000";
|
|
12
|
+
const TRACE_ID_PATTERN = /^[0-9a-fA-F]{32}$/u;
|
|
13
|
+
const SPAN_ID_PATTERN = /^[0-9a-fA-F]{16}$/u;
|
|
14
|
+
const TRACE_FLAGS_PATTERN = /^[0-9a-fA-F]{2}$/u;
|
|
15
|
+
const NEGATIVE_OFFSET_PATTERN = /.+-.+/u;
|
|
16
|
+
const RFC3339_TIMESTAMP_PATTERN =
|
|
17
|
+
/^(?<year>\d{4})-(?<month>\d{2})-(?<day>\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;
|
|
18
|
+
const EPOCH_MS = 0;
|
|
19
|
+
const YEAR_ZERO = 0;
|
|
20
|
+
const FIRST_MONTH = 1;
|
|
21
|
+
const MIN_POSITIVE_INTEGER = 1;
|
|
22
|
+
const MIN_NON_NEGATIVE_INTEGER = 0;
|
|
23
|
+
const NO_UNKNOWN_KEYS = 0;
|
|
24
|
+
const FIRST_CODE_POINT = 0;
|
|
25
|
+
const LAST_C0_CONTROL = 31;
|
|
26
|
+
const FIRST_C1_CONTROL = 127;
|
|
27
|
+
const LAST_C1_CONTROL = 159;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {unknown} value
|
|
31
|
+
* @returns {value is Readonly<Record<string, unknown>>}
|
|
32
|
+
*/
|
|
33
|
+
function isRecord(value) {
|
|
34
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {unknown} value
|
|
39
|
+
* @returns {value is readonly unknown[]}
|
|
40
|
+
*/
|
|
41
|
+
function isList(value) {
|
|
42
|
+
return Array.isArray(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {string} label
|
|
47
|
+
* @param {unknown} value
|
|
48
|
+
* @returns {Readonly<Record<string, unknown>>}
|
|
49
|
+
*/
|
|
50
|
+
function requireObject(label, value) {
|
|
51
|
+
if (!isRecord(value)) {
|
|
52
|
+
throw new SdkError("validation_error", `${label} must be an object`);
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {string} label
|
|
59
|
+
* @param {Readonly<Record<string, unknown>>} value
|
|
60
|
+
* @param {Readonly<ReadonlySet<string>>} allowed
|
|
61
|
+
* @returns {void}
|
|
62
|
+
*/
|
|
63
|
+
function rejectUnknownKeys(label, value, allowed) {
|
|
64
|
+
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
65
|
+
if (unknown.length > NO_UNKNOWN_KEYS) {
|
|
66
|
+
unknown.sort();
|
|
67
|
+
throw new SdkError("validation_error", `${label} has unsupported fields: ${unknown.join(", ")}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {string} value
|
|
73
|
+
* @returns {boolean}
|
|
74
|
+
*/
|
|
75
|
+
function hasUnicodeControlCharacter(value) {
|
|
76
|
+
return [...value].some((character) => {
|
|
77
|
+
const code = character.codePointAt(FIRST_CODE_POINT);
|
|
78
|
+
return code !== undefined && (code <= LAST_C0_CONTROL || (code >= FIRST_C1_CONTROL && code <= LAST_C1_CONTROL));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @template Value
|
|
84
|
+
* @template {string} Key
|
|
85
|
+
* @param {Key} key
|
|
86
|
+
* @param {Value | undefined} value
|
|
87
|
+
* @returns {{ [Field in Key]?: Value }}
|
|
88
|
+
*/
|
|
89
|
+
function optionalEntry(key, value) {
|
|
90
|
+
/** @type {{ [Field in Key]?: Value }} */
|
|
91
|
+
const entry = {};
|
|
92
|
+
if (value !== undefined) {
|
|
93
|
+
entry[key] = value;
|
|
94
|
+
}
|
|
95
|
+
return entry;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @param {string} label
|
|
100
|
+
* @param {unknown} value
|
|
101
|
+
* @returns {string}
|
|
102
|
+
*/
|
|
103
|
+
function requireNonEmpty(label, value) {
|
|
104
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
105
|
+
throw new SdkError("validation_error", `${label} must be non-empty`);
|
|
106
|
+
}
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @template {string} Allowed
|
|
112
|
+
* @param {string} value
|
|
113
|
+
* @param {Readonly<ReadonlySet<Allowed>>} allowedValues
|
|
114
|
+
* @returns {value is Allowed}
|
|
115
|
+
*/
|
|
116
|
+
function isAllowedValue(value, allowedValues) {
|
|
117
|
+
/** @type {ReadonlySet<string>} */
|
|
118
|
+
const values = allowedValues;
|
|
119
|
+
return values.has(value);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* @template {string} Allowed
|
|
124
|
+
* @param {string} label
|
|
125
|
+
* @param {unknown} value
|
|
126
|
+
* @param {Readonly<ReadonlySet<Allowed>>} allowedValues
|
|
127
|
+
* @returns {Allowed}
|
|
128
|
+
*/
|
|
129
|
+
function requireAllowedValue(label, value, allowedValues) {
|
|
130
|
+
const text = requireNonEmpty(label, value);
|
|
131
|
+
if (!isAllowedValue(text, allowedValues)) {
|
|
132
|
+
throw new SdkError("validation_error", `${label} must be one of: ${[...allowedValues].join(", ")}`);
|
|
133
|
+
}
|
|
134
|
+
return text;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {string} label
|
|
139
|
+
* @param {unknown} value
|
|
140
|
+
* @returns {number}
|
|
141
|
+
*/
|
|
142
|
+
function requireFiniteNumber(label, value) {
|
|
143
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
144
|
+
throw new SdkError("validation_error", `${label} must be a finite number`);
|
|
145
|
+
}
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @param {string} label
|
|
151
|
+
* @param {unknown} value
|
|
152
|
+
* @returns {number}
|
|
153
|
+
*/
|
|
154
|
+
function requirePositiveInteger(label, value) {
|
|
155
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < MIN_POSITIVE_INTEGER) {
|
|
156
|
+
throw new SdkError("validation_error", `${label} must be a positive integer`);
|
|
157
|
+
}
|
|
158
|
+
return value;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @param {string} label
|
|
163
|
+
* @param {unknown} value
|
|
164
|
+
* @returns {number}
|
|
165
|
+
*/
|
|
166
|
+
function requireNonNegativeInteger(label, value) {
|
|
167
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < MIN_NON_NEGATIVE_INTEGER) {
|
|
168
|
+
throw new SdkError("validation_error", `${label} must be a non-negative integer`);
|
|
169
|
+
}
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @param {unknown} traceId
|
|
175
|
+
* @returns {string}
|
|
176
|
+
*/
|
|
177
|
+
function requireTraceId(traceId) {
|
|
178
|
+
if (typeof traceId !== "string" || !TRACE_ID_PATTERN.test(traceId)) {
|
|
179
|
+
throw new SdkError("validation_error", "traceId must be 32 lowercase or uppercase hex characters");
|
|
180
|
+
}
|
|
181
|
+
if (traceId.toLowerCase() === ZERO_TRACE_ID) {
|
|
182
|
+
throw new SdkError("validation_error", "traceId must not be all zeros");
|
|
183
|
+
}
|
|
184
|
+
return traceId;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @param {string} label
|
|
189
|
+
* @param {unknown} spanId
|
|
190
|
+
* @returns {string}
|
|
191
|
+
*/
|
|
192
|
+
function requireSpanId(label, spanId) {
|
|
193
|
+
if (typeof spanId !== "string" || !SPAN_ID_PATTERN.test(spanId)) {
|
|
194
|
+
throw new SdkError("validation_error", `${label} must be 16 lowercase or uppercase hex characters`);
|
|
195
|
+
}
|
|
196
|
+
if (spanId.toLowerCase() === ZERO_SPAN_ID) {
|
|
197
|
+
throw new SdkError("validation_error", `${label} must not be all zeros`);
|
|
198
|
+
}
|
|
199
|
+
return spanId;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @param {unknown} traceFlags
|
|
204
|
+
* @returns {string}
|
|
205
|
+
*/
|
|
206
|
+
function requireTraceFlags(traceFlags) {
|
|
207
|
+
if (typeof traceFlags !== "string" || !TRACE_FLAGS_PATTERN.test(traceFlags)) {
|
|
208
|
+
throw new SdkError("validation_error", "traceFlags must be 2 lowercase or uppercase hex characters");
|
|
209
|
+
}
|
|
210
|
+
return traceFlags;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @param {string} timestamp
|
|
215
|
+
* @returns {boolean}
|
|
216
|
+
*/
|
|
217
|
+
function hasTimezone(timestamp) {
|
|
218
|
+
if (timestamp.endsWith("Z")) {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
const [, timePortion] = timestamp.split("T");
|
|
222
|
+
return timePortion !== undefined && (timePortion.includes("+") || NEGATIVE_OFFSET_PATTERN.test(timePortion));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* @param {string} timestamp
|
|
227
|
+
* @returns {boolean}
|
|
228
|
+
*/
|
|
229
|
+
function isCalendarDate(timestamp) {
|
|
230
|
+
const parts = RFC3339_TIMESTAMP_PATTERN.exec(timestamp)?.groups;
|
|
231
|
+
if (parts === undefined) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
const year = Number(parts["year"]);
|
|
235
|
+
const monthIndex = Number(parts["month"]) - FIRST_MONTH;
|
|
236
|
+
const day = Number(parts["day"]);
|
|
237
|
+
const calendar = new Date(EPOCH_MS);
|
|
238
|
+
calendar.setUTCFullYear(year, monthIndex, day);
|
|
239
|
+
return year !== YEAR_ZERO
|
|
240
|
+
&& calendar.getUTCFullYear() === year
|
|
241
|
+
&& calendar.getUTCMonth() === monthIndex
|
|
242
|
+
&& calendar.getUTCDate() === day;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* @param {unknown} timestamp
|
|
247
|
+
* @returns {string}
|
|
248
|
+
*/
|
|
249
|
+
function requireTimestamp(timestamp) {
|
|
250
|
+
const text = requireNonEmpty("timestamp", timestamp);
|
|
251
|
+
if (!hasTimezone(text)) {
|
|
252
|
+
throw new SdkError("validation_error", `timestamp must include a timezone offset: ${text}`);
|
|
253
|
+
}
|
|
254
|
+
if (!isCalendarDate(text)) {
|
|
255
|
+
throw new SdkError("validation_error", `invalid timestamp: ${text}`);
|
|
256
|
+
}
|
|
257
|
+
return text;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
module.exports = {
|
|
261
|
+
ZERO_SPAN_ID,
|
|
262
|
+
ZERO_TRACE_ID,
|
|
263
|
+
hasUnicodeControlCharacter,
|
|
264
|
+
isAllowedValue,
|
|
265
|
+
isList,
|
|
266
|
+
isRecord,
|
|
267
|
+
optionalEntry,
|
|
268
|
+
rejectUnknownKeys,
|
|
269
|
+
requireAllowedValue,
|
|
270
|
+
requireFiniteNumber,
|
|
271
|
+
requireNonEmpty,
|
|
272
|
+
requireNonNegativeInteger,
|
|
273
|
+
requireObject,
|
|
274
|
+
requirePositiveInteger,
|
|
275
|
+
requireSpanId,
|
|
276
|
+
requireTimestamp,
|
|
277
|
+
requireTraceFlags,
|
|
278
|
+
requireTraceId
|
|
279
|
+
};
|
package/winston.cjs
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
const { Writable } = require("node:stream");
|
|
2
2
|
|
|
3
3
|
const { LogBrewClient, SdkError } = require("./core.cjs");
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
compactMetadata,
|
|
6
|
+
isMetadataValue,
|
|
7
|
+
traceFromProvider,
|
|
8
|
+
traceMetadataFromLogContext
|
|
9
|
+
} = require("./log-context.cjs");
|
|
5
10
|
|
|
6
11
|
// Runtime packages with request scopes (such as @logbrew/node) register a recorder that keeps
|
|
7
12
|
// breadcrumbs with the active request instead of the whole client.
|
|
@@ -16,12 +21,6 @@ const WINSTON_RESERVED_FIELDS = new Set([
|
|
|
16
21
|
"error",
|
|
17
22
|
"stack"
|
|
18
23
|
]);
|
|
19
|
-
const {
|
|
20
|
-
compactMetadata,
|
|
21
|
-
isMetadataValue,
|
|
22
|
-
traceFromProvider,
|
|
23
|
-
traceMetadataFromLogContext
|
|
24
|
-
} = buildLogContextHelpers({ SdkError });
|
|
25
24
|
|
|
26
25
|
function createLogBrewWinstonTransport(config) {
|
|
27
26
|
if (!config || typeof config !== "object") {
|