evlog 1.9.0 → 1.11.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.
Files changed (37) hide show
  1. package/README.md +2 -3
  2. package/dist/{_severity-CXfyvxQi.mjs → _severity-D_IU9-90.mjs} +1 -1
  3. package/dist/{_severity-CXfyvxQi.mjs.map → _severity-D_IU9-90.mjs.map} +1 -1
  4. package/dist/adapters/otlp.mjs +1 -1
  5. package/dist/adapters/sentry.mjs +1 -1
  6. package/dist/next/client.d.mts +55 -0
  7. package/dist/next/client.d.mts.map +1 -0
  8. package/dist/next/client.mjs +44 -0
  9. package/dist/next/client.mjs.map +1 -0
  10. package/dist/next/index.d.mts +169 -0
  11. package/dist/next/index.d.mts.map +1 -0
  12. package/dist/next/index.mjs +280 -0
  13. package/dist/next/index.mjs.map +1 -0
  14. package/dist/nitro/errorHandler.mjs +1 -1
  15. package/dist/nitro/module.d.mts +1 -1
  16. package/dist/nitro/plugin.mjs +3 -1
  17. package/dist/nitro/plugin.mjs.map +1 -1
  18. package/dist/nitro/v3/errorHandler.mjs +1 -1
  19. package/dist/nitro/v3/index.d.mts +3 -2
  20. package/dist/nitro/v3/index.mjs +2 -1
  21. package/dist/nitro/v3/middleware.d.mts +25 -0
  22. package/dist/nitro/v3/middleware.d.mts.map +1 -0
  23. package/dist/nitro/v3/middleware.mjs +45 -0
  24. package/dist/nitro/v3/middleware.mjs.map +1 -0
  25. package/dist/nitro/v3/module.d.mts +1 -1
  26. package/dist/nitro/v3/plugin.mjs +9 -4
  27. package/dist/nitro/v3/plugin.mjs.map +1 -1
  28. package/dist/{nitro-D81NBVPi.d.mts → nitro-CrFBjY1Y.d.mts} +1 -1
  29. package/dist/nitro-CrFBjY1Y.d.mts.map +1 -0
  30. package/dist/nitro-Da8tEfJ3.mjs +39 -0
  31. package/dist/nitro-Da8tEfJ3.mjs.map +1 -0
  32. package/dist/nuxt/module.mjs +1 -1
  33. package/dist/{nitro-D57TWGyN.mjs → routes-BNbrnm14.mjs} +3 -37
  34. package/dist/routes-BNbrnm14.mjs.map +1 -0
  35. package/package.json +27 -3
  36. package/dist/nitro-D57TWGyN.mjs.map +0 -1
  37. package/dist/nitro-D81NBVPi.d.mts.map +0 -1
