evlog 2.1.0 → 2.4.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 (39) hide show
  1. package/README.md +105 -25
  2. package/dist/elysia/index.d.mts +58 -0
  3. package/dist/elysia/index.d.mts.map +1 -0
  4. package/dist/elysia/index.mjs +72 -0
  5. package/dist/elysia/index.mjs.map +1 -0
  6. package/dist/express/index.d.mts +34 -0
  7. package/dist/express/index.d.mts.map +1 -0
  8. package/dist/express/index.mjs +47 -0
  9. package/dist/express/index.mjs.map +1 -0
  10. package/dist/fastify/index.d.mts +37 -0
  11. package/dist/fastify/index.d.mts.map +1 -0
  12. package/dist/fastify/index.mjs +73 -0
  13. package/dist/fastify/index.mjs.map +1 -0
  14. package/dist/headers-CXOd5EyZ.mjs +141 -0
  15. package/dist/headers-CXOd5EyZ.mjs.map +1 -0
  16. package/dist/hono/index.d.mts +47 -0
  17. package/dist/hono/index.d.mts.map +1 -0
  18. package/dist/hono/index.mjs +48 -0
  19. package/dist/hono/index.mjs.map +1 -0
  20. package/dist/middleware-BoVCgsfQ.d.mts +36 -0
  21. package/dist/middleware-BoVCgsfQ.d.mts.map +1 -0
  22. package/dist/nestjs/index.d.mts +83 -0
  23. package/dist/nestjs/index.d.mts.map +1 -0
  24. package/dist/nestjs/index.mjs +109 -0
  25. package/dist/nestjs/index.mjs.map +1 -0
  26. package/dist/next/index.d.mts +3 -34
  27. package/dist/next/index.d.mts.map +1 -1
  28. package/dist/nitro/module.d.mts +1 -1
  29. package/dist/nitro/v3/module.d.mts +1 -1
  30. package/dist/{nitro-mfub2f8G.d.mts → nitro-BRisWfGy.d.mts} +1 -1
  31. package/dist/{nitro-mfub2f8G.d.mts.map → nitro-BRisWfGy.d.mts.map} +1 -1
  32. package/dist/nuxt/module.mjs +1 -1
  33. package/dist/storage-Dd3PHiMh.mjs +29 -0
  34. package/dist/storage-Dd3PHiMh.mjs.map +1 -0
  35. package/dist/sveltekit/index.d.mts +128 -0
  36. package/dist/sveltekit/index.d.mts.map +1 -0
  37. package/dist/sveltekit/index.mjs +163 -0
  38. package/dist/sveltekit/index.mjs.map +1 -0
  39. package/package.json +83 -5
package/README.md CHANGED
@@ -375,43 +375,116 @@ Notes:
375
375
 
376
376
  ## Hono
377
377
 
378
- Use the standalone API to create one wide event per request from a Hono middleware.
379
-
380
378
  ```typescript
381
379
  // src/index.ts
382
- import { serve } from '@hono/node-server'
383
380
  import { Hono } from 'hono'
384
- import { createRequestLogger, initLogger } from 'evlog'
381
+ import { initLogger } from 'evlog'
382
+ import { evlog, type EvlogVariables } from 'evlog/hono'
385
383
 
386
- initLogger({
387
- env: { service: 'hono-api' },
384
+ initLogger({ env: { service: 'hono-api' } })
385
+
386
+ const app = new Hono<EvlogVariables>()
387
+ app.use(evlog())
388
+
389
+ app.get('/api/users', (c) => {
390
+ const log = c.get('log')
391
+ log.set({ users: { count: 42 } })
392
+ return c.json({ users: [] })
388
393
  })
394
+ ```
389
395
 
