@guidelytech/node 0.16.0 → 0.17.0
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 +149 -5
- package/dist/index.cjs +1417 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +368 -7
- package/dist/index.d.ts +368 -7
- package/dist/index.js +1404 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -24,7 +34,9 @@ __export(index_exports, {
|
|
|
24
34
|
Guidely: () => Guidely,
|
|
25
35
|
GuidelyError: () => GuidelyError,
|
|
26
36
|
SDK_NAME: () => SDK_NAME,
|
|
27
|
-
SDK_VERSION: () => SDK_VERSION
|
|
37
|
+
SDK_VERSION: () => SDK_VERSION,
|
|
38
|
+
Scope: () => Scope,
|
|
39
|
+
parseStack: () => parseStack
|
|
28
40
|
});
|
|
29
41
|
module.exports = __toCommonJS(index_exports);
|
|
30
42
|
|
|
@@ -76,6 +88,11 @@ function id(name, value, required) {
|
|
|
76
88
|
if (length(s) > LIMITS.maxIdLength) throw new InvalidMessageError(`${name} is longer than ${LIMITS.maxIdLength} characters`);
|
|
77
89
|
return s;
|
|
78
90
|
}
|
|
91
|
+
function version(value) {
|
|
92
|
+
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
93
|
+
const s = String(value).trim();
|
|
94
|
+
return s ? s.slice(0, 200) : void 0;
|
|
95
|
+
}
|
|
79
96
|
function isPlainObject(v) {
|
|
80
97
|
if (typeof v !== "object" || v === null || Array.isArray(v)) return false;
|
|
81
98
|
const proto = Object.getPrototypeOf(v);
|
|
@@ -86,11 +103,11 @@ function object(name, value) {
|
|
|
86
103
|
if (!isPlainObject(value)) throw new InvalidMessageError(`${name} must be an object`);
|
|
87
104
|
return value;
|
|
88
105
|
}
|
|
89
|
-
function buildMessage(
|
|
90
|
-
if (!isPlainObject(args)) throw new InvalidMessageError(`${
|
|
106
|
+
function buildMessage(type2, args, now = () => /* @__PURE__ */ new Date()) {
|
|
107
|
+
if (!isPlainObject(args)) throw new InvalidMessageError(`${type2}() needs an object argument`);
|
|
91
108
|
const timestamp = args.timestamp === void 0 || args.timestamp === null ? now().toISOString() : formatTimestamp(args.timestamp);
|
|
92
|
-
const base = { type, messageId: newMessageId(), timestamp };
|
|
93
|
-
switch (
|
|
109
|
+
const base = { type: type2, messageId: newMessageId(), timestamp };
|
|
110
|
+
switch (type2) {
|
|
94
111
|
case "identify": {
|
|
95
112
|
const userId = id("userId", args.userId, true);
|
|
96
113
|
const traits = object("traits", args.traits);
|
|
@@ -106,13 +123,15 @@ function buildMessage(type, args, now = () => /* @__PURE__ */ new Date()) {
|
|
|
106
123
|
if (length(event) > LIMITS.maxEventLength) throw new InvalidMessageError(`event is longer than ${LIMITS.maxEventLength} characters`);
|
|
107
124
|
const properties = object("properties", args.properties);
|
|
108
125
|
const accountId = id("accountId", args.accountId, false);
|
|
126
|
+
const appVersion = version(args.appVersion);
|
|
109
127
|
return {
|
|
110
128
|
...base,
|
|
111
129
|
...userId ? { userId } : {},
|
|
112
130
|
...anonymousId ? { anonymousId } : {},
|
|
113
131
|
event,
|
|
114
132
|
...properties ? { properties } : {},
|
|
115
|
-
...accountId ? { accountId } : {}
|
|
133
|
+
...accountId ? { accountId } : {},
|
|
134
|
+
...appVersion ? { appVersion } : {}
|
|
116
135
|
};
|
|
117
136
|
}
|
|
118
137
|
case "group": {
|
|
@@ -145,12 +164,1249 @@ function jitter(delayMs, random) {
|
|
|
145
164
|
return Math.round(delayMs * (0.8 + 0.4 * random));
|
|
146
165
|
}
|
|
147
166
|
|
|
167
|
+
// src/errors/event.ts
|
|
168
|
+
var import_node_crypto2 = require("crypto");
|
|
169
|
+
var import_node_fs = require("fs");
|
|
170
|
+
var os = __toESM(require("os"), 1);
|
|
171
|
+
|
|
172
|
+
// src/errors/normalize.ts
|
|
173
|
+
var SENSITIVE_KEY = /(password|passwd|secret|token|credit|card|cvv|ssn|iban|authorization|cookie|api[-_]?key|private[-_]?key)/i;
|
|
174
|
+
var SENSITIVE_HEADER = /^(authorization|proxy-authorization|cookie|set-cookie|x-api-key|x-auth-token|x-csrf-token|x-xsrf-token|x-guidely-key|x-guidely-conversation)$|secret|token|password|passwd|api[-_]?key|signature|session/i;
|
|
175
|
+
var SENSITIVE_PARAM = /(token|key|secret|password|passwd|pwd|auth|session|code|sig|signature|credential|ssn|card|cvv|iban)/i;
|
|
176
|
+
var IP_HEADER = /^(x-forwarded-for|x-real-ip|forwarded|cf-connecting-ip|true-client-ip|x-client-ip)$/i;
|
|
177
|
+
var FILTERED = "[Filtered]";
|
|
178
|
+
var MAX_STRING = 2e3;
|
|
179
|
+
var MAX_KEYS = 100;
|
|
180
|
+
var MAX_ITEMS = 100;
|
|
181
|
+
function normalize(value, depth = 4, seen = /* @__PURE__ */ new WeakSet()) {
|
|
182
|
+
switch (typeof value) {
|
|
183
|
+
case "string":
|
|
184
|
+
return value.length > MAX_STRING ? `${value.slice(0, MAX_STRING)}\u2026` : value;
|
|
185
|
+
case "number":
|
|
186
|
+
return Number.isFinite(value) ? value : String(value);
|
|
187
|
+
case "boolean":
|
|
188
|
+
return value;
|
|
189
|
+
case "bigint":
|
|
190
|
+
return `${value}n`;
|
|
191
|
+
case "symbol":
|
|
192
|
+
return value.toString();
|
|
193
|
+
case "function":
|
|
194
|
+
return `[Function${value.name ? `: ${value.name}` : ""}]`;
|
|
195
|
+
case "undefined":
|
|
196
|
+
return void 0;
|
|
197
|
+
}
|
|
198
|
+
if (value === null) return null;
|
|
199
|
+
const obj = value;
|
|
200
|
+
if (obj instanceof Date) return Number.isNaN(obj.getTime()) ? "Invalid Date" : obj.toISOString();
|
|
201
|
+
if (obj instanceof Error) return `${obj.name}: ${obj.message}`.slice(0, MAX_STRING);
|
|
202
|
+
if (obj instanceof RegExp) return obj.toString();
|
|
203
|
+
if (ArrayBuffer.isView(obj)) return `[${obj.constructor?.name ?? "Buffer"} ${obj.byteLength} bytes]`;
|
|
204
|
+
if (seen.has(obj)) return "[Circular]";
|
|
205
|
+
if (depth <= 0) return Array.isArray(obj) ? "[Array]" : "[Object]";
|
|
206
|
+
seen.add(obj);
|
|
207
|
+
try {
|
|
208
|
+
if (Array.isArray(obj) || obj instanceof Set) {
|
|
209
|
+
const items = Array.isArray(obj) ? obj : [...obj];
|
|
210
|
+
const out2 = items.slice(0, MAX_ITEMS).map((v) => normalize(v, depth - 1, seen) ?? null);
|
|
211
|
+
if (items.length > MAX_ITEMS) out2.push(`[${items.length - MAX_ITEMS} more]`);
|
|
212
|
+
return out2;
|
|
213
|
+
}
|
|
214
|
+
const entries = obj instanceof Map ? [...obj].map(([k, v]) => [String(k), v]) : Object.keys(obj).map((k) => [k, void 0]);
|
|
215
|
+
const out = {};
|
|
216
|
+
let n = 0;
|
|
217
|
+
for (const [key, mapped] of entries) {
|
|
218
|
+
if (n >= MAX_KEYS) {
|
|
219
|
+
out["\u2026"] = `${entries.length - MAX_KEYS} more keys`;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
if (SENSITIVE_KEY.test(key)) continue;
|
|
223
|
+
let v;
|
|
224
|
+
try {
|
|
225
|
+
v = obj instanceof Map ? mapped : obj[key];
|
|
226
|
+
} catch {
|
|
227
|
+
v = "[Getter threw]";
|
|
228
|
+
}
|
|
229
|
+
const nv = normalize(v, depth - 1, seen);
|
|
230
|
+
if (nv === void 0) continue;
|
|
231
|
+
out[key] = nv;
|
|
232
|
+
n++;
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
} finally {
|
|
236
|
+
seen.delete(obj);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
function normalizeObject(value, depth = 4) {
|
|
240
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
241
|
+
const out = normalize(value, depth);
|
|
242
|
+
return out && typeof out === "object" && !Array.isArray(out) ? out : void 0;
|
|
243
|
+
}
|
|
244
|
+
function scrubQuery(query) {
|
|
245
|
+
return query.replace(/^\?/, "").split("&").filter(Boolean).slice(0, 100).map((pair) => {
|
|
246
|
+
const i = pair.indexOf("=");
|
|
247
|
+
const key = i < 0 ? pair : pair.slice(0, i);
|
|
248
|
+
let name = key;
|
|
249
|
+
try {
|
|
250
|
+
name = decodeURIComponent(key.replace(/\+/g, " "));
|
|
251
|
+
} catch {
|
|
252
|
+
}
|
|
253
|
+
return i >= 0 && SENSITIVE_PARAM.test(name) ? `${key}=${FILTERED}` : pair;
|
|
254
|
+
}).join("&").slice(0, 2e3);
|
|
255
|
+
}
|
|
256
|
+
function stripQuery(url) {
|
|
257
|
+
const i = url.search(/[?#]/);
|
|
258
|
+
return (i < 0 ? url : url.slice(0, i)).slice(0, 2e3);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/errors/stack.ts
|
|
262
|
+
var import_node_path = require("path");
|
|
263
|
+
var import_node_url = require("url");
|
|
264
|
+
var MAX_DETAIL_FRAMES = 250;
|
|
265
|
+
var MAX_PROPERTY_FRAMES = 30;
|
|
266
|
+
var LINE = /^\s*at (?:async )?(?:(.+?) \((.*)\)|(.+))$/;
|
|
267
|
+
var LOCATION = /^(.*?):(\d+)(?::(\d+))?$/;
|
|
268
|
+
var EVAL_ORIGIN = /\(([^()]*?:\d+:\d+)\)/;
|
|
269
|
+
function parseStack(stack, o = {}) {
|
|
270
|
+
if (typeof stack !== "string" || !stack) return [];
|
|
271
|
+
const frames = [];
|
|
272
|
+
for (const raw of stack.split("\n")) {
|
|
273
|
+
if (frames.length >= MAX_DETAIL_FRAMES * 2) break;
|
|
274
|
+
const m = LINE.exec(raw.length > 2e3 ? raw.slice(0, 2e3) : raw);
|
|
275
|
+
if (!m) continue;
|
|
276
|
+
const fn = m[1]?.trim();
|
|
277
|
+
let location = (m[2] ?? m[3] ?? "").trim();
|
|
278
|
+
if (location.startsWith("eval at ")) location = EVAL_ORIGIN.exec(location)?.[1] ?? location;
|
|
279
|
+
const at = LOCATION.exec(location);
|
|
280
|
+
const frame = {};
|
|
281
|
+
if (fn) frame.function = fn.slice(0, 200);
|
|
282
|
+
if (at) {
|
|
283
|
+
const file = filePath(at[1]);
|
|
284
|
+
frame.filename = file.slice(0, 1e3);
|
|
285
|
+
frame.abs_path = file.slice(0, 1e3);
|
|
286
|
+
frame.lineno = Number(at[2]);
|
|
287
|
+
if (at[3]) frame.colno = Number(at[3]);
|
|
288
|
+
const mod = moduleOf(file, o.root);
|
|
289
|
+
if (mod) frame.module = mod;
|
|
290
|
+
frame.in_app = isInApp(file, o.inAppExclude);
|
|
291
|
+
} else {
|
|
292
|
+
if (!fn && !location) continue;
|
|
293
|
+
if (location && !/^index \d+$/.test(location)) frame.filename = location.slice(0, 200);
|
|
294
|
+
frame.in_app = false;
|
|
295
|
+
}
|
|
296
|
+
frames.push(frame);
|
|
297
|
+
}
|
|
298
|
+
frames.reverse();
|
|
299
|
+
return frames.length > MAX_DETAIL_FRAMES ? frames.slice(-MAX_DETAIL_FRAMES) : frames;
|
|
300
|
+
}
|
|
301
|
+
function filePath(location) {
|
|
302
|
+
if (!location.startsWith("file://")) return location;
|
|
303
|
+
try {
|
|
304
|
+
return (0, import_node_url.fileURLToPath)(location);
|
|
305
|
+
} catch {
|
|
306
|
+
return location;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
function isInApp(file, exclude) {
|
|
310
|
+
if (!file || file.startsWith("node:") || file.startsWith("internal/")) return false;
|
|
311
|
+
if (!file.startsWith("/") && !/^[a-z]:[\\/]/i.test(file) && !file.startsWith("\\\\")) return false;
|
|
312
|
+
if (/[\\/]node_modules[\\/]/.test(file)) return false;
|
|
313
|
+
for (const p of exclude ?? []) {
|
|
314
|
+
if (typeof p === "string") {
|
|
315
|
+
if (p && file.includes(p)) return false;
|
|
316
|
+
} else if (p instanceof RegExp) {
|
|
317
|
+
p.lastIndex = 0;
|
|
318
|
+
if (p.test(file)) return false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
function moduleOf(file, root) {
|
|
324
|
+
if (!file || file.startsWith("node:") || file.startsWith("internal/")) return void 0;
|
|
325
|
+
const normalized = file.replace(/^[a-z]:/i, "").replace(/\\/g, "/");
|
|
326
|
+
if (!normalized.startsWith("/")) return void 0;
|
|
327
|
+
const parsed = import_node_path.posix.parse(normalized);
|
|
328
|
+
let name = /^\.[cm]?[jt]sx?$/.test(parsed.ext) ? parsed.name : parsed.base;
|
|
329
|
+
try {
|
|
330
|
+
name = decodeURIComponent(name);
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
const dir = parsed.dir || ".";
|
|
334
|
+
const n = dir.lastIndexOf("/node_modules");
|
|
335
|
+
if (n > -1) return `${dir.slice(n + 14).replace(/\//g, ".")}:${name}`;
|
|
336
|
+
const base = root?.replace(/^[a-z]:/i, "").replace(/\\/g, "/").replace(/\/+$/, "");
|
|
337
|
+
if (base && (dir === base || dir.startsWith(`${base}/`))) {
|
|
338
|
+
const mod = dir.slice(base.length + 1).replace(/\//g, ".");
|
|
339
|
+
return mod ? `${mod}:${name}` : name;
|
|
340
|
+
}
|
|
341
|
+
return name;
|
|
342
|
+
}
|
|
343
|
+
function toGuidelyFrames(frames) {
|
|
344
|
+
const out = [];
|
|
345
|
+
if (!frames) return out;
|
|
346
|
+
for (let i = frames.length - 1; i >= 0 && out.length < MAX_PROPERTY_FRAMES; i--) {
|
|
347
|
+
const f = frames[i];
|
|
348
|
+
const file = f.abs_path || f.filename;
|
|
349
|
+
out.push({
|
|
350
|
+
...f.function ? { fn: f.function.slice(0, 200) } : {},
|
|
351
|
+
...file ? { file: file.slice(0, 500) } : {},
|
|
352
|
+
...typeof f.lineno === "number" ? { line: f.lineno } : {},
|
|
353
|
+
...typeof f.colno === "number" ? { col: f.colno } : {},
|
|
354
|
+
...typeof f.in_app === "boolean" ? { inApp: f.in_app } : {}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
return out;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// src/errors/event.ts
|
|
361
|
+
var ERROR_LIMITS = {
|
|
362
|
+
/** `detail` (the Sentry-shaped event) after trimming. */
|
|
363
|
+
maxDetailBytes: 65536,
|
|
364
|
+
/** `properties`, inside the event's property budget. */
|
|
365
|
+
maxPropertyBytes: 8192,
|
|
366
|
+
/** A whole `exception` message: properties + detail + envelope. Larger ones lose their `detail`. */
|
|
367
|
+
maxMessageBytes: 81920,
|
|
368
|
+
/** Exceptions in a cause / AggregateError chain. */
|
|
369
|
+
maxChain: 20
|
|
370
|
+
};
|
|
371
|
+
var newEventId = () => (0, import_node_crypto2.randomUUID)().replace(/-/g, "");
|
|
372
|
+
var errorLike = (v) => v instanceof Error || !!v && typeof v === "object" && typeof v.message === "string" && (typeof v.stack === "string" || typeof v.name === "string");
|
|
373
|
+
function typeOf(e) {
|
|
374
|
+
const name = typeof e.name === "string" && e.name ? e.name : void 0;
|
|
375
|
+
const ctor = e.constructor?.name;
|
|
376
|
+
return (name === "Error" && typeof ctor === "string" && ctor && ctor !== "Error" ? ctor : name) ?? (typeof ctor === "string" && ctor ? ctor : "Error");
|
|
377
|
+
}
|
|
378
|
+
function describe(value) {
|
|
379
|
+
if (typeof value === "string") return value;
|
|
380
|
+
if (value === void 0 || value === null || typeof value !== "object") return String(value);
|
|
381
|
+
const keys = Object.keys(value).slice(0, 10);
|
|
382
|
+
return keys.length ? `Non-Error exception captured with keys: ${keys.join(", ")}` : `Non-Error exception captured: ${Object.prototype.toString.call(value)}`;
|
|
383
|
+
}
|
|
384
|
+
function exceptionChain(error, mechanism, o, fallbackType = "Error") {
|
|
385
|
+
const out = [];
|
|
386
|
+
const seen = /* @__PURE__ */ new Set();
|
|
387
|
+
const visit = (value, mech) => {
|
|
388
|
+
if (out.length >= ERROR_LIMITS.maxChain) return;
|
|
389
|
+
seen.add(value);
|
|
390
|
+
const id2 = out.length;
|
|
391
|
+
const isError = errorLike(value);
|
|
392
|
+
const frames = isError ? parseStack(value.stack, o) : [];
|
|
393
|
+
const aggregate = isError && Array.isArray(value.errors);
|
|
394
|
+
const ex = {
|
|
395
|
+
type: (isError ? typeOf(value) : fallbackType).slice(0, 200),
|
|
396
|
+
value: (isError ? String(value.message ?? "") : describe(value)).slice(0, 8e3),
|
|
397
|
+
mechanism: { ...mech, exception_id: id2, ...aggregate ? { is_exception_group: true } : {} },
|
|
398
|
+
...frames.length ? { stacktrace: { frames } } : {}
|
|
399
|
+
};
|
|
400
|
+
out.push(ex);
|
|
401
|
+
if (!isError) return;
|
|
402
|
+
const link = (child, source) => {
|
|
403
|
+
if (!errorLike(child) || seen.has(child)) return;
|
|
404
|
+
visit(child, { type: "chained", handled: mechanism.handled, source, parent_id: id2 });
|
|
405
|
+
};
|
|
406
|
+
if (aggregate) value.errors.slice(0, ERROR_LIMITS.maxChain).forEach((child, i) => link(child, `errors[${i}]`));
|
|
407
|
+
link(value.cause, "cause");
|
|
408
|
+
};
|
|
409
|
+
visit(error, { type: mechanism.type, handled: mechanism.handled });
|
|
410
|
+
return out.reverse();
|
|
411
|
+
}
|
|
412
|
+
function linuxRelease() {
|
|
413
|
+
try {
|
|
414
|
+
const text = (0, import_node_fs.readFileSync)("/etc/os-release", "utf8").slice(0, 1e4);
|
|
415
|
+
const field = (k) => new RegExp(`^${k}="?([^"\\n]*)"?$`, "m").exec(text)?.[1];
|
|
416
|
+
return { name: field("NAME"), version: field("VERSION_ID") };
|
|
417
|
+
} catch {
|
|
418
|
+
return {};
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function macosVersion(release2) {
|
|
422
|
+
const major = Number(release2.split(".")[0]);
|
|
423
|
+
if (!Number.isFinite(major)) return void 0;
|
|
424
|
+
if (major >= 25) return String(major + 1);
|
|
425
|
+
if (major >= 20) return String(major - 9);
|
|
426
|
+
return major >= 5 ? `10.${major - 4}` : void 0;
|
|
427
|
+
}
|
|
428
|
+
var staticContexts = null;
|
|
429
|
+
function processContexts() {
|
|
430
|
+
if (!staticContexts) {
|
|
431
|
+
const release2 = safe(() => os.release(), "");
|
|
432
|
+
const platform = process.platform;
|
|
433
|
+
const linux = platform === "linux" ? linuxRelease() : {};
|
|
434
|
+
const osName = platform === "darwin" ? "macOS" : platform === "win32" ? "Windows" : platform === "linux" ? linux.name ?? "Linux" : safe(() => os.type(), platform);
|
|
435
|
+
const osVersion = platform === "darwin" ? macosVersion(release2) : platform === "linux" ? linux.version : release2;
|
|
436
|
+
const cpus2 = safe(() => os.cpus(), []);
|
|
437
|
+
const intl = safe(() => Intl.DateTimeFormat().resolvedOptions(), void 0);
|
|
438
|
+
staticContexts = {
|
|
439
|
+
runtime: { name: "node", version: process.version },
|
|
440
|
+
os: { name: osName, ...osVersion ? { version: osVersion } : {}, ...release2 ? { kernel_version: release2 } : {} },
|
|
441
|
+
device: {
|
|
442
|
+
arch: process.arch,
|
|
443
|
+
memory_size: safe(() => os.totalmem(), void 0),
|
|
444
|
+
processor_count: cpus2.length || void 0,
|
|
445
|
+
cpu_description: cpus2[0]?.model,
|
|
446
|
+
processor_frequency: cpus2[0]?.speed || void 0,
|
|
447
|
+
boot_time: safe(() => new Date(Date.now() - os.uptime() * 1e3).toISOString(), void 0)
|
|
448
|
+
},
|
|
449
|
+
app: { app_start_time: new Date(Date.now() - process.uptime() * 1e3).toISOString() },
|
|
450
|
+
culture: intl ? { locale: intl.locale, timezone: intl.timeZone } : {}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
const s = staticContexts;
|
|
454
|
+
return {
|
|
455
|
+
runtime: { ...s.runtime },
|
|
456
|
+
os: { ...s.os },
|
|
457
|
+
device: { ...s.device, free_memory: safe(() => os.freemem(), void 0) },
|
|
458
|
+
app: { ...s.app, app_memory: safe(() => process.memoryUsage().rss, void 0) },
|
|
459
|
+
culture: { ...s.culture }
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function safe(fn, fallback) {
|
|
463
|
+
try {
|
|
464
|
+
return fn();
|
|
465
|
+
} catch {
|
|
466
|
+
return fallback;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
var bytes = (v) => Buffer.byteLength(JSON.stringify(v) ?? "", "utf8");
|
|
470
|
+
function trimDetail(event, max = ERROR_LIMITS.maxDetailBytes) {
|
|
471
|
+
let size = bytes(event);
|
|
472
|
+
if (size <= max) return event;
|
|
473
|
+
const e = { ...event };
|
|
474
|
+
const frames = () => (e.exception?.values ?? []).flatMap((x) => x.stacktrace?.frames ?? []);
|
|
475
|
+
let crumbs = e.breadcrumbs?.values ?? [];
|
|
476
|
+
while (crumbs.length > 0 && size > max) {
|
|
477
|
+
crumbs = crumbs.slice(Math.ceil(crumbs.length / 2));
|
|
478
|
+
e.breadcrumbs = { values: crumbs };
|
|
479
|
+
size = bytes(e);
|
|
480
|
+
}
|
|
481
|
+
if (size <= max) return e;
|
|
482
|
+
delete e.breadcrumbs;
|
|
483
|
+
e.exception = e.exception ? { values: e.exception.values.map((x) => ({ ...x, ...x.stacktrace ? { stacktrace: { frames: x.stacktrace.frames.map((f) => ({ ...f })) } } : {} })) } : void 0;
|
|
484
|
+
if (!e.exception) delete e.exception;
|
|
485
|
+
for (const f of frames()) {
|
|
486
|
+
delete f.pre_context;
|
|
487
|
+
delete f.post_context;
|
|
488
|
+
delete f.context_line;
|
|
489
|
+
delete f.vars;
|
|
490
|
+
}
|
|
491
|
+
size = bytes(e);
|
|
492
|
+
if (size <= max) return e;
|
|
493
|
+
e.extra = { _trimmed: true };
|
|
494
|
+
if (e.request) e.request = { ...e.request, headers: {} };
|
|
495
|
+
size = bytes(e);
|
|
496
|
+
if (size <= max) return e;
|
|
497
|
+
for (const x of e.exception?.values ?? []) if (x.stacktrace && x.stacktrace.frames.length > 50) x.stacktrace.frames = x.stacktrace.frames.slice(-50);
|
|
498
|
+
while (e.exception && e.exception.values.length > 1 && bytes(e) > max) e.exception.values.shift();
|
|
499
|
+
if (bytes(e) <= max) return e;
|
|
500
|
+
e.contexts = { runtime: e.contexts?.runtime ?? {} };
|
|
501
|
+
delete e.extra;
|
|
502
|
+
return bytes(e) <= max ? e : void 0;
|
|
503
|
+
}
|
|
504
|
+
function propertiesOf(event, source, release2, extra) {
|
|
505
|
+
const main = event.exception?.values?.[event.exception.values.length - 1];
|
|
506
|
+
const os3 = event.contexts?.os;
|
|
507
|
+
const props = {
|
|
508
|
+
type: (main?.type ?? "Message").slice(0, 200),
|
|
509
|
+
message: (main?.value ?? event.logentry?.formatted ?? "").slice(0, 1e3),
|
|
510
|
+
frames: toGuidelyFrames(main?.stacktrace?.frames),
|
|
511
|
+
source,
|
|
512
|
+
handled: main?.mechanism ? main.mechanism.handled !== false : event.level !== "fatal",
|
|
513
|
+
level: event.level,
|
|
514
|
+
platform: "node",
|
|
515
|
+
...main?.mechanism?.type ? { mechanism: main.mechanism.type.slice(0, 60) } : {},
|
|
516
|
+
...release2 ? { release: release2.slice(0, 200) } : {},
|
|
517
|
+
...Array.isArray(event.fingerprint) && event.fingerprint.length ? { fingerprintParts: event.fingerprint.slice(0, 20).map((p) => String(p).slice(0, 200)) } : {},
|
|
518
|
+
...event.transaction ? { transaction: String(event.transaction).slice(0, 200) } : {},
|
|
519
|
+
...os3?.name ? { os: [os3.name, os3.version].filter(Boolean).join(" ").slice(0, 60) } : {}
|
|
520
|
+
};
|
|
521
|
+
const user = normalize(extra, 3);
|
|
522
|
+
for (const [k, v] of Object.entries(user ?? {})) if (!(k in props) && !k.startsWith("$")) props[k] = v;
|
|
523
|
+
return fitProperties(props);
|
|
524
|
+
}
|
|
525
|
+
function fitProperties(props) {
|
|
526
|
+
if (bytes(props) <= ERROR_LIMITS.maxPropertyBytes) return props;
|
|
527
|
+
const core = /* @__PURE__ */ new Set(["type", "message", "frames", "source", "handled", "level", "platform", "mechanism", "release", "fingerprintParts", "transaction", "os"]);
|
|
528
|
+
const out = {};
|
|
529
|
+
for (const [k, v] of Object.entries(props)) if (core.has(k)) out[k] = v;
|
|
530
|
+
const frames = out.frames.slice();
|
|
531
|
+
while (frames.length > 1 && bytes(out) > ERROR_LIMITS.maxPropertyBytes) {
|
|
532
|
+
frames.pop();
|
|
533
|
+
out.frames = frames;
|
|
534
|
+
}
|
|
535
|
+
if (bytes(out) > ERROR_LIMITS.maxPropertyBytes) out.message = String(out.message).slice(0, 200);
|
|
536
|
+
return out;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/errors/monitor.ts
|
|
540
|
+
var import_node_async_hooks = require("async_hooks");
|
|
541
|
+
var os2 = __toESM(require("os"), 1);
|
|
542
|
+
var import_node_path3 = require("path");
|
|
543
|
+
|
|
544
|
+
// src/errors/breadcrumbs.ts
|
|
545
|
+
var dc = __toESM(require("diagnostics_channel"), 1);
|
|
546
|
+
var import_node_util = require("util");
|
|
547
|
+
var CONSOLE_LEVEL = { debug: "debug", info: "info", log: "log", warn: "warning", error: "error" };
|
|
548
|
+
var wrapped = /* @__PURE__ */ new Map();
|
|
549
|
+
var inConsole = false;
|
|
550
|
+
function instrumentConsole(levels, record) {
|
|
551
|
+
const target = console;
|
|
552
|
+
const mine = [];
|
|
553
|
+
for (const level of new Set(levels)) {
|
|
554
|
+
if (typeof target[level] !== "function") continue;
|
|
555
|
+
let w = wrapped.get(level);
|
|
556
|
+
if (w && w.listeners.size === 0 && target[level] !== w.wrapper) {
|
|
557
|
+
wrapped.delete(level);
|
|
558
|
+
w = void 0;
|
|
559
|
+
}
|
|
560
|
+
if (!w) {
|
|
561
|
+
const original = target[level];
|
|
562
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
563
|
+
const wrapper = function(...args) {
|
|
564
|
+
if (!inConsole && listeners.size > 0) {
|
|
565
|
+
inConsole = true;
|
|
566
|
+
try {
|
|
567
|
+
const message = (0, import_node_util.format)(...args);
|
|
568
|
+
if (!message.startsWith("[guidely-node]")) {
|
|
569
|
+
const crumb = { category: "console", level: CONSOLE_LEVEL[level], message: message.slice(0, 1e3), data: { arguments: normalize(args, 3), logger: "console" } };
|
|
570
|
+
for (const l of listeners) l({ ...crumb }, { input: args, level });
|
|
571
|
+
}
|
|
572
|
+
} catch {
|
|
573
|
+
} finally {
|
|
574
|
+
inConsole = false;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return original.apply(this, args);
|
|
578
|
+
};
|
|
579
|
+
w = { original, wrapper, listeners };
|
|
580
|
+
wrapped.set(level, w);
|
|
581
|
+
target[level] = wrapper;
|
|
582
|
+
}
|
|
583
|
+
w.listeners.add(record);
|
|
584
|
+
mine.push(level);
|
|
585
|
+
}
|
|
586
|
+
return () => {
|
|
587
|
+
for (const level of mine) {
|
|
588
|
+
const w = wrapped.get(level);
|
|
589
|
+
if (!w) continue;
|
|
590
|
+
w.listeners.delete(record);
|
|
591
|
+
if (w.listeners.size === 0 && target[level] === w.wrapper) {
|
|
592
|
+
target[level] = w.original;
|
|
593
|
+
wrapped.delete(level);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
function subscribe2(name, handler) {
|
|
599
|
+
try {
|
|
600
|
+
if (typeof dc.subscribe === "function") {
|
|
601
|
+
dc.subscribe(name, handler);
|
|
602
|
+
return () => dc.unsubscribe(name, handler);
|
|
603
|
+
}
|
|
604
|
+
const ch = dc.channel(name);
|
|
605
|
+
ch.subscribe(handler);
|
|
606
|
+
return () => ch.unsubscribe(handler);
|
|
607
|
+
} catch {
|
|
608
|
+
return () => {
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
var levelOf = (status) => status === void 0 || status >= 500 ? "error" : status >= 400 ? "warning" : "info";
|
|
613
|
+
function instrumentHttp(record, current, ignore) {
|
|
614
|
+
const started = /* @__PURE__ */ new WeakMap();
|
|
615
|
+
const begin = (request, method, url) => {
|
|
616
|
+
if (ignore(url)) return;
|
|
617
|
+
started.set(request, { method: typeof method === "string" ? method.toUpperCase() : "GET", url: stripQuery(url), start: Date.now(), target: current() });
|
|
618
|
+
};
|
|
619
|
+
const end = (request, response, status, error) => {
|
|
620
|
+
const s = started.get(request);
|
|
621
|
+
if (!s) return;
|
|
622
|
+
started.delete(request);
|
|
623
|
+
const data = { method: s.method, url: s.url, ...status !== void 0 ? { status_code: status } : {}, duration_ms: Date.now() - s.start };
|
|
624
|
+
if (error) data.error = error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200);
|
|
625
|
+
try {
|
|
626
|
+
record({ type: "http", category: "http", level: levelOf(status), data }, { request, response }, s.target);
|
|
627
|
+
} catch {
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
const statusOf2 = (response) => {
|
|
631
|
+
const code = response?.statusCode;
|
|
632
|
+
return typeof code === "number" ? code : void 0;
|
|
633
|
+
};
|
|
634
|
+
const offs = [
|
|
635
|
+
// http / https
|
|
636
|
+
subscribe2("http.client.request.start", (m) => {
|
|
637
|
+
const req = m.request;
|
|
638
|
+
if (!req || typeof req !== "object") return;
|
|
639
|
+
const host = typeof req.getHeader === "function" ? req.getHeader("host") : void 0;
|
|
640
|
+
begin(req, req.method, `${req.protocol ?? "http:"}//${typeof host === "string" ? host : req.host ?? ""}${req.path ?? "/"}`);
|
|
641
|
+
}),
|
|
642
|
+
subscribe2("http.client.response.finish", (m) => {
|
|
643
|
+
const { request, response } = m;
|
|
644
|
+
if (request) end(request, response, statusOf2(response));
|
|
645
|
+
}),
|
|
646
|
+
subscribe2("http.client.request.error", (m) => {
|
|
647
|
+
const { request, error } = m;
|
|
648
|
+
if (request) end(request, void 0, void 0, error);
|
|
649
|
+
}),
|
|
650
|
+
// fetch / undici
|
|
651
|
+
subscribe2("undici:request:create", (m) => {
|
|
652
|
+
const req = m.request;
|
|
653
|
+
if (!req || typeof req !== "object") return;
|
|
654
|
+
const origin = String(req.origin ?? "").replace(/\/+$/, "");
|
|
655
|
+
begin(req, req.method, `${origin}${req.path ?? "/"}`);
|
|
656
|
+
}),
|
|
657
|
+
subscribe2("undici:request:headers", (m) => {
|
|
658
|
+
const { request, response } = m;
|
|
659
|
+
if (request) end(request, response, statusOf2(response));
|
|
660
|
+
}),
|
|
661
|
+
subscribe2("undici:request:error", (m) => {
|
|
662
|
+
const { request, error } = m;
|
|
663
|
+
if (request) end(request, void 0, void 0, error);
|
|
664
|
+
})
|
|
665
|
+
];
|
|
666
|
+
return () => offs.forEach((off) => off());
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// src/errors/context-lines.ts
|
|
670
|
+
var import_promises = require("fs/promises");
|
|
671
|
+
var import_node_path2 = require("path");
|
|
672
|
+
var MAX_FILE_BYTES = 1e6;
|
|
673
|
+
var MAX_CACHED_FILES = 20;
|
|
674
|
+
var MAX_FRAMES = 50;
|
|
675
|
+
var MAX_LINE = 200;
|
|
676
|
+
var MAX_CONTEXT_LINES = 10;
|
|
677
|
+
var cache = /* @__PURE__ */ new Map();
|
|
678
|
+
var loading = /* @__PURE__ */ new Map();
|
|
679
|
+
function remember(file, lines) {
|
|
680
|
+
cache.delete(file);
|
|
681
|
+
cache.set(file, lines);
|
|
682
|
+
while (cache.size > MAX_CACHED_FILES) cache.delete(cache.keys().next().value);
|
|
683
|
+
}
|
|
684
|
+
function linesOf(file) {
|
|
685
|
+
if (cache.has(file)) {
|
|
686
|
+
const lines = cache.get(file);
|
|
687
|
+
remember(file, lines);
|
|
688
|
+
return Promise.resolve(lines);
|
|
689
|
+
}
|
|
690
|
+
let p = loading.get(file);
|
|
691
|
+
if (!p) {
|
|
692
|
+
p = (async () => {
|
|
693
|
+
try {
|
|
694
|
+
const s = await (0, import_promises.stat)(file);
|
|
695
|
+
if (!s.isFile() || s.size > MAX_FILE_BYTES) return null;
|
|
696
|
+
return (await (0, import_promises.readFile)(file, "utf8")).split(/\r?\n/);
|
|
697
|
+
} catch {
|
|
698
|
+
return null;
|
|
699
|
+
}
|
|
700
|
+
})().then((lines) => {
|
|
701
|
+
loading.delete(file);
|
|
702
|
+
remember(file, lines);
|
|
703
|
+
return lines;
|
|
704
|
+
});
|
|
705
|
+
loading.set(file, p);
|
|
706
|
+
}
|
|
707
|
+
return p;
|
|
708
|
+
}
|
|
709
|
+
function snip(line, colno) {
|
|
710
|
+
if (line.length <= MAX_LINE) return line;
|
|
711
|
+
const at = colno ? Math.max(0, colno - 1) : 0;
|
|
712
|
+
const start = Math.max(0, Math.min(at - 60, line.length - MAX_LINE));
|
|
713
|
+
const end = Math.min(line.length, start + MAX_LINE);
|
|
714
|
+
return `${start > 0 ? "{snip} " : ""}${line.slice(start, end)}${end < line.length ? " {snip}" : ""}`;
|
|
715
|
+
}
|
|
716
|
+
function wantsContext(frames) {
|
|
717
|
+
return frames.some((f) => f.in_app && f.lineno && f.abs_path && (0, import_node_path2.isAbsolute)(f.abs_path));
|
|
718
|
+
}
|
|
719
|
+
async function addContextLines(frames, n) {
|
|
720
|
+
const count = Math.min(Math.max(0, Math.floor(n)), MAX_CONTEXT_LINES);
|
|
721
|
+
if (count === 0) return;
|
|
722
|
+
const targets = frames.filter((f) => f.in_app && f.lineno && f.abs_path && (0, import_node_path2.isAbsolute)(f.abs_path)).slice(-MAX_FRAMES);
|
|
723
|
+
await Promise.all(
|
|
724
|
+
targets.map(async (f) => {
|
|
725
|
+
const lines = await linesOf(f.abs_path);
|
|
726
|
+
const i = f.lineno - 1;
|
|
727
|
+
if (!lines || i < 0 || i >= lines.length) return;
|
|
728
|
+
f.pre_context = lines.slice(Math.max(0, i - count), i).map((l) => snip(l));
|
|
729
|
+
f.context_line = snip(lines[i], f.colno);
|
|
730
|
+
f.post_context = lines.slice(i + 1, i + 1 + count).map((l) => snip(l));
|
|
731
|
+
})
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
// src/errors/process-handlers.ts
|
|
736
|
+
var import_node_util2 = require("util");
|
|
737
|
+
var OURS = /* @__PURE__ */ Symbol.for("guidely.processHandler");
|
|
738
|
+
var isOurs = (l) => typeof l === "function" && l[OURS] === true;
|
|
739
|
+
function nodeMode(proc) {
|
|
740
|
+
const args = [...proc.execArgv ?? [], ...(proc.env?.NODE_OPTIONS ?? "").split(/\s+/)];
|
|
741
|
+
for (const a of args) {
|
|
742
|
+
const m = /^--unhandled-rejections=([\w-]+)$/.exec(a);
|
|
743
|
+
if (m && ["throw", "strict", "warn", "warn-with-error-code", "none"].includes(m[1])) return m[1];
|
|
744
|
+
}
|
|
745
|
+
return "throw";
|
|
746
|
+
}
|
|
747
|
+
function installProcessHandlers(host, o = {}) {
|
|
748
|
+
const proc = o.process ?? process;
|
|
749
|
+
const exitOnUncaught = o.exitOnUncaught !== false;
|
|
750
|
+
const mode = o.unhandledRejection ?? nodeMode(proc);
|
|
751
|
+
const flushTimeoutMs = typeof o.flushTimeoutMs === "number" && o.flushTimeoutMs >= 0 ? o.flushTimeoutMs : 2e3;
|
|
752
|
+
let exiting = false;
|
|
753
|
+
const others = (event) => proc.listeners(event).filter((l) => !isOurs(l)).length;
|
|
754
|
+
const exit = (error, prefix) => {
|
|
755
|
+
if (exiting) return;
|
|
756
|
+
exiting = true;
|
|
757
|
+
try {
|
|
758
|
+
console.error(`${prefix}${(0, import_node_util2.inspect)(error)}`);
|
|
759
|
+
} catch {
|
|
760
|
+
}
|
|
761
|
+
let timer;
|
|
762
|
+
const timeout = new Promise((resolve) => {
|
|
763
|
+
timer = setTimeout(resolve, flushTimeoutMs);
|
|
764
|
+
});
|
|
765
|
+
void Promise.race([host.flush().catch(() => {
|
|
766
|
+
}), timeout]).then(() => {
|
|
767
|
+
clearTimeout(timer);
|
|
768
|
+
proc.exitCode = 1;
|
|
769
|
+
proc.exit(1);
|
|
770
|
+
});
|
|
771
|
+
};
|
|
772
|
+
const onUncaught = (error) => {
|
|
773
|
+
const crash = exitOnUncaught && others("uncaughtException") === 0;
|
|
774
|
+
host.capture(error, { mechanism: "uncaughtException", level: "fatal" });
|
|
775
|
+
if (crash) exit(error, "Uncaught ");
|
|
776
|
+
};
|
|
777
|
+
const onRejection = (reason) => {
|
|
778
|
+
const raised = mode === "strict" || mode === "throw" && others("unhandledRejection") === 0;
|
|
779
|
+
const crash = exitOnUncaught && raised && others("uncaughtException") === 0;
|
|
780
|
+
host.capture(reason, { mechanism: "unhandledRejection", level: crash ? "fatal" : "error", fallbackType: "UnhandledRejection" });
|
|
781
|
+
if (crash) return exit(reason, "Unhandled promise rejection: ");
|
|
782
|
+
if (mode === "none" || others("unhandledRejection") > 0) return;
|
|
783
|
+
if (mode === "warn-with-error-code") proc.exitCode = 1;
|
|
784
|
+
try {
|
|
785
|
+
console.warn(`Unhandled promise rejection: ${(0, import_node_util2.inspect)(reason)}`);
|
|
786
|
+
} catch {
|
|
787
|
+
}
|
|
788
|
+
};
|
|
789
|
+
for (const l of [onUncaught, onRejection]) l[OURS] = true;
|
|
790
|
+
proc.on("uncaughtException", onUncaught);
|
|
791
|
+
proc.on("unhandledRejection", onRejection);
|
|
792
|
+
let installed = true;
|
|
793
|
+
return () => {
|
|
794
|
+
if (!installed) return;
|
|
795
|
+
installed = false;
|
|
796
|
+
proc.removeListener("uncaughtException", onUncaught);
|
|
797
|
+
proc.removeListener("unhandledRejection", onRejection);
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// src/errors/request.ts
|
|
802
|
+
var str = (v, max = 2e3) => typeof v === "string" && v ? v.slice(0, max) : void 0;
|
|
803
|
+
function nodeRequestOf(r) {
|
|
804
|
+
const req = r.req;
|
|
805
|
+
return req && typeof req === "object" ? req : r;
|
|
806
|
+
}
|
|
807
|
+
function header(headers, name) {
|
|
808
|
+
const v = headers?.[name];
|
|
809
|
+
return Array.isArray(v) ? v[0] : typeof v === "string" ? v : typeof v === "number" ? String(v) : void 0;
|
|
810
|
+
}
|
|
811
|
+
function transactionOf(input) {
|
|
812
|
+
if (!input || typeof input !== "object") return void 0;
|
|
813
|
+
const r = input;
|
|
814
|
+
const method = str(r.method, 16)?.toUpperCase();
|
|
815
|
+
const routePath = typeof r.route?.path === "string" ? r.route.path : void 0;
|
|
816
|
+
const route = str(r.routeOptions?.url) ?? str(r.routerPath) ?? str(r._matchedRoute) ?? (routePath !== void 0 ? `${r.baseUrl ?? ""}${routePath}` || "/" : void 0);
|
|
817
|
+
return method && route ? `${method} ${route}`.slice(0, 200) : void 0;
|
|
818
|
+
}
|
|
819
|
+
function requestDataOf(input, sendDefaultPii) {
|
|
820
|
+
if (!input || typeof input !== "object") return void 0;
|
|
821
|
+
const r = input;
|
|
822
|
+
const node = nodeRequestOf(r);
|
|
823
|
+
const rawHeaders = (r.headers && typeof r.headers === "object" ? r.headers : node.headers) ?? {};
|
|
824
|
+
const headers = {};
|
|
825
|
+
let n = 0;
|
|
826
|
+
for (const [name, value] of Object.entries(rawHeaders)) {
|
|
827
|
+
if (n >= 50) break;
|
|
828
|
+
if (value === void 0 || SENSITIVE_HEADER.test(name) || !sendDefaultPii && IP_HEADER.test(name)) continue;
|
|
829
|
+
headers[name.toLowerCase()] = (Array.isArray(value) ? value.join(", ") : String(value)).slice(0, 1e3);
|
|
830
|
+
n++;
|
|
831
|
+
}
|
|
832
|
+
const raw = str(r.originalUrl) ?? str(node.originalUrl) ?? str(r.url) ?? str(node.url) ?? "";
|
|
833
|
+
let url = raw;
|
|
834
|
+
let query = typeof r.query_string === "string" ? r.query_string : void 0;
|
|
835
|
+
const q = raw.indexOf("?");
|
|
836
|
+
if (q >= 0) {
|
|
837
|
+
query = raw.slice(q + 1).replace(/#.*$/, "");
|
|
838
|
+
url = raw.slice(0, q);
|
|
839
|
+
}
|
|
840
|
+
url = url.replace(/#.*$/, "");
|
|
841
|
+
if (url && !/^[a-z][a-z0-9+.-]*:\/\//i.test(url)) {
|
|
842
|
+
const host = header(rawHeaders, "host") ?? str(r.host) ?? str(r.hostname);
|
|
843
|
+
const protocol = (str(r.protocol) ?? (node.socket?.encrypted ? "https" : "http")).replace(/:$/, "");
|
|
844
|
+
if (host) url = `${protocol}://${host}${url.startsWith("/") ? "" : "/"}${url}`;
|
|
845
|
+
}
|
|
846
|
+
const request = {
|
|
847
|
+
...str(r.method, 16) ? { method: str(r.method, 16).toUpperCase() } : {},
|
|
848
|
+
...url ? { url: url.slice(0, 2e3) } : {},
|
|
849
|
+
...query ? { query_string: scrubQuery(query) } : {},
|
|
850
|
+
headers
|
|
851
|
+
};
|
|
852
|
+
if (!sendDefaultPii) return { request };
|
|
853
|
+
const forwarded = header(rawHeaders, "x-forwarded-for")?.split(",")[0]?.trim();
|
|
854
|
+
const ip = str(r.ip, 64) ?? str(forwarded, 64) ?? str(node.socket?.remoteAddress, 64);
|
|
855
|
+
if (ip) request.env = { REMOTE_ADDR: ip };
|
|
856
|
+
return { request, ...ip ? { ip } : {} };
|
|
857
|
+
}
|
|
858
|
+
function scrubRequest(request, sendDefaultPii) {
|
|
859
|
+
if (!request || typeof request !== "object") return void 0;
|
|
860
|
+
const headers = {};
|
|
861
|
+
for (const [k, v] of Object.entries(request.headers ?? {})) if (!SENSITIVE_HEADER.test(k) && (sendDefaultPii || !IP_HEADER.test(k))) headers[k] = String(v).slice(0, 1e3);
|
|
862
|
+
return {
|
|
863
|
+
...request,
|
|
864
|
+
...typeof request.query_string === "string" ? { query_string: scrubQuery(request.query_string) } : {},
|
|
865
|
+
...typeof request.url === "string" && request.url.includes("?") ? { url: `${request.url.split("?")[0]}?${scrubQuery(request.url.split("?").slice(1).join("?"))}` } : {},
|
|
866
|
+
headers
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/errors/scope.ts
|
|
871
|
+
var MAX_BREADCRUMBS = 100;
|
|
872
|
+
var MAX_TAGS = 50;
|
|
873
|
+
var Scope = class _Scope {
|
|
874
|
+
/** @internal `max`: breadcrumbs kept; `hook`: the client's breadcrumb pipeline (`beforeBreadcrumb`). */
|
|
875
|
+
constructor(max = MAX_BREADCRUMBS, hook) {
|
|
876
|
+
this.max = max;
|
|
877
|
+
this.hook = hook;
|
|
878
|
+
}
|
|
879
|
+
max;
|
|
880
|
+
hook;
|
|
881
|
+
tags = {};
|
|
882
|
+
extra = {};
|
|
883
|
+
contexts = {};
|
|
884
|
+
user = null;
|
|
885
|
+
level;
|
|
886
|
+
fingerprint;
|
|
887
|
+
transaction;
|
|
888
|
+
crumbs = [];
|
|
889
|
+
/** @internal The framework request this scope was opened for (read when an error is captured). */
|
|
890
|
+
request;
|
|
891
|
+
/** A tag (searchable, ≤ 50 per event; keys ≤ 32 and values ≤ 200 characters). `null` removes it. */
|
|
892
|
+
setTag(key, value) {
|
|
893
|
+
if (typeof key !== "string" || !key) return this;
|
|
894
|
+
const k = key.slice(0, 32);
|
|
895
|
+
if (value === null || value === void 0) delete this.tags[k];
|
|
896
|
+
else if (k in this.tags || Object.keys(this.tags).length < MAX_TAGS) this.tags[k] = String(value).slice(0, 200);
|
|
897
|
+
return this;
|
|
898
|
+
}
|
|
899
|
+
setTags(tags) {
|
|
900
|
+
if (tags && typeof tags === "object") for (const [k, v] of Object.entries(tags)) this.setTag(k, v);
|
|
901
|
+
return this;
|
|
902
|
+
}
|
|
903
|
+
/** Arbitrary data shown with the event. `undefined` removes it. */
|
|
904
|
+
setExtra(key, value) {
|
|
905
|
+
if (typeof key !== "string" || !key) return this;
|
|
906
|
+
if (value === void 0) delete this.extra[key];
|
|
907
|
+
else this.extra[key] = value;
|
|
908
|
+
return this;
|
|
909
|
+
}
|
|
910
|
+
setExtras(extras) {
|
|
911
|
+
if (extras && typeof extras === "object") for (const [k, v] of Object.entries(extras)) this.setExtra(k, v);
|
|
912
|
+
return this;
|
|
913
|
+
}
|
|
914
|
+
/** A named context (`{ name: "checkout", cartSize: 3 }`); `null` removes it. */
|
|
915
|
+
setContext(name, context) {
|
|
916
|
+
if (typeof name !== "string" || !name) return this;
|
|
917
|
+
if (context === null || context === void 0 || typeof context !== "object") delete this.contexts[name];
|
|
918
|
+
else this.contexts[name] = { ...context };
|
|
919
|
+
return this;
|
|
920
|
+
}
|
|
921
|
+
/** The user errors are attributed to; `user.id` becomes the event's `userId`. `null` clears it. */
|
|
922
|
+
setUser(user) {
|
|
923
|
+
this.user = user && typeof user === "object" ? { ...user } : null;
|
|
924
|
+
return this;
|
|
925
|
+
}
|
|
926
|
+
/** Overrides the level of events captured in this scope. */
|
|
927
|
+
setLevel(level) {
|
|
928
|
+
this.level = level;
|
|
929
|
+
return this;
|
|
930
|
+
}
|
|
931
|
+
setFingerprint(fingerprint) {
|
|
932
|
+
this.fingerprint = Array.isArray(fingerprint) ? fingerprint.map(String) : void 0;
|
|
933
|
+
return this;
|
|
934
|
+
}
|
|
935
|
+
/** The transaction name (`GET /users/:id`); the request middlewares set it from the matched route. */
|
|
936
|
+
setTransactionName(name) {
|
|
937
|
+
this.transaction = typeof name === "string" && name ? name.slice(0, 200) : void 0;
|
|
938
|
+
return this;
|
|
939
|
+
}
|
|
940
|
+
/** Records a breadcrumb through the client's `beforeBreadcrumb`. */
|
|
941
|
+
addBreadcrumb(crumb) {
|
|
942
|
+
if (this.hook) this.hook(crumb, this);
|
|
943
|
+
else this.pushBreadcrumb(crumb);
|
|
944
|
+
return this;
|
|
945
|
+
}
|
|
946
|
+
/** @internal Adds an already-filtered breadcrumb, dropping the oldest beyond the limit. */
|
|
947
|
+
pushBreadcrumb(crumb) {
|
|
948
|
+
this.crumbs.push(crumb);
|
|
949
|
+
if (this.crumbs.length > this.max) this.crumbs.splice(0, this.crumbs.length - this.max);
|
|
950
|
+
}
|
|
951
|
+
clearBreadcrumbs() {
|
|
952
|
+
this.crumbs = [];
|
|
953
|
+
return this;
|
|
954
|
+
}
|
|
955
|
+
/** Removes everything (tags, extras, contexts, user, breadcrumbs…). */
|
|
956
|
+
clear() {
|
|
957
|
+
this.tags = {};
|
|
958
|
+
this.extra = {};
|
|
959
|
+
this.contexts = {};
|
|
960
|
+
this.user = null;
|
|
961
|
+
this.level = void 0;
|
|
962
|
+
this.fingerprint = void 0;
|
|
963
|
+
this.transaction = void 0;
|
|
964
|
+
this.crumbs = [];
|
|
965
|
+
this.request = void 0;
|
|
966
|
+
return this;
|
|
967
|
+
}
|
|
968
|
+
/** A copy whose changes do not affect this scope. */
|
|
969
|
+
clone() {
|
|
970
|
+
const s = new _Scope(this.max, this.hook);
|
|
971
|
+
s.tags = { ...this.tags };
|
|
972
|
+
s.extra = { ...this.extra };
|
|
973
|
+
s.contexts = { ...this.contexts };
|
|
974
|
+
s.user = this.user ? { ...this.user } : null;
|
|
975
|
+
s.level = this.level;
|
|
976
|
+
s.fingerprint = this.fingerprint;
|
|
977
|
+
s.transaction = this.transaction;
|
|
978
|
+
s.crumbs = this.crumbs.slice();
|
|
979
|
+
s.request = this.request;
|
|
980
|
+
return s;
|
|
981
|
+
}
|
|
982
|
+
/** @internal The scope's data at capture time. */
|
|
983
|
+
data() {
|
|
984
|
+
return {
|
|
985
|
+
tags: { ...this.tags },
|
|
986
|
+
extra: { ...this.extra },
|
|
987
|
+
contexts: { ...this.contexts },
|
|
988
|
+
user: this.user ? { ...this.user } : null,
|
|
989
|
+
level: this.level,
|
|
990
|
+
fingerprint: this.fingerprint,
|
|
991
|
+
transaction: this.transaction,
|
|
992
|
+
breadcrumbs: this.crumbs.slice(),
|
|
993
|
+
request: this.request
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
|
|
998
|
+
// src/errors/monitor.ts
|
|
999
|
+
var LEVELS = ["fatal", "error", "warning", "log", "info", "debug"];
|
|
1000
|
+
var levelOf2 = (v) => typeof v === "string" && LEVELS.includes(v) ? v : v === "warn" ? "warning" : void 0;
|
|
1001
|
+
var KNOWN_CONTEXT_KEYS = /* @__PURE__ */ new Set(["userId", "anonymousId", "accountId", "tags", "extra", "level", "fingerprint", "contexts", "request", "user", "mechanism", "transaction"]);
|
|
1002
|
+
var idString = (v) => typeof v === "string" && v.trim() ? v.slice(0, 256) : typeof v === "number" && Number.isSafeInteger(v) ? String(v) : void 0;
|
|
1003
|
+
function statusOf(error) {
|
|
1004
|
+
if (!error || typeof error !== "object") return void 0;
|
|
1005
|
+
const e = error;
|
|
1006
|
+
for (const v of [e.status, e.statusCode, e.status_code, e.output?.statusCode]) {
|
|
1007
|
+
const n = typeof v === "string" ? Number(v) : v;
|
|
1008
|
+
if (typeof n === "number" && Number.isInteger(n) && n >= 100 && n < 600) return n;
|
|
1009
|
+
}
|
|
1010
|
+
return void 0;
|
|
1011
|
+
}
|
|
1012
|
+
var defaultShouldHandle = (error) => {
|
|
1013
|
+
const status = statusOf(error);
|
|
1014
|
+
return status === void 0 || status >= 500;
|
|
1015
|
+
};
|
|
1016
|
+
var ErrorMonitor = class {
|
|
1017
|
+
constructor(host, options) {
|
|
1018
|
+
this.host = host;
|
|
1019
|
+
const o = options && typeof options === "object" ? options : {};
|
|
1020
|
+
this.options = o;
|
|
1021
|
+
const max = typeof o.maxBreadcrumbs === "number" && o.maxBreadcrumbs >= 0 ? Math.min(MAX_BREADCRUMBS, Math.floor(o.maxBreadcrumbs)) : MAX_BREADCRUMBS;
|
|
1022
|
+
this.global = new Scope(max, (crumb, scope) => this.record(crumb, void 0, scope));
|
|
1023
|
+
const entry = typeof process !== "undefined" ? process.argv?.[1] : void 0;
|
|
1024
|
+
this.frameOptions = { root: entry ? (0, import_node_path3.dirname)(entry) : safeCwd(), inAppExclude: Array.isArray(o.inAppExclude) ? o.inAppExclude : void 0 };
|
|
1025
|
+
this.sampleRate = typeof o.sampleRate === "number" && Number.isFinite(o.sampleRate) ? Math.min(1, Math.max(0, o.sampleRate)) : 1;
|
|
1026
|
+
this.contextLines = typeof o.contextLines === "number" && Number.isFinite(o.contextLines) ? Math.max(0, Math.floor(o.contextLines)) : 5;
|
|
1027
|
+
this.pii = o.sendDefaultPii === true;
|
|
1028
|
+
this.release = (typeof o.release === "string" && o.release.trim() ? o.release.trim() : host.appVersion)?.slice(0, 200);
|
|
1029
|
+
let serverName;
|
|
1030
|
+
try {
|
|
1031
|
+
serverName = typeof o.serverName === "string" && o.serverName ? o.serverName : os2.hostname();
|
|
1032
|
+
} catch {
|
|
1033
|
+
serverName = void 0;
|
|
1034
|
+
}
|
|
1035
|
+
this.serverName = serverName?.slice(0, 200);
|
|
1036
|
+
if (options === false) return;
|
|
1037
|
+
const levels = o.captureConsole === false ? [] : Array.isArray(o.captureConsole) ? o.captureConsole : ["warn", "error"];
|
|
1038
|
+
if (levels.length) this.cleanups.push(instrumentConsole(levels, (crumb, hint) => this.record(crumb, hint, this.current())));
|
|
1039
|
+
if (o.captureHttp !== false) {
|
|
1040
|
+
const own = originOf(host.apiUrl);
|
|
1041
|
+
this.cleanups.push(
|
|
1042
|
+
instrumentHttp(
|
|
1043
|
+
(crumb, hint, target) => this.record(crumb, hint, target instanceof Scope ? target : this.global),
|
|
1044
|
+
() => this.current(),
|
|
1045
|
+
(url) => !!own && originOf(url) === own
|
|
1046
|
+
)
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
host;
|
|
1051
|
+
global;
|
|
1052
|
+
als = new import_node_async_hooks.AsyncLocalStorage();
|
|
1053
|
+
requestScopes = /* @__PURE__ */ new WeakMap();
|
|
1054
|
+
/** Errors already reported by an automatic hook (an error passing through two middlewares, or a middleware and the process handler). */
|
|
1055
|
+
captured = /* @__PURE__ */ new WeakSet();
|
|
1056
|
+
bound = /* @__PURE__ */ new WeakSet();
|
|
1057
|
+
pending = /* @__PURE__ */ new Set();
|
|
1058
|
+
cleanups = [];
|
|
1059
|
+
options;
|
|
1060
|
+
frameOptions;
|
|
1061
|
+
sampleRate;
|
|
1062
|
+
contextLines;
|
|
1063
|
+
pii;
|
|
1064
|
+
release;
|
|
1065
|
+
serverName;
|
|
1066
|
+
// --- scopes --------------------------------------------------------------------------------
|
|
1067
|
+
/** The scope of the running request / `withScope()` callback, else the process-wide one. */
|
|
1068
|
+
current() {
|
|
1069
|
+
return this.als.getStore() ?? this.global;
|
|
1070
|
+
}
|
|
1071
|
+
withScope(fn) {
|
|
1072
|
+
const scope = this.current().clone();
|
|
1073
|
+
return this.als.run(scope, () => fn(scope));
|
|
1074
|
+
}
|
|
1075
|
+
addBreadcrumb(crumb, hint) {
|
|
1076
|
+
this.record(crumb, hint, this.current());
|
|
1077
|
+
}
|
|
1078
|
+
/** Validates a breadcrumb, runs `beforeBreadcrumb` and adds it to `scope`. */
|
|
1079
|
+
record(crumb, hint, scope) {
|
|
1080
|
+
try {
|
|
1081
|
+
if (!crumb || typeof crumb !== "object") return;
|
|
1082
|
+
let c = {
|
|
1083
|
+
timestamp: typeof crumb.timestamp === "number" && Number.isFinite(crumb.timestamp) ? crumb.timestamp : Date.now() / 1e3,
|
|
1084
|
+
...typeof crumb.type === "string" && crumb.type ? { type: crumb.type.slice(0, 40) } : {},
|
|
1085
|
+
...typeof crumb.category === "string" && crumb.category ? { category: crumb.category.slice(0, 100) } : {},
|
|
1086
|
+
...levelOf2(crumb.level) ? { level: levelOf2(crumb.level) } : {},
|
|
1087
|
+
...typeof crumb.message === "string" ? { message: crumb.message.slice(0, 1e3) } : crumb.message !== void 0 ? { message: String(crumb.message).slice(0, 1e3) } : {}
|
|
1088
|
+
};
|
|
1089
|
+
const data = normalizeObject(crumb.data, 3);
|
|
1090
|
+
if (data) c.data = data;
|
|
1091
|
+
const hook = this.options.beforeBreadcrumb;
|
|
1092
|
+
if (typeof hook === "function") {
|
|
1093
|
+
let out = c;
|
|
1094
|
+
try {
|
|
1095
|
+
out = hook(c, hint);
|
|
1096
|
+
} catch {
|
|
1097
|
+
out = c;
|
|
1098
|
+
}
|
|
1099
|
+
if (out === null) return;
|
|
1100
|
+
if (out && typeof out === "object" && out !== c) c = { ...out, ...out.data ? { data: normalizeObject(out.data, 3) } : {} };
|
|
1101
|
+
}
|
|
1102
|
+
scope.pushBreadcrumb(c);
|
|
1103
|
+
} catch {
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
// --- capture -------------------------------------------------------------------------------
|
|
1107
|
+
captureException(error, context) {
|
|
1108
|
+
const m = context?.mechanism;
|
|
1109
|
+
return this.capture({ error }, context, { type: m?.type ? String(m.type).slice(0, 60) : "generic", handled: m?.handled ?? true, level: "error", source: "manual" });
|
|
1110
|
+
}
|
|
1111
|
+
captureMessage(message, level, context) {
|
|
1112
|
+
const ctx = level && typeof level === "object" ? level : context;
|
|
1113
|
+
const lvl = typeof level === "string" ? levelOf2(level) : void 0;
|
|
1114
|
+
return this.capture({ message: typeof message === "string" ? message : String(message) }, ctx, { type: "generic", handled: true, level: lvl ?? "info", source: "manual" });
|
|
1115
|
+
}
|
|
1116
|
+
/** An error caught by an automatic hook (process handler, middleware); skipped when already reported. */
|
|
1117
|
+
captureAuto(error, kind) {
|
|
1118
|
+
if (error && typeof error === "object") {
|
|
1119
|
+
if (this.captured.has(error)) return void 0;
|
|
1120
|
+
}
|
|
1121
|
+
return this.capture({ error }, kind.request ? { request: kind.request } : void 0, {
|
|
1122
|
+
type: kind.mechanism,
|
|
1123
|
+
handled: false,
|
|
1124
|
+
level: kind.level ?? "error",
|
|
1125
|
+
source: kind.mechanism,
|
|
1126
|
+
fallbackType: kind.fallbackType
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
capture(input, context, mech) {
|
|
1130
|
+
const eventId = newEventId();
|
|
1131
|
+
try {
|
|
1132
|
+
if (!this.host.accepting()) {
|
|
1133
|
+
this.host.report(`${"error" in input ? "captureException" : "captureMessage"}() was called after shutdown(); the event was dropped`, void 0, "closed");
|
|
1134
|
+
return eventId;
|
|
1135
|
+
}
|
|
1136
|
+
const ctx = context && typeof context === "object" && !Array.isArray(context) ? context : {};
|
|
1137
|
+
const scope = (ctx.request && typeof ctx.request === "object" ? this.requestScopes.get(ctx.request) : void 0) ?? this.current();
|
|
1138
|
+
const data = scope.data();
|
|
1139
|
+
const values = "error" in input ? exceptionChain(input.error, { type: mech.type, handled: mech.handled }, this.frameOptions, mech.fallbackType) : void 0;
|
|
1140
|
+
const main = values?.[values.length - 1];
|
|
1141
|
+
if (this.ignored(main?.type, main ? main.value : input.message)) return eventId;
|
|
1142
|
+
if (this.sampleRate < 1 && Math.random() >= this.sampleRate) return eventId;
|
|
1143
|
+
if ("error" in input && input.error && typeof input.error === "object") this.captured.add(input.error);
|
|
1144
|
+
const requestSource = ctx.request ?? data.request;
|
|
1145
|
+
const req = requestSource ? requestDataOf(requestSource, this.pii) : void 0;
|
|
1146
|
+
const user = { ...data.user ?? {}, ...ctx.user && typeof ctx.user === "object" ? ctx.user : {} };
|
|
1147
|
+
if (idString(ctx.userId)) user.id = idString(ctx.userId);
|
|
1148
|
+
if (req?.ip && !user.ip_address) user.ip_address = req.ip;
|
|
1149
|
+
const tags = new Scope().setTags(data.tags).setTags(ctx.tags ?? {}).data().tags;
|
|
1150
|
+
const extra = { ...data.extra, ...ctx.extra && typeof ctx.extra === "object" ? ctx.extra : {} };
|
|
1151
|
+
const contexts = { ...processContexts(), ...data.contexts };
|
|
1152
|
+
for (const [k, v] of Object.entries(ctx.contexts ?? {})) {
|
|
1153
|
+
if (v && typeof v === "object") contexts[k] = v;
|
|
1154
|
+
else delete contexts[k];
|
|
1155
|
+
}
|
|
1156
|
+
const fingerprint = Array.isArray(ctx.fingerprint) ? ctx.fingerprint.map(String) : data.fingerprint;
|
|
1157
|
+
const transaction = (typeof ctx.transaction === "string" && ctx.transaction ? ctx.transaction.slice(0, 200) : void 0) ?? data.transaction ?? transactionOf(requestSource);
|
|
1158
|
+
const level = levelOf2(ctx.level) ?? data.level ?? mech.level;
|
|
1159
|
+
const event = {
|
|
1160
|
+
event_id: eventId,
|
|
1161
|
+
timestamp: Date.now() / 1e3,
|
|
1162
|
+
platform: "node",
|
|
1163
|
+
level,
|
|
1164
|
+
...this.release ? { release: this.release } : {},
|
|
1165
|
+
environment: this.host.environment,
|
|
1166
|
+
...this.serverName ? { server_name: this.serverName } : {},
|
|
1167
|
+
...transaction ? { transaction } : {},
|
|
1168
|
+
..."message" in input ? { logentry: { formatted: input.message.slice(0, 8e3) } } : {},
|
|
1169
|
+
...values ? { exception: { values } } : {},
|
|
1170
|
+
...data.breadcrumbs.length ? { breadcrumbs: { values: data.breadcrumbs } } : {},
|
|
1171
|
+
contexts,
|
|
1172
|
+
...Object.keys(tags).length ? { tags } : {},
|
|
1173
|
+
extra,
|
|
1174
|
+
user,
|
|
1175
|
+
...fingerprint?.length ? { fingerprint } : {},
|
|
1176
|
+
...req ? { request: req.request } : {},
|
|
1177
|
+
sdk: { ...this.host.sdk }
|
|
1178
|
+
};
|
|
1179
|
+
this.scrub(event);
|
|
1180
|
+
const properties = {};
|
|
1181
|
+
for (const [k, v] of Object.entries(ctx)) if (!KNOWN_CONTEXT_KEYS.has(k)) properties[k] = v;
|
|
1182
|
+
const ids = { anonymousId: idString(ctx.anonymousId), accountId: idString(ctx.accountId) };
|
|
1183
|
+
const hint = { originalException: "error" in input ? input.error : input.message, event_id: eventId };
|
|
1184
|
+
const finish = () => this.finish(event, hint, mech.source, properties, ids);
|
|
1185
|
+
const frames = values?.flatMap((v) => v.stacktrace?.frames ?? []) ?? [];
|
|
1186
|
+
if (this.contextLines > 0 && wantsContext(frames)) this.track(addContextLines(frames, this.contextLines).then(finish, finish));
|
|
1187
|
+
else {
|
|
1188
|
+
const done = finish();
|
|
1189
|
+
if (done) this.track(done);
|
|
1190
|
+
}
|
|
1191
|
+
} catch (e) {
|
|
1192
|
+
this.host.report(`${"error" in input ? "captureException" : "captureMessage"}() failed: ${e instanceof Error ? e.message : String(e)}`, e);
|
|
1193
|
+
}
|
|
1194
|
+
return eventId;
|
|
1195
|
+
}
|
|
1196
|
+
ignored(type2, message) {
|
|
1197
|
+
const patterns = this.options.ignoreErrors;
|
|
1198
|
+
if (!Array.isArray(patterns) || patterns.length === 0) return false;
|
|
1199
|
+
const candidates = [type2 && message !== void 0 ? `${type2}: ${message}` : void 0, message].filter((s) => typeof s === "string");
|
|
1200
|
+
return patterns.some(
|
|
1201
|
+
(p) => candidates.some((s) => {
|
|
1202
|
+
if (typeof p === "string") return !!p && s.includes(p);
|
|
1203
|
+
if (!(p instanceof RegExp)) return false;
|
|
1204
|
+
p.lastIndex = 0;
|
|
1205
|
+
return p.test(s);
|
|
1206
|
+
})
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
/** Makes user-supplied parts of an event JSON-safe and credential-free (again after `beforeSend`). */
|
|
1210
|
+
scrub(event) {
|
|
1211
|
+
event.extra = normalizeObject(event.extra, 4) ?? {};
|
|
1212
|
+
const contexts = {};
|
|
1213
|
+
for (const [k, v] of Object.entries(event.contexts ?? {}).slice(0, 50)) {
|
|
1214
|
+
const c = normalizeObject(v, 4);
|
|
1215
|
+
if (c) contexts[k] = c;
|
|
1216
|
+
}
|
|
1217
|
+
event.contexts = contexts;
|
|
1218
|
+
const user = normalizeObject(event.user, 3);
|
|
1219
|
+
if (user && !this.pii && user.ip_address === "{{auto}}") delete user.ip_address;
|
|
1220
|
+
if (user && Object.keys(user).length) event.user = user;
|
|
1221
|
+
else delete event.user;
|
|
1222
|
+
if (!Object.keys(event.extra).length) delete event.extra;
|
|
1223
|
+
if (event.tags) {
|
|
1224
|
+
const tags = {};
|
|
1225
|
+
for (const [k, v] of Object.entries(event.tags).slice(0, MAX_TAGS)) if (v !== null && v !== void 0) tags[String(k).slice(0, 32)] = String(v).slice(0, 200);
|
|
1226
|
+
event.tags = tags;
|
|
1227
|
+
}
|
|
1228
|
+
if (event.request) event.request = scrubRequest(event.request, this.pii);
|
|
1229
|
+
if (event.breadcrumbs?.values) event.breadcrumbs = { values: event.breadcrumbs.values.slice(-MAX_BREADCRUMBS).map((b) => b.data ? { ...b, data: normalizeObject(b.data, 3) } : b) };
|
|
1230
|
+
}
|
|
1231
|
+
/** `beforeSend`, then the message: properties ≤ 8 KB, detail ≤ 64 KB. May return a promise (async `beforeSend`). */
|
|
1232
|
+
finish(event, hint, source, properties, ids) {
|
|
1233
|
+
const send = (result2) => {
|
|
1234
|
+
if (result2 === null) return;
|
|
1235
|
+
const final = result2 && typeof result2 === "object" ? result2 : event;
|
|
1236
|
+
this.scrub(final);
|
|
1237
|
+
this.send(final, source, properties, ids);
|
|
1238
|
+
};
|
|
1239
|
+
const hook = this.options.beforeSend;
|
|
1240
|
+
if (typeof hook !== "function") return this.send(event, source, properties, ids);
|
|
1241
|
+
let result;
|
|
1242
|
+
try {
|
|
1243
|
+
result = hook(event, hint);
|
|
1244
|
+
} catch {
|
|
1245
|
+
return send(event);
|
|
1246
|
+
}
|
|
1247
|
+
if (result && typeof result.then === "function") return result.then(send, () => send(event));
|
|
1248
|
+
return send(result);
|
|
1249
|
+
}
|
|
1250
|
+
send(event, source, extraProperties, ids) {
|
|
1251
|
+
try {
|
|
1252
|
+
const properties = propertiesOf(event, source, this.release, extraProperties);
|
|
1253
|
+
const userId = idString(event.user?.id);
|
|
1254
|
+
const timestamp = new Date(typeof event.timestamp === "number" && Number.isFinite(event.timestamp) ? event.timestamp * 1e3 : Date.now()).toISOString();
|
|
1255
|
+
let safe2;
|
|
1256
|
+
try {
|
|
1257
|
+
safe2 = JSON.parse(JSON.stringify(event));
|
|
1258
|
+
} catch {
|
|
1259
|
+
safe2 = normalize(event, 12);
|
|
1260
|
+
}
|
|
1261
|
+
const detail = trimDetail(safe2);
|
|
1262
|
+
const message = {
|
|
1263
|
+
type: "exception",
|
|
1264
|
+
messageId: event.event_id,
|
|
1265
|
+
timestamp,
|
|
1266
|
+
...userId ? { userId } : {},
|
|
1267
|
+
...ids.anonymousId ? { anonymousId: ids.anonymousId } : {},
|
|
1268
|
+
...ids.accountId ? { accountId: ids.accountId } : {},
|
|
1269
|
+
...this.release ? { appVersion: this.release } : {},
|
|
1270
|
+
properties,
|
|
1271
|
+
...detail ? { detail } : {}
|
|
1272
|
+
};
|
|
1273
|
+
this.host.push(message);
|
|
1274
|
+
} catch (e) {
|
|
1275
|
+
this.host.report(`Could not build the exception event: ${e instanceof Error ? e.message : String(e)}`, e);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
track(p) {
|
|
1279
|
+
const tracked = p.catch((e) => this.host.report(`Could not build the exception event: ${e instanceof Error ? e.message : String(e)}`, e)).finally(() => {
|
|
1280
|
+
this.pending.delete(tracked);
|
|
1281
|
+
});
|
|
1282
|
+
this.pending.add(tracked);
|
|
1283
|
+
}
|
|
1284
|
+
/** Resolves once every event being prepared (source lines, async `beforeSend`) is queued; `null` when none is. */
|
|
1285
|
+
idle() {
|
|
1286
|
+
if (this.pending.size === 0) return null;
|
|
1287
|
+
return Promise.all([...this.pending]).then(() => this.idle() ?? void 0);
|
|
1288
|
+
}
|
|
1289
|
+
// --- process and framework hooks ----------------------------------------------------------
|
|
1290
|
+
installProcessHandlers(options) {
|
|
1291
|
+
try {
|
|
1292
|
+
const off = installProcessHandlers(
|
|
1293
|
+
{
|
|
1294
|
+
capture: (error, kind) => void this.captureAuto(error, { mechanism: kind.mechanism, level: kind.level, fallbackType: kind.fallbackType }),
|
|
1295
|
+
flush: () => this.host.flush()
|
|
1296
|
+
},
|
|
1297
|
+
options
|
|
1298
|
+
);
|
|
1299
|
+
this.cleanups.push(off);
|
|
1300
|
+
return off;
|
|
1301
|
+
} catch (e) {
|
|
1302
|
+
this.host.report(`installProcessHandlers() failed: ${e instanceof Error ? e.message : String(e)}`, e);
|
|
1303
|
+
return () => {
|
|
1304
|
+
};
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
/** Runs `fn` in a new scope for `request`, with the request's events bound to it (body parsing, `finish`). */
|
|
1308
|
+
runRequest(request, emitters, fn) {
|
|
1309
|
+
if (!request || typeof request !== "object") return fn();
|
|
1310
|
+
const scope = this.current().clone();
|
|
1311
|
+
scope.request = request;
|
|
1312
|
+
scope.clearBreadcrumbs();
|
|
1313
|
+
this.requestScopes.set(request, scope);
|
|
1314
|
+
return this.als.run(scope, () => {
|
|
1315
|
+
for (const e of emitters) this.bindEmitter(e);
|
|
1316
|
+
return fn();
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Listeners of a request's / response's events (`data`, `end`, `finish`) run in the async context
|
|
1321
|
+
* of the server's socket, not of the middleware: bind `emit` to the request scope, as domains did.
|
|
1322
|
+
*/
|
|
1323
|
+
bindEmitter(emitter) {
|
|
1324
|
+
if (!emitter || typeof emitter !== "object" || this.bound.has(emitter)) return;
|
|
1325
|
+
const e = emitter;
|
|
1326
|
+
if (typeof e.emit !== "function") return;
|
|
1327
|
+
try {
|
|
1328
|
+
e.emit = import_node_async_hooks.AsyncResource.bind(e.emit, "GuidelyRequest", emitter);
|
|
1329
|
+
this.bound.add(emitter);
|
|
1330
|
+
} catch {
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
captureRequestError(error, request, mechanism, options) {
|
|
1334
|
+
try {
|
|
1335
|
+
const should = typeof options?.shouldHandleError === "function" ? options.shouldHandleError : defaultShouldHandle;
|
|
1336
|
+
if (should(error)) this.captureAuto(error, { mechanism, request });
|
|
1337
|
+
} catch {
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
expressRequestHandler() {
|
|
1341
|
+
return (req, res, next) => this.runRequest(req, [req, res], () => next());
|
|
1342
|
+
}
|
|
1343
|
+
expressErrorHandler(options) {
|
|
1344
|
+
return (error, req, _res, next) => {
|
|
1345
|
+
this.captureRequestError(error, req, "express", options);
|
|
1346
|
+
next(error);
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
fastifyErrorHook(options) {
|
|
1350
|
+
return (request, _reply, error, done) => {
|
|
1351
|
+
this.captureRequestError(error, request, "fastify", options);
|
|
1352
|
+
done?.();
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
fastifyPlugin(options) {
|
|
1356
|
+
const plugin = (fastify, _opts, done) => {
|
|
1357
|
+
fastify.addHook("onRequest", (request, reply, next) => this.runRequest(request, [request?.raw, reply?.raw], () => next()));
|
|
1358
|
+
fastify.addHook("preHandler", (request, _reply, next) => {
|
|
1359
|
+
const scope = request && typeof request === "object" ? this.requestScopes.get(request) : void 0;
|
|
1360
|
+
if (scope) this.als.run(scope, () => next());
|
|
1361
|
+
else next();
|
|
1362
|
+
});
|
|
1363
|
+
fastify.addHook("onError", this.fastifyErrorHook(options));
|
|
1364
|
+
done?.();
|
|
1365
|
+
};
|
|
1366
|
+
Object.assign(plugin, { [/* @__PURE__ */ Symbol.for("skip-override")]: true, [/* @__PURE__ */ Symbol.for("fastify.display-name")]: "@guidelytech/node" });
|
|
1367
|
+
return plugin;
|
|
1368
|
+
}
|
|
1369
|
+
koaMiddleware(options) {
|
|
1370
|
+
return (ctx, next) => this.runRequest(ctx, [ctx?.req, ctx?.res], async () => {
|
|
1371
|
+
try {
|
|
1372
|
+
return await next();
|
|
1373
|
+
} catch (error) {
|
|
1374
|
+
this.captureRequestError(error, ctx, "koa", options);
|
|
1375
|
+
throw error;
|
|
1376
|
+
}
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
/** Stops the automatic breadcrumbs and process handlers. */
|
|
1380
|
+
close() {
|
|
1381
|
+
for (const off of this.cleanups.splice(0)) {
|
|
1382
|
+
try {
|
|
1383
|
+
off();
|
|
1384
|
+
} catch {
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
function originOf(url) {
|
|
1390
|
+
try {
|
|
1391
|
+
return new URL(url).origin;
|
|
1392
|
+
} catch {
|
|
1393
|
+
return void 0;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
function safeCwd() {
|
|
1397
|
+
try {
|
|
1398
|
+
return process.cwd();
|
|
1399
|
+
} catch {
|
|
1400
|
+
return void 0;
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
148
1404
|
// src/client.ts
|
|
149
1405
|
var SDK_NAME = "guidely-node";
|
|
150
|
-
var SDK_VERSION = "0.
|
|
1406
|
+
var SDK_VERSION = "0.17.0";
|
|
151
1407
|
var DEFAULT_API_URL = "https://guidelytech.com";
|
|
152
1408
|
var BATCH_PATH = "/api/v1/sdk/server/batch";
|
|
153
|
-
var
|
|
1409
|
+
var ENV_KEY = /^[a-z][a-z0-9-]{0,29}$/;
|
|
154
1410
|
var GuidelyError = class extends Error {
|
|
155
1411
|
constructor(code, message, count = 1, status, options) {
|
|
156
1412
|
super(message, options);
|
|
@@ -175,6 +1431,7 @@ var Guidely = class {
|
|
|
175
1431
|
timeoutMs;
|
|
176
1432
|
onError;
|
|
177
1433
|
fetch;
|
|
1434
|
+
appVersion;
|
|
178
1435
|
sdkJson = JSON.stringify({ name: SDK_NAME, version: SDK_VERSION });
|
|
179
1436
|
queue = [];
|
|
180
1437
|
droppedSinceReport = 0;
|
|
@@ -189,14 +1446,17 @@ var Guidely = class {
|
|
|
189
1446
|
shutdownPromise = null;
|
|
190
1447
|
exitDrain = null;
|
|
191
1448
|
exitListener = null;
|
|
1449
|
+
/** Set once `shutdown()` has finished: events still being prepared then are dropped, not sent. */
|
|
1450
|
+
finished = false;
|
|
1451
|
+
monitor;
|
|
192
1452
|
constructor(options) {
|
|
193
1453
|
const o = options ?? {};
|
|
194
1454
|
const key = typeof o.secretKey === "string" ? o.secretKey.trim() : "";
|
|
195
1455
|
if (!key.startsWith("gly_sk_")) {
|
|
196
1456
|
throw new TypeError("Guidely: secretKey must be a secret API key (gly_sk_\u2026) with the events:write scope");
|
|
197
1457
|
}
|
|
198
|
-
if (!
|
|
199
|
-
throw new TypeError('Guidely: environment must be "development", "staging" or "
|
|
1458
|
+
if (typeof o.environment !== "string" || !ENV_KEY.test(o.environment)) {
|
|
1459
|
+
throw new TypeError('Guidely: environment must be an environment key: "development", "staging", "production" or one you added (e.g. "qa"), as shown in Settings \u2192 Environments');
|
|
200
1460
|
}
|
|
201
1461
|
const fetchImpl = o.fetch ?? (typeof globalThis.fetch === "function" ? globalThis.fetch.bind(globalThis) : void 0);
|
|
202
1462
|
if (!fetchImpl) throw new TypeError("Guidely: no global fetch (Node 18+ is required), pass the fetch option");
|
|
@@ -216,6 +1476,20 @@ var Guidely = class {
|
|
|
216
1476
|
this.shutdownTimeoutMs = int(o.shutdownTimeoutMs, 1e4, 0);
|
|
217
1477
|
this.timeoutMs = int(o.timeoutMs, 1e4, 1);
|
|
218
1478
|
this.onError = o.onError ?? ((e) => console.warn(`[${SDK_NAME}] ${e.message}`));
|
|
1479
|
+
this.appVersion = typeof o.appVersion === "string" && o.appVersion.trim() ? o.appVersion.trim() : void 0;
|
|
1480
|
+
this.monitor = new ErrorMonitor(
|
|
1481
|
+
{
|
|
1482
|
+
environment: o.environment,
|
|
1483
|
+
appVersion: this.appVersion,
|
|
1484
|
+
apiUrl,
|
|
1485
|
+
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
|
1486
|
+
push: (message) => this.enqueueException(message),
|
|
1487
|
+
accepting: () => !this.closed,
|
|
1488
|
+
report: (message, cause, code = "invalid_message") => this.report(new GuidelyError(code, message, 1, void 0, { cause })),
|
|
1489
|
+
flush: () => this.flush()
|
|
1490
|
+
},
|
|
1491
|
+
o.errors
|
|
1492
|
+
);
|
|
219
1493
|
if (o.handleExit !== false && typeof process !== "undefined" && typeof process.on === "function") {
|
|
220
1494
|
this.exitListener = () => this.onBeforeExit();
|
|
221
1495
|
process.on("beforeExit", this.exitListener);
|
|
@@ -227,58 +1501,166 @@ var Guidely = class {
|
|
|
227
1501
|
}
|
|
228
1502
|
/** What a user (or anonymous visitor) did. */
|
|
229
1503
|
track(message) {
|
|
230
|
-
this.
|
|
1504
|
+
const withVersion = this.appVersion && message && typeof message === "object" && (message.appVersion === void 0 || message.appVersion === null);
|
|
1505
|
+
this.enqueue("track", withVersion ? { ...message, appVersion: this.appVersion } : message);
|
|
231
1506
|
}
|
|
232
1507
|
/** Account traits, and optionally adds `userId` to the account. */
|
|
233
1508
|
group(message) {
|
|
234
1509
|
this.enqueue("group", message);
|
|
235
1510
|
}
|
|
1511
|
+
// --- errors ----------------------------------------------------------------------------------
|
|
1512
|
+
/**
|
|
1513
|
+
* Reports an error as an `exception` event (grouped into an issue), with the current scope's tags,
|
|
1514
|
+
* extras, contexts, user and breadcrumbs. Returns the event id. Never throws.
|
|
1515
|
+
*
|
|
1516
|
+
* ```ts
|
|
1517
|
+
* guidely.captureException(err, { userId: "u_42", tags: { area: "billing" }, extra: { invoiceId } });
|
|
1518
|
+
* ```
|
|
1519
|
+
*/
|
|
1520
|
+
captureException(error, context) {
|
|
1521
|
+
return this.monitor.captureException(error, context);
|
|
1522
|
+
}
|
|
1523
|
+
/** Reports a message (level `info` unless given), grouped by its text. Returns the event id. */
|
|
1524
|
+
captureMessage(message, level, context) {
|
|
1525
|
+
return this.monitor.captureMessage(message, level, context);
|
|
1526
|
+
}
|
|
1527
|
+
/** A tag on every later error of the current scope (`null` removes it). */
|
|
1528
|
+
setTag(key, value) {
|
|
1529
|
+
this.monitor.current().setTag(key, value);
|
|
1530
|
+
}
|
|
1531
|
+
setTags(tags) {
|
|
1532
|
+
this.monitor.current().setTags(tags);
|
|
1533
|
+
}
|
|
1534
|
+
setExtra(key, value) {
|
|
1535
|
+
this.monitor.current().setExtra(key, value);
|
|
1536
|
+
}
|
|
1537
|
+
setExtras(extras) {
|
|
1538
|
+
this.monitor.current().setExtras(extras);
|
|
1539
|
+
}
|
|
1540
|
+
/** A named context on every later error of the current scope (`null` removes it). */
|
|
1541
|
+
setContext(name, context) {
|
|
1542
|
+
this.monitor.current().setContext(name, context);
|
|
1543
|
+
}
|
|
1544
|
+
/** The user later errors of the current scope belong to (`user.id` becomes `userId`); `null` clears it. */
|
|
1545
|
+
setUser(user) {
|
|
1546
|
+
this.monitor.current().setUser(user);
|
|
1547
|
+
}
|
|
1548
|
+
/** Adds a breadcrumb to the current scope (the last 100 ride along with each error). */
|
|
1549
|
+
addBreadcrumb(breadcrumb, hint) {
|
|
1550
|
+
this.monitor.addBreadcrumb(breadcrumb, hint);
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Runs `fn` with a copy of the current scope: tags, user and breadcrumbs set inside (and in
|
|
1554
|
+
* everything it starts, through AsyncLocalStorage) stay there. Returns what `fn` returns.
|
|
1555
|
+
*/
|
|
1556
|
+
withScope(fn) {
|
|
1557
|
+
return this.monitor.withScope(fn);
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1560
|
+
* Reports `uncaughtException` (fatal) and `unhandledRejection`, keeping Node's behaviour: the error
|
|
1561
|
+
* is printed, queued events are sent (at most `flushTimeoutMs`) and the process exits with code 1.
|
|
1562
|
+
* Returns a function that removes the handlers (`shutdown()` removes them too).
|
|
1563
|
+
*/
|
|
1564
|
+
installProcessHandlers(options) {
|
|
1565
|
+
return this.monitor.installProcessHandlers(options);
|
|
1566
|
+
}
|
|
1567
|
+
/** Express: `app.use(guidely.expressRequestHandler())` first, so each request gets its own scope. */
|
|
1568
|
+
expressRequestHandler() {
|
|
1569
|
+
return this.monitor.expressRequestHandler();
|
|
1570
|
+
}
|
|
1571
|
+
/** Express: `app.use(guidely.expressErrorHandler())` after your routes, before other error handlers. */
|
|
1572
|
+
expressErrorHandler(options) {
|
|
1573
|
+
return this.monitor.expressErrorHandler(options);
|
|
1574
|
+
}
|
|
1575
|
+
/** Fastify: `await app.register(guidely.fastifyPlugin())` (request scopes + the `onError` hook). */
|
|
1576
|
+
fastifyPlugin(options) {
|
|
1577
|
+
return this.monitor.fastifyPlugin(options);
|
|
1578
|
+
}
|
|
1579
|
+
/** Fastify: `app.addHook("onError", guidely.fastifyErrorHook())`, when you do not use the plugin. */
|
|
1580
|
+
fastifyErrorHook(options) {
|
|
1581
|
+
return this.monitor.fastifyErrorHook(options);
|
|
1582
|
+
}
|
|
1583
|
+
/** Koa: `app.use(guidely.koaMiddleware())` first: a scope per request; errors are reported and rethrown. */
|
|
1584
|
+
koaMiddleware(options) {
|
|
1585
|
+
return this.monitor.koaMiddleware(options);
|
|
1586
|
+
}
|
|
1587
|
+
// --- delivery ------------------------------------------------------------------------------
|
|
236
1588
|
/** Sends everything queued; resolves once it has been delivered or dropped. Never rejects. */
|
|
237
1589
|
flush() {
|
|
238
|
-
|
|
1590
|
+
const preparing = this.monitor.idle();
|
|
1591
|
+
return preparing ? preparing.then(() => this.startPump(true)) : this.startPump(true);
|
|
239
1592
|
}
|
|
240
1593
|
/**
|
|
241
|
-
* Stops the timer and the
|
|
242
|
-
* are dropped (and reported). Call it before your process exits.
|
|
1594
|
+
* Stops the timer, the exit hook and the error hooks, then flushes, waiting at most
|
|
1595
|
+
* `shutdownTimeoutMs`. Later calls are dropped (and reported). Call it before your process exits.
|
|
1596
|
+
* Never rejects.
|
|
243
1597
|
*/
|
|
244
1598
|
shutdown() {
|
|
245
1599
|
if (this.shutdownPromise) return this.shutdownPromise;
|
|
246
1600
|
this.closed = true;
|
|
247
1601
|
this.clearTimer();
|
|
1602
|
+
this.monitor.close();
|
|
248
1603
|
if (this.exitListener) process.removeListener("beforeExit", this.exitListener);
|
|
249
|
-
this.shutdownPromise = this.drain(this.shutdownTimeoutMs)
|
|
1604
|
+
this.shutdownPromise = this.drain(this.shutdownTimeoutMs).finally(() => {
|
|
1605
|
+
this.finished = true;
|
|
1606
|
+
});
|
|
250
1607
|
return this.shutdownPromise;
|
|
251
1608
|
}
|
|
252
1609
|
// --- queueing ------------------------------------------------------------------------------
|
|
253
|
-
enqueue(
|
|
1610
|
+
enqueue(type2, args) {
|
|
254
1611
|
try {
|
|
255
1612
|
if (this.closed) {
|
|
256
|
-
this.report(new GuidelyError("closed", `${
|
|
1613
|
+
this.report(new GuidelyError("closed", `${type2}() was called after shutdown(); the message was dropped`));
|
|
257
1614
|
return;
|
|
258
1615
|
}
|
|
259
|
-
const message = buildMessage(
|
|
1616
|
+
const message = buildMessage(type2, args);
|
|
260
1617
|
let json;
|
|
261
1618
|
try {
|
|
262
1619
|
json = JSON.stringify(message);
|
|
263
1620
|
} catch (e) {
|
|
264
1621
|
throw new InvalidMessageError(`could not be serialized to JSON (${e instanceof Error ? e.message : String(e)})`);
|
|
265
1622
|
}
|
|
266
|
-
const
|
|
267
|
-
if (
|
|
268
|
-
this.report(new GuidelyError("message_too_large", `${
|
|
1623
|
+
const bytes2 = Buffer.byteLength(json, "utf8");
|
|
1624
|
+
if (bytes2 > LIMITS.maxMessageBytes) {
|
|
1625
|
+
this.report(new GuidelyError("message_too_large", `${type2}() message is ${bytes2} bytes, more than the ${LIMITS.maxMessageBytes}-byte limit; it was dropped`));
|
|
269
1626
|
return;
|
|
270
1627
|
}
|
|
271
|
-
|
|
272
|
-
this.queue.shift();
|
|
273
|
-
this.droppedSinceReport++;
|
|
274
|
-
}
|
|
275
|
-
this.queue.push({ json, bytes });
|
|
276
|
-
if (this.queue.length >= this.flushAt) void this.startPump(false);
|
|
277
|
-
else this.scheduleTimer();
|
|
1628
|
+
this.push(json, bytes2, false);
|
|
278
1629
|
} catch (e) {
|
|
279
|
-
if (e instanceof InvalidMessageError) this.report(new GuidelyError("invalid_message", `Invalid ${
|
|
280
|
-
else this.report(new GuidelyError("invalid_message", `${
|
|
1630
|
+
if (e instanceof InvalidMessageError) this.report(new GuidelyError("invalid_message", `Invalid ${type2}() call dropped: ${e.message}`));
|
|
1631
|
+
else this.report(new GuidelyError("invalid_message", `${type2}() failed: ${e instanceof Error ? e.message : String(e)}`, 1, void 0, { cause: e }));
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Queues a built `exception` message (it may finish preparing after `shutdown()` began, which
|
|
1636
|
+
* still sends it) and starts sending right away: errors should not wait for the flush interval.
|
|
1637
|
+
*/
|
|
1638
|
+
enqueueException(message) {
|
|
1639
|
+
if (this.finished) {
|
|
1640
|
+
this.report(new GuidelyError("closed", "an exception finished preparing after shutdown(); it was dropped"));
|
|
1641
|
+
return;
|
|
1642
|
+
}
|
|
1643
|
+
let json = JSON.stringify(message);
|
|
1644
|
+
let bytes2 = Buffer.byteLength(json, "utf8");
|
|
1645
|
+
if (bytes2 > ERROR_LIMITS.maxMessageBytes && "detail" in message) {
|
|
1646
|
+
json = JSON.stringify({ ...message, detail: void 0 });
|
|
1647
|
+
bytes2 = Buffer.byteLength(json, "utf8");
|
|
1648
|
+
}
|
|
1649
|
+
if (bytes2 > ERROR_LIMITS.maxMessageBytes) {
|
|
1650
|
+
this.report(new GuidelyError("message_too_large", `exception message is ${bytes2} bytes, more than the ${ERROR_LIMITS.maxMessageBytes}-byte limit; it was dropped`));
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
this.push(json, bytes2, true);
|
|
1654
|
+
}
|
|
1655
|
+
push(json, bytes2, prompt) {
|
|
1656
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
1657
|
+
this.queue.shift();
|
|
1658
|
+
this.droppedSinceReport++;
|
|
281
1659
|
}
|
|
1660
|
+
this.queue.push({ json, bytes: bytes2 });
|
|
1661
|
+
if (prompt) void this.startPump(true);
|
|
1662
|
+
else if (this.queue.length >= this.flushAt) void this.startPump(false);
|
|
1663
|
+
else this.scheduleTimer();
|
|
282
1664
|
}
|
|
283
1665
|
scheduleTimer() {
|
|
284
1666
|
if (this.timer !== void 0 || this.closed) return;
|
|
@@ -388,7 +1770,7 @@ var Guidely = class {
|
|
|
388
1770
|
async drain(timeoutMs) {
|
|
389
1771
|
let timer;
|
|
390
1772
|
const timedOut = await Promise.race([
|
|
391
|
-
this.
|
|
1773
|
+
this.flush().then(() => false),
|
|
392
1774
|
new Promise((resolve) => {
|
|
393
1775
|
timer = setTimeout(() => resolve(true), timeoutMs);
|
|
394
1776
|
})
|
|
@@ -407,7 +1789,7 @@ var Guidely = class {
|
|
|
407
1789
|
if (left > 0) this.report(new GuidelyError("delivery_failed", `${left} queued messages were not sent within ${timeoutMs} ms`, left, 0));
|
|
408
1790
|
}
|
|
409
1791
|
onBeforeExit() {
|
|
410
|
-
if (this.closed || this.exitDrain || this.queue.length === 0 && !this.pumping) return;
|
|
1792
|
+
if (this.closed || this.exitDrain || this.queue.length === 0 && !this.pumping && !this.monitor.idle()) return;
|
|
411
1793
|
this.exitDrain = this.drain(this.shutdownTimeoutMs).finally(() => {
|
|
412
1794
|
this.exitDrain = null;
|
|
413
1795
|
});
|
|
@@ -439,6 +1821,8 @@ function failureMessage(status, failure, count, attempts) {
|
|
|
439
1821
|
Guidely,
|
|
440
1822
|
GuidelyError,
|
|
441
1823
|
SDK_NAME,
|
|
442
|
-
SDK_VERSION
|
|
1824
|
+
SDK_VERSION,
|
|
1825
|
+
Scope,
|
|
1826
|
+
parseStack
|
|
443
1827
|
});
|
|
444
1828
|
//# sourceMappingURL=index.cjs.map
|