@tanngo11/log 0.3.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/CHANGELOG.md +48 -0
- package/CONTRACT.md +135 -0
- package/LICENSE +21 -0
- package/README.md +291 -0
- package/dist/bullmq.cjs +108 -0
- package/dist/bullmq.cjs.map +1 -0
- package/dist/bullmq.d.cts +14 -0
- package/dist/bullmq.d.ts +14 -0
- package/dist/bullmq.js +29 -0
- package/dist/bullmq.js.map +1 -0
- package/dist/chunk-BP264YAM.js +72 -0
- package/dist/chunk-BP264YAM.js.map +1 -0
- package/dist/chunk-BVKAY2OK.js +53 -0
- package/dist/chunk-BVKAY2OK.js.map +1 -0
- package/dist/chunk-DO5HNNTP.js +66 -0
- package/dist/chunk-DO5HNNTP.js.map +1 -0
- package/dist/chunk-KD5H5G6Y.js +209 -0
- package/dist/chunk-KD5H5G6Y.js.map +1 -0
- package/dist/chunk-KLBL7QEN.js +29 -0
- package/dist/chunk-KLBL7QEN.js.map +1 -0
- package/dist/chunk-QMC6BWGK.js +203 -0
- package/dist/chunk-QMC6BWGK.js.map +1 -0
- package/dist/chunk-UXKPPF4G.js +41 -0
- package/dist/chunk-UXKPPF4G.js.map +1 -0
- package/dist/express.cjs +316 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.cts +10 -0
- package/dist/express.d.ts +10 -0
- package/dist/express.js +12 -0
- package/dist/express.js.map +1 -0
- package/dist/fastify.cjs +309 -0
- package/dist/fastify.cjs.map +1 -0
- package/dist/fastify.d.cts +10 -0
- package/dist/fastify.d.ts +10 -0
- package/dist/fastify.js +10 -0
- package/dist/fastify.js.map +1 -0
- package/dist/http-BI6ARZlD.d.ts +25 -0
- package/dist/http-BXAiU-rN.d.cts +25 -0
- package/dist/index.cjs +677 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +69 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +214 -0
- package/dist/index.js.map +1 -0
- package/dist/logger-CFMTzjsm.d.cts +47 -0
- package/dist/logger-CFMTzjsm.d.ts +47 -0
- package/dist/nest.cjs +458 -0
- package/dist/nest.cjs.map +1 -0
- package/dist/nest.d.cts +41 -0
- package/dist/nest.d.ts +41 -0
- package/dist/nest.js +107 -0
- package/dist/nest.js.map +1 -0
- package/dist/next-edge.cjs +44 -0
- package/dist/next-edge.cjs.map +1 -0
- package/dist/next-edge.d.cts +7 -0
- package/dist/next-edge.d.ts +7 -0
- package/dist/next-edge.js +16 -0
- package/dist/next-edge.js.map +1 -0
- package/dist/next.cjs +423 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +36 -0
- package/dist/next.d.ts +36 -0
- package/dist/next.js +138 -0
- package/dist/next.js.map +1 -0
- package/dist/prisma.cjs +63 -0
- package/dist/prisma.cjs.map +1 -0
- package/dist/prisma.d.cts +31 -0
- package/dist/prisma.d.ts +31 -0
- package/dist/prisma.js +38 -0
- package/dist/prisma.js.map +1 -0
- package/package.json +138 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// src/context.ts
|
|
2
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3
|
+
|
|
4
|
+
// src/global.ts
|
|
5
|
+
function globalSingleton(name, make) {
|
|
6
|
+
const key = /* @__PURE__ */ Symbol.for(`@tanngo11/log.${name}`);
|
|
7
|
+
const g = globalThis;
|
|
8
|
+
let value = g[key];
|
|
9
|
+
if (value === void 0) {
|
|
10
|
+
value = make();
|
|
11
|
+
g[key] = value;
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/context.ts
|
|
17
|
+
var als = globalSingleton("als", () => new AsyncLocalStorage());
|
|
18
|
+
var STORE_KEY = /* @__PURE__ */ Symbol.for("@tanngo11/log.store");
|
|
19
|
+
function runWithStore(fields, fn) {
|
|
20
|
+
const parent = als.getStore();
|
|
21
|
+
const store = { fields: { ...parent?.fields, ...fields }, parent };
|
|
22
|
+
return als.run(store, () => fn(store));
|
|
23
|
+
}
|
|
24
|
+
function withContext(fields, fn) {
|
|
25
|
+
return runWithStore(fields, () => fn());
|
|
26
|
+
}
|
|
27
|
+
function currentStore() {
|
|
28
|
+
return als.getStore();
|
|
29
|
+
}
|
|
30
|
+
function getContext() {
|
|
31
|
+
return als.getStore()?.fields ?? {};
|
|
32
|
+
}
|
|
33
|
+
function addContext(fields) {
|
|
34
|
+
for (let s = als.getStore(); s; s = s.parent) Object.assign(s.fields, fields);
|
|
35
|
+
}
|
|
36
|
+
function recordErrorIn(store, err) {
|
|
37
|
+
store.error = err;
|
|
38
|
+
for (let s = store.parent; s; s = s.parent) if (s.error === void 0) s.error = err;
|
|
39
|
+
}
|
|
40
|
+
function recordError(err) {
|
|
41
|
+
const store = als.getStore();
|
|
42
|
+
if (store) recordErrorIn(store, err);
|
|
43
|
+
}
|
|
44
|
+
function attachStore(target, store) {
|
|
45
|
+
target[STORE_KEY] = store;
|
|
46
|
+
}
|
|
47
|
+
function storeOf(target) {
|
|
48
|
+
if (typeof target !== "object" || target === null) return void 0;
|
|
49
|
+
return target[STORE_KEY];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/redact.ts
|
|
53
|
+
var DEFAULT_REDACT_KEYS = [
|
|
54
|
+
"password",
|
|
55
|
+
"passwd",
|
|
56
|
+
"pwd",
|
|
57
|
+
"passphrase",
|
|
58
|
+
"secret",
|
|
59
|
+
"client_secret",
|
|
60
|
+
"token",
|
|
61
|
+
"access_token",
|
|
62
|
+
"refresh_token",
|
|
63
|
+
"id_token",
|
|
64
|
+
"auth_token",
|
|
65
|
+
"api_key",
|
|
66
|
+
"apikey",
|
|
67
|
+
"access_key",
|
|
68
|
+
"secret_key",
|
|
69
|
+
"private_key",
|
|
70
|
+
"authorization",
|
|
71
|
+
"cookie",
|
|
72
|
+
"set_cookie",
|
|
73
|
+
"session_id",
|
|
74
|
+
"sessionid",
|
|
75
|
+
"credential",
|
|
76
|
+
"credentials",
|
|
77
|
+
"dsn",
|
|
78
|
+
"connection_string",
|
|
79
|
+
"conn_string",
|
|
80
|
+
"jwt",
|
|
81
|
+
"bearer"
|
|
82
|
+
];
|
|
83
|
+
var REDACT_PATTERNS = {
|
|
84
|
+
/** Vietnamese mobile numbers: 0912345678, +84912345678, 84912345678. */
|
|
85
|
+
phoneVN: /(?<![\d+])(?:\+?84|0)[35789]\d{8}(?!\d)/g,
|
|
86
|
+
/** Visa, Mastercard, Amex, Discover-like card numbers, with optional spaces or dashes. */
|
|
87
|
+
paymentCard: /(?<!\d)(?:4\d{3}|5[1-5]\d{2}|2[2-7]\d{2}|3[47]\d{2}|6(?:011|5\d{2}))(?:[ -]?\d{4}){2}[ -]?\d{1,4}(?!\d)/g,
|
|
88
|
+
/** IPv4 addresses. */
|
|
89
|
+
ipv4: /(?<![\d.])(?:\d{1,3}\.){3}\d{1,3}(?![\d.])/g
|
|
90
|
+
};
|
|
91
|
+
var SAFE_SUFFIXES = ["_count", "_type", "_length", "_expires_at", "_expires_in", "_ttl", "_at", "_ms", "_enabled"];
|
|
92
|
+
var REDACTED = "[REDACTED]";
|
|
93
|
+
function toSnakeCase(key) {
|
|
94
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/[^A-Za-z0-9_]+/g, "_").toLowerCase();
|
|
95
|
+
}
|
|
96
|
+
var URL_CREDENTIALS_RE = /([a-z][a-z0-9+.-]*:\/\/)[^\s/@:"']+:[^\s/@"']+@/gi;
|
|
97
|
+
var BEARER_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi;
|
|
98
|
+
var JWT_RE = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
|
|
99
|
+
var EMAIL_RE = /[A-Za-z0-9._%+-]+@([A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,})/g;
|
|
100
|
+
function globalize(re) {
|
|
101
|
+
return re.flags.includes("g") ? new RegExp(re.source, re.flags) : new RegExp(re.source, `${re.flags}g`);
|
|
102
|
+
}
|
|
103
|
+
function createRedactor(opts = {}) {
|
|
104
|
+
const words = [...DEFAULT_REDACT_KEYS];
|
|
105
|
+
const keyPatterns = [];
|
|
106
|
+
for (const k of opts.keys ?? []) {
|
|
107
|
+
if (typeof k === "string") words.push(toSnakeCase(k));
|
|
108
|
+
else keyPatterns.push(new RegExp(k.source, k.flags.replace("g", "")));
|
|
109
|
+
}
|
|
110
|
+
const valuePatterns = (opts.values ?? []).map(globalize);
|
|
111
|
+
const isSensitive = (key) => {
|
|
112
|
+
const k = toSnakeCase(key);
|
|
113
|
+
if (keyPatterns.some((re) => re.test(k))) return true;
|
|
114
|
+
if (!words.includes(k) && SAFE_SUFFIXES.some((s) => k.endsWith(s))) return false;
|
|
115
|
+
return words.some((w) => k === w || k.startsWith(`${w}_`) || k.endsWith(`_${w}`) || k.includes(`_${w}_`));
|
|
116
|
+
};
|
|
117
|
+
const scrub = (s) => {
|
|
118
|
+
let out = s.replace(URL_CREDENTIALS_RE, `$1${REDACTED}@`).replace(BEARER_RE, `Bearer ${REDACTED}`).replace(JWT_RE, "[REDACTED_JWT]").replace(EMAIL_RE, "***@$1");
|
|
119
|
+
for (const re of valuePatterns) out = out.replace(re, REDACTED);
|
|
120
|
+
return out;
|
|
121
|
+
};
|
|
122
|
+
const json = (value) => {
|
|
123
|
+
if (typeof value === "string") return value;
|
|
124
|
+
try {
|
|
125
|
+
const text = JSON.stringify(value, (key, val) => {
|
|
126
|
+
if (key && val !== null && val !== void 0 && isSensitive(key)) return REDACTED;
|
|
127
|
+
if (typeof val === "bigint") return val.toString();
|
|
128
|
+
if (val instanceof Error) return { name: val.name, message: val.message };
|
|
129
|
+
return val;
|
|
130
|
+
});
|
|
131
|
+
return text ?? String(value);
|
|
132
|
+
} catch {
|
|
133
|
+
return String(value);
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
return { isSensitive, scrub, json };
|
|
137
|
+
}
|
|
138
|
+
var defaultRedactor = createRedactor();
|
|
139
|
+
|
|
140
|
+
// src/error.ts
|
|
141
|
+
var MAX_CAUSE_DEPTH = 5;
|
|
142
|
+
function isErrorLike(v) {
|
|
143
|
+
if (v instanceof Error) return true;
|
|
144
|
+
return typeof v === "object" && v !== null && typeof v.message === "string" && ("stack" in v || "name" in v);
|
|
145
|
+
}
|
|
146
|
+
function errorType(e) {
|
|
147
|
+
if (e.name && e.name !== "Error") return e.name;
|
|
148
|
+
const ctor = e.constructor?.name;
|
|
149
|
+
return ctor && ctor !== "Object" ? ctor : "Error";
|
|
150
|
+
}
|
|
151
|
+
function serializeError(err) {
|
|
152
|
+
if (!isErrorLike(err)) {
|
|
153
|
+
if (typeof err === "string") return { err_type: "string", err_message: err };
|
|
154
|
+
return { err_type: typeof err, err_message: defaultRedactor.json(err) };
|
|
155
|
+
}
|
|
156
|
+
const e = err;
|
|
157
|
+
const out = { err_type: errorType(e), err_message: String(e.message ?? "") };
|
|
158
|
+
if (e.code !== void 0 && e.code !== null) out.err_code = String(e.code);
|
|
159
|
+
if (typeof e.stack === "string") out.err_stack = e.stack;
|
|
160
|
+
const causes = [];
|
|
161
|
+
let cause = e.cause;
|
|
162
|
+
while (cause !== void 0 && cause !== null && causes.length < MAX_CAUSE_DEPTH) {
|
|
163
|
+
if (isErrorLike(cause)) {
|
|
164
|
+
causes.push(`${errorType(cause)}: ${cause.message}`);
|
|
165
|
+
cause = cause.cause;
|
|
166
|
+
} else {
|
|
167
|
+
causes.push(defaultRedactor.json(cause));
|
|
168
|
+
cause = void 0;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (causes.length > 0) out.err_cause = causes.join(" <- ");
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
var logged = globalSingleton("logged", () => /* @__PURE__ */ new WeakSet());
|
|
175
|
+
function markLogged(err) {
|
|
176
|
+
if (typeof err === "object" && err !== null) logged.add(err);
|
|
177
|
+
}
|
|
178
|
+
function isLogged(err) {
|
|
179
|
+
return typeof err === "object" && err !== null && logged.has(err);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export {
|
|
183
|
+
globalSingleton,
|
|
184
|
+
runWithStore,
|
|
185
|
+
withContext,
|
|
186
|
+
currentStore,
|
|
187
|
+
getContext,
|
|
188
|
+
addContext,
|
|
189
|
+
recordErrorIn,
|
|
190
|
+
recordError,
|
|
191
|
+
attachStore,
|
|
192
|
+
storeOf,
|
|
193
|
+
REDACT_PATTERNS,
|
|
194
|
+
REDACTED,
|
|
195
|
+
toSnakeCase,
|
|
196
|
+
createRedactor,
|
|
197
|
+
defaultRedactor,
|
|
198
|
+
isErrorLike,
|
|
199
|
+
serializeError,
|
|
200
|
+
markLogged,
|
|
201
|
+
isLogged
|
|
202
|
+
};
|
|
203
|
+
//# sourceMappingURL=chunk-QMC6BWGK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context.ts","../src/global.ts","../src/redact.ts","../src/error.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { globalSingleton } from \"./global\";\nimport type { Fields } from \"./normalize\";\n\nexport interface ContextStore {\n fields: Fields;\n error?: unknown;\n /** Route template captured while the router still knew it (Express error path). */\n route?: string;\n parent?: ContextStore;\n}\n\nconst als = globalSingleton(\"als\", () => new AsyncLocalStorage<ContextStore>());\n\nexport const STORE_KEY: symbol = Symbol.for(\"@tanngo11/log.store\");\n\nexport function runWithStore<T>(fields: Fields, fn: (store: ContextStore) => T): T {\n const parent = als.getStore();\n const store: ContextStore = { fields: { ...parent?.fields, ...fields }, parent };\n return als.run(store, () => fn(store));\n}\n\nexport function withContext<T>(fields: Fields, fn: () => T): T {\n return runWithStore(fields, () => fn());\n}\n\nexport function currentStore(): ContextStore | undefined {\n return als.getStore();\n}\n\nexport function getContext(): Fields {\n return als.getStore()?.fields ?? {};\n}\n\n// Applies to the current context and every enclosing one, so a field added inside a nested\n// withContext/runJob still reaches the request's http.request line.\nexport function addContext(fields: Fields): void {\n for (let s = als.getStore(); s; s = s.parent) Object.assign(s.fields, fields);\n}\n\n// Sets the error on the store and on enclosing stores that have none yet.\nexport function recordErrorIn(store: ContextStore, err: unknown): void {\n store.error = err;\n for (let s = store.parent; s; s = s.parent) if (s.error === undefined) s.error = err;\n}\n\n// Marks the error that the current request/job summary line should carry. Does not write a line.\nexport function recordError(err: unknown): void {\n const store = als.getStore();\n if (store) recordErrorIn(store, err);\n}\n\nexport function attachStore(target: object, store: ContextStore): void {\n (target as Record<symbol, ContextStore>)[STORE_KEY] = store;\n}\n\nexport function storeOf(target: unknown): ContextStore | undefined {\n if (typeof target !== \"object\" || target === null) return undefined;\n return (target as Record<symbol, ContextStore | undefined>)[STORE_KEY];\n}\n","// Singletons shared by every copy of this package loaded in the process (ESM and CJS builds,\n// or duplicated installs), so context and dedupe state never split.\nexport function globalSingleton<T>(name: string, make: () => T): T {\n const key = Symbol.for(`@tanngo11/log.${name}`);\n const g = globalThis as unknown as Record<symbol, T | undefined>;\n let value = g[key];\n if (value === undefined) {\n value = make();\n g[key] = value;\n }\n return value;\n}\n","// Redaction shared by the logger and the error serializer. The collector redacts again,\n// but it cannot see secrets inside JSON-encoded strings, so the app side must catch them.\n\nexport const DEFAULT_REDACT_KEYS = [\n \"password\", \"passwd\", \"pwd\", \"passphrase\", \"secret\", \"client_secret\",\n \"token\", \"access_token\", \"refresh_token\", \"id_token\", \"auth_token\",\n \"api_key\", \"apikey\", \"access_key\", \"secret_key\", \"private_key\",\n \"authorization\", \"cookie\", \"set_cookie\", \"session_id\", \"sessionid\",\n \"credential\", \"credentials\", \"dsn\", \"connection_string\", \"conn_string\", \"jwt\", \"bearer\",\n];\n\n/** Opt-in value patterns for `redactValues`. Not on by default: they can hit ordinary numbers. */\nexport const REDACT_PATTERNS = {\n /** Vietnamese mobile numbers: 0912345678, +84912345678, 84912345678. */\n phoneVN: /(?<![\\d+])(?:\\+?84|0)[35789]\\d{8}(?!\\d)/g,\n /** Visa, Mastercard, Amex, Discover-like card numbers, with optional spaces or dashes. */\n paymentCard: /(?<!\\d)(?:4\\d{3}|5[1-5]\\d{2}|2[2-7]\\d{2}|3[47]\\d{2}|6(?:011|5\\d{2}))(?:[ -]?\\d{4}){2}[ -]?\\d{1,4}(?!\\d)/g,\n /** IPv4 addresses. */\n ipv4: /(?<![\\d.])(?:\\d{1,3}\\.){3}\\d{1,3}(?![\\d.])/g,\n} as const;\n\n// Keys that only describe a secret (count, type, timestamps, flags) stay readable.\nconst SAFE_SUFFIXES = [\"_count\", \"_type\", \"_length\", \"_expires_at\", \"_expires_in\", \"_ttl\", \"_at\", \"_ms\", \"_enabled\"];\n\nexport const REDACTED = \"[REDACTED]\";\n\nexport function toSnakeCase(key: string): string {\n return key\n .replace(/([a-z0-9])([A-Z])/g, \"$1_$2\")\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1_$2\")\n .replace(/[^A-Za-z0-9_]+/g, \"_\")\n .toLowerCase();\n}\n\nconst URL_CREDENTIALS_RE = /([a-z][a-z0-9+.-]*:\\/\\/)[^\\s/@:\"']+:[^\\s/@\"']+@/gi;\nconst BEARER_RE = /\\bBearer\\s+[A-Za-z0-9._~+/=-]+/gi;\nconst JWT_RE = /\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+/g;\nconst EMAIL_RE = /[A-Za-z0-9._%+-]+@([A-Za-z0-9-]+(?:\\.[A-Za-z0-9-]+)*\\.[A-Za-z]{2,})/g;\n\nexport type KeyPattern = string | RegExp;\n\nexport interface RedactorOptions {\n /** Extra sensitive keys: names (matched like the defaults) or patterns tested on the snake_case key. */\n keys?: readonly KeyPattern[];\n /** Extra value patterns; every match inside any string becomes [REDACTED]. */\n values?: readonly RegExp[];\n}\n\nexport interface Redactor {\n isSensitive(key: string): boolean;\n /** Masks secrets and emails that appear inside free text. */\n scrub(s: string): string;\n /** JSON.stringify that redacts sensitive keys at any depth and never throws. */\n json(value: unknown): string;\n}\n\nfunction globalize(re: RegExp): RegExp {\n return re.flags.includes(\"g\") ? new RegExp(re.source, re.flags) : new RegExp(re.source, `${re.flags}g`);\n}\n\nexport function createRedactor(opts: RedactorOptions = {}): Redactor {\n const words = [...DEFAULT_REDACT_KEYS];\n const keyPatterns: RegExp[] = [];\n for (const k of opts.keys ?? []) {\n if (typeof k === \"string\") words.push(toSnakeCase(k));\n else keyPatterns.push(new RegExp(k.source, k.flags.replace(\"g\", \"\")));\n }\n const valuePatterns = (opts.values ?? []).map(globalize);\n\n const isSensitive = (key: string): boolean => {\n const k = toSnakeCase(key);\n if (keyPatterns.some((re) => re.test(k))) return true;\n if (!words.includes(k) && SAFE_SUFFIXES.some((s) => k.endsWith(s))) return false;\n return words.some((w) => k === w || k.startsWith(`${w}_`) || k.endsWith(`_${w}`) || k.includes(`_${w}_`));\n };\n\n const scrub = (s: string): string => {\n let out = s\n .replace(URL_CREDENTIALS_RE, `$1${REDACTED}@`)\n .replace(BEARER_RE, `Bearer ${REDACTED}`)\n .replace(JWT_RE, \"[REDACTED_JWT]\")\n .replace(EMAIL_RE, \"***@$1\");\n for (const re of valuePatterns) out = out.replace(re, REDACTED);\n return out;\n };\n\n const json = (value: unknown): string => {\n if (typeof value === \"string\") return value;\n try {\n const text = JSON.stringify(value, (key, val: unknown) => {\n if (key && val !== null && val !== undefined && isSensitive(key)) return REDACTED;\n if (typeof val === \"bigint\") return val.toString();\n if (val instanceof Error) return { name: val.name, message: val.message };\n return val;\n });\n return text ?? String(value);\n } catch {\n return String(value);\n }\n };\n\n return { isSensitive, scrub, json };\n}\n\nexport const defaultRedactor: Redactor = createRedactor();\n","import { globalSingleton } from \"./global\";\nimport { defaultRedactor } from \"./redact\";\n\nexport interface ErrFields {\n err_type?: string;\n err_message?: string;\n err_code?: string;\n err_stack?: string;\n err_cause?: string;\n}\n\nconst MAX_CAUSE_DEPTH = 5;\n\nexport function isErrorLike(v: unknown): v is Error {\n if (v instanceof Error) return true;\n return (\n typeof v === \"object\" &&\n v !== null &&\n typeof (v as { message?: unknown }).message === \"string\" &&\n (\"stack\" in v || \"name\" in v)\n );\n}\n\nfunction errorType(e: Error): string {\n if (e.name && e.name !== \"Error\") return e.name;\n const ctor = (e as { constructor?: { name?: string } }).constructor?.name;\n return ctor && ctor !== \"Object\" ? ctor : \"Error\";\n}\n\nexport function serializeError(err: unknown): ErrFields {\n if (!isErrorLike(err)) {\n if (typeof err === \"string\") return { err_type: \"string\", err_message: err };\n return { err_type: typeof err, err_message: defaultRedactor.json(err) };\n }\n const e = err as Error & { code?: unknown; cause?: unknown };\n const out: ErrFields = { err_type: errorType(e), err_message: String(e.message ?? \"\") };\n if (e.code !== undefined && e.code !== null) out.err_code = String(e.code);\n if (typeof e.stack === \"string\") out.err_stack = e.stack;\n\n const causes: string[] = [];\n let cause = e.cause;\n while (cause !== undefined && cause !== null && causes.length < MAX_CAUSE_DEPTH) {\n if (isErrorLike(cause)) {\n causes.push(`${errorType(cause)}: ${cause.message}`);\n cause = (cause as { cause?: unknown }).cause;\n } else {\n causes.push(defaultRedactor.json(cause));\n cause = undefined;\n }\n }\n if (causes.length > 0) out.err_cause = causes.join(\" <- \");\n return out;\n}\n\nconst logged = globalSingleton(\"logged\", () => new WeakSet<object>());\n\n// Marks an error as already written, so a later boundary (e.g. Next's onRequestError)\n// does not log it a second time.\nexport function markLogged(err: unknown): void {\n if (typeof err === \"object\" && err !== null) logged.add(err);\n}\n\nexport function isLogged(err: unknown): boolean {\n return typeof err === \"object\" && err !== null && logged.has(err);\n}\n"],"mappings":";AAAA,SAAS,yBAAyB;;;ACE3B,SAAS,gBAAmB,MAAc,MAAkB;AACjE,QAAM,MAAM,uBAAO,IAAI,iBAAiB,IAAI,EAAE;AAC9C,QAAM,IAAI;AACV,MAAI,QAAQ,EAAE,GAAG;AACjB,MAAI,UAAU,QAAW;AACvB,YAAQ,KAAK;AACb,MAAE,GAAG,IAAI;AAAA,EACX;AACA,SAAO;AACT;;;ADCA,IAAM,MAAM,gBAAgB,OAAO,MAAM,IAAI,kBAAgC,CAAC;AAEvE,IAAM,YAAoB,uBAAO,IAAI,qBAAqB;AAE1D,SAAS,aAAgB,QAAgB,IAAmC;AACjF,QAAM,SAAS,IAAI,SAAS;AAC5B,QAAM,QAAsB,EAAE,QAAQ,EAAE,GAAG,QAAQ,QAAQ,GAAG,OAAO,GAAG,OAAO;AAC/E,SAAO,IAAI,IAAI,OAAO,MAAM,GAAG,KAAK,CAAC;AACvC;AAEO,SAAS,YAAe,QAAgB,IAAgB;AAC7D,SAAO,aAAa,QAAQ,MAAM,GAAG,CAAC;AACxC;AAEO,SAAS,eAAyC;AACvD,SAAO,IAAI,SAAS;AACtB;AAEO,SAAS,aAAqB;AACnC,SAAO,IAAI,SAAS,GAAG,UAAU,CAAC;AACpC;AAIO,SAAS,WAAW,QAAsB;AAC/C,WAAS,IAAI,IAAI,SAAS,GAAG,GAAG,IAAI,EAAE,OAAQ,QAAO,OAAO,EAAE,QAAQ,MAAM;AAC9E;AAGO,SAAS,cAAc,OAAqB,KAAoB;AACrE,QAAM,QAAQ;AACd,WAAS,IAAI,MAAM,QAAQ,GAAG,IAAI,EAAE,OAAQ,KAAI,EAAE,UAAU,OAAW,GAAE,QAAQ;AACnF;AAGO,SAAS,YAAY,KAAoB;AAC9C,QAAM,QAAQ,IAAI,SAAS;AAC3B,MAAI,MAAO,eAAc,OAAO,GAAG;AACrC;AAEO,SAAS,YAAY,QAAgB,OAA2B;AACrE,EAAC,OAAwC,SAAS,IAAI;AACxD;AAEO,SAAS,QAAQ,QAA2C;AACjE,MAAI,OAAO,WAAW,YAAY,WAAW,KAAM,QAAO;AAC1D,SAAQ,OAAoD,SAAS;AACvE;;;AExDO,IAAM,sBAAsB;AAAA,EACjC;AAAA,EAAY;AAAA,EAAU;AAAA,EAAO;AAAA,EAAc;AAAA,EAAU;AAAA,EACrD;AAAA,EAAS;AAAA,EAAgB;AAAA,EAAiB;AAAA,EAAY;AAAA,EACtD;AAAA,EAAW;AAAA,EAAU;AAAA,EAAc;AAAA,EAAc;AAAA,EACjD;AAAA,EAAiB;AAAA,EAAU;AAAA,EAAc;AAAA,EAAc;AAAA,EACvD;AAAA,EAAc;AAAA,EAAe;AAAA,EAAO;AAAA,EAAqB;AAAA,EAAe;AAAA,EAAO;AACjF;AAGO,IAAM,kBAAkB;AAAA;AAAA,EAE7B,SAAS;AAAA;AAAA,EAET,aAAa;AAAA;AAAA,EAEb,MAAM;AACR;AAGA,IAAM,gBAAgB,CAAC,UAAU,SAAS,WAAW,eAAe,eAAe,QAAQ,OAAO,OAAO,UAAU;AAE5G,IAAM,WAAW;AAEjB,SAAS,YAAY,KAAqB;AAC/C,SAAO,IACJ,QAAQ,sBAAsB,OAAO,EACrC,QAAQ,yBAAyB,OAAO,EACxC,QAAQ,mBAAmB,GAAG,EAC9B,YAAY;AACjB;AAEA,IAAM,qBAAqB;AAC3B,IAAM,YAAY;AAClB,IAAM,SAAS;AACf,IAAM,WAAW;AAmBjB,SAAS,UAAU,IAAoB;AACrC,SAAO,GAAG,MAAM,SAAS,GAAG,IAAI,IAAI,OAAO,GAAG,QAAQ,GAAG,KAAK,IAAI,IAAI,OAAO,GAAG,QAAQ,GAAG,GAAG,KAAK,GAAG;AACxG;AAEO,SAAS,eAAe,OAAwB,CAAC,GAAa;AACnE,QAAM,QAAQ,CAAC,GAAG,mBAAmB;AACrC,QAAM,cAAwB,CAAC;AAC/B,aAAW,KAAK,KAAK,QAAQ,CAAC,GAAG;AAC/B,QAAI,OAAO,MAAM,SAAU,OAAM,KAAK,YAAY,CAAC,CAAC;AAAA,QAC/C,aAAY,KAAK,IAAI,OAAO,EAAE,QAAQ,EAAE,MAAM,QAAQ,KAAK,EAAE,CAAC,CAAC;AAAA,EACtE;AACA,QAAM,iBAAiB,KAAK,UAAU,CAAC,GAAG,IAAI,SAAS;AAEvD,QAAM,cAAc,CAAC,QAAyB;AAC5C,UAAM,IAAI,YAAY,GAAG;AACzB,QAAI,YAAY,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,CAAC,EAAG,QAAO;AACjD,QAAI,CAAC,MAAM,SAAS,CAAC,KAAK,cAAc,KAAK,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,EAAG,QAAO;AAC3E,WAAO,MAAM,KAAK,CAAC,MAAM,MAAM,KAAK,EAAE,WAAW,GAAG,CAAC,GAAG,KAAK,EAAE,SAAS,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,IAAI,CAAC,GAAG,CAAC;AAAA,EAC1G;AAEA,QAAM,QAAQ,CAAC,MAAsB;AACnC,QAAI,MAAM,EACP,QAAQ,oBAAoB,KAAK,QAAQ,GAAG,EAC5C,QAAQ,WAAW,UAAU,QAAQ,EAAE,EACvC,QAAQ,QAAQ,gBAAgB,EAChC,QAAQ,UAAU,QAAQ;AAC7B,eAAW,MAAM,cAAe,OAAM,IAAI,QAAQ,IAAI,QAAQ;AAC9D,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,CAAC,UAA2B;AACvC,QAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAI;AACF,YAAM,OAAO,KAAK,UAAU,OAAO,CAAC,KAAK,QAAiB;AACxD,YAAI,OAAO,QAAQ,QAAQ,QAAQ,UAAa,YAAY,GAAG,EAAG,QAAO;AACzE,YAAI,OAAO,QAAQ,SAAU,QAAO,IAAI,SAAS;AACjD,YAAI,eAAe,MAAO,QAAO,EAAE,MAAM,IAAI,MAAM,SAAS,IAAI,QAAQ;AACxE,eAAO;AAAA,MACT,CAAC;AACD,aAAO,QAAQ,OAAO,KAAK;AAAA,IAC7B,QAAQ;AACN,aAAO,OAAO,KAAK;AAAA,IACrB;AAAA,EACF;AAEA,SAAO,EAAE,aAAa,OAAO,KAAK;AACpC;AAEO,IAAM,kBAA4B,eAAe;;;AC7FxD,IAAM,kBAAkB;AAEjB,SAAS,YAAY,GAAwB;AAClD,MAAI,aAAa,MAAO,QAAO;AAC/B,SACE,OAAO,MAAM,YACb,MAAM,QACN,OAAQ,EAA4B,YAAY,aAC/C,WAAW,KAAK,UAAU;AAE/B;AAEA,SAAS,UAAU,GAAkB;AACnC,MAAI,EAAE,QAAQ,EAAE,SAAS,QAAS,QAAO,EAAE;AAC3C,QAAM,OAAQ,EAA0C,aAAa;AACrE,SAAO,QAAQ,SAAS,WAAW,OAAO;AAC5C;AAEO,SAAS,eAAe,KAAyB;AACtD,MAAI,CAAC,YAAY,GAAG,GAAG;AACrB,QAAI,OAAO,QAAQ,SAAU,QAAO,EAAE,UAAU,UAAU,aAAa,IAAI;AAC3E,WAAO,EAAE,UAAU,OAAO,KAAK,aAAa,gBAAgB,KAAK,GAAG,EAAE;AAAA,EACxE;AACA,QAAM,IAAI;AACV,QAAM,MAAiB,EAAE,UAAU,UAAU,CAAC,GAAG,aAAa,OAAO,EAAE,WAAW,EAAE,EAAE;AACtF,MAAI,EAAE,SAAS,UAAa,EAAE,SAAS,KAAM,KAAI,WAAW,OAAO,EAAE,IAAI;AACzE,MAAI,OAAO,EAAE,UAAU,SAAU,KAAI,YAAY,EAAE;AAEnD,QAAM,SAAmB,CAAC;AAC1B,MAAI,QAAQ,EAAE;AACd,SAAO,UAAU,UAAa,UAAU,QAAQ,OAAO,SAAS,iBAAiB;AAC/E,QAAI,YAAY,KAAK,GAAG;AACtB,aAAO,KAAK,GAAG,UAAU,KAAK,CAAC,KAAK,MAAM,OAAO,EAAE;AACnD,cAAS,MAA8B;AAAA,IACzC,OAAO;AACL,aAAO,KAAK,gBAAgB,KAAK,KAAK,CAAC;AACvC,cAAQ;AAAA,IACV;AAAA,EACF;AACA,MAAI,OAAO,SAAS,EAAG,KAAI,YAAY,OAAO,KAAK,MAAM;AACzD,SAAO;AACT;AAEA,IAAM,SAAS,gBAAgB,UAAU,MAAM,oBAAI,QAAgB,CAAC;AAI7D,SAAS,WAAW,KAAoB;AAC7C,MAAI,OAAO,QAAQ,YAAY,QAAQ,KAAM,QAAO,IAAI,GAAG;AAC7D;AAEO,SAAS,SAAS,KAAuB;AAC9C,SAAO,OAAO,QAAQ,YAAY,QAAQ,QAAQ,OAAO,IAAI,GAAG;AAClE;","names":[]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getContext,
|
|
3
|
+
markLogged,
|
|
4
|
+
withContext
|
|
5
|
+
} from "./chunk-QMC6BWGK.js";
|
|
6
|
+
|
|
7
|
+
// src/job.ts
|
|
8
|
+
async function runJob(log, info, fn) {
|
|
9
|
+
const ctx = { ...info.fields, job_name: info.job_name };
|
|
10
|
+
if (info.job_id !== void 0) ctx.job_id = String(info.job_id);
|
|
11
|
+
if (info.job_attempt !== void 0) ctx.job_attempt = info.job_attempt;
|
|
12
|
+
if (info.request_id !== void 0) ctx.request_id = info.request_id;
|
|
13
|
+
return withContext(ctx, async () => {
|
|
14
|
+
const start = performance.now();
|
|
15
|
+
try {
|
|
16
|
+
const result = await fn();
|
|
17
|
+
log.info("job completed", { event: "job.completed", duration_ms: Math.round(performance.now() - start) });
|
|
18
|
+
return result;
|
|
19
|
+
} catch (err) {
|
|
20
|
+
const final = typeof info.final_attempt === "function" ? info.final_attempt(err) : info.final_attempt ?? true;
|
|
21
|
+
log[final ? "error" : "warn"]("job failed", {
|
|
22
|
+
event: "job.failed",
|
|
23
|
+
duration_ms: Math.round(performance.now() - start),
|
|
24
|
+
err
|
|
25
|
+
});
|
|
26
|
+
markLogged(err);
|
|
27
|
+
throw err;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function jobData(data) {
|
|
32
|
+
const requestId = getContext().request_id;
|
|
33
|
+
if (typeof requestId !== "string" || data.request_id !== void 0) return data;
|
|
34
|
+
return { ...data, request_id: requestId };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export {
|
|
38
|
+
runJob,
|
|
39
|
+
jobData
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=chunk-UXKPPF4G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/job.ts"],"sourcesContent":["import { getContext, withContext } from \"./context\";\nimport { markLogged } from \"./error\";\nimport type { Logger } from \"./logger\";\nimport type { Fields } from \"./normalize\";\n\nexport interface JobInfo {\n job_name: string;\n job_id?: string | number;\n job_attempt?: number;\n /** No retries left after this attempt (or decided per error). Defaults to true when unknown. */\n final_attempt?: boolean | ((err: unknown) => boolean);\n request_id?: string;\n fields?: Fields;\n}\n\nexport async function runJob<T>(log: Logger, info: JobInfo, fn: () => T | Promise<T>): Promise<T> {\n const ctx: Fields = { ...info.fields, job_name: info.job_name };\n if (info.job_id !== undefined) ctx.job_id = String(info.job_id);\n if (info.job_attempt !== undefined) ctx.job_attempt = info.job_attempt;\n if (info.request_id !== undefined) ctx.request_id = info.request_id;\n\n return withContext(ctx, async () => {\n const start = performance.now();\n try {\n const result = await fn();\n log.info(\"job completed\", { event: \"job.completed\", duration_ms: Math.round(performance.now() - start) });\n return result;\n } catch (err) {\n const final = typeof info.final_attempt === \"function\" ? info.final_attempt(err) : (info.final_attempt ?? true);\n log[final ? \"error\" : \"warn\"](\"job failed\", {\n event: \"job.failed\",\n duration_ms: Math.round(performance.now() - start),\n err,\n });\n markLogged(err);\n throw err;\n }\n });\n}\n\n/** Adds the current request_id to a job payload, so the job's logs link back to the request. */\nexport function jobData<T extends Record<string, unknown>>(data: T): T & { request_id?: string } {\n const requestId = getContext().request_id;\n if (typeof requestId !== \"string\" || data.request_id !== undefined) return data;\n return { ...data, request_id: requestId };\n}\n"],"mappings":";;;;;;;AAeA,eAAsB,OAAU,KAAa,MAAe,IAAsC;AAChG,QAAM,MAAc,EAAE,GAAG,KAAK,QAAQ,UAAU,KAAK,SAAS;AAC9D,MAAI,KAAK,WAAW,OAAW,KAAI,SAAS,OAAO,KAAK,MAAM;AAC9D,MAAI,KAAK,gBAAgB,OAAW,KAAI,cAAc,KAAK;AAC3D,MAAI,KAAK,eAAe,OAAW,KAAI,aAAa,KAAK;AAEzD,SAAO,YAAY,KAAK,YAAY;AAClC,UAAM,QAAQ,YAAY,IAAI;AAC9B,QAAI;AACF,YAAM,SAAS,MAAM,GAAG;AACxB,UAAI,KAAK,iBAAiB,EAAE,OAAO,iBAAiB,aAAa,KAAK,MAAM,YAAY,IAAI,IAAI,KAAK,EAAE,CAAC;AACxG,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,QAAQ,OAAO,KAAK,kBAAkB,aAAa,KAAK,cAAc,GAAG,IAAK,KAAK,iBAAiB;AAC1G,UAAI,QAAQ,UAAU,MAAM,EAAE,cAAc;AAAA,QAC1C,OAAO;AAAA,QACP,aAAa,KAAK,MAAM,YAAY,IAAI,IAAI,KAAK;AAAA,QACjD;AAAA,MACF,CAAC;AACD,iBAAW,GAAG;AACd,YAAM;AAAA,IACR;AAAA,EACF,CAAC;AACH;AAGO,SAAS,QAA2C,MAAsC;AAC/F,QAAM,YAAY,WAAW,EAAE;AAC/B,MAAI,OAAO,cAAc,YAAY,KAAK,eAAe,OAAW,QAAO;AAC3E,SAAO,EAAE,GAAG,MAAM,YAAY,UAAU;AAC1C;","names":[]}
|
package/dist/express.cjs
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/express.ts
|
|
21
|
+
var express_exports = {};
|
|
22
|
+
__export(express_exports, {
|
|
23
|
+
errorRecorder: () => errorRecorder,
|
|
24
|
+
requestLogger: () => requestLogger
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(express_exports);
|
|
27
|
+
|
|
28
|
+
// src/context.ts
|
|
29
|
+
var import_node_async_hooks = require("async_hooks");
|
|
30
|
+
|
|
31
|
+
// src/global.ts
|
|
32
|
+
function globalSingleton(name, make) {
|
|
33
|
+
const key = /* @__PURE__ */ Symbol.for(`@tanngo11/log.${name}`);
|
|
34
|
+
const g = globalThis;
|
|
35
|
+
let value = g[key];
|
|
36
|
+
if (value === void 0) {
|
|
37
|
+
value = make();
|
|
38
|
+
g[key] = value;
|
|
39
|
+
}
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/context.ts
|
|
44
|
+
var als = globalSingleton("als", () => new import_node_async_hooks.AsyncLocalStorage());
|
|
45
|
+
var STORE_KEY = /* @__PURE__ */ Symbol.for("@tanngo11/log.store");
|
|
46
|
+
function runWithStore(fields, fn) {
|
|
47
|
+
const parent = als.getStore();
|
|
48
|
+
const store = { fields: { ...parent?.fields, ...fields }, parent };
|
|
49
|
+
return als.run(store, () => fn(store));
|
|
50
|
+
}
|
|
51
|
+
function recordErrorIn(store, err) {
|
|
52
|
+
store.error = err;
|
|
53
|
+
for (let s = store.parent; s; s = s.parent) if (s.error === void 0) s.error = err;
|
|
54
|
+
}
|
|
55
|
+
function attachStore(target, store) {
|
|
56
|
+
target[STORE_KEY] = store;
|
|
57
|
+
}
|
|
58
|
+
function storeOf(target) {
|
|
59
|
+
if (typeof target !== "object" || target === null) return void 0;
|
|
60
|
+
return target[STORE_KEY];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/redact.ts
|
|
64
|
+
var DEFAULT_REDACT_KEYS = [
|
|
65
|
+
"password",
|
|
66
|
+
"passwd",
|
|
67
|
+
"pwd",
|
|
68
|
+
"passphrase",
|
|
69
|
+
"secret",
|
|
70
|
+
"client_secret",
|
|
71
|
+
"token",
|
|
72
|
+
"access_token",
|
|
73
|
+
"refresh_token",
|
|
74
|
+
"id_token",
|
|
75
|
+
"auth_token",
|
|
76
|
+
"api_key",
|
|
77
|
+
"apikey",
|
|
78
|
+
"access_key",
|
|
79
|
+
"secret_key",
|
|
80
|
+
"private_key",
|
|
81
|
+
"authorization",
|
|
82
|
+
"cookie",
|
|
83
|
+
"set_cookie",
|
|
84
|
+
"session_id",
|
|
85
|
+
"sessionid",
|
|
86
|
+
"credential",
|
|
87
|
+
"credentials",
|
|
88
|
+
"dsn",
|
|
89
|
+
"connection_string",
|
|
90
|
+
"conn_string",
|
|
91
|
+
"jwt",
|
|
92
|
+
"bearer"
|
|
93
|
+
];
|
|
94
|
+
var SAFE_SUFFIXES = ["_count", "_type", "_length", "_expires_at", "_expires_in", "_ttl", "_at", "_ms", "_enabled"];
|
|
95
|
+
var REDACTED = "[REDACTED]";
|
|
96
|
+
function toSnakeCase(key) {
|
|
97
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/[^A-Za-z0-9_]+/g, "_").toLowerCase();
|
|
98
|
+
}
|
|
99
|
+
var URL_CREDENTIALS_RE = /([a-z][a-z0-9+.-]*:\/\/)[^\s/@:"']+:[^\s/@"']+@/gi;
|
|
100
|
+
var BEARER_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi;
|
|
101
|
+
var JWT_RE = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
|
|
102
|
+
var EMAIL_RE = /[A-Za-z0-9._%+-]+@([A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,})/g;
|
|
103
|
+
function globalize(re) {
|
|
104
|
+
return re.flags.includes("g") ? new RegExp(re.source, re.flags) : new RegExp(re.source, `${re.flags}g`);
|
|
105
|
+
}
|
|
106
|
+
function createRedactor(opts = {}) {
|
|
107
|
+
const words = [...DEFAULT_REDACT_KEYS];
|
|
108
|
+
const keyPatterns = [];
|
|
109
|
+
for (const k of opts.keys ?? []) {
|
|
110
|
+
if (typeof k === "string") words.push(toSnakeCase(k));
|
|
111
|
+
else keyPatterns.push(new RegExp(k.source, k.flags.replace("g", "")));
|
|
112
|
+
}
|
|
113
|
+
const valuePatterns = (opts.values ?? []).map(globalize);
|
|
114
|
+
const isSensitive = (key) => {
|
|
115
|
+
const k = toSnakeCase(key);
|
|
116
|
+
if (keyPatterns.some((re) => re.test(k))) return true;
|
|
117
|
+
if (!words.includes(k) && SAFE_SUFFIXES.some((s) => k.endsWith(s))) return false;
|
|
118
|
+
return words.some((w) => k === w || k.startsWith(`${w}_`) || k.endsWith(`_${w}`) || k.includes(`_${w}_`));
|
|
119
|
+
};
|
|
120
|
+
const scrub = (s) => {
|
|
121
|
+
let out = s.replace(URL_CREDENTIALS_RE, `$1${REDACTED}@`).replace(BEARER_RE, `Bearer ${REDACTED}`).replace(JWT_RE, "[REDACTED_JWT]").replace(EMAIL_RE, "***@$1");
|
|
122
|
+
for (const re of valuePatterns) out = out.replace(re, REDACTED);
|
|
123
|
+
return out;
|
|
124
|
+
};
|
|
125
|
+
const json = (value) => {
|
|
126
|
+
if (typeof value === "string") return value;
|
|
127
|
+
try {
|
|
128
|
+
const text = JSON.stringify(value, (key, val) => {
|
|
129
|
+
if (key && val !== null && val !== void 0 && isSensitive(key)) return REDACTED;
|
|
130
|
+
if (typeof val === "bigint") return val.toString();
|
|
131
|
+
if (val instanceof Error) return { name: val.name, message: val.message };
|
|
132
|
+
return val;
|
|
133
|
+
});
|
|
134
|
+
return text ?? String(value);
|
|
135
|
+
} catch {
|
|
136
|
+
return String(value);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
return { isSensitive, scrub, json };
|
|
140
|
+
}
|
|
141
|
+
var defaultRedactor = createRedactor();
|
|
142
|
+
|
|
143
|
+
// src/error.ts
|
|
144
|
+
var MAX_CAUSE_DEPTH = 5;
|
|
145
|
+
function isErrorLike(v) {
|
|
146
|
+
if (v instanceof Error) return true;
|
|
147
|
+
return typeof v === "object" && v !== null && typeof v.message === "string" && ("stack" in v || "name" in v);
|
|
148
|
+
}
|
|
149
|
+
function errorType(e) {
|
|
150
|
+
if (e.name && e.name !== "Error") return e.name;
|
|
151
|
+
const ctor = e.constructor?.name;
|
|
152
|
+
return ctor && ctor !== "Object" ? ctor : "Error";
|
|
153
|
+
}
|
|
154
|
+
function serializeError(err) {
|
|
155
|
+
if (!isErrorLike(err)) {
|
|
156
|
+
if (typeof err === "string") return { err_type: "string", err_message: err };
|
|
157
|
+
return { err_type: typeof err, err_message: defaultRedactor.json(err) };
|
|
158
|
+
}
|
|
159
|
+
const e = err;
|
|
160
|
+
const out = { err_type: errorType(e), err_message: String(e.message ?? "") };
|
|
161
|
+
if (e.code !== void 0 && e.code !== null) out.err_code = String(e.code);
|
|
162
|
+
if (typeof e.stack === "string") out.err_stack = e.stack;
|
|
163
|
+
const causes = [];
|
|
164
|
+
let cause = e.cause;
|
|
165
|
+
while (cause !== void 0 && cause !== null && causes.length < MAX_CAUSE_DEPTH) {
|
|
166
|
+
if (isErrorLike(cause)) {
|
|
167
|
+
causes.push(`${errorType(cause)}: ${cause.message}`);
|
|
168
|
+
cause = cause.cause;
|
|
169
|
+
} else {
|
|
170
|
+
causes.push(defaultRedactor.json(cause));
|
|
171
|
+
cause = void 0;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (causes.length > 0) out.err_cause = causes.join(" <- ");
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
var logged = globalSingleton("logged", () => /* @__PURE__ */ new WeakSet());
|
|
178
|
+
function markLogged(err) {
|
|
179
|
+
if (typeof err === "object" && err !== null) logged.add(err);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/normalize.ts
|
|
183
|
+
function stripUrl(url) {
|
|
184
|
+
const noQuery = url.split(/[?#]/, 1)[0] ?? "";
|
|
185
|
+
return noQuery.replace(/^([a-z][a-z0-9+.-]*:\/\/)[^/@\s]*@/i, "$1");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/request-id.ts
|
|
189
|
+
var REQUEST_ID_RE = /^[A-Za-z0-9._-]{1,64}$/;
|
|
190
|
+
var TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
|
|
191
|
+
function newRequestId() {
|
|
192
|
+
return globalThis.crypto.randomUUID();
|
|
193
|
+
}
|
|
194
|
+
function first(header2) {
|
|
195
|
+
return Array.isArray(header2) ? header2[0] : header2 ?? void 0;
|
|
196
|
+
}
|
|
197
|
+
function resolveRequestId(header2) {
|
|
198
|
+
const value = first(header2);
|
|
199
|
+
return value && REQUEST_ID_RE.test(value) ? value : newRequestId();
|
|
200
|
+
}
|
|
201
|
+
function parseTraceparent(header2) {
|
|
202
|
+
const value = first(header2)?.trim().toLowerCase();
|
|
203
|
+
const m = value ? TRACEPARENT_RE.exec(value) : null;
|
|
204
|
+
if (!m) return void 0;
|
|
205
|
+
const [, version, traceId, spanId] = m;
|
|
206
|
+
if (version === "ff" || /^0+$/.test(traceId) || /^0+$/.test(spanId)) return void 0;
|
|
207
|
+
return { trace_id: traceId, span_id: spanId };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/http.ts
|
|
211
|
+
function header(headers, name) {
|
|
212
|
+
if (typeof headers.get === "function") return headers.get(name) ?? void 0;
|
|
213
|
+
return headers[name];
|
|
214
|
+
}
|
|
215
|
+
function requestContextFields(headers) {
|
|
216
|
+
const out = {
|
|
217
|
+
request_id: resolveRequestId(header(headers, "x-request-id"))
|
|
218
|
+
};
|
|
219
|
+
const tp = parseTraceparent(header(headers, "traceparent"));
|
|
220
|
+
if (tp) Object.assign(out, tp);
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
var DEFAULT_SKIP_PATHS = [
|
|
224
|
+
/^\/(health|healthz|ready|readyz|livez)\/?$/,
|
|
225
|
+
"/favicon.ico",
|
|
226
|
+
/^\/_next\/static\//
|
|
227
|
+
];
|
|
228
|
+
function shouldSkip(method, path, skipPaths = DEFAULT_SKIP_PATHS) {
|
|
229
|
+
if (method.toUpperCase() === "OPTIONS") return true;
|
|
230
|
+
const p = stripUrl(path);
|
|
231
|
+
return skipPaths.some((s) => typeof s === "string" ? p === s : s.test(p));
|
|
232
|
+
}
|
|
233
|
+
function logHttpRequest(log, info, opts = {}) {
|
|
234
|
+
if (shouldSkip(info.method, info.path, opts.skipPaths)) return;
|
|
235
|
+
const level = info.status >= 500 ? "error" : info.status >= 400 ? "warn" : "info";
|
|
236
|
+
const path = stripUrl(info.path);
|
|
237
|
+
const errFields = {};
|
|
238
|
+
if (info.err !== void 0 && info.err !== null) {
|
|
239
|
+
Object.assign(errFields, serializeError(info.err));
|
|
240
|
+
if (info.status < 500) delete errFields.err_stack;
|
|
241
|
+
markLogged(info.err);
|
|
242
|
+
}
|
|
243
|
+
const message = info.route ? `${info.method} ${info.route} ${info.status}` : `${info.method} ${info.status}`;
|
|
244
|
+
log[level](message, {
|
|
245
|
+
...info.fields,
|
|
246
|
+
event: "http.request",
|
|
247
|
+
http_method: info.method,
|
|
248
|
+
http_route: info.route,
|
|
249
|
+
http_path: path,
|
|
250
|
+
http_status: info.status,
|
|
251
|
+
duration_ms: info.duration_ms === void 0 ? void 0 : Math.round(info.duration_ms),
|
|
252
|
+
...errFields
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/express.ts
|
|
257
|
+
function joinRoute(baseUrl, path) {
|
|
258
|
+
return `${baseUrl ?? ""}${String(path)}` || "/";
|
|
259
|
+
}
|
|
260
|
+
function captureRoute(req, store) {
|
|
261
|
+
let value;
|
|
262
|
+
Object.defineProperty(req, "route", {
|
|
263
|
+
configurable: true,
|
|
264
|
+
enumerable: true,
|
|
265
|
+
get: () => value,
|
|
266
|
+
set: (route) => {
|
|
267
|
+
value = route;
|
|
268
|
+
if (route?.path !== void 0) store.route = joinRoute(req.baseUrl, route.path);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
function requestLogger(log, opts = {}) {
|
|
273
|
+
return (req, res, next) => {
|
|
274
|
+
const ctx = requestContextFields(req.headers);
|
|
275
|
+
res.setHeader("x-request-id", ctx.request_id);
|
|
276
|
+
const start = performance.now();
|
|
277
|
+
runWithStore(ctx, (store) => {
|
|
278
|
+
attachStore(res, store);
|
|
279
|
+
captureRoute(req, store);
|
|
280
|
+
let done = false;
|
|
281
|
+
const finish = () => {
|
|
282
|
+
if (done) return;
|
|
283
|
+
done = true;
|
|
284
|
+
logHttpRequest(
|
|
285
|
+
log,
|
|
286
|
+
{
|
|
287
|
+
method: req.method,
|
|
288
|
+
route: store.route,
|
|
289
|
+
path: req.originalUrl,
|
|
290
|
+
status: res.writableFinished || res.headersSent ? res.statusCode : 499,
|
|
291
|
+
duration_ms: performance.now() - start,
|
|
292
|
+
err: store.error,
|
|
293
|
+
fields: store.fields
|
|
294
|
+
},
|
|
295
|
+
opts
|
|
296
|
+
);
|
|
297
|
+
};
|
|
298
|
+
res.once("finish", finish);
|
|
299
|
+
res.once("close", finish);
|
|
300
|
+
next();
|
|
301
|
+
});
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function errorRecorder() {
|
|
305
|
+
return (err, _req, res, next) => {
|
|
306
|
+
const store = storeOf(res);
|
|
307
|
+
if (store) recordErrorIn(store, err);
|
|
308
|
+
next(err);
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
312
|
+
0 && (module.exports = {
|
|
313
|
+
errorRecorder,
|
|
314
|
+
requestLogger
|
|
315
|
+
});
|
|
316
|
+
//# sourceMappingURL=express.cjs.map
|