evlog 2.12.0 → 2.13.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 (110) hide show
  1. package/README.md +38 -0
  2. package/dist/adapters/axiom.d.mts +1 -1
  3. package/dist/adapters/better-stack.d.mts +1 -1
  4. package/dist/adapters/datadog.d.mts +1 -1
  5. package/dist/adapters/fs.d.mts +1 -1
  6. package/dist/adapters/hyperdx.d.mts +1 -1
  7. package/dist/adapters/otlp.d.mts +1 -1
  8. package/dist/adapters/posthog.d.mts +1 -1
  9. package/dist/adapters/sentry.d.mts +1 -1
  10. package/dist/ai/index.d.mts +1 -1
  11. package/dist/better-auth/index.d.mts +220 -0
  12. package/dist/better-auth/index.d.mts.map +1 -0
  13. package/dist/better-auth/index.mjs +205 -0
  14. package/dist/better-auth/index.mjs.map +1 -0
  15. package/dist/browser.d.mts +1 -1
  16. package/dist/elysia/index.d.mts +2 -2
  17. package/dist/elysia/index.d.mts.map +1 -1
  18. package/dist/elysia/index.mjs +16 -4
  19. package/dist/elysia/index.mjs.map +1 -1
  20. package/dist/enrichers.d.mts +1 -1
  21. package/dist/{error-WRz4_F3W.d.mts → error-B9CiGK_i.d.mts} +2 -2
  22. package/dist/{error-WRz4_F3W.d.mts.map → error-B9CiGK_i.d.mts.map} +1 -1
  23. package/dist/error.d.mts +1 -1
  24. package/dist/{errors-J2kt7mZh.d.mts → errors-Dr0r4OpR.d.mts} +2 -2
  25. package/dist/{errors-J2kt7mZh.d.mts.map → errors-Dr0r4OpR.d.mts.map} +1 -1
  26. package/dist/express/index.d.mts +2 -2
  27. package/dist/express/index.d.mts.map +1 -1
  28. package/dist/express/index.mjs +8 -4
  29. package/dist/express/index.mjs.map +1 -1
  30. package/dist/fastify/index.d.mts +2 -2
  31. package/dist/fastify/index.d.mts.map +1 -1
  32. package/dist/fastify/index.mjs +8 -4
  33. package/dist/fastify/index.mjs.map +1 -1
  34. package/dist/fork-Y4z8iHti.mjs +72 -0
  35. package/dist/fork-Y4z8iHti.mjs.map +1 -0
  36. package/dist/headers-D74M0wsg.mjs +30 -0
  37. package/dist/headers-D74M0wsg.mjs.map +1 -0
  38. package/dist/hono/index.d.mts +2 -2
  39. package/dist/hono/index.mjs +2 -1
  40. package/dist/hono/index.mjs.map +1 -1
  41. package/dist/http.d.mts +1 -1
  42. package/dist/index.d.mts +6 -6
  43. package/dist/index.mjs +1 -1
  44. package/dist/{logger-DY0X5oQd.mjs → logger-DnobymUQ.mjs} +40 -3
  45. package/dist/logger-DnobymUQ.mjs.map +1 -0
  46. package/dist/{logger-Bm0k3Hf3.d.mts → logger-Dp6nYWjH.d.mts} +6 -2
  47. package/dist/logger-Dp6nYWjH.d.mts.map +1 -0
  48. package/dist/logger.d.mts +1 -1
  49. package/dist/logger.mjs +1 -1
  50. package/dist/{headers-ht4yS2mx.mjs → middleware-BtBuosFV.mjs} +9 -30
  51. package/dist/middleware-BtBuosFV.mjs.map +1 -0
  52. package/dist/{middleware-D_igVy93.d.mts → middleware-FgC1OdOD.d.mts} +14 -3
  53. package/dist/{middleware-D_igVy93.d.mts.map → middleware-FgC1OdOD.d.mts.map} +1 -1
  54. package/dist/nestjs/index.d.mts +2 -2
  55. package/dist/nestjs/index.d.mts.map +1 -1
  56. package/dist/nestjs/index.mjs +8 -4
  57. package/dist/nestjs/index.mjs.map +1 -1
  58. package/dist/next/client.d.mts +1 -1
  59. package/dist/next/index.d.mts +4 -4
  60. package/dist/next/index.d.mts.map +1 -1
  61. package/dist/next/index.mjs +15 -1
  62. package/dist/next/index.mjs.map +1 -1
  63. package/dist/next/instrumentation.d.mts +1 -1
  64. package/dist/next/instrumentation.mjs +1 -1
  65. package/dist/nitro/module.d.mts +2 -2
  66. package/dist/nitro/plugin.mjs +1 -1
  67. package/dist/nitro/v3/index.d.mts +2 -2
  68. package/dist/nitro/v3/module.d.mts +1 -1
  69. package/dist/nitro/v3/plugin.mjs +1 -1
  70. package/dist/nitro/v3/useLogger.d.mts +1 -1
  71. package/dist/{nitro-BeRXZcBd.d.mts → nitro-CDHLfRdw.d.mts} +2 -2
  72. package/dist/{nitro-BeRXZcBd.d.mts.map → nitro-CDHLfRdw.d.mts.map} +1 -1
  73. package/dist/nuxt/module.d.mts +1 -1
  74. package/dist/nuxt/module.mjs +1 -1
  75. package/dist/{parseError-DhXS_vzM.d.mts → parseError-DM-lyezZ.d.mts} +2 -2
  76. package/dist/parseError-DM-lyezZ.d.mts.map +1 -0
  77. package/dist/react-router/index.d.mts +2 -2
  78. package/dist/react-router/index.d.mts.map +1 -1
  79. package/dist/react-router/index.mjs +8 -4
  80. package/dist/react-router/index.mjs.map +1 -1
  81. package/dist/runtime/client/log.d.mts +1 -1
  82. package/dist/runtime/server/routes/_evlog/ingest.post.mjs +1 -1
  83. package/dist/runtime/server/useLogger.d.mts +1 -1
  84. package/dist/runtime/utils/parseError.d.mts +2 -2
  85. package/dist/{storage-DpLJYMoc.mjs → storage-CFGTn37X.mjs} +1 -1
  86. package/dist/{storage-DpLJYMoc.mjs.map → storage-CFGTn37X.mjs.map} +1 -1
  87. package/dist/sveltekit/index.d.mts +2 -2
  88. package/dist/sveltekit/index.d.mts.map +1 -1
  89. package/dist/sveltekit/index.mjs +8 -4
  90. package/dist/sveltekit/index.mjs.map +1 -1
  91. package/dist/toolkit.d.mts +41 -4
  92. package/dist/toolkit.d.mts.map +1 -1
  93. package/dist/toolkit.mjs +5 -3
  94. package/dist/{types-D5OwxZCw.d.mts → types-DbzDln7O.d.mts} +50 -3
  95. package/dist/types-DbzDln7O.d.mts.map +1 -0
  96. package/dist/types.d.mts +1 -1
  97. package/dist/{useLogger-Dcj1Nrsa.d.mts → useLogger-N5A-d5l9.d.mts} +2 -2
  98. package/dist/{useLogger-Dcj1Nrsa.d.mts.map → useLogger-N5A-d5l9.d.mts.map} +1 -1
  99. package/dist/{utils-Bnc95-VC.d.mts → utils-DnX6VMNi.d.mts} +2 -2
  100. package/dist/{utils-Bnc95-VC.d.mts.map → utils-DnX6VMNi.d.mts.map} +1 -1
  101. package/dist/utils.d.mts +1 -1
  102. package/dist/vite/index.d.mts +1 -1
  103. package/dist/workers.d.mts +1 -1
  104. package/dist/workers.mjs +1 -1
  105. package/package.json +16 -3
  106. package/dist/headers-ht4yS2mx.mjs.map +0 -1
  107. package/dist/logger-Bm0k3Hf3.d.mts.map +0 -1
  108. package/dist/logger-DY0X5oQd.mjs.map +0 -1
  109. package/dist/parseError-DhXS_vzM.d.mts.map +0 -1
  110. package/dist/types-D5OwxZCw.d.mts.map +0 -1
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/HugoRCD/evlog/main/assets/evlog-banner.gif" width="100%" alt="evlog — Digging through logs is not observability. It's hope" />
3
+ </p>
4
+
1
5
  # evlog
