@tanngo11/log 0.4.6 → 0.5.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/dist/nest.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/nest.ts","../src/context.ts","../src/global.ts","../src/redact.ts","../src/error.ts","../src/normalize.ts","../src/request-id.ts","../src/http.ts","../src/express.ts","../src/fastify.ts"],"sourcesContent":["import type { CallHandler, ExecutionContext, LoggerService, NestInterceptor } from \"@nestjs/common\";\nimport { catchError, throwError, type Observable } from \"rxjs\";\nimport { currentStore, recordError, recordErrorIn, storeOf } from \"./context\";\nimport { requestLogger as expressRequestLogger } from \"./express\";\nimport { fastifyLogger } from \"./fastify\";\nimport type { HttpLogOptions } from \"./http\";\nimport type { Level } from \"./levels\";\nimport type { Logger } from \"./logger\";\nimport type { Fields } from \"./normalize\";\n\nexport { requestLogger } from \"./express\";\nexport { fastifyLogger } from \"./fastify\";\n\nconst STACK_RE = /\\n\\s+at /;\n\n/** Routes Nest's own logs (bootstrap, ExceptionsHandler, Logger calls) into the JSON logger. */\nexport class NestLogger implements LoggerService {\n constructor(private readonly logger: Logger) {}\n\n log(message: unknown, ...params: unknown[]): void {\n this.emit(\"info\", message, params);\n }\n error(message: unknown, ...params: unknown[]): void {\n this.emit(\"error\", message, params);\n }\n warn(message: unknown, ...params: unknown[]): void {\n this.emit(\"warn\", message, params);\n }\n debug(message: unknown, ...params: unknown[]): void {\n this.emit(\"debug\", message, params);\n }\n verbose(message: unknown, ...params: unknown[]): void {\n this.emit(\"trace\", message, params);\n }\n fatal(message: unknown, ...params: unknown[]): void {\n this.emit(\"fatal\", message, params);\n }\n\n private emit(level: Level, message: unknown, params: unknown[]): void {\n const rest = [...params];\n const last = rest[rest.length - 1];\n const nestContext = typeof last === \"string\" && !STACK_RE.test(last) ? (rest.pop() as string) : undefined;\n const stack = typeof rest[0] === \"string\" && STACK_RE.test(rest[0]) ? (rest.shift() as string) : undefined;\n\n // Nest's ExceptionsHandler logs unhandled errors; the http.request line already carries\n // them, so hand the error to the request instead of writing a second error line.\n const store = currentStore();\n if (nestContext === \"ExceptionsHandler\" && store?.fields.request_id !== undefined) {\n if (store.error === undefined) {\n const e = message instanceof Error ? message : Object.assign(new Error(String(message)), { stack });\n recordErrorIn(store, e);\n }\n return;\n }\n\n const fields: Fields = { nest_context: nestContext };\n // Extra arguments: objects become fields, errors become err, anything else goes to nest_args.\n const extras: unknown[] = [];\n for (const arg of rest) {\n if (arg instanceof Error && fields.err === undefined) fields.err = arg;\n else if (typeof arg === \"object\" && arg !== null && !Array.isArray(arg)) Object.assign(fields, arg);\n else extras.push(arg);\n }\n if (extras.length > 0) fields.nest_args = extras;\n let text: string;\n if (message instanceof Error) {\n fields.err = message;\n text = message.message;\n } else if (typeof message === \"object\" && message !== null) {\n fields.nest_payload = message;\n text = \"nest log\";\n } else {\n text = String(message);\n }\n if (stack !== undefined) fields.err_stack = stack;\n this.logger[level](text, fields);\n }\n}\n\n/** Records request errors for the http.request line, then rethrows to Nest's exception filters. */\nexport class ErrorRecorderInterceptor implements NestInterceptor {\n intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {\n return next.handle().pipe(\n catchError((err: unknown) => {\n const store = storeOf(context.switchToHttp().getResponse());\n if (store) recordErrorIn(store, err);\n else recordError(err);\n return throwError(() => err);\n }),\n );\n }\n}\n\n\n/** The parts of a Nest application that setupNestLogging needs, for either HTTP adapter. */\nexport interface NestAppLike {\n getHttpAdapter(): { getType(): string };\n useGlobalInterceptors(...interceptors: NestInterceptor[]): unknown;\n use?(...args: unknown[]): unknown;\n register?(...args: unknown[]): Promise<unknown>;\n}\n\n/**\n * One-call setup for Express and Fastify Nest apps: request context, the http.request line and\n * error recording. Call before `app.listen()`.\n */\nexport async function setupNestLogging(app: NestAppLike, log: Logger, opts: HttpLogOptions = {}): Promise<void> {\n const type = app.getHttpAdapter().getType();\n if (type === \"fastify\") {\n if (!app.register) throw new Error(\"setupNestLogging: Fastify app has no register()\");\n await app.register(fastifyLogger, { ...opts, log });\n } else {\n if (!app.use) throw new Error(\"setupNestLogging: app has no use()\");\n app.use(expressRequestLogger(log, opts));\n }\n app.useGlobalInterceptors(new ErrorRecorderInterceptor());\n}\n","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","import { isErrorLike, serializeError } from \"./error\";\nimport type { Level } from \"./levels\";\nimport { REDACTED, toSnakeCase, type Redactor } from \"./redact\";\n\nexport { DEFAULT_REDACT_KEYS, toSnakeCase } from \"./redact\";\n\n/** Final per-field hook: return a replacement, or undefined to drop the field. */\nexport type RedactHook = (key: string, value: Primitive) => unknown;\n\nexport type Fields = Record<string, unknown>;\nexport type Primitive = string | number | boolean | null;\nexport type LogRecord = Record<string, Primitive>;\n\nexport interface NormalizeOptions {\n snakeCase: boolean;\n redactor: Redactor;\n hook?: RedactHook;\n}\n\n// Fields the redact hook never sees, so every line keeps its level, message and event.\nconst HOOK_EXEMPT = new Set([\"level\", \"message\", \"event\"]);\n\n// Keys owned by Vector (overwritten or deleted on ingest) or by the logger itself.\nexport const RESERVED_KEYS = new Set([\n \"project\", \"service\", \"vps\", \"version\", \"container_name\", \"container_id\", \"image\", \"stream\",\n \"_timestamp\", \"label\", \"host\", \"source_type\", \"msg\", \"lvl\", \"severity\", \"level\", \"message\",\n]);\n\nexport const MAX_LINE_BYTES = 16000;\nconst MAX_MESSAGE = 500;\nconst MAX_STACK = 4000;\nconst MAX_STRING = 1000;\nconst MAX_DEPTH = 3;\nconst TRUNCATED = \"…[truncated]\";\n\n// Fields kept when a line is too large.\nconst KEEP_ON_SHRINK = new Set([\n \"level\", \"message\", \"event\", \"request_id\", \"trace_id\", \"span_id\", \"job_name\", \"job_id\", \"job_attempt\",\n \"user_id\", \"tenant_id\", \"err_type\", \"err_message\", \"err_code\",\n]);\n\nexport function stripUrl(url: string): string {\n const noQuery = url.split(/[?#]/, 1)[0] ?? \"\";\n return noQuery.replace(/^([a-z][a-z0-9+.-]*:\\/\\/)[^/@\\s]*@/i, \"$1\");\n}\n\nexport function truncate(s: string, max: number): string {\n return s.length > max ? s.slice(0, max - TRUNCATED.length) + TRUNCATED : s;\n}\n\nfunction isPlainObject(v: unknown): v is Fields {\n if (typeof v !== \"object\" || v === null) return false;\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n}\n\nfunction isUrlKey(key: string): boolean {\n return key === \"http_path\" || key === \"http_url\" || key.endsWith(\"_url\");\n}\n\nfunction flattenInto(out: LogRecord, prefix: string, obj: Fields, depth: number, opts: NormalizeOptions): void {\n for (const [rawKey, value] of Object.entries(obj)) {\n const key = opts.snakeCase ? toSnakeCase(rawKey) : rawKey;\n const full = prefix ? `${prefix}_${key}` : key;\n if (value === undefined || typeof value === \"function\" || typeof value === \"symbol\") continue;\n if (value !== null && (opts.redactor.isSensitive(rawKey) || opts.redactor.isSensitive(full))) {\n out[full] = REDACTED;\n continue;\n }\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") {\n out[full] = value;\n } else if (typeof value === \"number\") {\n out[full] = Number.isFinite(value) ? value : String(value);\n } else if (typeof value === \"bigint\") {\n out[full] = value.toString();\n } else if (value instanceof Date) {\n out[full] = Number.isNaN(value.getTime()) ? \"Invalid Date\" : value.toISOString();\n } else if (isErrorLike(value)) {\n out[full] = `${value.name}: ${value.message}`;\n } else if (isPlainObject(value) && depth < MAX_DEPTH - 1) {\n flattenInto(out, full, value, depth + 1, opts);\n } else {\n // Arrays, deep objects, class instances: stringified with sensitive keys redacted.\n out[full] = opts.redactor.json(value);\n }\n }\n}\n\nfunction finalize(key: string, value: Primitive, opts: NormalizeOptions): Primitive {\n if (typeof value !== \"string\") return value;\n const v = opts.redactor.scrub(isUrlKey(key) ? stripUrl(value) : value);\n return truncate(v, key === \"err_stack\" ? MAX_STACK : MAX_STRING);\n}\n\nexport function buildRecord(\n level: Level,\n message: unknown,\n sources: (Fields | undefined)[],\n opts: NormalizeOptions,\n): LogRecord {\n const merged: Fields = {};\n for (const source of sources) if (source) Object.assign(merged, source);\n\n let err: unknown;\n if (merged.err !== undefined && merged.err !== null) {\n err = merged.err;\n delete merged.err;\n } else if (isErrorLike(merged.error)) {\n err = merged.error;\n delete merged.error;\n }\n\n const flat: LogRecord = {};\n flattenInto(flat, \"\", merged, 0, opts);\n if (err !== undefined) Object.assign(flat, serializeError(err));\n\n const text = typeof message === \"string\" ? message : opts.redactor.json(message);\n const out: LogRecord = { level, message: truncate(opts.redactor.scrub(text), MAX_MESSAGE) };\n for (const [k, v] of Object.entries(flat)) {\n const key = RESERVED_KEYS.has(k) ? `app_${k}` : k;\n let value = finalize(key, v, opts);\n if (opts.hook && !HOOK_EXEMPT.has(key)) {\n const replaced = opts.hook(key, value);\n if (replaced === undefined) continue;\n value = typeof replaced === \"object\" && replaced !== null ? opts.redactor.json(replaced) : (replaced as Primitive);\n }\n out[key] = value;\n }\n return out;\n}\n\nfunction byteLength(s: string): number {\n // UTF-8 uses at most 3 bytes per UTF-16 code unit: skip encoding short lines.\n if (s.length * 3 <= MAX_LINE_BYTES) return s.length;\n return new TextEncoder().encode(s).length;\n}\n\nexport function serializeRecord(record: LogRecord): string {\n const line = JSON.stringify(record);\n if (byteLength(line) <= MAX_LINE_BYTES) return line;\n const small: LogRecord = {};\n for (const [k, v] of Object.entries(record)) {\n if (KEEP_ON_SHRINK.has(k) || k.startsWith(\"http_\")) small[k] = v;\n }\n small.log_truncated = true;\n return JSON.stringify(small);\n}\n","// No imports: this module is shared with the edge entry (next-edge).\nexport const REQUEST_ID_RE = /^[A-Za-z0-9._-]{1,64}$/;\nconst TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;\n\nexport function newRequestId(): string {\n return globalThis.crypto.randomUUID();\n}\n\nfunction first(header?: string | string[] | null): string | undefined {\n return Array.isArray(header) ? header[0] : (header ?? undefined);\n}\n\nexport function resolveRequestId(header?: string | string[] | null): string {\n const value = first(header);\n return value && REQUEST_ID_RE.test(value) ? value : newRequestId();\n}\n\nexport function parseTraceparent(header?: string | string[] | null): { trace_id: string; span_id: string } | undefined {\n const value = first(header)?.trim().toLowerCase();\n const m = value ? TRACEPARENT_RE.exec(value) : null;\n if (!m) return undefined;\n const [, version, traceId, spanId] = m as unknown as [string, string, string, string];\n if (version === \"ff\" || /^0+$/.test(traceId) || /^0+$/.test(spanId)) return undefined;\n return { trace_id: traceId, span_id: spanId };\n}\n","import { getContext } from \"./context\";\nimport { markLogged, serializeError } from \"./error\";\nimport type { Logger } from \"./logger\";\nimport { stripUrl, type Fields } from \"./normalize\";\nimport { parseTraceparent, resolveRequestId } from \"./request-id\";\n\nexport { newRequestId, parseTraceparent, REQUEST_ID_RE, resolveRequestId } from \"./request-id\";\n\nexport type HeaderSource = Headers | Record<string, string | string[] | undefined>;\n\nfunction header(headers: HeaderSource, name: string): string | string[] | undefined {\n if (typeof (headers as Headers).get === \"function\") return (headers as Headers).get(name) ?? undefined;\n return (headers as Record<string, string | string[] | undefined>)[name];\n}\n\nexport function requestContextFields(headers: HeaderSource): { request_id: string; trace_id?: string; span_id?: string } {\n const out: { request_id: string; trace_id?: string; span_id?: string } = {\n request_id: resolveRequestId(header(headers, \"x-request-id\")),\n };\n const tp = parseTraceparent(header(headers, \"traceparent\"));\n if (tp) Object.assign(out, tp);\n return out;\n}\n\n// Headers to forward to services we own, so their logs share request_id / trace_id.\nexport function propagationHeaders(): Record<string, string> {\n const ctx = getContext();\n const out: Record<string, string> = {};\n if (typeof ctx.request_id === \"string\") out[\"x-request-id\"] = ctx.request_id;\n if (typeof ctx.trace_id === \"string\" && typeof ctx.span_id === \"string\") {\n out.traceparent = `00-${ctx.trace_id}-${ctx.span_id}-01`;\n }\n return out;\n}\n\nexport const DEFAULT_SKIP_PATHS: (string | RegExp)[] = [\n /^\\/(health|healthz|ready|readyz|livez)\\/?$/,\n \"/favicon.ico\",\n /^\\/_next\\/static\\//,\n];\n\nexport interface HttpLogOptions {\n skipPaths?: (string | RegExp)[];\n}\n\nexport interface HttpRequestInfo {\n method: string;\n route?: string;\n path: string;\n status: number;\n duration_ms?: number;\n err?: unknown;\n fields?: Fields;\n}\n\nexport function shouldSkip(method: string, path: string, skipPaths: (string | RegExp)[] = DEFAULT_SKIP_PATHS): boolean {\n if (method.toUpperCase() === \"OPTIONS\") return true;\n const p = stripUrl(path);\n return skipPaths.some((s) => (typeof s === \"string\" ? p === s : s.test(p)));\n}\n\n// Writes the one `http.request` summary line for an inbound request.\nexport function logHttpRequest(log: Logger, info: HttpRequestInfo, opts: HttpLogOptions = {}): void {\n if (shouldSkip(info.method, info.path, opts.skipPaths)) return;\n const level = info.status >= 500 ? \"error\" : info.status >= 400 ? \"warn\" : \"info\";\n const path = stripUrl(info.path);\n const errFields: Fields = {};\n if (info.err !== undefined && info.err !== null) {\n Object.assign(errFields, serializeError(info.err));\n if (info.status < 500) delete errFields.err_stack;\n markLogged(info.err);\n }\n // Route templates keep the message bounded; the raw path would make one message per URL.\n const message = info.route ? `${info.method} ${info.route} ${info.status}` : `${info.method} ${info.status}`;\n log[level](message, {\n ...info.fields,\n event: \"http.request\",\n http_method: info.method,\n http_route: info.route,\n http_path: path,\n http_status: info.status,\n duration_ms: info.duration_ms === undefined ? undefined : Math.round(info.duration_ms),\n ...errFields,\n });\n}\n","import type { ErrorRequestHandler, RequestHandler } from \"express\";\nimport { attachStore, recordErrorIn, runWithStore, storeOf, type ContextStore } from \"./context\";\nimport { logHttpRequest, requestContextFields, type HttpLogOptions } from \"./http\";\nimport type { Logger } from \"./logger\";\n\nfunction joinRoute(baseUrl: string | undefined, path: unknown): string {\n return `${baseUrl ?? \"\"}${String(path)}` || \"/\";\n}\n\n// Express assigns req.route when a route matches, while req.baseUrl still holds the mount\n// path. It resets baseUrl before the response ends, so capture the full template here.\nfunction captureRoute(req: { baseUrl?: string }, store: ContextStore): void {\n let value: unknown;\n Object.defineProperty(req, \"route\", {\n configurable: true,\n enumerable: true,\n get: () => value,\n set: (route: { path?: unknown } | undefined) => {\n value = route;\n if (route?.path !== undefined) store.route = joinRoute(req.baseUrl, route.path);\n },\n });\n}\n\n/** Mount before routes. Opens the request context and writes the http.request line. */\nexport function requestLogger(log: Logger, opts: HttpLogOptions = {}): RequestHandler {\n return (req, res, next) => {\n const ctx = requestContextFields(req.headers);\n res.setHeader(\"x-request-id\", ctx.request_id);\n const start = performance.now();\n runWithStore(ctx, (store) => {\n attachStore(res, store);\n captureRoute(req, store);\n let done = false;\n const finish = () => {\n if (done) return;\n done = true;\n logHttpRequest(\n log,\n {\n method: req.method,\n route: store.route,\n path: req.originalUrl,\n status: res.writableFinished || res.headersSent ? res.statusCode : 499,\n duration_ms: performance.now() - start,\n err: store.error,\n fields: store.fields,\n },\n opts,\n );\n };\n res.once(\"finish\", finish);\n res.once(\"close\", finish);\n next();\n });\n };\n}\n\n/** Mount after routes, before your own error handler. Records the error for the summary line. */\nexport function errorRecorder(): ErrorRequestHandler {\n return (err, _req, res, next) => {\n const store = storeOf(res);\n if (store) recordErrorIn(store, err);\n next(err);\n };\n}\n","import type { FastifyPluginCallback, FastifyRequest } from \"fastify\";\nimport { attachStore, recordErrorIn, runWithStore, storeOf } from \"./context\";\nimport { logHttpRequest, requestContextFields, type HttpLogOptions } from \"./http\";\nimport type { Logger } from \"./logger\";\n\nexport interface FastifyLoggerOptions extends HttpLogOptions {\n log: Logger;\n}\n\nconst starts = new WeakMap<object, number>();\nconst finished = new WeakSet<object>();\n\nconst plugin: FastifyPluginCallback<FastifyLoggerOptions> = (app, opts, done) => {\n const summary = (req: FastifyRequest, status: number) => {\n if (finished.has(req)) return;\n finished.add(req);\n const store = storeOf(req);\n const start = starts.get(req);\n logHttpRequest(\n opts.log,\n {\n method: req.method,\n route: req.routeOptions?.url,\n path: req.url,\n status,\n duration_ms: start === undefined ? undefined : performance.now() - start,\n err: store?.error,\n fields: store?.fields,\n },\n opts,\n );\n };\n\n app.addHook(\"onRequest\", (req, reply, next) => {\n const ctx = requestContextFields(req.headers);\n reply.header(\"x-request-id\", ctx.request_id);\n starts.set(req, performance.now());\n runWithStore(ctx, (store) => {\n attachStore(req, store);\n attachStore(reply, store);\n next();\n });\n });\n app.addHook(\"onError\", (_req, reply, err, next) => {\n const store = storeOf(reply);\n if (store) recordErrorIn(store, err);\n next();\n });\n app.addHook(\"onResponse\", (req, reply, next) => {\n summary(req, reply.statusCode);\n next();\n });\n // Client closed the connection before the response (Fastify >= 4.21).\n app.addHook(\"onRequestAbort\", (req, next) => {\n summary(req, 499);\n next();\n });\n done();\n};\n\n// Same effect as fastify-plugin: hooks apply to the whole app, not an encapsulated child.\n(plugin as unknown as Record<symbol, boolean>)[Symbol.for(\"skip-override\")] = true;\n\nexport const fastifyLogger = plugin;\n"],"mappings":"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","names":["header"]}
1
+ {"version":3,"sources":["../src/nest.ts","../src/context.ts","../src/global.ts","../src/redact.ts","../src/error.ts","../src/normalize.ts","../src/request-id.ts","../src/http.ts","../src/express.ts","../src/fastify.ts"],"sourcesContent":["import type { CallHandler, ExecutionContext, LoggerService, NestInterceptor } from \"@nestjs/common\";\nimport { catchError, throwError, type Observable } from \"rxjs\";\nimport { currentStore, recordError, recordErrorIn, storeOf } from \"./context\";\nimport { requestLogger as expressRequestLogger } from \"./express\";\nimport { fastifyLogger } from \"./fastify\";\nimport type { HttpLogOptions } from \"./http\";\nimport type { Level } from \"./levels\";\nimport type { Logger } from \"./logger\";\nimport type { Fields } from \"./normalize\";\n\nexport { requestLogger } from \"./express\";\nexport { fastifyLogger } from \"./fastify\";\n\nconst STACK_RE = /\\n\\s+at /;\n\n/** Routes Nest's own logs (bootstrap, ExceptionsHandler, Logger calls) into the JSON logger. */\nexport class NestLogger implements LoggerService {\n constructor(private readonly logger: Logger) {}\n\n log(message: unknown, ...params: unknown[]): void {\n this.emit(\"info\", message, params);\n }\n error(message: unknown, ...params: unknown[]): void {\n this.emit(\"error\", message, params);\n }\n warn(message: unknown, ...params: unknown[]): void {\n this.emit(\"warn\", message, params);\n }\n debug(message: unknown, ...params: unknown[]): void {\n this.emit(\"debug\", message, params);\n }\n verbose(message: unknown, ...params: unknown[]): void {\n this.emit(\"trace\", message, params);\n }\n fatal(message: unknown, ...params: unknown[]): void {\n this.emit(\"fatal\", message, params);\n }\n\n private emit(level: Level, message: unknown, params: unknown[]): void {\n const rest = [...params];\n const last = rest[rest.length - 1];\n const nestContext = typeof last === \"string\" && !STACK_RE.test(last) ? (rest.pop() as string) : undefined;\n const stack = typeof rest[0] === \"string\" && STACK_RE.test(rest[0]) ? (rest.shift() as string) : undefined;\n\n // Nest's ExceptionsHandler logs unhandled errors; the http.request line already carries\n // them, so hand the error to the request instead of writing a second error line.\n const store = currentStore();\n if (nestContext === \"ExceptionsHandler\" && store?.fields.request_id !== undefined) {\n if (store.error === undefined) {\n const e = message instanceof Error ? message : Object.assign(new Error(String(message)), { stack });\n recordErrorIn(store, e);\n }\n return;\n }\n\n const fields: Fields = { nest_context: nestContext };\n // Extra arguments: objects become fields, errors become err, anything else goes to nest_args.\n const extras: unknown[] = [];\n for (const arg of rest) {\n if (arg instanceof Error && fields.err === undefined) fields.err = arg;\n else if (typeof arg === \"object\" && arg !== null && !Array.isArray(arg)) Object.assign(fields, arg);\n else extras.push(arg);\n }\n if (extras.length > 0) fields.nest_args = extras;\n let text: string;\n if (message instanceof Error) {\n fields.err = message;\n text = message.message;\n } else if (typeof message === \"object\" && message !== null) {\n fields.nest_payload = message;\n text = \"nest log\";\n } else {\n text = String(message);\n }\n if (stack !== undefined) fields.err_stack = stack;\n this.logger[level](text, fields);\n }\n}\n\n/** Records request errors for the http.request line, then rethrows to Nest's exception filters. */\nexport class ErrorRecorderInterceptor implements NestInterceptor {\n intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {\n return next.handle().pipe(\n catchError((err: unknown) => {\n const store = storeOf(context.switchToHttp().getResponse());\n if (store) recordErrorIn(store, err);\n else recordError(err);\n return throwError(() => err);\n }),\n );\n }\n}\n\n\n/** The parts of a Nest application that setupNestLogging needs, for either HTTP adapter. */\nexport interface NestAppLike {\n getHttpAdapter(): { getType(): string };\n useGlobalInterceptors(...interceptors: NestInterceptor[]): unknown;\n use?(...args: unknown[]): unknown;\n register?(...args: unknown[]): Promise<unknown>;\n}\n\n/**\n * One-call setup for Express and Fastify Nest apps: request context, the http.request line and\n * error recording. Call before `app.listen()`.\n */\nexport async function setupNestLogging(app: NestAppLike, log: Logger, opts: HttpLogOptions = {}): Promise<void> {\n const type = app.getHttpAdapter().getType();\n if (type === \"fastify\") {\n if (!app.register) throw new Error(\"setupNestLogging: Fastify app has no register()\");\n await app.register(fastifyLogger, { ...opts, log });\n } else {\n if (!app.use) throw new Error(\"setupNestLogging: app has no use()\");\n app.use(expressRequestLogger(log, opts));\n }\n app.useGlobalInterceptors(new ErrorRecorderInterceptor());\n}\n","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","import { isErrorLike, serializeError } from \"./error\";\nimport type { Level } from \"./levels\";\nimport { REDACTED, toSnakeCase, type Redactor } from \"./redact\";\n\nexport { DEFAULT_REDACT_KEYS, toSnakeCase } from \"./redact\";\n\n/** Final per-field hook: return a replacement, or undefined to drop the field. */\nexport type RedactHook = (key: string, value: Primitive) => unknown;\n\nexport type Fields = Record<string, unknown>;\nexport type Primitive = string | number | boolean | null;\nexport type LogRecord = Record<string, Primitive>;\n\nexport interface NormalizeOptions {\n snakeCase: boolean;\n redactor: Redactor;\n hook?: RedactHook;\n}\n\n// Fields the redact hook never sees, so every line keeps its level, message and event.\nconst HOOK_EXEMPT = new Set([\"level\", \"message\", \"event\"]);\n\n// Keys owned by Vector (overwritten or deleted on ingest) or by the logger itself.\nexport const RESERVED_KEYS = new Set([\n \"project\", \"service\", \"vps\", \"version\", \"container_name\", \"container_id\", \"image\", \"stream\",\n \"_timestamp\", \"label\", \"host\", \"source_type\", \"msg\", \"lvl\", \"severity\", \"level\", \"message\",\n]);\n\nexport const MAX_LINE_BYTES = 16000;\nconst MAX_MESSAGE = 500;\nconst MAX_STACK = 4000;\nconst MAX_STRING = 1000;\nconst MAX_DEPTH = 3;\nconst TRUNCATED = \"…[truncated]\";\n\n// Fields kept when a line is too large.\nconst KEEP_ON_SHRINK = new Set([\n \"level\", \"message\", \"event\", \"request_id\", \"trace_id\", \"span_id\", \"job_name\", \"job_id\", \"job_attempt\",\n \"user_id\", \"tenant_id\", \"err_type\", \"err_message\", \"err_code\",\n]);\n\nexport function stripUrl(url: string): string {\n const noQuery = url.split(/[?#]/, 1)[0] ?? \"\";\n return noQuery.replace(/^([a-z][a-z0-9+.-]*:\\/\\/)[^/@\\s]*@/i, \"$1\");\n}\n\nexport function truncate(s: string, max: number): string {\n return s.length > max ? s.slice(0, max - TRUNCATED.length) + TRUNCATED : s;\n}\n\nfunction isPlainObject(v: unknown): v is Fields {\n if (typeof v !== \"object\" || v === null) return false;\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n}\n\nfunction isUrlKey(key: string): boolean {\n return key === \"http_path\" || key === \"http_url\" || key.endsWith(\"_url\");\n}\n\nfunction flattenInto(out: LogRecord, prefix: string, obj: Fields, depth: number, opts: NormalizeOptions): void {\n for (const [rawKey, value] of Object.entries(obj)) {\n const key = opts.snakeCase ? toSnakeCase(rawKey) : rawKey;\n const full = prefix ? `${prefix}_${key}` : key;\n if (value === undefined || typeof value === \"function\" || typeof value === \"symbol\") continue;\n if (value !== null && (opts.redactor.isSensitive(rawKey) || opts.redactor.isSensitive(full))) {\n out[full] = REDACTED;\n continue;\n }\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") {\n out[full] = value;\n } else if (typeof value === \"number\") {\n out[full] = Number.isFinite(value) ? value : String(value);\n } else if (typeof value === \"bigint\") {\n out[full] = value.toString();\n } else if (value instanceof Date) {\n out[full] = Number.isNaN(value.getTime()) ? \"Invalid Date\" : value.toISOString();\n } else if (isErrorLike(value)) {\n out[full] = `${value.name}: ${value.message}`;\n } else if (isPlainObject(value) && depth < MAX_DEPTH - 1) {\n flattenInto(out, full, value, depth + 1, opts);\n } else {\n // Arrays, deep objects, class instances: stringified with sensitive keys redacted.\n out[full] = opts.redactor.json(value);\n }\n }\n}\n\nfunction finalize(key: string, value: Primitive, opts: NormalizeOptions): Primitive {\n if (typeof value !== \"string\") return value;\n const v = opts.redactor.scrub(isUrlKey(key) ? stripUrl(value) : value);\n return truncate(v, key === \"err_stack\" ? MAX_STACK : MAX_STRING);\n}\n\nexport function buildRecord(\n level: Level,\n message: unknown,\n sources: (Fields | undefined)[],\n opts: NormalizeOptions,\n): LogRecord {\n const merged: Fields = {};\n for (const source of sources) if (source) Object.assign(merged, source);\n\n let err: unknown;\n if (merged.err !== undefined && merged.err !== null) {\n err = merged.err;\n delete merged.err;\n } else if (isErrorLike(merged.error)) {\n err = merged.error;\n delete merged.error;\n }\n\n const flat: LogRecord = {};\n flattenInto(flat, \"\", merged, 0, opts);\n if (err !== undefined) Object.assign(flat, serializeError(err));\n\n const text = typeof message === \"string\" ? message : opts.redactor.json(message);\n const out: LogRecord = { level, message: truncate(opts.redactor.scrub(text), MAX_MESSAGE) };\n for (const [k, v] of Object.entries(flat)) {\n const key = RESERVED_KEYS.has(k) ? `app_${k}` : k;\n let value = finalize(key, v, opts);\n if (opts.hook && !HOOK_EXEMPT.has(key)) {\n const replaced = opts.hook(key, value);\n if (replaced === undefined) continue;\n value = typeof replaced === \"object\" && replaced !== null ? opts.redactor.json(replaced) : (replaced as Primitive);\n }\n out[key] = value;\n }\n return out;\n}\n\nfunction byteLength(s: string): number {\n // UTF-8 uses at most 3 bytes per UTF-16 code unit: skip encoding short lines.\n if (s.length * 3 <= MAX_LINE_BYTES) return s.length;\n return new TextEncoder().encode(s).length;\n}\n\nexport function serializeRecord(record: LogRecord): string {\n const line = JSON.stringify(record);\n if (byteLength(line) <= MAX_LINE_BYTES) return line;\n const small: LogRecord = {};\n for (const [k, v] of Object.entries(record)) {\n if (KEEP_ON_SHRINK.has(k) || k.startsWith(\"http_\")) small[k] = v;\n }\n small.log_truncated = true;\n return JSON.stringify(small);\n}\n","// No imports: this module is shared with the edge entry (next-edge).\nexport const REQUEST_ID_RE = /^[A-Za-z0-9._-]{1,64}$/;\nconst TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;\n\nexport function newRequestId(): string {\n return globalThis.crypto.randomUUID();\n}\n\nfunction first(header?: string | string[] | null): string | undefined {\n return Array.isArray(header) ? header[0] : (header ?? undefined);\n}\n\nexport function resolveRequestId(header?: string | string[] | null): string {\n const value = first(header);\n return value && REQUEST_ID_RE.test(value) ? value : newRequestId();\n}\n\nexport function parseTraceparent(header?: string | string[] | null): { trace_id: string; span_id: string } | undefined {\n const value = first(header)?.trim().toLowerCase();\n const m = value ? TRACEPARENT_RE.exec(value) : null;\n if (!m) return undefined;\n const [, version, traceId, spanId] = m as unknown as [string, string, string, string];\n if (version === \"ff\" || /^0+$/.test(traceId) || /^0+$/.test(spanId)) return undefined;\n return { trace_id: traceId, span_id: spanId };\n}\n","import { getContext } from \"./context\";\nimport { markLogged, serializeError } from \"./error\";\nimport type { Logger } from \"./logger\";\nimport { stripUrl, type Fields } from \"./normalize\";\nimport { parseTraceparent, resolveRequestId } from \"./request-id\";\n\nexport { newRequestId, parseTraceparent, REQUEST_ID_RE, resolveRequestId } from \"./request-id\";\n\nexport type HeaderSource = Headers | Record<string, string | string[] | undefined>;\n\nfunction header(headers: HeaderSource, name: string): string | string[] | undefined {\n if (typeof (headers as Headers).get === \"function\") return (headers as Headers).get(name) ?? undefined;\n return (headers as Record<string, string | string[] | undefined>)[name];\n}\n\nexport function requestContextFields(headers: HeaderSource): { request_id: string; trace_id?: string; span_id?: string } {\n const out: { request_id: string; trace_id?: string; span_id?: string } = {\n request_id: resolveRequestId(header(headers, \"x-request-id\")),\n };\n const tp = parseTraceparent(header(headers, \"traceparent\"));\n if (tp) Object.assign(out, tp);\n return out;\n}\n\n// Headers to forward to services we own, so their logs share request_id / trace_id.\nexport function propagationHeaders(): Record<string, string> {\n const ctx = getContext();\n const out: Record<string, string> = {};\n if (typeof ctx.request_id === \"string\") out[\"x-request-id\"] = ctx.request_id;\n if (typeof ctx.trace_id === \"string\" && typeof ctx.span_id === \"string\") {\n out.traceparent = `00-${ctx.trace_id}-${ctx.span_id}-01`;\n }\n return out;\n}\n\nexport const DEFAULT_SKIP_PATHS: (string | RegExp)[] = [\n // Also under a prefix such as /api/v1/health.\n /(^|\\/)(health|healthz|ready|readyz|livez)\\/?$/,\n \"/favicon.ico\",\n /^\\/_next\\/static\\//,\n];\n\nexport interface HttpLogOptions {\n skipPaths?: (string | RegExp)[];\n}\n\nexport interface HttpRequestInfo {\n method: string;\n route?: string;\n path: string;\n status: number;\n duration_ms?: number;\n err?: unknown;\n fields?: Fields;\n}\n\nexport function shouldSkip(method: string, path: string, skipPaths: (string | RegExp)[] = DEFAULT_SKIP_PATHS): boolean {\n if (method.toUpperCase() === \"OPTIONS\") return true;\n const p = stripUrl(path);\n return skipPaths.some((s) => (typeof s === \"string\" ? p === s : s.test(p)));\n}\n\n// Writes the one `http.request` summary line for an inbound request. Skipped paths are still\n// logged when they fail with a 5xx, so a failing health check stays visible.\nexport function logHttpRequest(log: Logger, info: HttpRequestInfo, opts: HttpLogOptions = {}): void {\n if (info.status < 500 && shouldSkip(info.method, info.path, opts.skipPaths)) return;\n const level = info.status >= 500 ? \"error\" : info.status >= 400 ? \"warn\" : \"info\";\n const path = stripUrl(info.path);\n const errFields: Fields = {};\n if (info.err !== undefined && info.err !== null) {\n Object.assign(errFields, serializeError(info.err));\n if (info.status < 500) delete errFields.err_stack;\n markLogged(info.err);\n }\n // Route templates keep the message bounded; the raw path would make one message per URL.\n const message = info.route ? `${info.method} ${info.route} ${info.status}` : `${info.method} ${info.status}`;\n log[level](message, {\n ...info.fields,\n event: \"http.request\",\n http_method: info.method,\n http_route: info.route,\n http_path: path,\n http_status: info.status,\n duration_ms: info.duration_ms === undefined ? undefined : Math.round(info.duration_ms),\n ...errFields,\n });\n}\n","import type { ErrorRequestHandler, RequestHandler } from \"express\";\nimport { attachStore, recordErrorIn, runWithStore, storeOf, type ContextStore } from \"./context\";\nimport { logHttpRequest, requestContextFields, type HttpLogOptions } from \"./http\";\nimport type { Logger } from \"./logger\";\n\nfunction joinRoute(baseUrl: string | undefined, path: unknown): string {\n return `${baseUrl ?? \"\"}${String(path)}` || \"/\";\n}\n\n// Express assigns req.route when a route matches, while req.baseUrl still holds the mount\n// path. It resets baseUrl before the response ends, so capture the full template here.\nfunction captureRoute(req: { baseUrl?: string }, store: ContextStore): void {\n let value: unknown;\n Object.defineProperty(req, \"route\", {\n configurable: true,\n enumerable: true,\n get: () => value,\n set: (route: { path?: unknown } | undefined) => {\n value = route;\n if (route?.path !== undefined) store.route = joinRoute(req.baseUrl, route.path);\n },\n });\n}\n\n/** Mount before routes. Opens the request context and writes the http.request line. */\nexport function requestLogger(log: Logger, opts: HttpLogOptions = {}): RequestHandler {\n return (req, res, next) => {\n const ctx = requestContextFields(req.headers);\n res.setHeader(\"x-request-id\", ctx.request_id);\n const start = performance.now();\n runWithStore(ctx, (store) => {\n attachStore(res, store);\n captureRoute(req, store);\n let done = false;\n const finish = () => {\n if (done) return;\n done = true;\n logHttpRequest(\n log,\n {\n method: req.method,\n route: store.route,\n path: req.originalUrl,\n status: res.writableFinished || res.headersSent ? res.statusCode : 499,\n duration_ms: performance.now() - start,\n err: store.error,\n fields: store.fields,\n },\n opts,\n );\n };\n res.once(\"finish\", finish);\n res.once(\"close\", finish);\n next();\n });\n };\n}\n\n/** Mount after routes, before your own error handler. Records the error for the summary line. */\nexport function errorRecorder(): ErrorRequestHandler {\n return (err, _req, res, next) => {\n const store = storeOf(res);\n if (store) recordErrorIn(store, err);\n next(err);\n };\n}\n","import type { FastifyPluginCallback, FastifyRequest } from \"fastify\";\nimport { attachStore, recordErrorIn, runWithStore, storeOf } from \"./context\";\nimport { logHttpRequest, requestContextFields, type HttpLogOptions } from \"./http\";\nimport type { Logger } from \"./logger\";\n\nexport interface FastifyLoggerOptions extends HttpLogOptions {\n log: Logger;\n}\n\nconst starts = new WeakMap<object, number>();\nconst finished = new WeakSet<object>();\n\nconst plugin: FastifyPluginCallback<FastifyLoggerOptions> = (app, opts, done) => {\n const summary = (req: FastifyRequest, status: number) => {\n if (finished.has(req)) return;\n finished.add(req);\n const store = storeOf(req);\n const start = starts.get(req);\n logHttpRequest(\n opts.log,\n {\n method: req.method,\n route: req.routeOptions?.url,\n path: req.url,\n status,\n duration_ms: start === undefined ? undefined : performance.now() - start,\n err: store?.error,\n fields: store?.fields,\n },\n opts,\n );\n };\n\n app.addHook(\"onRequest\", (req, reply, next) => {\n const ctx = requestContextFields(req.headers);\n reply.header(\"x-request-id\", ctx.request_id);\n starts.set(req, performance.now());\n runWithStore(ctx, (store) => {\n attachStore(req, store);\n attachStore(reply, store);\n next();\n });\n });\n app.addHook(\"onError\", (_req, reply, err, next) => {\n const store = storeOf(reply);\n if (store) recordErrorIn(store, err);\n next();\n });\n app.addHook(\"onResponse\", (req, reply, next) => {\n summary(req, reply.statusCode);\n next();\n });\n // Client closed the connection before the response (Fastify >= 4.21).\n app.addHook(\"onRequestAbort\", (req, next) => {\n summary(req, 499);\n next();\n });\n done();\n};\n\n// Same effect as fastify-plugin: hooks apply to the whole app, not an encapsulated child.\n(plugin as unknown as Record<symbol, boolean>)[Symbol.for(\"skip-override\")] = true;\n\nexport const fastifyLogger = plugin;\n"],"mappings":"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","names":["header"]}
package/dist/nest.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  requestLogger
3
- } from "./chunk-BP264YAM.js";
3
+ } from "./chunk-42ZPVKHN.js";
4
4
  import {
5
5
  fastifyLogger
6
- } from "./chunk-DO5HNNTP.js";
7
- import "./chunk-KD5H5G6Y.js";
6
+ } from "./chunk-7DW57ECW.js";
7
+ import "./chunk-754Y5TQX.js";
8
8
  import "./chunk-KLBL7QEN.js";
