nuxt-error-tracker 0.1.15 → 0.1.17
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/dist/module.d.mts +9 -0
- package/dist/module.d.ts +9 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -0
- package/dist/runtime/plugin.client.js +95 -4
- package/package.json +1 -1
package/dist/module.d.mts
CHANGED
|
@@ -5,6 +5,15 @@ interface ModuleOptions {
|
|
|
5
5
|
enabled?: boolean;
|
|
6
6
|
/** Wrap window.fetch to capture failed requests (network errors + 5xx). Default true. */
|
|
7
7
|
captureFetch?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* On a failed request (HTTP >= 400 or a network error), attach full request/
|
|
10
|
+
* response detail (method, url, status, timing, headers, bodies) to the fetch
|
|
11
|
+
* breadcrumb's `data` for precise debugging. Sensitive headers (authorization/
|
|
12
|
+
* cookie/…) are redacted; bodies are truncated. Only failed requests carry
|
|
13
|
+
* detail — successful ones stay lightweight. Default true; set false to keep
|
|
14
|
+
* fetch breadcrumbs message-only (e.g. to avoid capturing request payloads).
|
|
15
|
+
*/
|
|
16
|
+
captureFetchDetails?: boolean;
|
|
8
17
|
/** Patch console.error to capture logged-but-not-thrown errors. Default true. */
|
|
9
18
|
captureConsole?: boolean;
|
|
10
19
|
/**
|
package/dist/module.d.ts
CHANGED
|
@@ -5,6 +5,15 @@ interface ModuleOptions {
|
|
|
5
5
|
enabled?: boolean;
|
|
6
6
|
/** Wrap window.fetch to capture failed requests (network errors + 5xx). Default true. */
|
|
7
7
|
captureFetch?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* On a failed request (HTTP >= 400 or a network error), attach full request/
|
|
10
|
+
* response detail (method, url, status, timing, headers, bodies) to the fetch
|
|
11
|
+
* breadcrumb's `data` for precise debugging. Sensitive headers (authorization/
|
|
12
|
+
* cookie/…) are redacted; bodies are truncated. Only failed requests carry
|
|
13
|
+
* detail — successful ones stay lightweight. Default true; set false to keep
|
|
14
|
+
* fetch breadcrumbs message-only (e.g. to avoid capturing request payloads).
|
|
15
|
+
*/
|
|
16
|
+
captureFetchDetails?: boolean;
|
|
8
17
|
/** Patch console.error to capture logged-but-not-thrown errors. Default true. */
|
|
9
18
|
captureConsole?: boolean;
|
|
10
19
|
/**
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -15,6 +15,7 @@ const module = defineNuxtModule({
|
|
|
15
15
|
appVersion: "unknown",
|
|
16
16
|
enabled: true,
|
|
17
17
|
captureFetch: true,
|
|
18
|
+
captureFetchDetails: true,
|
|
18
19
|
captureConsole: true,
|
|
19
20
|
captureInputs: false,
|
|
20
21
|
flushDelay: 2e3,
|
|
@@ -30,6 +31,7 @@ const module = defineNuxtModule({
|
|
|
30
31
|
publicKey: options.publicKey,
|
|
31
32
|
appVersion: options.appVersion ?? "unknown",
|
|
32
33
|
captureFetch: options.captureFetch !== false,
|
|
34
|
+
captureFetchDetails: options.captureFetchDetails !== false,
|
|
33
35
|
captureConsole: options.captureConsole !== false,
|
|
34
36
|
captureInputs: options.captureInputs === true,
|
|
35
37
|
flushDelay: posInt(options.flushDelay, 2e3),
|
|
@@ -116,7 +116,16 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
116
116
|
(event) => capture(event.reason, "promise")
|
|
117
117
|
);
|
|
118
118
|
const formatDataAttrs = (el) => {
|
|
119
|
-
|
|
119
|
+
let node = el;
|
|
120
|
+
let dataset;
|
|
121
|
+
for (let depth = 0; node && depth < 5; depth++) {
|
|
122
|
+
const ds = node.dataset;
|
|
123
|
+
if (ds && Object.keys(ds).length > 0) {
|
|
124
|
+
dataset = ds;
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
node = node.parentElement;
|
|
128
|
+
}
|
|
120
129
|
if (!dataset) return "";
|
|
121
130
|
return Object.entries(dataset).slice(0, 5).map(
|
|
122
131
|
([k, v]) => `data-${k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)}=${(v ?? "").slice(0, 40)}`
|
|
@@ -190,18 +199,92 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
190
199
|
});
|
|
191
200
|
if (config.captureFetch && typeof window.fetch === "function") {
|
|
192
201
|
const originalFetch = window.fetch.bind(window);
|
|
202
|
+
const REDACTED = "[redacted]";
|
|
203
|
+
const SENSITIVE_HEADER = /^(authorization|cookie|set-cookie|proxy-authorization|x-api-key|x-auth-token|x-csrf-token|x-xsrf-token)$/i;
|
|
204
|
+
const MAX_BODY = 2048;
|
|
205
|
+
const MAX_HEADER_VAL = 256;
|
|
206
|
+
const trunc = (s, max) => s.length > max ? `${s.slice(0, max)}\u2026[+${s.length - max}]` : s;
|
|
207
|
+
const headersToObj = (h) => {
|
|
208
|
+
const out = {};
|
|
209
|
+
if (!h) return out;
|
|
210
|
+
try {
|
|
211
|
+
const entries = h instanceof Headers ? [...h.entries()] : Array.isArray(h) ? h : Object.entries(h);
|
|
212
|
+
for (const [k, v] of entries) {
|
|
213
|
+
out[k] = SENSITIVE_HEADER.test(k) ? REDACTED : trunc(String(v), MAX_HEADER_VAL);
|
|
214
|
+
}
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
return out;
|
|
218
|
+
};
|
|
219
|
+
const reqHeaders = (input, init) => {
|
|
220
|
+
const base = input instanceof Request ? headersToObj(input.headers) : {};
|
|
221
|
+
return { ...base, ...headersToObj(init?.headers) };
|
|
222
|
+
};
|
|
223
|
+
const readReqBody = async (input, init) => {
|
|
224
|
+
try {
|
|
225
|
+
const body = init?.body;
|
|
226
|
+
if (body != null) {
|
|
227
|
+
if (typeof body === "string") return trunc(body, MAX_BODY);
|
|
228
|
+
if (body instanceof URLSearchParams)
|
|
229
|
+
return trunc(body.toString(), MAX_BODY);
|
|
230
|
+
if (typeof FormData !== "undefined" && body instanceof FormData)
|
|
231
|
+
return "[FormData]";
|
|
232
|
+
if (typeof Blob !== "undefined" && body instanceof Blob)
|
|
233
|
+
return `[Blob ${body.size}b]`;
|
|
234
|
+
if (body instanceof ArrayBuffer)
|
|
235
|
+
return `[ArrayBuffer ${body.byteLength}b]`;
|
|
236
|
+
return "[stream]";
|
|
237
|
+
}
|
|
238
|
+
if (input instanceof Request) {
|
|
239
|
+
const t = await input.clone().text();
|
|
240
|
+
return t ? trunc(t, MAX_BODY) : void 0;
|
|
241
|
+
}
|
|
242
|
+
} catch {
|
|
243
|
+
}
|
|
244
|
+
return void 0;
|
|
245
|
+
};
|
|
246
|
+
const readResBody = async (res) => {
|
|
247
|
+
try {
|
|
248
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
249
|
+
if (!/json|text|xml|form-urlencoded/i.test(ct))
|
|
250
|
+
return `[${ct || "binary"}]`;
|
|
251
|
+
const len = Number(res.headers.get("content-length") ?? 0);
|
|
252
|
+
if (len > MAX_BODY * 8) return `[too large ${len}b]`;
|
|
253
|
+
const t = await res.clone().text();
|
|
254
|
+
return t ? trunc(t, MAX_BODY) : void 0;
|
|
255
|
+
} catch {
|
|
256
|
+
}
|
|
257
|
+
return void 0;
|
|
258
|
+
};
|
|
193
259
|
window.fetch = async (input, init) => {
|
|
194
260
|
const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
195
261
|
const reqUrl = input instanceof Request ? input.url : String(input);
|
|
196
262
|
const isOwn = reqUrl.startsWith(config.endpoint);
|
|
263
|
+
const startedAt = Date.now();
|
|
197
264
|
try {
|
|
198
265
|
const res = await originalFetch(input, init);
|
|
199
266
|
if (!isOwn) {
|
|
267
|
+
const failed = res.status >= 400;
|
|
268
|
+
let data;
|
|
269
|
+
if (failed && config.captureFetchDetails) {
|
|
270
|
+
data = {
|
|
271
|
+
method,
|
|
272
|
+
url: reqUrl,
|
|
273
|
+
status: res.status,
|
|
274
|
+
statusText: res.statusText,
|
|
275
|
+
durationMs: Date.now() - startedAt,
|
|
276
|
+
requestHeaders: reqHeaders(input, init),
|
|
277
|
+
requestBody: await readReqBody(input, init),
|
|
278
|
+
responseHeaders: headersToObj(res.headers),
|
|
279
|
+
responseBody: await readResBody(res)
|
|
280
|
+
};
|
|
281
|
+
}
|
|
200
282
|
addCrumb({
|
|
201
283
|
type: "fetch",
|
|
202
284
|
category: "http",
|
|
203
|
-
level:
|
|
204
|
-
message: `${method} ${reqUrl} \u2192 ${res.status}
|
|
285
|
+
level: failed ? "error" : "info",
|
|
286
|
+
message: `${method} ${reqUrl} \u2192 ${res.status}`,
|
|
287
|
+
data
|
|
205
288
|
});
|
|
206
289
|
if (res.status >= 500) {
|
|
207
290
|
capture(
|
|
@@ -218,7 +301,15 @@ export default defineNuxtPlugin((nuxtApp) => {
|
|
|
218
301
|
type: "fetch",
|
|
219
302
|
category: "http",
|
|
220
303
|
level: "error",
|
|
221
|
-
message: `${method} ${reqUrl} \u2192 failed: ${reason}
|
|
304
|
+
message: `${method} ${reqUrl} \u2192 failed: ${reason}`,
|
|
305
|
+
data: config.captureFetchDetails ? {
|
|
306
|
+
method,
|
|
307
|
+
url: reqUrl,
|
|
308
|
+
error: reason,
|
|
309
|
+
durationMs: Date.now() - startedAt,
|
|
310
|
+
requestHeaders: reqHeaders(input, init),
|
|
311
|
+
requestBody: await readReqBody(input, init)
|
|
312
|
+
} : void 0
|
|
222
313
|
});
|
|
223
314
|
capture(
|
|
224
315
|
new Error(`Fetch failed ${method} ${reqUrl}: ${reason}`),
|