package/README.md CHANGED
@@ -987,17 +987,16 @@ try {
987
987
 
988
988
  ## Framework Support
989
989
 
990
- evlog works with any framework powered by [Nitro](https://nitro.unjs.io/):
991
-
992
990
  | Framework | Integration |
993
991
  |-----------|-------------|
994
992
  | **Nuxt** | `modules: ['evlog/nuxt']` |
993
+ | **Next.js** | `createEvlog()` factory with `import { createEvlog } from 'evlog/next'` ([example](./examples/nextjs)) |
995
994
  | **Nitro v3** | `modules: [evlog()]` with `import evlog from 'evlog/nitro/v3'` |
996
995
  | **Nitro v2** | `modules: [evlog()]` with `import evlog from 'evlog/nitro'` |
997
996
  | **Analog** | Nitro v2 module setup |
998
997
  | **Vinxi** | Nitro v2 module setup |
999
998
  | **SolidStart** | Nitro v2 module setup ([example](./examples/solidstart)) |
1000
- | **TanStack Start** | Nitro v2 module setup |
999
+ | **TanStack Start** | Nitro v3 module setup ([example](./examples/tanstack-start)) |
1001
1000
 
1002
1001
  ## Agent Skills
1003
1002
 
@@ -14,4 +14,4 @@ const OTEL_SEVERITY_TEXT = {
14
14
 
15
15
  //#endregion
16
16
  export { OTEL_SEVERITY_TEXT as n, OTEL_SEVERITY_NUMBER as t };
17
- //# sourceMappingURL=_severity-CXfyvxQi.mjs.map
17
+ //# sourceMappingURL=_severity-D_IU9-90.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_severity-CXfyvxQi.mjs","names":[],"sources":["../src/adapters/_severity.ts"],"sourcesContent":["import type { LogLevel } from '../types'\n\nexport const OTEL_SEVERITY_NUMBER: Record<LogLevel, number> = {\n debug: 5,\n info: 9,\n warn: 13,\n error: 17,\n}\n\nexport const OTEL_SEVERITY_TEXT: Record<LogLevel, string> = {\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n}\n"],"mappings":";AAEA,MAAa,uBAAiD;CAC5D,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACR;AAED,MAAa,qBAA+C;CAC1D,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACR"}
1
+ {"version":3,"file":"_severity-D_IU9-90.mjs","names":[],"sources":["../src/adapters/_severity.ts"],"sourcesContent":["import type { LogLevel } from '../types'\n\nexport const OTEL_SEVERITY_NUMBER: Record<LogLevel, number> = {\n debug: 5,\n info: 9,\n warn: 13,\n error: 17,\n}\n\nexport const OTEL_SEVERITY_TEXT: Record<LogLevel, string> = {\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n}\n"],"mappings":";AAEA,MAAa,uBAAiD;CAC5D,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACR;AAED,MAAa,qBAA+C;CAC1D,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACR"}
@@ -1,5 +1,5 @@
1
1
  import { n as defineDrain, r as resolveAdapterConfig, t as httpPost } from "../_http-DVDwNag0.mjs";
2
- import { n as OTEL_SEVERITY_TEXT, t as OTEL_SEVERITY_NUMBER } from "../_severity-CXfyvxQi.mjs";
2
+ import { n as OTEL_SEVERITY_TEXT, t as OTEL_SEVERITY_NUMBER } from "../_severity-D_IU9-90.mjs";
3
3
 
4
4
  //#region src/adapters/otlp.ts
5
5
  const OTLP_FIELDS = [
@@ -1,5 +1,5 @@
1
1
  import { n as defineDrain, r as resolveAdapterConfig, t as httpPost } from "../_http-DVDwNag0.mjs";
2
- import { t as OTEL_SEVERITY_NUMBER } from "../_severity-CXfyvxQi.mjs";
2
+ import { t as OTEL_SEVERITY_NUMBER } from "../_severity-D_IU9-90.mjs";
3
3
 
4
4
  //#region src/adapters/sentry.ts
5
5
  const SENTRY_FIELDS = [
@@ -0,0 +1,55 @@
1
+ import { TransportConfig } from "../types.mjs";
2
+ import { clearIdentity, log as _clientLog, setIdentity } from "../runtime/client/log.mjs";
3
+ import * as react from "react";
4
+
5
+ //#region src/next/client.d.ts
6
+ interface EvlogProviderProps {
7
+ /**
8
+ * Service name for client-side logs.
9
+ * @default 'client'
10
+ */
11
+ service?: string;
12
+ /**
13
+ * Enable pretty printing in the browser console.
14
+ * @default true
15
+ */
16
+ pretty?: boolean;
17
+ /**
18
+ * Transport configuration for sending client logs to the server.
19
+ */
20
+ transport?: TransportConfig;
21
+ /**
22
+ * Enable or disable client-side logging.
23
+ * @default true
24
+ */
25
+ enabled?: boolean;
26
+ children: React.ReactNode;
27
+ }
28
+ /**
29
+ * React provider that initializes evlog client-side logging.
30
+ * Place this in your root layout to enable client logging throughout your app.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * // app/layout.tsx
35
+ * import { EvlogProvider } from 'evlog/next/client'
36
+ *
37
+ * export default function Layout({ children }) {
38
+ * return (
39
+ * <EvlogProvider service="my-app" transport={{ enabled: true }}>
40
+ * {children}
41
+ * </EvlogProvider>
42
+ * )
43
+ * }
44
+ * ```
45
+ */
46
+ declare function EvlogProvider({
47
+ service,
48
+ pretty,
49
+ transport,
50
+ enabled,
51
+ children
52
+ }: EvlogProviderProps): react.ReactNode;
53
+ //#endregion
54
+ export { EvlogProvider, EvlogProviderProps, clearIdentity, _clientLog as log, setIdentity };
55
+ //# sourceMappingURL=client.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.mts","names":[],"sources":["../../src/next/client.ts"],"mappings":";;;;;UAQiB,kBAAA;;;AAAjB;;EAKE,OAAA;EAmByB;;;;EAbzB,MAAA;EAWA;;;EANA,SAAA,GAAY,eAAA;EAQa;;AAsB3B;;EAxBE,OAAA;EAEA,QAAA,EAAU,KAAA,CAAM,SAAA;AAAA;;;;;;;;;;;;;;;;;;;iBAsBF,aAAA,CAAA;EAAgB,OAAA;EAAS,MAAA;EAAQ,SAAA;EAAW,OAAA;EAAS;AAAA,GAAY,kBAAA,GAAkB,KAAA,CAAA,SAAA"}
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+
3
+ import { clearIdentity, initLog, log as _clientLog, setIdentity } from "../runtime/client/log.mjs";
4
+ import { useEffect } from "react";
5
+
6
+ //#region src/next/client.ts
7
+ /**
8
+ * React provider that initializes evlog client-side logging.
9
+ * Place this in your root layout to enable client logging throughout your app.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * // app/layout.tsx
14
+ * import { EvlogProvider } from 'evlog/next/client'
15
+ *
16
+ * export default function Layout({ children }) {
17
+ * return (
18
+ * <EvlogProvider service="my-app" transport={{ enabled: true }}>
19
+ * {children}
20
+ * </EvlogProvider>
21
+ * )
22
+ * }
23
+ * ```
24
+ */
25
+ function EvlogProvider({ service, pretty, transport, enabled, children }) {
26
+ useEffect(() => {
27
+ initLog({
28
+ enabled,
29
+ pretty,
30
+ service,
31
+ transport
32
+ });
33
+ }, [
34
+ enabled,
35
+ pretty,
36
+ service,
37
+ transport
38
+ ]);
39
+ return children;
40
+ }
41
+
42
+ //#endregion
43
+ export { EvlogProvider, clearIdentity, _clientLog as log, setIdentity };
44
+ //# sourceMappingURL=client.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.mjs","names":[],"sources":["../../src/next/client.ts"],"sourcesContent":["'use client'\n\nimport { useEffect } from 'react'\nimport type { TransportConfig } from '../types'\nimport { initLog, log, setIdentity, clearIdentity } from '../runtime/client/log'\n\nexport { log, setIdentity, clearIdentity } from '../runtime/client/log'\n\nexport interface EvlogProviderProps {\n /**\n * Service name for client-side logs.\n * @default 'client'\n */\n service?: string\n\n /**\n * Enable pretty printing in the browser console.\n * @default true\n */\n pretty?: boolean\n\n /**\n * Transport configuration for sending client logs to the server.\n */\n transport?: TransportConfig\n\n /**\n * Enable or disable client-side logging.\n * @default true\n */\n enabled?: boolean\n\n children: React.ReactNode\n}\n\n/**\n * React provider that initializes evlog client-side logging.\n * Place this in your root layout to enable client logging throughout your app.\n *\n * @example\n * ```tsx\n * // app/layout.tsx\n * import { EvlogProvider } from 'evlog/next/client'\n *\n * export default function Layout({ children }) {\n * return (\n * <EvlogProvider service=\"my-app\" transport={{ enabled: true }}>\n * {children}\n * </EvlogProvider>\n * )\n * }\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport function EvlogProvider({ service, pretty, transport, enabled, children }: EvlogProviderProps) {\n useEffect(() => {\n initLog({\n enabled,\n pretty,\n service,\n transport,\n })\n }, [enabled, pretty, service, transport])\n\n return children\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAsDA,SAAgB,cAAc,EAAE,SAAS,QAAQ,WAAW,SAAS,YAAgC;AACnG,iBAAgB;AACd,UAAQ;GACN;GACA;GACA;GACA;GACD,CAAC;IACD;EAAC;EAAS;EAAQ;EAAS;EAAU,CAAC;AAEzC,QAAO"}
@@ -0,0 +1,169 @@
1
+ import { DrainContext, EnrichContext, EnvironmentContext, Log, RequestLogger, RouteConfig, SamplingConfig, TailSamplingContext } from "../types.mjs";
2
+ import { createError } from "../error.mjs";
3
+ import { log as _log } from "../logger.mjs";
4
+ import "../index.mjs";
5
+ import { AsyncLocalStorage } from "node:async_hooks";
6
+
7
+ //#region src/next/types.d.ts
8
+ interface NextEvlogOptions {
9
+ /**
10
+ * Service name for all logged events.
11
+ * @default auto-detected from SERVICE_NAME env or 'app'
12
+ */
13
+ service?: string;
14
+ /**
15
+ * Environment context overrides.
16
+ */
17
+ env?: Partial<EnvironmentContext>;
18
+ /**
19
+ * Enable pretty printing.
20
+ * @default true in development, false in production
21
+ */
22
+ pretty?: boolean;
23
+ /**
24
+ * Enable or disable all logging globally.
25
+ * @default true
26
+ */
27
+ enabled?: boolean;
28
+ /**
29
+ * Sampling configuration for filtering logs.
30
+ */
31
+ sampling?: SamplingConfig;
32
+ /**
33
+ * Route patterns to include in logging.
34
+ * Supports glob patterns like '/api/**'.
35
+ * If not set, all routes are logged.
36
+ */
37
+ include?: string[];
38
+ /**
39
+ * Route patterns to exclude from logging.
40
+ * Supports glob patterns like '/_next/**'.
41
+ * Exclusions take precedence over inclusions.
42
+ */
43
+ exclude?: string[];
44
+ /**
45
+ * Route-specific service configuration.
46
+ */
47
+ routes?: Record<string, RouteConfig>;
48
+ /**
49
+ * Drain callback called with every emitted event (fire-and-forget).
50
+ * Compatible with drain adapters and pipeline-wrapped drains.
51
+ */
52
+ drain?: (ctx: DrainContext) => void | Promise<void>;
53
+ /**
54
+ * Enrich callback called after emit, before drain.
55
+ * Use this to add derived context (e.g. geo, deployment info).
56
+ */
57
+ enrich?: (ctx: EnrichContext) => void | Promise<void>;
58
+ /**
59
+ * Custom tail sampling callback called before emit.
60
+ * Set `ctx.shouldKeep = true` to force-keep the log regardless of head sampling.
61
+ * Equivalent to Nuxt's `evlog:emit:keep` hook.
62
+ */
63
+ keep?: (ctx: TailSamplingContext) => void | Promise<void>;
64
+ /**
65
+ * When pretty is disabled, emit JSON strings (default) or raw objects.
66
+ * @default true
67
+ */
68
+ stringify?: boolean;
69
+ }
70
+ interface EvlogMiddlewareConfig {
71
+ /**
72
+ * Route patterns to include in middleware processing.
73
+ * Supports glob patterns like '/api/**'.
74
+ */
75
+ include?: string[];
76
+ /**
77
+ * Route patterns to exclude from middleware processing.
78
+ * Supports glob patterns like '/_next/**'.
79
+ */
80
+ exclude?: string[];
81
+ }
82
+ //#endregion
83
+ //#region src/next/storage.d.ts
84
+ /**
85
+ * Get the current request-scoped logger.
86
+ * Must be called inside a `withEvlog()` wrapper.
87
+ *
88
+ * @throws {Error} if called outside of `withEvlog()` context
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * export const POST = withEvlog(async (request) => {
93
+ * const log = useLogger()
94
+ * log.set({ user: { id: '123' } })
95
+ * return Response.json({ ok: true })
96
+ * })
97
+ * ```
98
+ */
99
+ declare function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T>;
100
+ //#endregion
101
+ //#region src/next/middleware.d.ts
102
+ type NextRequest = {
103
+ nextUrl: {
104
+ pathname: string;
105
+ };
106
+ headers: {
107
+ get(name: string): string | null;
108
+ };
109
+ };
110
+ type NextResponse = {
111
+ headers: {
112
+ set(name: string, value: string): void;
113
+ };
114
+ };
115
+ /**
116
+ * Create an evlog middleware for Next.js.
117
+ * Sets `x-request-id` and `x-evlog-start` headers so `withEvlog()` can reuse them
118
+ * for timing consistency across the middleware -> handler chain.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * // middleware.ts
123
+ * import { evlogMiddleware } from 'evlog/next'
124
+ * export const middleware = evlogMiddleware()
125
+ * export const config = { matcher: ['/api/:path*'] }
126
+ * ```
127
+ */
128
+ declare function evlogMiddleware(config?: EvlogMiddlewareConfig): (request: NextRequest) => Promise<NextResponse>;
129
+ //#endregion
130
+ //#region src/next/index.d.ts
131
+ /**
132
+ * Create an evlog instance configured for Next.js.
133
+ * Returns all helpers needed for server-side logging.
134
+ *
135
+ * @example
136
+ * ```ts
137
+ * // lib/evlog.ts
138
+ * import { createEvlog } from 'evlog/next'
139
+ * import { createAxiomDrain } from 'evlog/axiom'
140
+ * import { createDrainPipeline } from 'evlog/pipeline'
141
+ *
142
+ * const pipeline = createDrainPipeline({ batch: { size: 50 } })
143
+ *
144
+ * export const { withEvlog, useLogger, log, createEvlogError } = createEvlog({
145
+ * service: 'my-app',
146
+ * sampling: {
147
+ * rates: { info: 10 },
148
+ * keep: [{ status: 400 }, { duration: 1000 }],
149
+ * },
150
+ * drain: pipeline(createAxiomDrain({
151
+ * dataset: 'logs',
152
+ * token: process.env.AXIOM_TOKEN!,
153
+ * })),
154
+ * enrich: (ctx) => {
155
+ * ctx.event.deploymentId = process.env.VERCEL_DEPLOYMENT_ID
156
+ * },
157
+ * })
158
+ * ```
159
+ */
160
+ declare function createEvlog(options?: NextEvlogOptions): {
161
+ withEvlog: <TArgs extends unknown[], TReturn>(handler: (...args: TArgs) => TReturn) => (...args: TArgs) => Promise<Awaited<TReturn>>;
162
+ useLogger: typeof useLogger;
163
+ log: Log;
164
+ createError: typeof createError;
165
+ createEvlogError: typeof createError;
166
+ };
167
+ //#endregion
168
+ export { type EvlogMiddlewareConfig, type NextEvlogOptions, createError, createError as createEvlogError, createEvlog, evlogMiddleware, _log as log, useLogger };
169
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/next/types.ts","../../src/next/storage.ts","../../src/next/middleware.ts","../../src/next/index.ts"],"mappings":";;;;;;;UAEiB,gBAAA;;;;;EAKf,OAAA;EALe;;;EAUf,GAAA,GAAM,OAAA,CAAQ,kBAAA;EAAR;;;;EAMN,MAAA;EAoCsC;;;;EA9BtC,OAAA;EA2CmD;;;EAtCnD,QAAA,GAAW,cAAA;EAjBL;;;;;EAwBN,OAAA;EAAA;;;;;EAOA,OAAA;EAWc;;;EANd,MAAA,GAAS,MAAA,SAAe,WAAA;EAYT;;;;EANf,KAAA,IAAS,GAAA,EAAK,YAAA,YAAwB,OAAA;EAa9B;;;;EAPR,MAAA,IAAU,GAAA,EAAK,aAAA,YAAyB,OAAA;EAgBzB;;;;;EATf,IAAA,IAAQ,GAAA,EAAK,mBAAA,YAA+B,OAAA;;;AC/C9C;;EDqDE,SAAA;AAAA;AAAA,UAGe,qBAAA;ECxDwD;;;;ED6DvE,OAAA;EC7DuE;;;;EDmEvE,OAAA;AAAA;;;;;;;AArFF;;;;;;;;;;;iBCkBgB,SAAA,oBAA6B,MAAA,kBAAA,CAAA,GAA4B,aAAA,CAAc,CAAA;;;KCjBlF,WAAA;EACH,OAAA;IAAW,QAAA;EAAA;EACX,OAAA;IAAW,GAAA,CAAI,IAAA;EAAA;AAAA;AAAA,KAGZ,YAAA;EACH,OAAA;IAAW,GAAA,CAAI,IAAA,UAAc,KAAA;EAAA;AAAA;;;;;;;;;;;;;;iBAoBf,eAAA,CAAgB,MAAA,GAAS,qBAAA,IACzB,OAAA,EAAS,WAAA,KAAW,OAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCYpB,WAAA,CAAY,OAAA,GAAS,gBAAA;6DAWg8D,IAAA,EAAA,KAAA,KAAA,OAAA,SAAkC,IAAA,EAAA,KAAA,KAAA,OAAA,CAAA,OAAA,CAAA,OAAA"}
@@ -0,0 +1,280 @@
1
+ import { filterSafeHeaders } from "../utils.mjs";
2
+ import { EvlogError, createError } from "../error.mjs";
3
+ import { createRequestLogger, initLogger, isEnabled, log as _log } from "../logger.mjs";
4
+ import { n as shouldLog, t as getServiceForPath } from "../routes-BNbrnm14.mjs";
5
+ import { AsyncLocalStorage } from "node:async_hooks";
6
+
7
+ //#region src/next/storage.ts
8
+ const evlogStorage = new AsyncLocalStorage();
9
+ /**
10
+ * Get the current request-scoped logger.
11
+ * Must be called inside a `withEvlog()` wrapper.
12
+ *
13
+ * @throws {Error} if called outside of `withEvlog()` context
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * export const POST = withEvlog(async (request) => {
18
+ * const log = useLogger()
19
+ * log.set({ user: { id: '123' } })
20
+ * return Response.json({ ok: true })
21
+ * })
22
+ * ```
23
+ */
24
+ function useLogger() {
25
+ const logger = evlogStorage.getStore();
26
+ if (!logger) throw new Error("[evlog] useLogger() was called outside of a withEvlog() context. Wrap your route handler or server action with withEvlog().");
27
+ return logger;
28
+ }
29
+
30
+ //#endregion
31
+ //#region src/next/handler.ts
32
+ const state = {
33
+ initialized: false,
34
+ options: {}
35
+ };
36
+ function configureHandler(options) {
37
+ state.options = options;
38
+ state.initialized = true;
39
+ initLogger({
40
+ enabled: options.enabled,
41
+ env: {
42
+ service: options.service,
43
+ ...options.env
44
+ },
45
+ pretty: options.pretty,
46
+ sampling: options.sampling,
47
+ stringify: options.stringify
48
+ });
49
+ }
50
+ function extractRequestInfo(request) {
51
+ const { method } = request;
52
+ const path = new URL(request.url, "http://localhost").pathname;
53
+ const headers = {};
54
+ request.headers.forEach((value, key) => {
55
+ headers[key] = value;
56
+ });
57
+ return {
58
+ method,
59
+ path,
60
+ headers: filterSafeHeaders(headers)
61
+ };
62
+ }
63
+ async function callEnrichAndDrain(emittedEvent, requestInfo, headers, responseStatus) {
64
+ if (!emittedEvent) return;
65
+ const { enrich, drain } = state.options;
66
+ const run = async () => {
67
+ if (enrich) {
68
+ const enrichCtx = {
69
+ event: emittedEvent,
70
+ request: requestInfo,
71
+ headers,
72
+ response: { status: responseStatus }
73
+ };
74
+ try {
75
+ await enrich(enrichCtx);
76
+ } catch (err) {
77
+ console.error("[evlog] enrich failed:", err);
78
+ }
79
+ }
80
+ if (drain) {
81
+ const drainCtx = {
82
+ event: emittedEvent,
83
+ request: requestInfo,
84
+ headers
85
+ };
86
+ try {
87
+ await drain(drainCtx);
88
+ } catch (err) {
89
+ console.error("[evlog] drain failed:", err);
90
+ }
91
+ }
92
+ };
93
+ try {
94
+ const { after } = await import("next/server");
95
+ if (typeof after === "function") {
96
+ after(run);
97
+ return;
98
+ }
99
+ } catch {}
100
+ run().catch(() => {});
101
+ }
102
+ /**
103
+ * Wrap a Next.js route handler or server action with evlog request-scoped logging.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * // Route handler
108
+ * export const POST = withEvlog(async (request: NextRequest) => {
109
+ * const log = useLogger()
110
+ * log.set({ user: { id: '123' } })
111
+ * return Response.json({ success: true })
112
+ * })
113
+ *
114
+ * // Server action
115
+ * export const checkout = withEvlog(async (formData: FormData) => {
116
+ * const log = useLogger()
117
+ * log.set({ action: 'checkout' })
118
+ * })
119
+ * ```
120
+ */
121
+ function createWithEvlog(options) {
122
+ configureHandler(options);
123
+ return function withEvlog(handler) {
124
+ return async (...args) => {
125
+ if (!isEnabled()) return await handler(...args);
126
+ const [firstArg] = args;
127
+ const isRequest = firstArg instanceof Request;
128
+ let method = "UNKNOWN";
129
+ let path = "/";
130
+ let headers = {};
131
+ let requestId = crypto.randomUUID();
132
+ if (isRequest) {
133
+ ({method, path, headers} = extractRequestInfo(firstArg));
134
+ const middlewareRequestId = firstArg.headers.get("x-request-id");
135
+ if (middlewareRequestId) requestId = middlewareRequestId;
136
+ }
137
+ if (!shouldLog(path, state.options.include, state.options.exclude)) return await handler(...args);
138
+ const logger = createRequestLogger({
139
+ method,
140
+ path,
141
+ requestId
142
+ });
143
+ const routeService = getServiceForPath(path, state.options.routes);
144
+ if (routeService) logger.set({ service: routeService });
145
+ if (isRequest) {
146
+ const startHeader = firstArg.headers.get("x-evlog-start");
147
+ if (startHeader) logger.set({ middlewareStart: Number(startHeader) });
148
+ }
149
+ try {
150
+ const result = await evlogStorage.run(logger, () => handler(...args));
151
+ let { status } = { status: 200 };
152
+ if (result instanceof Response) ({status} = result);
153
+ logger.set({ status });
154
+ let forceKeep = false;
155
+ if (state.options.keep) try {
156
+ const tailCtx = {
157
+ status,
158
+ path,
159
+ method,
160
+ context: logger.getContext(),
161
+ shouldKeep: false
162
+ };
163
+ await state.options.keep(tailCtx);
164
+ forceKeep = tailCtx.shouldKeep ?? false;
165
+ } catch (err) {
166
+ console.error("[evlog] keep callback failed:", err);
167
+ }
168
+ await callEnrichAndDrain(logger.emit({ _forceKeep: forceKeep }), {
169
+ method,
170
+ path,
171
+ requestId
172
+ }, headers, status);
173
+ return result;
174
+ } catch (error) {
175
+ logger.error(error instanceof Error ? error : new Error(String(error)));
176
+ const errorStatus = error.status ?? error.statusCode ?? 500;
177
+ logger.set({ status: errorStatus });
178
+ let forceKeep = false;
179
+ if (state.options.keep) try {
180
+ const tailCtx = {
181
+ status: errorStatus,
182
+ path,
183
+ method,
184
+ context: logger.getContext(),
185
+ shouldKeep: false
186
+ };
187
+ await state.options.keep(tailCtx);
188
+ forceKeep = tailCtx.shouldKeep ?? false;
189
+ } catch (err) {
190
+ console.error("[evlog] keep callback failed:", err);
191
+ }
192
+ await callEnrichAndDrain(logger.emit({ _forceKeep: forceKeep }), {
193
+ method,
194
+ path,
195
+ requestId
196
+ }, headers, errorStatus);
197
+ if (isRequest && error instanceof EvlogError) return Response.json(error.toJSON(), { status: error.status });
198
+ throw error;
199
+ }
200
+ };
201
+ };
202
+ }
203
+
204
+ //#endregion
205
+ //#region src/next/middleware.ts
206
+ /**
207
+ * Create an evlog middleware for Next.js.
208
+ * Sets `x-request-id` and `x-evlog-start` headers so `withEvlog()` can reuse them
209
+ * for timing consistency across the middleware -> handler chain.
210
+ *
211
+ * @example
212
+ * ```ts
213
+ * // middleware.ts
214
+ * import { evlogMiddleware } from 'evlog/next'
215
+ * export const middleware = evlogMiddleware()
216
+ * export const config = { matcher: ['/api/:path*'] }
217
+ * ```
218
+ */
219
+ function evlogMiddleware(config) {
220
+ return async (request) => {
221
+ const path = request.nextUrl.pathname;
222
+ if (!shouldLog(path, config?.include, config?.exclude)) {
223
+ const { NextResponse: nextResponse } = await import("next/server");
224
+ return nextResponse.next();
225
+ }
226
+ const requestId = request.headers.get("x-request-id") || crypto.randomUUID();
227
+ const requestHeaders = new Headers(request.headers);
228
+ requestHeaders.set("x-request-id", requestId);
229
+ requestHeaders.set("x-evlog-start", String(Date.now()));
230
+ const { NextResponse: nextResponse } = await import("next/server");
231
+ const response = nextResponse.next({ request: { headers: requestHeaders } });
232
+ response.headers.set("x-request-id", requestId);
233
+ return response;
234
+ };
235
+ }
236
+
237
+ //#endregion
238
+ //#region src/next/index.ts
239
+ /**
240
+ * Create an evlog instance configured for Next.js.
241
+ * Returns all helpers needed for server-side logging.
242
+ *
243
+ * @example
244
+ * ```ts
245
+ * // lib/evlog.ts
246
+ * import { createEvlog } from 'evlog/next'
247
+ * import { createAxiomDrain } from 'evlog/axiom'
248
+ * import { createDrainPipeline } from 'evlog/pipeline'
249
+ *
250
+ * const pipeline = createDrainPipeline({ batch: { size: 50 } })
251
+ *
252
+ * export const { withEvlog, useLogger, log, createEvlogError } = createEvlog({
253
+ * service: 'my-app',
254
+ * sampling: {
255
+ * rates: { info: 10 },
256
+ * keep: [{ status: 400 }, { duration: 1000 }],
257
+ * },
258
+ * drain: pipeline(createAxiomDrain({
259
+ * dataset: 'logs',
260
+ * token: process.env.AXIOM_TOKEN!,
261
+ * })),
262
+ * enrich: (ctx) => {
263
+ * ctx.event.deploymentId = process.env.VERCEL_DEPLOYMENT_ID
264
+ * },
265
+ * })
266
+ * ```
267
+ */
268
+ function createEvlog(options = {}) {
269
+ return {
270
+ withEvlog: createWithEvlog(options),
271
+ useLogger,
272
+ log: _log,
273
+ createError,
274
+ createEvlogError: createError
275
+ };
276
+ }
277
+
278
+ //#endregion
279
+ export { createError, createError as createEvlogError, createEvlog, evlogMiddleware, _log as log, useLogger };
280
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/next/storage.ts","../../src/next/handler.ts","../../src/next/middleware.ts","../../src/next/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport type { RequestLogger } from '../types'\n\nexport const evlogStorage = new AsyncLocalStorage<RequestLogger>()\n\n/**\n * Get the current request-scoped logger.\n * Must be called inside a `withEvlog()` wrapper.\n *\n * @throws {Error} if called outside of `withEvlog()` context\n *\n * @example\n * ```ts\n * export const POST = withEvlog(async (request) => {\n * const log = useLogger()\n * log.set({ user: { id: '123' } })\n * return Response.json({ ok: true })\n * })\n * ```\n */\nexport function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T> {\n const logger = evlogStorage.getStore()\n if (!logger) {\n throw new Error(\n '[evlog] useLogger() was called outside of a withEvlog() context. '\n + 'Wrap your route handler or server action with withEvlog().',\n )\n }\n return logger as RequestLogger<T>\n}\n","import type { DrainContext, EnrichContext, TailSamplingContext, WideEvent } from '../types'\nimport { createRequestLogger, initLogger, isEnabled } from '../logger'\nimport { filterSafeHeaders } from '../utils'\nimport { shouldLog, getServiceForPath } from '../shared/routes'\nimport { EvlogError } from '../error'\nimport type { NextEvlogOptions } from './types'\nimport { evlogStorage } from './storage'\n\ninterface WithEvlogState {\n initialized: boolean\n options: NextEvlogOptions\n}\n\nconst state: WithEvlogState = {\n initialized: false,\n options: {},\n}\n\nexport function configureHandler(options: NextEvlogOptions): void {\n state.options = options\n state.initialized = true\n\n // Don't pass drain to initLogger — the global drain fires inside emitWideEvent\n // which doesn't have request/header context. Instead, we call drain ourselves\n // in callEnrichAndDrain after enrich, with full context.\n initLogger({\n enabled: options.enabled,\n env: {\n service: options.service,\n ...options.env,\n },\n pretty: options.pretty,\n sampling: options.sampling,\n stringify: options.stringify,\n })\n}\n\nfunction extractRequestInfo(request: Request): { method: string, path: string, headers: Record<string, string> } {\n const { method } = request\n const url = new URL(request.url, 'http://localhost')\n const path = url.pathname\n\n const headers: Record<string, string> = {}\n request.headers.forEach((value, key) => {\n headers[key] = value\n })\n\n return { method, path, headers: filterSafeHeaders(headers) }\n}\n\nasync function callEnrichAndDrain(\n emittedEvent: WideEvent | null,\n requestInfo: { method: string, path: string, requestId: string },\n headers: Record<string, string>,\n responseStatus?: number,\n): Promise<void> {\n if (!emittedEvent) return\n\n const { enrich, drain } = state.options\n\n const run = async () => {\n if (enrich) {\n const enrichCtx: EnrichContext = {\n event: emittedEvent,\n request: requestInfo,\n headers,\n response: { status: responseStatus },\n }\n try {\n await enrich(enrichCtx)\n } catch (err) {\n console.error('[evlog] enrich failed:', err)\n }\n }\n\n if (drain) {\n const drainCtx: DrainContext = {\n event: emittedEvent,\n request: requestInfo,\n headers,\n }\n try {\n await drain(drainCtx)\n } catch (err) {\n console.error('[evlog] drain failed:', err)\n }\n }\n }\n\n // Use next/server after() if available to run enrich+drain after response\n try {\n const { after } = await import('next/server')\n if (typeof after === 'function') {\n after(run)\n return\n }\n } catch {\n // next/server not available or after() not exported — run inline\n }\n\n // Fallback: fire-and-forget (enrich still awaited for correctness)\n run().catch(() => {})\n}\n\n/**\n * Wrap a Next.js route handler or server action with evlog request-scoped logging.\n *\n * @example\n * ```ts\n * // Route handler\n * export const POST = withEvlog(async (request: NextRequest) => {\n * const log = useLogger()\n * log.set({ user: { id: '123' } })\n * return Response.json({ success: true })\n * })\n *\n * // Server action\n * export const checkout = withEvlog(async (formData: FormData) => {\n * const log = useLogger()\n * log.set({ action: 'checkout' })\n * })\n * ```\n */\nexport function createWithEvlog(options: NextEvlogOptions) {\n configureHandler(options)\n\n return function withEvlog<TArgs extends unknown[], TReturn>(\n handler: (...args: TArgs) => TReturn,\n ): (...args: TArgs) => Promise<Awaited<TReturn>> {\n return async (...args: TArgs): Promise<Awaited<TReturn>> => {\n if (!isEnabled()) {\n return await handler(...args) as Awaited<TReturn>\n }\n\n // Extract request info from first argument if it's a Request\n const [firstArg] = args\n const isRequest = firstArg instanceof Request\n\n let method = 'UNKNOWN'\n let path = '/'\n let headers: Record<string, string> = {}\n let requestId = crypto.randomUUID()\n\n if (isRequest) {\n ({ method, path, headers } = extractRequestInfo(firstArg))\n\n // Reuse request-id from middleware if present\n const middlewareRequestId = firstArg.headers.get('x-request-id')\n if (middlewareRequestId) requestId = middlewareRequestId\n }\n\n // Check include/exclude patterns\n if (!shouldLog(path, state.options.include, state.options.exclude)) {\n return await handler(...args) as Awaited<TReturn>\n }\n\n const logger = createRequestLogger({ method, path, requestId })\n\n // Apply route-based service configuration\n const routeService = getServiceForPath(path, state.options.routes)\n if (routeService) {\n logger.set({ service: routeService })\n }\n\n // Apply start time from middleware if present\n if (isRequest) {\n const startHeader = firstArg.headers.get('x-evlog-start')\n if (startHeader) {\n logger.set({ middlewareStart: Number(startHeader) })\n }\n }\n\n try {\n const result = await evlogStorage.run(logger, () => handler(...args))\n\n // Extract response status\n let { status } = { status: 200 }\n if (result instanceof Response) {\n ({ status } = result)\n }\n logger.set({ status })\n\n // Build tail sampling context and call keep callback\n let forceKeep = false\n if (state.options.keep) {\n try {\n const tailCtx: TailSamplingContext = {\n status,\n path,\n method,\n context: logger.getContext(),\n shouldKeep: false,\n }\n await state.options.keep(tailCtx)\n forceKeep = tailCtx.shouldKeep ?? false\n } catch (err) {\n console.error('[evlog] keep callback failed:', err)\n }\n }\n\n const emittedEvent = logger.emit({ _forceKeep: forceKeep })\n await callEnrichAndDrain(emittedEvent, { method, path, requestId }, headers, status)\n\n return result as Awaited<TReturn>\n } catch (error) {\n logger.error(error instanceof Error ? error : new Error(String(error)))\n\n const errorStatus = (error as { status?: number }).status\n ?? (error as { statusCode?: number }).statusCode\n ?? 500\n logger.set({ status: errorStatus })\n\n // Build tail sampling context and call keep callback\n let forceKeep = false\n if (state.options.keep) {\n try {\n const tailCtx: TailSamplingContext = {\n status: errorStatus,\n path,\n method,\n context: logger.getContext(),\n shouldKeep: false,\n }\n await state.options.keep(tailCtx)\n forceKeep = tailCtx.shouldKeep ?? false\n } catch (err) {\n console.error('[evlog] keep callback failed:', err)\n }\n }\n\n const emittedEvent = logger.emit({ _forceKeep: forceKeep })\n await callEnrichAndDrain(emittedEvent, { method, path, requestId }, headers, errorStatus)\n\n // Return structured JSON response for EvlogErrors (like H3 does for Nuxt)\n if (isRequest && error instanceof EvlogError) {\n return Response.json(error.toJSON(), { status: error.status }) as Awaited<TReturn>\n }\n\n throw error\n }\n }\n }\n}\n","import { shouldLog } from '../shared/routes'\nimport type { EvlogMiddlewareConfig } from './types'\n\ntype NextRequest = {\n nextUrl: { pathname: string }\n headers: { get(name: string): string | null }\n}\n\ntype NextResponse = {\n headers: { set(name: string, value: string): void }\n}\n\ntype NextResponseStatic = {\n next(options?: { request?: { headers: Headers } }): NextResponse\n}\n\n/**\n * Create an evlog middleware for Next.js.\n * Sets `x-request-id` and `x-evlog-start` headers so `withEvlog()` can reuse them\n * for timing consistency across the middleware -> handler chain.\n *\n * @example\n * ```ts\n * // middleware.ts\n * import { evlogMiddleware } from 'evlog/next'\n * export const middleware = evlogMiddleware()\n * export const config = { matcher: ['/api/:path*'] }\n * ```\n */\nexport function evlogMiddleware(config?: EvlogMiddlewareConfig) {\n return async (request: NextRequest) => {\n const path = request.nextUrl.pathname\n\n // Check include/exclude patterns\n if (!shouldLog(path, config?.include, config?.exclude)) {\n const { NextResponse: nextResponse } = await import('next/server') as { NextResponse: NextResponseStatic }\n return nextResponse.next()\n }\n\n // Generate or reuse request ID\n const existingId = request.headers.get('x-request-id')\n const requestId = existingId || crypto.randomUUID()\n\n // Forward modified headers to the route handler\n const requestHeaders = new Headers(request.headers as HeadersInit)\n\n requestHeaders.set('x-request-id', requestId)\n requestHeaders.set('x-evlog-start', String(Date.now()))\n\n const { NextResponse: nextResponse } = await import('next/server') as { NextResponse: NextResponseStatic }\n const response = nextResponse.next({\n request: { headers: requestHeaders },\n })\n\n // Also set on response for downstream consumers\n response.headers.set('x-request-id', requestId)\n\n return response\n }\n}\n","import { log } from '../logger'\nimport { createError, createEvlogError } from '../error'\nimport type { NextEvlogOptions } from './types'\nimport { createWithEvlog } from './handler'\nimport { useLogger } from './storage'\n\nexport type { NextEvlogOptions, EvlogMiddlewareConfig } from './types'\n\nexport { evlogMiddleware } from './middleware'\nexport { useLogger } from './storage'\nexport { log } from '../logger'\nexport { createError, createEvlogError } from '../error'\n\n/**\n * Create an evlog instance configured for Next.js.\n * Returns all helpers needed for server-side logging.\n *\n * @example\n * ```ts\n * // lib/evlog.ts\n * import { createEvlog } from 'evlog/next'\n * import { createAxiomDrain } from 'evlog/axiom'\n * import { createDrainPipeline } from 'evlog/pipeline'\n *\n * const pipeline = createDrainPipeline({ batch: { size: 50 } })\n *\n * export const { withEvlog, useLogger, log, createEvlogError } = createEvlog({\n * service: 'my-app',\n * sampling: {\n * rates: { info: 10 },\n * keep: [{ status: 400 }, { duration: 1000 }],\n * },\n * drain: pipeline(createAxiomDrain({\n * dataset: 'logs',\n * token: process.env.AXIOM_TOKEN!,\n * })),\n * enrich: (ctx) => {\n * ctx.event.deploymentId = process.env.VERCEL_DEPLOYMENT_ID\n * },\n * })\n * ```\n */\nexport function createEvlog(options: NextEvlogOptions = {}) {\n const withEvlog = createWithEvlog(options)\n\n return {\n withEvlog,\n useLogger,\n log,\n createError,\n createEvlogError,\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { i as serializeEvlogErrorResponse, r as resolveEvlogError, t as extractErrorStatus } from "../nitro-D57TWGyN.mjs";
1
+ import { n as resolveEvlogError, r as serializeEvlogErrorResponse, t as extractErrorStatus } from "../nitro-Da8tEfJ3.mjs";
2
2
  import { getRequestURL, send, setResponseHeader, setResponseStatus } from "h3";
3
3
  import { defineNitroErrorHandler } from "nitropack/runtime/internal/error/utils";
4
4
 
@@ -1,5 +1,5 @@
1
1
  import { useLogger } from "../runtime/server/useLogger.mjs";
2
- import { t as NitroModuleOptions } from "../nitro-D81NBVPi.mjs";
2
+ import { t as NitroModuleOptions } from "../nitro-CrFBjY1Y.mjs";
3
3
 
4
4
  //#region src/nitro/module.d.ts
5
5
  declare function evlog(options?: NitroModuleOptions): {
@@ -1,6 +1,7 @@
1
1
  import { filterSafeHeaders } from "../utils.mjs";
2
2
  import { createRequestLogger, initLogger, isEnabled } from "../logger.mjs";
3
- import { a as shouldLog, n as getServiceForPath, t as extractErrorStatus } from "../nitro-D57TWGyN.mjs";
3
+ import { n as shouldLog, t as getServiceForPath } from "../routes-BNbrnm14.mjs";
4
+ import { t as extractErrorStatus } from "../nitro-Da8tEfJ3.mjs";
4
5
  import { defineNitroPlugin } from "nitropack/runtime/internal/plugin";
5
6
  import { getHeaders } from "h3";
6
7
 
@@ -61,6 +62,7 @@ async function callEnrichAndDrain(nitroApp, emittedEvent, event) {
61
62
  });
62
63
  const waitUntilCtx = event.context.cloudflare?.context ?? event.context;
63
64
  if (typeof waitUntilCtx?.waitUntil === "function") waitUntilCtx.waitUntil(drainPromise);
65
+ else await drainPromise;
64
66
  }
65
67
  var plugin_default = defineNitroPlugin(async (nitroApp) => {
66
68
  let evlogConfig;
@@ -1 +1 @@
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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"}
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, 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 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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"}
@@ -1,4 +1,4 @@
1
- import { i as serializeEvlogErrorResponse, r as resolveEvlogError, t as extractErrorStatus } from "../../nitro-D57TWGyN.mjs";
1
+ import { n as resolveEvlogError, r as serializeEvlogErrorResponse, t as extractErrorStatus } from "../../nitro-Da8tEfJ3.mjs";
2
2
  import { defineErrorHandler } from "nitro";
3
3
  import { parseURL } from "ufo";
4
4
 
@@ -1,4 +1,5 @@
1
- import "../../nitro-D81NBVPi.mjs";
1
+ import "../../nitro-CrFBjY1Y.mjs";
2
2
  import evlog from "./module.mjs";
3
3
  import { useLogger } from "./useLogger.mjs";
4
- export { evlog as default, useLogger };
4
+ import { evlogErrorHandler } from "./middleware.mjs";
5
+ export { evlog as default, evlogErrorHandler, useLogger };
@@ -1,4 +1,5 @@
1
1
  import evlog from "./module.mjs";
2
2
  import { useLogger } from "./useLogger.mjs";
3
+ import { evlogErrorHandler } from "./middleware.mjs";
3
4
 
4
- export { evlog as default, useLogger };
5
+ export { evlog as default, evlogErrorHandler, useLogger };
@@ -0,0 +1,25 @@
1
+ //#region src/nitro-v3/middleware.d.ts
2
+ /**
3
+ * Server middleware handler that catches EvlogError and returns a structured JSON response.
4
+ *
5
+ * Frameworks like TanStack Start catch thrown errors before Nitro's error handler,
6
+ * stripping the `data` field (containing `why`, `fix`, `link`). This middleware
7
+ * intercepts EvlogErrors first, attaches them to the wide event, and throws a
8
+ * `Response` that the framework passes through directly.
9
+ *
10
+ * @example TanStack Start
11
+ * ```ts
12
+ * import { createMiddleware } from '@tanstack/react-start'
13
+ * import { evlogErrorHandler } from 'evlog/nitro/v3'
14
+ *
15
+ * const evlogMiddleware = createMiddleware().server(evlogErrorHandler)
16
+ *
17
+ * export const Route = createRootRoute({
18
+ * server: { middleware: [evlogMiddleware] },
19
+ * })
20
+ * ```
21
+ */
22
+ declare function evlogErrorHandler<T>(next: (...args: any[]) => Promise<T>): Promise<T>;
23
+ //#endregion
24
+ export { evlogErrorHandler };
25
+ //# sourceMappingURL=middleware.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.mts","names":[],"sources":["../../../src/nitro-v3/middleware.ts"],"mappings":";;AAyBA;;;;;;;;;;;;;;;;;;;iBAAsB,iBAAA,GAAA,CAAqB,IAAA,MAAU,IAAA,YAAgB,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA"}
@@ -0,0 +1,45 @@
1
+ import { EvlogError } from "../../error.mjs";
2
+
3
+ //#region src/nitro-v3/middleware.ts
4
+ /**
5
+ * Server middleware handler that catches EvlogError and returns a structured JSON response.
6
+ *
7
+ * Frameworks like TanStack Start catch thrown errors before Nitro's error handler,
8
+ * stripping the `data` field (containing `why`, `fix`, `link`). This middleware
9
+ * intercepts EvlogErrors first, attaches them to the wide event, and throws a
10
+ * `Response` that the framework passes through directly.
11
+ *
12
+ * @example TanStack Start
13
+ * ```ts
14
+ * import { createMiddleware } from '@tanstack/react-start'
15
+ * import { evlogErrorHandler } from 'evlog/nitro/v3'
16
+ *
17
+ * const evlogMiddleware = createMiddleware().server(evlogErrorHandler)
18
+ *
19
+ * export const Route = createRootRoute({
20
+ * server: { middleware: [evlogMiddleware] },
21
+ * })
22
+ * ```
23
+ */
24
+ async function evlogErrorHandler(next) {
25
+ try {
26
+ return await next();
27
+ } catch (error) {
28
+ if (error instanceof EvlogError || error && typeof error === "object" && error.name === "EvlogError") {
29
+ const evlogError = error;
30
+ try {
31
+ const { useRequest } = await import("nitro/context");
32
+ (useRequest().context?.log)?.error(evlogError);
33
+ } catch {}
34
+ throw new Response(JSON.stringify(evlogError.toJSON()), {
35
+ status: evlogError.status || 500,
36
+ headers: { "Content-Type": "application/json" }
37
+ });
38
+ }
39
+ throw error;
40
+ }
41
+ }
42
+
43
+ //#endregion
44
+ export { evlogErrorHandler };
45
+ //# sourceMappingURL=middleware.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.mjs","names":[],"sources":["../../../src/nitro-v3/middleware.ts"],"sourcesContent":["import type { RequestLogger } from '../types'\nimport { EvlogError } from '../error'\nimport { serializeEvlogErrorResponse } from '../nitro'\n\n/**\n * Server middleware handler that catches EvlogError and returns a structured JSON response.\n *\n * Frameworks like TanStack Start catch thrown errors before Nitro's error handler,\n * stripping the `data` field (containing `why`, `fix`, `link`). This middleware\n * intercepts EvlogErrors first, attaches them to the wide event, and throws a\n * `Response` that the framework passes through directly.\n *\n * @example TanStack Start\n * ```ts\n * import { createMiddleware } from '@tanstack/react-start'\n * import { evlogErrorHandler } from 'evlog/nitro/v3'\n *\n * const evlogMiddleware = createMiddleware().server(evlogErrorHandler)\n *\n * export const Route = createRootRoute({\n * server: { middleware: [evlogMiddleware] },\n * })\n * ```\n */\n\nexport async function evlogErrorHandler<T>(next: (...args: any[]) => Promise<T>): Promise<T> {\n try {\n return await next()\n } catch (error: unknown) {\n if (error instanceof EvlogError || (error && typeof error === 'object' && (error as Error).name === 'EvlogError')) {\n const evlogError = error as EvlogError\n\n try {\n const { useRequest } = await import('nitro/context')\n const req = useRequest()\n const log = req.context?.log as RequestLogger | undefined\n log?.error(evlogError)\n } catch {\n // ignore\n }\n\n // Throw as Response so frameworks (TanStack Start, SolidStart, etc.) pass it through\n throw new Response(JSON.stringify(evlogError.toJSON()), {\n status: evlogError.status || 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n throw error\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAyBA,eAAsB,kBAAqB,MAAkD;AAC3F,KAAI;AACF,SAAO,MAAM,MAAM;UACZ,OAAgB;AACvB,MAAI,iBAAiB,cAAe,SAAS,OAAO,UAAU,YAAa,MAAgB,SAAS,cAAe;GACjH,MAAM,aAAa;AAEnB,OAAI;IACF,MAAM,EAAE,eAAe,MAAM,OAAO;AAGpC,KAFY,YAAY,CACR,SAAS,MACpB,MAAM,WAAW;WAChB;AAKR,SAAM,IAAI,SAAS,KAAK,UAAU,WAAW,QAAQ,CAAC,EAAE;IACtD,QAAQ,WAAW,UAAU;IAC7B,SAAS,EAAE,gBAAgB,oBAAoB;IAChD,CAAC;;AAEJ,QAAM"}
@@ -1,4 +1,4 @@
1
- import { t as NitroModuleOptions } from "../../nitro-D81NBVPi.mjs";
1
+ import { t as NitroModuleOptions } from "../../nitro-CrFBjY1Y.mjs";
2
2
 
3
3
  //#region src/nitro-v3/module.d.ts
4
4
  declare function evlog(options?: NitroModuleOptions): {
@@ -1,6 +1,7 @@
1
1
  import { filterSafeHeaders } from "../../utils.mjs";
2
2
  import { createRequestLogger, initLogger, isEnabled } from "../../logger.mjs";
3
- import { a as shouldLog, n as getServiceForPath, t as extractErrorStatus } from "../../nitro-D57TWGyN.mjs";
3
+ import { n as shouldLog, t as getServiceForPath } from "../../routes-BNbrnm14.mjs";
4
+ import { t as extractErrorStatus } from "../../nitro-Da8tEfJ3.mjs";
4
5
  import { definePlugin } from "nitro";
5
6
  import { useRuntimeConfig } from "nitro/runtime-config";
6
7
  import { parseURL } from "ufo";
@@ -40,7 +41,7 @@ function buildHookContext(event, res) {
40
41
  }
41
42
  };
42
43
  }
43
- function callDrainHook(hooks, emittedEvent, event, hookContext) {
44
+ async function callDrainHook(hooks, emittedEvent, event, hookContext) {
44
45
  if (!emittedEvent) return;
45
46
  let drainPromise;
46
47
  try {
@@ -48,11 +49,15 @@ function callDrainHook(hooks, emittedEvent, event, hookContext) {
48
49
  event: emittedEvent,
49
50
  request: hookContext.request,
50
51
  headers: hookContext.headers
52
+ })?.catch?.((err) => {
53
+ console.error("[evlog] drain failed:", err);
51
54
  });
52
55
  } catch (err) {
53
56
  console.error("[evlog] drain failed:", err);
54
57
  }
55
- if (drainPromise && typeof event.req.waitUntil === "function") event.req.waitUntil(drainPromise);
58
+ if (!drainPromise) return;
59
+ if (typeof event.req.waitUntil === "function") event.req.waitUntil(drainPromise);
60
+ else await drainPromise;
56
61
  }
57
62
  async function callEnrichAndDrain(hooks, emittedEvent, event, res) {
58
63
  if (!emittedEvent) return;
@@ -65,7 +70,7 @@ async function callEnrichAndDrain(hooks, emittedEvent, event, res) {
65
70
  } catch (err) {
66
71
  console.error("[evlog] enrich failed:", err);
67
72
  }
68
- callDrainHook(hooks, emittedEvent, event, hookContext);
73
+ await callDrainHook(hooks, emittedEvent, event, hookContext);
69
74
  }
70
75
  /**
71
76
  * Nitro v3 plugin entry point.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.mjs","names":[],"sources":["../../../src/nitro-v3/plugin.ts"],"sourcesContent":["import { definePlugin } from 'nitro'\nimport { useRuntimeConfig } from 'nitro/runtime-config'\nimport type { CaptureError } from 'nitro/types'\nimport type { HTTPEvent } from 'nitro/h3'\nimport { parseURL } from 'ufo'\nimport { createRequestLogger, initLogger, isEnabled } from '../logger'\nimport { shouldLog, getServiceForPath, extractErrorStatus } from '../nitro'\nimport type { EvlogConfig } from '../nitro'\nimport type { EnrichContext, RequestLogger, TailSamplingContext, WideEvent } from '../types'\nimport { filterSafeHeaders } from '../utils'\n\n// Nitro v3 doesn't fully export hook types yet\n// https://github.com/nitrojs/nitro/blob/8882bc9e1dbf2d342e73097f22a2156f70f50575/src/types/runtime/nitro.ts#L48-L53\ninterface NitroV3Hooks {\n close: () => void\n error: CaptureError\n request: (event: HTTPEvent) => void | Promise<void>\n response: (res: Response, event: HTTPEvent) => void | Promise<void>\n 'evlog:emit:keep': (ctx: TailSamplingContext) => void | Promise<void>\n 'evlog:enrich': (ctx: EnrichContext) => void | Promise<void>\n 'evlog:drain': (ctx: { event: WideEvent; request?: { method?: string; path: string; requestId?: string }; headers?: Record<string, string> }) => void | Promise<void>\n}\n\ntype Hooks = {\n hook: <T extends keyof NitroV3Hooks>(name: T, listener: NitroV3Hooks[T]) => void\n callHook: <T extends keyof NitroV3Hooks>(name: T, ...args: Parameters<NitroV3Hooks[T]>) => Promise<void>\n}\n\nfunction getContext(event: HTTPEvent): Record<string, unknown> {\n if (!event.req.context) {\n event.req.context = {}\n }\n return event.req.context\n}\n\nfunction getSafeRequestHeaders(event: HTTPEvent): Record<string, string> {\n const headers: Record<string, string> = {}\n event.req.headers.forEach((value, key) => {\n headers[key] = value\n })\n return filterSafeHeaders(headers)\n}\n\nfunction getSafeResponseHeaders(res: Response): Record<string, string> | undefined {\n const headers: Record<string, string> = {}\n res.headers.forEach((value, key) => {\n headers[key] = value\n })\n if (Object.keys(headers).length === 0) return undefined\n return filterSafeHeaders(headers)\n}\n\nfunction buildHookContext(\n event: HTTPEvent,\n res?: Response,\n): Omit<EnrichContext, 'event'> {\n const { pathname } = parseURL(event.req.url)\n const responseHeaders = res ? getSafeResponseHeaders(res) : undefined\n return {\n request: { method: event.req.method, path: pathname },\n headers: getSafeRequestHeaders(event),\n response: {\n status: res?.status ?? 200,\n headers: responseHeaders,\n },\n }\n}\n\nfunction callDrainHook(\n hooks: Hooks,\n emittedEvent: WideEvent | null,\n event: HTTPEvent,\n hookContext: Omit<EnrichContext, 'event'>,\n): void {\n if (!emittedEvent) return\n let drainPromise: Promise<any> | undefined\n try {\n drainPromise = hooks.callHook('evlog:drain', {\n event: emittedEvent,\n request: hookContext.request,\n headers: hookContext.headers,\n })\n } catch (err) {\n console.error('[evlog] drain failed:', err)\n }\n\n // Use waitUntil if available (srvx native — Cloudflare Workers, Vercel Edge, etc.)\n // This ensures drains complete before the runtime terminates\n if (drainPromise && typeof event.req.waitUntil === 'function') {\n event.req.waitUntil(drainPromise)\n }\n}\n\nasync function callEnrichAndDrain(\n hooks: Hooks,\n emittedEvent: WideEvent | null,\n event: HTTPEvent,\n res?: Response,\n): Promise<void> {\n if (!emittedEvent) return\n\n const hookContext = buildHookContext(event, res)\n\n try {\n await hooks.callHook('evlog:enrich', { event: emittedEvent, ...hookContext })\n } catch (err) {\n console.error('[evlog] enrich failed:', err)\n }\n\n callDrainHook(hooks, emittedEvent, event, hookContext)\n}\n\n/**\n * Nitro v3 plugin entry point.\n *\n * Usage in Nitro v3:\n * ```ts\n * // plugins/evlog.ts\n * export { default } from 'evlog/nitro/v3'\n * ```\n */\nexport default definePlugin((nitroApp) => {\n // In production builds the plugin is bundled and useRuntimeConfig()\n // resolves the virtual module correctly. In dev mode the plugin is\n // loaded externally so useRuntimeConfig() returns a stub — fall back\n // to the env var bridge set by the module.\n const evlogConfig = (useRuntimeConfig().evlog ?? (process.env.__EVLOG_CONFIG ? JSON.parse(process.env.__EVLOG_CONFIG) : undefined)) as EvlogConfig | undefined\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 const hooks = nitroApp.hooks as unknown as Hooks\n\n hooks.hook('request', (event) => {\n const { pathname } = parseURL(event.req.url)\n\n // Skip logging for routes not matching include/exclude patterns\n if (!shouldLog(pathname, evlogConfig?.include, evlogConfig?.exclude)) {\n return\n }\n\n const ctx = getContext(event)\n\n // Store start time for duration calculation in tail sampling\n ctx._evlogStartTime = Date.now()\n\n let requestIdOverride: string | undefined = undefined\n if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') {\n const cfRay = event.req.headers.get('cf-ray')\n if (cfRay) requestIdOverride = cfRay\n }\n\n const log = createRequestLogger({\n method: event.req.method,\n path: pathname,\n requestId: requestIdOverride || ctx.requestId as string | undefined || crypto.randomUUID(),\n })\n\n // Apply route-based service configuration if a matching route is found\n const routeService = getServiceForPath(pathname, evlogConfig?.routes)\n if (routeService) {\n log.set({ service: routeService })\n }\n\n ctx.log = log\n })\n\n hooks.hook('response', async (res, event) => {\n const ctx = event.req.context\n // Skip if already emitted by error hook\n if (ctx?._evlogEmitted) return\n\n const log = ctx?.log as RequestLogger | undefined\n if (!log || !ctx) return\n\n const { status } = res\n log.set({ status })\n\n const startTime = ctx._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const { pathname } = parseURL(event.req.url)\n\n const tailCtx: TailSamplingContext = {\n status,\n duration: durationMs,\n path: pathname,\n method: event.req.method,\n context: log.getContext(),\n shouldKeep: false,\n }\n\n await hooks.callHook('evlog:emit:keep', tailCtx)\n\n const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(hooks, emittedEvent, event, res)\n })\n\n hooks.hook('error', async (error, { event }) => {\n if (!event) return\n const e = event as HTTPEvent\n\n const ctx = e.req.context\n const log = ctx?.log as RequestLogger | undefined\n if (!log || !ctx) return\n\n // Check if error.cause is an EvlogError (thrown errors get wrapped in HTTPError by nitro)\n const actualError = (error.cause as Error)?.name === 'EvlogError' \n ? error.cause as Error \n : error as Error\n\n log.error(actualError)\n\n const errorStatus = extractErrorStatus(actualError)\n log.set({ status: errorStatus })\n\n const { pathname } = parseURL(e.req.url)\n const startTime = ctx._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: pathname,\n method: e.req.method,\n context: log.getContext(),\n shouldKeep: false,\n }\n\n await hooks.callHook('evlog:emit:keep', tailCtx)\n\n ctx._evlogEmitted = true\n\n const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(hooks, emittedEvent, e)\n })\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.mjs","names":[],"sources":["../../../src/nitro-v3/plugin.ts"],"sourcesContent":["import { definePlugin } from 'nitro'\nimport { useRuntimeConfig } from 'nitro/runtime-config'\nimport type { CaptureError } from 'nitro/types'\nimport type { HTTPEvent } from 'nitro/h3'\nimport { parseURL } from 'ufo'\nimport { createRequestLogger, initLogger, isEnabled } from '../logger'\nimport { shouldLog, getServiceForPath, extractErrorStatus } from '../nitro'\nimport type { EvlogConfig } from '../nitro'\nimport type { EnrichContext, RequestLogger, TailSamplingContext, WideEvent } from '../types'\nimport { filterSafeHeaders } from '../utils'\n\n// Nitro v3 doesn't fully export hook types yet\n// https://github.com/nitrojs/nitro/blob/8882bc9e1dbf2d342e73097f22a2156f70f50575/src/types/runtime/nitro.ts#L48-L53\ninterface NitroV3Hooks {\n close: () => void\n error: CaptureError\n request: (event: HTTPEvent) => void | Promise<void>\n response: (res: Response, event: HTTPEvent) => void | Promise<void>\n 'evlog:emit:keep': (ctx: TailSamplingContext) => void | Promise<void>\n 'evlog:enrich': (ctx: EnrichContext) => void | Promise<void>\n 'evlog:drain': (ctx: { event: WideEvent; request?: { method?: string; path: string; requestId?: string }; headers?: Record<string, string> }) => void | Promise<void>\n}\n\ntype Hooks = {\n hook: <T extends keyof NitroV3Hooks>(name: T, listener: NitroV3Hooks[T]) => void\n callHook: <T extends keyof NitroV3Hooks>(name: T, ...args: Parameters<NitroV3Hooks[T]>) => Promise<void>\n}\n\nfunction getContext(event: HTTPEvent): Record<string, unknown> {\n if (!event.req.context) {\n event.req.context = {}\n }\n return event.req.context\n}\n\nfunction getSafeRequestHeaders(event: HTTPEvent): Record<string, string> {\n const headers: Record<string, string> = {}\n event.req.headers.forEach((value, key) => {\n headers[key] = value\n })\n return filterSafeHeaders(headers)\n}\n\nfunction getSafeResponseHeaders(res: Response): Record<string, string> | undefined {\n const headers: Record<string, string> = {}\n res.headers.forEach((value, key) => {\n headers[key] = value\n })\n if (Object.keys(headers).length === 0) return undefined\n return filterSafeHeaders(headers)\n}\n\nfunction buildHookContext(\n event: HTTPEvent,\n res?: Response,\n): Omit<EnrichContext, 'event'> {\n const { pathname } = parseURL(event.req.url)\n const responseHeaders = res ? getSafeResponseHeaders(res) : undefined\n return {\n request: { method: event.req.method, path: pathname },\n headers: getSafeRequestHeaders(event),\n response: {\n status: res?.status ?? 200,\n headers: responseHeaders,\n },\n }\n}\n\nasync function callDrainHook(\n hooks: Hooks,\n emittedEvent: WideEvent | null,\n event: HTTPEvent,\n hookContext: Omit<EnrichContext, 'event'>,\n): Promise<void> {\n if (!emittedEvent) return\n\n let drainPromise: Promise<unknown> | undefined\n try {\n const result = hooks.callHook('evlog:drain', {\n event: emittedEvent,\n request: hookContext.request,\n headers: hookContext.headers,\n })\n drainPromise = result?.catch?.((err: unknown) => {\n console.error('[evlog] drain failed:', err)\n })\n } catch (err) {\n console.error('[evlog] drain failed:', err)\n }\n\n if (!drainPromise) return\n\n // Use waitUntil if available (srvx native — Cloudflare Workers, Vercel Edge, etc.)\n // This keeps the runtime alive for background work without blocking the response\n if (typeof event.req.waitUntil === 'function') {\n event.req.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 the\n // response hook (response already sent); on the error path it may run\n // before the error response is finalized.\n await drainPromise\n }\n}\n\nasync function callEnrichAndDrain(\n hooks: Hooks,\n emittedEvent: WideEvent | null,\n event: HTTPEvent,\n res?: Response,\n): Promise<void> {\n if (!emittedEvent) return\n\n const hookContext = buildHookContext(event, res)\n\n try {\n await hooks.callHook('evlog:enrich', { event: emittedEvent, ...hookContext })\n } catch (err) {\n console.error('[evlog] enrich failed:', err)\n }\n\n await callDrainHook(hooks, emittedEvent, event, hookContext)\n}\n\n/**\n * Nitro v3 plugin entry point.\n *\n * Usage in Nitro v3:\n * ```ts\n * // plugins/evlog.ts\n * export { default } from 'evlog/nitro/v3'\n * ```\n */\nexport default definePlugin((nitroApp) => {\n // In production builds the plugin is bundled and useRuntimeConfig()\n // resolves the virtual module correctly. In dev mode the plugin is\n // loaded externally so useRuntimeConfig() returns a stub — fall back\n // to the env var bridge set by the module.\n const evlogConfig = (useRuntimeConfig().evlog ?? (process.env.__EVLOG_CONFIG ? JSON.parse(process.env.__EVLOG_CONFIG) : undefined)) as EvlogConfig | undefined\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 const hooks = nitroApp.hooks as unknown as Hooks\n\n hooks.hook('request', (event) => {\n const { pathname } = parseURL(event.req.url)\n\n // Skip logging for routes not matching include/exclude patterns\n if (!shouldLog(pathname, evlogConfig?.include, evlogConfig?.exclude)) {\n return\n }\n\n const ctx = getContext(event)\n\n // Store start time for duration calculation in tail sampling\n ctx._evlogStartTime = Date.now()\n\n let requestIdOverride: string | undefined = undefined\n if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') {\n const cfRay = event.req.headers.get('cf-ray')\n if (cfRay) requestIdOverride = cfRay\n }\n\n const log = createRequestLogger({\n method: event.req.method,\n path: pathname,\n requestId: requestIdOverride || ctx.requestId as string | undefined || crypto.randomUUID(),\n })\n\n // Apply route-based service configuration if a matching route is found\n const routeService = getServiceForPath(pathname, evlogConfig?.routes)\n if (routeService) {\n log.set({ service: routeService })\n }\n\n ctx.log = log\n })\n\n hooks.hook('response', async (res, event) => {\n const ctx = event.req.context\n // Skip if already emitted by error hook\n if (ctx?._evlogEmitted) return\n\n const log = ctx?.log as RequestLogger | undefined\n if (!log || !ctx) return\n\n const { status } = res\n log.set({ status })\n\n const startTime = ctx._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const { pathname } = parseURL(event.req.url)\n\n const tailCtx: TailSamplingContext = {\n status,\n duration: durationMs,\n path: pathname,\n method: event.req.method,\n context: log.getContext(),\n shouldKeep: false,\n }\n\n await hooks.callHook('evlog:emit:keep', tailCtx)\n\n const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(hooks, emittedEvent, event, res)\n })\n\n hooks.hook('error', async (error, { event }) => {\n if (!event) return\n const e = event as HTTPEvent\n\n const ctx = e.req.context\n const log = ctx?.log as RequestLogger | undefined\n if (!log || !ctx) return\n\n // Check if error.cause is an EvlogError (thrown errors get wrapped in HTTPError by nitro)\n const actualError = (error.cause as Error)?.name === 'EvlogError' \n ? error.cause as Error \n : error as Error\n\n log.error(actualError)\n\n const errorStatus = extractErrorStatus(actualError)\n log.set({ status: errorStatus })\n\n const { pathname } = parseURL(e.req.url)\n const startTime = ctx._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: pathname,\n method: e.req.method,\n context: log.getContext(),\n shouldKeep: false,\n }\n\n await hooks.callHook('evlog:emit:keep', tailCtx)\n\n ctx._evlogEmitted = true\n\n const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(hooks, emittedEvent, e)\n })\n})\n"],"mappings":"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"}
@@ -39,4 +39,4 @@ interface NitroModuleOptions {
39
39
  }
40
40
  //#endregion
41
41
  export { NitroModuleOptions as t };
42
- //# sourceMappingURL=nitro-D81NBVPi.d.mts.map
42
+ //# sourceMappingURL=nitro-CrFBjY1Y.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nitro-CrFBjY1Y.d.mts","names":[],"sources":["../src/nitro.ts"],"mappings":";;;UAIiB,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"}
@@ -0,0 +1,39 @@
1
+ //#region src/nitro.ts
2
+ /**
3
+ * Resolve an EvlogError from an error or its cause chain.
4
+ * Both Nitro v2 (h3) and v3 wrap thrown errors — this unwraps them.
5
+ */
6
+ function resolveEvlogError(error) {
7
+ if (error.name === "EvlogError") return error;
8
+ if (error.cause?.name === "EvlogError") return error.cause;
9
+ return null;
10
+ }
11
+ /**
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
+ * Build a standard evlog error JSON response body.
19
+ * Used by both v2 and v3 error handlers to ensure consistent shape.
20
+ */
21
+ function serializeEvlogErrorResponse(error, url) {
22
+ const status = extractErrorStatus(error);
23
+ const { data } = error;
24
+ const statusMessage = error.statusMessage || error.message;
25
+ return {
26
+ url,
27
+ status,
28
+ statusCode: status,
29
+ statusText: statusMessage,
30
+ statusMessage,
31
+ message: error.message,
32
+ error: true,
33
+ ...data !== void 0 && { data }
34
+ };
35
+ }
36
+
37
+ //#endregion
38
+ export { resolveEvlogError as n, serializeEvlogErrorResponse as r, extractErrorStatus as t };
39
+ //# sourceMappingURL=nitro-Da8tEfJ3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nitro-Da8tEfJ3.mjs","names":[],"sources":["../src/nitro.ts"],"sourcesContent":["import type { EnvironmentContext, RouteConfig, SamplingConfig } from './types'\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\n/**\n * Extract HTTP status from an error, checking both `status` and `statusCode`.\n */\nexport function extractErrorStatus(error: unknown): number {\n return (error as { status?: number }).status\n ?? (error as { statusCode?: number }).statusCode\n ?? 500\n}\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":";;;;;AA6DA,SAAgB,kBAAkB,OAA4B;AAC5D,KAAI,MAAM,SAAS,aAAc,QAAO;AACxC,KAAK,MAAM,OAAiB,SAAS,aAAc,QAAO,MAAM;AAChE,QAAO;;;;;AAMT,SAAgB,mBAAmB,OAAwB;AACzD,QAAQ,MAA8B,UAChC,MAAkC,cACnC;;;;;;AAOP,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,UAAa,EAAE,MAAM;EACnC"}
@@ -2,7 +2,7 @@ import { addImports, addPlugin, addServerHandler, addServerImports, addServerPlu
2
2
 
3
3
  //#region package.json
4
4
  var name = "evlog";
5
- var version = "1.9.0";
5
+ var version = "1.11.0";
6
6
 
7
7
  //#endregion
8
8
  //#region src/nuxt/module.ts
@@ -1,40 +1,6 @@
1
1
  import { matchesPattern } from "./utils.mjs";
2
2
 
3
- //#region src/nitro.ts
4
- /**
5
- * Resolve an EvlogError from an error or its cause chain.
6
- * Both Nitro v2 (h3) and v3 wrap thrown errors — this unwraps them.
7
- */
8
- function resolveEvlogError(error) {
9
- if (error.name === "EvlogError") return error;
10
- if (error.cause?.name === "EvlogError") return error.cause;
11
- return null;
12
- }
13
- /**
14
- * Extract HTTP status from an error, checking both `status` and `statusCode`.
15
- */
16
- function extractErrorStatus(error) {
17
- return error.status ?? error.statusCode ?? 500;
18
- }
19
- /**
20
- * Build a standard evlog error JSON response body.
21
- * Used by both v2 and v3 error handlers to ensure consistent shape.
22
- */
23
- function serializeEvlogErrorResponse(error, url) {
24
- const status = extractErrorStatus(error);
25
- const { data } = error;
26
- const statusMessage = error.statusMessage || error.message;
27
- return {
28
- url,
29
- status,
30
- statusCode: status,
31
- statusText: statusMessage,
32
- statusMessage,
33
- message: error.message,
34
- error: true,
35
- ...data !== void 0 && { data }
36
- };
37
- }
3
+ //#region src/shared/routes.ts
38
4
  function shouldLog(path, include, exclude) {
39
5
  if (exclude && exclude.length > 0) {
40
6
  if (exclude.some((pattern) => matchesPattern(path, pattern))) return false;
@@ -69,5 +35,5 @@ function getServiceForPath(path, routes) {
69
35
  }
70
36
 
71
37
  //#endregion
72
- export { shouldLog as a, serializeEvlogErrorResponse as i, getServiceForPath as n, resolveEvlogError as r, extractErrorStatus as t };
73
- //# sourceMappingURL=nitro-D57TWGyN.mjs.map
38
+ export { shouldLog as n, getServiceForPath as t };
39
+ //# sourceMappingURL=routes-BNbrnm14.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes-BNbrnm14.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,GAC9B;MAAI,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;AAEpB,MAAK,MAAM,CAAC,SAAS,WAAW,OAAO,QAAQ,OAAO,CACpD,KAAI,eAAe,MAAM,QAAQ,CAC/B,QAAO,OAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evlog",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "description": "Wide event logging library with structured error handling. Inspired by LoggingSucks.",
5
5
  "author": "HugoRCD <contact@hrcd.fr>",
6
6
  "homepage": "https://evlog.dev",
@@ -18,6 +18,8 @@
18
18
  "errors",
19
19
  "nuxt",
20
20
  "nitro",
21
+ "nextjs",
22
+ "tanstack-start",
21
23
  "typescript"
22
24
  ],
23
25
  "license": "MIT",
@@ -75,6 +77,14 @@
75
77
  "./browser": {
76
78
  "types": "./dist/browser.d.mts",
77
79
  "import": "./dist/browser.mjs"
80
+ },
81
+ "./next": {
82
+ "types": "./dist/next/index.d.mts",
83
+ "import": "./dist/next/index.mjs"
84
+ },
85
+ "./next/client": {
86
+ "types": "./dist/next/client.d.mts",
87
+ "import": "./dist/next/client.mjs"
78
88
  }
79
89
  },
80
90
  "main": "./dist/index.mjs",
@@ -119,6 +129,12 @@
119
129
  ],
120
130
  "browser": [
121
131
  "./dist/browser.d.mts"
132
+ ],
133
+ "next": [
134
+ "./dist/next/index.d.mts"
135
+ ],
136
+ "next/client": [
137
+ "./dist/next/client.d.mts"
122
138
  ]
123
139
  }
124
140
  },
@@ -139,7 +155,7 @@
139
155
  },
140
156
  "devDependencies": {
141
157
  "ufo": "^1.6.3",
142
- "@nuxt/devtools": "^3.1.1",
158
+ "@nuxt/devtools": "^3.2.1",
143
159
  "@nuxt/schema": "^4.3.1",
144
160
  "@nuxt/test-utils": "^4.0.0",
145
161
  "changelogen": "^0.6.2",
@@ -157,7 +173,9 @@
157
173
  "h3": "^1.15.5",
158
174
  "nitropack": "^2.13.1",
159
175
  "ofetch": "^1.5.1",
160
- "nitro": "^3.0.1-alpha.2"
176
+ "nitro": "^3.0.1-alpha.2",
177
+ "next": ">=14.0.0",
178
+ "react": ">=18.0.0"
161
179
  },
162
180
  "peerDependenciesMeta": {
163
181
  "@nuxt/kit": {
@@ -174,6 +192,12 @@
174
192
  },
175
193
  "nitro": {
176
194
  "optional": true
195
+ },
196
+ "next": {
197
+ "optional": true
198
+ },
199
+ "react": {
200
+ "optional": true
177
201
  }
178
202
  }
179
203
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"nitro-D57TWGyN.mjs","names":[],"sources":["../src/nitro.ts"],"sourcesContent":["import type { EnvironmentContext, RouteConfig, SamplingConfig } from './types'\nimport { matchesPattern } from './utils'\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\n/**\n * Extract HTTP status from an error, checking both `status` and `statusCode`.\n */\nexport function extractErrorStatus(error: unknown): number {\n return (error as { status?: number }).status\n ?? (error as { statusCode?: number }).statusCode\n ?? 500\n}\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\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":";;;;;;;AA4DA,SAAgB,kBAAkB,OAA4B;AAC5D,KAAI,MAAM,SAAS,aAAc,QAAO;AACxC,KAAK,MAAM,OAAiB,SAAS,aAAc,QAAO,MAAM;AAChE,QAAO;;;;;AAMT,SAAgB,mBAAmB,OAAwB;AACzD,QAAQ,MAA8B,UAChC,MAAkC,cACnC;;;;;;AAOP,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,UAAa,EAAE,MAAM;EACnC;;AAGH,SAAgB,UAAU,MAAc,SAAoB,SAA6B;AAEvF,KAAI,WAAW,QAAQ,SAAS,GAC9B;MAAI,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;AAEpB,MAAK,MAAM,CAAC,SAAS,WAAW,OAAO,QAAQ,OAAO,CACpD,KAAI,eAAe,MAAM,QAAQ,CAC/B,QAAO,OAAO"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"nitro-D81NBVPi.d.mts","names":[],"sources":["../src/nitro.ts"],"mappings":";;;UAGiB,kBAAA;;AAAjB;;;EAKE,OAAA;EAKM;;;EAAN,GAAA,GAAM,OAAA,CAAQ,kBAAA;EA8BW;;;;EAxBzB,MAAA;EANc;;;;;EAad,OAAA;EAYwB;;;;;EALxB,OAAA;;;;EAKA,MAAA,GAAS,MAAA,SAAe,WAAA;;;;EAKxB,QAAA,GAAW,cAAA;AAAA"}