9
9
  import {
10
10
  currentStore,
package/dist/next.cjs CHANGED
@@ -226,7 +226,8 @@ function requestContextFields(headers) {
226
226
  return out;
227
227
  }
228
228
  var DEFAULT_SKIP_PATHS = [
229
- /^\/(health|healthz|ready|readyz|livez)\/?$/,
229
+ // Also under a prefix such as /api/v1/health.
230
+ /(^|\/)(health|healthz|ready|readyz|livez)\/?$/,
230
231
  "/favicon.ico",
231
232
  /^\/_next\/static\//
232
233
  ];
@@ -236,7 +237,7 @@ function shouldSkip(method, path, skipPaths = DEFAULT_SKIP_PATHS) {
236
237
  return skipPaths.some((s) => typeof s === "string" ? p === s : s.test(p));
237
238
  }
238
239
  function logHttpRequest(log, info, opts = {}) {
239
- if (shouldSkip(info.method, info.path, opts.skipPaths)) return;
240
+ if (info.status < 500 && shouldSkip(info.method, info.path, opts.skipPaths)) return;
240
241
  const level = info.status >= 500 ? "error" : info.status >= 400 ? "warn" : "info";
241
242
  const path = stripUrl(info.path);
242
243
  const errFields = {};
@@ -301,11 +302,19 @@ var warningState = globalSingleton("warnings", () => ({ installed: false }));
301
302
  function raiseStackTraceLimit(limit) {
302
303
  if (Error.stackTraceLimit < limit) Error.stackTraceLimit = limit;
303
304
  }
305
+ var MAX_SEEN_WARNINGS = 200;
304
306
  function logProcessWarnings(log, opts = {}) {
305
307
  if (warningState.installed || typeof process === "undefined" || typeof process.on !== "function") return;
306
308
  warningState.installed = true;
307
309
  raiseStackTraceLimit(opts.stackTraceLimit ?? 30);
310
+ if (opts.replaceDefault ?? true) {
311
+ for (const l of process.listeners("warning")) if (l.name === "onWarning") process.off("warning", l);
312
+ }
313
+ const seen = /* @__PURE__ */ new Set();
308
314
  process.on("warning", (warning) => {
315
+ const key = `${warning?.name}:${warning?.code ?? ""}:${warning?.message}`;
316
+ if (seen.has(key)) return;
317
+ if (seen.size < MAX_SEEN_WARNINGS) seen.add(key);
309
318
  log.warn("process warning", { event: "process.warning", err: warning });
310
319
  });
311
320
  }
package/dist/next.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/next.ts","../src/context.ts","../src/global.ts","../src/redact.ts","../src/error.ts","../src/normalize.ts","../src/request-id.ts","../src/http.ts","../src/process.ts"],"sourcesContent":["import { runWithStore } from \"./context\";\nimport { isErrorLike, isLogged, markLogged } from \"./error\";\nimport { logHttpRequest, requestContextFields, type HeaderSource, type HttpLogOptions } from \"./http\";\nimport type { Logger } from \"./logger\";\nimport type { Fields } from \"./normalize\";\nimport { installProcessHandlers, logProcessWarnings, logStartup } from \"./process\";\n\nexport interface WithLoggingOptions extends HttpLogOptions {\n /** Route template, e.g. \"/api/orders/[id]\". Left empty when not given. */\n route?: string;\n}\n\n/** Status for Next's control-flow errors (redirect(), notFound(), forbidden()...), else undefined. */\nexport function nextControlStatus(err: unknown): number | undefined {\n const digest = (err as { digest?: unknown } | null)?.digest;\n if (typeof digest !== \"string\") return undefined;\n if (digest.startsWith(\"NEXT_REDIRECT\")) {\n const status = Number(digest.split(\";\")[3]);\n return Number.isInteger(status) && status >= 300 ? status : 307;\n }\n if (digest === \"NEXT_NOT_FOUND\") return 404;\n if (digest.startsWith(\"NEXT_HTTP_ERROR_FALLBACK;\")) return Number(digest.split(\";\")[1]) || 404;\n return undefined;\n}\n\nfunction asError(value: unknown): unknown {\n if (typeof value === \"object\" && value !== null) return value;\n return Object.assign(new Error(String(value)), { name: \"NonErrorThrown\", thrown: value });\n}\n\nfunction trySetHeader(res: Response, name: string, value: string): void {\n try {\n res.headers.set(name, value);\n } catch {\n // Some responses (Response.redirect, proxied fetch responses) have immutable headers.\n }\n}\n\n/** Any App Router route handler: `()`, `(request)` or `(request, { params })`. */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type RouteHandler = (...args: any[]) => Response | Promise<Response>;\n\n/**\n * Wraps an App Router route handler: request context + one http.request line. The returned\n * function has exactly the handler's parameters, so Next's route type checks still pass.\n */\nexport function withLogging<H extends RouteHandler>(\n log: Logger,\n handler: H,\n opts: WithLoggingOptions = {},\n): (...args: Parameters<H>) => Promise<Awaited<ReturnType<H>>> {\n return async (...args): Promise<Awaited<ReturnType<H>>> => {\n const request: unknown = args[0];\n // Next always passes the request; direct calls in unit tests may not.\n if (!(request instanceof Request)) return (await handler(...args)) as Awaited<ReturnType<H>>;\n const ctx = requestContextFields(request.headers);\n const path = new URL(request.url).pathname;\n const start = performance.now();\n return await runWithStore(ctx, async (store): Promise<Awaited<ReturnType<H>>> => {\n const summary = (status: number, err: unknown) =>\n logHttpRequest(\n log,\n {\n method: request.method,\n route: opts.route,\n path,\n status,\n duration_ms: performance.now() - start,\n err,\n fields: store.fields,\n },\n opts,\n );\n try {\n const res = (await handler(...args)) as Awaited<ReturnType<H>>;\n summary((res as Response).status, store.error);\n trySetHeader(res as Response, \"x-request-id\", ctx.request_id);\n return res;\n } catch (caught) {\n const control = nextControlStatus(caught);\n // Non-objects cannot be marked as logged, so onRequestError would log them again.\n const err = control === undefined ? asError(caught) : caught;\n summary(control ?? 500, control === undefined ? err : store.error);\n throw err;\n }\n });\n };\n}\n\n/**\n * patchConsole skip predicate for Next.js: Next prints a render error with console.error right\n * before calling onRequestError, by then the error carries a digest. Dropping that console\n * call leaves the single, richer onRequestError line.\n */\nexport function nextConsoleSkip(args: unknown[]): boolean {\n return args.some((arg) => isErrorLike(arg) && typeof (arg as { digest?: unknown }).digest === \"string\");\n}\n\nexport interface NextRequestInfo {\n path?: string;\n method?: string;\n headers?: Record<string, string | string[] | undefined>;\n}\n\nexport interface NextErrorContext {\n routerKind?: string;\n routePath?: string;\n routeType?: string;\n}\n\n/** `export const onRequestError = nextOnRequestError(log)` in instrumentation.ts. */\nexport function nextOnRequestError(log: Logger, opts: HttpLogOptions = {}) {\n return async (err: unknown, request: NextRequestInfo, context: NextErrorContext): Promise<void> => {\n if (isLogged(err) || nextControlStatus(err) !== undefined) return;\n const digest = (err as { digest?: unknown } | null)?.digest;\n logHttpRequest(\n log,\n {\n method: request?.method ?? \"GET\",\n route: context?.routePath,\n path: request?.path ?? \"\",\n status: 500,\n err,\n fields: {\n ...requestContextFields(request?.headers ?? {}),\n next_route_type: context?.routeType,\n next_router_kind: context?.routerKind,\n next_digest: typeof digest === \"string\" ? digest : undefined,\n },\n },\n opts,\n );\n };\n}\n\n/** Call from `register()` in instrumentation.ts. Does nothing on the edge runtime. */\nexport function registerNext(log: Logger, fields?: Fields): void {\n if (process.env.NEXT_RUNTIME && process.env.NEXT_RUNTIME !== \"nodejs\") return;\n // Next keeps serving after an unhandled rejection, so do not exit.\n installProcessHandlers(log, { exitOnCrash: false });\n logProcessWarnings(log);\n logStartup(log, { port: process.env.PORT ? Number(process.env.PORT) : undefined, ...fields });\n}\n\nexport interface WithActionOptions {\n /** Request headers source. Defaults to `headers()` from next/headers. */\n headers?: () => HeaderSource | Promise<HeaderSource>;\n}\n\nasync function nextHeaders(): Promise<HeaderSource> {\n try {\n const mod = (await import(\"next/headers\")) as { headers: () => HeaderSource | Promise<HeaderSource> };\n return await mod.headers();\n } catch {\n // Called outside a request (tests, scripts) or Next is not installed.\n return {};\n }\n}\n\n/**\n * Wraps a server action: request context (request_id from x-request-id) plus one\n * `action.completed` / `action.failed` line. redirect()/notFound() count as completed.\n */\nexport function withAction<A extends unknown[], R>(\n log: Logger,\n name: string,\n action: (...args: A) => Promise<R>,\n opts: WithActionOptions = {},\n): (...args: A) => Promise<R> {\n return async (...args) => {\n const headers = await (opts.headers ?? nextHeaders)();\n const start = performance.now();\n return runWithStore({ ...requestContextFields(headers), action_name: name }, async (store) => {\n const fields = () => ({ ...store.fields, duration_ms: Math.round(performance.now() - start) });\n try {\n const result = await action(...args);\n log.info(\"server action completed\", { ...fields(), event: \"action.completed\", err: store.error });\n return result;\n } catch (caught) {\n const control = nextControlStatus(caught);\n if (control !== undefined) {\n log.info(\"server action completed\", { ...fields(), event: \"action.completed\", next_control_status: control });\n throw caught;\n }\n const err = asError(caught);\n log.error(\"server action failed\", { ...fields(), event: \"action.failed\", err });\n markLogged(err);\n throw err;\n }\n });\n };\n}\n","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","import { isErrorLike, serializeError } from \"./error\";\nimport type { Level } from \"./levels\";\nimport { REDACTED, toSnakeCase, type Redactor } from \"./redact\";\n\nexport { DEFAULT_REDACT_KEYS, toSnakeCase } from \"./redact\";\n\n/** Final per-field hook: return a replacement, or undefined to drop the field. */\nexport type RedactHook = (key: string, value: Primitive) => unknown;\n\nexport type Fields = Record<string, unknown>;\nexport type Primitive = string | number | boolean | null;\nexport type LogRecord = Record<string, Primitive>;\n\nexport interface NormalizeOptions {\n snakeCase: boolean;\n redactor: Redactor;\n hook?: RedactHook;\n}\n\n// Fields the redact hook never sees, so every line keeps its level, message and event.\nconst HOOK_EXEMPT = new Set([\"level\", \"message\", \"event\"]);\n\n// Keys owned by Vector (overwritten or deleted on ingest) or by the logger itself.\nexport const RESERVED_KEYS = new Set([\n \"project\", \"service\", \"vps\", \"version\", \"container_name\", \"container_id\", \"image\", \"stream\",\n \"_timestamp\", \"label\", \"host\", \"source_type\", \"msg\", \"lvl\", \"severity\", \"level\", \"message\",\n]);\n\nexport const MAX_LINE_BYTES = 16000;\nconst MAX_MESSAGE = 500;\nconst MAX_STACK = 4000;\nconst MAX_STRING = 1000;\nconst MAX_DEPTH = 3;\nconst TRUNCATED = \"…[truncated]\";\n\n// Fields kept when a line is too large.\nconst KEEP_ON_SHRINK = new Set([\n \"level\", \"message\", \"event\", \"request_id\", \"trace_id\", \"span_id\", \"job_name\", \"job_id\", \"job_attempt\",\n \"user_id\", \"tenant_id\", \"err_type\", \"err_message\", \"err_code\",\n]);\n\nexport function stripUrl(url: string): string {\n const noQuery = url.split(/[?#]/, 1)[0] ?? \"\";\n return noQuery.replace(/^([a-z][a-z0-9+.-]*:\\/\\/)[^/@\\s]*@/i, \"$1\");\n}\n\nexport function truncate(s: string, max: number): string {\n return s.length > max ? s.slice(0, max - TRUNCATED.length) + TRUNCATED : s;\n}\n\nfunction isPlainObject(v: unknown): v is Fields {\n if (typeof v !== \"object\" || v === null) return false;\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n}\n\nfunction isUrlKey(key: string): boolean {\n return key === \"http_path\" || key === \"http_url\" || key.endsWith(\"_url\");\n}\n\nfunction flattenInto(out: LogRecord, prefix: string, obj: Fields, depth: number, opts: NormalizeOptions): void {\n for (const [rawKey, value] of Object.entries(obj)) {\n const key = opts.snakeCase ? toSnakeCase(rawKey) : rawKey;\n const full = prefix ? `${prefix}_${key}` : key;\n if (value === undefined || typeof value === \"function\" || typeof value === \"symbol\") continue;\n if (value !== null && (opts.redactor.isSensitive(rawKey) || opts.redactor.isSensitive(full))) {\n out[full] = REDACTED;\n continue;\n }\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") {\n out[full] = value;\n } else if (typeof value === \"number\") {\n out[full] = Number.isFinite(value) ? value : String(value);\n } else if (typeof value === \"bigint\") {\n out[full] = value.toString();\n } else if (value instanceof Date) {\n out[full] = Number.isNaN(value.getTime()) ? \"Invalid Date\" : value.toISOString();\n } else if (isErrorLike(value)) {\n out[full] = `${value.name}: ${value.message}`;\n } else if (isPlainObject(value) && depth < MAX_DEPTH - 1) {\n flattenInto(out, full, value, depth + 1, opts);\n } else {\n // Arrays, deep objects, class instances: stringified with sensitive keys redacted.\n out[full] = opts.redactor.json(value);\n }\n }\n}\n\nfunction finalize(key: string, value: Primitive, opts: NormalizeOptions): Primitive {\n if (typeof value !== \"string\") return value;\n const v = opts.redactor.scrub(isUrlKey(key) ? stripUrl(value) : value);\n return truncate(v, key === \"err_stack\" ? MAX_STACK : MAX_STRING);\n}\n\nexport function buildRecord(\n level: Level,\n message: unknown,\n sources: (Fields | undefined)[],\n opts: NormalizeOptions,\n): LogRecord {\n const merged: Fields = {};\n for (const source of sources) if (source) Object.assign(merged, source);\n\n let err: unknown;\n if (merged.err !== undefined && merged.err !== null) {\n err = merged.err;\n delete merged.err;\n } else if (isErrorLike(merged.error)) {\n err = merged.error;\n delete merged.error;\n }\n\n const flat: LogRecord = {};\n flattenInto(flat, \"\", merged, 0, opts);\n if (err !== undefined) Object.assign(flat, serializeError(err));\n\n const text = typeof message === \"string\" ? message : opts.redactor.json(message);\n const out: LogRecord = { level, message: truncate(opts.redactor.scrub(text), MAX_MESSAGE) };\n for (const [k, v] of Object.entries(flat)) {\n const key = RESERVED_KEYS.has(k) ? `app_${k}` : k;\n let value = finalize(key, v, opts);\n if (opts.hook && !HOOK_EXEMPT.has(key)) {\n const replaced = opts.hook(key, value);\n if (replaced === undefined) continue;\n value = typeof replaced === \"object\" && replaced !== null ? opts.redactor.json(replaced) : (replaced as Primitive);\n }\n out[key] = value;\n }\n return out;\n}\n\nfunction byteLength(s: string): number {\n // UTF-8 uses at most 3 bytes per UTF-16 code unit: skip encoding short lines.\n if (s.length * 3 <= MAX_LINE_BYTES) return s.length;\n return new TextEncoder().encode(s).length;\n}\n\nexport function serializeRecord(record: LogRecord): string {\n const line = JSON.stringify(record);\n if (byteLength(line) <= MAX_LINE_BYTES) return line;\n const small: LogRecord = {};\n for (const [k, v] of Object.entries(record)) {\n if (KEEP_ON_SHRINK.has(k) || k.startsWith(\"http_\")) small[k] = v;\n }\n small.log_truncated = true;\n return JSON.stringify(small);\n}\n","// No imports: this module is shared with the edge entry (next-edge).\nexport const REQUEST_ID_RE = /^[A-Za-z0-9._-]{1,64}$/;\nconst TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;\n\nexport function newRequestId(): string {\n return globalThis.crypto.randomUUID();\n}\n\nfunction first(header?: string | string[] | null): string | undefined {\n return Array.isArray(header) ? header[0] : (header ?? undefined);\n}\n\nexport function resolveRequestId(header?: string | string[] | null): string {\n const value = first(header);\n return value && REQUEST_ID_RE.test(value) ? value : newRequestId();\n}\n\nexport function parseTraceparent(header?: string | string[] | null): { trace_id: string; span_id: string } | undefined {\n const value = first(header)?.trim().toLowerCase();\n const m = value ? TRACEPARENT_RE.exec(value) : null;\n if (!m) return undefined;\n const [, version, traceId, spanId] = m as unknown as [string, string, string, string];\n if (version === \"ff\" || /^0+$/.test(traceId) || /^0+$/.test(spanId)) return undefined;\n return { trace_id: traceId, span_id: spanId };\n}\n","import { getContext } from \"./context\";\nimport { markLogged, serializeError } from \"./error\";\nimport type { Logger } from \"./logger\";\nimport { stripUrl, type Fields } from \"./normalize\";\nimport { parseTraceparent, resolveRequestId } from \"./request-id\";\n\nexport { newRequestId, parseTraceparent, REQUEST_ID_RE, resolveRequestId } from \"./request-id\";\n\nexport type HeaderSource = Headers | Record<string, string | string[] | undefined>;\n\nfunction header(headers: HeaderSource, name: string): string | string[] | undefined {\n if (typeof (headers as Headers).get === \"function\") return (headers as Headers).get(name) ?? undefined;\n return (headers as Record<string, string | string[] | undefined>)[name];\n}\n\nexport function requestContextFields(headers: HeaderSource): { request_id: string; trace_id?: string; span_id?: string } {\n const out: { request_id: string; trace_id?: string; span_id?: string } = {\n request_id: resolveRequestId(header(headers, \"x-request-id\")),\n };\n const tp = parseTraceparent(header(headers, \"traceparent\"));\n if (tp) Object.assign(out, tp);\n return out;\n}\n\n// Headers to forward to services we own, so their logs share request_id / trace_id.\nexport function propagationHeaders(): Record<string, string> {\n const ctx = getContext();\n const out: Record<string, string> = {};\n if (typeof ctx.request_id === \"string\") out[\"x-request-id\"] = ctx.request_id;\n if (typeof ctx.trace_id === \"string\" && typeof ctx.span_id === \"string\") {\n out.traceparent = `00-${ctx.trace_id}-${ctx.span_id}-01`;\n }\n return out;\n}\n\nexport const DEFAULT_SKIP_PATHS: (string | RegExp)[] = [\n /^\\/(health|healthz|ready|readyz|livez)\\/?$/,\n \"/favicon.ico\",\n /^\\/_next\\/static\\//,\n];\n\nexport interface HttpLogOptions {\n skipPaths?: (string | RegExp)[];\n}\n\nexport interface HttpRequestInfo {\n method: string;\n route?: string;\n path: string;\n status: number;\n duration_ms?: number;\n err?: unknown;\n fields?: Fields;\n}\n\nexport function shouldSkip(method: string, path: string, skipPaths: (string | RegExp)[] = DEFAULT_SKIP_PATHS): boolean {\n if (method.toUpperCase() === \"OPTIONS\") return true;\n const p = stripUrl(path);\n return skipPaths.some((s) => (typeof s === \"string\" ? p === s : s.test(p)));\n}\n\n// Writes the one `http.request` summary line for an inbound request.\nexport function logHttpRequest(log: Logger, info: HttpRequestInfo, opts: HttpLogOptions = {}): void {\n if (shouldSkip(info.method, info.path, opts.skipPaths)) return;\n const level = info.status >= 500 ? \"error\" : info.status >= 400 ? \"warn\" : \"info\";\n const path = stripUrl(info.path);\n const errFields: Fields = {};\n if (info.err !== undefined && info.err !== null) {\n Object.assign(errFields, serializeError(info.err));\n if (info.status < 500) delete errFields.err_stack;\n markLogged(info.err);\n }\n // Route templates keep the message bounded; the raw path would make one message per URL.\n const message = info.route ? `${info.method} ${info.route} ${info.status}` : `${info.method} ${info.status}`;\n log[level](message, {\n ...info.fields,\n event: \"http.request\",\n http_method: info.method,\n http_route: info.route,\n http_path: path,\n http_status: info.status,\n duration_ms: info.duration_ms === undefined ? undefined : Math.round(info.duration_ms),\n ...errFields,\n });\n}\n","import { globalSingleton } from \"./global\";\nimport type { Logger } from \"./logger\";\nimport type { Fields } from \"./normalize\";\n\nexport function logStartup(log: Logger, fields: Fields = {}): void {\n log.info(\"app started\", {\n event: \"app.started\",\n runtime_version: `node ${process.versions.node}`,\n log_level: log.level,\n ...fields,\n });\n}\n\n/** Call at the start of your own shutdown handler (e.g. on SIGTERM). The library never handles signals. */\nexport function logShutdown(log: Logger, fields: Fields = {}): void {\n log.info(\"app stopping\", { event: \"app.stopping\", ...fields });\n}\n\nexport interface ProcessHandlerOptions {\n /** Exit with code 1 after an uncaught error. Next.js keeps serving, so its adapter passes false. */\n exitOnCrash?: boolean;\n /** Time other crash handlers (Sentry, APM) get to flush before exit. Default 1000 ms. */\n exitDelayMs?: number;\n exit?: (code: number) => void;\n}\n\nexport function crashHandlers(log: Logger, opts: ProcessHandlerOptions = {}) {\n const exitOnCrash = opts.exitOnCrash ?? true;\n const exitDelayMs = opts.exitDelayMs ?? 1000;\n const exit = opts.exit ?? ((code: number) => process.exit(code));\n let exiting = false;\n const crash = (source: string, err: unknown) => {\n if (!exitOnCrash) {\n log.error(\"unhandled error\", { event: \"app.unhandled_error\", crash_source: source, err });\n return;\n }\n log.fatal(\"app crashed\", { event: \"app.crashed\", crash_source: source, err });\n if (exiting) return;\n exiting = true;\n if (typeof process !== \"undefined\") process.exitCode = 1;\n // Deferred so listeners registered after ours still run; unref so a process with nothing\n // left to do exits on its own (with exitCode 1) without waiting.\n const timer = setTimeout(() => exit(1), exitDelayMs);\n (timer as { unref?: () => void }).unref?.();\n };\n return {\n onUncaught: (err: unknown) => crash(\"uncaughtException\", err),\n onRejection: (reason: unknown) => crash(\"unhandledRejection\", reason),\n };\n}\n\nconst state = globalSingleton(\"process\", () => ({ installed: false }));\n\n/** Logs uncaught exceptions and unhandled rejections. Does not touch SIGTERM/SIGINT. */\nexport function installProcessHandlers(log: Logger, opts: ProcessHandlerOptions = {}): void {\n if (state.installed || typeof process === \"undefined\" || typeof process.on !== \"function\") return;\n state.installed = true;\n const h = crashHandlers(log, opts);\n process.on(\"uncaughtException\", h.onUncaught);\n process.on(\"unhandledRejection\", h.onRejection);\n}\n\nconst warningState = globalSingleton(\"warnings\", () => ({ installed: false }));\n\n/**\n * Node keeps 10 frames by default; ORM/framework frames (knex, Strapi, Prisma) often use all of\n * them, hiding the app code that triggered a warning or error. Only ever raises the limit.\n */\nexport function raiseStackTraceLimit(limit: number): void {\n if (Error.stackTraceLimit < limit) Error.stackTraceLimit = limit;\n}\n\n/**\n * Logs Node process warnings (deprecations, MaxListeners...) as `process.warning` lines whose\n * err_stack points at the code that triggered them. Node still prints its own stderr copy\n * unless the process runs with --no-warnings (NODE_OPTIONS=--no-warnings).\n * Also raises Error.stackTraceLimit (default 30) so the stack reaches past framework frames.\n */\nexport function logProcessWarnings(log: Logger, opts: { stackTraceLimit?: number } = {}): void {\n if (warningState.installed || typeof process === \"undefined\" || typeof process.on !== \"function\") return;\n warningState.installed = true;\n raiseStackTraceLimit(opts.stackTraceLimit ?? 30);\n process.on(\"warning\", (warning: Error) => {\n log.warn(\"process warning\", { event: \"process.warning\", err: warning });\n });\n}\n"],"mappings":"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","names":["header"]}
1
+ {"version":3,"sources":["../src/next.ts","../src/context.ts","../src/global.ts","../src/redact.ts","../src/error.ts","../src/normalize.ts","../src/request-id.ts","../src/http.ts","../src/process.ts"],"sourcesContent":["import { runWithStore } from \"./context\";\nimport { isErrorLike, isLogged, markLogged } from \"./error\";\nimport { logHttpRequest, requestContextFields, type HeaderSource, type HttpLogOptions } from \"./http\";\nimport type { Logger } from \"./logger\";\nimport type { Fields } from \"./normalize\";\nimport { installProcessHandlers, logProcessWarnings, logStartup } from \"./process\";\n\nexport interface WithLoggingOptions extends HttpLogOptions {\n /** Route template, e.g. \"/api/orders/[id]\". Left empty when not given. */\n route?: string;\n}\n\n/** Status for Next's control-flow errors (redirect(), notFound(), forbidden()...), else undefined. */\nexport function nextControlStatus(err: unknown): number | undefined {\n const digest = (err as { digest?: unknown } | null)?.digest;\n if (typeof digest !== \"string\") return undefined;\n if (digest.startsWith(\"NEXT_REDIRECT\")) {\n const status = Number(digest.split(\";\")[3]);\n return Number.isInteger(status) && status >= 300 ? status : 307;\n }\n if (digest === \"NEXT_NOT_FOUND\") return 404;\n if (digest.startsWith(\"NEXT_HTTP_ERROR_FALLBACK;\")) return Number(digest.split(\";\")[1]) || 404;\n return undefined;\n}\n\nfunction asError(value: unknown): unknown {\n if (typeof value === \"object\" && value !== null) return value;\n return Object.assign(new Error(String(value)), { name: \"NonErrorThrown\", thrown: value });\n}\n\nfunction trySetHeader(res: Response, name: string, value: string): void {\n try {\n res.headers.set(name, value);\n } catch {\n // Some responses (Response.redirect, proxied fetch responses) have immutable headers.\n }\n}\n\n/** Any App Router route handler: `()`, `(request)` or `(request, { params })`. */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type RouteHandler = (...args: any[]) => Response | Promise<Response>;\n\n/**\n * Wraps an App Router route handler: request context + one http.request line. The returned\n * function has exactly the handler's parameters, so Next's route type checks still pass.\n */\nexport function withLogging<H extends RouteHandler>(\n log: Logger,\n handler: H,\n opts: WithLoggingOptions = {},\n): (...args: Parameters<H>) => Promise<Awaited<ReturnType<H>>> {\n return async (...args): Promise<Awaited<ReturnType<H>>> => {\n const request: unknown = args[0];\n // Next always passes the request; direct calls in unit tests may not.\n if (!(request instanceof Request)) return (await handler(...args)) as Awaited<ReturnType<H>>;\n const ctx = requestContextFields(request.headers);\n const path = new URL(request.url).pathname;\n const start = performance.now();\n return await runWithStore(ctx, async (store): Promise<Awaited<ReturnType<H>>> => {\n const summary = (status: number, err: unknown) =>\n logHttpRequest(\n log,\n {\n method: request.method,\n route: opts.route,\n path,\n status,\n duration_ms: performance.now() - start,\n err,\n fields: store.fields,\n },\n opts,\n );\n try {\n const res = (await handler(...args)) as Awaited<ReturnType<H>>;\n summary((res as Response).status, store.error);\n trySetHeader(res as Response, \"x-request-id\", ctx.request_id);\n return res;\n } catch (caught) {\n const control = nextControlStatus(caught);\n // Non-objects cannot be marked as logged, so onRequestError would log them again.\n const err = control === undefined ? asError(caught) : caught;\n summary(control ?? 500, control === undefined ? err : store.error);\n throw err;\n }\n });\n };\n}\n\n/**\n * patchConsole skip predicate for Next.js: Next prints a render error with console.error right\n * before calling onRequestError, by then the error carries a digest. Dropping that console\n * call leaves the single, richer onRequestError line.\n */\nexport function nextConsoleSkip(args: unknown[]): boolean {\n return args.some((arg) => isErrorLike(arg) && typeof (arg as { digest?: unknown }).digest === \"string\");\n}\n\nexport interface NextRequestInfo {\n path?: string;\n method?: string;\n headers?: Record<string, string | string[] | undefined>;\n}\n\nexport interface NextErrorContext {\n routerKind?: string;\n routePath?: string;\n routeType?: string;\n}\n\n/** `export const onRequestError = nextOnRequestError(log)` in instrumentation.ts. */\nexport function nextOnRequestError(log: Logger, opts: HttpLogOptions = {}) {\n return async (err: unknown, request: NextRequestInfo, context: NextErrorContext): Promise<void> => {\n if (isLogged(err) || nextControlStatus(err) !== undefined) return;\n const digest = (err as { digest?: unknown } | null)?.digest;\n logHttpRequest(\n log,\n {\n method: request?.method ?? \"GET\",\n route: context?.routePath,\n path: request?.path ?? \"\",\n status: 500,\n err,\n fields: {\n ...requestContextFields(request?.headers ?? {}),\n next_route_type: context?.routeType,\n next_router_kind: context?.routerKind,\n next_digest: typeof digest === \"string\" ? digest : undefined,\n },\n },\n opts,\n );\n };\n}\n\n/** Call from `register()` in instrumentation.ts. Does nothing on the edge runtime. */\nexport function registerNext(log: Logger, fields?: Fields): void {\n if (process.env.NEXT_RUNTIME && process.env.NEXT_RUNTIME !== \"nodejs\") return;\n // Next keeps serving after an unhandled rejection, so do not exit.\n installProcessHandlers(log, { exitOnCrash: false });\n logProcessWarnings(log);\n logStartup(log, { port: process.env.PORT ? Number(process.env.PORT) : undefined, ...fields });\n}\n\nexport interface WithActionOptions {\n /** Request headers source. Defaults to `headers()` from next/headers. */\n headers?: () => HeaderSource | Promise<HeaderSource>;\n}\n\nasync function nextHeaders(): Promise<HeaderSource> {\n try {\n const mod = (await import(\"next/headers\")) as { headers: () => HeaderSource | Promise<HeaderSource> };\n return await mod.headers();\n } catch {\n // Called outside a request (tests, scripts) or Next is not installed.\n return {};\n }\n}\n\n/**\n * Wraps a server action: request context (request_id from x-request-id) plus one\n * `action.completed` / `action.failed` line. redirect()/notFound() count as completed.\n */\nexport function withAction<A extends unknown[], R>(\n log: Logger,\n name: string,\n action: (...args: A) => Promise<R>,\n opts: WithActionOptions = {},\n): (...args: A) => Promise<R> {\n return async (...args) => {\n const headers = await (opts.headers ?? nextHeaders)();\n const start = performance.now();\n return runWithStore({ ...requestContextFields(headers), action_name: name }, async (store) => {\n const fields = () => ({ ...store.fields, duration_ms: Math.round(performance.now() - start) });\n try {\n const result = await action(...args);\n log.info(\"server action completed\", { ...fields(), event: \"action.completed\", err: store.error });\n return result;\n } catch (caught) {\n const control = nextControlStatus(caught);\n if (control !== undefined) {\n log.info(\"server action completed\", { ...fields(), event: \"action.completed\", next_control_status: control });\n throw caught;\n }\n const err = asError(caught);\n log.error(\"server action failed\", { ...fields(), event: \"action.failed\", err });\n markLogged(err);\n throw err;\n }\n });\n };\n}\n","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","import { isErrorLike, serializeError } from \"./error\";\nimport type { Level } from \"./levels\";\nimport { REDACTED, toSnakeCase, type Redactor } from \"./redact\";\n\nexport { DEFAULT_REDACT_KEYS, toSnakeCase } from \"./redact\";\n\n/** Final per-field hook: return a replacement, or undefined to drop the field. */\nexport type RedactHook = (key: string, value: Primitive) => unknown;\n\nexport type Fields = Record<string, unknown>;\nexport type Primitive = string | number | boolean | null;\nexport type LogRecord = Record<string, Primitive>;\n\nexport interface NormalizeOptions {\n snakeCase: boolean;\n redactor: Redactor;\n hook?: RedactHook;\n}\n\n// Fields the redact hook never sees, so every line keeps its level, message and event.\nconst HOOK_EXEMPT = new Set([\"level\", \"message\", \"event\"]);\n\n// Keys owned by Vector (overwritten or deleted on ingest) or by the logger itself.\nexport const RESERVED_KEYS = new Set([\n \"project\", \"service\", \"vps\", \"version\", \"container_name\", \"container_id\", \"image\", \"stream\",\n \"_timestamp\", \"label\", \"host\", \"source_type\", \"msg\", \"lvl\", \"severity\", \"level\", \"message\",\n]);\n\nexport const MAX_LINE_BYTES = 16000;\nconst MAX_MESSAGE = 500;\nconst MAX_STACK = 4000;\nconst MAX_STRING = 1000;\nconst MAX_DEPTH = 3;\nconst TRUNCATED = \"…[truncated]\";\n\n// Fields kept when a line is too large.\nconst KEEP_ON_SHRINK = new Set([\n \"level\", \"message\", \"event\", \"request_id\", \"trace_id\", \"span_id\", \"job_name\", \"job_id\", \"job_attempt\",\n \"user_id\", \"tenant_id\", \"err_type\", \"err_message\", \"err_code\",\n]);\n\nexport function stripUrl(url: string): string {\n const noQuery = url.split(/[?#]/, 1)[0] ?? \"\";\n return noQuery.replace(/^([a-z][a-z0-9+.-]*:\\/\\/)[^/@\\s]*@/i, \"$1\");\n}\n\nexport function truncate(s: string, max: number): string {\n return s.length > max ? s.slice(0, max - TRUNCATED.length) + TRUNCATED : s;\n}\n\nfunction isPlainObject(v: unknown): v is Fields {\n if (typeof v !== \"object\" || v === null) return false;\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n}\n\nfunction isUrlKey(key: string): boolean {\n return key === \"http_path\" || key === \"http_url\" || key.endsWith(\"_url\");\n}\n\nfunction flattenInto(out: LogRecord, prefix: string, obj: Fields, depth: number, opts: NormalizeOptions): void {\n for (const [rawKey, value] of Object.entries(obj)) {\n const key = opts.snakeCase ? toSnakeCase(rawKey) : rawKey;\n const full = prefix ? `${prefix}_${key}` : key;\n if (value === undefined || typeof value === \"function\" || typeof value === \"symbol\") continue;\n if (value !== null && (opts.redactor.isSensitive(rawKey) || opts.redactor.isSensitive(full))) {\n out[full] = REDACTED;\n continue;\n }\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") {\n out[full] = value;\n } else if (typeof value === \"number\") {\n out[full] = Number.isFinite(value) ? value : String(value);\n } else if (typeof value === \"bigint\") {\n out[full] = value.toString();\n } else if (value instanceof Date) {\n out[full] = Number.isNaN(value.getTime()) ? \"Invalid Date\" : value.toISOString();\n } else if (isErrorLike(value)) {\n out[full] = `${value.name}: ${value.message}`;\n } else if (isPlainObject(value) && depth < MAX_DEPTH - 1) {\n flattenInto(out, full, value, depth + 1, opts);\n } else {\n // Arrays, deep objects, class instances: stringified with sensitive keys redacted.\n out[full] = opts.redactor.json(value);\n }\n }\n}\n\nfunction finalize(key: string, value: Primitive, opts: NormalizeOptions): Primitive {\n if (typeof value !== \"string\") return value;\n const v = opts.redactor.scrub(isUrlKey(key) ? stripUrl(value) : value);\n return truncate(v, key === \"err_stack\" ? MAX_STACK : MAX_STRING);\n}\n\nexport function buildRecord(\n level: Level,\n message: unknown,\n sources: (Fields | undefined)[],\n opts: NormalizeOptions,\n): LogRecord {\n const merged: Fields = {};\n for (const source of sources) if (source) Object.assign(merged, source);\n\n let err: unknown;\n if (merged.err !== undefined && merged.err !== null) {\n err = merged.err;\n delete merged.err;\n } else if (isErrorLike(merged.error)) {\n err = merged.error;\n delete merged.error;\n }\n\n const flat: LogRecord = {};\n flattenInto(flat, \"\", merged, 0, opts);\n if (err !== undefined) Object.assign(flat, serializeError(err));\n\n const text = typeof message === \"string\" ? message : opts.redactor.json(message);\n const out: LogRecord = { level, message: truncate(opts.redactor.scrub(text), MAX_MESSAGE) };\n for (const [k, v] of Object.entries(flat)) {\n const key = RESERVED_KEYS.has(k) ? `app_${k}` : k;\n let value = finalize(key, v, opts);\n if (opts.hook && !HOOK_EXEMPT.has(key)) {\n const replaced = opts.hook(key, value);\n if (replaced === undefined) continue;\n value = typeof replaced === \"object\" && replaced !== null ? opts.redactor.json(replaced) : (replaced as Primitive);\n }\n out[key] = value;\n }\n return out;\n}\n\nfunction byteLength(s: string): number {\n // UTF-8 uses at most 3 bytes per UTF-16 code unit: skip encoding short lines.\n if (s.length * 3 <= MAX_LINE_BYTES) return s.length;\n return new TextEncoder().encode(s).length;\n}\n\nexport function serializeRecord(record: LogRecord): string {\n const line = JSON.stringify(record);\n if (byteLength(line) <= MAX_LINE_BYTES) return line;\n const small: LogRecord = {};\n for (const [k, v] of Object.entries(record)) {\n if (KEEP_ON_SHRINK.has(k) || k.startsWith(\"http_\")) small[k] = v;\n }\n small.log_truncated = true;\n return JSON.stringify(small);\n}\n","// No imports: this module is shared with the edge entry (next-edge).\nexport const REQUEST_ID_RE = /^[A-Za-z0-9._-]{1,64}$/;\nconst TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;\n\nexport function newRequestId(): string {\n return globalThis.crypto.randomUUID();\n}\n\nfunction first(header?: string | string[] | null): string | undefined {\n return Array.isArray(header) ? header[0] : (header ?? undefined);\n}\n\nexport function resolveRequestId(header?: string | string[] | null): string {\n const value = first(header);\n return value && REQUEST_ID_RE.test(value) ? value : newRequestId();\n}\n\nexport function parseTraceparent(header?: string | string[] | null): { trace_id: string; span_id: string } | undefined {\n const value = first(header)?.trim().toLowerCase();\n const m = value ? TRACEPARENT_RE.exec(value) : null;\n if (!m) return undefined;\n const [, version, traceId, spanId] = m as unknown as [string, string, string, string];\n if (version === \"ff\" || /^0+$/.test(traceId) || /^0+$/.test(spanId)) return undefined;\n return { trace_id: traceId, span_id: spanId };\n}\n","import { getContext } from \"./context\";\nimport { markLogged, serializeError } from \"./error\";\nimport type { Logger } from \"./logger\";\nimport { stripUrl, type Fields } from \"./normalize\";\nimport { parseTraceparent, resolveRequestId } from \"./request-id\";\n\nexport { newRequestId, parseTraceparent, REQUEST_ID_RE, resolveRequestId } from \"./request-id\";\n\nexport type HeaderSource = Headers | Record<string, string | string[] | undefined>;\n\nfunction header(headers: HeaderSource, name: string): string | string[] | undefined {\n if (typeof (headers as Headers).get === \"function\") return (headers as Headers).get(name) ?? undefined;\n return (headers as Record<string, string | string[] | undefined>)[name];\n}\n\nexport function requestContextFields(headers: HeaderSource): { request_id: string; trace_id?: string; span_id?: string } {\n const out: { request_id: string; trace_id?: string; span_id?: string } = {\n request_id: resolveRequestId(header(headers, \"x-request-id\")),\n };\n const tp = parseTraceparent(header(headers, \"traceparent\"));\n if (tp) Object.assign(out, tp);\n return out;\n}\n\n// Headers to forward to services we own, so their logs share request_id / trace_id.\nexport function propagationHeaders(): Record<string, string> {\n const ctx = getContext();\n const out: Record<string, string> = {};\n if (typeof ctx.request_id === \"string\") out[\"x-request-id\"] = ctx.request_id;\n if (typeof ctx.trace_id === \"string\" && typeof ctx.span_id === \"string\") {\n out.traceparent = `00-${ctx.trace_id}-${ctx.span_id}-01`;\n }\n return out;\n}\n\nexport const DEFAULT_SKIP_PATHS: (string | RegExp)[] = [\n // Also under a prefix such as /api/v1/health.\n /(^|\\/)(health|healthz|ready|readyz|livez)\\/?$/,\n \"/favicon.ico\",\n /^\\/_next\\/static\\//,\n];\n\nexport interface HttpLogOptions {\n skipPaths?: (string | RegExp)[];\n}\n\nexport interface HttpRequestInfo {\n method: string;\n route?: string;\n path: string;\n status: number;\n duration_ms?: number;\n err?: unknown;\n fields?: Fields;\n}\n\nexport function shouldSkip(method: string, path: string, skipPaths: (string | RegExp)[] = DEFAULT_SKIP_PATHS): boolean {\n if (method.toUpperCase() === \"OPTIONS\") return true;\n const p = stripUrl(path);\n return skipPaths.some((s) => (typeof s === \"string\" ? p === s : s.test(p)));\n}\n\n// Writes the one `http.request` summary line for an inbound request. Skipped paths are still\n// logged when they fail with a 5xx, so a failing health check stays visible.\nexport function logHttpRequest(log: Logger, info: HttpRequestInfo, opts: HttpLogOptions = {}): void {\n if (info.status < 500 && shouldSkip(info.method, info.path, opts.skipPaths)) return;\n const level = info.status >= 500 ? \"error\" : info.status >= 400 ? \"warn\" : \"info\";\n const path = stripUrl(info.path);\n const errFields: Fields = {};\n if (info.err !== undefined && info.err !== null) {\n Object.assign(errFields, serializeError(info.err));\n if (info.status < 500) delete errFields.err_stack;\n markLogged(info.err);\n }\n // Route templates keep the message bounded; the raw path would make one message per URL.\n const message = info.route ? `${info.method} ${info.route} ${info.status}` : `${info.method} ${info.status}`;\n log[level](message, {\n ...info.fields,\n event: \"http.request\",\n http_method: info.method,\n http_route: info.route,\n http_path: path,\n http_status: info.status,\n duration_ms: info.duration_ms === undefined ? undefined : Math.round(info.duration_ms),\n ...errFields,\n });\n}\n","import { globalSingleton } from \"./global\";\nimport type { Logger } from \"./logger\";\nimport type { Fields } from \"./normalize\";\n\nexport function logStartup(log: Logger, fields: Fields = {}): void {\n log.info(\"app started\", {\n event: \"app.started\",\n runtime_version: `node ${process.versions.node}`,\n log_level: log.level,\n ...fields,\n });\n}\n\n/** Call at the start of your own shutdown handler (e.g. on SIGTERM). The library never handles signals. */\nexport function logShutdown(log: Logger, fields: Fields = {}): void {\n log.info(\"app stopping\", { event: \"app.stopping\", ...fields });\n}\n\nexport interface ProcessHandlerOptions {\n /** Exit with code 1 after an uncaught error. Next.js keeps serving, so its adapter passes false. */\n exitOnCrash?: boolean;\n /** Time other crash handlers (Sentry, APM) get to flush before exit. Default 1000 ms. */\n exitDelayMs?: number;\n exit?: (code: number) => void;\n}\n\nexport function crashHandlers(log: Logger, opts: ProcessHandlerOptions = {}) {\n const exitOnCrash = opts.exitOnCrash ?? true;\n const exitDelayMs = opts.exitDelayMs ?? 1000;\n const exit = opts.exit ?? ((code: number) => process.exit(code));\n let exiting = false;\n const crash = (source: string, err: unknown) => {\n if (!exitOnCrash) {\n log.error(\"unhandled error\", { event: \"app.unhandled_error\", crash_source: source, err });\n return;\n }\n log.fatal(\"app crashed\", { event: \"app.crashed\", crash_source: source, err });\n if (exiting) return;\n exiting = true;\n if (typeof process !== \"undefined\") process.exitCode = 1;\n // Deferred so listeners registered after ours still run; unref so a process with nothing\n // left to do exits on its own (with exitCode 1) without waiting.\n const timer = setTimeout(() => exit(1), exitDelayMs);\n (timer as { unref?: () => void }).unref?.();\n };\n return {\n onUncaught: (err: unknown) => crash(\"uncaughtException\", err),\n onRejection: (reason: unknown) => crash(\"unhandledRejection\", reason),\n };\n}\n\nconst state = globalSingleton(\"process\", () => ({ installed: false }));\n\n/** Logs uncaught exceptions and unhandled rejections. Does not touch SIGTERM/SIGINT. */\nexport function installProcessHandlers(log: Logger, opts: ProcessHandlerOptions = {}): void {\n if (state.installed || typeof process === \"undefined\" || typeof process.on !== \"function\") return;\n state.installed = true;\n const h = crashHandlers(log, opts);\n process.on(\"uncaughtException\", h.onUncaught);\n process.on(\"unhandledRejection\", h.onRejection);\n}\n\nconst warningState = globalSingleton(\"warnings\", () => ({ installed: false }));\n\n/**\n * Node keeps 10 frames by default; ORM/framework frames (knex, Strapi, Prisma) often use all of\n * them, hiding the app code that triggered a warning or error. Only ever raises the limit.\n */\nexport function raiseStackTraceLimit(limit: number): void {\n if (Error.stackTraceLimit < limit) Error.stackTraceLimit = limit;\n}\n\nexport interface ProcessWarningOptions {\n stackTraceLimit?: number;\n /**\n * Remove Node's own stderr printer, so a warning is one JSON line instead of that line plus\n * \"(node:1) Warning: ...\" and \"(Use `node --trace-deprecation ...`...)\". Default true.\n */\n replaceDefault?: boolean;\n}\n\n// Some libraries (pg) emit the same warning on every occurrence; one line per process is enough.\nconst MAX_SEEN_WARNINGS = 200;\n\n/**\n * Logs Node process warnings (deprecations, MaxListeners...) as `process.warning` lines whose\n * err_stack points at the code that triggered them. Each distinct warning is logged once.\n * Also raises Error.stackTraceLimit (default 30) so the stack reaches past framework frames.\n */\nexport function logProcessWarnings(log: Logger, opts: ProcessWarningOptions = {}): void {\n if (warningState.installed || typeof process === \"undefined\" || typeof process.on !== \"function\") return;\n warningState.installed = true;\n raiseStackTraceLimit(opts.stackTraceLimit ?? 30);\n if (opts.replaceDefault ?? true) {\n for (const l of process.listeners(\"warning\")) if (l.name === \"onWarning\") process.off(\"warning\", l);\n }\n const seen = new Set<string>();\n process.on(\"warning\", (warning: Error) => {\n const key = `${warning?.name}:${(warning as { code?: unknown })?.code ?? \"\"}:${warning?.message}`;\n if (seen.has(key)) return;\n if (seen.size < MAX_SEEN_WARNINGS) seen.add(key);\n log.warn(\"process warning\", { event: \"process.warning\", err: warning });\n });\n}\n"],"mappings":"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","names":["header"]}
package/dist/next.js CHANGED
@@ -2,11 +2,11 @@ import {
2
2
  installProcessHandlers,
3
3
  logProcessWarnings,
4
4
  logStartup
5
- } from "./chunk-VZRJCT2Z.js";
5
+ } from "./chunk-AL4LJLUZ.js";
6
6
  import {
7
7
  logHttpRequest,
8
8
  requestContextFields
9
- } from "./chunk-KD5H5G6Y.js";
9
+ } from "./chunk-754Y5TQX.js";
10
10
  import "./chunk-KLBL7QEN.js";
