evlog 1.8.0 → 1.9.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 CHANGED
@@ -4,13 +4,12 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dm/evlog?color=black)](https://npm.chart.dev/evlog)
5
5
  [![CI](https://img.shields.io/github/actions/workflow/status/HugoRCD/evlog/ci.yml?branch=main&color=black)](https://github.com/HugoRCD/evlog/actions/workflows/ci.yml)
6
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-black?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
7
- [![Nuxt](https://img.shields.io/badge/Nuxt-black?logo=nuxt&logoColor=white)](https://nuxt.com/)
8
7
  [![Documentation](https://img.shields.io/badge/Documentation-black?logo=readme&logoColor=white)](https://evlog.dev)
9
8
  [![license](https://img.shields.io/github/license/HugoRCD/evlog?color=black)](https://github.com/HugoRCD/evlog/blob/main/LICENSE)
10
9
 
11
10
  **Your logs are lying to you.**
12
11
 
13
- A single request generates 10+ log lines. When production breaks at 3am, you're grep-ing through noise, praying you'll find signal. Your errors say "Something went wrong" thanks, very helpful.
12
+ A single request generates 10+ log lines. When production breaks at 3am, you're grep-ing through noise, praying you'll find signal. Your errors say "Something went wrong" -- thanks, very helpful.
14
13
 
15
14
  **evlog fixes this.** One log per request. All context included. Errors that explain themselves.
16
15
 
@@ -21,13 +20,13 @@ A single request generates 10+ log lines. When production breaks at 3am, you're
21
20
  ```typescript
22
21
  // server/api/checkout.post.ts
23
22
 
24
- // Scattered logs - impossible to debug
23
+ // Scattered logs - impossible to debug
25
24
  console.log('Request received')
26
25
  console.log('User:', user.id)
27
26
  console.log('Cart loaded')
28
27
  console.log('Payment failed') // Good luck finding this at 3am
29
28
 
30
- throw new Error('Something went wrong') // 🤷‍♂️
29
+ throw new Error('Something went wrong')
31
30
  ```
32
31
 
33
32
  ### The Solution
@@ -36,7 +35,7 @@ throw new Error('Something went wrong') // 🤷‍♂️
36
35
  // server/api/checkout.post.ts
37
36
  import { useLogger } from 'evlog'
38
37
 
39
- // One comprehensive event per request
38
+ // One comprehensive event per request
40
39
  export default defineEventHandler(async (event) => {
41
40
  const log = useLogger(event) // Auto-injected by evlog
42
41
 
@@ -176,18 +175,43 @@ The wide event emitted at the end contains **everything**:
176
175
 
177
176
  Works with **any framework powered by Nitro**: Nuxt, Analog, Vinxi, SolidStart, TanStack Start, and more.
178
177
 
178
+ ### Nitro v3
179
+
180
+ ```typescript
181
+ // nitro.config.ts
182
+ import { defineConfig } from 'nitro'
183
+ import evlog from 'evlog/nitro/v3'
184
+
185
+ export default defineConfig({
186
+ modules: [
187
+ evlog({ env: { service: 'my-api' } })
188
+ ],
189
+ })
190
+ ```
191
+
192
+ ### Nitro v2
193
+
179
194
  ```typescript
180
195
  // nitro.config.ts
196
+ import { defineNitroConfig } from 'nitropack/config'
197
+ import evlog from 'evlog/nitro'
198
+
181
199
  export default defineNitroConfig({
182
- plugins: ['evlog/nitro'],
200
+ modules: [
201
+ evlog({ env: { service: 'my-api' } })
202
+ ],
183
203
  })
184
204
  ```
185
205
 
186
- Same API, same wide events:
206
+ Then use `useLogger` in any route. Import from `evlog/nitro/v3` (v3) or `evlog/nitro` (v2):
187
207
 
188
208
  ```typescript
189
209
  // routes/api/documents/[id]/export.post.ts
190
- import { useLogger, createError } from 'evlog'
210
+ // Nitro v3: import { defineHandler } from 'nitro/h3' + import { useLogger } from 'evlog/nitro/v3'
211
+ // Nitro v2: import { defineEventHandler } from 'h3' + import { useLogger } from 'evlog/nitro'
212
+ import { defineEventHandler } from 'h3'
213
+ import { useLogger } from 'evlog/nitro'
214
+ import { createError } from 'evlog'
191
215
 
192
216
  export default defineEventHandler(async (event) => {
193
217
  const log = useLogger(event)
@@ -248,47 +272,6 @@ Output when the export completes:
248
272
  }
249
273
  ```
250
274
 
251
- ## Structured Errors
252
-
253
- Errors should tell you **what** happened, **why**, and **how to fix it**.
254
-
255
- ```typescript
256
- // server/api/repos/sync.post.ts
257
- import { useLogger, createError } from 'evlog'
258
-
259
- export default defineEventHandler(async (event) => {
260
- const log = useLogger(event)
261
-
262
- log.set({ repo: { owner: 'acme', name: 'my-project' } })
263
-
264
- try {
265
- const result = await syncWithGitHub()
266
- log.set({ sync: { commits: result.commits, files: result.files } })
267
- return result
268
- } catch (error) {
269
- log.error(error, { step: 'github-sync' })
270
-
271
- throw createError({
272
- message: 'Failed to sync repository',
273
- status: 503,
274
- why: 'GitHub API rate limit exceeded',
275
- fix: 'Wait 1 hour or use a different token',
276
- link: 'https://docs.github.com/en/rest/rate-limit',
277
- cause: error,
278
- })
279
- }
280
- })
281
- ```
282
-
283
- Console output (development):
284
-
285
- ```
286
- Error: Failed to sync repository
287
- Why: GitHub API rate limit exceeded
288
- Fix: Wait 1 hour or use a different token
289
- More info: https://docs.github.com/en/rest/rate-limit
290
- ```
291
-
292
275
  ## Standalone TypeScript
293
276
 
294
277
  For scripts, workers, or any TypeScript project:
@@ -390,6 +373,130 @@ Notes:
390
373
  - `request.cf` is included (colo, country, asn) unless disabled
391
374
  - Use `headerAllowlist` to avoid logging sensitive headers
392
375
 
376
+ ## Hono
377
+
378
+ Use the standalone API to create one wide event per request from a Hono middleware.
379
+
380
+ ```typescript
381
+ // src/index.ts
382
+ import { serve } from '@hono/node-server'
383
+ import { Hono } from 'hono'
384
+ import { createRequestLogger, initLogger } from 'evlog'
385
+
386
+ initLogger({
387
+ env: { service: 'hono-api' },
388
+ })
389
+
390
+ const app = new Hono()
391
+
392
+ app.use('*', async (c, next) => {
393
+ const startedAt = Date.now()
394
+ const log = createRequestLogger({ method: c.req.method, path: c.req.path })
395
+
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
+ }
407
+ })
408
+
409
+ app.get('/health', (c) => c.json({ ok: true }))
410
+
411
+ serve({ fetch: app.fetch, port: 3000 })
412
+ ```
413
+
414
+ See the full [hono example](https://github.com/HugoRCD/evlog/tree/main/examples/hono) for a complete working project.
415
+
416
+ ## Browser
417
+
418
+ Use the `log` API on the client side for structured browser logging:
419
+
420
+ ```typescript
421
+ import { log } from 'evlog/browser'
422
+
423
+ log.info('checkout', 'User initiated checkout')
424
+ log.error({ action: 'payment', error: 'validation_failed' })
425
+ ```
426
+
427
+ In Nuxt, `log` is auto-imported -- no import needed in Vue components:
428
+
429
+ ```vue
430
+ <script setup>
431
+ log.info('checkout', 'User initiated checkout')
432
+ </script>
433
+ ```
434
+
435
+ Client logs output to the browser console with colored tags in development.
436
+
437
+ ### Client Transport
438
+
439
+ To send client logs to the server for centralized logging, enable the transport:
440
+
441
+ ```typescript
442
+ // nuxt.config.ts
443
+ export default defineNuxtConfig({
444
+ modules: ['evlog/nuxt'],
445
+ evlog: {
446
+ transport: {
447
+ enabled: true, // Send client logs to server
448
+ },
449
+ },
450
+ })
451
+ ```
452
+
453
+ When enabled:
454
+ 1. Client logs are sent to `/api/_evlog/ingest` via POST
455
+ 2. Server enriches with environment context (service, version, etc.)
456
+ 3. `evlog:drain` hook is called with `source: 'client'`
457
+ 4. External services receive the log
458
+
459
+ ## Structured Errors
460
+
461
+ Errors should tell you **what** happened, **why**, and **how to fix it**.
462
+
463
+ ```typescript
464
+ // server/api/repos/sync.post.ts
465
+ import { useLogger, createError } from 'evlog'
466
+
467
+ export default defineEventHandler(async (event) => {
468
+ const log = useLogger(event)
469
+
470
+ log.set({ repo: { owner: 'acme', name: 'my-project' } })
471
+
472
+ try {
473
+ const result = await syncWithGitHub()
474
+ log.set({ sync: { commits: result.commits, files: result.files } })
475
+ return result
476
+ } catch (error) {
477
+ log.error(error, { step: 'github-sync' })
478
+
479
+ throw createError({
480
+ message: 'Failed to sync repository',
481
+ status: 503,
482
+ why: 'GitHub API rate limit exceeded',
483
+ fix: 'Wait 1 hour or use a different token',
484
+ link: 'https://docs.github.com/en/rest/rate-limit',
485
+ cause: error,
486
+ })
487
+ }
488
+ })
489
+ ```
490
+
491
+ Console output (development):
492
+
493
+ ```
494
+ Error: Failed to sync repository
495
+ Why: GitHub API rate limit exceeded
496
+ Fix: Wait 1 hour or use a different token
497
+ More info: https://docs.github.com/en/rest/rate-limit
498
+ ```
499
+
393
500
  ## Enrichment Hook
394
501
 
395
502
  Use the `evlog:enrich` hook to add derived context after emit, before drain.
@@ -428,6 +535,58 @@ export default defineNitroPlugin((nitroApp) => {
428
535
  })
429
536
  ```
430
537
 
538
+ Each enricher adds a specific field to the event:
539
+
540
+ | Enricher | Event Field | Shape |
541
+ |----------|-------------|-------|
542
+ | `createUserAgentEnricher()` | `event.userAgent` | `{ raw, browser?: { name, version? }, os?: { name, version? }, device?: { type } }` |
543
+ | `createGeoEnricher()` | `event.geo` | `{ country?, region?, regionCode?, city?, latitude?, longitude? }` |
544
+ | `createRequestSizeEnricher()` | `event.requestSize` | `{ requestBytes?, responseBytes? }` |
545
+ | `createTraceContextEnricher()` | `event.traceContext` + `event.traceId` + `event.spanId` | `{ traceparent?, tracestate?, traceId?, spanId? }` |
546
+
547
+ All enrichers accept an optional `{ overwrite?: boolean }` option. By default (`overwrite: false`), user-provided data on the event takes precedence over enricher-computed values. Set `overwrite: true` to always replace existing fields.
548
+
549
+ > **Cloudflare geo note:** Only `cf-ipcountry` is a real Cloudflare HTTP header. The `cf-region`, `cf-city`, `cf-latitude`, `cf-longitude` headers are NOT standard -- they are properties of `request.cf`. For full geo data on Cloudflare, write a custom enricher that reads `request.cf`, or use a Workers middleware to forward `cf` properties as custom headers.
550
+
551
+ ### Custom Enrichers
552
+
553
+ The `evlog:enrich` hook receives an `EnrichContext` with these fields:
554
+
555
+ ```typescript
556
+ interface EnrichContext {
557
+ event: WideEvent // The emitted wide event (mutable -- modify it directly)
558
+ request?: { // Request metadata
559
+ method?: string
560
+ path?: string
561
+ requestId?: string
562
+ }
563
+ headers?: Record<string, string> // Safe HTTP headers (sensitive headers filtered)
564
+ response?: { // Response metadata
565
+ status?: number
566
+ headers?: Record<string, string>
567
+ }
568
+ }
569
+ ```
570
+
571
+ Example custom enricher:
572
+
573
+ ```typescript
574
+ // server/plugins/evlog-enrich.ts
575
+ export default defineNitroPlugin((nitroApp) => {
576
+ nitroApp.hooks.hook('evlog:enrich', (ctx) => {
577
+ // Add deployment metadata
578
+ ctx.event.deploymentId = process.env.DEPLOYMENT_ID
579
+ ctx.event.region = process.env.FLY_REGION
580
+
581
+ // Extract data from headers
582
+ const tenantId = ctx.headers?.['x-tenant-id']
583
+ if (tenantId) {
584
+ ctx.event.tenantId = tenantId
585
+ }
586
+ })
587
+ })
588
+ ```
589
+
431
590
  ## Adapters
432
591
 
433
592
  Send your logs to external observability platforms with built-in adapters.
@@ -469,6 +628,24 @@ Set environment variables:
469
628
  NUXT_OTLP_ENDPOINT=http://localhost:4318
470
629
  ```
471
630
 
631
+ ### PostHog
632
+
633
+ ```typescript
634
+ // server/plugins/evlog-drain.ts
635
+ import { createPostHogDrain } from 'evlog/posthog'
636
+
637
+ export default defineNitroPlugin((nitroApp) => {
638
+ nitroApp.hooks.hook('evlog:drain', createPostHogDrain())
639
+ })
640
+ ```
641
+
642
+ Set environment variables:
643
+
644
+ ```bash
645
+ NUXT_POSTHOG_API_KEY=phc_your-key
646
+ NUXT_POSTHOG_HOST=https://us.i.posthog.com # Optional: for EU or self-hosted
647
+ ```
648
+
472
649
  ### Sentry
473
650
 
474
651
  ```typescript
@@ -486,6 +663,23 @@ Set environment variables:
486
663
  NUXT_SENTRY_DSN=https://public@o0.ingest.sentry.io/123
487
664
  ```
488
665
 
666
+ ### Better Stack
667
+
668
+ ```typescript
669
+ // server/plugins/evlog-drain.ts
670
+ import { createBetterStackDrain } from 'evlog/better-stack'
671
+
672
+ export default defineNitroPlugin((nitroApp) => {
673
+ nitroApp.hooks.hook('evlog:drain', createBetterStackDrain())
674
+ })
675
+ ```
676
+
677
+ Set environment variables:
678
+
679
+ ```bash
680
+ NUXT_BETTER_STACK_SOURCE_TOKEN=your-source-token
681
+ ```
682
+
489
683
  ### Multiple Destinations
490
684
 
491
685
  Send logs to multiple services:
@@ -571,15 +765,15 @@ export default defineNitroPlugin((nitroApp) => {
571
765
  | `retry.initialDelayMs` | `1000` | Base delay for first retry |
572
766
  | `retry.maxDelayMs` | `30000` | Upper bound for any retry delay |
573
767
  | `maxBufferSize` | `1000` | Max buffered events before dropping oldest |
574
- | `onDropped` | | Callback when events are dropped |
768
+ | `onDropped` | -- | Callback when events are dropped |
575
769
 
576
770
  ### Returned drain function
577
771
 
578
772
  The function returned by `pipeline(drain)` is hook-compatible and exposes:
579
773
 
580
- - **`drain(ctx)`** Push a single event into the buffer
581
- - **`drain.flush()`** Force-flush all buffered events (call on server shutdown)
582
- - **`drain.pending`** Number of events currently buffered
774
+ - **`drain(ctx)`** -- Push a single event into the buffer
775
+ - **`drain.flush()`** -- Force-flush all buffered events (call on server shutdown)
776
+ - **`drain.pending`** -- Number of events currently buffered
583
777
 
584
778
  ## API Reference
585
779
 
@@ -589,7 +783,7 @@ Initialize the logger. Required for standalone usage, automatic with Nuxt/Nitro
589
783
 
590
784
  ```typescript
591
785
  initLogger({
592
- enabled: boolean // (optional) Enable/disable all logging (default: true)
786
+ enabled: boolean // Optional. Enable/disable all logging (default: true)
593
787
  env: {
594
788
  service: string // Service name
595
789
  environment: string // 'production' | 'development' | 'test'
@@ -680,9 +874,9 @@ In development, evlog uses a compact tree format:
680
874
 
681
875
  ```
682
876
  16:45:31.060 INFO [my-app] GET /api/checkout 200 in 234ms
683
- ├─ user: id=123 plan=premium
684
- ├─ cart: items=3 total=9999
685
- └─ payment: id=pay_xyz method=card
877
+ |- user: id=123 plan=premium
878
+ |- cart: items=3 total=9999
879
+ +- payment: id=pay_xyz method=card
686
880
  ```
687
881
 
688
882
  In production (`pretty: false`), logs are emitted as JSON for machine parsing.
@@ -798,11 +992,12 @@ evlog works with any framework powered by [Nitro](https://nitro.unjs.io/):
798
992
  | Framework | Integration |
799
993
  |-----------|-------------|
800
994
  | **Nuxt** | `modules: ['evlog/nuxt']` |
801
- | **Analog** | `plugins: ['evlog/nitro']` |
802
- | **Vinxi** | `plugins: ['evlog/nitro']` |
803
- | **SolidStart** | `plugins: ['evlog/nitro']` |
804
- | **TanStack Start** | `plugins: ['evlog/nitro']` |
805
- | **Standalone Nitro** | `plugins: ['evlog/nitro']` |
995
+ | **Nitro v3** | `modules: [evlog()]` with `import evlog from 'evlog/nitro/v3'` |
996
+ | **Nitro v2** | `modules: [evlog()]` with `import evlog from 'evlog/nitro'` |
997
+ | **Analog** | Nitro v2 module setup |
998
+ | **Vinxi** | Nitro v2 module setup |
999
+ | **SolidStart** | Nitro v2 module setup ([example](./examples/solidstart)) |
1000
+ | **TanStack Start** | Nitro v2 module setup |
806
1001
 
807
1002
  ## Agent Skills
808
1003
 
@@ -151,6 +151,12 @@ interface ModuleOptions {
151
151
  tags?: Record<string, string>; /** Request timeout in milliseconds. Default: 5000 */
152
152
  timeout?: number;
153
153
  };
154
+ /**
155
+ * How long to retain events before cleanup (used by @evlog/nuxthub).
156
+ * Supports "30d" (days), "24h" (hours), "60m" (minutes).
157
+ * @default '30d'
158
+ */
159
+ retention?: string;
154
160
  }
155
161
  declare const _default: _nuxt_schema0.NuxtModule<ModuleOptions, ModuleOptions, false>;
156
162
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.mts","names":[],"sources":["../../src/nuxt/module.ts"],"mappings":";;;;UAYiB,aAAA;;;AAAjB;;;EAME,OAAA;EAKM;;;EAAN,GAAA,GAAM,OAAA,CAAQ,kBAAA;EAoEF;;;;EA9DZ,MAAA;EA+Je;;;;;;EAvJf,OAAA;EAQA;;;;;;EAAA,OAAA;EA8CY;;;;;;;;;;;;;EA/BZ,MAAA,GAAS,MAAA,SAAe,WAAA;EAkFtB;;;;;;;;;;;;;;;;EAhEF,QAAA,GAAW,cAAA;;;;;;;;;;;;EAaX,SAAA,GAAY,eAAA;;;;;;;;;;;;;EAcZ,KAAA;6BAEE,OAAA;IAEA,KAAA;IAEA,KAAA;IAEA,OAAA;IAEA,OAAA;EAAA;;;;;;;;;;;;;;;EAiBF,IAAA;2DAEE,QAAA;IAEA,WAAA;IAEA,kBAAA,GAAqB,MAAA;IAErB,OAAA,GAAU,MAAA;IAEV,OAAA;EAAA;;;;;;;;;;;;EAcF,OAAA;kCAEE,MAAA;IAEA,IAAA;IAEA,SAAA;IAEA,UAAA;IAEA,OAAA;EAAA;;;;;;;;;;;;EAcF,MAAA;qBAEE,GAAA;IAEA,WAAA;IAEA,OAAA;IAEA,IAAA,GAAO,MAAA;IAEP,OAAA;EAAA;AAAA;AAAA,cAEH,QAAA"}
1
+ {"version":3,"file":"module.d.mts","names":[],"sources":["../../src/nuxt/module.ts"],"mappings":";;;;UAaiB,aAAA;;;AAAjB;;;EAME,OAAA;EAKM;;;EAAN,GAAA,GAAM,OAAA,CAAQ,kBAAA;EAoEF;;;;EA9DZ,MAAA;EA+Je;;;;;;EAvJf,OAAA;EAQA;;;;;;EAAA,OAAA;EA8CY;;;;;;;;;;;;;EA/BZ,MAAA,GAAS,MAAA,SAAe,WAAA;EAkFtB;;;;;;;;;;;;;;;;EAhEF,QAAA,GAAW,cAAA;EAyHZ;;;;;;;;;;;EA5GC,SAAA,GAAY,eAAA;;;;;;;;;;;;;EAcZ,KAAA;6BAEE,OAAA;IAEA,KAAA;IAEA,KAAA;IAEA,OAAA;IAEA,OAAA;EAAA;;;;;;;;;;;;;;;EAiBF,IAAA;2DAEE,QAAA;IAEA,WAAA;IAEA,kBAAA,GAAqB,MAAA;IAErB,OAAA,GAAU,MAAA;IAEV,OAAA;EAAA;;;;;;;;;;;;EAcF,OAAA;kCAEE,MAAA;IAEA,IAAA;IAEA,SAAA;IAEA,UAAA;IAEA,OAAA;EAAA;;;;;;;;;;;;EAcF,MAAA;qBAEE,GAAA;IAEA,WAAA;IAEA,OAAA;IAEA,IAAA,GAAO,MAAA;IAEP,OAAA;EAAA;;;;;;EAQF,SAAA;AAAA;AAAA,cACD,QAAA"}
@@ -1,10 +1,16 @@
1
1
  import { addImports, addPlugin, addServerHandler, addServerImports, addServerPlugin, createResolver, defineNuxtModule } from "@nuxt/kit";
2
2
 
3
+ //#region package.json
4
+ var name = "evlog";
5
+ var version = "1.9.0";
6
+
7
+ //#endregion
3
8
  //#region src/nuxt/module.ts
4
9
  var module_default = defineNuxtModule({
5
10
  meta: {
6
- name: "evlog",
7
- configKey: "evlog",
11
+ name,
12
+ version,
13
+ configKey: name,
8
14
  docs: "https://evlog.dev"
9
15
  },
10
16
  defaults: {},
@@ -1 +1 @@
1
- {"version":3,"file":"module.mjs","names":[],"sources":["../../src/nuxt/module.ts"],"sourcesContent":["import {\n addImports,\n addPlugin,\n addServerHandler,\n addServerImports,\n addServerPlugin,\n createResolver,\n defineNuxtModule,\n} from '@nuxt/kit'\nimport type { NitroConfig } from 'nitropack'\nimport type { EnvironmentContext, RouteConfig, SamplingConfig, TransportConfig } from '../types'\n\nexport interface ModuleOptions {\n /**\n * Enable or disable all logging globally.\n * When false, all emits, tagged logs, and request logger operations become no-ops.\n * @default true\n */\n enabled?: boolean\n\n /**\n * Environment context overrides.\n */\n env?: Partial<EnvironmentContext>\n\n /**\n * Enable pretty printing.\n * @default true in development, false in production\n */\n pretty?: boolean\n\n /**\n * Route patterns to include in logging.\n * Supports glob patterns like '/api/**'.\n * If not set, all routes are logged.\n * @example ['/api/**', '/auth/**']\n */\n include?: string[]\n\n /**\n * Route patterns to exclude from logging.\n * Supports glob patterns like '/api/_nuxt_icon/**'.\n * Exclusions take precedence over inclusions.\n * @example ['/api/_nuxt_icon/**', '/health']\n */\n exclude?: string[]\n\n /**\n * Route-specific service configuration.\n * Allows setting different service names for different routes.\n * Patterns are matched using glob syntax.\n *\n * @example\n * ```ts\n * routes: {\n * '/api/foo/**': { service: 'service1' },\n * '/api/bar/**': { service: 'service2' }\n * }\n * ```\n */\n routes?: Record<string, RouteConfig>\n\n /**\n * Sampling configuration for filtering logs.\n * Allows configuring what percentage of logs to keep per level.\n *\n * @example\n * ```ts\n * sampling: {\n * rates: {\n * info: 10, // Keep 10% of info logs\n * warn: 50, // Keep 50% of warning logs\n * debug: 5, // Keep 5% of debug logs\n * error: 100, // Always keep errors (default)\n * }\n * }\n * ```\n */\n sampling?: SamplingConfig\n\n /**\n * Transport configuration for sending client logs to the server.\n *\n * @example\n * ```ts\n * transport: {\n * enabled: true, // Send logs to server API\n * endpoint: '/api/_evlog/ingest' // Custom endpoint\n * }\n * ```\n */\n transport?: TransportConfig\n\n /**\n * Axiom adapter configuration.\n * When configured, use `createAxiomDrain()` from `evlog/axiom` to send logs.\n *\n * @example\n * ```ts\n * axiom: {\n * dataset: 'my-app-logs',\n * token: process.env.AXIOM_TOKEN,\n * }\n * ```\n */\n axiom?: {\n /** Axiom dataset name */\n dataset: string\n /** Axiom API token */\n token: string\n /** Organization ID (required for Personal Access Tokens) */\n orgId?: string\n /** Base URL for Axiom API. Default: https://api.axiom.co */\n baseUrl?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n }\n\n /**\n * OTLP adapter configuration.\n * When configured, use `createOTLPDrain()` from `evlog/otlp` to send logs.\n *\n * @example\n * ```ts\n * otlp: {\n * endpoint: 'http://localhost:4318',\n * headers: {\n * 'Authorization': `Basic ${process.env.GRAFANA_TOKEN}`,\n * },\n * }\n * ```\n */\n otlp?: {\n /** OTLP HTTP endpoint (e.g., http://localhost:4318) */\n endpoint: string\n /** Override service name (defaults to event.service) */\n serviceName?: string\n /** Additional resource attributes */\n resourceAttributes?: Record<string, string | number | boolean>\n /** Custom headers (e.g., for authentication) */\n headers?: Record<string, string>\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n }\n\n /**\n * PostHog adapter configuration.\n * When configured, use `createPostHogDrain()` from `evlog/posthog` to send logs.\n *\n * @example\n * ```ts\n * posthog: {\n * apiKey: process.env.POSTHOG_API_KEY,\n * }\n * ```\n */\n posthog?: {\n /** PostHog project API key */\n apiKey: string\n /** PostHog host URL. Default: https://us.i.posthog.com */\n host?: string\n /** PostHog event name. Default: evlog_wide_event */\n eventName?: string\n /** Override distinct_id (defaults to event.service) */\n distinctId?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n }\n\n /**\n * Sentry adapter configuration.\n * When configured, use `createSentryDrain()` from `evlog/sentry` to send logs.\n *\n * @example\n * ```ts\n * sentry: {\n * dsn: process.env.SENTRY_DSN,\n * }\n * ```\n */\n sentry?: {\n /** Sentry DSN */\n dsn: string\n /** Environment override (defaults to event.environment) */\n environment?: string\n /** Release version override (defaults to event.version) */\n release?: string\n /** Additional tags to attach as attributes */\n tags?: Record<string, string>\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n }\n}\n\nexport default defineNuxtModule<ModuleOptions>({\n meta: {\n name: 'evlog',\n configKey: 'evlog',\n docs: 'https://evlog.dev',\n },\n defaults: {},\n setup(options, nuxt) {\n const resolver = createResolver(import.meta.url)\n\n const transportEnabled = options.transport?.enabled ?? false\n const transportEndpoint = options.transport?.endpoint ?? '/api/_evlog/ingest'\n\n // Register custom error handler for proper EvlogError serialization\n // Only set if not already configured to avoid overwriting user's custom handler\n // @ts-expect-error nitro:config hook exists but is not in NuxtHooks type\n nuxt.hook('nitro:config', (nitroConfig: NitroConfig) => {\n nitroConfig.errorHandler = nitroConfig.errorHandler || resolver.resolve('../nitro/errorHandler')\n })\n\n nuxt.options.runtimeConfig.evlog = options\n nuxt.options.runtimeConfig.public.evlog = {\n enabled: options.enabled ?? true,\n pretty: options.pretty,\n transport: {\n enabled: transportEnabled,\n endpoint: transportEndpoint,\n },\n }\n\n if (transportEnabled) {\n addServerHandler({\n route: transportEndpoint,\n method: 'post',\n handler: resolver.resolve('../runtime/server/routes/_evlog/ingest.post'),\n })\n }\n\n addServerPlugin(resolver.resolve('../nitro/plugin'))\n\n addPlugin({\n src: resolver.resolve('../runtime/client/plugin'),\n mode: 'client',\n })\n\n addImports([\n {\n name: 'log',\n from: resolver.resolve('../runtime/client/log'),\n },\n {\n name: 'setIdentity',\n from: resolver.resolve('../runtime/client/log'),\n },\n {\n name: 'clearIdentity',\n from: resolver.resolve('../runtime/client/log'),\n },\n {\n name: 'createEvlogError',\n from: resolver.resolve('../error'),\n },\n {\n name: 'parseError',\n from: resolver.resolve('../runtime/utils/parseError'),\n },\n ])\n\n addServerImports([\n {\n name: 'useLogger',\n from: resolver.resolve('../runtime/server/useLogger'),\n },\n {\n name: 'log',\n from: resolver.resolve('../logger'),\n },\n {\n name: 'createEvlogError',\n from: resolver.resolve('../error'),\n },\n ])\n },\n})\n"],"mappings":";;;AAkMA,qBAAe,iBAAgC;CAC7C,MAAM;EACJ,MAAM;EACN,WAAW;EACX,MAAM;EACP;CACD,UAAU,EAAE;CACZ,MAAM,SAAS,MAAM;EACnB,MAAM,WAAW,eAAe,OAAO,KAAK,IAAI;EAEhD,MAAM,mBAAmB,QAAQ,WAAW,WAAW;EACvD,MAAM,oBAAoB,QAAQ,WAAW,YAAY;AAKzD,OAAK,KAAK,iBAAiB,gBAA6B;AACtD,eAAY,eAAe,YAAY,gBAAgB,SAAS,QAAQ,wBAAwB;IAChG;AAEF,OAAK,QAAQ,cAAc,QAAQ;AACnC,OAAK,QAAQ,cAAc,OAAO,QAAQ;GACxC,SAAS,QAAQ,WAAW;GAC5B,QAAQ,QAAQ;GAChB,WAAW;IACT,SAAS;IACT,UAAU;IACX;GACF;AAED,MAAI,iBACF,kBAAiB;GACf,OAAO;GACP,QAAQ;GACR,SAAS,SAAS,QAAQ,8CAA8C;GACzE,CAAC;AAGJ,kBAAgB,SAAS,QAAQ,kBAAkB,CAAC;AAEpD,YAAU;GACR,KAAK,SAAS,QAAQ,2BAA2B;GACjD,MAAM;GACP,CAAC;AAEF,aAAW;GACT;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,wBAAwB;IAChD;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,wBAAwB;IAChD;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,wBAAwB;IAChD;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,WAAW;IACnC;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,8BAA8B;IACtD;GACF,CAAC;AAEF,mBAAiB;GACf;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,8BAA8B;IACtD;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,YAAY;IACpC;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,WAAW;IACnC;GACF,CAAC;;CAEL,CAAC"}
1
+ {"version":3,"file":"module.mjs","names":[],"sources":["../../package.json","../../src/nuxt/module.ts"],"sourcesContent":["","import {\n addImports,\n addPlugin,\n addServerHandler,\n addServerImports,\n addServerPlugin,\n createResolver,\n defineNuxtModule,\n} from '@nuxt/kit'\nimport type { NitroConfig } from 'nitropack'\nimport type { EnvironmentContext, RouteConfig, SamplingConfig, TransportConfig } from '../types'\nimport { name, version } from '../../package.json'\n\nexport interface ModuleOptions {\n /**\n * Enable or disable all logging globally.\n * When false, all emits, tagged logs, and request logger operations become no-ops.\n * @default true\n */\n enabled?: boolean\n\n /**\n * Environment context overrides.\n */\n env?: Partial<EnvironmentContext>\n\n /**\n * Enable pretty printing.\n * @default true in development, false in production\n */\n pretty?: boolean\n\n /**\n * Route patterns to include in logging.\n * Supports glob patterns like '/api/**'.\n * If not set, all routes are logged.\n * @example ['/api/**', '/auth/**']\n */\n include?: string[]\n\n /**\n * Route patterns to exclude from logging.\n * Supports glob patterns like '/api/_nuxt_icon/**'.\n * Exclusions take precedence over inclusions.\n * @example ['/api/_nuxt_icon/**', '/health']\n */\n exclude?: string[]\n\n /**\n * Route-specific service configuration.\n * Allows setting different service names for different routes.\n * Patterns are matched using glob syntax.\n *\n * @example\n * ```ts\n * routes: {\n * '/api/foo/**': { service: 'service1' },\n * '/api/bar/**': { service: 'service2' }\n * }\n * ```\n */\n routes?: Record<string, RouteConfig>\n\n /**\n * Sampling configuration for filtering logs.\n * Allows configuring what percentage of logs to keep per level.\n *\n * @example\n * ```ts\n * sampling: {\n * rates: {\n * info: 10, // Keep 10% of info logs\n * warn: 50, // Keep 50% of warning logs\n * debug: 5, // Keep 5% of debug logs\n * error: 100, // Always keep errors (default)\n * }\n * }\n * ```\n */\n sampling?: SamplingConfig\n\n /**\n * Transport configuration for sending client logs to the server.\n *\n * @example\n * ```ts\n * transport: {\n * enabled: true, // Send logs to server API\n * endpoint: '/api/_evlog/ingest' // Custom endpoint\n * }\n * ```\n */\n transport?: TransportConfig\n\n /**\n * Axiom adapter configuration.\n * When configured, use `createAxiomDrain()` from `evlog/axiom` to send logs.\n *\n * @example\n * ```ts\n * axiom: {\n * dataset: 'my-app-logs',\n * token: process.env.AXIOM_TOKEN,\n * }\n * ```\n */\n axiom?: {\n /** Axiom dataset name */\n dataset: string\n /** Axiom API token */\n token: string\n /** Organization ID (required for Personal Access Tokens) */\n orgId?: string\n /** Base URL for Axiom API. Default: https://api.axiom.co */\n baseUrl?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n }\n\n /**\n * OTLP adapter configuration.\n * When configured, use `createOTLPDrain()` from `evlog/otlp` to send logs.\n *\n * @example\n * ```ts\n * otlp: {\n * endpoint: 'http://localhost:4318',\n * headers: {\n * 'Authorization': `Basic ${process.env.GRAFANA_TOKEN}`,\n * },\n * }\n * ```\n */\n otlp?: {\n /** OTLP HTTP endpoint (e.g., http://localhost:4318) */\n endpoint: string\n /** Override service name (defaults to event.service) */\n serviceName?: string\n /** Additional resource attributes */\n resourceAttributes?: Record<string, string | number | boolean>\n /** Custom headers (e.g., for authentication) */\n headers?: Record<string, string>\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n }\n\n /**\n * PostHog adapter configuration.\n * When configured, use `createPostHogDrain()` from `evlog/posthog` to send logs.\n *\n * @example\n * ```ts\n * posthog: {\n * apiKey: process.env.POSTHOG_API_KEY,\n * }\n * ```\n */\n posthog?: {\n /** PostHog project API key */\n apiKey: string\n /** PostHog host URL. Default: https://us.i.posthog.com */\n host?: string\n /** PostHog event name. Default: evlog_wide_event */\n eventName?: string\n /** Override distinct_id (defaults to event.service) */\n distinctId?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n }\n\n /**\n * Sentry adapter configuration.\n * When configured, use `createSentryDrain()` from `evlog/sentry` to send logs.\n *\n * @example\n * ```ts\n * sentry: {\n * dsn: process.env.SENTRY_DSN,\n * }\n * ```\n */\n sentry?: {\n /** Sentry DSN */\n dsn: string\n /** Environment override (defaults to event.environment) */\n environment?: string\n /** Release version override (defaults to event.version) */\n release?: string\n /** Additional tags to attach as attributes */\n tags?: Record<string, string>\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n }\n\n /**\n * How long to retain events before cleanup (used by @evlog/nuxthub).\n * Supports \"30d\" (days), \"24h\" (hours), \"60m\" (minutes).\n * @default '30d'\n */\n retention?: string\n}\n\nexport default defineNuxtModule<ModuleOptions>({\n meta: {\n name,\n version,\n configKey: name,\n docs: 'https://evlog.dev',\n },\n defaults: {},\n setup(options, nuxt) {\n const resolver = createResolver(import.meta.url)\n\n const transportEnabled = options.transport?.enabled ?? false\n const transportEndpoint = options.transport?.endpoint ?? '/api/_evlog/ingest'\n\n // Register custom error handler for proper EvlogError serialization\n // Only set if not already configured to avoid overwriting user's custom handler\n // @ts-expect-error nitro:config hook exists but is not in NuxtHooks type\n nuxt.hook('nitro:config', (nitroConfig: NitroConfig) => {\n nitroConfig.errorHandler = nitroConfig.errorHandler || resolver.resolve('../nitro/errorHandler')\n })\n\n nuxt.options.runtimeConfig.evlog = options\n nuxt.options.runtimeConfig.public.evlog = {\n enabled: options.enabled ?? true,\n pretty: options.pretty,\n transport: {\n enabled: transportEnabled,\n endpoint: transportEndpoint,\n },\n }\n\n if (transportEnabled) {\n addServerHandler({\n route: transportEndpoint,\n method: 'post',\n handler: resolver.resolve('../runtime/server/routes/_evlog/ingest.post'),\n })\n }\n\n addServerPlugin(resolver.resolve('../nitro/plugin'))\n\n addPlugin({\n src: resolver.resolve('../runtime/client/plugin'),\n mode: 'client',\n })\n\n addImports([\n {\n name: 'log',\n from: resolver.resolve('../runtime/client/log'),\n },\n {\n name: 'setIdentity',\n from: resolver.resolve('../runtime/client/log'),\n },\n {\n name: 'clearIdentity',\n from: resolver.resolve('../runtime/client/log'),\n },\n {\n name: 'createEvlogError',\n from: resolver.resolve('../error'),\n },\n {\n name: 'parseError',\n from: resolver.resolve('../runtime/utils/parseError'),\n },\n ])\n\n addServerImports([\n {\n name: 'useLogger',\n from: resolver.resolve('../runtime/server/useLogger'),\n },\n {\n name: 'log',\n from: resolver.resolve('../logger'),\n },\n {\n name: 'createEvlogError',\n from: resolver.resolve('../error'),\n },\n ])\n },\n})\n"],"mappings":";;;;;;;;AC0MA,qBAAe,iBAAgC;CAC7C,MAAM;EACJ;EACA;EACA,WAAW;EACX,MAAM;EACP;CACD,UAAU,EAAE;CACZ,MAAM,SAAS,MAAM;EACnB,MAAM,WAAW,eAAe,OAAO,KAAK,IAAI;EAEhD,MAAM,mBAAmB,QAAQ,WAAW,WAAW;EACvD,MAAM,oBAAoB,QAAQ,WAAW,YAAY;AAKzD,OAAK,KAAK,iBAAiB,gBAA6B;AACtD,eAAY,eAAe,YAAY,gBAAgB,SAAS,QAAQ,wBAAwB;IAChG;AAEF,OAAK,QAAQ,cAAc,QAAQ;AACnC,OAAK,QAAQ,cAAc,OAAO,QAAQ;GACxC,SAAS,QAAQ,WAAW;GAC5B,QAAQ,QAAQ;GAChB,WAAW;IACT,SAAS;IACT,UAAU;IACX;GACF;AAED,MAAI,iBACF,kBAAiB;GACf,OAAO;GACP,QAAQ;GACR,SAAS,SAAS,QAAQ,8CAA8C;GACzE,CAAC;AAGJ,kBAAgB,SAAS,QAAQ,kBAAkB,CAAC;AAEpD,YAAU;GACR,KAAK,SAAS,QAAQ,2BAA2B;GACjD,MAAM;GACP,CAAC;AAEF,aAAW;GACT;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,wBAAwB;IAChD;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,wBAAwB;IAChD;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,wBAAwB;IAChD;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,WAAW;IACnC;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,8BAA8B;IACtD;GACF,CAAC;AAEF,mBAAiB;GACf;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,8BAA8B;IACtD;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,YAAY;IACpC;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,WAAW;IACnC;GACF,CAAC;;CAEL,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evlog",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Wide event logging library with structured error handling. Inspired by LoggingSucks.",
5
5
  "author": "HugoRCD <contact@hrcd.fr>",
6
6
  "homepage": "https://evlog.dev",
@@ -130,7 +130,6 @@
130
130
  "build": "tsdown",
131
131
  "dev": "tsdown --watch",
132
132
  "dev:prepare": "tsdown",
133
- "release": "bun run lint && bun run test && bun run build && changelogen --release && npm publish && git push --follow-tags",
134
133
  "lint": "eslint .",
135
134
  "lint:fix": "eslint . --fix",
136
135
  "test": "vitest run",