@tanngo11/log 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/CONTRACT.md +135 -0
- package/LICENSE +21 -0
- package/README.md +291 -0
- package/dist/bullmq.cjs +108 -0
- package/dist/bullmq.cjs.map +1 -0
- package/dist/bullmq.d.cts +14 -0
- package/dist/bullmq.d.ts +14 -0
- package/dist/bullmq.js +29 -0
- package/dist/bullmq.js.map +1 -0
- package/dist/chunk-BP264YAM.js +72 -0
- package/dist/chunk-BP264YAM.js.map +1 -0
- package/dist/chunk-BVKAY2OK.js +53 -0
- package/dist/chunk-BVKAY2OK.js.map +1 -0
- package/dist/chunk-DO5HNNTP.js +66 -0
- package/dist/chunk-DO5HNNTP.js.map +1 -0
- package/dist/chunk-KD5H5G6Y.js +209 -0
- package/dist/chunk-KD5H5G6Y.js.map +1 -0
- package/dist/chunk-KLBL7QEN.js +29 -0
- package/dist/chunk-KLBL7QEN.js.map +1 -0
- package/dist/chunk-QMC6BWGK.js +203 -0
- package/dist/chunk-QMC6BWGK.js.map +1 -0
- package/dist/chunk-UXKPPF4G.js +41 -0
- package/dist/chunk-UXKPPF4G.js.map +1 -0
- package/dist/express.cjs +316 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.cts +10 -0
- package/dist/express.d.ts +10 -0
- package/dist/express.js +12 -0
- package/dist/express.js.map +1 -0
- package/dist/fastify.cjs +309 -0
- package/dist/fastify.cjs.map +1 -0
- package/dist/fastify.d.cts +10 -0
- package/dist/fastify.d.ts +10 -0
- package/dist/fastify.js +10 -0
- package/dist/fastify.js.map +1 -0
- package/dist/http-BI6ARZlD.d.ts +25 -0
- package/dist/http-BXAiU-rN.d.cts +25 -0
- package/dist/index.cjs +677 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +69 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +214 -0
- package/dist/index.js.map +1 -0
- package/dist/logger-CFMTzjsm.d.cts +47 -0
- package/dist/logger-CFMTzjsm.d.ts +47 -0
- package/dist/nest.cjs +458 -0
- package/dist/nest.cjs.map +1 -0
- package/dist/nest.d.cts +41 -0
- package/dist/nest.d.ts +41 -0
- package/dist/nest.js +107 -0
- package/dist/nest.js.map +1 -0
- package/dist/next-edge.cjs +44 -0
- package/dist/next-edge.cjs.map +1 -0
- package/dist/next-edge.d.cts +7 -0
- package/dist/next-edge.d.ts +7 -0
- package/dist/next-edge.js +16 -0
- package/dist/next-edge.js.map +1 -0
- package/dist/next.cjs +423 -0
- package/dist/next.cjs.map +1 -0
- package/dist/next.d.cts +36 -0
- package/dist/next.d.ts +36 -0
- package/dist/next.js +138 -0
- package/dist/next.js.map +1 -0
- package/dist/prisma.cjs +63 -0
- package/dist/prisma.cjs.map +1 -0
- package/dist/prisma.d.cts +31 -0
- package/dist/prisma.d.ts +31 -0
- package/dist/prisma.js +38 -0
- package/dist/prisma.js.map +1 -0
- package/package.json +138 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/express.ts","../src/context.ts","../src/global.ts","../src/redact.ts","../src/error.ts","../src/normalize.ts","../src/request-id.ts","../src/http.ts"],"sourcesContent":["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 { 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"],"mappings":"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","names":["header"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ErrorRequestHandler, RequestHandler } from 'express';
|
|
2
|
+
import { H as HttpLogOptions } from './http-BXAiU-rN.cjs';
|
|
3
|
+
import { L as Logger } from './logger-CFMTzjsm.cjs';
|
|
4
|
+
|
|
5
|
+
/** Mount before routes. Opens the request context and writes the http.request line. */
|
|
6
|
+
declare function requestLogger(log: Logger, opts?: HttpLogOptions): RequestHandler;
|
|
7
|
+
/** Mount after routes, before your own error handler. Records the error for the summary line. */
|
|
8
|
+
declare function errorRecorder(): ErrorRequestHandler;
|
|
9
|
+
|
|
10
|
+
export { errorRecorder, requestLogger };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ErrorRequestHandler, RequestHandler } from 'express';
|
|
2
|
+
import { H as HttpLogOptions } from './http-BI6ARZlD.js';
|
|
3
|
+
import { L as Logger } from './logger-CFMTzjsm.js';
|
|
4
|
+
|
|
5
|
+
/** Mount before routes. Opens the request context and writes the http.request line. */
|
|
6
|
+
declare function requestLogger(log: Logger, opts?: HttpLogOptions): RequestHandler;
|
|
7
|
+
/** Mount after routes, before your own error handler. Records the error for the summary line. */
|
|
8
|
+
declare function errorRecorder(): ErrorRequestHandler;
|
|
9
|
+
|
|
10
|
+
export { errorRecorder, requestLogger };
|
package/dist/express.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
errorRecorder,
|
|
3
|
+
requestLogger
|
|
4
|
+
} from "./chunk-BP264YAM.js";
|
|
5
|
+
import "./chunk-KD5H5G6Y.js";
|
|
6
|
+
import "./chunk-KLBL7QEN.js";
|
|
7
|
+
import "./chunk-QMC6BWGK.js";
|
|
8
|
+
export {
|
|
9
|
+
errorRecorder,
|
|
10
|
+
requestLogger
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/fastify.cjs
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/fastify.ts
|
|
21
|
+
var fastify_exports = {};
|
|
22
|
+
__export(fastify_exports, {
|
|
23
|
+
fastifyLogger: () => fastifyLogger
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(fastify_exports);
|
|
26
|
+
|
|
27
|
+
// src/context.ts
|
|
28
|
+
var import_node_async_hooks = require("async_hooks");
|
|
29
|
+
|
|
30
|
+
// src/global.ts
|
|
31
|
+
function globalSingleton(name, make) {
|
|
32
|
+
const key = /* @__PURE__ */ Symbol.for(`@tanngo11/log.${name}`);
|
|
33
|
+
const g = globalThis;
|
|
34
|
+
let value = g[key];
|
|
35
|
+
if (value === void 0) {
|
|
36
|
+
value = make();
|
|
37
|
+
g[key] = value;
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/context.ts
|
|
43
|
+
var als = globalSingleton("als", () => new import_node_async_hooks.AsyncLocalStorage());
|
|
44
|
+
var STORE_KEY = /* @__PURE__ */ Symbol.for("@tanngo11/log.store");
|
|
45
|
+
function runWithStore(fields, fn) {
|
|
46
|
+
const parent = als.getStore();
|
|
47
|
+
const store = { fields: { ...parent?.fields, ...fields }, parent };
|
|
48
|
+
return als.run(store, () => fn(store));
|
|
49
|
+
}
|
|
50
|
+
function recordErrorIn(store, err) {
|
|
51
|
+
store.error = err;
|
|
52
|
+
for (let s = store.parent; s; s = s.parent) if (s.error === void 0) s.error = err;
|
|
53
|
+
}
|
|
54
|
+
function attachStore(target, store) {
|
|
55
|
+
target[STORE_KEY] = store;
|
|
56
|
+
}
|
|
57
|
+
function storeOf(target) {
|
|
58
|
+
if (typeof target !== "object" || target === null) return void 0;
|
|
59
|
+
return target[STORE_KEY];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/redact.ts
|
|
63
|
+
var DEFAULT_REDACT_KEYS = [
|
|
64
|
+
"password",
|
|
65
|
+
"passwd",
|
|
66
|
+
"pwd",
|
|
67
|
+
"passphrase",
|
|
68
|
+
"secret",
|
|
69
|
+
"client_secret",
|
|
70
|
+
"token",
|
|
71
|
+
"access_token",
|
|
72
|
+
"refresh_token",
|
|
73
|
+
"id_token",
|
|
74
|
+
"auth_token",
|
|
75
|
+
"api_key",
|
|
76
|
+
"apikey",
|
|
77
|
+
"access_key",
|
|
78
|
+
"secret_key",
|
|
79
|
+
"private_key",
|
|
80
|
+
"authorization",
|
|
81
|
+
"cookie",
|
|
82
|
+
"set_cookie",
|
|
83
|
+
"session_id",
|
|
84
|
+
"sessionid",
|
|
85
|
+
"credential",
|
|
86
|
+
"credentials",
|
|
87
|
+
"dsn",
|
|
88
|
+
"connection_string",
|
|
89
|
+
"conn_string",
|
|
90
|
+
"jwt",
|
|
91
|
+
"bearer"
|
|
92
|
+
];
|
|
93
|
+
var SAFE_SUFFIXES = ["_count", "_type", "_length", "_expires_at", "_expires_in", "_ttl", "_at", "_ms", "_enabled"];
|
|
94
|
+
var REDACTED = "[REDACTED]";
|
|
95
|
+
function toSnakeCase(key) {
|
|
96
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/[^A-Za-z0-9_]+/g, "_").toLowerCase();
|
|
97
|
+
}
|
|
98
|
+
var URL_CREDENTIALS_RE = /([a-z][a-z0-9+.-]*:\/\/)[^\s/@:"']+:[^\s/@"']+@/gi;
|
|
99
|
+
var BEARER_RE = /\bBearer\s+[A-Za-z0-9._~+/=-]+/gi;
|
|
100
|
+
var JWT_RE = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g;
|
|
101
|
+
var EMAIL_RE = /[A-Za-z0-9._%+-]+@([A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,})/g;
|
|
102
|
+
function globalize(re) {
|
|
103
|
+
return re.flags.includes("g") ? new RegExp(re.source, re.flags) : new RegExp(re.source, `${re.flags}g`);
|
|
104
|
+
}
|
|
105
|
+
function createRedactor(opts = {}) {
|
|
106
|
+
const words = [...DEFAULT_REDACT_KEYS];
|
|
107
|
+
const keyPatterns = [];
|
|
108
|
+
for (const k of opts.keys ?? []) {
|
|
109
|
+
if (typeof k === "string") words.push(toSnakeCase(k));
|
|
110
|
+
else keyPatterns.push(new RegExp(k.source, k.flags.replace("g", "")));
|
|
111
|
+
}
|
|
112
|
+
const valuePatterns = (opts.values ?? []).map(globalize);
|
|
113
|
+
const isSensitive = (key) => {
|
|
114
|
+
const k = toSnakeCase(key);
|
|
115
|
+
if (keyPatterns.some((re) => re.test(k))) return true;
|
|
116
|
+
if (!words.includes(k) && SAFE_SUFFIXES.some((s) => k.endsWith(s))) return false;
|
|
117
|
+
return words.some((w) => k === w || k.startsWith(`${w}_`) || k.endsWith(`_${w}`) || k.includes(`_${w}_`));
|
|
118
|
+
};
|
|
119
|
+
const scrub = (s) => {
|
|
120
|
+
let out = s.replace(URL_CREDENTIALS_RE, `$1${REDACTED}@`).replace(BEARER_RE, `Bearer ${REDACTED}`).replace(JWT_RE, "[REDACTED_JWT]").replace(EMAIL_RE, "***@$1");
|
|
121
|
+
for (const re of valuePatterns) out = out.replace(re, REDACTED);
|
|
122
|
+
return out;
|
|
123
|
+
};
|
|
124
|
+
const json = (value) => {
|
|
125
|
+
if (typeof value === "string") return value;
|
|
126
|
+
try {
|
|
127
|
+
const text = JSON.stringify(value, (key, val) => {
|
|
128
|
+
if (key && val !== null && val !== void 0 && isSensitive(key)) return REDACTED;
|
|
129
|
+
if (typeof val === "bigint") return val.toString();
|
|
130
|
+
if (val instanceof Error) return { name: val.name, message: val.message };
|
|
131
|
+
return val;
|
|
132
|
+
});
|
|
133
|
+
return text ?? String(value);
|
|
134
|
+
} catch {
|
|
135
|
+
return String(value);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
return { isSensitive, scrub, json };
|
|
139
|
+
}
|
|
140
|
+
var defaultRedactor = createRedactor();
|
|
141
|
+
|
|
142
|
+
// src/error.ts
|
|
143
|
+
var MAX_CAUSE_DEPTH = 5;
|
|
144
|
+
function isErrorLike(v) {
|
|
145
|
+
if (v instanceof Error) return true;
|
|
146
|
+
return typeof v === "object" && v !== null && typeof v.message === "string" && ("stack" in v || "name" in v);
|
|
147
|
+
}
|
|
148
|
+
function errorType(e) {
|
|
149
|
+
if (e.name && e.name !== "Error") return e.name;
|
|
150
|
+
const ctor = e.constructor?.name;
|
|
151
|
+
return ctor && ctor !== "Object" ? ctor : "Error";
|
|
152
|
+
}
|
|
153
|
+
function serializeError(err) {
|
|
154
|
+
if (!isErrorLike(err)) {
|
|
155
|
+
if (typeof err === "string") return { err_type: "string", err_message: err };
|
|
156
|
+
return { err_type: typeof err, err_message: defaultRedactor.json(err) };
|
|
157
|
+
}
|
|
158
|
+
const e = err;
|
|
159
|
+
const out = { err_type: errorType(e), err_message: String(e.message ?? "") };
|
|
160
|
+
if (e.code !== void 0 && e.code !== null) out.err_code = String(e.code);
|
|
161
|
+
if (typeof e.stack === "string") out.err_stack = e.stack;
|
|
162
|
+
const causes = [];
|
|
163
|
+
let cause = e.cause;
|
|
164
|
+
while (cause !== void 0 && cause !== null && causes.length < MAX_CAUSE_DEPTH) {
|
|
165
|
+
if (isErrorLike(cause)) {
|
|
166
|
+
causes.push(`${errorType(cause)}: ${cause.message}`);
|
|
167
|
+
cause = cause.cause;
|
|
168
|
+
} else {
|
|
169
|
+
causes.push(defaultRedactor.json(cause));
|
|
170
|
+
cause = void 0;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (causes.length > 0) out.err_cause = causes.join(" <- ");
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
var logged = globalSingleton("logged", () => /* @__PURE__ */ new WeakSet());
|
|
177
|
+
function markLogged(err) {
|
|
178
|
+
if (typeof err === "object" && err !== null) logged.add(err);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// src/normalize.ts
|
|
182
|
+
function stripUrl(url) {
|
|
183
|
+
const noQuery = url.split(/[?#]/, 1)[0] ?? "";
|
|
184
|
+
return noQuery.replace(/^([a-z][a-z0-9+.-]*:\/\/)[^/@\s]*@/i, "$1");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/request-id.ts
|
|
188
|
+
var REQUEST_ID_RE = /^[A-Za-z0-9._-]{1,64}$/;
|
|
189
|
+
var TRACEPARENT_RE = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
|
|
190
|
+
function newRequestId() {
|
|
191
|
+
return globalThis.crypto.randomUUID();
|
|
192
|
+
}
|
|
193
|
+
function first(header2) {
|
|
194
|
+
return Array.isArray(header2) ? header2[0] : header2 ?? void 0;
|
|
195
|
+
}
|
|
196
|
+
function resolveRequestId(header2) {
|
|
197
|
+
const value = first(header2);
|
|
198
|
+
return value && REQUEST_ID_RE.test(value) ? value : newRequestId();
|
|
199
|
+
}
|
|
200
|
+
function parseTraceparent(header2) {
|
|
201
|
+
const value = first(header2)?.trim().toLowerCase();
|
|
202
|
+
const m = value ? TRACEPARENT_RE.exec(value) : null;
|
|
203
|
+
if (!m) return void 0;
|
|
204
|
+
const [, version, traceId, spanId] = m;
|
|
205
|
+
if (version === "ff" || /^0+$/.test(traceId) || /^0+$/.test(spanId)) return void 0;
|
|
206
|
+
return { trace_id: traceId, span_id: spanId };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/http.ts
|
|
210
|
+
function header(headers, name) {
|
|
211
|
+
if (typeof headers.get === "function") return headers.get(name) ?? void 0;
|
|
212
|
+
return headers[name];
|
|
213
|
+
}
|
|
214
|
+
function requestContextFields(headers) {
|
|
215
|
+
const out = {
|
|
216
|
+
request_id: resolveRequestId(header(headers, "x-request-id"))
|
|
217
|
+
};
|
|
218
|
+
const tp = parseTraceparent(header(headers, "traceparent"));
|
|
219
|
+
if (tp) Object.assign(out, tp);
|
|
220
|
+
return out;
|
|
221
|
+
}
|
|
222
|
+
var DEFAULT_SKIP_PATHS = [
|
|
223
|
+
/^\/(health|healthz|ready|readyz|livez)\/?$/,
|
|
224
|
+
"/favicon.ico",
|
|
225
|
+
/^\/_next\/static\//
|
|
226
|
+
];
|
|
227
|
+
function shouldSkip(method, path, skipPaths = DEFAULT_SKIP_PATHS) {
|
|
228
|
+
if (method.toUpperCase() === "OPTIONS") return true;
|
|
229
|
+
const p = stripUrl(path);
|
|
230
|
+
return skipPaths.some((s) => typeof s === "string" ? p === s : s.test(p));
|
|
231
|
+
}
|
|
232
|
+
function logHttpRequest(log, info, opts = {}) {
|
|
233
|
+
if (shouldSkip(info.method, info.path, opts.skipPaths)) return;
|
|
234
|
+
const level = info.status >= 500 ? "error" : info.status >= 400 ? "warn" : "info";
|
|
235
|
+
const path = stripUrl(info.path);
|
|
236
|
+
const errFields = {};
|
|
237
|
+
if (info.err !== void 0 && info.err !== null) {
|
|
238
|
+
Object.assign(errFields, serializeError(info.err));
|
|
239
|
+
if (info.status < 500) delete errFields.err_stack;
|
|
240
|
+
markLogged(info.err);
|
|
241
|
+
}
|
|
242
|
+
const message = info.route ? `${info.method} ${info.route} ${info.status}` : `${info.method} ${info.status}`;
|
|
243
|
+
log[level](message, {
|
|
244
|
+
...info.fields,
|
|
245
|
+
event: "http.request",
|
|
246
|
+
http_method: info.method,
|
|
247
|
+
http_route: info.route,
|
|
248
|
+
http_path: path,
|
|
249
|
+
http_status: info.status,
|
|
250
|
+
duration_ms: info.duration_ms === void 0 ? void 0 : Math.round(info.duration_ms),
|
|
251
|
+
...errFields
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/fastify.ts
|
|
256
|
+
var starts = /* @__PURE__ */ new WeakMap();
|
|
257
|
+
var finished = /* @__PURE__ */ new WeakSet();
|
|
258
|
+
var plugin = (app, opts, done) => {
|
|
259
|
+
const summary = (req, status) => {
|
|
260
|
+
if (finished.has(req)) return;
|
|
261
|
+
finished.add(req);
|
|
262
|
+
const store = storeOf(req);
|
|
263
|
+
const start = starts.get(req);
|
|
264
|
+
logHttpRequest(
|
|
265
|
+
opts.log,
|
|
266
|
+
{
|
|
267
|
+
method: req.method,
|
|
268
|
+
route: req.routeOptions?.url,
|
|
269
|
+
path: req.url,
|
|
270
|
+
status,
|
|
271
|
+
duration_ms: start === void 0 ? void 0 : performance.now() - start,
|
|
272
|
+
err: store?.error,
|
|
273
|
+
fields: store?.fields
|
|
274
|
+
},
|
|
275
|
+
opts
|
|
276
|
+
);
|
|
277
|
+
};
|
|
278
|
+
app.addHook("onRequest", (req, reply, next) => {
|
|
279
|
+
const ctx = requestContextFields(req.headers);
|
|
280
|
+
reply.header("x-request-id", ctx.request_id);
|
|
281
|
+
starts.set(req, performance.now());
|
|
282
|
+
runWithStore(ctx, (store) => {
|
|
283
|
+
attachStore(req, store);
|
|
284
|
+
attachStore(reply, store);
|
|
285
|
+
next();
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
app.addHook("onError", (_req, reply, err, next) => {
|
|
289
|
+
const store = storeOf(reply);
|
|
290
|
+
if (store) recordErrorIn(store, err);
|
|
291
|
+
next();
|
|
292
|
+
});
|
|
293
|
+
app.addHook("onResponse", (req, reply, next) => {
|
|
294
|
+
summary(req, reply.statusCode);
|
|
295
|
+
next();
|
|
296
|
+
});
|
|
297
|
+
app.addHook("onRequestAbort", (req, next) => {
|
|
298
|
+
summary(req, 499);
|
|
299
|
+
next();
|
|
300
|
+
});
|
|
301
|
+
done();
|
|
302
|
+
};
|
|
303
|
+
plugin[/* @__PURE__ */ Symbol.for("skip-override")] = true;
|
|
304
|
+
var fastifyLogger = plugin;
|
|
305
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
306
|
+
0 && (module.exports = {
|
|
307
|
+
fastifyLogger
|
|
308
|
+
});
|
|
309
|
+
//# sourceMappingURL=fastify.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/fastify.ts","../src/context.ts","../src/global.ts","../src/redact.ts","../src/error.ts","../src/normalize.ts","../src/request-id.ts","../src/http.ts"],"sourcesContent":["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","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"],"mappings":"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","names":["header"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FastifyPluginCallback } from 'fastify';
|
|
2
|
+
import { H as HttpLogOptions } from './http-BXAiU-rN.cjs';
|
|
3
|
+
import { L as Logger } from './logger-CFMTzjsm.cjs';
|
|
4
|
+
|
|
5
|
+
interface FastifyLoggerOptions extends HttpLogOptions {
|
|
6
|
+
log: Logger;
|
|
7
|
+
}
|
|
8
|
+
declare const fastifyLogger: FastifyPluginCallback<FastifyLoggerOptions>;
|
|
9
|
+
|
|
10
|
+
export { type FastifyLoggerOptions, fastifyLogger };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { FastifyPluginCallback } from 'fastify';
|
|
2
|
+
import { H as HttpLogOptions } from './http-BI6ARZlD.js';
|
|
3
|
+
import { L as Logger } from './logger-CFMTzjsm.js';
|
|
4
|
+
|
|
5
|
+
interface FastifyLoggerOptions extends HttpLogOptions {
|
|
6
|
+
log: Logger;
|
|
7
|
+
}
|
|
8
|
+
declare const fastifyLogger: FastifyPluginCallback<FastifyLoggerOptions>;
|
|
9
|
+
|
|
10
|
+
export { type FastifyLoggerOptions, fastifyLogger };
|
package/dist/fastify.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { F as Fields, L as Logger } from './logger-CFMTzjsm.js';
|
|
2
|
+
|
|
3
|
+
type HeaderSource = Headers | Record<string, string | string[] | undefined>;
|
|
4
|
+
declare function requestContextFields(headers: HeaderSource): {
|
|
5
|
+
request_id: string;
|
|
6
|
+
trace_id?: string;
|
|
7
|
+
span_id?: string;
|
|
8
|
+
};
|
|
9
|
+
declare function propagationHeaders(): Record<string, string>;
|
|
10
|
+
declare const DEFAULT_SKIP_PATHS: (string | RegExp)[];
|
|
11
|
+
interface HttpLogOptions {
|
|
12
|
+
skipPaths?: (string | RegExp)[];
|
|
13
|
+
}
|
|
14
|
+
interface HttpRequestInfo {
|
|
15
|
+
method: string;
|
|
16
|
+
route?: string;
|
|
17
|
+
path: string;
|
|
18
|
+
status: number;
|
|
19
|
+
duration_ms?: number;
|
|
20
|
+
err?: unknown;
|
|
21
|
+
fields?: Fields;
|
|
22
|
+
}
|
|
23
|
+
declare function logHttpRequest(log: Logger, info: HttpRequestInfo, opts?: HttpLogOptions): void;
|
|
24
|
+
|
|
25
|
+
export { DEFAULT_SKIP_PATHS as D, type HttpLogOptions as H, type HttpRequestInfo as a, type HeaderSource as b, logHttpRequest as l, propagationHeaders as p, requestContextFields as r };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { F as Fields, L as Logger } from './logger-CFMTzjsm.cjs';
|
|
2
|
+
|
|
3
|
+
type HeaderSource = Headers | Record<string, string | string[] | undefined>;
|
|
4
|
+
declare function requestContextFields(headers: HeaderSource): {
|
|
5
|
+
request_id: string;
|
|
6
|
+
trace_id?: string;
|
|
7
|
+
span_id?: string;
|
|
8
|
+
};
|
|
9
|
+
declare function propagationHeaders(): Record<string, string>;
|
|
10
|
+
declare const DEFAULT_SKIP_PATHS: (string | RegExp)[];
|
|
11
|
+
interface HttpLogOptions {
|
|
12
|
+
skipPaths?: (string | RegExp)[];
|
|
13
|
+
}
|
|
14
|
+
interface HttpRequestInfo {
|
|
15
|
+
method: string;
|
|
16
|
+
route?: string;
|
|
17
|
+
path: string;
|
|
18
|
+
status: number;
|
|
19
|
+
duration_ms?: number;
|
|
20
|
+
err?: unknown;
|
|
21
|
+
fields?: Fields;
|
|
22
|
+
}
|
|
23
|
+
declare function logHttpRequest(log: Logger, info: HttpRequestInfo, opts?: HttpLogOptions): void;
|
|
24
|
+
|
|
25
|
+
export { DEFAULT_SKIP_PATHS as D, type HttpLogOptions as H, type HttpRequestInfo as a, type HeaderSource as b, logHttpRequest as l, propagationHeaders as p, requestContextFields as r };
|