11
11
  import {
12
12
  isErrorLike,
package/dist/strapi.cjs CHANGED
@@ -22,7 +22,8 @@ var strapi_exports = {};
22
22
  __export(strapi_exports, {
23
23
  strapiErrorCapture: () => strapiErrorCapture,
24
24
  strapiLoggerConfig: () => strapiLoggerConfig,
25
- strapiRequestLogger: () => strapiRequestLogger
25
+ strapiRequestLogger: () => strapiRequestLogger,
26
+ strapiServerErrors: () => strapiServerErrors
26
27
  });
27
28
  module.exports = __toCommonJS(strapi_exports);
28
29
  var import_node_stream = require("stream");
@@ -205,7 +206,8 @@ function parseTraceparent(header) {
205
206
 
206
207
  // src/http.ts
207
208
  var DEFAULT_SKIP_PATHS = [
208
- /^\/(health|healthz|ready|readyz|livez)\/?$/,
209
+ // Also under a prefix such as /api/v1/health.
210
+ /(^|\/)(health|healthz|ready|readyz|livez)\/?$/,
209
211
  "/favicon.ico",
210
212
  /^\/_next\/static\//
211
213
  ];
@@ -215,7 +217,7 @@ function shouldSkip(method, path, skipPaths = DEFAULT_SKIP_PATHS) {
215
217
  return skipPaths.some((s) => typeof s === "string" ? p === s : s.test(p));
216
218
  }
217
219
  function logHttpRequest(log, info, opts = {}) {
218
- if (shouldSkip(info.method, info.path, opts.skipPaths)) return;
220
+ if (info.status < 500 && shouldSkip(info.method, info.path, opts.skipPaths)) return;
219
221
  const level = info.status >= 500 ? "error" : info.status >= 400 ? "warn" : "info";
220
222
  const path = stripUrl(info.path);
221
223
  const errFields = {};
@@ -295,6 +297,31 @@ function strapiErrorCapture() {
295
297
  }
296
298
  };
297
299
  }
300
+ function strapiServerErrors(app, log, opts = {}) {
301
+ const stateKey = opts.stateKey ?? "requestId";
302
+ app.on("error", (err, ctx) => {
303
+ if (isLogged(err)) return;
304
+ const e = err;
305
+ if (e?.status === 404 || e?.expose === true) return;
306
+ const status = typeof e?.status === "number" ? e.status : 500;
307
+ const fields = { event: "http.server_error", err };
308
+ const client = isClientDisconnect(err);
309
+ if (client) fields.client_disconnect = true;
310
+ if (ctx) {
311
+ const requestId = ctx.state?.[stateKey];
312
+ if (typeof requestId === "string" && currentStore()?.fields.request_id === void 0) fields.request_id = requestId;
313
+ fields.http_method = ctx.method;
314
+ fields.http_path = ctx.url;
315
+ }
316
+ markLogged(err);
317
+ log[status >= 500 && !client ? "error" : "warn"]("http server error", fields);
318
+ });
319
+ }
320
+ function isClientDisconnect(err) {
321
+ const e = err;
322
+ const code = typeof e?.code === "string" ? e.code : "";
323
+ return code === "ECONNRESET" || code === "ECONNABORTED" || code === "EPIPE" || code === "ERR_STREAM_PREMATURE_CLOSE" || code.startsWith("HPE_") || e?.message === "aborted";
324
+ }
298
325
  var LEVELS = {
299
326
  error: "error",
300
327
  warn: "warn",
@@ -360,6 +387,7 @@ function strapiLoggerConfig(log) {
360
387
  0 && (module.exports = {
361
388
  strapiErrorCapture,
362
389
  strapiLoggerConfig,
363
- strapiRequestLogger
390
+ strapiRequestLogger,
391
+ strapiServerErrors
364
392
  });
365
393
  //# sourceMappingURL=strapi.cjs.map