{"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 { MxllogConfig } 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('mxllog:enrich', { event: emittedEvent, ...hookContext })\n  } catch (err) {\n    console.error('[mxllog] enrich failed:', err)\n  }\n\n  const drainPromise = nitroApp.hooks.callHook('mxllog:drain', {\n    event: emittedEvent,\n    request: hookContext.request,\n    headers: hookContext.headers,\n  }).catch((err) => {\n    console.error('[mxllog] drain failed:', err)\n  })\n\n  // Use waitUntil if available (Cloudflare Workers, Vercel Edge, etc.)\n  // This keeps the runtime alive for background work without blocking the response\n  const waitUntilCtx = event.context.cloudflare?.context ?? event.context\n  if (typeof waitUntilCtx?.waitUntil === 'function') {\n    waitUntilCtx.waitUntil(drainPromise)\n  } else {\n    // Fallback: await drain to prevent lost logs in serverless environments\n    // (e.g. Vercel Fluid Compute). On the normal path this runs from\n    // afterResponse (response already sent); on the error path it may run\n    // before the error response is finalized.\n    await 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 mxllogConfig: MxllogConfig | undefined\n  if (process.env.__EVLOG_CONFIG) {\n    mxllogConfig = 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      mxllogConfig = (useRuntimeConfig() as Record<string, any>).mxllog\n    } catch {\n      // Expected in dev mode — virtual modules unavailable outside rollup\n    }\n  }\n\n  initLogger({\n    enabled: mxllogConfig?.enabled,\n    env: mxllogConfig?.env,\n    pretty: mxllogConfig?.pretty,\n    sampling: mxllogConfig?.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, mxllogConfig?.include, mxllogConfig?.exclude)) {\n      return\n    }\n\n    // Store start time for duration calculation in tail sampling\n    e.context._mxllogStartTime = 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, mxllogConfig?.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._mxllogStartTime 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 mxllog:emit:keep hook\n      await nitroApp.hooks.callHook('mxllog:emit:keep', tailCtx)\n\n      e.context._mxllogEmitted = 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._mxllogEmitted) 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._mxllogStartTime 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('mxllog: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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"}