2
6
 
3
7
  [![npm version](https://img.shields.io/npm/v/evlog?color=black)](https://npmjs.com/package/evlog)
@@ -1062,6 +1066,40 @@ log.emit() // Emit final event
1062
1066
  log.getContext() // Get current context
1063
1067
  ```
1064
1068
 
1069
+ ### Wide event lifecycle and `log.fork()`
1070
+
1071
+ The framework emits **one wide event per HTTP request** when the response finishes (or on error). After `emit()` runs — including when head sampling drops the event (`emit()` returns `null`) — that logger instance is **sealed**: further `set`, `error`, `info`, and `warn` calls are ignored and emit a **`[evlog]` console warning** listing dropped keys. A second `emit()` is ignored with a warning. This avoids silent data loss when async work (unawaited promises, `setTimeout`, etc.) still resolves `useLogger()` to the same logger via `AsyncLocalStorage` after the response has already been logged.
1072
+
1073
+ **`log.fork(label, fn)`** runs work under a **child** request logger: inside `fn`, `useLogger()` returns the child. When `fn` settles, the child emits its **own** wide event with `operation` set to `label` and `_parentRequestId` set to the parent’s `requestId` (query and dashboard correlation). The parent event may be emitted **before** the child event; they are two separate events ordered by time.
1074
+
1075
+ `fork` is attached by integrations that use `AsyncLocalStorage` for `useLogger()`. Standalone `createLogger()` instances do not have `fork`.
1076
+
1077
+ | Integration | `log.fork()` |
1078
+ |-------------|----------------|
1079
+ | Express, Fastify, NestJS, SvelteKit, React Router, Elysia | Yes |
1080
+ | Next.js `withEvlog` | Yes |
1081
+ | Hono (`c.get('log')` only) | Not yet |
1082
+ | Nitro / Nuxt `useLogger(event)` | Not yet — use post-emit warnings; see [Wide events](https://evlog.dev/logging/wide-events) |
1083
+
1084
+ ```typescript
1085
+ import { evlog, useLogger } from 'evlog/express'
1086
+
1087
+ app.post('/checkout', (req, res) => {
1088
+ const log = req.log
1089
+ log.set({ order_dispatched: true })
1090
+
1091
+ log.fork!('process_order', async () => {
1092
+ const childLog = useLogger()
1093
+ childLog.set({ inventory_checked: true })
1094
+ // child emits automatically when this async function completes
1095
+ })
1096
+
1097
+ res.json({ ok: true })
1098
+ })
1099
+ ```
1100
+
1101
+ Use optional chaining if `fork` might be absent: `log.fork?.('task', async () => { ... })`.
1102
+
1065
1103
  ### `initWorkersLogger(options?)`
1066
1104
 
1067
1105
  Initialize evlog for Cloudflare Workers (object logs + correct severity).
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-D5OwxZCw.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  //#region src/adapters/axiom.d.ts
3
3
  interface BaseAxiomConfig {
4
4
  /** Axiom dataset name */
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-D5OwxZCw.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  //#region src/adapters/better-stack.d.ts
3
3
  interface BetterStackConfig {
4
4
  /** Better Stack source token */
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-D5OwxZCw.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  //#region src/adapters/datadog.d.ts
3
3
  interface DatadogConfig {
4
4
  /** Datadog API key with Logs intake permission */
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-D5OwxZCw.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  //#region src/adapters/fs.d.ts
3
3
  interface FsConfig {
4
4
  /** Directory for log files */
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-D5OwxZCw.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  import { OTLPConfig } from "./otlp.mjs";
3
3
 
4
4
  //#region src/adapters/hyperdx.d.ts
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-D5OwxZCw.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  //#region src/adapters/otlp.d.ts
3
3
  interface OTLPConfig {
4
4
  /** OTLP HTTP endpoint (e.g., http://localhost:4318) */
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-D5OwxZCw.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  //#region src/adapters/posthog.d.ts
3
3
  interface PostHogConfig {
4
4
  /** PostHog project API key */
@@ -1,4 +1,4 @@
1
- import { E as WideEvent, r as DrainContext } from "../types-D5OwxZCw.mjs";
1
+ import { E as WideEvent, r as DrainContext } from "../types-DbzDln7O.mjs";
2
2
  //#region src/adapters/sentry.d.ts
3
3
  interface SentryConfig {
4
4
  /** Sentry DSN */
@@ -1,4 +1,4 @@
1
- import { _ as RequestLogger } from "../types-D5OwxZCw.mjs";
1
+ import { _ as RequestLogger } from "../types-DbzDln7O.mjs";
2
2
  import { GatewayModelId, TelemetryIntegration } from "ai";
3
3
  import { LanguageModelV3, LanguageModelV3Middleware } from "@ai-sdk/provider";
4
4
 
@@ -0,0 +1,220 @@
1
+ import { _ as RequestLogger } from "../types-DbzDln7O.mjs";
2
+
3
+ //#region src/better-auth/index.d.ts
4
+ /**
5
+ * Minimal type for the Better Auth instance.
6
+ * Only requires `api.getSession` — compatible with any Better Auth configuration.
7
+ */
8
+ interface BetterAuthInstance {
9
+ api: {
10
+ getSession: (opts: {
11
+ headers: Headers | Record<string, string | string[] | undefined>;
12
+ }) => Promise<{
13
+ user: Record<string, unknown>;
14
+ session: Record<string, unknown>;
15
+ } | null>;
16
+ };
17
+ }
18
+ /**
19
+ * User fields extracted from a Better Auth session.
20
+ */
21
+ interface AuthUserData {
22
+ id: string;
23
+ name?: string;
24
+ email?: string;
25
+ image?: string;
26
+ emailVerified?: boolean;
27
+ createdAt?: string;
28
+ }
29
+ /**
30
+ * Session fields extracted from a Better Auth session.
31
+ */
32
+ interface AuthSessionData {
33
+ id: string;
34
+ expiresAt?: string;
35
+ ipAddress?: string;
36
+ userAgent?: string;
37
+ createdAt?: string;
38
+ }
39
+ /**
40
+ * Options for `identifyUser`.
41
+ */
42
+ interface IdentifyOptions {
43
+ /**
44
+ * Whether to mask the user email (e.g. `h***@domain.com`).
45
+ * @default false
46
+ */
47
+ maskEmail?: boolean;
48
+ /**
49
+ * Whether to include session metadata on the wide event.
50
+ * @default true
51
+ */
52
+ session?: boolean;
53
+ /**
54
+ * Whitelist of user fields to include.
55
+ * @default ['id', 'name', 'email', 'image', 'emailVerified', 'createdAt']
56
+ */
57
+ fields?: string[];
58
+ /**
59
+ * Extend the wide event with additional fields derived from the session.
60
+ * Useful for Better Auth plugins (organizations, roles, etc.).
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * identifyUser(log, session, {
65
+ * extend: (session) => ({
66
+ * organization: session.user.activeOrganization,
67
+ * role: session.user.role,
68
+ * }),
69
+ * })
70
+ * ```
71
+ */
72
+ extend?: (session: {
73
+ user: Record<string, unknown>;
74
+ session: Record<string, unknown>;
75
+ }) => Record<string, unknown> | undefined;
76
+ }
77
+ /**
78
+ * Options for `createAuthMiddleware`.
79
+ */
80
+ interface AuthMiddlewareOptions extends IdentifyOptions {
81
+ /**
82
+ * Route patterns to skip session resolution (glob).
83
+ * @default ['/api/auth/**']
84
+ */
85
+ exclude?: string[];
86
+ /**
87
+ * Route patterns to apply session resolution (glob).
88
+ * If set, only matching routes are resolved.
89
+ */
90
+ include?: string[];
91
+ /**
92
+ * Called after a user is successfully identified.
93
+ * Use to add conditional logic based on user data (e.g. force-keep logs for premium users).
94
+ */
95
+ onIdentify?: (log: RequestLogger, session: {
96
+ user: Record<string, unknown>;
97
+ session: Record<string, unknown>;
98
+ }) => void | Promise<void>;
99
+ /**
100
+ * Called when no session is found (anonymous request).
101
+ */
102
+ onAnonymous?: (log: RequestLogger) => void | Promise<void>;
103
+ }
104
+ /**
105
+ * Options for `createAuthIdentifier`.
106
+ */
107
+ type AuthIdentifierOptions = AuthMiddlewareOptions;
108
+ /**
109
+ * Mask an email address for safe logging: `hugo@example.com` -> `h***@example.com`.
110
+ */
111
+ declare function maskEmail(email: string): string;
112
+ /**
113
+ * Identify a user on a wide event from a Better Auth session result.
114
+ *
115
+ * Sets `userId`, `user`, and optionally `session` fields on the logger.
116
+ * Safe by default — only extracts whitelisted fields and never logs passwords or tokens.
117
+ *
118
+ * Returns `true` if the user was identified, `false` if session data was missing.
119
+ *
120
+ * @example
121
+ * ```ts
122
+ * import { identifyUser } from 'evlog/better-auth'
123
+ *
124
+ * const session = await auth.api.getSession({ headers: event.headers })
125
+ * if (session) {
126
+ * identifyUser(log, session)
127
+ * }
128
+ * ```
129
+ *
130
+ * @example With email masking
131
+ * ```ts
132
+ * identifyUser(log, session, { maskEmail: true })
133
+ * // user.email → "h***@example.com"
134
+ * ```
135
+ *
136
+ * @example With extend for Better Auth plugins
137
+ * ```ts
138
+ * identifyUser(log, session, {
139
+ * extend: (s) => ({
140
+ * organization: s.user.activeOrganization,
141
+ * role: s.user.role,
142
+ * }),
143
+ * })
144
+ * ```
145
+ */
146
+ declare function identifyUser(log: RequestLogger, session: {
147
+ user: Record<string, unknown>;
148
+ session: Record<string, unknown>;
149
+ }, options?: IdentifyOptions): boolean;
150
+ /**
151
+ * Create an async function that resolves a Better Auth session from headers
152
+ * and identifies the user on the logger.
153
+ *
154
+ * Works with any framework — just pass the auth instance and call the returned
155
+ * function with a logger and headers. Supports `include`/`exclude` route patterns
156
+ * and lifecycle hooks (`onIdentify`, `onAnonymous`).
157
+ *
158
+ * @example Nuxt server middleware
159
+ * ```ts
160
+ * import { createAuthMiddleware } from 'evlog/better-auth'
161
+ *
162
+ * const identify = createAuthMiddleware(auth, {
163
+ * exclude: ['/api/auth/**', '/api/public/**'],
164
+ * })
165
+ *
166
+ * export default defineEventHandler(async (event) => {
167
+ * if (!event.context.log) return
168
+ * await identify(event.context.log, event.headers, event.path)
169
+ * })
170
+ * ```
171
+ *
172
+ * @example Express
173
+ * ```ts
174
+ * const identify = createAuthMiddleware(auth, { maskEmail: true })
175
+ *
176
+ * app.use(async (req, res, next) => {
177
+ * await identify(req.log, req.headers, req.path)
178
+ * next()
179
+ * })
180
+ * ```
181
+ */
182
+ declare function createAuthMiddleware(auth: BetterAuthInstance, options?: AuthMiddlewareOptions): (log: RequestLogger, headers: Headers | Record<string, string | string[] | undefined>, path?: string) => Promise<boolean>;
183
+ /**
184
+ * Create a Nitro `request` hook that auto-identifies users from Better Auth sessions.
185
+ *
186
+ * Resolves the session from request cookies on every request and sets user/session
187
+ * context on the evlog logger. Skips `/api/auth/**` by default to avoid resolving
188
+ * sessions during auth flows.
189
+ *
190
+ * @example
191
+ * ```ts
192
+ * // server/plugins/evlog-auth.ts
193
+ * import { createAuthIdentifier } from 'evlog/better-auth'
194
+ * import { auth } from '~/lib/auth'
195
+ *
196
+ * export default defineNitroPlugin((nitroApp) => {
197
+ * nitroApp.hooks.hook('request', createAuthIdentifier(auth))
198
+ * })
199
+ * ```
200
+ *
201
+ * @example With options
202
+ * ```ts
203
+ * nitroApp.hooks.hook('request', createAuthIdentifier(auth, {
204
+ * maskEmail: true,
205
+ * exclude: ['/api/auth/**', '/api/public/**'],
206
+ * }))
207
+ * ```
208
+ */
209
+ declare function createAuthIdentifier(auth: BetterAuthInstance, options?: AuthIdentifierOptions): (event: {
210
+ path: string;
211
+ headers: Headers | {
212
+ get(name: string): string | null;
213
+ };
214
+ context: {
215
+ log?: RequestLogger;
216
+ };
217
+ }) => Promise<void>;
218
+ //#endregion
219
+ export { AuthIdentifierOptions, AuthMiddlewareOptions, AuthSessionData, AuthUserData, BetterAuthInstance, IdentifyOptions, createAuthIdentifier, createAuthMiddleware, identifyUser, maskEmail };
220
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/better-auth/index.ts"],"mappings":";;;;;AAOA;;UAAiB,kBAAA;EACf,GAAA;IACE,UAAA,GAAa,IAAA;MACX,OAAA,EAAS,OAAA,GAAU,MAAA;IAAA,MACf,OAAA;MACJ,IAAA,EAAM,MAAA;MACN,OAAA,EAAS,MAAA;IAAA;EAAA;AAAA;;;;UAQE,YAAA;EACf,EAAA;EACA,IAAA;EACA,KAAA;EACA,KAAA;EACA,aAAA;EACA,SAAA;AAAA;AANF;;;AAAA,UAYiB,eAAA;EACf,EAAA;EACA,SAAA;EACA,SAAA;EACA,SAAA;EACA,SAAA;AAAA;;;AALF;UAWiB,eAAA;;;;;EAKf,SAAA;EAZA;;;;EAiBA,OAAA;EAV8B;;;;EAe9B,MAAA;EAeiG;;;;;;;;;;;;;;EAAjG,MAAA,IAAU,OAAA;IAAW,IAAA,EAAM,MAAA;IAAyB,OAAA,EAAS,MAAA;EAAA,MAA8B,MAAA;AAAA;;;;UAM5E,qBAAA,SAA8B,eAAA;EAAA;;;;EAK7C,OAAA;EAKA;;;;EAAA,OAAA;EAKmD;;;;EAAnD,UAAA,IAAc,GAAA,EAAK,aAAA,EAAe,OAAA;IAAW,IAAA,EAAM,MAAA;IAAyB,OAAA,EAAS,MAAA;EAAA,aAAqC,OAAA;EAI7E;;;EAA7C,WAAA,IAAe,GAAA,EAAK,aAAA,YAAyB,OAAA;AAAA;;;;KAMnC,qBAAA,GAAwB,qBAAA;;;;iBASpB,SAAA,CAAU,KAAA;AAyF1B;;;;;;;;;;;;;;;;;;;;AA4EA;;;;;;;;;;;;;;AA5EA,iBAAgB,YAAA,CACd,GAAA,EAAK,aAAA,EACL,OAAA;EAAW,IAAA,EAAM,MAAA;EAAyB,OAAA,EAAS,MAAA;AAAA,GACnD,OAAA,GAAU,eAAA;;;;;;;AAqIZ;;;;;;;;;;;;;;;;;;;;;;;;;;iBA5DgB,oBAAA,CACd,IAAA,EAAM,kBAAA,EACN,OAAA,GAAU,qBAAA,IACR,GAAA,EAAK,aAAA,EAAe,OAAA,EAAS,OAAA,GAAU,MAAA,yCAA+C,IAAA,cAAkB,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyD5F,oBAAA,CACd,IAAA,EAAM,kBAAA,EACN,OAAA,GAAU,qBAAA,IACR,KAAA;EAAS,IAAA;EAAc,OAAA,EAAS,OAAA;IAAY,GAAA,CAAI,IAAA;EAAA;EAAgC,OAAA;IAAW,GAAA,GAAM,aAAA;EAAA;AAAA,MAAsB,OAAA"}
@@ -0,0 +1,205 @@
1
+ import { matchesPattern } from "../utils.mjs";
2
+ //#region src/better-auth/index.ts
3
+ const DEFAULT_USER_FIELDS = [
4
+ "id",
5
+ "name",
6
+ "email",
7
+ "image",
8
+ "emailVerified",
9
+ "createdAt"
10
+ ];
11
+ const isDev = typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
12
+ /**
13
+ * Mask an email address for safe logging: `hugo@example.com` -> `h***@example.com`.
14
+ */
15
+ function maskEmail(email) {
16
+ const atIndex = email.indexOf("@");
17
+ if (atIndex <= 0) return "***";
18
+ return `${email[0]}***${email.slice(atIndex)}`;
19
+ }
20
+ function extractUserData(user, options) {
21
+ const fields = options?.fields ?? DEFAULT_USER_FIELDS;
22
+ const data = {};
23
+ if (user.id !== void 0 && user.id !== null) data.id = user.id;
24
+ for (const field of fields) {
25
+ if (field === "id") continue;
26
+ const value = user[field];
27
+ if (value === void 0 || value === null) continue;
28
+ if (field === "email" && options?.maskEmail && typeof value === "string") data[field] = maskEmail(value);
29
+ else if (field === "createdAt" && value instanceof Date) data[field] = value.toISOString();
30
+ else data[field] = value;
31
+ }
32
+ return data;
33
+ }
34
+ function extractSessionData(session) {
35
+ const data = { id: String(session.id) };
36
+ if (session.expiresAt) data.expiresAt = session.expiresAt instanceof Date ? session.expiresAt.toISOString() : String(session.expiresAt);
37
+ if (typeof session.ipAddress === "string") data.ipAddress = session.ipAddress;
38
+ if (typeof session.userAgent === "string") data.userAgent = session.userAgent;
39
+ if (session.createdAt) data.createdAt = session.createdAt instanceof Date ? session.createdAt.toISOString() : String(session.createdAt);
40
+ return data;
41
+ }
42
+ /**
43
+ * Identify a user on a wide event from a Better Auth session result.
44
+ *
45
+ * Sets `userId`, `user`, and optionally `session` fields on the logger.
46
+ * Safe by default — only extracts whitelisted fields and never logs passwords or tokens.
47
+ *
48
+ * Returns `true` if the user was identified, `false` if session data was missing.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { identifyUser } from 'evlog/better-auth'
53
+ *
54
+ * const session = await auth.api.getSession({ headers: event.headers })
55
+ * if (session) {
56
+ * identifyUser(log, session)
57
+ * }
58
+ * ```
59
+ *
60
+ * @example With email masking
61
+ * ```ts
62
+ * identifyUser(log, session, { maskEmail: true })
63
+ * // user.email → "h***@example.com"
64
+ * ```
65
+ *
66
+ * @example With extend for Better Auth plugins
67
+ * ```ts
68
+ * identifyUser(log, session, {
69
+ * extend: (s) => ({
70
+ * organization: s.user.activeOrganization,
71
+ * role: s.user.role,
72
+ * }),
73
+ * })
74
+ * ```
75
+ */
76
+ function identifyUser(log, session, options) {
77
+ const user = extractUserData(session.user, options);
78
+ if (!user.id) return false;
79
+ const includeSession = options?.session !== false;
80
+ const data = {
81
+ userId: user.id,
82
+ user
83
+ };
84
+ if (includeSession) data.session = extractSessionData(session.session);
85
+ if (options?.extend) {
86
+ const extra = options.extend(session);
87
+ if (extra) Object.assign(data, extra);
88
+ }
89
+ log.set(data);
90
+ return true;
91
+ }
92
+ function shouldResolve(path, options) {
93
+ const exclude = options?.exclude ?? ["/api/auth/**"];
94
+ for (const pattern of exclude) if (matchesPattern(path, pattern)) return false;
95
+ if (options?.include) {
96
+ for (const pattern of options.include) if (matchesPattern(path, pattern)) return true;
97
+ return false;
98
+ }
99
+ return true;
100
+ }
101
+ /**
102
+ * Create an async function that resolves a Better Auth session from headers
103
+ * and identifies the user on the logger.
104
+ *
105
+ * Works with any framework — just pass the auth instance and call the returned
106
+ * function with a logger and headers. Supports `include`/`exclude` route patterns
107
+ * and lifecycle hooks (`onIdentify`, `onAnonymous`).
108
+ *
109
+ * @example Nuxt server middleware
110
+ * ```ts
111
+ * import { createAuthMiddleware } from 'evlog/better-auth'
112
+ *
113
+ * const identify = createAuthMiddleware(auth, {
114
+ * exclude: ['/api/auth/**', '/api/public/**'],
115
+ * })
116
+ *
117
+ * export default defineEventHandler(async (event) => {
118
+ * if (!event.context.log) return
119
+ * await identify(event.context.log, event.headers, event.path)
120
+ * })
121
+ * ```
122
+ *
123
+ * @example Express
124
+ * ```ts
125
+ * const identify = createAuthMiddleware(auth, { maskEmail: true })
126
+ *
127
+ * app.use(async (req, res, next) => {
128
+ * await identify(req.log, req.headers, req.path)
129
+ * next()
130
+ * })
131
+ * ```
132
+ */
133
+ function createAuthMiddleware(auth, options) {
134
+ return async (log, headers, path) => {
135
+ if (path && !shouldResolve(path, options)) return false;
136
+ const start = Date.now();
137
+ try {
138
+ const session = await auth.api.getSession({ headers });
139
+ const resolvedIn = Date.now() - start;
140
+ if (session) {
141
+ if (identifyUser(log, session, options)) {
142
+ log.set({ auth: {
143
+ resolvedIn,
144
+ identified: true
145
+ } });
146
+ if (options?.onIdentify) await options.onIdentify(log, session);
147
+ return true;
148
+ }
149
+ }
150
+ log.set({ auth: {
151
+ resolvedIn,
152
+ identified: false
153
+ } });
154
+ if (options?.onAnonymous) await options.onAnonymous(log);
155
+ return false;
156
+ } catch (err) {
157
+ const resolvedIn = Date.now() - start;
158
+ log.set({ auth: {
159
+ resolvedIn,
160
+ identified: false,
161
+ error: true
162
+ } });
163
+ if (isDev) console.warn("[evlog/better-auth] Session resolution failed:", err);
164
+ if (options?.onAnonymous) await options.onAnonymous(log);
165
+ return false;
166
+ }
167
+ };
168
+ }
169
+ /**
170
+ * Create a Nitro `request` hook that auto-identifies users from Better Auth sessions.
171
+ *
172
+ * Resolves the session from request cookies on every request and sets user/session
173
+ * context on the evlog logger. Skips `/api/auth/**` by default to avoid resolving
174
+ * sessions during auth flows.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * // server/plugins/evlog-auth.ts
179
+ * import { createAuthIdentifier } from 'evlog/better-auth'
180
+ * import { auth } from '~/lib/auth'
181
+ *
182
+ * export default defineNitroPlugin((nitroApp) => {
183
+ * nitroApp.hooks.hook('request', createAuthIdentifier(auth))
184
+ * })
185
+ * ```
186
+ *
187
+ * @example With options
188
+ * ```ts
189
+ * nitroApp.hooks.hook('request', createAuthIdentifier(auth, {
190
+ * maskEmail: true,
191
+ * exclude: ['/api/auth/**', '/api/public/**'],
192
+ * }))
193
+ * ```
194
+ */
195
+ function createAuthIdentifier(auth, options) {
196
+ const middleware = createAuthMiddleware(auth, options);
197
+ return async (event) => {
198
+ if (!event.context.log) return;
199
+ await middleware(event.context.log, event.headers, event.path);
200
+ };
201
+ }
202
+ //#endregion
203
+ export { createAuthIdentifier, createAuthMiddleware, identifyUser, maskEmail };
204
+
205
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/better-auth/index.ts"],"sourcesContent":["import type { RequestLogger } from '../types'\nimport { matchesPattern } from '../utils'\n\n/**\n * Minimal type for the Better Auth instance.\n * Only requires `api.getSession` — compatible with any Better Auth configuration.\n */\nexport interface BetterAuthInstance {\n api: {\n getSession: (opts: {\n headers: Headers | Record<string, string | string[] | undefined>\n }) => Promise<{\n user: Record<string, unknown>\n session: Record<string, unknown>\n } | null>\n }\n}\n\n/**\n * User fields extracted from a Better Auth session.\n */\nexport interface AuthUserData {\n id: string\n name?: string\n email?: string\n image?: string\n emailVerified?: boolean\n createdAt?: string\n}\n\n/**\n * Session fields extracted from a Better Auth session.\n */\nexport interface AuthSessionData {\n id: string\n expiresAt?: string\n ipAddress?: string\n userAgent?: string\n createdAt?: string\n}\n\n/**\n * Options for `identifyUser`.\n */\nexport interface IdentifyOptions {\n /**\n * Whether to mask the user email (e.g. `h***@domain.com`).\n * @default false\n */\n maskEmail?: boolean\n /**\n * Whether to include session metadata on the wide event.\n * @default true\n */\n session?: boolean\n /**\n * Whitelist of user fields to include.\n * @default ['id', 'name', 'email', 'image', 'emailVerified', 'createdAt']\n */\n fields?: string[]\n /**\n * Extend the wide event with additional fields derived from the session.\n * Useful for Better Auth plugins (organizations, roles, etc.).\n *\n * @example\n * ```ts\n * identifyUser(log, session, {\n * extend: (session) => ({\n * organization: session.user.activeOrganization,\n * role: session.user.role,\n * }),\n * })\n * ```\n */\n extend?: (session: { user: Record<string, unknown>, session: Record<string, unknown> }) => Record<string, unknown> | undefined\n}\n\n/**\n * Options for `createAuthMiddleware`.\n */\nexport interface AuthMiddlewareOptions extends IdentifyOptions {\n /**\n * Route patterns to skip session resolution (glob).\n * @default ['/api/auth/**']\n */\n exclude?: string[]\n /**\n * Route patterns to apply session resolution (glob).\n * If set, only matching routes are resolved.\n */\n include?: string[]\n /**\n * Called after a user is successfully identified.\n * Use to add conditional logic based on user data (e.g. force-keep logs for premium users).\n */\n onIdentify?: (log: RequestLogger, session: { user: Record<string, unknown>, session: Record<string, unknown> }) => void | Promise<void>\n /**\n * Called when no session is found (anonymous request).\n */\n onAnonymous?: (log: RequestLogger) => void | Promise<void>\n}\n\n/**\n * Options for `createAuthIdentifier`.\n */\nexport type AuthIdentifierOptions = AuthMiddlewareOptions\n\nconst DEFAULT_USER_FIELDS = ['id', 'name', 'email', 'image', 'emailVerified', 'createdAt']\n\nconst isDev = typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production'\n\n/**\n * Mask an email address for safe logging: `hugo@example.com` -> `h***@example.com`.\n */\nexport function maskEmail(email: string): string {\n const atIndex = email.indexOf('@')\n if (atIndex <= 0) return '***'\n return `${email[0]}***${email.slice(atIndex)}`\n}\n\nfunction extractUserData(\n user: Record<string, unknown>,\n options?: IdentifyOptions,\n): AuthUserData {\n const fields = options?.fields ?? DEFAULT_USER_FIELDS\n const data: Record<string, unknown> = {}\n\n if (user.id !== undefined && user.id !== null) {\n data.id = user.id\n }\n\n for (const field of fields) {\n if (field === 'id') continue\n const value = user[field]\n if (value === undefined || value === null) continue\n\n if (field === 'email' && options?.maskEmail && typeof value === 'string') {\n data[field] = maskEmail(value)\n } else if (field === 'createdAt' && value instanceof Date) {\n data[field] = value.toISOString()\n } else {\n data[field] = value\n }\n }\n\n return data as unknown as AuthUserData\n}\n\nfunction extractSessionData(\n session: Record<string, unknown>,\n): AuthSessionData {\n const data: AuthSessionData = { id: String(session.id) }\n\n if (session.expiresAt) {\n data.expiresAt = session.expiresAt instanceof Date\n ? session.expiresAt.toISOString()\n : String(session.expiresAt)\n }\n if (typeof session.ipAddress === 'string') data.ipAddress = session.ipAddress\n if (typeof session.userAgent === 'string') data.userAgent = session.userAgent\n if (session.createdAt) {\n data.createdAt = session.createdAt instanceof Date\n ? session.createdAt.toISOString()\n : String(session.createdAt)\n }\n\n return data\n}\n\n/**\n * Identify a user on a wide event from a Better Auth session result.\n *\n * Sets `userId`, `user`, and optionally `session` fields on the logger.\n * Safe by default — only extracts whitelisted fields and never logs passwords or tokens.\n *\n * Returns `true` if the user was identified, `false` if session data was missing.\n *\n * @example\n * ```ts\n * import { identifyUser } from 'evlog/better-auth'\n *\n * const session = await auth.api.getSession({ headers: event.headers })\n * if (session) {\n * identifyUser(log, session)\n * }\n * ```\n *\n * @example With email masking\n * ```ts\n * identifyUser(log, session, { maskEmail: true })\n * // user.email → \"h***@example.com\"\n * ```\n *\n * @example With extend for Better Auth plugins\n * ```ts\n * identifyUser(log, session, {\n * extend: (s) => ({\n * organization: s.user.activeOrganization,\n * role: s.user.role,\n * }),\n * })\n * ```\n */\nexport function identifyUser(\n log: RequestLogger,\n session: { user: Record<string, unknown>, session: Record<string, unknown> },\n options?: IdentifyOptions,\n): boolean {\n const user = extractUserData(session.user, options)\n if (!user.id) return false\n\n const includeSession = options?.session !== false\n\n const data: Record<string, unknown> = {\n userId: user.id,\n user,\n }\n\n if (includeSession) {\n data.session = extractSessionData(session.session)\n }\n\n if (options?.extend) {\n const extra = options.extend(session)\n if (extra) Object.assign(data, extra)\n }\n\n log.set(data)\n return true\n}\n\nfunction shouldResolve(path: string, options?: { exclude?: string[], include?: string[] }): boolean {\n const exclude = options?.exclude ?? ['/api/auth/**']\n for (const pattern of exclude) {\n if (matchesPattern(path, pattern)) return false\n }\n\n if (options?.include) {\n for (const pattern of options.include) {\n if (matchesPattern(path, pattern)) return true\n }\n return false\n }\n\n return true\n}\n\n/**\n * Create an async function that resolves a Better Auth session from headers\n * and identifies the user on the logger.\n *\n * Works with any framework — just pass the auth instance and call the returned\n * function with a logger and headers. Supports `include`/`exclude` route patterns\n * and lifecycle hooks (`onIdentify`, `onAnonymous`).\n *\n * @example Nuxt server middleware\n * ```ts\n * import { createAuthMiddleware } from 'evlog/better-auth'\n *\n * const identify = createAuthMiddleware(auth, {\n * exclude: ['/api/auth/**', '/api/public/**'],\n * })\n *\n * export default defineEventHandler(async (event) => {\n * if (!event.context.log) return\n * await identify(event.context.log, event.headers, event.path)\n * })\n * ```\n *\n * @example Express\n * ```ts\n * const identify = createAuthMiddleware(auth, { maskEmail: true })\n *\n * app.use(async (req, res, next) => {\n * await identify(req.log, req.headers, req.path)\n * next()\n * })\n * ```\n */\nexport function createAuthMiddleware(\n auth: BetterAuthInstance,\n options?: AuthMiddlewareOptions,\n): (log: RequestLogger, headers: Headers | Record<string, string | string[] | undefined>, path?: string) => Promise<boolean> {\n return async (log, headers, path?) => {\n if (path && !shouldResolve(path, options)) return false\n\n const start = Date.now()\n try {\n const session = await auth.api.getSession({ headers })\n const resolvedIn = Date.now() - start\n\n if (session) {\n const identified = identifyUser(log, session, options)\n if (identified) {\n log.set({ auth: { resolvedIn, identified: true } } as Record<string, unknown>)\n if (options?.onIdentify) await options.onIdentify(log, session)\n return true\n }\n }\n\n log.set({ auth: { resolvedIn, identified: false } } as Record<string, unknown>)\n if (options?.onAnonymous) await options.onAnonymous(log)\n return false\n } catch (err) {\n const resolvedIn = Date.now() - start\n log.set({ auth: { resolvedIn, identified: false, error: true } } as Record<string, unknown>)\n if (isDev) console.warn('[evlog/better-auth] Session resolution failed:', err)\n if (options?.onAnonymous) await options.onAnonymous(log)\n return false\n }\n }\n}\n\n/**\n * Create a Nitro `request` hook that auto-identifies users from Better Auth sessions.\n *\n * Resolves the session from request cookies on every request and sets user/session\n * context on the evlog logger. Skips `/api/auth/**` by default to avoid resolving\n * sessions during auth flows.\n *\n * @example\n * ```ts\n * // server/plugins/evlog-auth.ts\n * import { createAuthIdentifier } from 'evlog/better-auth'\n * import { auth } from '~/lib/auth'\n *\n * export default defineNitroPlugin((nitroApp) => {\n * nitroApp.hooks.hook('request', createAuthIdentifier(auth))\n * })\n * ```\n *\n * @example With options\n * ```ts\n * nitroApp.hooks.hook('request', createAuthIdentifier(auth, {\n * maskEmail: true,\n * exclude: ['/api/auth/**', '/api/public/**'],\n * }))\n * ```\n */\nexport function createAuthIdentifier(\n auth: BetterAuthInstance,\n options?: AuthIdentifierOptions,\n): (event: { path: string, headers: Headers | { get(name: string): string | null }, context: { log?: RequestLogger } }) => Promise<void> {\n const middleware = createAuthMiddleware(auth, options)\n\n return async (event) => {\n if (!event.context.log) return\n await middleware(event.context.log, event.headers as Headers, event.path)\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { r as DrainContext } from "./types-D5OwxZCw.mjs";
1
+ import { r as DrainContext } from "./types-DbzDln7O.mjs";
2
2
  import { PipelineDrainFn } from "./pipeline.mjs";
3
3
  import { HttpDrainConfig, HttpLogDrainOptions } from "./http.mjs";
4
4
 
@@ -1,5 +1,5 @@
1
- import { _ as RequestLogger } from "../types-D5OwxZCw.mjs";
2
- import { t as BaseEvlogOptions } from "../middleware-D_igVy93.mjs";
1
+ import { _ as RequestLogger } from "../types-DbzDln7O.mjs";
2
+ import { t as BaseEvlogOptions } from "../middleware-FgC1OdOD.mjs";
3
3
  import { Elysia } from "elysia";
4
4
 
5
5
  //#region src/elysia/index.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/elysia/index.ts"],"mappings":";;;;;KAeY,kBAAA,GAAqB,gBAAA;;AAAjC;;;;;AAoBA;;;;;;;;;;;;iBAAgB,SAAA,oBAA6B,MAAA,kBAAA,CAAA,GAA4B,aAAA,CAAc,CAAA;AAAA,iBAwCvE,KAAA,CAAM,OAAA,GAAS,kBAAA,GAAuB,MAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/elysia/index.ts"],"mappings":";;;;;KAgBY,kBAAA,GAAqB,gBAAA;;AAAjC;;;;;AAoBA;;;;;;;;;;;;iBAAgB,SAAA,oBAA6B,MAAA,kBAAA,CAAA,GAA4B,aAAA,CAAc,CAAA;AAAA,iBAwCvE,KAAA,CAAM,OAAA,GAAS,kBAAA,GAAuB,MAAA"}
@@ -1,5 +1,6 @@
1
1
  import { filterSafeHeaders } from "../utils.mjs";
2
- import { r as createMiddlewareLogger } from "../headers-ht4yS2mx.mjs";
2
+ import { t as createMiddlewareLogger } from "../middleware-BtBuosFV.mjs";
3
+ import { t as attachForkToLogger } from "../fork-Y4z8iHti.mjs";
3
4
  import { AsyncLocalStorage } from "node:async_hooks";
4
5
  import { Elysia } from "elysia";
5
6
  //#region src/elysia/index.ts
@@ -32,14 +33,25 @@ function evlog(options = {}) {
32
33
  const emitted = /* @__PURE__ */ new WeakSet();
33
34
  const requestState = /* @__PURE__ */ new WeakMap();
34
35
  return new Elysia({ name: "evlog" }).derive({ as: "global" }, ({ request, path, headers }) => {
35
- const { logger, finish, skipped } = createMiddlewareLogger({
36
+ const middlewareOpts = {
36
37
  method: request.method,
37
38
  path,
38
39
  requestId: headers["x-request-id"] || crypto.randomUUID(),
39
40
  headers: filterSafeHeaders(headers),
40
41
  ...options
41
- });
42
- if (!skipped) activeLoggers.add(logger);
42
+ };
43
+ const { logger, finish, skipped } = createMiddlewareLogger(middlewareOpts);
44
+ if (!skipped) {
45
+ attachForkToLogger(storage, logger, middlewareOpts, {
46
+ onChildEnter: (child) => {
47
+ activeLoggers.add(child);
48
+ },
49
+ onChildExit: (child) => {
50
+ activeLoggers.delete(child);
51
+ }
52
+ });
53
+ activeLoggers.add(logger);
54
+ }
43
55
  storage.enterWith(logger);
44
56
  requestState.set(request, {
45
57
  finish,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/elysia/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport { Elysia } from 'elysia'\nimport type { RequestLogger } from '../types'\nimport { createMiddlewareLogger, type BaseEvlogOptions } from '../shared/middleware'\nimport { extractSafeHeaders } from '../shared/headers'\nimport { filterSafeHeaders } from '../utils'\n\nconst storage = new AsyncLocalStorage<RequestLogger>()\n\n// Tracks loggers that are currently active (within a live request).\n// Elysia uses storage.enterWith() which persists in the async context\n// even after the request ends, so we use this set to distinguish\n// an in-flight logger from a stale one.\nconst activeLoggers = new WeakSet<RequestLogger>()\n\nexport type EvlogElysiaOptions = BaseEvlogOptions\n\n/**\n * Get the request-scoped logger from anywhere in the call stack.\n * Must be called inside a request handled by the `evlog()` plugin.\n *\n * Unlike other frameworks, Elysia uses `storage.enterWith()` which persists\n * beyond the request lifecycle. This accessor additionally checks `activeLoggers`\n * to ensure the logger belongs to an in-flight request.\n *\n * @example\n * ```ts\n * import { useLogger } from 'evlog/elysia'\n *\n * function findUser(id: string) {\n * const log = useLogger()\n * log.set({ user: { id } })\n * }\n * ```\n */\nexport function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T> {\n const logger = storage.getStore()\n if (!logger || !activeLoggers.has(logger)) {\n throw new Error(\n '[evlog] useLogger() was called outside of an evlog plugin context. '\n + 'Make sure app.use(evlog()) is registered before your routes.',\n )\n }\n return logger as RequestLogger<T>\n}\n\n/**\n * Create an evlog plugin for Elysia.\n *\n * @example\n * ```ts\n * import { Elysia } from 'elysia'\n * import { evlog } from 'evlog/elysia'\n * import { createAxiomDrain } from 'evlog/axiom'\n *\n * const app = new Elysia()\n * .use(evlog({\n * drain: createAxiomDrain(),\n * enrich: (ctx) => {\n * ctx.event.region = process.env.FLY_REGION\n * },\n * }))\n * .get('/health', ({ log }) => {\n * log.set({ route: 'health' })\n * return { ok: true }\n * })\n * .listen(3000)\n * ```\n */\ninterface RequestState {\n finish: (opts?: { status?: number; error?: Error }) => Promise<unknown>\n skipped: boolean\n logger: RequestLogger\n}\n\nexport function evlog(options: EvlogElysiaOptions = {}) {\n const emitted = new WeakSet<Request>()\n const requestState = new WeakMap<Request, RequestState>()\n\n return new Elysia({ name: 'evlog' })\n .derive({ as: 'global' }, ({ request, path, headers }) => {\n const { logger, finish, skipped } = createMiddlewareLogger({\n method: request.method,\n path,\n requestId: headers['x-request-id'] || crypto.randomUUID(),\n // It's recommended to use context.headers instead of context.request.headers\n // because Elysia has fast path for getting headers on Bun\n headers: filterSafeHeaders(headers as Record<string, string>),\n ...options,\n })\n\n if (!skipped) activeLoggers.add(logger)\n storage.enterWith(logger)\n requestState.set(request, { finish, skipped, logger })\n\n return { log: logger }\n })\n .onAfterResponse({ as: 'global' }, async ({ request, set }) => {\n const state = requestState.get(request)\n if (!state || state.skipped || emitted.has(request)) return\n emitted.add(request)\n await state.finish({ status: set.status as number || 200 })\n activeLoggers.delete(state.logger)\n storage.enterWith(undefined as unknown as RequestLogger)\n })\n .onError({ as: 'global' }, async ({ request, error }) => {\n const state = requestState.get(request)\n if (!state || state.skipped || emitted.has(request)) return\n emitted.add(request)\n const err = error instanceof Error ? error : new Error(String(error))\n state.logger.error(err)\n await state.finish({ error: err })\n activeLoggers.delete(state.logger)\n storage.enterWith(undefined as unknown as RequestLogger)\n })\n}\n"],"mappings":";;;;;AAOA,MAAM,UAAU,IAAI,mBAAkC;AAMtD,MAAM,gCAAgB,IAAI,SAAwB;;;;;;;;;;;;;;;;;;;AAsBlD,SAAgB,YAA0E;CACxF,MAAM,SAAS,QAAQ,UAAU;AACjC,KAAI,CAAC,UAAU,CAAC,cAAc,IAAI,OAAO,CACvC,OAAM,IAAI,MACR,kIAED;AAEH,QAAO;;AAgCT,SAAgB,MAAM,UAA8B,EAAE,EAAE;CACtD,MAAM,0BAAU,IAAI,SAAkB;CACtC,MAAM,+BAAe,IAAI,SAAgC;AAEzD,QAAO,IAAI,OAAO,EAAE,MAAM,SAAS,CAAC,CACjC,OAAO,EAAE,IAAI,UAAU,GAAG,EAAE,SAAS,MAAM,cAAc;EACxD,MAAM,EAAE,QAAQ,QAAQ,YAAY,uBAAuB;GACzD,QAAQ,QAAQ;GAChB;GACA,WAAW,QAAQ,mBAAmB,OAAO,YAAY;GAGzD,SAAS,kBAAkB,QAAkC;GAC7D,GAAG;GACJ,CAAC;AAEF,MAAI,CAAC,QAAS,eAAc,IAAI,OAAO;AACvC,UAAQ,UAAU,OAAO;AACzB,eAAa,IAAI,SAAS;GAAE;GAAQ;GAAS;GAAQ,CAAC;AAEtD,SAAO,EAAE,KAAK,QAAQ;GACtB,CACD,gBAAgB,EAAE,IAAI,UAAU,EAAE,OAAO,EAAE,SAAS,UAAU;EAC7D,MAAM,QAAQ,aAAa,IAAI,QAAQ;AACvC,MAAI,CAAC,SAAS,MAAM,WAAW,QAAQ,IAAI,QAAQ,CAAE;AACrD,UAAQ,IAAI,QAAQ;AACpB,QAAM,MAAM,OAAO,EAAE,QAAQ,IAAI,UAAoB,KAAK,CAAC;AAC3D,gBAAc,OAAO,MAAM,OAAO;AAClC,UAAQ,UAAU,KAAA,EAAsC;GACxD,CACD,QAAQ,EAAE,IAAI,UAAU,EAAE,OAAO,EAAE,SAAS,YAAY;EACvD,MAAM,QAAQ,aAAa,IAAI,QAAQ;AACvC,MAAI,CAAC,SAAS,MAAM,WAAW,QAAQ,IAAI,QAAQ,CAAE;AACrD,UAAQ,IAAI,QAAQ;EACpB,MAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AACrE,QAAM,OAAO,MAAM,IAAI;AACvB,QAAM,MAAM,OAAO,EAAE,OAAO,KAAK,CAAC;AAClC,gBAAc,OAAO,MAAM,OAAO;AAClC,UAAQ,UAAU,KAAA,EAAsC;GACxD"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/elysia/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport { Elysia } from 'elysia'\nimport type { RequestLogger } from '../types'\nimport { createMiddlewareLogger, type BaseEvlogOptions } from '../shared/middleware'\nimport { attachForkToLogger } from '../shared/fork'\nimport { extractSafeHeaders } from '../shared/headers'\nimport { filterSafeHeaders } from '../utils'\n\nconst storage = new AsyncLocalStorage<RequestLogger>()\n\n// Tracks loggers that are currently active (within a live request).\n// Elysia uses storage.enterWith() which persists in the async context\n// even after the request ends, so we use this set to distinguish\n// an in-flight logger from a stale one.\nconst activeLoggers = new WeakSet<RequestLogger>()\n\nexport type EvlogElysiaOptions = BaseEvlogOptions\n\n/**\n * Get the request-scoped logger from anywhere in the call stack.\n * Must be called inside a request handled by the `evlog()` plugin.\n *\n * Unlike other frameworks, Elysia uses `storage.enterWith()` which persists\n * beyond the request lifecycle. This accessor additionally checks `activeLoggers`\n * to ensure the logger belongs to an in-flight request.\n *\n * @example\n * ```ts\n * import { useLogger } from 'evlog/elysia'\n *\n * function findUser(id: string) {\n * const log = useLogger()\n * log.set({ user: { id } })\n * }\n * ```\n */\nexport function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T> {\n const logger = storage.getStore()\n if (!logger || !activeLoggers.has(logger)) {\n throw new Error(\n '[evlog] useLogger() was called outside of an evlog plugin context. '\n + 'Make sure app.use(evlog()) is registered before your routes.',\n )\n }\n return logger as RequestLogger<T>\n}\n\n/**\n * Create an evlog plugin for Elysia.\n *\n * @example\n * ```ts\n * import { Elysia } from 'elysia'\n * import { evlog } from 'evlog/elysia'\n * import { createAxiomDrain } from 'evlog/axiom'\n *\n * const app = new Elysia()\n * .use(evlog({\n * drain: createAxiomDrain(),\n * enrich: (ctx) => {\n * ctx.event.region = process.env.FLY_REGION\n * },\n * }))\n * .get('/health', ({ log }) => {\n * log.set({ route: 'health' })\n * return { ok: true }\n * })\n * .listen(3000)\n * ```\n */\ninterface RequestState {\n finish: (opts?: { status?: number; error?: Error }) => Promise<unknown>\n skipped: boolean\n logger: RequestLogger\n}\n\nexport function evlog(options: EvlogElysiaOptions = {}) {\n const emitted = new WeakSet<Request>()\n const requestState = new WeakMap<Request, RequestState>()\n\n return new Elysia({ name: 'evlog' })\n .derive({ as: 'global' }, ({ request, path, headers }) => {\n const middlewareOpts = {\n method: request.method,\n path,\n requestId: headers['x-request-id'] || crypto.randomUUID(),\n // It's recommended to use context.headers instead of context.request.headers\n // because Elysia has fast path for getting headers on Bun\n headers: filterSafeHeaders(headers as Record<string, string>),\n ...options,\n }\n const { logger, finish, skipped } = createMiddlewareLogger(middlewareOpts)\n\n if (!skipped) {\n attachForkToLogger(storage, logger, middlewareOpts, {\n onChildEnter: (child) => {\n activeLoggers.add(child)\n },\n onChildExit: (child) => {\n activeLoggers.delete(child)\n },\n })\n activeLoggers.add(logger)\n }\n storage.enterWith(logger)\n requestState.set(request, { finish, skipped, logger })\n\n return { log: logger }\n })\n .onAfterResponse({ as: 'global' }, async ({ request, set }) => {\n const state = requestState.get(request)\n if (!state || state.skipped || emitted.has(request)) return\n emitted.add(request)\n await state.finish({ status: set.status as number || 200 })\n activeLoggers.delete(state.logger)\n storage.enterWith(undefined as unknown as RequestLogger)\n })\n .onError({ as: 'global' }, async ({ request, error }) => {\n const state = requestState.get(request)\n if (!state || state.skipped || emitted.has(request)) return\n emitted.add(request)\n const err = error instanceof Error ? error : new Error(String(error))\n state.logger.error(err)\n await state.finish({ error: err })\n activeLoggers.delete(state.logger)\n storage.enterWith(undefined as unknown as RequestLogger)\n })\n}\n"],"mappings":";;;;;;AAQA,MAAM,UAAU,IAAI,mBAAkC;AAMtD,MAAM,gCAAgB,IAAI,SAAwB;;;;;;;;;;;;;;;;;;;AAsBlD,SAAgB,YAA0E;CACxF,MAAM,SAAS,QAAQ,UAAU;AACjC,KAAI,CAAC,UAAU,CAAC,cAAc,IAAI,OAAO,CACvC,OAAM,IAAI,MACR,kIAED;AAEH,QAAO;;AAgCT,SAAgB,MAAM,UAA8B,EAAE,EAAE;CACtD,MAAM,0BAAU,IAAI,SAAkB;CACtC,MAAM,+BAAe,IAAI,SAAgC;AAEzD,QAAO,IAAI,OAAO,EAAE,MAAM,SAAS,CAAC,CACjC,OAAO,EAAE,IAAI,UAAU,GAAG,EAAE,SAAS,MAAM,cAAc;EACxD,MAAM,iBAAiB;GACrB,QAAQ,QAAQ;GAChB;GACA,WAAW,QAAQ,mBAAmB,OAAO,YAAY;GAGzD,SAAS,kBAAkB,QAAkC;GAC7D,GAAG;GACJ;EACD,MAAM,EAAE,QAAQ,QAAQ,YAAY,uBAAuB,eAAe;AAE1E,MAAI,CAAC,SAAS;AACZ,sBAAmB,SAAS,QAAQ,gBAAgB;IAClD,eAAe,UAAU;AACvB,mBAAc,IAAI,MAAM;;IAE1B,cAAc,UAAU;AACtB,mBAAc,OAAO,MAAM;;IAE9B,CAAC;AACF,iBAAc,IAAI,OAAO;;AAE3B,UAAQ,UAAU,OAAO;AACzB,eAAa,IAAI,SAAS;GAAE;GAAQ;GAAS;GAAQ,CAAC;AAEtD,SAAO,EAAE,KAAK,QAAQ;GACtB,CACD,gBAAgB,EAAE,IAAI,UAAU,EAAE,OAAO,EAAE,SAAS,UAAU;EAC7D,MAAM,QAAQ,aAAa,IAAI,QAAQ;AACvC,MAAI,CAAC,SAAS,MAAM,WAAW,QAAQ,IAAI,QAAQ,CAAE;AACrD,UAAQ,IAAI,QAAQ;AACpB,QAAM,MAAM,OAAO,EAAE,QAAQ,IAAI,UAAoB,KAAK,CAAC;AAC3D,gBAAc,OAAO,MAAM,OAAO;AAClC,UAAQ,UAAU,KAAA,EAAsC;GACxD,CACD,QAAQ,EAAE,IAAI,UAAU,EAAE,OAAO,EAAE,SAAS,YAAY;EACvD,MAAM,QAAQ,aAAa,IAAI,QAAQ;AACvC,MAAI,CAAC,SAAS,MAAM,WAAW,QAAQ,IAAI,QAAQ,CAAE;AACrD,UAAQ,IAAI,QAAQ;EACpB,MAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AACrE,QAAM,OAAO,MAAM,IAAI;AACvB,QAAM,MAAM,OAAO,EAAE,OAAO,KAAK,CAAC;AAClC,gBAAc,OAAO,MAAM,OAAO;AAClC,UAAQ,UAAU,KAAA,EAAsC;GACxD"}