evlog 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/{_severity-CLNgC2HU.mjs → _severity-Q1BuITU_.mjs} +1 -1
- package/dist/{_severity-CLNgC2HU.mjs.map → _severity-Q1BuITU_.mjs.map} +1 -1
- package/dist/adapters/axiom.d.mts +1 -1
- package/dist/adapters/better-stack.d.mts +1 -1
- package/dist/adapters/otlp.d.mts +1 -1
- package/dist/adapters/otlp.mjs +1 -1
- package/dist/adapters/posthog.d.mts +1 -1
- package/dist/adapters/sentry.d.mts +1 -1
- package/dist/adapters/sentry.mjs +1 -1
- package/dist/browser.d.mts +1 -1
- package/dist/{dist-Dalk68oO.mjs → dist-BsWcv7B8.mjs} +1 -1
- package/dist/{dist-Dalk68oO.mjs.map → dist-BsWcv7B8.mjs.map} +1 -1
- package/dist/elysia/index.d.mts +2 -2
- package/dist/elysia/index.mjs +1 -1
- package/dist/enrichers.d.mts +1 -1
- package/dist/{error-Bu4fv0NO.d.mts → error-iV3zJCY3.d.mts} +2 -2
- package/dist/{error-Bu4fv0NO.d.mts.map → error-iV3zJCY3.d.mts.map} +1 -1
- package/dist/error.d.mts +1 -1
- package/dist/errors-BJRXUfMg.mjs +18 -0
- package/dist/errors-BJRXUfMg.mjs.map +1 -0
- package/dist/errors-CKSfdvLa.d.mts +39 -0
- package/dist/errors-CKSfdvLa.d.mts.map +1 -0
- package/dist/express/index.d.mts +2 -2
- package/dist/express/index.mjs +2 -2
- package/dist/fastify/index.d.mts +2 -2
- package/dist/fastify/index.mjs +2 -2
- package/dist/{headers-C95MOQ4w.mjs → headers-Ba1eKT3i.mjs} +5 -3
- package/dist/headers-Ba1eKT3i.mjs.map +1 -0
- package/dist/hono/index.d.mts +2 -2
- package/dist/hono/index.mjs +1 -1
- package/dist/index.d.mts +5 -5
- package/dist/{logger-BNxG5Kyd.d.mts → logger-CvDYZUze.d.mts} +2 -2
- package/dist/{logger-BNxG5Kyd.d.mts.map → logger-CvDYZUze.d.mts.map} +1 -1
- package/dist/logger.d.mts +1 -1
- package/dist/logger.mjs +44 -1
- package/dist/logger.mjs.map +1 -1
- package/dist/middleware-hZqyXoSk.d.mts +75 -0
- package/dist/middleware-hZqyXoSk.d.mts.map +1 -0
- package/dist/nestjs/index.d.mts +2 -2
- package/dist/nestjs/index.mjs +2 -2
- package/dist/next/client.d.mts +1 -1
- package/dist/next/index.d.mts +4 -4
- package/dist/next/index.mjs +1 -1
- package/dist/nitro/errorHandler.mjs +2 -1
- package/dist/nitro/errorHandler.mjs.map +1 -1
- package/dist/nitro/module.d.mts +2 -2
- package/dist/nitro/plugin.mjs +2 -2
- package/dist/nitro/v3/errorHandler.mjs +3 -2
- package/dist/nitro/v3/errorHandler.mjs.map +1 -1
- package/dist/nitro/v3/module.d.mts +1 -1
- package/dist/nitro/v3/module.mjs +3 -1
- package/dist/nitro/v3/module.mjs.map +1 -1
- package/dist/nitro/v3/plugin.d.mts +3 -1
- package/dist/nitro/v3/plugin.mjs +3 -3
- package/dist/nitro/v3/useLogger.d.mts +1 -1
- package/dist/{nitro-wZ05H2wx.d.mts → nitro-CGGTUned.d.mts} +2 -2
- package/dist/nitro-CGGTUned.d.mts.map +1 -0
- package/dist/{nitro-B3vZrLGI.mjs → nitro-D1pPm37T.mjs} +3 -8
- package/dist/nitro-D1pPm37T.mjs.map +1 -0
- package/dist/nuxt/module.d.mts +1 -1
- package/dist/nuxt/module.mjs +1 -1
- package/dist/{parseError-BeVEVQBh.d.mts → parseError-BztqcPwZ.d.mts} +2 -2
- package/dist/parseError-BztqcPwZ.d.mts.map +1 -0
- package/dist/{routes-DdmLpEnG.mjs → routes-CE3_c-iZ.mjs} +1 -1
- package/dist/{routes-DdmLpEnG.mjs.map → routes-CE3_c-iZ.mjs.map} +1 -1
- package/dist/runtime/client/log.d.mts +1 -1
- package/dist/runtime/client/log.d.mts.map +1 -1
- package/dist/runtime/client/log.mjs +3 -9
- package/dist/runtime/client/log.mjs.map +1 -1
- package/dist/runtime/server/useLogger.d.mts +1 -1
- package/dist/runtime/utils/parseError.d.mts +2 -2
- package/dist/{storage-CRSAAEiG.mjs → storage-CJBW5Vos.mjs} +3 -1
- package/dist/storage-CJBW5Vos.mjs.map +1 -0
- package/dist/sveltekit/index.d.mts +2 -2
- package/dist/sveltekit/index.mjs +4 -3
- package/dist/sveltekit/index.mjs.map +1 -1
- package/dist/toolkit.d.mts +38 -0
- package/dist/toolkit.d.mts.map +1 -0
- package/dist/toolkit.mjs +5 -0
- package/dist/{types-CRj5tGVA.d.mts → types-B8-kC2ME.d.mts} +1 -1
- package/dist/{types-CRj5tGVA.d.mts.map → types-B8-kC2ME.d.mts.map} +1 -1
- package/dist/types.d.mts +1 -1
- package/dist/{useLogger-w7MZjqW_.d.mts → useLogger-_Ec6mXoR.d.mts} +2 -2
- package/dist/{useLogger-w7MZjqW_.d.mts.map → useLogger-_Ec6mXoR.d.mts.map} +1 -1
- package/dist/utils.d.mts +18 -2
- package/dist/utils.d.mts.map +1 -1
- package/dist/utils.mjs +27 -1
- package/dist/utils.mjs.map +1 -1
- package/dist/workers.d.mts +1 -1
- package/package.json +14 -6
- package/dist/headers-C95MOQ4w.mjs.map +0 -1
- package/dist/middleware-gCuB0w7F.d.mts +0 -36
- package/dist/middleware-gCuB0w7F.d.mts.map +0 -1
- package/dist/nitro-B3vZrLGI.mjs.map +0 -1
- package/dist/nitro-wZ05H2wx.d.mts.map +0 -1
- package/dist/parseError-BeVEVQBh.d.mts.map +0 -1
- package/dist/storage-CRSAAEiG.mjs.map +0 -1
package/dist/logger.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.mjs","names":[],"sources":["../src/logger.ts"],"sourcesContent":["import type { DrainContext, EnvironmentContext, FieldContext, Log, LogLevel, LoggerConfig, RequestLogger, RequestLoggerOptions, SamplingConfig, TailSamplingContext, WideEvent } from './types'\nimport { colors, detectEnvironment, formatDuration, getConsoleMethod, getLevelColor, isDev, matchesPattern } from './utils'\n\nfunction isPlainObject(val: unknown): val is Record<string, unknown> {\n return val !== null && typeof val === 'object' && !Array.isArray(val)\n}\n\nfunction deepDefaults(base: Record<string, unknown>, defaults: Record<string, unknown>): Record<string, unknown> {\n const result = { ...base }\n for (const key in defaults) {\n const baseVal = result[key]\n const defaultVal = defaults[key]\n if (baseVal === undefined || baseVal === null) {\n result[key] = defaultVal\n } else if (isPlainObject(baseVal) && isPlainObject(defaultVal)) {\n result[key] = deepDefaults(baseVal, defaultVal)\n }\n }\n return result\n}\n\nlet globalEnv: EnvironmentContext = {\n service: 'app',\n environment: 'development',\n}\n\nlet globalPretty = isDev()\nlet globalSampling: SamplingConfig = {}\nlet globalStringify = true\nlet globalDrain: ((ctx: DrainContext) => void | Promise<void>) | undefined\nlet globalEnabled = true\n\n/**\n * Initialize the logger with configuration.\n * Call this once at application startup.\n */\nexport function initLogger(config: LoggerConfig = {}): void {\n globalEnabled = config.enabled ?? true\n const detected = detectEnvironment()\n\n globalEnv = {\n service: config.env?.service ?? detected.service ?? 'app',\n environment: config.env?.environment ?? detected.environment ?? 'development',\n version: config.env?.version ?? detected.version,\n commitHash: config.env?.commitHash ?? detected.commitHash,\n region: config.env?.region ?? detected.region,\n }\n\n globalPretty = config.pretty ?? isDev()\n globalSampling = config.sampling ?? {}\n globalStringify = config.stringify ?? true\n globalDrain = config.drain\n}\n\n/**\n * Check if logging is globally enabled.\n */\nexport function isEnabled(): boolean {\n return globalEnabled\n}\n\n/**\n * Determine if a log at the given level should be emitted based on sampling config.\n * Error level defaults to 100% (always logged) unless explicitly configured otherwise.\n */\nfunction shouldSample(level: LogLevel): boolean {\n const { rates } = globalSampling\n if (!rates) {\n return true // No sampling configured, log everything\n }\n\n // Error defaults to 100% unless explicitly set\n const percentage = level === 'error' && rates.error === undefined\n ? 100\n : rates[level] ?? 100\n\n // 0% = never log, 100% = always log\n if (percentage <= 0) return false\n if (percentage >= 100) return true\n\n return Math.random() * 100 < percentage\n}\n\n/**\n * Evaluate tail sampling conditions to determine if a log should be force-kept.\n * Returns true if ANY condition matches (OR logic).\n */\nexport function shouldKeep(ctx: TailSamplingContext): boolean {\n const { keep } = globalSampling\n if (!keep?.length) return false\n\n return keep.some((condition) => {\n if (condition.status !== undefined && ctx.status !== undefined && ctx.status >= condition.status) {\n return true\n }\n if (condition.duration !== undefined && ctx.duration !== undefined && ctx.duration >= condition.duration) {\n return true\n }\n if (condition.path && ctx.path && matchesPattern(ctx.path, condition.path)) {\n return true\n }\n return false\n })\n}\n\nfunction emitWideEvent(level: LogLevel, event: Record<string, unknown>, skipSamplingCheck = false): WideEvent | null {\n if (!globalEnabled) return null\n\n if (!skipSamplingCheck && !shouldSample(level)) {\n return null\n }\n\n const formatted: WideEvent = {\n timestamp: new Date().toISOString(),\n level,\n ...globalEnv,\n ...event,\n }\n\n if (globalPretty) {\n prettyPrintWideEvent(formatted)\n } else if (globalStringify) {\n console[getConsoleMethod(level)](JSON.stringify(formatted))\n } else {\n console[getConsoleMethod(level)](formatted)\n }\n\n if (globalDrain) {\n Promise.resolve(globalDrain({ event: formatted })).catch((err) => {\n console.error('[evlog] drain failed:', err)\n })\n }\n\n return formatted\n}\n\nfunction emitTaggedLog(level: LogLevel, tag: string, message: string): void {\n if (!globalEnabled) return\n\n if (globalPretty) {\n if (!shouldSample(level)) {\n return\n }\n const color = getLevelColor(level)\n const timestamp = new Date().toISOString().slice(11, 23)\n console.log(`${colors.dim}${timestamp}${colors.reset} ${color}[${tag}]${colors.reset} ${message}`)\n return\n }\n emitWideEvent(level, { tag, message })\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null || value === undefined) {\n return String(value)\n }\n if (typeof value === 'object') {\n // Flatten object to key=value pairs\n const pairs: string[] = []\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (v !== undefined && v !== null) {\n if (typeof v === 'object') {\n // For nested objects, show as JSON\n pairs.push(`${k}=${JSON.stringify(v)}`)\n } else {\n pairs.push(`${k}=${v}`)\n }\n }\n }\n return pairs.join(' ')\n }\n return String(value)\n}\n\nfunction prettyPrintWideEvent(event: Record<string, unknown>): void {\n const { timestamp, level, service, environment, version, ...rest } = event\n const levelColor = getLevelColor(level as string)\n const ts = (timestamp as string).slice(11, 23)\n\n let header = `${colors.dim}${ts}${colors.reset} ${levelColor}${(level as string).toUpperCase()}${colors.reset}`\n header += ` ${colors.cyan}[${service}]${colors.reset}`\n\n if (rest.method && rest.path) {\n header += ` ${rest.method} ${rest.path}`\n delete rest.method\n delete rest.path\n }\n\n if (rest.status) {\n const statusColor = (rest.status as number) >= 400 ? colors.red : colors.green\n header += ` ${statusColor}${rest.status}${colors.reset}`\n delete rest.status\n }\n\n if (rest.duration) {\n header += ` ${colors.dim}in ${rest.duration}${colors.reset}`\n delete rest.duration\n }\n\n console.log(header)\n\n const entries = Object.entries(rest).filter(([_, v]) => v !== undefined)\n const lastIndex = entries.length - 1\n\n entries.forEach(([key, value], index) => {\n const isLast = index === lastIndex\n const prefix = isLast ? '└─' : '├─'\n const formatted = formatValue(value)\n console.log(` ${colors.dim}${prefix}${colors.reset} ${colors.cyan}${key}:${colors.reset} ${formatted}`)\n })\n}\n\nfunction createLogMethod(level: LogLevel) {\n return function logMethod(tagOrEvent: string | Record<string, unknown>, message?: string): void {\n if (typeof tagOrEvent === 'string' && message !== undefined) {\n emitTaggedLog(level, tagOrEvent, message)\n } else if (typeof tagOrEvent === 'object') {\n emitWideEvent(level, tagOrEvent)\n } else {\n emitTaggedLog(level, 'log', String(tagOrEvent))\n }\n }\n}\n\n/**\n * Simple logging API - as easy as console.log\n *\n * @example\n * ```ts\n * log.info('auth', 'User logged in')\n * log.error({ action: 'payment', error: 'failed' })\n * ```\n */\nconst _log: Log = {\n info: createLogMethod('info'),\n error: createLogMethod('error'),\n warn: createLogMethod('warn'),\n debug: createLogMethod('debug'),\n}\n\nexport { _log as log }\n\nconst noopLogger: RequestLogger = {\n set() {},\n error() {},\n info() {},\n warn() {},\n emit() {\n return null\n },\n getContext() {\n return {}\n },\n}\n\n/**\n * Create a scoped logger for building wide events.\n * Use this for any context: workflows, jobs, scripts, queues, etc.\n *\n * @example\n * ```ts\n * const log = createLogger({ jobId: job.id, queue: 'emails' })\n * log.set({ batch: { size: 50, processed: 12 } })\n * log.emit()\n * ```\n */\nexport function createLogger<T extends object = Record<string, unknown>>(initialContext: Record<string, unknown> = {}): RequestLogger<T> {\n if (!globalEnabled) return noopLogger as RequestLogger<T>\n\n const startTime = Date.now()\n let context: Record<string, unknown> = { ...initialContext }\n let hasError = false\n let hasWarn = false\n\n function addLog(level: 'info' | 'warn', message: string): void {\n const entry = {\n level,\n message,\n timestamp: new Date().toISOString(),\n }\n\n const requestLogs = Array.isArray(context.requestLogs)\n ? [...context.requestLogs, entry]\n : [entry]\n\n context = {\n ...context,\n requestLogs,\n }\n }\n\n return {\n set(data: FieldContext<T>): void {\n context = deepDefaults(data as Record<string, unknown>, context) as Record<string, unknown>\n },\n\n error(error: Error | string, errorContext?: FieldContext<T>): void {\n hasError = true\n const err = typeof error === 'string' ? new Error(error) : error\n\n const errorData = {\n ...(errorContext as Record<string, unknown>),\n error: {\n name: err.name,\n message: err.message,\n stack: err.stack,\n ...('status' in err && { status: (err as Record<string, unknown>).status }),\n ...('statusText' in err && { statusText: (err as Record<string, unknown>).statusText }),\n ...('statusCode' in err && { statusCode: (err as Record<string, unknown>).statusCode }),\n ...('statusMessage' in err && { statusMessage: (err as Record<string, unknown>).statusMessage }),\n ...('data' in err && { data: (err as Record<string, unknown>).data }),\n ...('cause' in err && { cause: (err as unknown as Record<string, unknown>).cause }),\n },\n }\n context = deepDefaults(errorData, context) as Record<string, unknown>\n },\n\n info(message: string, infoContext?: FieldContext<T>): void {\n addLog('info', message)\n if (infoContext) {\n const { requestLogs: _, ...rest } = infoContext as Record<string, unknown>\n context = deepDefaults(rest, context) as Record<string, unknown>\n }\n },\n\n warn(message: string, warnContext?: FieldContext<T>): void {\n hasWarn = true\n addLog('warn', message)\n if (warnContext) {\n const { requestLogs: _, ...rest } = warnContext as Record<string, unknown>\n context = deepDefaults(rest, context) as Record<string, unknown>\n }\n },\n\n emit(overrides?: FieldContext<T> & { _forceKeep?: boolean }): WideEvent | null {\n const durationMs = Date.now() - startTime\n const duration = formatDuration(durationMs)\n const level: LogLevel = hasError ? 'error' : hasWarn ? 'warn' : 'info'\n\n // Extract _forceKeep from overrides (set by evlog:emit:keep hook)\n const { _forceKeep, ...restOverrides } = (overrides ?? {}) as Record<string, unknown> & { _forceKeep?: boolean }\n\n // Build tail sampling context\n const tailCtx: TailSamplingContext = {\n status: (context.status ?? restOverrides.status) as number | undefined,\n duration: durationMs,\n path: context.path as string | undefined,\n method: context.method as string | undefined,\n context: { ...context, ...restOverrides },\n }\n\n // Tail sampling: force keep if hook or built-in conditions match\n const forceKeep = _forceKeep || shouldKeep(tailCtx)\n\n // Apply head sampling only if not force-kept\n if (!forceKeep && !shouldSample(level)) {\n return null\n }\n\n return emitWideEvent(level, {\n ...context,\n ...restOverrides,\n duration,\n }, true)\n },\n\n getContext(): FieldContext<T> & Record<string, unknown> {\n return { ...context }\n },\n }\n}\n\n/**\n * Create a request-scoped logger for building wide events.\n * Convenience wrapper around `createLogger` that pre-populates HTTP request fields.\n *\n * @example\n * ```ts\n * const log = createRequestLogger({ method: 'POST', path: '/checkout' })\n * log.set({ user: { id: '123' } })\n * log.set({ cart: { items: 3 } })\n * log.emit()\n * ```\n */\nexport function createRequestLogger<T extends object = Record<string, unknown>>(options: RequestLoggerOptions = {}): RequestLogger<T> {\n const initial: Record<string, unknown> = {}\n if (options.method !== undefined) initial.method = options.method\n if (options.path !== undefined) initial.path = options.path\n if (options.requestId !== undefined) initial.requestId = options.requestId\n return createLogger<T>(initial)\n}\n\n/**\n * Get the current environment context.\n */\nexport function getEnvironment(): EnvironmentContext {\n return { ...globalEnv }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"logger.mjs","names":[],"sources":["../src/logger.ts"],"sourcesContent":["import type { DrainContext, EnvironmentContext, FieldContext, Log, LogLevel, LoggerConfig, RequestLogger, RequestLoggerOptions, SamplingConfig, TailSamplingContext, WideEvent } from './types'\nimport { colors, cssColors, detectEnvironment, escapeFormatString, formatDuration, getConsoleMethod, getCssLevelColor, getLevelColor, isClient, isDev, matchesPattern } from './utils'\n\nfunction isPlainObject(val: unknown): val is Record<string, unknown> {\n return val !== null && typeof val === 'object' && !Array.isArray(val)\n}\n\nfunction deepDefaults(base: Record<string, unknown>, defaults: Record<string, unknown>): Record<string, unknown> {\n const result = { ...base }\n for (const key in defaults) {\n const baseVal = result[key]\n const defaultVal = defaults[key]\n if (baseVal === undefined || baseVal === null) {\n result[key] = defaultVal\n } else if (isPlainObject(baseVal) && isPlainObject(defaultVal)) {\n result[key] = deepDefaults(baseVal, defaultVal)\n }\n }\n return result\n}\n\nlet globalEnv: EnvironmentContext = {\n service: 'app',\n environment: 'development',\n}\n\nlet globalPretty = isDev()\nlet globalSampling: SamplingConfig = {}\nlet globalStringify = true\nlet globalDrain: ((ctx: DrainContext) => void | Promise<void>) | undefined\nlet globalEnabled = true\n\n/**\n * Initialize the logger with configuration.\n * Call this once at application startup.\n */\nexport function initLogger(config: LoggerConfig = {}): void {\n globalEnabled = config.enabled ?? true\n const detected = detectEnvironment()\n\n globalEnv = {\n service: config.env?.service ?? detected.service ?? 'app',\n environment: config.env?.environment ?? detected.environment ?? 'development',\n version: config.env?.version ?? detected.version,\n commitHash: config.env?.commitHash ?? detected.commitHash,\n region: config.env?.region ?? detected.region,\n }\n\n globalPretty = config.pretty ?? isDev()\n globalSampling = config.sampling ?? {}\n globalStringify = config.stringify ?? true\n globalDrain = config.drain\n}\n\n/**\n * Check if logging is globally enabled.\n */\nexport function isEnabled(): boolean {\n return globalEnabled\n}\n\n/**\n * Determine if a log at the given level should be emitted based on sampling config.\n * Error level defaults to 100% (always logged) unless explicitly configured otherwise.\n */\nfunction shouldSample(level: LogLevel): boolean {\n const { rates } = globalSampling\n if (!rates) {\n return true // No sampling configured, log everything\n }\n\n // Error defaults to 100% unless explicitly set\n const percentage = level === 'error' && rates.error === undefined\n ? 100\n : rates[level] ?? 100\n\n // 0% = never log, 100% = always log\n if (percentage <= 0) return false\n if (percentage >= 100) return true\n\n return Math.random() * 100 < percentage\n}\n\n/**\n * Evaluate tail sampling conditions to determine if a log should be force-kept.\n * Returns true if ANY condition matches (OR logic).\n */\nexport function shouldKeep(ctx: TailSamplingContext): boolean {\n const { keep } = globalSampling\n if (!keep?.length) return false\n\n return keep.some((condition) => {\n if (condition.status !== undefined && ctx.status !== undefined && ctx.status >= condition.status) {\n return true\n }\n if (condition.duration !== undefined && ctx.duration !== undefined && ctx.duration >= condition.duration) {\n return true\n }\n if (condition.path && ctx.path && matchesPattern(ctx.path, condition.path)) {\n return true\n }\n return false\n })\n}\n\nfunction emitWideEvent(level: LogLevel, event: Record<string, unknown>, skipSamplingCheck = false): WideEvent | null {\n if (!globalEnabled) return null\n\n if (!skipSamplingCheck && !shouldSample(level)) {\n return null\n }\n\n const formatted: WideEvent = {\n timestamp: new Date().toISOString(),\n level,\n ...globalEnv,\n ...event,\n }\n\n if (globalPretty) {\n prettyPrintWideEvent(formatted)\n } else if (globalStringify) {\n console[getConsoleMethod(level)](JSON.stringify(formatted))\n } else {\n console[getConsoleMethod(level)](formatted)\n }\n\n if (globalDrain) {\n Promise.resolve(globalDrain({ event: formatted })).catch((err) => {\n console.error('[evlog] drain failed:', err)\n })\n }\n\n return formatted\n}\n\nfunction emitTaggedLog(level: LogLevel, tag: string, message: string): void {\n if (!globalEnabled) return\n\n if (globalPretty) {\n if (!shouldSample(level)) {\n return\n }\n\n if (isClient()) {\n const levelColor = getCssLevelColor(level)\n const timestamp = new Date().toISOString().slice(11, 23)\n console.log(\n `%c${timestamp}%c %c[${escapeFormatString(tag)}]%c ${escapeFormatString(message)}`,\n cssColors.dim,\n cssColors.reset,\n levelColor,\n cssColors.reset,\n )\n return\n }\n\n const color = getLevelColor(level)\n const timestamp = new Date().toISOString().slice(11, 23)\n console.log(`${colors.dim}${timestamp}${colors.reset} ${color}[${tag}]${colors.reset} ${message}`)\n return\n }\n emitWideEvent(level, { tag, message })\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null || value === undefined) {\n return String(value)\n }\n if (typeof value === 'object') {\n // Flatten object to key=value pairs\n const pairs: string[] = []\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (v !== undefined && v !== null) {\n if (typeof v === 'object') {\n // For nested objects, show as JSON\n pairs.push(`${k}=${JSON.stringify(v)}`)\n } else {\n pairs.push(`${k}=${v}`)\n }\n }\n }\n return pairs.join(' ')\n }\n return String(value)\n}\n\nfunction prettyPrintWideEvent(event: Record<string, unknown>): void {\n if (isClient()) {\n prettyPrintWideEventBrowser(event)\n return\n }\n\n const { timestamp, level, service, environment, version, ...rest } = event\n const levelColor = getLevelColor(level as string)\n const ts = (timestamp as string).slice(11, 23)\n\n let header = `${colors.dim}${ts}${colors.reset} ${levelColor}${(level as string).toUpperCase()}${colors.reset}`\n header += ` ${colors.cyan}[${service}]${colors.reset}`\n\n if (rest.method && rest.path) {\n header += ` ${rest.method} ${rest.path}`\n delete rest.method\n delete rest.path\n }\n\n if (rest.status) {\n const statusColor = (rest.status as number) >= 400 ? colors.red : colors.green\n header += ` ${statusColor}${rest.status}${colors.reset}`\n delete rest.status\n }\n\n if (rest.duration) {\n header += ` ${colors.dim}in ${rest.duration}${colors.reset}`\n delete rest.duration\n }\n\n console.log(header)\n\n const entries = Object.entries(rest).filter(([_, v]) => v !== undefined)\n const lastIndex = entries.length - 1\n\n entries.forEach(([key, value], index) => {\n const isLast = index === lastIndex\n const prefix = isLast ? '└─' : '├─'\n const formatted = formatValue(value)\n console.log(` ${colors.dim}${prefix}${colors.reset} ${colors.cyan}${key}:${colors.reset} ${formatted}`)\n })\n}\n\nfunction prettyPrintWideEventBrowser(event: Record<string, unknown>): void {\n const { timestamp, level, service, environment, version, ...rest } = event\n const levelColor = getCssLevelColor(level as string)\n const ts = (timestamp as string).slice(11, 23)\n\n const parts: string[] = []\n const styles: string[] = []\n\n parts.push(`%c${ts}%c %c${(level as string).toUpperCase()}%c %c[${escapeFormatString(String(service))}]%c`)\n styles.push(cssColors.dim, cssColors.reset, levelColor, cssColors.reset, cssColors.cyan, cssColors.reset)\n\n if (rest.method && rest.path) {\n parts.push(` ${escapeFormatString(String(rest.method))} ${escapeFormatString(String(rest.path))}`)\n delete rest.method\n delete rest.path\n }\n\n if (rest.status) {\n const statusColor = (rest.status as number) >= 400 ? cssColors.red : cssColors.green\n parts.push(` %c${rest.status}%c`)\n styles.push(statusColor, cssColors.reset)\n delete rest.status\n }\n\n if (rest.duration) {\n parts.push(` %c${escapeFormatString(`in ${rest.duration}`)}%c`)\n styles.push(cssColors.dim, cssColors.reset)\n delete rest.duration\n }\n\n console.log(parts.join(''), ...styles)\n\n const entries = Object.entries(rest).filter(([_, v]) => v !== undefined)\n const lastIndex = entries.length - 1\n\n entries.forEach(([key, value], index) => {\n const isLast = index === lastIndex\n const prefix = isLast ? '└─' : '├─'\n const formatted = escapeFormatString(formatValue(value))\n console.log(\n ` %c${prefix}%c %c${escapeFormatString(key)}:%c ${formatted}`,\n cssColors.dim,\n cssColors.reset,\n cssColors.cyan,\n cssColors.reset,\n )\n })\n}\n\nfunction createLogMethod(level: LogLevel) {\n return function logMethod(tagOrEvent: string | Record<string, unknown>, message?: string): void {\n if (typeof tagOrEvent === 'string' && message !== undefined) {\n emitTaggedLog(level, tagOrEvent, message)\n } else if (typeof tagOrEvent === 'object') {\n emitWideEvent(level, tagOrEvent)\n } else {\n emitTaggedLog(level, 'log', String(tagOrEvent))\n }\n }\n}\n\n/**\n * Simple logging API - as easy as console.log\n *\n * @example\n * ```ts\n * log.info('auth', 'User logged in')\n * log.error({ action: 'payment', error: 'failed' })\n * ```\n */\nconst _log: Log = {\n info: createLogMethod('info'),\n error: createLogMethod('error'),\n warn: createLogMethod('warn'),\n debug: createLogMethod('debug'),\n}\n\nexport { _log as log }\n\nconst noopLogger: RequestLogger = {\n set() {},\n error() {},\n info() {},\n warn() {},\n emit() {\n return null\n },\n getContext() {\n return {}\n },\n}\n\n/**\n * Create a scoped logger for building wide events.\n * Use this for any context: workflows, jobs, scripts, queues, etc.\n *\n * @example\n * ```ts\n * const log = createLogger({ jobId: job.id, queue: 'emails' })\n * log.set({ batch: { size: 50, processed: 12 } })\n * log.emit()\n * ```\n */\nexport function createLogger<T extends object = Record<string, unknown>>(initialContext: Record<string, unknown> = {}): RequestLogger<T> {\n if (!globalEnabled) return noopLogger as RequestLogger<T>\n\n const startTime = Date.now()\n let context: Record<string, unknown> = { ...initialContext }\n let hasError = false\n let hasWarn = false\n\n function addLog(level: 'info' | 'warn', message: string): void {\n const entry = {\n level,\n message,\n timestamp: new Date().toISOString(),\n }\n\n const requestLogs = Array.isArray(context.requestLogs)\n ? [...context.requestLogs, entry]\n : [entry]\n\n context = {\n ...context,\n requestLogs,\n }\n }\n\n return {\n set(data: FieldContext<T>): void {\n context = deepDefaults(data as Record<string, unknown>, context) as Record<string, unknown>\n },\n\n error(error: Error | string, errorContext?: FieldContext<T>): void {\n hasError = true\n const err = typeof error === 'string' ? new Error(error) : error\n\n const errorData = {\n ...(errorContext as Record<string, unknown>),\n error: {\n name: err.name,\n message: err.message,\n stack: err.stack,\n ...('status' in err && { status: (err as Record<string, unknown>).status }),\n ...('statusText' in err && { statusText: (err as Record<string, unknown>).statusText }),\n ...('statusCode' in err && { statusCode: (err as Record<string, unknown>).statusCode }),\n ...('statusMessage' in err && { statusMessage: (err as Record<string, unknown>).statusMessage }),\n ...('data' in err && { data: (err as Record<string, unknown>).data }),\n ...('cause' in err && { cause: (err as unknown as Record<string, unknown>).cause }),\n },\n }\n context = deepDefaults(errorData, context) as Record<string, unknown>\n },\n\n info(message: string, infoContext?: FieldContext<T>): void {\n addLog('info', message)\n if (infoContext) {\n const { requestLogs: _, ...rest } = infoContext as Record<string, unknown>\n context = deepDefaults(rest, context) as Record<string, unknown>\n }\n },\n\n warn(message: string, warnContext?: FieldContext<T>): void {\n hasWarn = true\n addLog('warn', message)\n if (warnContext) {\n const { requestLogs: _, ...rest } = warnContext as Record<string, unknown>\n context = deepDefaults(rest, context) as Record<string, unknown>\n }\n },\n\n emit(overrides?: FieldContext<T> & { _forceKeep?: boolean }): WideEvent | null {\n const durationMs = Date.now() - startTime\n const duration = formatDuration(durationMs)\n const level: LogLevel = hasError ? 'error' : hasWarn ? 'warn' : 'info'\n\n // Extract _forceKeep from overrides (set by evlog:emit:keep hook)\n const { _forceKeep, ...restOverrides } = (overrides ?? {}) as Record<string, unknown> & { _forceKeep?: boolean }\n\n // Build tail sampling context\n const tailCtx: TailSamplingContext = {\n status: (context.status ?? restOverrides.status) as number | undefined,\n duration: durationMs,\n path: context.path as string | undefined,\n method: context.method as string | undefined,\n context: { ...context, ...restOverrides },\n }\n\n // Tail sampling: force keep if hook or built-in conditions match\n const forceKeep = _forceKeep || shouldKeep(tailCtx)\n\n // Apply head sampling only if not force-kept\n if (!forceKeep && !shouldSample(level)) {\n return null\n }\n\n return emitWideEvent(level, {\n ...context,\n ...restOverrides,\n duration,\n }, true)\n },\n\n getContext(): FieldContext<T> & Record<string, unknown> {\n return { ...context }\n },\n }\n}\n\n/**\n * Create a request-scoped logger for building wide events.\n * Convenience wrapper around `createLogger` that pre-populates HTTP request fields.\n *\n * @example\n * ```ts\n * const log = createRequestLogger({ method: 'POST', path: '/checkout' })\n * log.set({ user: { id: '123' } })\n * log.set({ cart: { items: 3 } })\n * log.emit()\n * ```\n */\nexport function createRequestLogger<T extends object = Record<string, unknown>>(options: RequestLoggerOptions = {}): RequestLogger<T> {\n const initial: Record<string, unknown> = {}\n if (options.method !== undefined) initial.method = options.method\n if (options.path !== undefined) initial.path = options.path\n if (options.requestId !== undefined) initial.requestId = options.requestId\n return createLogger<T>(initial)\n}\n\n/**\n * Get the current environment context.\n */\nexport function getEnvironment(): EnvironmentContext {\n return { ...globalEnv }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { C as TailSamplingContext, T as WideEvent, g as RequestLogger, i as EnrichContext, r as DrainContext, v as RouteConfig } from "./types-B8-kC2ME.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/shared/middleware.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Base options shared by all framework integrations.
|
|
6
|
+
*
|
|
7
|
+
* Every framework-specific options interface (e.g. `EvlogExpressOptions`)
|
|
8
|
+
* extends this type. If a framework needs extra fields it can add them
|
|
9
|
+
* on top; otherwise the base is used as-is.
|
|
10
|
+
*
|
|
11
|
+
* @beta Part of `evlog/toolkit` — the public API for building custom integrations.
|
|
12
|
+
*/
|
|
13
|
+
interface BaseEvlogOptions {
|
|
14
|
+
/** Route patterns to include in logging (glob). If not set, all routes are logged */
|
|
15
|
+
include?: string[];
|
|
16
|
+
/** Route patterns to exclude from logging. Exclusions take precedence over inclusions */
|
|
17
|
+
exclude?: string[];
|
|
18
|
+
/** Route-specific service configuration */
|
|
19
|
+
routes?: Record<string, RouteConfig>;
|
|
20
|
+
/**
|
|
21
|
+
* Drain callback called with every emitted event.
|
|
22
|
+
* Use with drain adapters (Axiom, OTLP, Sentry, etc.) or custom endpoints.
|
|
23
|
+
*/
|
|
24
|
+
drain?: (ctx: DrainContext) => void | Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Enrich callback called after emit, before drain.
|
|
27
|
+
* Use to add derived context (geo, deployment info, user agent, etc.).
|
|
28
|
+
*/
|
|
29
|
+
enrich?: (ctx: EnrichContext) => void | Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Custom tail sampling callback.
|
|
32
|
+
* Set `ctx.shouldKeep = true` to force-keep the log regardless of head sampling.
|
|
33
|
+
*/
|
|
34
|
+
keep?: (ctx: TailSamplingContext) => void | Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Internal options consumed by `createMiddlewareLogger`.
|
|
38
|
+
* Extends `BaseEvlogOptions` with the request-specific fields
|
|
39
|
+
* that framework adapters must provide.
|
|
40
|
+
*/
|
|
41
|
+
interface MiddlewareLoggerOptions extends BaseEvlogOptions {
|
|
42
|
+
method: string;
|
|
43
|
+
path: string;
|
|
44
|
+
requestId?: string;
|
|
45
|
+
/** Pre-filtered safe request headers (used for enrich/drain context) */
|
|
46
|
+
headers?: Record<string, string>;
|
|
47
|
+
}
|
|
48
|
+
interface MiddlewareLoggerResult {
|
|
49
|
+
logger: RequestLogger;
|
|
50
|
+
finish: (opts?: {
|
|
51
|
+
status?: number;
|
|
52
|
+
error?: Error;
|
|
53
|
+
}) => Promise<WideEvent | null>;
|
|
54
|
+
skipped: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Create a middleware-aware request logger with full lifecycle management.
|
|
58
|
+
*
|
|
59
|
+
* Handles the complete pipeline shared across all framework integrations:
|
|
60
|
+
* route filtering, logger creation, service overrides, duration tracking,
|
|
61
|
+
* tail sampling evaluation, event emission, enrichment, and draining.
|
|
62
|
+
*
|
|
63
|
+
* Framework adapters only need to:
|
|
64
|
+
* 1. Extract method/path/requestId/headers from the framework request
|
|
65
|
+
* 2. Call `createMiddlewareLogger()` with those + user options
|
|
66
|
+
* 3. Check `skipped` — if true, skip to next middleware
|
|
67
|
+
* 4. Store `logger` in framework-specific context (e.g., `c.set('log', logger)`)
|
|
68
|
+
* 5. Call `finish({ status })` or `finish({ error })` at response end
|
|
69
|
+
*
|
|
70
|
+
* @beta Part of `evlog/toolkit` — the public API for building custom integrations.
|
|
71
|
+
*/
|
|
72
|
+
declare function createMiddlewareLogger(options: MiddlewareLoggerOptions): MiddlewareLoggerResult;
|
|
73
|
+
//#endregion
|
|
74
|
+
export { createMiddlewareLogger as i, MiddlewareLoggerOptions as n, MiddlewareLoggerResult as r, BaseEvlogOptions as t };
|
|
75
|
+
//# sourceMappingURL=middleware-hZqyXoSk.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware-hZqyXoSk.d.mts","names":[],"sources":["../src/shared/middleware.ts"],"mappings":";;;;;AAcA;;;;;;;UAAiB,gBAAA;EAgByB;EAdxC,OAAA;EAmB4C;EAjB5C,OAAA;EAiBmD;EAfnD,MAAA,GAAS,MAAA,SAAe,WAAA;EAFxB;;;;EAOA,KAAA,IAAS,GAAA,EAAK,YAAA,YAAwB,OAAA;EAAxB;;;;EAKd,MAAA,IAAU,GAAA,EAAK,aAAA,YAAyB,OAAA;EAA9B;;;;EAKV,IAAA,IAAQ,GAAA,EAAK,mBAAA,YAA+B,OAAA;AAAA;;;AAQ9C;;;UAAiB,uBAAA,SAAgC,gBAAA;EAC/C,MAAA;EACA,IAAA;EACA,SAAA;EAAA;EAEA,OAAA,GAAU,MAAA;AAAA;AAAA,UAGK,sBAAA;EACf,MAAA,EAAQ,aAAA;EACR,MAAA,GAAS,IAAA;IAAS,MAAA;IAAiB,KAAA,GAAQ,KAAA;EAAA,MAAY,OAAA,CAAQ,SAAA;EAC/D,OAAA;AAAA;;;;;;;;;;;;;;;;AAsEF;iBAAgB,sBAAA,CAAuB,OAAA,EAAS,uBAAA,GAA0B,sBAAA"}
|
package/dist/nestjs/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { g as RequestLogger } from "../types-
|
|
2
|
-
import { t as BaseEvlogOptions } from "../middleware-
|
|
1
|
+
import { g as RequestLogger } from "../types-B8-kC2ME.mjs";
|
|
2
|
+
import { t as BaseEvlogOptions } from "../middleware-hZqyXoSk.mjs";
|
|
3
3
|
import { DynamicModule, MiddlewareConsumer, NestModule } from "@nestjs/common";
|
|
4
4
|
|
|
5
5
|
//#region src/nestjs/index.d.ts
|
package/dist/nestjs/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { n as extractSafeNodeHeaders, r as createMiddlewareLogger } from "../headers-
|
|
2
|
-
import { t as createLoggerStorage } from "../storage-
|
|
1
|
+
import { n as extractSafeNodeHeaders, r as createMiddlewareLogger } from "../headers-Ba1eKT3i.mjs";
|
|
2
|
+
import { t as createLoggerStorage } from "../storage-CJBW5Vos.mjs";
|
|
3
3
|
//#region src/nestjs/index.ts
|
|
4
4
|
const { storage, useLogger } = createLoggerStorage("middleware context. Make sure EvlogModule.forRoot() is imported in your AppModule.");
|
|
5
5
|
function createEvlogMiddleware(getOptions) {
|
package/dist/next/client.d.mts
CHANGED
package/dist/next/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as EnvironmentContext, d as Log, g as RequestLogger, y as SamplingConfig } from "../types-
|
|
2
|
-
import { n as createError } from "../error-
|
|
3
|
-
import { t as _log } from "../logger-
|
|
4
|
-
import { t as BaseEvlogOptions } from "../middleware-
|
|
1
|
+
import { a as EnvironmentContext, d as Log, g as RequestLogger, y as SamplingConfig } from "../types-B8-kC2ME.mjs";
|
|
2
|
+
import { n as createError } from "../error-iV3zJCY3.mjs";
|
|
3
|
+
import { t as _log } from "../logger-CvDYZUze.mjs";
|
|
4
|
+
import { t as BaseEvlogOptions } from "../middleware-hZqyXoSk.mjs";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
|
|
7
7
|
//#region src/next/types.d.ts
|
package/dist/next/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { filterSafeHeaders } from "../utils.mjs";
|
|
2
2
|
import { EvlogError, createError } from "../error.mjs";
|
|
3
3
|
import { createRequestLogger, initLogger, isEnabled, log as _log } from "../logger.mjs";
|
|
4
|
-
import { n as shouldLog, t as getServiceForPath } from "../routes-
|
|
4
|
+
import { n as shouldLog, t as getServiceForPath } from "../routes-CE3_c-iZ.mjs";
|
|
5
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
6
|
//#region src/next/storage.ts
|
|
7
7
|
const evlogStorage = new AsyncLocalStorage();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as extractErrorStatus } from "../errors-BJRXUfMg.mjs";
|
|
2
|
+
import { n as serializeEvlogErrorResponse, t as resolveEvlogError } from "../nitro-D1pPm37T.mjs";
|
|
2
3
|
import { getRequestURL, send, setResponseHeader, setResponseStatus } from "h3";
|
|
3
4
|
import { defineNitroErrorHandler } from "nitropack/runtime/internal/error/utils";
|
|
4
5
|
//#region src/nitro/errorHandler.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errorHandler.mjs","names":[],"sources":["../../src/nitro/errorHandler.ts"],"sourcesContent":["// Import from specific subpath — the barrel 'nitropack/runtime' re-exports from\n// internal/app.mjs which imports virtual modules that crash outside rollup builds.\nimport { defineNitroErrorHandler } from 'nitropack/runtime/internal/error/utils'\nimport { getRequestURL, setResponseHeader, setResponseStatus, send } from 'h3'\nimport { resolveEvlogError, extractErrorStatus, serializeEvlogErrorResponse } from '../nitro'\n\n/**\n * Custom Nitro error handler that properly serializes EvlogError.\n * This ensures that 'data' (containing 'why', 'fix', 'link') is preserved\n * in the JSON response regardless of the underlying HTTP framework.\n *\n * For non-EvlogError, it preserves Nitro's default response shape while\n * sanitizing internal error details in production for 5xx errors.\n */\nexport default defineNitroErrorHandler((error, event) => {\n const evlogError = resolveEvlogError(error)\n\n const isDev = process.env.NODE_ENV === 'development'\n const url = getRequestURL(event, { xForwardedHost: true }).pathname\n\n // For non-EvlogError, preserve Nitro's default response shape\n if (!evlogError) {\n const status = extractErrorStatus(error)\n\n // Derive message from statusText/statusMessage/message for cross-version compatibility\n const rawMessage = ((error as { statusText?: string }).statusText\n ?? (error as { statusMessage?: string }).statusMessage\n ?? error.message) || 'Internal Server Error'\n\n // Sanitize internal error details in production for 5xx errors\n const message = isDev\n ? rawMessage\n : (status >= 500 ? 'Internal Server Error' : rawMessage)\n\n setResponseStatus(event, status)\n setResponseHeader(event, 'Content-Type', 'application/json')\n\n return send(event, JSON.stringify({\n url,\n status,\n statusCode: status,\n statusText: message,\n statusMessage: message,\n message,\n error: true,\n }))\n }\n\n const status = extractErrorStatus(evlogError)\n\n setResponseStatus(event, status)\n setResponseHeader(event, 'Content-Type', 'application/json')\n\n return send(event, JSON.stringify(serializeEvlogErrorResponse(evlogError, url)))\n})\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"errorHandler.mjs","names":[],"sources":["../../src/nitro/errorHandler.ts"],"sourcesContent":["// Import from specific subpath — the barrel 'nitropack/runtime' re-exports from\n// internal/app.mjs which imports virtual modules that crash outside rollup builds.\nimport { defineNitroErrorHandler } from 'nitropack/runtime/internal/error/utils'\nimport { getRequestURL, setResponseHeader, setResponseStatus, send } from 'h3'\nimport { resolveEvlogError, extractErrorStatus, serializeEvlogErrorResponse } from '../nitro'\n\n/**\n * Custom Nitro error handler that properly serializes EvlogError.\n * This ensures that 'data' (containing 'why', 'fix', 'link') is preserved\n * in the JSON response regardless of the underlying HTTP framework.\n *\n * For non-EvlogError, it preserves Nitro's default response shape while\n * sanitizing internal error details in production for 5xx errors.\n */\nexport default defineNitroErrorHandler((error, event) => {\n const evlogError = resolveEvlogError(error)\n\n const isDev = process.env.NODE_ENV === 'development'\n const url = getRequestURL(event, { xForwardedHost: true }).pathname\n\n // For non-EvlogError, preserve Nitro's default response shape\n if (!evlogError) {\n const status = extractErrorStatus(error)\n\n // Derive message from statusText/statusMessage/message for cross-version compatibility\n const rawMessage = ((error as { statusText?: string }).statusText\n ?? (error as { statusMessage?: string }).statusMessage\n ?? error.message) || 'Internal Server Error'\n\n // Sanitize internal error details in production for 5xx errors\n const message = isDev\n ? rawMessage\n : (status >= 500 ? 'Internal Server Error' : rawMessage)\n\n setResponseStatus(event, status)\n setResponseHeader(event, 'Content-Type', 'application/json')\n\n return send(event, JSON.stringify({\n url,\n status,\n statusCode: status,\n statusText: message,\n statusMessage: message,\n message,\n error: true,\n }))\n }\n\n const status = extractErrorStatus(evlogError)\n\n setResponseStatus(event, status)\n setResponseHeader(event, 'Content-Type', 'application/json')\n\n return send(event, JSON.stringify(serializeEvlogErrorResponse(evlogError, url)))\n})\n"],"mappings":";;;;;;;;;;;;;AAcA,IAAA,uBAAe,yBAAyB,OAAO,UAAU;CACvD,MAAM,aAAa,kBAAkB,MAAM;CAE3C,MAAM,QAAQ,QAAQ,IAAI,aAAa;CACvC,MAAM,MAAM,cAAc,OAAO,EAAE,gBAAgB,MAAM,CAAC,CAAC;AAG3D,KAAI,CAAC,YAAY;EACf,MAAM,SAAS,mBAAmB,MAAM;EAGxC,MAAM,cAAe,MAAkC,cACjD,MAAqC,iBACtC,MAAM,YAAY;EAGvB,MAAM,UAAU,QACZ,aACC,UAAU,MAAM,0BAA0B;AAE/C,oBAAkB,OAAO,OAAO;AAChC,oBAAkB,OAAO,gBAAgB,mBAAmB;AAE5D,SAAO,KAAK,OAAO,KAAK,UAAU;GAChC;GACA;GACA,YAAY;GACZ,YAAY;GACZ,eAAe;GACf;GACA,OAAO;GACR,CAAC,CAAC;;AAKL,mBAAkB,OAFH,mBAAmB,WAAW,CAEb;AAChC,mBAAkB,OAAO,gBAAgB,mBAAmB;AAE5D,QAAO,KAAK,OAAO,KAAK,UAAU,4BAA4B,YAAY,IAAI,CAAC,CAAC;EAChF"}
|
package/dist/nitro/module.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as useLogger } from "../useLogger-
|
|
2
|
-
import { t as NitroModuleOptions } from "../nitro-
|
|
1
|
+
import { t as useLogger } from "../useLogger-_Ec6mXoR.mjs";
|
|
2
|
+
import { t as NitroModuleOptions } from "../nitro-CGGTUned.mjs";
|
|
3
3
|
import { Nitro } from "nitropack";
|
|
4
4
|
|
|
5
5
|
//#region src/nitro/module.d.ts
|
package/dist/nitro/plugin.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { filterSafeHeaders } from "../utils.mjs";
|
|
2
2
|
import { createRequestLogger, initLogger, isEnabled } from "../logger.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { t as
|
|
3
|
+
import { t as extractErrorStatus } from "../errors-BJRXUfMg.mjs";
|
|
4
|
+
import { n as shouldLog, t as getServiceForPath } from "../routes-CE3_c-iZ.mjs";
|
|
5
5
|
import { defineNitroPlugin } from "nitropack/runtime/internal/plugin";
|
|
6
6
|
import { getHeaders } from "h3";
|
|
7
7
|
//#region src/nitro/plugin.ts
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as
|
|
1
|
+
import { t as extractErrorStatus } from "../../errors-BJRXUfMg.mjs";
|
|
2
|
+
import { n as serializeEvlogErrorResponse, t as resolveEvlogError } from "../../nitro-D1pPm37T.mjs";
|
|
3
|
+
import { t as parseURL } from "../../dist-BsWcv7B8.mjs";
|
|
3
4
|
import { defineErrorHandler } from "nitro";
|
|
4
5
|
//#region src/nitro-v3/errorHandler.ts
|
|
5
6
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errorHandler.mjs","names":[],"sources":["../../../src/nitro-v3/errorHandler.ts"],"sourcesContent":["import { parseURL } from 'ufo'\nimport { defineErrorHandler } from 'nitro'\nimport { resolveEvlogError, extractErrorStatus, serializeEvlogErrorResponse } from '../nitro'\n\n/**\n * Custom Nitro v3 error handler that properly serializes EvlogError.\n * This ensures that 'data' (containing 'why', 'fix', 'link') is preserved\n * in the JSON response regardless of the underlying HTTP framework.\n *\n * For non-EvlogError, returns undefined to let Nitro's default handler take over.\n *\n * Usage in nitro.config.ts:\n * ```ts\n * // errorHandler.ts\n * export { default } from 'evlog/nitro/v3/errorHandler'\n * // nitro.config.ts\n * export default defineConfig({\n * errorHandler: './errorHandler',\n * })\n * ```\n */\nexport default defineErrorHandler((error, event) => {\n const evlogError = resolveEvlogError(error)\n\n if (!evlogError) return\n\n const url = parseURL(event.req.url).pathname\n const status = extractErrorStatus(evlogError)\n\n return new Response(JSON.stringify(serializeEvlogErrorResponse(evlogError, url)), {\n status,\n headers: { 'Content-Type': 'application/json' },\n })\n})\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"errorHandler.mjs","names":[],"sources":["../../../src/nitro-v3/errorHandler.ts"],"sourcesContent":["import { parseURL } from 'ufo'\nimport { defineErrorHandler } from 'nitro'\nimport { resolveEvlogError, extractErrorStatus, serializeEvlogErrorResponse } from '../nitro'\n\n/**\n * Custom Nitro v3 error handler that properly serializes EvlogError.\n * This ensures that 'data' (containing 'why', 'fix', 'link') is preserved\n * in the JSON response regardless of the underlying HTTP framework.\n *\n * For non-EvlogError, returns undefined to let Nitro's default handler take over.\n *\n * Usage in nitro.config.ts:\n * ```ts\n * // errorHandler.ts\n * export { default } from 'evlog/nitro/v3/errorHandler'\n * // nitro.config.ts\n * export default defineConfig({\n * errorHandler: './errorHandler',\n * })\n * ```\n */\nexport default defineErrorHandler((error, event) => {\n const evlogError = resolveEvlogError(error)\n\n if (!evlogError) return\n\n const url = parseURL(event.req.url).pathname\n const status = extractErrorStatus(evlogError)\n\n return new Response(JSON.stringify(serializeEvlogErrorResponse(evlogError, url)), {\n status,\n headers: { 'Content-Type': 'application/json' },\n })\n})\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAqBA,IAAA,uBAAe,oBAAoB,OAAO,UAAU;CAClD,MAAM,aAAa,kBAAkB,MAAM;AAE3C,KAAI,CAAC,WAAY;CAEjB,MAAM,MAAM,SAAS,MAAM,IAAI,IAAI,CAAC;CACpC,MAAM,SAAS,mBAAmB,WAAW;AAE7C,QAAO,IAAI,SAAS,KAAK,UAAU,4BAA4B,YAAY,IAAI,CAAC,EAAE;EAChF;EACA,SAAS,EAAE,gBAAgB,oBAAoB;EAChD,CAAC;EACF"}
|
package/dist/nitro/v3/module.mjs
CHANGED
|
@@ -10,7 +10,9 @@ function evlog(options) {
|
|
|
10
10
|
nitro.options.plugins.push(resolve(_dir, "plugin"));
|
|
11
11
|
if (!nitro.options.noExternals) nitro.options.noExternals = ["evlog"];
|
|
12
12
|
else if (Array.isArray(nitro.options.noExternals)) nitro.options.noExternals.push("evlog");
|
|
13
|
-
if (!nitro.options.errorHandler) nitro.options.errorHandler = resolve(_dir, "errorHandler");
|
|
13
|
+
if (!nitro.options.errorHandler) nitro.options.errorHandler = [resolve(_dir, "errorHandler")];
|
|
14
|
+
else if (Array.isArray(nitro.options.errorHandler)) nitro.options.errorHandler.unshift(resolve(_dir, "errorHandler"));
|
|
15
|
+
else if (typeof nitro.options.errorHandler === "string") nitro.options.errorHandler = [resolve(_dir, "errorHandler"), nitro.options.errorHandler];
|
|
14
16
|
nitro.options.runtimeConfig = nitro.options.runtimeConfig || {};
|
|
15
17
|
nitro.options.runtimeConfig.evlog = options || {};
|
|
16
18
|
process.env.__EVLOG_CONFIG = JSON.stringify(options || {});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.mjs","names":[],"sources":["../../../src/nitro-v3/module.ts"],"sourcesContent":["import { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { Nitro } from 'nitro/types'\nimport type { NitroModuleOptions } from '../nitro'\n\nexport type { NitroModuleOptions }\n\nconst _dir = dirname(fileURLToPath(import.meta.url))\n\nexport default function evlog(options?: NitroModuleOptions) {\n return {\n name: 'evlog',\n setup(nitro: Nitro) {\n // Push the plugin (no extension — Nitro's bundler resolves it)\n nitro.options.plugins = nitro.options.plugins || []\n nitro.options.plugins.push(resolve(_dir, 'plugin'))\n\n // explicitly tell nitro to bundle evlog's files to correctly resolve nitro dependencies\n if (!nitro.options.noExternals) {\n nitro.options.noExternals = ['evlog']\n } else if (Array.isArray(nitro.options.noExternals)) {\n nitro.options.noExternals.push('evlog')\n }\n \n\n // Set error handler only if not already configured by user\n if (!nitro.options.errorHandler) {\n nitro.options.errorHandler = resolve(_dir, 'errorHandler')\n }\n\n // Inject config into runtimeConfig — works in production where the\n // plugin is bundled through Nitro's builder and the virtual\n // runtime-config module resolves correctly.\n nitro.options.runtimeConfig = nitro.options.runtimeConfig || {}\n nitro.options.runtimeConfig.evlog = options || {}\n\n // In dev mode, Nitro loads plugins externally (not bundled), so the\n // virtual runtime-config module is unreachable and useRuntimeConfig()\n // returns a stub without our values. process.env is inherited by the\n // Worker Threads that run the dev server, making it a reliable bridge.\n // The plugin reads: useRuntimeConfig().evlog ?? process.env.__EVLOG_CONFIG\n process.env.__EVLOG_CONFIG = JSON.stringify(options || {})\n },\n }\n}\n"],"mappings":";;;AAOA,MAAM,OAAO,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAEpD,SAAwB,MAAM,SAA8B;AAC1D,QAAO;EACL,MAAM;EACN,MAAM,OAAc;AAElB,SAAM,QAAQ,UAAU,MAAM,QAAQ,WAAW,EAAE;AACnD,SAAM,QAAQ,QAAQ,KAAK,QAAQ,MAAM,SAAS,CAAC;AAGnD,OAAI,CAAC,MAAM,QAAQ,YACjB,OAAM,QAAQ,cAAc,CAAC,QAAQ;YAC5B,MAAM,QAAQ,MAAM,QAAQ,YAAY,CACjD,OAAM,QAAQ,YAAY,KAAK,QAAQ;AAKzC,OAAI,CAAC,MAAM,QAAQ,aACjB,OAAM,QAAQ,eAAe,QAAQ,MAAM,eAAe;
|
|
1
|
+
{"version":3,"file":"module.mjs","names":[],"sources":["../../../src/nitro-v3/module.ts"],"sourcesContent":["import { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { Nitro } from 'nitro/types'\nimport type { NitroModuleOptions } from '../nitro'\n\nexport type { NitroModuleOptions }\n\nconst _dir = dirname(fileURLToPath(import.meta.url))\n\nexport default function evlog(options?: NitroModuleOptions) {\n return {\n name: 'evlog',\n setup(nitro: Nitro) {\n // Push the plugin (no extension — Nitro's bundler resolves it)\n nitro.options.plugins = nitro.options.plugins || []\n nitro.options.plugins.push(resolve(_dir, 'plugin'))\n\n // explicitly tell nitro to bundle evlog's files to correctly resolve nitro dependencies\n if (!nitro.options.noExternals) {\n nitro.options.noExternals = ['evlog']\n } else if (Array.isArray(nitro.options.noExternals)) {\n nitro.options.noExternals.push('evlog')\n }\n \n\n // Set error handler only if not already configured by user\n if (!nitro.options.errorHandler) {\n nitro.options.errorHandler = [resolve(_dir, 'errorHandler')]\n } else if (Array.isArray(nitro.options.errorHandler)) {\n nitro.options.errorHandler.unshift(resolve(_dir, 'errorHandler'))\n } else if (typeof nitro.options.errorHandler === 'string') {\n nitro.options.errorHandler = [resolve(_dir, 'errorHandler'), nitro.options.errorHandler]\n }\n\n // Inject config into runtimeConfig — works in production where the\n // plugin is bundled through Nitro's builder and the virtual\n // runtime-config module resolves correctly.\n nitro.options.runtimeConfig = nitro.options.runtimeConfig || {}\n nitro.options.runtimeConfig.evlog = options || {}\n\n // In dev mode, Nitro loads plugins externally (not bundled), so the\n // virtual runtime-config module is unreachable and useRuntimeConfig()\n // returns a stub without our values. process.env is inherited by the\n // Worker Threads that run the dev server, making it a reliable bridge.\n // The plugin reads: useRuntimeConfig().evlog ?? process.env.__EVLOG_CONFIG\n process.env.__EVLOG_CONFIG = JSON.stringify(options || {})\n },\n }\n}\n"],"mappings":";;;AAOA,MAAM,OAAO,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAEpD,SAAwB,MAAM,SAA8B;AAC1D,QAAO;EACL,MAAM;EACN,MAAM,OAAc;AAElB,SAAM,QAAQ,UAAU,MAAM,QAAQ,WAAW,EAAE;AACnD,SAAM,QAAQ,QAAQ,KAAK,QAAQ,MAAM,SAAS,CAAC;AAGnD,OAAI,CAAC,MAAM,QAAQ,YACjB,OAAM,QAAQ,cAAc,CAAC,QAAQ;YAC5B,MAAM,QAAQ,MAAM,QAAQ,YAAY,CACjD,OAAM,QAAQ,YAAY,KAAK,QAAQ;AAKzC,OAAI,CAAC,MAAM,QAAQ,aACjB,OAAM,QAAQ,eAAe,CAAC,QAAQ,MAAM,eAAe,CAAC;YACnD,MAAM,QAAQ,MAAM,QAAQ,aAAa,CAClD,OAAM,QAAQ,aAAa,QAAQ,QAAQ,MAAM,eAAe,CAAC;YACxD,OAAO,MAAM,QAAQ,iBAAiB,SAC/C,OAAM,QAAQ,eAAe,CAAC,QAAQ,MAAM,eAAe,EAAE,MAAM,QAAQ,aAAa;AAM1F,SAAM,QAAQ,gBAAgB,MAAM,QAAQ,iBAAiB,EAAE;AAC/D,SAAM,QAAQ,cAAc,QAAQ,WAAW,EAAE;AAOjD,WAAQ,IAAI,iBAAiB,KAAK,UAAU,WAAW,EAAE,CAAC;;EAE7D"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as nitro_types0 from "nitro/types";
|
|
2
|
+
|
|
1
3
|
//#region src/nitro-v3/plugin.d.ts
|
|
2
4
|
/**
|
|
3
5
|
* Nitro v3 plugin entry point.
|
|
@@ -8,7 +10,7 @@
|
|
|
8
10
|
* export { default } from 'evlog/nitro/v3'
|
|
9
11
|
* ```
|
|
10
12
|
*/
|
|
11
|
-
declare const _default:
|
|
13
|
+
declare const _default: nitro_types0.NitroAppPlugin;
|
|
12
14
|
//#endregion
|
|
13
15
|
export { _default as default };
|
|
14
16
|
//# sourceMappingURL=plugin.d.mts.map
|
package/dist/nitro/v3/plugin.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { filterSafeHeaders } from "../../utils.mjs";
|
|
2
2
|
import { createRequestLogger, initLogger, isEnabled } from "../../logger.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { t as
|
|
5
|
-
import { t as parseURL } from "../../dist-
|
|
3
|
+
import { t as extractErrorStatus } from "../../errors-BJRXUfMg.mjs";
|
|
4
|
+
import { n as shouldLog, t as getServiceForPath } from "../../routes-CE3_c-iZ.mjs";
|
|
5
|
+
import { t as parseURL } from "../../dist-BsWcv7B8.mjs";
|
|
6
6
|
import { definePlugin } from "nitro";
|
|
7
7
|
import { useRuntimeConfig } from "nitro/runtime-config";
|
|
8
8
|
//#region src/nitro-v3/plugin.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as EnvironmentContext, v as RouteConfig, y as SamplingConfig } from "./types-
|
|
1
|
+
import { a as EnvironmentContext, v as RouteConfig, y as SamplingConfig } from "./types-B8-kC2ME.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/nitro.d.ts
|
|
4
4
|
interface NitroModuleOptions {
|
|
@@ -39,4 +39,4 @@ interface NitroModuleOptions {
|
|
|
39
39
|
}
|
|
40
40
|
//#endregion
|
|
41
41
|
export { NitroModuleOptions as t };
|
|
42
|
-
//# sourceMappingURL=nitro-
|
|
42
|
+
//# sourceMappingURL=nitro-CGGTUned.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nitro-CGGTUned.d.mts","names":[],"sources":["../src/nitro.ts"],"mappings":";;;UAKiB,kBAAA;EAAA;;;;EAKf,OAAA;EA8BwB;;;EAzBxB,GAAA,GAAM,OAAA,CAAQ,kBAAA;EA8BW;;;;EAxBzB,MAAA;EAAA;;;;;EAOA,OAAA;EAiBA;;;;;EAVA,OAAA;;;;EAKA,MAAA,GAAS,MAAA,SAAe,WAAA;;;;EAKxB,QAAA,GAAW,cAAA;AAAA"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as extractErrorStatus } from "./errors-BJRXUfMg.mjs";
|
|
1
2
|
//#region src/nitro.ts
|
|
2
3
|
/**
|
|
3
4
|
* Resolve an EvlogError from an error or its cause chain.
|
|
@@ -9,12 +10,6 @@ function resolveEvlogError(error) {
|
|
|
9
10
|
return null;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
|
-
* Extract HTTP status from an error, checking both `status` and `statusCode`.
|
|
13
|
-
*/
|
|
14
|
-
function extractErrorStatus(error) {
|
|
15
|
-
return error.status ?? error.statusCode ?? 500;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
13
|
* Build a standard evlog error JSON response body.
|
|
19
14
|
* Used by both v2 and v3 error handlers to ensure consistent shape.
|
|
20
15
|
*/
|
|
@@ -34,6 +29,6 @@ function serializeEvlogErrorResponse(error, url) {
|
|
|
34
29
|
};
|
|
35
30
|
}
|
|
36
31
|
//#endregion
|
|
37
|
-
export {
|
|
32
|
+
export { serializeEvlogErrorResponse as n, resolveEvlogError as t };
|
|
38
33
|
|
|
39
|
-
//# sourceMappingURL=nitro-
|
|
34
|
+
//# sourceMappingURL=nitro-D1pPm37T.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nitro-D1pPm37T.mjs","names":[],"sources":["../src/nitro.ts"],"sourcesContent":["import type { EnvironmentContext, RouteConfig, SamplingConfig } from './types'\nimport { extractErrorStatus } from './shared/errors'\n\nexport { shouldLog, getServiceForPath } from './shared/routes'\n\nexport interface NitroModuleOptions {\n /**\n * Enable or disable all logging globally.\n * @default true\n */\n enabled?: boolean\n\n /**\n * Environment context overrides.\n */\n env?: Partial<EnvironmentContext>\n\n /**\n * Enable pretty printing.\n * @default true in development, false in production\n */\n pretty?: boolean\n\n /**\n * Route patterns to include in logging.\n * Supports glob patterns like '/api/**'.\n * If not set, all routes are logged.\n */\n include?: string[]\n\n /**\n * Route patterns to exclude from logging.\n * Supports glob patterns like '/_nitro/**'.\n * Exclusions take precedence over inclusions.\n */\n exclude?: string[]\n\n /**\n * Route-specific service configuration.\n */\n routes?: Record<string, RouteConfig>\n\n /**\n * Sampling configuration for filtering logs.\n */\n sampling?: SamplingConfig\n}\n\nexport interface EvlogConfig {\n enabled?: boolean\n env?: Record<string, unknown>\n pretty?: boolean\n include?: string[]\n exclude?: string[]\n routes?: Record<string, RouteConfig>\n sampling?: SamplingConfig\n}\n\n/**\n * Resolve an EvlogError from an error or its cause chain.\n * Both Nitro v2 (h3) and v3 wrap thrown errors — this unwraps them.\n */\nexport function resolveEvlogError(error: Error): Error | null {\n if (error.name === 'EvlogError') return error\n if ((error.cause as Error)?.name === 'EvlogError') return error.cause as Error\n return null\n}\n\nexport { extractErrorStatus } from './shared/errors'\n\n/**\n * Build a standard evlog error JSON response body.\n * Used by both v2 and v3 error handlers to ensure consistent shape.\n */\nexport function serializeEvlogErrorResponse(error: Error, url: string): Record<string, unknown> {\n const status = extractErrorStatus(error)\n const { data } = error as { data?: unknown }\n const statusMessage = (error as { statusMessage?: string }).statusMessage || error.message\n return {\n url,\n status,\n statusCode: status,\n statusText: statusMessage,\n statusMessage,\n message: error.message,\n error: true,\n ...(data !== undefined && { data }),\n }\n}\n\n"],"mappings":";;;;;;AA8DA,SAAgB,kBAAkB,OAA4B;AAC5D,KAAI,MAAM,SAAS,aAAc,QAAO;AACxC,KAAK,MAAM,OAAiB,SAAS,aAAc,QAAO,MAAM;AAChE,QAAO;;;;;;AAST,SAAgB,4BAA4B,OAAc,KAAsC;CAC9F,MAAM,SAAS,mBAAmB,MAAM;CACxC,MAAM,EAAE,SAAS;CACjB,MAAM,gBAAiB,MAAqC,iBAAiB,MAAM;AACnF,QAAO;EACL;EACA;EACA,YAAY;EACZ,YAAY;EACZ;EACA,SAAS,MAAM;EACf,OAAO;EACP,GAAI,SAAS,KAAA,KAAa,EAAE,MAAM;EACnC"}
|
package/dist/nuxt/module.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as EnvironmentContext, v as RouteConfig, w as TransportConfig, y as SamplingConfig } from "../types-
|
|
1
|
+
import { a as EnvironmentContext, v as RouteConfig, w as TransportConfig, y as SamplingConfig } from "../types-B8-kC2ME.mjs";
|
|
2
2
|
import * as _nuxt_schema0 from "@nuxt/schema";
|
|
3
3
|
|
|
4
4
|
//#region src/nuxt/module.d.ts
|
package/dist/nuxt/module.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { m as ParsedError } from "./types-
|
|
1
|
+
import { m as ParsedError } from "./types-B8-kC2ME.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/runtime/utils/parseError.d.ts
|
|
4
4
|
declare function parseError(error: unknown): ParsedError;
|
|
5
5
|
//#endregion
|
|
6
6
|
export { parseError as t };
|
|
7
|
-
//# sourceMappingURL=parseError-
|
|
7
|
+
//# sourceMappingURL=parseError-BztqcPwZ.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseError-BztqcPwZ.d.mts","names":[],"sources":["../src/runtime/utils/parseError.ts"],"mappings":";;;iBAKgB,UAAA,CAAW,KAAA,YAAiB,WAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes-
|
|
1
|
+
{"version":3,"file":"routes-CE3_c-iZ.mjs","names":[],"sources":["../src/shared/routes.ts"],"sourcesContent":["import type { RouteConfig } from '../types'\nimport { matchesPattern } from '../utils'\n\nexport function shouldLog(path: string, include?: string[], exclude?: string[]): boolean {\n // Check exclusions first (they take precedence)\n if (exclude && exclude.length > 0) {\n if (exclude.some(pattern => matchesPattern(path, pattern))) {\n return false\n }\n }\n\n // If no include patterns, log everything (that wasn't excluded)\n if (!include || include.length === 0) {\n return true\n }\n\n // Log only if path matches at least one include pattern\n return include.some(pattern => matchesPattern(path, pattern))\n}\n\n/**\n * Find the service name for a given path based on route patterns.\n *\n * When multiple patterns match the same path, the first matching pattern wins\n * based on object iteration order. To ensure predictable behavior, order your\n * route patterns from most specific to most general.\n *\n * @param path - The request path to match\n * @param routes - Route configuration mapping patterns to service names\n * @returns The service name for the matching route, or undefined if no match\n *\n * @example\n * ```ts\n * // Good: specific patterns first, general patterns last\n * routes: {\n * '/api/auth/admin/**': { service: 'admin-service' },\n * '/api/auth/**': { service: 'auth-service' },\n * '/api/**': { service: 'api-service' },\n * }\n * ```\n */\nexport function getServiceForPath(path: string, routes?: Record<string, RouteConfig>): string | undefined {\n if (!routes) return undefined\n\n for (const [pattern, config] of Object.entries(routes)) {\n if (matchesPattern(path, pattern)) {\n return config.service\n }\n }\n\n return undefined\n}\n"],"mappings":";;AAGA,SAAgB,UAAU,MAAc,SAAoB,SAA6B;AAEvF,KAAI,WAAW,QAAQ,SAAS;MAC1B,QAAQ,MAAK,YAAW,eAAe,MAAM,QAAQ,CAAC,CACxD,QAAO;;AAKX,KAAI,CAAC,WAAW,QAAQ,WAAW,EACjC,QAAO;AAIT,QAAO,QAAQ,MAAK,YAAW,eAAe,MAAM,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;AAwB/D,SAAgB,kBAAkB,MAAc,QAA0D;AACxG,KAAI,CAAC,OAAQ,QAAO,KAAA;AAEpB,MAAK,MAAM,CAAC,SAAS,WAAW,OAAO,QAAQ,OAAO,CACpD,KAAI,eAAe,MAAM,QAAQ,CAC/B,QAAO,OAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log.d.mts","names":[],"sources":["../../../src/runtime/client/log.ts"],"mappings":";;;iBAagB,WAAA,CAAY,QAAA,EAAU,MAAA;AAAA,iBAItB,aAAA,CAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"log.d.mts","names":[],"sources":["../../../src/runtime/client/log.ts"],"mappings":";;;iBAagB,WAAA,CAAY,QAAA,EAAU,MAAA;AAAA,iBAItB,aAAA,CAAA;AAAA,iBAKA,OAAA,CAAQ,OAAA;EAAW,OAAA;EAAmB,OAAA;EAAmB,MAAA;EAAkB,OAAA;EAAkB,SAAA,GAAY,eAAA;AAAA;AAAA,cAoFnH,UAAA,EAAY,GAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getConsoleMethod } from "../../utils.mjs";
|
|
1
|
+
import { cssColors, escapeFormatString, getConsoleMethod, getCssLevelColor } from "../../utils.mjs";
|
|
2
2
|
//#region src/runtime/client/log.ts
|
|
3
3
|
const isClient = typeof window !== "undefined";
|
|
4
4
|
let clientEnabled = true;
|
|
@@ -14,12 +14,6 @@ function setIdentity(identity) {
|
|
|
14
14
|
function clearIdentity() {
|
|
15
15
|
identityContext = {};
|
|
16
16
|
}
|
|
17
|
-
const LEVEL_COLORS = {
|
|
18
|
-
error: "color: #ef4444; font-weight: bold",
|
|
19
|
-
warn: "color: #f59e0b; font-weight: bold",
|
|
20
|
-
info: "color: #06b6d4; font-weight: bold",
|
|
21
|
-
debug: "color: #6b7280; font-weight: bold"
|
|
22
|
-
};
|
|
23
17
|
function initLog(options = {}) {
|
|
24
18
|
clientEnabled = typeof options.enabled === "boolean" ? options.enabled : true;
|
|
25
19
|
clientConsole = typeof options.console === "boolean" ? options.console : true;
|
|
@@ -53,7 +47,7 @@ function emitLog(level, event) {
|
|
|
53
47
|
const method = getConsoleMethod(level);
|
|
54
48
|
if (clientPretty) {
|
|
55
49
|
const { level: lvl, service, ...rest } = formatted;
|
|
56
|
-
console[method](`%c[${service}]%c ${lvl}`,
|
|
50
|
+
console[method](`%c[${escapeFormatString(String(service))}]%c ${lvl}`, getCssLevelColor(lvl), cssColors.reset, rest);
|
|
57
51
|
} else console[method](JSON.stringify(formatted));
|
|
58
52
|
}
|
|
59
53
|
sendToServer(formatted);
|
|
@@ -61,7 +55,7 @@ function emitLog(level, event) {
|
|
|
61
55
|
function emitTaggedLog(level, tag, message) {
|
|
62
56
|
if (!clientEnabled) return;
|
|
63
57
|
if (clientPretty) {
|
|
64
|
-
if (clientConsole) console[getConsoleMethod(level)](`%c[${tag}]%c ${message}`,
|
|
58
|
+
if (clientConsole) console[getConsoleMethod(level)](`%c[${escapeFormatString(tag)}]%c ${escapeFormatString(message)}`, getCssLevelColor(level), cssColors.reset);
|
|
65
59
|
sendToServer({
|
|
66
60
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
67
61
|
level,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"log.mjs","names":[],"sources":["../../../src/runtime/client/log.ts"],"sourcesContent":["import type { Log, LogLevel, TransportConfig } from '../../types'\nimport { getConsoleMethod } from '../../utils'\n\nconst isClient = typeof window !== 'undefined'\n\nlet clientEnabled = true\nlet clientConsole = true\nlet clientPretty = true\nlet clientService = 'client'\nlet transportEnabled = false\nlet transportEndpoint = '/api/_evlog/ingest'\nlet identityContext: Record<string, unknown> = {}\n\nexport function setIdentity(identity: Record<string, unknown>): void {\n identityContext = { ...identity }\n}\n\nexport function clearIdentity(): void {\n identityContext = {}\n}\n\
|
|
1
|
+
{"version":3,"file":"log.mjs","names":[],"sources":["../../../src/runtime/client/log.ts"],"sourcesContent":["import type { Log, LogLevel, TransportConfig } from '../../types'\nimport { cssColors, escapeFormatString, getConsoleMethod, getCssLevelColor } from '../../utils'\n\nconst isClient = typeof window !== 'undefined'\n\nlet clientEnabled = true\nlet clientConsole = true\nlet clientPretty = true\nlet clientService = 'client'\nlet transportEnabled = false\nlet transportEndpoint = '/api/_evlog/ingest'\nlet identityContext: Record<string, unknown> = {}\n\nexport function setIdentity(identity: Record<string, unknown>): void {\n identityContext = { ...identity }\n}\n\nexport function clearIdentity(): void {\n identityContext = {}\n}\n\n\nexport function initLog(options: { enabled?: boolean, console?: boolean, pretty?: boolean, service?: string, transport?: TransportConfig } = {}): void {\n clientEnabled = typeof options.enabled === 'boolean' ? options.enabled : true\n clientConsole = typeof options.console === 'boolean' ? options.console : true\n clientPretty = typeof options.pretty === 'boolean' ? options.pretty : true\n clientService = options.service ?? 'client'\n transportEnabled = options.transport?.enabled ?? false\n transportEndpoint = options.transport?.endpoint ?? '/api/_evlog/ingest'\n}\n\nasync function sendToServer(event: Record<string, unknown>): Promise<void> {\n if (!transportEnabled) return\n\n try {\n await fetch(transportEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(event),\n keepalive: true,\n credentials: 'same-origin',\n })\n } catch {\n // Silently fail - don't break the app\n }\n}\n\nfunction emitLog(level: LogLevel, event: Record<string, unknown>): void {\n if (!clientEnabled) return\n\n const formatted = {\n timestamp: new Date().toISOString(),\n level,\n service: clientService,\n ...identityContext,\n ...event,\n }\n\n if (clientConsole) {\n const method = getConsoleMethod(level)\n if (clientPretty) {\n const { level: lvl, service, ...rest } = formatted\n console[method](`%c[${escapeFormatString(String(service))}]%c ${lvl}`, getCssLevelColor(lvl), cssColors.reset, rest)\n } else {\n console[method](JSON.stringify(formatted))\n }\n }\n\n sendToServer(formatted)\n}\n\nfunction emitTaggedLog(level: LogLevel, tag: string, message: string): void {\n if (!clientEnabled) return\n if (clientPretty) {\n if (clientConsole) {\n console[getConsoleMethod(level)](`%c[${escapeFormatString(tag)}]%c ${escapeFormatString(message)}`, getCssLevelColor(level), cssColors.reset)\n }\n sendToServer({\n timestamp: new Date().toISOString(),\n level,\n service: clientService,\n ...identityContext,\n tag,\n message,\n })\n } else {\n emitLog(level, { tag, message })\n }\n}\n\nfunction createLogMethod(level: LogLevel) {\n return function logMethod(tagOrEvent: string | Record<string, unknown>, message?: string): void {\n if (!(import.meta.client ?? isClient)) {\n return\n }\n\n if (typeof tagOrEvent === 'string' && message !== undefined) {\n emitTaggedLog(level, tagOrEvent, message)\n } else if (typeof tagOrEvent === 'object') {\n emitLog(level, tagOrEvent)\n } else {\n emitTaggedLog(level, 'log', String(tagOrEvent))\n }\n }\n}\n\nconst _clientLog: Log = {\n info: createLogMethod('info'),\n error: createLogMethod('error'),\n warn: createLogMethod('warn'),\n debug: createLogMethod('debug'),\n}\n\nexport { _clientLog as log }\n"],"mappings":";;AAGA,MAAM,WAAW,OAAO,WAAW;AAEnC,IAAI,gBAAgB;AACpB,IAAI,gBAAgB;AACpB,IAAI,eAAe;AACnB,IAAI,gBAAgB;AACpB,IAAI,mBAAmB;AACvB,IAAI,oBAAoB;AACxB,IAAI,kBAA2C,EAAE;AAEjD,SAAgB,YAAY,UAAyC;AACnE,mBAAkB,EAAE,GAAG,UAAU;;AAGnC,SAAgB,gBAAsB;AACpC,mBAAkB,EAAE;;AAItB,SAAgB,QAAQ,UAAqH,EAAE,EAAQ;AACrJ,iBAAgB,OAAO,QAAQ,YAAY,YAAY,QAAQ,UAAU;AACzE,iBAAgB,OAAO,QAAQ,YAAY,YAAY,QAAQ,UAAU;AACzE,gBAAe,OAAO,QAAQ,WAAW,YAAY,QAAQ,SAAS;AACtE,iBAAgB,QAAQ,WAAW;AACnC,oBAAmB,QAAQ,WAAW,WAAW;AACjD,qBAAoB,QAAQ,WAAW,YAAY;;AAGrD,eAAe,aAAa,OAA+C;AACzE,KAAI,CAAC,iBAAkB;AAEvB,KAAI;AACF,QAAM,MAAM,mBAAmB;GAC7B,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM,KAAK,UAAU,MAAM;GAC3B,WAAW;GACX,aAAa;GACd,CAAC;SACI;;AAKV,SAAS,QAAQ,OAAiB,OAAsC;AACtE,KAAI,CAAC,cAAe;CAEpB,MAAM,YAAY;EAChB,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC;EACA,SAAS;EACT,GAAG;EACH,GAAG;EACJ;AAED,KAAI,eAAe;EACjB,MAAM,SAAS,iBAAiB,MAAM;AACtC,MAAI,cAAc;GAChB,MAAM,EAAE,OAAO,KAAK,SAAS,GAAG,SAAS;AACzC,WAAQ,QAAQ,MAAM,mBAAmB,OAAO,QAAQ,CAAC,CAAC,MAAM,OAAO,iBAAiB,IAAI,EAAE,UAAU,OAAO,KAAK;QAEpH,SAAQ,QAAQ,KAAK,UAAU,UAAU,CAAC;;AAI9C,cAAa,UAAU;;AAGzB,SAAS,cAAc,OAAiB,KAAa,SAAuB;AAC1E,KAAI,CAAC,cAAe;AACpB,KAAI,cAAc;AAChB,MAAI,cACF,SAAQ,iBAAiB,MAAM,EAAE,MAAM,mBAAmB,IAAI,CAAC,MAAM,mBAAmB,QAAQ,IAAI,iBAAiB,MAAM,EAAE,UAAU,MAAM;AAE/I,eAAa;GACX,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC;GACA,SAAS;GACT,GAAG;GACH;GACA;GACD,CAAC;OAEF,SAAQ,OAAO;EAAE;EAAK;EAAS,CAAC;;AAIpC,SAAS,gBAAgB,OAAiB;AACxC,QAAO,SAAS,UAAU,YAA8C,SAAwB;AAC9F,MAAI,EAAE,OAAO,KAAK,UAAU,UAC1B;AAGF,MAAI,OAAO,eAAe,YAAY,YAAY,KAAA,EAChD,eAAc,OAAO,YAAY,QAAQ;WAChC,OAAO,eAAe,SAC/B,SAAQ,OAAO,WAAW;MAE1B,eAAc,OAAO,OAAO,OAAO,WAAW,CAAC;;;AAKrD,MAAM,aAAkB;CACtB,MAAM,gBAAgB,OAAO;CAC7B,OAAO,gBAAgB,QAAQ;CAC/B,MAAM,gBAAgB,OAAO;CAC7B,OAAO,gBAAgB,QAAQ;CAChC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as useLogger } from "../../useLogger-
|
|
1
|
+
import { t as useLogger } from "../../useLogger-_Ec6mXoR.mjs";
|
|
2
2
|
export { useLogger };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { m as ParsedError } from "../../types-
|
|
2
|
-
import { t as parseError } from "../../parseError-
|
|
1
|
+
import { m as ParsedError } from "../../types-B8-kC2ME.mjs";
|
|
2
|
+
import { t as parseError } from "../../parseError-BztqcPwZ.mjs";
|
|
3
3
|
export { ParsedError, parseError };
|
|
@@ -9,6 +9,8 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
9
9
|
* @param contextHint - Human-readable hint appended to the error message when
|
|
10
10
|
* `useLogger()` is called outside of a request (e.g.
|
|
11
11
|
* `"middleware context. Make sure app.use(evlog()) is registered before your routes."`).
|
|
12
|
+
*
|
|
13
|
+
* @beta Part of `evlog/toolkit` — the public API for building custom integrations.
|
|
12
14
|
*/
|
|
13
15
|
function createLoggerStorage(contextHint) {
|
|
14
16
|
const storage = new AsyncLocalStorage();
|
|
@@ -41,4 +43,4 @@ function createLoggerStorage(contextHint) {
|
|
|
41
43
|
//#endregion
|
|
42
44
|
export { createLoggerStorage as t };
|
|
43
45
|
|
|
44
|
-
//# sourceMappingURL=storage-
|
|
46
|
+
//# sourceMappingURL=storage-CJBW5Vos.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-CJBW5Vos.mjs","names":[],"sources":["../src/shared/storage.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport type { RequestLogger } from '../types'\n\n/**\n * Create a request-scoped `AsyncLocalStorage` and a matching `useLogger` accessor.\n *\n * Every framework that needs `useLogger()` (Express, Fastify, NestJS, SvelteKit)\n * calls this once at module level to get its own isolated storage + accessor pair.\n *\n * @param contextHint - Human-readable hint appended to the error message when\n * `useLogger()` is called outside of a request (e.g.\n * `\"middleware context. Make sure app.use(evlog()) is registered before your routes.\"`).\n *\n * @beta Part of `evlog/toolkit` — the public API for building custom integrations.\n */\nexport function createLoggerStorage(contextHint: string) {\n const storage = new AsyncLocalStorage<RequestLogger>()\n\n /**\n * Access the request-scoped logger created by the evlog middleware.\n *\n * Must be called inside a request that is handled by the evlog middleware.\n * Throws if called outside of a request context.\n *\n * @example\n * ```ts\n * import { useLogger } from 'evlog/express' // or /fastify, /nestjs, /sveltekit, /elysia\n *\n * function myService() {\n * const log = useLogger()\n * log.set({ users: { count: 42 } })\n * }\n * ```\n */\n function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T> {\n const logger = storage.getStore()\n if (!logger) {\n throw new Error(\n `[evlog] useLogger() was called outside of an evlog ${contextHint}`,\n )\n }\n return logger as RequestLogger<T>\n }\n\n return { storage, useLogger }\n}\n"],"mappings":";;;;;;;;;;;;;;AAeA,SAAgB,oBAAoB,aAAqB;CACvD,MAAM,UAAU,IAAI,mBAAkC;;;;;;;;;;;;;;;;;CAkBtD,SAAS,YAA0E;EACjF,MAAM,SAAS,QAAQ,UAAU;AACjC,MAAI,CAAC,OACH,OAAM,IAAI,MACR,sDAAsD,cACvD;AAEH,SAAO;;AAGT,QAAO;EAAE;EAAS;EAAW"}
|