390
- const app = new Hono()
396
+ See the full [hono example](https://github.com/HugoRCD/evlog/tree/main/examples/hono) for a complete working project.
391
397
 
392
- app.use('*', async (c, next) => {
393
- const startedAt = Date.now()
394
- const log = createRequestLogger({ method: c.req.method, path: c.req.path })
398
+ ## Express
395
399
 
396
- try {
397
- await next()
398
- } catch (error) {
399
- log.error(error as Error)
400
- throw error
401
- } finally {
402
- log.emit({
403
- status: c.res.status,
404
- duration: Date.now() - startedAt,
405
- })
406
- }
400
+ ```typescript
401
+ // src/index.ts
402
+ import express from 'express'
403
+ import { initLogger } from 'evlog'
404
+ import { evlog, useLogger } from 'evlog/express'
405
+
406
+ initLogger({ env: { service: 'express-api' } })
407
+
408
+ const app = express()
409
+ app.use(evlog())
410
+
411
+ app.get('/api/users', (req, res) => {
412
+ req.log.set({ users: { count: 42 } })
413
+ res.json({ users: [] })
407
414
  })
415
+ ```
416
+
417
+ Use `useLogger()` to access the logger from anywhere in the call stack without passing `req`.
418
+
419
+ See the full [express example](https://github.com/HugoRCD/evlog/tree/main/examples/express) for a complete working project.
420
+
421
+ ## Fastify
422
+
423
+ ```typescript
424
+ // src/index.ts
425
+ import Fastify from 'fastify'
426
+ import { initLogger } from 'evlog'
427
+ import { evlog, useLogger } from 'evlog/fastify'
428
+
429
+ initLogger({ env: { service: 'fastify-api' } })
408
430
 
409
- app.get('/health', (c) => c.json({ ok: true }))
431
+ const app = Fastify({ logger: false })
432
+ await app.register(evlog)
410
433
 
411
- serve({ fetch: app.fetch, port: 3000 })
434
+ app.get('/api/users', async (request) => {
435
+ request.log.set({ users: { count: 42 } })
436
+ return { users: [] }
437
+ })
412
438
  ```
413
439
 
414
- See the full [hono example](https://github.com/HugoRCD/evlog/tree/main/examples/hono) for a complete working project.
440
+ `request.log` is the evlog wide-event logger (shadows Fastify's built-in pino logger on the request). Use `useLogger()` to access the logger from anywhere in the call stack.
441
+
442
+ See the full [fastify example](https://github.com/HugoRCD/evlog/tree/main/examples/fastify) for a complete working project.
443
+
444
+ ## Elysia
445
+
446
+ ```typescript
447
+ // src/index.ts
448
+ import { Elysia } from 'elysia'
449
+ import { initLogger } from 'evlog'
450
+ import { evlog, useLogger } from 'evlog/elysia'
451
+
452
+ initLogger({ env: { service: 'elysia-api' } })
453
+
454
+ const app = new Elysia()
455
+ .use(evlog())
456
+ .get('/api/users', ({ log }) => {
457
+ log.set({ users: { count: 42 } })
458
+ return { users: [] }
459
+ })
460
+ .listen(3000)
461
+ ```
462
+
463
+ Use `useLogger()` to access the logger from anywhere in the call stack.
464
+
465
+ See the full [elysia example](https://github.com/HugoRCD/evlog/tree/main/examples/elysia) for a complete working project.
466
+
467
+ ## NestJS
468
+
469
+ ```typescript
470
+ // src/app.module.ts
471
+ import { Module } from '@nestjs/common'
472
+ import { EvlogModule } from 'evlog/nestjs'
473
+
474
+ @Module({
475
+ imports: [EvlogModule.forRoot()],
476
+ })
477
+ export class AppModule {}
478
+
479
+ // In any controller or service:
480
+ import { useLogger } from 'evlog/nestjs'
481
+ const log = useLogger()
482
+ log.set({ users: { count: 42 } })
483
+ ```
484
+
485
+ `EvlogModule.forRoot()` registers a global middleware that creates a request-scoped logger for every request. Use `useLogger()` to access it anywhere in the call stack, or `req.log` directly. Supports `forRootAsync()` for async configuration.
486
+
487
+ See the full [nestjs example](https://github.com/HugoRCD/evlog/tree/main/examples/nestjs) for a complete working project.
415
488
 
416
489
  ## Browser
417
490
 
@@ -991,12 +1064,19 @@ try {
991
1064
  |-----------|-------------|
992
1065
  | **Nuxt** | `modules: ['evlog/nuxt']` |
993
1066
  | **Next.js** | `createEvlog()` factory with `import { createEvlog } from 'evlog/next'` ([example](./examples/nextjs)) |
1067
+ | **SvelteKit** | `export const { handle, handleError } = createEvlogHooks()` with `import { createEvlogHooks } from 'evlog/sveltekit'` ([example](./examples/sveltekit)) |
994
1068
  | **Nitro v3** | `modules: [evlog()]` with `import evlog from 'evlog/nitro/v3'` |
995
1069
  | **Nitro v2** | `modules: [evlog()]` with `import evlog from 'evlog/nitro'` |
1070
+ | **TanStack Start** | Nitro v3 module setup ([example](./examples/tanstack-start)) |
1071
+ | **NestJS** | `EvlogModule.forRoot()` with `import { EvlogModule } from 'evlog/nestjs'` ([example](./examples/nestjs)) |
1072
+ | **Express** | `app.use(evlog())` with `import { evlog } from 'evlog/express'` ([example](./examples/express)) |
1073
+ | **Hono** | `app.use(evlog())` with `import { evlog } from 'evlog/hono'` ([example](./examples/hono)) |
1074
+ | **Fastify** | `app.register(evlog)` with `import { evlog } from 'evlog/fastify'` ([example](./examples/fastify)) |
1075
+ | **Elysia** | `.use(evlog())` with `import { evlog } from 'evlog/elysia'` ([example](./examples/elysia)) |
1076
+ | **Cloudflare Workers** | Manual setup with `import { initLogger, createRequestLogger } from 'evlog'` ([example](./examples/workers)) |
996
1077
  | **Analog** | Nitro v2 module setup |
997
1078
  | **Vinxi** | Nitro v2 module setup |
998
1079
  | **SolidStart** | Nitro v2 module setup ([example](./examples/solidstart)) |
999
- | **TanStack Start** | Nitro v3 module setup ([example](./examples/tanstack-start)) |
1000
1080
 
1001
1081
  ## Agent Skills
1002
1082
 
@@ -0,0 +1,58 @@
1
+ import { RequestLogger } from "../types.mjs";
2
+ import { t as BaseEvlogOptions } from "../middleware-BoVCgsfQ.mjs";
3
+ import { Elysia } from "elysia";
4
+
5
+ //#region src/elysia/index.d.ts
6
+ type EvlogElysiaOptions = BaseEvlogOptions;
7
+ /**
8
+ * Get the request-scoped logger from anywhere in the call stack.
9
+ * Must be called inside a request handled by the `evlog()` plugin.
10
+ *
11
+ * Unlike other frameworks, Elysia uses `storage.enterWith()` which persists
12
+ * beyond the request lifecycle. This accessor additionally checks `activeLoggers`
13
+ * to ensure the logger belongs to an in-flight request.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { useLogger } from 'evlog/elysia'
18
+ *
19
+ * function findUser(id: string) {
20
+ * const log = useLogger()
21
+ * log.set({ user: { id } })
22
+ * }
23
+ * ```
24
+ */
25
+ declare function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T>;
26
+ declare function evlog(options?: EvlogElysiaOptions): Elysia<"", {
27
+ decorator: {};
28
+ store: {};
29
+ derive: {
30
+ readonly log: RequestLogger<Record<string, unknown>>;
31
+ };
32
+ resolve: {};
33
+ }, {
34
+ typebox: {};
35
+ error: {};
36
+ }, {
37
+ schema: {};
38
+ standaloneSchema: {};
39
+ macro: {};
40
+ macroFn: {};
41
+ parser: {};
42
+ response: {};
43
+ }, {}, {
44
+ derive: {};
45
+ resolve: {};
46
+ schema: {};
47
+ standaloneSchema: {};
48
+ response: {};
49
+ }, {
50
+ derive: {};
51
+ resolve: {};
52
+ schema: {};
53
+ standaloneSchema: {};
54
+ response: {};
55
+ }>;
56
+ //#endregion
57
+ export { EvlogElysiaOptions, evlog, useLogger };
58
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/elysia/index.ts"],"mappings":";;;;;KAcY,kBAAA,GAAqB,gBAAA;;AAAjC;;;;;AAoBA;;;;;;;;;;;;iBAAgB,SAAA,oBAA6B,MAAA,kBAAA,CAAA,GAA4B,aAAA,CAAc,CAAA;AAAA,iBAwCvE,KAAA,CAAM,OAAA,GAAS,kBAAA,GAAuB,MAAA"}
@@ -0,0 +1,72 @@
1
+ import { r as createMiddlewareLogger, t as extractSafeHeaders } from "../headers-CXOd5EyZ.mjs";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ import { Elysia } from "elysia";
4
+
5
+ //#region src/elysia/index.ts
6
+ const storage = new AsyncLocalStorage();
7
+ const activeLoggers = /* @__PURE__ */ new WeakSet();
8
+ /**
9
+ * Get the request-scoped logger from anywhere in the call stack.
10
+ * Must be called inside a request handled by the `evlog()` plugin.
11
+ *
12
+ * Unlike other frameworks, Elysia uses `storage.enterWith()` which persists
13
+ * beyond the request lifecycle. This accessor additionally checks `activeLoggers`
14
+ * to ensure the logger belongs to an in-flight request.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { useLogger } from 'evlog/elysia'
19
+ *
20
+ * function findUser(id: string) {
21
+ * const log = useLogger()
22
+ * log.set({ user: { id } })
23
+ * }
24
+ * ```
25
+ */
26
+ function useLogger() {
27
+ const logger = storage.getStore();
28
+ if (!logger || !activeLoggers.has(logger)) throw new Error("[evlog] useLogger() was called outside of an evlog plugin context. Make sure app.use(evlog()) is registered before your routes.");
29
+ return logger;
30
+ }
31
+ function evlog(options = {}) {
32
+ const emitted = /* @__PURE__ */ new WeakSet();
33
+ const requestState = /* @__PURE__ */ new WeakMap();
34
+ return new Elysia({ name: "evlog" }).derive({ as: "global" }, ({ request }) => {
35
+ const url = new URL(request.url);
36
+ const { logger, finish, skipped } = createMiddlewareLogger({
37
+ method: request.method,
38
+ path: url.pathname,
39
+ requestId: request.headers.get("x-request-id") || crypto.randomUUID(),
40
+ headers: extractSafeHeaders(request.headers),
41
+ ...options
42
+ });
43
+ if (!skipped) activeLoggers.add(logger);
44
+ storage.enterWith(logger);
45
+ requestState.set(request, {
46
+ finish,
47
+ skipped,
48
+ logger
49
+ });
50
+ return { log: logger };
51
+ }).onAfterHandle({ as: "global" }, async ({ request, set }) => {
52
+ const state = requestState.get(request);
53
+ if (!state || state.skipped || emitted.has(request)) return;
54
+ emitted.add(request);
55
+ await state.finish({ status: set.status || 200 });
56
+ activeLoggers.delete(state.logger);
57
+ storage.enterWith(void 0);
58
+ }).onError({ as: "global" }, async ({ request, error }) => {
59
+ const state = requestState.get(request);
60
+ if (!state || state.skipped || emitted.has(request)) return;
61
+ emitted.add(request);
62
+ const err = error instanceof Error ? error : new Error(String(error));
63
+ state.logger.error(err);
64
+ await state.finish({ error: err });
65
+ activeLoggers.delete(state.logger);
66
+ storage.enterWith(void 0);
67
+ });
68
+ }
69
+
70
+ //#endregion
71
+ export { evlog, useLogger };
72
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +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'\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 }) => {\n const url = new URL(request.url)\n\n const { logger, finish, skipped } = createMiddlewareLogger({\n method: request.method,\n path: url.pathname,\n requestId: request.headers.get('x-request-id') || crypto.randomUUID(),\n headers: extractSafeHeaders(request.headers),\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 .onAfterHandle({ 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":";;;;;AAMA,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,cAAc;EACzC,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;EAEhC,MAAM,EAAE,QAAQ,QAAQ,YAAY,uBAAuB;GACzD,QAAQ,QAAQ;GAChB,MAAM,IAAI;GACV,WAAW,QAAQ,QAAQ,IAAI,eAAe,IAAI,OAAO,YAAY;GACrE,SAAS,mBAAmB,QAAQ,QAAQ;GAC5C,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,cAAc,EAAE,IAAI,UAAU,EAAE,OAAO,EAAE,SAAS,UAAU;EAC3D,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,OAAsC;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,OAAsC;GACxD"}
@@ -0,0 +1,34 @@
1
+ import { RequestLogger } from "../types.mjs";
2
+ import { t as BaseEvlogOptions } from "../middleware-BoVCgsfQ.mjs";
3
+ import { RequestHandler } from "express";
4
+
5
+ //#region src/express/index.d.ts
6
+ declare const useLogger: <T extends object = Record<string, unknown>>() => RequestLogger<T>;
7
+ type EvlogExpressOptions = BaseEvlogOptions;
8
+ declare module 'express-serve-static-core' {
9
+ interface Request {
10
+ log: RequestLogger;
11
+ }
12
+ }
13
+ /**
14
+ * Create an evlog middleware for Express.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import express from 'express'
19
+ * import { evlog } from 'evlog/express'
20
+ * import { createAxiomDrain } from 'evlog/axiom'
21
+ *
22
+ * const app = express()
23
+ * app.use(evlog({
24
+ * drain: createAxiomDrain(),
25
+ * enrich: (ctx) => {
26
+ * ctx.event.region = process.env.FLY_REGION
27
+ * },
28
+ * }))
29
+ * ```
30
+ */
31
+ declare function evlog(options?: EvlogExpressOptions): RequestHandler;
32
+ //#endregion
33
+ export { EvlogExpressOptions, evlog, useLogger };
34
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/express/index.ts"],"mappings":";;;;;cAMiB,SAAA,sBAAS,MAAA,wBAAA,aAAA,CAAA,CAAA;AAAA,KAId,mBAAA,GAAsB,gBAAA;AAAA;EAAA,UAKtB,OAAA;IACR,GAAA,EAAK,aAAA;EAAA;AAAA;;;;;;;;;AANT;;;;;AAEoB;;;;;iBA0BJ,KAAA,CAAM,OAAA,GAAS,mBAAA,GAA2B,cAAA"}
@@ -0,0 +1,47 @@
1
+ import { n as extractSafeNodeHeaders, r as createMiddlewareLogger } from "../headers-CXOd5EyZ.mjs";
2
+ import { t as createLoggerStorage } from "../storage-Dd3PHiMh.mjs";
3
+
4
+ //#region src/express/index.ts
5
+ const { storage, useLogger } = createLoggerStorage("middleware context. Make sure app.use(evlog()) is registered before your routes.");
6
+ /**
7
+ * Create an evlog middleware for Express.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import express from 'express'
12
+ * import { evlog } from 'evlog/express'
13
+ * import { createAxiomDrain } from 'evlog/axiom'
14
+ *
15
+ * const app = express()
16
+ * app.use(evlog({
17
+ * drain: createAxiomDrain(),
18
+ * enrich: (ctx) => {
19
+ * ctx.event.region = process.env.FLY_REGION
20
+ * },
21
+ * }))
22
+ * ```
23
+ */
24
+ function evlog(options = {}) {
25
+ return (req, res, next) => {
26
+ const { logger, finish, skipped } = createMiddlewareLogger({
27
+ method: req.method,
28
+ path: req.path,
29
+ requestId: req.get("x-request-id") || crypto.randomUUID(),
30
+ headers: extractSafeNodeHeaders(req.headers),
31
+ ...options
32
+ });
33
+ if (skipped) {
34
+ next();
35
+ return;
36
+ }
37
+ req.log = logger;
38
+ res.on("finish", () => {
39
+ finish({ status: res.statusCode }).catch(() => {});
40
+ });
41
+ storage.run(logger, () => next());
42
+ };
43
+ }
44
+
45
+ //#endregion
46
+ export { evlog, useLogger };
47
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/express/index.ts"],"sourcesContent":["import type { Request, Response, NextFunction, RequestHandler } from 'express'\nimport type { RequestLogger } from '../types'\nimport { createMiddlewareLogger, type BaseEvlogOptions } from '../shared/middleware'\nimport { extractSafeNodeHeaders } from '../shared/headers'\nimport { createLoggerStorage } from '../shared/storage'\n\nconst { storage, useLogger } = createLoggerStorage(\n 'middleware context. Make sure app.use(evlog()) is registered before your routes.',\n)\n\nexport type EvlogExpressOptions = BaseEvlogOptions\n\nexport { useLogger }\n\ndeclare module 'express-serve-static-core' {\n interface Request {\n log: RequestLogger\n }\n}\n\n/**\n * Create an evlog middleware for Express.\n *\n * @example\n * ```ts\n * import express from 'express'\n * import { evlog } from 'evlog/express'\n * import { createAxiomDrain } from 'evlog/axiom'\n *\n * const app = express()\n * app.use(evlog({\n * drain: createAxiomDrain(),\n * enrich: (ctx) => {\n * ctx.event.region = process.env.FLY_REGION\n * },\n * }))\n * ```\n */\nexport function evlog(options: EvlogExpressOptions = {}): RequestHandler {\n return (req: Request, res: Response, next: NextFunction) => {\n const { logger, finish, skipped } = createMiddlewareLogger({\n method: req.method,\n path: req.path,\n requestId: req.get('x-request-id') || crypto.randomUUID(),\n headers: extractSafeNodeHeaders(req.headers),\n ...options,\n })\n\n if (skipped) {\n next()\n return\n }\n\n req.log = logger\n\n res.on('finish', () => {\n finish({ status: res.statusCode }).catch(() => {})\n })\n\n storage.run(logger, () => next())\n }\n}\n"],"mappings":";;;;AAMA,MAAM,EAAE,SAAS,cAAc,oBAC7B,mFACD;;;;;;;;;;;;;;;;;;;AA8BD,SAAgB,MAAM,UAA+B,EAAE,EAAkB;AACvE,SAAQ,KAAc,KAAe,SAAuB;EAC1D,MAAM,EAAE,QAAQ,QAAQ,YAAY,uBAAuB;GACzD,QAAQ,IAAI;GACZ,MAAM,IAAI;GACV,WAAW,IAAI,IAAI,eAAe,IAAI,OAAO,YAAY;GACzD,SAAS,uBAAuB,IAAI,QAAQ;GAC5C,GAAG;GACJ,CAAC;AAEF,MAAI,SAAS;AACX,SAAM;AACN;;AAGF,MAAI,MAAM;AAEV,MAAI,GAAG,gBAAgB;AACrB,UAAO,EAAE,QAAQ,IAAI,YAAY,CAAC,CAAC,YAAY,GAAG;IAClD;AAEF,UAAQ,IAAI,cAAc,MAAM,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { RequestLogger } from "../types.mjs";
2
+ import { t as BaseEvlogOptions } from "../middleware-BoVCgsfQ.mjs";
3
+ import { FastifyPluginCallback } from "fastify";
4
+
5
+ //#region src/fastify/index.d.ts
6
+ declare const useLogger: <T extends object = Record<string, unknown>>() => RequestLogger<T>;
7
+ type EvlogFastifyOptions = BaseEvlogOptions;
8
+ declare module 'fastify' {
9
+ interface FastifyRequest {
10
+ log: any;
11
+ }
12
+ }
13
+ /**
14
+ * Create an evlog plugin for Fastify.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import Fastify from 'fastify'
19
+ * import { initLogger } from 'evlog'
20
+ * import { evlog } from 'evlog/fastify'
21
+ * import { createAxiomDrain } from 'evlog/axiom'
22
+ *
23
+ * initLogger({ env: { service: 'fastify-api' } })
24
+ *
25
+ * const app = Fastify()
26
+ * await app.register(evlog, {
27
+ * drain: createAxiomDrain(),
28
+ * enrich: (ctx) => {
29
+ * ctx.event.region = process.env.FLY_REGION
30
+ * },
31
+ * })
32
+ * ```
33
+ */
34
+ declare const evlog: FastifyPluginCallback<BaseEvlogOptions>;
35
+ //#endregion
36
+ export { EvlogFastifyOptions, evlog, useLogger };
37
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/fastify/index.ts"],"mappings":";;;;;cAMiB,SAAA,sBAAS,MAAA,wBAAA,aAAA,CAAA,CAAA;AAAA,KAId,mBAAA,GAAsB,gBAAA;AAAA;EAAA,UAKtB,cAAA;IAER,GAAA;EAAA;AAAA;;;;;;;;;AAPJ;;;;;AAEoB;;;;;;AAyFpB;;cAAa,KAAA,EAAK,qBAAA,CAAA,gBAAA"}
@@ -0,0 +1,73 @@
1
+ import { n as extractSafeNodeHeaders, r as createMiddlewareLogger } from "../headers-CXOd5EyZ.mjs";
2
+ import { t as createLoggerStorage } from "../storage-Dd3PHiMh.mjs";
3
+
4
+ //#region src/fastify/index.ts
5
+ const { storage, useLogger } = createLoggerStorage("plugin context. Make sure app.register(evlog) is called before your routes.");
6
+ const evlogPlugin = (fastify, options, done) => {
7
+ const emitted = /* @__PURE__ */ new WeakSet();
8
+ const requestState = /* @__PURE__ */ new WeakMap();
9
+ fastify.addHook("onRequest", (request, _reply, done) => {
10
+ const headers = extractSafeNodeHeaders(request.headers);
11
+ const path = new URL(request.url, "http://localhost").pathname;
12
+ const { logger, finish, skipped } = createMiddlewareLogger({
13
+ method: request.method,
14
+ path,
15
+ requestId: headers["x-request-id"] || crypto.randomUUID(),
16
+ headers,
17
+ ...options
18
+ });
19
+ if (skipped) {
20
+ done();
21
+ return;
22
+ }
23
+ const req = request;
24
+ req.log = logger;
25
+ requestState.set(request, { finish });
26
+ storage.run(logger, () => done());
27
+ });
28
+ fastify.addHook("onResponse", async (request, reply) => {
29
+ const state = requestState.get(request);
30
+ if (!state || emitted.has(request)) return;
31
+ emitted.add(request);
32
+ await state.finish({ status: reply.statusCode });
33
+ });
34
+ fastify.addHook("onError", async (request, _reply, error) => {
35
+ const state = requestState.get(request);
36
+ if (!state || emitted.has(request)) return;
37
+ emitted.add(request);
38
+ const logger = request.log;
39
+ const err = error instanceof Error ? error : new Error(String(error));
40
+ if (logger && typeof logger.error === "function") logger.error(err);
41
+ await state.finish({ error: err });
42
+ });
43
+ done();
44
+ };
45
+ const plugin = evlogPlugin;
46
+ plugin[Symbol.for("skip-override")] = true;
47
+ plugin[Symbol.for("fastify.display-name")] = "evlog";
48
+ /**
49
+ * Create an evlog plugin for Fastify.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * import Fastify from 'fastify'
54
+ * import { initLogger } from 'evlog'
55
+ * import { evlog } from 'evlog/fastify'
56
+ * import { createAxiomDrain } from 'evlog/axiom'
57
+ *
58
+ * initLogger({ env: { service: 'fastify-api' } })
59
+ *
60
+ * const app = Fastify()
61
+ * await app.register(evlog, {
62
+ * drain: createAxiomDrain(),
63
+ * enrich: (ctx) => {
64
+ * ctx.event.region = process.env.FLY_REGION
65
+ * },
66
+ * })
67
+ * ```
68
+ */
69
+ const evlog = evlogPlugin;
70
+
71
+ //#endregion
72
+ export { evlog, useLogger };
73
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/fastify/index.ts"],"sourcesContent":["import type { FastifyPluginCallback } from 'fastify'\nimport type { RequestLogger } from '../types'\nimport { createMiddlewareLogger, type BaseEvlogOptions } from '../shared/middleware'\nimport { extractSafeNodeHeaders } from '../shared/headers'\nimport { createLoggerStorage } from '../shared/storage'\n\nconst { storage, useLogger } = createLoggerStorage(\n 'plugin context. Make sure app.register(evlog) is called before your routes.',\n)\n\nexport type EvlogFastifyOptions = BaseEvlogOptions\n\nexport { useLogger }\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n // Overrides Fastify's built-in pino logger on the request with evlog's RequestLogger.\n log: any\n }\n}\n\ninterface RequestState {\n finish: (opts?: { status?: number; error?: Error }) => Promise<unknown>\n}\n\nconst evlogPlugin: FastifyPluginCallback<EvlogFastifyOptions> = (fastify, options, done) => {\n const emitted = new WeakSet<object>()\n const requestState = new WeakMap<object, RequestState>()\n\n fastify.addHook('onRequest', (request, _reply, done) => {\n const headers = extractSafeNodeHeaders(request.headers)\n const path = new URL(request.url, 'http://localhost').pathname\n\n const { logger, finish, skipped } = createMiddlewareLogger({\n method: request.method,\n path,\n requestId: headers['x-request-id'] || crypto.randomUUID(),\n headers,\n ...options,\n })\n\n if (skipped) {\n done()\n return\n }\n\n // Shadow Fastify's built-in pino logger with evlog's request-scoped logger\n const req = request as any\n req.log = logger\n requestState.set(request, { finish })\n\n storage.run(logger, () => done())\n })\n\n fastify.addHook('onResponse', async (request, reply) => {\n const state = requestState.get(request)\n if (!state || emitted.has(request)) return\n emitted.add(request)\n await state.finish({ status: reply.statusCode })\n })\n\n fastify.addHook('onError', async (request, _reply, error) => {\n const state = requestState.get(request)\n if (!state || emitted.has(request)) return\n emitted.add(request)\n const logger = (request as any).log\n const err = error instanceof Error ? error : new Error(String(error))\n if (logger && typeof logger.error === 'function') logger.error(err)\n await state.finish({ error: err })\n })\n\n done()\n}\n\n// Break Fastify plugin encapsulation without a runtime dependency on fastify-plugin.\n// This is the same mechanism fastify-plugin uses internally.\nconst plugin = evlogPlugin as any\nplugin[Symbol.for('skip-override')] = true\nplugin[Symbol.for('fastify.display-name')] = 'evlog'\n\n/**\n * Create an evlog plugin for Fastify.\n *\n * @example\n * ```ts\n * import Fastify from 'fastify'\n * import { initLogger } from 'evlog'\n * import { evlog } from 'evlog/fastify'\n * import { createAxiomDrain } from 'evlog/axiom'\n *\n * initLogger({ env: { service: 'fastify-api' } })\n *\n * const app = Fastify()\n * await app.register(evlog, {\n * drain: createAxiomDrain(),\n * enrich: (ctx) => {\n * ctx.event.region = process.env.FLY_REGION\n * },\n * })\n * ```\n */\nexport const evlog = evlogPlugin\n"],"mappings":";;;;AAMA,MAAM,EAAE,SAAS,cAAc,oBAC7B,8EACD;AAiBD,MAAM,eAA2D,SAAS,SAAS,SAAS;CAC1F,MAAM,0BAAU,IAAI,SAAiB;CACrC,MAAM,+BAAe,IAAI,SAA+B;AAExD,SAAQ,QAAQ,cAAc,SAAS,QAAQ,SAAS;EACtD,MAAM,UAAU,uBAAuB,QAAQ,QAAQ;EACvD,MAAM,OAAO,IAAI,IAAI,QAAQ,KAAK,mBAAmB,CAAC;EAEtD,MAAM,EAAE,QAAQ,QAAQ,YAAY,uBAAuB;GACzD,QAAQ,QAAQ;GAChB;GACA,WAAW,QAAQ,mBAAmB,OAAO,YAAY;GACzD;GACA,GAAG;GACJ,CAAC;AAEF,MAAI,SAAS;AACX,SAAM;AACN;;EAIF,MAAM,MAAM;AACZ,MAAI,MAAM;AACV,eAAa,IAAI,SAAS,EAAE,QAAQ,CAAC;AAErC,UAAQ,IAAI,cAAc,MAAM,CAAC;GACjC;AAEF,SAAQ,QAAQ,cAAc,OAAO,SAAS,UAAU;EACtD,MAAM,QAAQ,aAAa,IAAI,QAAQ;AACvC,MAAI,CAAC,SAAS,QAAQ,IAAI,QAAQ,CAAE;AACpC,UAAQ,IAAI,QAAQ;AACpB,QAAM,MAAM,OAAO,EAAE,QAAQ,MAAM,YAAY,CAAC;GAChD;AAEF,SAAQ,QAAQ,WAAW,OAAO,SAAS,QAAQ,UAAU;EAC3D,MAAM,QAAQ,aAAa,IAAI,QAAQ;AACvC,MAAI,CAAC,SAAS,QAAQ,IAAI,QAAQ,CAAE;AACpC,UAAQ,IAAI,QAAQ;EACpB,MAAM,SAAU,QAAgB;EAChC,MAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AACrE,MAAI,UAAU,OAAO,OAAO,UAAU,WAAY,QAAO,MAAM,IAAI;AACnE,QAAM,MAAM,OAAO,EAAE,OAAO,KAAK,CAAC;GAClC;AAEF,OAAM;;AAKR,MAAM,SAAS;AACf,OAAO,OAAO,IAAI,gBAAgB,IAAI;AACtC,OAAO,OAAO,IAAI,uBAAuB,IAAI;;;;;;;;;;;;;;;;;;;;;;AAuB7C,MAAa,QAAQ"}
@@ -0,0 +1,141 @@
1
+ import { filterSafeHeaders } from "./utils.mjs";
2
+ import { createRequestLogger, isEnabled, shouldKeep } from "./logger.mjs";
3
+ import { n as shouldLog, t as getServiceForPath } from "./routes-BNbrnm14.mjs";
4
+ import { t as extractErrorStatus } from "./nitro-Da8tEfJ3.mjs";
5
+
6
+ //#region src/shared/middleware.ts
7
+ const noopResult = {
8
+ logger: {
9
+ set() {},
10
+ error() {},
11
+ info() {},
12
+ warn() {},
13
+ emit() {
14
+ return null;
15
+ },
16
+ getContext() {
17
+ return {};
18
+ }
19
+ },
20
+ finish: () => Promise.resolve(null),
21
+ skipped: true
22
+ };
23
+ async function runEnrichAndDrain(emittedEvent, options, requestInfo, responseStatus) {
24
+ if (options.enrich) {
25
+ const enrichCtx = {
26
+ event: emittedEvent,
27
+ request: requestInfo,
28
+ headers: options.headers,
29
+ response: { status: responseStatus }
30
+ };
31
+ try {
32
+ await options.enrich(enrichCtx);
33
+ } catch (err) {
34
+ console.error("[evlog] enrich failed:", err);
35
+ }
36
+ }
37
+ if (options.drain) {
38
+ const drainCtx = {
39
+ event: emittedEvent,
40
+ request: requestInfo,
41
+ headers: options.headers
42
+ };
43
+ try {
44
+ await options.drain(drainCtx);
45
+ } catch (err) {
46
+ console.error("[evlog] drain failed:", err);
47
+ }
48
+ }
49
+ }
50
+ /**
51
+ * Create a middleware-aware request logger with full lifecycle management.
52
+ *
53
+ * Handles the complete pipeline shared across all framework integrations:
54
+ * route filtering, logger creation, service overrides, duration tracking,
55
+ * tail sampling evaluation, event emission, enrichment, and draining.
56
+ *
57
+ * Framework adapters only need to:
58
+ * 1. Extract method/path/requestId/headers from the framework request
59
+ * 2. Call `createMiddlewareLogger()` with those + user options
60
+ * 3. Check `skipped` — if true, skip to next middleware
61
+ * 4. Store `logger` in framework-specific context (e.g., `c.set('log', logger)`)
62
+ * 5. Call `finish({ status })` or `finish({ error })` at response end
63
+ */
64
+ function createMiddlewareLogger(options) {
65
+ if (!isEnabled()) return noopResult;
66
+ const { method, path, requestId, include, exclude, routes, keep } = options;
67
+ if (!shouldLog(path, include, exclude)) return noopResult;
68
+ const resolvedRequestId = requestId || crypto.randomUUID();
69
+ const logger = createRequestLogger({
70
+ method,
71
+ path,
72
+ requestId: resolvedRequestId
73
+ });
74
+ const routeService = getServiceForPath(path, routes);
75
+ if (routeService) logger.set({ service: routeService });
76
+ const startTime = Date.now();
77
+ const requestInfo = {
78
+ method,
79
+ path,
80
+ requestId: resolvedRequestId
81
+ };
82
+ const finish = async (opts) => {
83
+ const { status, error } = opts ?? {};
84
+ if (error) {
85
+ logger.error(error);
86
+ const errorStatus = extractErrorStatus(error);
87
+ logger.set({ status: errorStatus });
88
+ } else if (status !== void 0) logger.set({ status });
89
+ const durationMs = Date.now() - startTime;
90
+ const resolvedStatus = error ? extractErrorStatus(error) : status ?? logger.getContext().status;
91
+ const tailCtx = {
92
+ status: resolvedStatus,
93
+ duration: durationMs,
94
+ path,
95
+ method,
96
+ context: logger.getContext(),
97
+ shouldKeep: false
98
+ };
99
+ if (keep) await keep(tailCtx);
100
+ const forceKeep = tailCtx.shouldKeep || shouldKeep(tailCtx);
101
+ const emittedEvent = logger.emit({ _forceKeep: forceKeep });
102
+ if (emittedEvent && (options.enrich || options.drain)) await runEnrichAndDrain(emittedEvent, options, requestInfo, resolvedStatus);
103
+ return emittedEvent;
104
+ };
105
+ return {
106
+ logger,
107
+ finish,
108
+ skipped: false
109
+ };
110
+ }
111
+
112
+ //#endregion
113
+ //#region src/shared/headers.ts
114
+ /**
115
+ * Extract headers from a Web API `Headers` object and filter out sensitive ones.
116
+ * Works with any runtime that supports the standard `Headers` API (Hono, Elysia,
117
+ * Nitro v3, Cloudflare Workers, Bun, Deno, etc.).
118
+ */
119
+ function extractSafeHeaders(headers) {
120
+ const raw = {};
121
+ headers.forEach((value, key) => {
122
+ raw[key] = value;
123
+ });
124
+ return filterSafeHeaders(raw);
125
+ }
126
+ /**
127
+ * Extract headers from Node.js `IncomingHttpHeaders` and filter out sensitive ones.
128
+ * Works with Express, Fastify, and any Node.js HTTP server using `req.headers`.
129
+ */
130
+ function extractSafeNodeHeaders(headers) {
131
+ const raw = {};
132
+ for (const [key, value] of Object.entries(headers)) {
133
+ if (value === void 0) continue;
134
+ raw[key] = Array.isArray(value) ? value.join(", ") : value;
135
+ }
136
+ return filterSafeHeaders(raw);
137
+ }
138
+
139
+ //#endregion
140
+ export { extractSafeNodeHeaders as n, createMiddlewareLogger as r, extractSafeHeaders as t };
141
+ //# sourceMappingURL=headers-CXOd5EyZ.mjs.map