evlog 1.7.0 → 1.9.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 +257 -61
- package/dist/_http-DVDwNag0.mjs +76 -0
- package/dist/_http-DVDwNag0.mjs.map +1 -0
- package/dist/_severity-CXfyvxQi.mjs +17 -0
- package/dist/_severity-CXfyvxQi.mjs.map +1 -0
- package/dist/adapters/axiom.d.mts +1 -0
- package/dist/adapters/axiom.d.mts.map +1 -1
- package/dist/adapters/axiom.mjs +40 -44
- package/dist/adapters/axiom.mjs.map +1 -1
- package/dist/adapters/better-stack.d.mts +1 -0
- package/dist/adapters/better-stack.d.mts.map +1 -1
- package/dist/adapters/better-stack.mjs +34 -45
- package/dist/adapters/better-stack.mjs.map +1 -1
- package/dist/adapters/otlp.d.mts +1 -0
- package/dist/adapters/otlp.d.mts.map +1 -1
- package/dist/adapters/otlp.mjs +61 -81
- package/dist/adapters/otlp.mjs.map +1 -1
- package/dist/adapters/posthog.d.mts +35 -1
- package/dist/adapters/posthog.d.mts.map +1 -1
- package/dist/adapters/posthog.mjs +91 -45
- package/dist/adapters/posthog.mjs.map +1 -1
- package/dist/adapters/sentry.d.mts +1 -0
- package/dist/adapters/sentry.d.mts.map +1 -1
- package/dist/adapters/sentry.mjs +41 -53
- package/dist/adapters/sentry.mjs.map +1 -1
- package/dist/browser.d.mts +63 -0
- package/dist/browser.d.mts.map +1 -0
- package/dist/browser.mjs +95 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/logger.d.mts +6 -2
- package/dist/logger.d.mts.map +1 -1
- package/dist/logger.mjs +56 -3
- package/dist/logger.mjs.map +1 -1
- package/dist/nitro/errorHandler.mjs +6 -17
- package/dist/nitro/errorHandler.mjs.map +1 -1
- package/dist/nitro/module.d.mts +11 -0
- package/dist/nitro/module.d.mts.map +1 -0
- package/dist/nitro/module.mjs +23 -0
- package/dist/nitro/module.mjs.map +1 -0
- package/dist/nitro/plugin.mjs +28 -52
- package/dist/nitro/plugin.mjs.map +1 -1
- package/dist/nitro/v3/errorHandler.d.mts +24 -0
- package/dist/nitro/v3/errorHandler.d.mts.map +1 -0
- package/dist/nitro/v3/errorHandler.mjs +36 -0
- package/dist/nitro/v3/errorHandler.mjs.map +1 -0
- package/dist/nitro/v3/index.d.mts +4 -0
- package/dist/nitro/v3/index.mjs +4 -0
- package/dist/nitro/v3/module.d.mts +10 -0
- package/dist/nitro/v3/module.d.mts.map +1 -0
- package/dist/nitro/v3/module.mjs +22 -0
- package/dist/nitro/v3/module.mjs.map +1 -0
- package/dist/nitro/v3/plugin.d.mts +14 -0
- package/dist/nitro/v3/plugin.d.mts.map +1 -0
- package/dist/nitro/v3/plugin.mjs +157 -0
- package/dist/nitro/v3/plugin.mjs.map +1 -0
- package/dist/nitro/v3/useLogger.d.mts +24 -0
- package/dist/nitro/v3/useLogger.d.mts.map +1 -0
- package/dist/nitro/v3/useLogger.mjs +27 -0
- package/dist/nitro/v3/useLogger.mjs.map +1 -0
- package/dist/nitro-D57TWGyN.mjs +73 -0
- package/dist/nitro-D57TWGyN.mjs.map +1 -0
- package/dist/nitro-D81NBVPi.d.mts +42 -0
- package/dist/nitro-D81NBVPi.d.mts.map +1 -0
- package/dist/nuxt/module.d.mts +12 -0
- package/dist/nuxt/module.d.mts.map +1 -1
- package/dist/nuxt/module.mjs +17 -2
- package/dist/nuxt/module.mjs.map +1 -1
- package/dist/runtime/client/log.d.mts +5 -2
- package/dist/runtime/client/log.d.mts.map +1 -1
- package/dist/runtime/client/log.mjs +16 -3
- package/dist/runtime/client/log.mjs.map +1 -1
- package/dist/runtime/client/plugin.mjs +1 -0
- package/dist/runtime/client/plugin.mjs.map +1 -1
- package/dist/runtime/server/routes/_evlog/ingest.post.mjs +1 -1
- package/dist/types.d.mts +32 -2
- package/dist/types.d.mts.map +1 -1
- package/package.json +30 -7
- package/dist/_utils-DZA9nou3.mjs +0 -23
- package/dist/_utils-DZA9nou3.mjs.map +0 -1
package/dist/logger.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.mts","names":[],"sources":["../src/logger.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"logger.d.mts","names":[],"sources":["../src/logger.ts"],"mappings":";;;;;AAoCA;;iBAAgB,UAAA,CAAW,MAAA,GAAQ,YAAA;;;AAqBnC;iBAAgB,SAAA,CAAA;;;;AA8BhB;iBAAgB,UAAA,CAAW,GAAA,EAAK,mBAAA;;;;AAgB/B;;;;;AAkKD;cAjCM,IAAA,EAAM,GAAA;;;;;;;;;;;;iBAiCI,mBAAA,oBAAuC,MAAA,kBAAA,CAAyB,OAAA,GAAS,oBAAA,GAA4B,aAAA,CAAc,CAAA;;;AAiHnI;iBAAgB,cAAA,CAAA,GAAkB,kBAAA"}
|
package/dist/logger.mjs
CHANGED
|
@@ -22,11 +22,13 @@ let globalPretty = isDev();
|
|
|
22
22
|
let globalSampling = {};
|
|
23
23
|
let globalStringify = true;
|
|
24
24
|
let globalDrain;
|
|
25
|
+
let globalEnabled = true;
|
|
25
26
|
/**
|
|
26
27
|
* Initialize the logger with configuration.
|
|
27
28
|
* Call this once at application startup.
|
|
28
29
|
*/
|
|
29
30
|
function initLogger(config = {}) {
|
|
31
|
+
globalEnabled = config.enabled ?? true;
|
|
30
32
|
const detected = detectEnvironment();
|
|
31
33
|
globalEnv = {
|
|
32
34
|
service: config.env?.service ?? detected.service ?? "app",
|
|
@@ -41,6 +43,12 @@ function initLogger(config = {}) {
|
|
|
41
43
|
globalDrain = config.drain;
|
|
42
44
|
}
|
|
43
45
|
/**
|
|
46
|
+
* Check if logging is globally enabled.
|
|
47
|
+
*/
|
|
48
|
+
function isEnabled() {
|
|
49
|
+
return globalEnabled;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
44
52
|
* Determine if a log at the given level should be emitted based on sampling config.
|
|
45
53
|
* Error level defaults to 100% (always logged) unless explicitly configured otherwise.
|
|
46
54
|
*/
|
|
@@ -67,6 +75,7 @@ function shouldKeep(ctx) {
|
|
|
67
75
|
});
|
|
68
76
|
}
|
|
69
77
|
function emitWideEvent(level, event, skipSamplingCheck = false) {
|
|
78
|
+
if (!globalEnabled) return null;
|
|
70
79
|
if (!skipSamplingCheck && !shouldSample(level)) return null;
|
|
71
80
|
const formatted = {
|
|
72
81
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -83,6 +92,7 @@ function emitWideEvent(level, event, skipSamplingCheck = false) {
|
|
|
83
92
|
return formatted;
|
|
84
93
|
}
|
|
85
94
|
function emitTaggedLog(level, tag, message) {
|
|
95
|
+
if (!globalEnabled) return;
|
|
86
96
|
if (globalPretty) {
|
|
87
97
|
if (!shouldSample(level)) return;
|
|
88
98
|
const color = getLevelColor(level);
|
|
@@ -150,12 +160,24 @@ function createLogMethod(level) {
|
|
|
150
160
|
* log.error({ action: 'payment', error: 'failed' })
|
|
151
161
|
* ```
|
|
152
162
|
*/
|
|
153
|
-
const
|
|
163
|
+
const _log = {
|
|
154
164
|
info: createLogMethod("info"),
|
|
155
165
|
error: createLogMethod("error"),
|
|
156
166
|
warn: createLogMethod("warn"),
|
|
157
167
|
debug: createLogMethod("debug")
|
|
158
168
|
};
|
|
169
|
+
const noopLogger = {
|
|
170
|
+
set() {},
|
|
171
|
+
error() {},
|
|
172
|
+
info() {},
|
|
173
|
+
warn() {},
|
|
174
|
+
emit() {
|
|
175
|
+
return null;
|
|
176
|
+
},
|
|
177
|
+
getContext() {
|
|
178
|
+
return {};
|
|
179
|
+
}
|
|
180
|
+
};
|
|
159
181
|
/**
|
|
160
182
|
* Create a request-scoped logger for building wide events.
|
|
161
183
|
*
|
|
@@ -168,6 +190,7 @@ const log = {
|
|
|
168
190
|
* ```
|
|
169
191
|
*/
|
|
170
192
|
function createRequestLogger(options = {}) {
|
|
193
|
+
if (!globalEnabled) return noopLogger;
|
|
171
194
|
const startTime = Date.now();
|
|
172
195
|
let context = {
|
|
173
196
|
method: options.method,
|
|
@@ -175,6 +198,19 @@ function createRequestLogger(options = {}) {
|
|
|
175
198
|
requestId: options.requestId
|
|
176
199
|
};
|
|
177
200
|
let hasError = false;
|
|
201
|
+
let hasWarn = false;
|
|
202
|
+
function addRequestLog(level, message) {
|
|
203
|
+
const entry = {
|
|
204
|
+
level,
|
|
205
|
+
message,
|
|
206
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
207
|
+
};
|
|
208
|
+
const requestLogs = Array.isArray(context.requestLogs) ? [...context.requestLogs, entry] : [entry];
|
|
209
|
+
context = {
|
|
210
|
+
...context,
|
|
211
|
+
requestLogs
|
|
212
|
+
};
|
|
213
|
+
}
|
|
178
214
|
return {
|
|
179
215
|
set(data) {
|
|
180
216
|
context = deepDefaults(data, context);
|
|
@@ -188,6 +224,8 @@ function createRequestLogger(options = {}) {
|
|
|
188
224
|
name: err.name,
|
|
189
225
|
message: err.message,
|
|
190
226
|
stack: err.stack,
|
|
227
|
+
..."status" in err && { status: err.status },
|
|
228
|
+
..."statusText" in err && { statusText: err.statusText },
|
|
191
229
|
..."statusCode" in err && { statusCode: err.statusCode },
|
|
192
230
|
..."statusMessage" in err && { statusMessage: err.statusMessage },
|
|
193
231
|
..."data" in err && { data: err.data },
|
|
@@ -195,10 +233,25 @@ function createRequestLogger(options = {}) {
|
|
|
195
233
|
}
|
|
196
234
|
}, context);
|
|
197
235
|
},
|
|
236
|
+
info(message, infoContext) {
|
|
237
|
+
addRequestLog("info", message);
|
|
238
|
+
if (infoContext) {
|
|
239
|
+
const { requestLogs: _, ...rest } = infoContext;
|
|
240
|
+
context = deepDefaults(rest, context);
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
warn(message, warnContext) {
|
|
244
|
+
hasWarn = true;
|
|
245
|
+
addRequestLog("warn", message);
|
|
246
|
+
if (warnContext) {
|
|
247
|
+
const { requestLogs: _, ...rest } = warnContext;
|
|
248
|
+
context = deepDefaults(rest, context);
|
|
249
|
+
}
|
|
250
|
+
},
|
|
198
251
|
emit(overrides) {
|
|
199
252
|
const durationMs = Date.now() - startTime;
|
|
200
253
|
const duration = formatDuration(durationMs);
|
|
201
|
-
const level = hasError ? "error" : "info";
|
|
254
|
+
const level = hasError ? "error" : hasWarn ? "warn" : "info";
|
|
202
255
|
const { _forceKeep, ...restOverrides } = overrides ?? {};
|
|
203
256
|
const tailCtx = {
|
|
204
257
|
status: context.status ?? restOverrides.status,
|
|
@@ -230,5 +283,5 @@ function getEnvironment() {
|
|
|
230
283
|
}
|
|
231
284
|
|
|
232
285
|
//#endregion
|
|
233
|
-
export { createRequestLogger, getEnvironment, initLogger, log, shouldKeep };
|
|
286
|
+
export { createRequestLogger, getEnvironment, initLogger, isEnabled, _log as log, shouldKeep };
|
|
234
287
|
//# sourceMappingURL=logger.mjs.map
|
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\n\n/**\n * Initialize the logger with configuration.\n * Call this once at application startup.\n */\nexport function initLogger(config: LoggerConfig = {}): void {\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 * 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 (!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 (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 */\nexport const log: Log = {\n info: createLogMethod('info'),\n error: createLogMethod('error'),\n warn: createLogMethod('warn'),\n debug: createLogMethod('debug'),\n}\n\n/**\n * Create a request-scoped logger for building wide events.\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 startTime = Date.now()\n let context: Record<string, unknown> = {\n method: options.method,\n path: options.path,\n requestId: options.requestId,\n }\n let hasError = false\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 ...('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 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' : '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 * 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, 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 request-scoped logger for building wide events.\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 if (!globalEnabled) return noopLogger as RequestLogger<T>\n\n const startTime = Date.now()\n let context: Record<string, unknown> = {\n method: options.method,\n path: options.path,\n requestId: options.requestId,\n }\n let hasError = false\n let hasWarn = false\n\n function addRequestLog(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 addRequestLog('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 addRequestLog('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 * Get the current environment context.\n */\nexport function getEnvironment(): EnvironmentContext {\n return { ...globalEnv }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as serializeEvlogErrorResponse, r as resolveEvlogError, t as extractErrorStatus } from "../nitro-D57TWGyN.mjs";
|
|
2
2
|
import { getRequestURL, send, setResponseHeader, setResponseStatus } from "h3";
|
|
3
|
+
import { defineNitroErrorHandler } from "nitropack/runtime/internal/error/utils";
|
|
3
4
|
|
|
4
5
|
//#region src/nitro/errorHandler.ts
|
|
5
6
|
/**
|
|
@@ -11,11 +12,11 @@ import { getRequestURL, send, setResponseHeader, setResponseStatus } from "h3";
|
|
|
11
12
|
* sanitizing internal error details in production for 5xx errors.
|
|
12
13
|
*/
|
|
13
14
|
var errorHandler_default = defineNitroErrorHandler((error, event) => {
|
|
14
|
-
const evlogError = error
|
|
15
|
+
const evlogError = resolveEvlogError(error);
|
|
15
16
|
const isDev = process.env.NODE_ENV === "development";
|
|
16
17
|
const url = getRequestURL(event, { xForwardedHost: true }).pathname;
|
|
17
18
|
if (!evlogError) {
|
|
18
|
-
const status = error
|
|
19
|
+
const status = extractErrorStatus(error);
|
|
19
20
|
const rawMessage = (error.statusText ?? error.statusMessage ?? error.message) || "Internal Server Error";
|
|
20
21
|
const message = isDev ? rawMessage : status >= 500 ? "Internal Server Error" : rawMessage;
|
|
21
22
|
setResponseStatus(event, status);
|
|
@@ -30,21 +31,9 @@ var errorHandler_default = defineNitroErrorHandler((error, event) => {
|
|
|
30
31
|
error: true
|
|
31
32
|
}));
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
-
setResponseStatus(event, status);
|
|
34
|
+
setResponseStatus(event, extractErrorStatus(evlogError));
|
|
35
35
|
setResponseHeader(event, "Content-Type", "application/json");
|
|
36
|
-
|
|
37
|
-
const statusMessage = evlogError.statusMessage || evlogError.message;
|
|
38
|
-
return send(event, JSON.stringify({
|
|
39
|
-
url,
|
|
40
|
-
status,
|
|
41
|
-
statusCode: status,
|
|
42
|
-
statusText: statusMessage,
|
|
43
|
-
statusMessage,
|
|
44
|
-
message: evlogError.message,
|
|
45
|
-
error: true,
|
|
46
|
-
...data !== void 0 && { data }
|
|
47
|
-
}));
|
|
36
|
+
return send(event, JSON.stringify(serializeEvlogErrorResponse(evlogError, url)));
|
|
48
37
|
});
|
|
49
38
|
|
|
50
39
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errorHandler.mjs","names":[],"sources":["../../src/nitro/errorHandler.ts"],"sourcesContent":["
|
|
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,2BAAe,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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useLogger } from "../runtime/server/useLogger.mjs";
|
|
2
|
+
import { t as NitroModuleOptions } from "../nitro-D81NBVPi.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/nitro/module.d.ts
|
|
5
|
+
declare function evlog(options?: NitroModuleOptions): {
|
|
6
|
+
name: string;
|
|
7
|
+
setup(nitro: any): void;
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { type NitroModuleOptions, evlog as default, useLogger };
|
|
11
|
+
//# sourceMappingURL=module.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.mts","names":[],"sources":["../../src/nitro/module.ts"],"mappings":";;;;iBAQwB,KAAA,CAAM,OAAA,GAAU,kBAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useLogger } from "../runtime/server/useLogger.mjs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
//#region src/nitro/module.ts
|
|
6
|
+
const _dir = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
function evlog(options) {
|
|
8
|
+
return {
|
|
9
|
+
name: "evlog",
|
|
10
|
+
setup(nitro) {
|
|
11
|
+
nitro.options.plugins = nitro.options.plugins || [];
|
|
12
|
+
nitro.options.plugins.push(resolve(_dir, "plugin"));
|
|
13
|
+
if (!nitro.options.errorHandler) nitro.options.errorHandler = resolve(_dir, "errorHandler");
|
|
14
|
+
nitro.options.runtimeConfig = nitro.options.runtimeConfig || {};
|
|
15
|
+
nitro.options.runtimeConfig.evlog = options || {};
|
|
16
|
+
process.env.__EVLOG_CONFIG = JSON.stringify(options || {});
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { evlog as default, useLogger };
|
|
23
|
+
//# sourceMappingURL=module.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.mjs","names":[],"sources":["../../src/nitro/module.ts"],"sourcesContent":["import { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\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: any) {\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 // 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\nexport { useLogger } from '../runtime/server/useLogger'\n"],"mappings":";;;;;AAMA,MAAM,OAAO,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAEpD,SAAwB,MAAM,SAA8B;AAC1D,QAAO;EACL,MAAM;EACN,MAAM,OAAY;AAEhB,SAAM,QAAQ,UAAU,MAAM,QAAQ,WAAW,EAAE;AACnD,SAAM,QAAQ,QAAQ,KAAK,QAAQ,MAAM,SAAS,CAAC;AAGnD,OAAI,CAAC,MAAM,QAAQ,aACjB,OAAM,QAAQ,eAAe,QAAQ,MAAM,eAAe;AAM5D,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"}
|
package/dist/nitro/plugin.mjs
CHANGED
|
@@ -1,41 +1,10 @@
|
|
|
1
|
-
import { filterSafeHeaders
|
|
2
|
-
import { createRequestLogger, initLogger } from "../logger.mjs";
|
|
3
|
-
import {
|
|
1
|
+
import { filterSafeHeaders } from "../utils.mjs";
|
|
2
|
+
import { createRequestLogger, initLogger, isEnabled } from "../logger.mjs";
|
|
3
|
+
import { a as shouldLog, n as getServiceForPath, t as extractErrorStatus } from "../nitro-D57TWGyN.mjs";
|
|
4
|
+
import { defineNitroPlugin } from "nitropack/runtime/internal/plugin";
|
|
4
5
|
import { getHeaders } from "h3";
|
|
5
6
|
|
|
6
7
|
//#region src/nitro/plugin.ts
|
|
7
|
-
function shouldLog(path, include, exclude) {
|
|
8
|
-
if (exclude && exclude.length > 0) {
|
|
9
|
-
if (exclude.some((pattern) => matchesPattern(path, pattern))) return false;
|
|
10
|
-
}
|
|
11
|
-
if (!include || include.length === 0) return true;
|
|
12
|
-
return include.some((pattern) => matchesPattern(path, pattern));
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Find the service name for a given path based on route patterns.
|
|
16
|
-
*
|
|
17
|
-
* When multiple patterns match the same path, the first matching pattern wins
|
|
18
|
-
* based on object iteration order. To ensure predictable behavior, order your
|
|
19
|
-
* route patterns from most specific to most general.
|
|
20
|
-
*
|
|
21
|
-
* @param path - The request path to match
|
|
22
|
-
* @param routes - Route configuration mapping patterns to service names
|
|
23
|
-
* @returns The service name for the matching route, or undefined if no match
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* ```ts
|
|
27
|
-
* // Good: specific patterns first, general patterns last
|
|
28
|
-
* routes: {
|
|
29
|
-
* '/api/auth/admin/**': { service: 'admin-service' },
|
|
30
|
-
* '/api/auth/**': { service: 'auth-service' },
|
|
31
|
-
* '/api/**': { service: 'api-service' },
|
|
32
|
-
* }
|
|
33
|
-
* ```
|
|
34
|
-
*/
|
|
35
|
-
function getServiceForPath(path, routes) {
|
|
36
|
-
if (!routes) return void 0;
|
|
37
|
-
for (const [pattern, config] of Object.entries(routes)) if (matchesPattern(path, pattern)) return config.service;
|
|
38
|
-
}
|
|
39
8
|
function getSafeHeaders(event) {
|
|
40
9
|
return filterSafeHeaders(getHeaders(event));
|
|
41
10
|
}
|
|
@@ -93,13 +62,20 @@ async function callEnrichAndDrain(nitroApp, emittedEvent, event) {
|
|
|
93
62
|
const waitUntilCtx = event.context.cloudflare?.context ?? event.context;
|
|
94
63
|
if (typeof waitUntilCtx?.waitUntil === "function") waitUntilCtx.waitUntil(drainPromise);
|
|
95
64
|
}
|
|
96
|
-
var plugin_default = defineNitroPlugin((nitroApp) => {
|
|
97
|
-
|
|
65
|
+
var plugin_default = defineNitroPlugin(async (nitroApp) => {
|
|
66
|
+
let evlogConfig;
|
|
67
|
+
if (process.env.__EVLOG_CONFIG) evlogConfig = JSON.parse(process.env.__EVLOG_CONFIG);
|
|
68
|
+
else try {
|
|
69
|
+
const { useRuntimeConfig } = await import("nitropack/runtime/internal/config");
|
|
70
|
+
evlogConfig = useRuntimeConfig().evlog;
|
|
71
|
+
} catch {}
|
|
98
72
|
initLogger({
|
|
73
|
+
enabled: evlogConfig?.enabled,
|
|
99
74
|
env: evlogConfig?.env,
|
|
100
75
|
pretty: evlogConfig?.pretty,
|
|
101
76
|
sampling: evlogConfig?.sampling
|
|
102
77
|
});
|
|
78
|
+
if (!isEnabled()) return;
|
|
103
79
|
nitroApp.hooks.hook("request", (event) => {
|
|
104
80
|
const e = event;
|
|
105
81
|
if (!shouldLog(e.path, evlogConfig?.include, evlogConfig?.exclude)) return;
|
|
@@ -109,55 +85,55 @@ var plugin_default = defineNitroPlugin((nitroApp) => {
|
|
|
109
85
|
const cfRay = getSafeHeaders(e)?.["cf-ray"];
|
|
110
86
|
if (cfRay) requestIdOverride = cfRay;
|
|
111
87
|
}
|
|
112
|
-
const
|
|
88
|
+
const requestLog = createRequestLogger({
|
|
113
89
|
method: e.method,
|
|
114
90
|
path: e.path,
|
|
115
91
|
requestId: requestIdOverride || e.context.requestId || crypto.randomUUID()
|
|
116
92
|
});
|
|
117
93
|
const routeService = getServiceForPath(e.path, evlogConfig?.routes);
|
|
118
|
-
if (routeService)
|
|
119
|
-
e.context.log =
|
|
94
|
+
if (routeService) requestLog.set({ service: routeService });
|
|
95
|
+
e.context.log = requestLog;
|
|
120
96
|
});
|
|
121
97
|
nitroApp.hooks.hook("error", async (error, { event }) => {
|
|
122
98
|
const e = event;
|
|
123
99
|
if (!e) return;
|
|
124
|
-
const
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
const errorStatus = error
|
|
128
|
-
|
|
100
|
+
const requestLog = e.context.log;
|
|
101
|
+
if (requestLog) {
|
|
102
|
+
requestLog.error(error);
|
|
103
|
+
const errorStatus = extractErrorStatus(error);
|
|
104
|
+
requestLog.set({ status: errorStatus });
|
|
129
105
|
const startTime = e.context._evlogStartTime;
|
|
130
106
|
const tailCtx = {
|
|
131
107
|
status: errorStatus,
|
|
132
108
|
duration: startTime ? Date.now() - startTime : void 0,
|
|
133
109
|
path: e.path,
|
|
134
110
|
method: e.method,
|
|
135
|
-
context:
|
|
111
|
+
context: requestLog.getContext(),
|
|
136
112
|
shouldKeep: false
|
|
137
113
|
};
|
|
138
114
|
await nitroApp.hooks.callHook("evlog:emit:keep", tailCtx);
|
|
139
115
|
e.context._evlogEmitted = true;
|
|
140
|
-
await callEnrichAndDrain(nitroApp,
|
|
116
|
+
await callEnrichAndDrain(nitroApp, requestLog.emit({ _forceKeep: tailCtx.shouldKeep }), e);
|
|
141
117
|
}
|
|
142
118
|
});
|
|
143
119
|
nitroApp.hooks.hook("afterResponse", async (event) => {
|
|
144
120
|
const e = event;
|
|
145
121
|
if (e.context._evlogEmitted) return;
|
|
146
|
-
const
|
|
147
|
-
if (
|
|
122
|
+
const requestLog = e.context.log;
|
|
123
|
+
if (requestLog) {
|
|
148
124
|
const status = getResponseStatus(e);
|
|
149
|
-
|
|
125
|
+
requestLog.set({ status });
|
|
150
126
|
const startTime = e.context._evlogStartTime;
|
|
151
127
|
const tailCtx = {
|
|
152
128
|
status,
|
|
153
129
|
duration: startTime ? Date.now() - startTime : void 0,
|
|
154
130
|
path: e.path,
|
|
155
131
|
method: e.method,
|
|
156
|
-
context:
|
|
132
|
+
context: requestLog.getContext(),
|
|
157
133
|
shouldKeep: false
|
|
158
134
|
};
|
|
159
135
|
await nitroApp.hooks.callHook("evlog:emit:keep", tailCtx);
|
|
160
|
-
await callEnrichAndDrain(nitroApp,
|
|
136
|
+
await callEnrichAndDrain(nitroApp, requestLog.emit({ _forceKeep: tailCtx.shouldKeep }), e);
|
|
161
137
|
}
|
|
162
138
|
});
|
|
163
139
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.mjs","names":[],"sources":["../../src/nitro/plugin.ts"],"sourcesContent":["import type { NitroApp } from 'nitropack/types'\nimport { defineNitroPlugin, useRuntimeConfig } from 'nitropack/runtime'\nimport { getHeaders } from 'h3'\nimport { createRequestLogger, initLogger } from '../logger'\nimport type { EnrichContext, RequestLogger, RouteConfig, SamplingConfig, ServerEvent, TailSamplingContext, WideEvent } from '../types'\nimport { filterSafeHeaders, matchesPattern } from '../utils'\n\ninterface EvlogConfig {\n env?: Record<string, unknown>\n pretty?: boolean\n include?: string[]\n exclude?: string[]\n routes?: Record<string, RouteConfig>\n sampling?: SamplingConfig\n}\n\nfunction 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 */\nfunction 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\nfunction getSafeHeaders(event: ServerEvent): Record<string, string> {\n const allHeaders = getHeaders(event as Parameters<typeof getHeaders>[0])\n return filterSafeHeaders(allHeaders)\n}\n\nfunction getSafeResponseHeaders(event: ServerEvent): Record<string, string> | undefined {\n const headers: Record<string, string> = {}\n const nodeRes = event.node?.res as { getHeaders?: () => Record<string, unknown> } | undefined\n\n if (nodeRes?.getHeaders) {\n for (const [key, value] of Object.entries(nodeRes.getHeaders())) {\n if (value === undefined) continue\n headers[key] = Array.isArray(value) ? value.join(', ') : String(value)\n }\n }\n\n if (event.response?.headers) {\n event.response.headers.forEach((value, key) => {\n headers[key] = value\n })\n }\n\n if (Object.keys(headers).length === 0) return undefined\n return filterSafeHeaders(headers)\n}\n\nfunction getResponseStatus(event: ServerEvent): number {\n // Node.js style\n if (event.node?.res?.statusCode) {\n return event.node.res.statusCode\n }\n\n // Web Standard\n if (event.response?.status) {\n return event.response.status\n }\n\n // Context-based\n if (typeof event.context.status === 'number') {\n return event.context.status\n }\n\n return 200\n}\n\nfunction buildHookContext(event: ServerEvent): Omit<EnrichContext, 'event'> {\n const responseHeaders = getSafeResponseHeaders(event)\n return {\n request: { method: event.method, path: event.path },\n headers: getSafeHeaders(event),\n response: {\n status: getResponseStatus(event),\n headers: responseHeaders,\n },\n }\n}\n\nasync function callEnrichAndDrain(\n nitroApp: NitroApp,\n emittedEvent: WideEvent | null,\n event: ServerEvent,\n): Promise<void> {\n if (!emittedEvent) return\n\n const hookContext = buildHookContext(event)\n\n try {\n await nitroApp.hooks.callHook('evlog:enrich', { event: emittedEvent, ...hookContext })\n } catch (err) {\n console.error('[evlog] enrich failed:', err)\n }\n\n const drainPromise = nitroApp.hooks.callHook('evlog:drain', {\n event: emittedEvent,\n request: hookContext.request,\n headers: hookContext.headers,\n }).catch((err) => {\n console.error('[evlog] drain failed:', err)\n })\n\n // Use waitUntil if available (Cloudflare Workers, Vercel Edge)\n // This ensures drains complete before the runtime terminates\n const waitUntilCtx = event.context.cloudflare?.context ?? event.context\n if (typeof waitUntilCtx?.waitUntil === 'function') {\n waitUntilCtx.waitUntil(drainPromise)\n }\n}\n\nexport default defineNitroPlugin((nitroApp) => {\n const config = useRuntimeConfig()\n const evlogConfig = config.evlog as EvlogConfig | undefined\n\n initLogger({\n env: evlogConfig?.env,\n pretty: evlogConfig?.pretty,\n sampling: evlogConfig?.sampling,\n })\n\n nitroApp.hooks.hook('request', (event) => {\n const e = event as ServerEvent\n\n // Skip logging for routes not matching include/exclude patterns\n if (!shouldLog(e.path, evlogConfig?.include, evlogConfig?.exclude)) {\n return\n }\n\n // Store start time for duration calculation in tail sampling\n e.context._evlogStartTime = Date.now()\n\n let requestIdOverride: string | undefined = undefined\n if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') {\n const cfRay = getSafeHeaders(e)?.['cf-ray']\n if (cfRay) requestIdOverride = cfRay\n }\n\n const log = createRequestLogger({\n method: e.method,\n path: e.path,\n requestId: requestIdOverride || e.context.requestId || crypto.randomUUID(),\n })\n\n // Apply route-based service configuration if a matching route is found\n const routeService = getServiceForPath(e.path, evlogConfig?.routes)\n if (routeService) {\n log.set({ service: routeService })\n }\n\n e.context.log = log\n })\n\n nitroApp.hooks.hook('error', async (error, { event }) => {\n const e = event as ServerEvent | undefined\n if (!e) return\n\n const log = e.context.log as RequestLogger | undefined\n if (log) {\n log.error(error as Error)\n\n // Get the actual error status code\n const errorStatus = (error as { statusCode?: number }).statusCode ?? 500\n log.set({ status: errorStatus })\n\n // Build tail sampling context\n const startTime = e.context._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const tailCtx: TailSamplingContext = {\n status: errorStatus,\n duration: durationMs,\n path: e.path,\n method: e.method,\n context: log.getContext(),\n shouldKeep: false,\n }\n\n // Call evlog:emit:keep hook\n await nitroApp.hooks.callHook('evlog:emit:keep', tailCtx)\n\n e.context._evlogEmitted = true\n\n const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(nitroApp, emittedEvent, e)\n }\n })\n\n nitroApp.hooks.hook('afterResponse', async (event) => {\n const e = event as ServerEvent\n // Skip if already emitted by error hook\n if (e.context._evlogEmitted) return\n\n const log = e.context.log as RequestLogger | undefined\n if (log) {\n const status = getResponseStatus(e)\n log.set({ status })\n\n const startTime = e.context._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const tailCtx: TailSamplingContext = {\n status,\n duration: durationMs,\n path: e.path,\n method: e.method,\n context: log.getContext(),\n shouldKeep: false,\n }\n\n await nitroApp.hooks.callHook('evlog:emit:keep', tailCtx)\n\n const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(nitroApp, emittedEvent, e)\n }\n })\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.mjs","names":[],"sources":["../../src/nitro/plugin.ts"],"sourcesContent":["import type { NitroApp } from 'nitropack/types'\n// Import from specific subpaths to avoid the barrel 'nitropack/runtime' which\n// re-exports from internal/app.mjs — that file imports #nitro-internal-virtual/*\n// modules that only exist inside rollup builds and crash when loaded externally\n// (nitropack dev loads plugins outside the bundle via Worker threads).\nimport { defineNitroPlugin } from 'nitropack/runtime/internal/plugin'\nimport { getHeaders } from 'h3'\nimport { createRequestLogger, initLogger, isEnabled } from '../logger'\nimport { shouldLog, getServiceForPath, extractErrorStatus } from '../nitro'\nimport type { EvlogConfig } from '../nitro'\nimport type { EnrichContext, RequestLogger, ServerEvent, TailSamplingContext, WideEvent } from '../types'\nimport { filterSafeHeaders } from '../utils'\n\nfunction getSafeHeaders(event: ServerEvent): Record<string, string> {\n const allHeaders = getHeaders(event as Parameters<typeof getHeaders>[0])\n return filterSafeHeaders(allHeaders)\n}\n\nfunction getSafeResponseHeaders(event: ServerEvent): Record<string, string> | undefined {\n const headers: Record<string, string> = {}\n const nodeRes = event.node?.res as { getHeaders?: () => Record<string, unknown> } | undefined\n\n if (nodeRes?.getHeaders) {\n for (const [key, value] of Object.entries(nodeRes.getHeaders())) {\n if (value === undefined) continue\n headers[key] = Array.isArray(value) ? value.join(', ') : String(value)\n }\n }\n\n if (event.response?.headers) {\n event.response.headers.forEach((value, key) => {\n headers[key] = value\n })\n }\n\n if (Object.keys(headers).length === 0) return undefined\n return filterSafeHeaders(headers)\n}\n\nfunction getResponseStatus(event: ServerEvent): number {\n // Node.js style\n if (event.node?.res?.statusCode) {\n return event.node.res.statusCode\n }\n\n // Web Standard\n if (event.response?.status) {\n return event.response.status\n }\n\n // Context-based\n if (typeof event.context.status === 'number') {\n return event.context.status\n }\n\n return 200\n}\n\nfunction buildHookContext(event: ServerEvent): Omit<EnrichContext, 'event'> {\n const responseHeaders = getSafeResponseHeaders(event)\n return {\n request: { method: event.method, path: event.path },\n headers: getSafeHeaders(event),\n response: {\n status: getResponseStatus(event),\n headers: responseHeaders,\n },\n }\n}\n\nasync function callEnrichAndDrain(\n nitroApp: NitroApp,\n emittedEvent: WideEvent | null,\n event: ServerEvent,\n): Promise<void> {\n if (!emittedEvent) return\n\n const hookContext = buildHookContext(event)\n\n try {\n await nitroApp.hooks.callHook('evlog:enrich', { event: emittedEvent, ...hookContext })\n } catch (err) {\n console.error('[evlog] enrich failed:', err)\n }\n\n const drainPromise = nitroApp.hooks.callHook('evlog:drain', {\n event: emittedEvent,\n request: hookContext.request,\n headers: hookContext.headers,\n }).catch((err) => {\n console.error('[evlog] drain failed:', err)\n })\n\n // Use waitUntil if available (Cloudflare Workers, Vercel Edge)\n // This ensures drains complete before the runtime terminates\n const waitUntilCtx = event.context.cloudflare?.context ?? event.context\n if (typeof waitUntilCtx?.waitUntil === 'function') {\n waitUntilCtx.waitUntil(drainPromise)\n }\n}\n\nexport default defineNitroPlugin(async (nitroApp) => {\n // Config resolution: process.env bridge first (always set by the module),\n // then lazy useRuntimeConfig() for production builds where env may not persist.\n let evlogConfig: EvlogConfig | undefined\n if (process.env.__EVLOG_CONFIG) {\n evlogConfig = JSON.parse(process.env.__EVLOG_CONFIG)\n } else {\n try {\n // nitropack/runtime/internal/config imports virtual modules —\n // only works inside rollup-bundled output (production builds).\n const { useRuntimeConfig } = await import('nitropack/runtime/internal/config')\n evlogConfig = (useRuntimeConfig() as Record<string, any>).evlog\n } catch {\n // Expected in dev mode — virtual modules unavailable outside rollup\n }\n }\n\n initLogger({\n enabled: evlogConfig?.enabled,\n env: evlogConfig?.env,\n pretty: evlogConfig?.pretty,\n sampling: evlogConfig?.sampling,\n })\n\n if (!isEnabled()) return\n\n nitroApp.hooks.hook('request', (event) => {\n const e = event as ServerEvent\n\n // Skip logging for routes not matching include/exclude patterns\n if (!shouldLog(e.path, evlogConfig?.include, evlogConfig?.exclude)) {\n return\n }\n\n // Store start time for duration calculation in tail sampling\n e.context._evlogStartTime = Date.now()\n\n let requestIdOverride: string | undefined = undefined\n if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') {\n const cfRay = getSafeHeaders(e)?.['cf-ray']\n if (cfRay) requestIdOverride = cfRay\n }\n\n const requestLog = createRequestLogger({\n method: e.method,\n path: e.path,\n requestId: requestIdOverride || e.context.requestId || crypto.randomUUID(),\n })\n\n // Apply route-based service configuration if a matching route is found\n const routeService = getServiceForPath(e.path, evlogConfig?.routes)\n if (routeService) {\n requestLog.set({ service: routeService })\n }\n\n e.context.log = requestLog\n })\n\n nitroApp.hooks.hook('error', async (error, { event }) => {\n const e = event as ServerEvent | undefined\n if (!e) return\n\n const requestLog = e.context.log as RequestLogger | undefined\n if (requestLog) {\n requestLog.error(error as Error)\n\n const errorStatus = extractErrorStatus(error)\n requestLog.set({ status: errorStatus })\n\n // Build tail sampling context\n const startTime = e.context._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const tailCtx: TailSamplingContext = {\n status: errorStatus,\n duration: durationMs,\n path: e.path,\n method: e.method,\n context: requestLog.getContext(),\n shouldKeep: false,\n }\n\n // Call evlog:emit:keep hook\n await nitroApp.hooks.callHook('evlog:emit:keep', tailCtx)\n\n e.context._evlogEmitted = true\n\n const emittedEvent = requestLog.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(nitroApp, emittedEvent, e)\n }\n })\n\n nitroApp.hooks.hook('afterResponse', async (event) => {\n const e = event as ServerEvent\n // Skip if already emitted by error hook\n if (e.context._evlogEmitted) return\n\n const requestLog = e.context.log as RequestLogger | undefined\n if (requestLog) {\n const status = getResponseStatus(e)\n requestLog.set({ status })\n\n const startTime = e.context._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const tailCtx: TailSamplingContext = {\n status,\n duration: durationMs,\n path: e.path,\n method: e.method,\n context: requestLog.getContext(),\n shouldKeep: false,\n }\n\n await nitroApp.hooks.callHook('evlog:emit:keep', tailCtx)\n\n const emittedEvent = requestLog.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(nitroApp, emittedEvent, e)\n }\n })\n})\n"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as nitro_types0 from "nitro/types";
|
|
2
|
+
|
|
3
|
+
//#region src/nitro-v3/errorHandler.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Custom Nitro v3 error handler that properly serializes EvlogError.
|
|
6
|
+
* This ensures that 'data' (containing 'why', 'fix', 'link') is preserved
|
|
7
|
+
* in the JSON response regardless of the underlying HTTP framework.
|
|
8
|
+
*
|
|
9
|
+
* For non-EvlogError, returns undefined to let Nitro's default handler take over.
|
|
10
|
+
*
|
|
11
|
+
* Usage in nitro.config.ts:
|
|
12
|
+
* ```ts
|
|
13
|
+
* // errorHandler.ts
|
|
14
|
+
* export { default } from 'evlog/nitro/v3/errorHandler'
|
|
15
|
+
* // nitro.config.ts
|
|
16
|
+
* export default defineConfig({
|
|
17
|
+
* errorHandler: './errorHandler',
|
|
18
|
+
* })
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
declare const _default: nitro_types0.NitroErrorHandler;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { _default as default };
|
|
24
|
+
//# sourceMappingURL=errorHandler.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorHandler.d.mts","names":[],"sources":["../../../src/nitro-v3/errorHandler.ts"],"mappings":""}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { i as serializeEvlogErrorResponse, r as resolveEvlogError, t as extractErrorStatus } from "../../nitro-D57TWGyN.mjs";
|
|
2
|
+
import { defineErrorHandler } from "nitro";
|
|
3
|
+
import { parseURL } from "ufo";
|
|
4
|
+
|
|
5
|
+
//#region src/nitro-v3/errorHandler.ts
|
|
6
|
+
/**
|
|
7
|
+
* Custom Nitro v3 error handler that properly serializes EvlogError.
|
|
8
|
+
* This ensures that 'data' (containing 'why', 'fix', 'link') is preserved
|
|
9
|
+
* in the JSON response regardless of the underlying HTTP framework.
|
|
10
|
+
*
|
|
11
|
+
* For non-EvlogError, returns undefined to let Nitro's default handler take over.
|
|
12
|
+
*
|
|
13
|
+
* Usage in nitro.config.ts:
|
|
14
|
+
* ```ts
|
|
15
|
+
* // errorHandler.ts
|
|
16
|
+
* export { default } from 'evlog/nitro/v3/errorHandler'
|
|
17
|
+
* // nitro.config.ts
|
|
18
|
+
* export default defineConfig({
|
|
19
|
+
* errorHandler: './errorHandler',
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
var errorHandler_default = defineErrorHandler((error, event) => {
|
|
24
|
+
const evlogError = resolveEvlogError(error);
|
|
25
|
+
if (!evlogError) return;
|
|
26
|
+
const url = parseURL(event.req.url).pathname;
|
|
27
|
+
const status = extractErrorStatus(evlogError);
|
|
28
|
+
return new Response(JSON.stringify(serializeEvlogErrorResponse(evlogError, url)), {
|
|
29
|
+
status,
|
|
30
|
+
headers: { "Content-Type": "application/json" }
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
export { errorHandler_default as default };
|
|
36
|
+
//# sourceMappingURL=errorHandler.mjs.map
|
|
@@ -0,0 +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":";;;;;;;;;;;;;;;;;;;;;;AAqBA,2BAAe,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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { t as NitroModuleOptions } from "../../nitro-D81NBVPi.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/nitro-v3/module.d.ts
|
|
4
|
+
declare function evlog(options?: NitroModuleOptions): {
|
|
5
|
+
name: string;
|
|
6
|
+
setup(nitro: any): void;
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { type NitroModuleOptions, evlog as default };
|
|
10
|
+
//# sourceMappingURL=module.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.mts","names":[],"sources":["../../../src/nitro-v3/module.ts"],"mappings":";;;iBAQwB,KAAA,CAAM,OAAA,GAAU,kBAAA"}
|