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.
- package/README.md +38 -0
- package/dist/adapters/axiom.d.mts +1 -1
- package/dist/adapters/better-stack.d.mts +1 -1
- package/dist/adapters/datadog.d.mts +1 -1
- package/dist/adapters/fs.d.mts +1 -1
- package/dist/adapters/hyperdx.d.mts +1 -1
- package/dist/adapters/otlp.d.mts +1 -1
- package/dist/adapters/posthog.d.mts +1 -1
- package/dist/adapters/sentry.d.mts +1 -1
- package/dist/ai/index.d.mts +1 -1
- package/dist/better-auth/index.d.mts +220 -0
- package/dist/better-auth/index.d.mts.map +1 -0
- package/dist/better-auth/index.mjs +205 -0
- package/dist/better-auth/index.mjs.map +1 -0
- package/dist/browser.d.mts +1 -1
- package/dist/elysia/index.d.mts +2 -2
- package/dist/elysia/index.d.mts.map +1 -1
- package/dist/elysia/index.mjs +16 -4
- package/dist/elysia/index.mjs.map +1 -1
- package/dist/enrichers.d.mts +1 -1
- package/dist/{error-WRz4_F3W.d.mts → error-B9CiGK_i.d.mts} +2 -2
- package/dist/{error-WRz4_F3W.d.mts.map → error-B9CiGK_i.d.mts.map} +1 -1
- package/dist/error.d.mts +1 -1
- package/dist/{errors-J2kt7mZh.d.mts → errors-Dr0r4OpR.d.mts} +2 -2
- package/dist/{errors-J2kt7mZh.d.mts.map → errors-Dr0r4OpR.d.mts.map} +1 -1
- package/dist/express/index.d.mts +2 -2
- package/dist/express/index.d.mts.map +1 -1
- package/dist/express/index.mjs +8 -4
- package/dist/express/index.mjs.map +1 -1
- package/dist/fastify/index.d.mts +2 -2
- package/dist/fastify/index.d.mts.map +1 -1
- package/dist/fastify/index.mjs +8 -4
- package/dist/fastify/index.mjs.map +1 -1
- package/dist/fork-Y4z8iHti.mjs +72 -0
- package/dist/fork-Y4z8iHti.mjs.map +1 -0
- package/dist/headers-D74M0wsg.mjs +30 -0
- package/dist/headers-D74M0wsg.mjs.map +1 -0
- package/dist/hono/index.d.mts +2 -2
- package/dist/hono/index.mjs +2 -1
- package/dist/hono/index.mjs.map +1 -1
- package/dist/http.d.mts +1 -1
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +1 -1
- package/dist/{logger-DY0X5oQd.mjs → logger-DnobymUQ.mjs} +40 -3
- package/dist/logger-DnobymUQ.mjs.map +1 -0
- package/dist/{logger-Bm0k3Hf3.d.mts → logger-Dp6nYWjH.d.mts} +6 -2
- package/dist/logger-Dp6nYWjH.d.mts.map +1 -0
- package/dist/logger.d.mts +1 -1
- package/dist/logger.mjs +1 -1
- package/dist/{headers-ht4yS2mx.mjs → middleware-BtBuosFV.mjs} +9 -30
- package/dist/middleware-BtBuosFV.mjs.map +1 -0
- package/dist/{middleware-D_igVy93.d.mts → middleware-FgC1OdOD.d.mts} +14 -3
- package/dist/{middleware-D_igVy93.d.mts.map → middleware-FgC1OdOD.d.mts.map} +1 -1
- package/dist/nestjs/index.d.mts +2 -2
- package/dist/nestjs/index.d.mts.map +1 -1
- package/dist/nestjs/index.mjs +8 -4
- package/dist/nestjs/index.mjs.map +1 -1
- package/dist/next/client.d.mts +1 -1
- package/dist/next/index.d.mts +4 -4
- package/dist/next/index.d.mts.map +1 -1
- package/dist/next/index.mjs +15 -1
- package/dist/next/index.mjs.map +1 -1
- package/dist/next/instrumentation.d.mts +1 -1
- package/dist/next/instrumentation.mjs +1 -1
- package/dist/nitro/module.d.mts +2 -2
- package/dist/nitro/plugin.mjs +1 -1
- package/dist/nitro/v3/index.d.mts +2 -2
- package/dist/nitro/v3/module.d.mts +1 -1
- package/dist/nitro/v3/plugin.mjs +1 -1
- package/dist/nitro/v3/useLogger.d.mts +1 -1
- package/dist/{nitro-BeRXZcBd.d.mts → nitro-CDHLfRdw.d.mts} +2 -2
- package/dist/{nitro-BeRXZcBd.d.mts.map → nitro-CDHLfRdw.d.mts.map} +1 -1
- package/dist/nuxt/module.d.mts +1 -1
- package/dist/nuxt/module.mjs +1 -1
- package/dist/{parseError-DhXS_vzM.d.mts → parseError-DM-lyezZ.d.mts} +2 -2
- package/dist/parseError-DM-lyezZ.d.mts.map +1 -0
- package/dist/react-router/index.d.mts +2 -2
- package/dist/react-router/index.d.mts.map +1 -1
- package/dist/react-router/index.mjs +8 -4
- package/dist/react-router/index.mjs.map +1 -1
- package/dist/runtime/client/log.d.mts +1 -1
- package/dist/runtime/server/routes/_evlog/ingest.post.mjs +1 -1
- package/dist/runtime/server/useLogger.d.mts +1 -1
- package/dist/runtime/utils/parseError.d.mts +2 -2
- package/dist/{storage-DpLJYMoc.mjs → storage-CFGTn37X.mjs} +1 -1
- package/dist/{storage-DpLJYMoc.mjs.map → storage-CFGTn37X.mjs.map} +1 -1
- package/dist/sveltekit/index.d.mts +2 -2
- package/dist/sveltekit/index.d.mts.map +1 -1
- package/dist/sveltekit/index.mjs +8 -4
- package/dist/sveltekit/index.mjs.map +1 -1
- package/dist/toolkit.d.mts +41 -4
- package/dist/toolkit.d.mts.map +1 -1
- package/dist/toolkit.mjs +5 -3
- package/dist/{types-D5OwxZCw.d.mts → types-DbzDln7O.d.mts} +50 -3
- package/dist/types-DbzDln7O.d.mts.map +1 -0
- package/dist/types.d.mts +1 -1
- package/dist/{useLogger-Dcj1Nrsa.d.mts → useLogger-N5A-d5l9.d.mts} +2 -2
- package/dist/{useLogger-Dcj1Nrsa.d.mts.map → useLogger-N5A-d5l9.d.mts.map} +1 -1
- package/dist/{utils-Bnc95-VC.d.mts → utils-DnX6VMNi.d.mts} +2 -2
- package/dist/{utils-Bnc95-VC.d.mts.map → utils-DnX6VMNi.d.mts.map} +1 -1
- package/dist/utils.d.mts +1 -1
- package/dist/vite/index.d.mts +1 -1
- package/dist/workers.d.mts +1 -1
- package/dist/workers.mjs +1 -1
- package/package.json +16 -3
- package/dist/headers-ht4yS2mx.mjs.map +0 -1
- package/dist/logger-Bm0k3Hf3.d.mts.map +0 -1
- package/dist/logger-DY0X5oQd.mjs.map +0 -1
- package/dist/parseError-DhXS_vzM.d.mts.map +0 -1
- 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
|
[](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).
|
package/dist/adapters/fs.d.mts
CHANGED
package/dist/adapters/otlp.d.mts
CHANGED
package/dist/ai/index.d.mts
CHANGED
|
@@ -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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"}
|
package/dist/browser.d.mts
CHANGED
package/dist/elysia/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { _ as RequestLogger } from "../types-
|
|
2
|
-
import { t as BaseEvlogOptions } from "../middleware-
|
|
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":";;;;;
|
|
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"}
|
package/dist/elysia/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { filterSafeHeaders } from "../utils.mjs";
|
|
2
|
-
import {
|
|
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
|
|
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
|
-
|
|
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
|
|
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"}
|