From 5d0c92098bae569100c1fbf8380936cda9b0df7f Mon Sep 17 00:00:00 2001 From: Hugo Richard Date: Thu, 9 Apr 2026 20:05:02 +0100 Subject: [PATCH] fix(nuxt): sync runtime evlog to nitro and noop logger when disabled --- .changeset/nuxt-nitro-config-bridge.md | 5 +++++ AGENTS.md | 2 ++ packages/evlog/src/nitro-v3/plugin.ts | 22 ++++++++++++++++++++-- packages/evlog/src/nitro/plugin.ts | 19 ++++++++++++++++++- packages/evlog/src/nuxt/module.ts | 12 ++++++++++-- 5 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 .changeset/nuxt-nitro-config-bridge.md diff --git a/.changeset/nuxt-nitro-config-bridge.md b/.changeset/nuxt-nitro-config-bridge.md new file mode 100644 index 00000000..69b148fa --- /dev/null +++ b/.changeset/nuxt-nitro-config-bridge.md @@ -0,0 +1,5 @@ +--- +'evlog': patch +--- + +Fix Nuxt `evlog` options not reaching the Nitro plugin in dev: the Nuxt module now mirrors standalone Nitro by setting `process.env.__EVLOG_CONFIG` during `nitro:config`. When `enabled` is `false`, the Nitro plugins still attach a no-op request logger so `useLogger(event)` does not throw. diff --git a/AGENTS.md b/AGENTS.md index 3b595580..e0a1afb7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -234,6 +234,8 @@ export default defineConfig({ The Nuxt module uses `addVitePlugin()` to add strip + source location plugins internally. Auto-imports and client init are NOT delegated (Nuxt handles those natively). This is purely additive — no breaking change. +On `nitro:config`, the module serializes `runtimeConfig.evlog` into **`process.env.__EVLOG_CONFIG`** (same bridge as standalone Nitro). The Nitro plugin resolves config with **env first**, then `useRuntimeConfig().evlog`. That env injection is required because dev workers often cannot resolve the virtual Nitro runtime-config module reliably. **Do not** set `__EVLOG_CONFIG` yourself in a Nuxt app unless you mean to override the entire `evlog` block from `nuxt.config`. + ## Framework Integration > **Creating a new framework integration?** Follow the skill at `.agents/skills/create-framework-integration/SKILL.md`. It covers all touchpoints: source code, build config, package exports, tests, example app, and all documentation updates. diff --git a/packages/evlog/src/nitro-v3/plugin.ts b/packages/evlog/src/nitro-v3/plugin.ts index b62ae275..523435a5 100644 --- a/packages/evlog/src/nitro-v3/plugin.ts +++ b/packages/evlog/src/nitro-v3/plugin.ts @@ -142,10 +142,28 @@ export default definePlugin(async (nitroApp) => { _suppressDrainWarning: true, }) - if (!isEnabled()) return - const hooks = nitroApp.hooks as unknown as Hooks + // When globally disabled, createRequestLogger returns a no-op logger — still + // attach it so handlers can call useLogger without throwing. + if (!isEnabled()) { + hooks.hook('request', (event) => { + const { pathname } = parseURL(event.req.url) + const ctx = getContext(event) + let requestIdOverride: string | undefined + if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') { + const cfRay = event.req.headers.get('cf-ray') + if (cfRay) requestIdOverride = cfRay + } + ctx.log = createRequestLogger({ + method: event.req.method, + path: pathname, + requestId: requestIdOverride || ctx.requestId as string | undefined || crypto.randomUUID(), + }, { _deferDrain: true }) + }) + return + } + hooks.hook('request', (event) => { const { pathname } = parseURL(event.req.url) const ctx = getContext(event) diff --git a/packages/evlog/src/nitro/plugin.ts b/packages/evlog/src/nitro/plugin.ts index feea9151..871d9ad2 100644 --- a/packages/evlog/src/nitro/plugin.ts +++ b/packages/evlog/src/nitro/plugin.ts @@ -117,7 +117,24 @@ export default defineNitroPlugin(async (nitroApp) => { _suppressDrainWarning: true, }) - if (!isEnabled()) return + // When globally disabled, createRequestLogger returns a no-op logger — still + // attach it so handlers can call useLogger(event) without throwing. + if (!isEnabled()) { + nitroApp.hooks.hook('request', (event) => { + const e = event as ServerEvent + let requestIdOverride: string | undefined + if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') { + const cfRay = getSafeHeaders(e)?.['cf-ray'] + if (cfRay) requestIdOverride = cfRay + } + e.context.log = createRequestLogger({ + method: e.method, + path: e.path, + requestId: requestIdOverride || e.context.requestId || crypto.randomUUID(), + }, { _deferDrain: true }) + }) + return + } nitroApp.hooks.hook('request', (event) => { const e = event as ServerEvent diff --git a/packages/evlog/src/nuxt/module.ts b/packages/evlog/src/nuxt/module.ts index 0d2555ad..1e8c3af6 100644 --- a/packages/evlog/src/nuxt/module.ts +++ b/packages/evlog/src/nuxt/module.ts @@ -267,14 +267,22 @@ export default defineNuxtModule({ const transportEndpoint = options.transport?.endpoint ?? '/api/_evlog/ingest' const transportCredentials = options.transport?.credentials ?? 'same-origin' + nuxt.options.runtimeConfig.evlog = options + // Register custom error handler for proper EvlogError serialization // Only set if not already configured to avoid overwriting user's custom handler + // Mirror standalone Nitro modules: serialize evlog options into __EVLOG_CONFIG so + // resolveEvlogConfigForNitroPlugin() picks them up in dev (Nitro worker threads + // often cannot resolve useRuntimeConfig().evlog via dynamic import reliably). // @ts-expect-error nitro:config hook exists but is not in NuxtHooks type nuxt.hook('nitro:config', (nitroConfig: NitroConfig) => { nitroConfig.errorHandler = nitroConfig.errorHandler || resolver.resolve('../nitro/errorHandler') - }) - nuxt.options.runtimeConfig.evlog = options + const evlogForNitro = nuxt.options.runtimeConfig.evlog ?? options + if (evlogForNitro !== undefined && typeof evlogForNitro === 'object') { + process.env.__EVLOG_CONFIG = JSON.stringify(evlogForNitro) + } + }) nuxt.options.runtimeConfig.public.evlog = { enabled: options.enabled ?? true, console: options.console,