evlog 1.7.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.
Files changed (81) hide show
  1. package/README.md +257 -61
  2. package/dist/_http-DVDwNag0.mjs +76 -0
  3. package/dist/_http-DVDwNag0.mjs.map +1 -0
  4. package/dist/_severity-CXfyvxQi.mjs +17 -0
  5. package/dist/_severity-CXfyvxQi.mjs.map +1 -0
  6. package/dist/adapters/axiom.d.mts +1 -0
  7. package/dist/adapters/axiom.d.mts.map +1 -1
  8. package/dist/adapters/axiom.mjs +40 -44
  9. package/dist/adapters/axiom.mjs.map +1 -1
  10. package/dist/adapters/better-stack.d.mts +1 -0
  11. package/dist/adapters/better-stack.d.mts.map +1 -1
  12. package/dist/adapters/better-stack.mjs +34 -45
  13. package/dist/adapters/better-stack.mjs.map +1 -1
  14. package/dist/adapters/otlp.d.mts +1 -0
  15. package/dist/adapters/otlp.d.mts.map +1 -1
  16. package/dist/adapters/otlp.mjs +61 -81
  17. package/dist/adapters/otlp.mjs.map +1 -1
  18. package/dist/adapters/posthog.d.mts +35 -1
  19. package/dist/adapters/posthog.d.mts.map +1 -1
  20. package/dist/adapters/posthog.mjs +91 -45
  21. package/dist/adapters/posthog.mjs.map +1 -1
  22. package/dist/adapters/sentry.d.mts +1 -0
  23. package/dist/adapters/sentry.d.mts.map +1 -1
  24. package/dist/adapters/sentry.mjs +41 -53
  25. package/dist/adapters/sentry.mjs.map +1 -1
  26. package/dist/browser.d.mts +63 -0
  27. package/dist/browser.d.mts.map +1 -0
  28. package/dist/browser.mjs +95 -0
  29. package/dist/browser.mjs.map +1 -0
  30. package/dist/index.d.mts +2 -2
  31. package/dist/index.mjs +2 -2
  32. package/dist/logger.d.mts +6 -2
  33. package/dist/logger.d.mts.map +1 -1
  34. package/dist/logger.mjs +56 -3
  35. package/dist/logger.mjs.map +1 -1
  36. package/dist/nitro/errorHandler.mjs +6 -17
  37. package/dist/nitro/errorHandler.mjs.map +1 -1
  38. package/dist/nitro/module.d.mts +11 -0
  39. package/dist/nitro/module.d.mts.map +1 -0
  40. package/dist/nitro/module.mjs +23 -0
  41. package/dist/nitro/module.mjs.map +1 -0
  42. package/dist/nitro/plugin.mjs +28 -52
  43. package/dist/nitro/plugin.mjs.map +1 -1
  44. package/dist/nitro/v3/errorHandler.d.mts +24 -0
  45. package/dist/nitro/v3/errorHandler.d.mts.map +1 -0
  46. package/dist/nitro/v3/errorHandler.mjs +36 -0
  47. package/dist/nitro/v3/errorHandler.mjs.map +1 -0
  48. package/dist/nitro/v3/index.d.mts +4 -0
  49. package/dist/nitro/v3/index.mjs +4 -0
  50. package/dist/nitro/v3/module.d.mts +10 -0
  51. package/dist/nitro/v3/module.d.mts.map +1 -0
  52. package/dist/nitro/v3/module.mjs +22 -0
  53. package/dist/nitro/v3/module.mjs.map +1 -0
  54. package/dist/nitro/v3/plugin.d.mts +14 -0
  55. package/dist/nitro/v3/plugin.d.mts.map +1 -0
  56. package/dist/nitro/v3/plugin.mjs +157 -0
  57. package/dist/nitro/v3/plugin.mjs.map +1 -0
  58. package/dist/nitro/v3/useLogger.d.mts +24 -0
  59. package/dist/nitro/v3/useLogger.d.mts.map +1 -0
  60. package/dist/nitro/v3/useLogger.mjs +27 -0
  61. package/dist/nitro/v3/useLogger.mjs.map +1 -0
  62. package/dist/nitro-D57TWGyN.mjs +73 -0
  63. package/dist/nitro-D57TWGyN.mjs.map +1 -0
  64. package/dist/nitro-D81NBVPi.d.mts +42 -0
  65. package/dist/nitro-D81NBVPi.d.mts.map +1 -0
  66. package/dist/nuxt/module.d.mts +12 -0
  67. package/dist/nuxt/module.d.mts.map +1 -1
  68. package/dist/nuxt/module.mjs +17 -2
  69. package/dist/nuxt/module.mjs.map +1 -1
  70. package/dist/runtime/client/log.d.mts +5 -2
  71. package/dist/runtime/client/log.d.mts.map +1 -1
  72. package/dist/runtime/client/log.mjs +16 -3
  73. package/dist/runtime/client/log.mjs.map +1 -1
  74. package/dist/runtime/client/plugin.mjs +1 -0
  75. package/dist/runtime/client/plugin.mjs.map +1 -1
  76. package/dist/runtime/server/routes/_evlog/ingest.post.mjs +1 -1
  77. package/dist/types.d.mts +32 -2
  78. package/dist/types.d.mts.map +1 -1
  79. package/package.json +30 -7
  80. package/dist/_utils-DZA9nou3.mjs +0 -23
  81. package/dist/_utils-DZA9nou3.mjs.map +0 -1
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,6 +783,7 @@ Initialize the logger. Required for standalone usage, automatic with Nuxt/Nitro
589
783
 
590
784
  ```typescript
591
785
  initLogger({
786
+ enabled: boolean // Optional. Enable/disable all logging (default: true)
592
787
  env: {
593
788
  service: string // Service name
594
789
  environment: string // 'production' | 'development' | 'test'
@@ -679,9 +874,9 @@ In development, evlog uses a compact tree format:
679
874
 
680
875
  ```
681
876
  16:45:31.060 INFO [my-app] GET /api/checkout 200 in 234ms
682
- ├─ user: id=123 plan=premium
683
- ├─ cart: items=3 total=9999
684
- └─ 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
685
880
  ```
686
881
 
687
882
  In production (`pretty: false`), logs are emitted as JSON for machine parsing.
@@ -797,11 +992,12 @@ evlog works with any framework powered by [Nitro](https://nitro.unjs.io/):
797
992
  | Framework | Integration |
798
993
  |-----------|-------------|
799
994
  | **Nuxt** | `modules: ['evlog/nuxt']` |
800
- | **Analog** | `plugins: ['evlog/nitro']` |
801
- | **Vinxi** | `plugins: ['evlog/nitro']` |
802
- | **SolidStart** | `plugins: ['evlog/nitro']` |
803
- | **TanStack Start** | `plugins: ['evlog/nitro']` |
804
- | **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 |
805
1001
 
806
1002
  ## Agent Skills
807
1003
 
@@ -0,0 +1,76 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ //#region \0rolldown/runtime.js
4
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
5
+
6
+ //#endregion
7
+ //#region src/adapters/_config.ts
8
+ /**
9
+ * Try to get runtime config from Nitro/Nuxt environment.
10
+ * Returns undefined if not in a Nitro context.
11
+ */
12
+ function getRuntimeConfig() {
13
+ try {
14
+ const { useRuntimeConfig } = __require("nitropack/runtime");
15
+ return useRuntimeConfig();
16
+ } catch {
17
+ return;
18
+ }
19
+ }
20
+ function resolveAdapterConfig(namespace, fields, overrides) {
21
+ const runtimeConfig = getRuntimeConfig();
22
+ const evlogNs = runtimeConfig?.evlog?.[namespace];
23
+ const rootNs = runtimeConfig?.[namespace];
24
+ const config = {};
25
+ for (const { key, env } of fields) config[key] = overrides?.[key] ?? evlogNs?.[key] ?? rootNs?.[key] ?? resolveEnv(env);
26
+ return config;
27
+ }
28
+ function resolveEnv(envKeys) {
29
+ if (!envKeys) return void 0;
30
+ for (const key of envKeys) {
31
+ const val = process.env[key];
32
+ if (val) return val;
33
+ }
34
+ }
35
+
36
+ //#endregion
37
+ //#region src/adapters/_drain.ts
38
+ function defineDrain(options) {
39
+ return async (ctx) => {
40
+ const contexts = Array.isArray(ctx) ? ctx : [ctx];
41
+ if (contexts.length === 0) return;
42
+ const config = options.resolve();
43
+ if (!config) return;
44
+ try {
45
+ await options.send(contexts.map((c) => c.event), config);
46
+ } catch (error) {
47
+ console.error(`[evlog/${options.name}] Failed to send events:`, error);
48
+ }
49
+ };
50
+ }
51
+
52
+ //#endregion
53
+ //#region src/adapters/_http.ts
54
+ async function httpPost({ url, headers, body, timeout, label }) {
55
+ const controller = new AbortController();
56
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
57
+ try {
58
+ const response = await fetch(url, {
59
+ method: "POST",
60
+ headers,
61
+ body,
62
+ signal: controller.signal
63
+ });
64
+ if (!response.ok) {
65
+ const text = await response.text().catch(() => "Unknown error");
66
+ const safeText = text.length > 200 ? `${text.slice(0, 200)}...[truncated]` : text;
67
+ throw new Error(`${label} API error: ${response.status} ${response.statusText} - ${safeText}`);
68
+ }
69
+ } finally {
70
+ clearTimeout(timeoutId);
71
+ }
72
+ }
73
+
74
+ //#endregion
75
+ export { defineDrain as n, resolveAdapterConfig as r, httpPost as t };
76
+ //# sourceMappingURL=_http-DVDwNag0.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_http-DVDwNag0.mjs","names":[],"sources":["../src/adapters/_config.ts","../src/adapters/_drain.ts","../src/adapters/_http.ts"],"sourcesContent":["/**\n * Try to get runtime config from Nitro/Nuxt environment.\n * Returns undefined if not in a Nitro context.\n */\nexport function getRuntimeConfig(): Record<string, any> | undefined {\n try {\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const { useRuntimeConfig } = require('nitropack/runtime')\n return useRuntimeConfig()\n } catch {\n return undefined\n }\n}\n\nexport interface ConfigField<T> {\n key: keyof T & string\n env?: string[]\n}\n\nexport function resolveAdapterConfig<T>(\n namespace: string,\n fields: ConfigField<T>[],\n overrides?: Partial<T>,\n): Partial<T> {\n const runtimeConfig = getRuntimeConfig()\n const evlogNs = runtimeConfig?.evlog?.[namespace]\n const rootNs = runtimeConfig?.[namespace]\n\n const config: Record<string, unknown> = {}\n\n for (const { key, env } of fields) {\n config[key] =\n overrides?.[key]\n ?? evlogNs?.[key]\n ?? rootNs?.[key]\n ?? resolveEnv(env)\n }\n\n return config as Partial<T>\n}\n\nfunction resolveEnv(envKeys?: string[]): string | undefined {\n if (!envKeys) return undefined\n for (const key of envKeys) {\n const val = process.env[key]\n if (val) return val\n }\n return undefined\n}\n","import type { DrainContext, WideEvent } from '../types'\n\nexport interface DrainOptions<TConfig> {\n name: string\n resolve: () => TConfig | null\n send: (events: WideEvent[], config: TConfig) => Promise<void>\n}\n\nexport function defineDrain<TConfig>(options: DrainOptions<TConfig>): (ctx: DrainContext | DrainContext[]) => Promise<void> {\n return async (ctx: DrainContext | DrainContext[]) => {\n const contexts = Array.isArray(ctx) ? ctx : [ctx]\n if (contexts.length === 0) return\n\n const config = options.resolve()\n if (!config) return\n\n try {\n await options.send(contexts.map(c => c.event), config)\n } catch (error) {\n console.error(`[evlog/${options.name}] Failed to send events:`, error)\n }\n }\n}\n","export interface HttpPostOptions {\n url: string\n headers: Record<string, string>\n body: string\n timeout: number\n label: string\n}\n\nexport async function httpPost({ url, headers, body, timeout, label }: HttpPostOptions): Promise<void> {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body,\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const text = await response.text().catch(() => 'Unknown error')\n const safeText = text.length > 200 ? `${text.slice(0, 200)}...[truncated]` : text\n throw new Error(`${label} API error: ${response.status} ${response.statusText} - ${safeText}`)\n }\n } finally {\n clearTimeout(timeoutId)\n }\n}\n"],"mappings":";;;;;;;;;;;AAIA,SAAgB,mBAAoD;AAClE,KAAI;EAEF,MAAM,EAAE,+BAA6B,oBAAoB;AACzD,SAAO,kBAAkB;SACnB;AACN;;;AASJ,SAAgB,qBACd,WACA,QACA,WACY;CACZ,MAAM,gBAAgB,kBAAkB;CACxC,MAAM,UAAU,eAAe,QAAQ;CACvC,MAAM,SAAS,gBAAgB;CAE/B,MAAM,SAAkC,EAAE;AAE1C,MAAK,MAAM,EAAE,KAAK,SAAS,OACzB,QAAO,OACL,YAAY,QACT,UAAU,QACV,SAAS,QACT,WAAW,IAAI;AAGtB,QAAO;;AAGT,SAAS,WAAW,SAAwC;AAC1D,KAAI,CAAC,QAAS,QAAO;AACrB,MAAK,MAAM,OAAO,SAAS;EACzB,MAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,IAAK,QAAO;;;;;;ACrCpB,SAAgB,YAAqB,SAAuF;AAC1H,QAAO,OAAO,QAAuC;EACnD,MAAM,WAAW,MAAM,QAAQ,IAAI,GAAG,MAAM,CAAC,IAAI;AACjD,MAAI,SAAS,WAAW,EAAG;EAE3B,MAAM,SAAS,QAAQ,SAAS;AAChC,MAAI,CAAC,OAAQ;AAEb,MAAI;AACF,SAAM,QAAQ,KAAK,SAAS,KAAI,MAAK,EAAE,MAAM,EAAE,OAAO;WAC/C,OAAO;AACd,WAAQ,MAAM,UAAU,QAAQ,KAAK,2BAA2B,MAAM;;;;;;;ACX5E,eAAsB,SAAS,EAAE,KAAK,SAAS,MAAM,SAAS,SAAyC;CACrG,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,QAAQ;AAE/D,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,QAAQ;GACR;GACA;GACA,QAAQ,WAAW;GACpB,CAAC;AAEF,MAAI,CAAC,SAAS,IAAI;GAChB,MAAM,OAAO,MAAM,SAAS,MAAM,CAAC,YAAY,gBAAgB;GAC/D,MAAM,WAAW,KAAK,SAAS,MAAM,GAAG,KAAK,MAAM,GAAG,IAAI,CAAC,kBAAkB;AAC7E,SAAM,IAAI,MAAM,GAAG,MAAM,cAAc,SAAS,OAAO,GAAG,SAAS,WAAW,KAAK,WAAW;;WAExF;AACR,eAAa,UAAU"}
@@ -0,0 +1,17 @@
1
+ //#region src/adapters/_severity.ts
2
+ const OTEL_SEVERITY_NUMBER = {
3
+ debug: 5,
4
+ info: 9,
5
+ warn: 13,
6
+ error: 17
7
+ };
8
+ const OTEL_SEVERITY_TEXT = {
9
+ debug: "DEBUG",
10
+ info: "INFO",
11
+ warn: "WARN",
12
+ error: "ERROR"
13
+ };
14
+
15
+ //#endregion
16
+ export { OTEL_SEVERITY_TEXT as n, OTEL_SEVERITY_NUMBER as t };
17
+ //# sourceMappingURL=_severity-CXfyvxQi.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_severity-CXfyvxQi.mjs","names":[],"sources":["../src/adapters/_severity.ts"],"sourcesContent":["import type { LogLevel } from '../types'\n\nexport const OTEL_SEVERITY_NUMBER: Record<LogLevel, number> = {\n debug: 5,\n info: 9,\n warn: 13,\n error: 17,\n}\n\nexport const OTEL_SEVERITY_TEXT: Record<LogLevel, string> = {\n debug: 'DEBUG',\n info: 'INFO',\n warn: 'WARN',\n error: 'ERROR',\n}\n"],"mappings":";AAEA,MAAa,uBAAiD;CAC5D,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACR;AAED,MAAa,qBAA+C;CAC1D,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACR"}
@@ -1,4 +1,5 @@
1
1
  import { DrainContext, WideEvent } from "../types.mjs";
2
+ import "../index.mjs";
2
3
 
3
4
  //#region src/adapters/axiom.d.ts
4
5
  interface AxiomConfig {
@@ -1 +1 @@
1
- {"version":3,"file":"axiom.d.mts","names":[],"sources":["../../src/adapters/axiom.ts"],"mappings":";;;UAGiB,WAAA;;EAEf,OAAA;EAF0B;EAI1B,KAAA;EAJ0B;EAM1B,KAAA;EAFA;EAIA,OAAA;EAAA;EAEA,OAAA;AAAA;;AAuBF;;;;;;;;;;;;;;;;;;;iBAAgB,gBAAA,CAAiB,SAAA,GAAY,OAAA,CAAQ,WAAA,KAAgB,GAAA,EAAK,YAAA,GAAe,YAAA,OAAmB,OAAA;;;;;;;;;;;;iBA2CtF,WAAA,CAAY,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,WAAA,GAAc,OAAA;;;AAe1E;;;;;;;;;iBAAsB,gBAAA,CAAiB,MAAA,EAAQ,SAAA,IAAa,MAAA,EAAQ,WAAA,GAAc,OAAA"}
1
+ {"version":3,"file":"axiom.d.mts","names":[],"sources":["../../src/adapters/axiom.ts"],"mappings":";;;;UAMiB,WAAA;;EAEf,OAAA;EAFe;EAIf,KAAA;;EAEA,KAAA;EAJA;EAMA,OAAA;EAFA;EAIA,OAAA;AAAA;;;AA+BF;;;;;;;;;;;;;;;;;;iBAAgB,gBAAA,CAAiB,SAAA,GAAY,OAAA,CAAQ,WAAA,KAAY,GAAA,EAAb,YAAA,GAAa,YAAA,OAAA,OAAA;AA0BjE;;;;;;;;;;;AAAA,iBAAsB,WAAA,CAAY,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,WAAA,GAAc,OAAA;;;;AAe1E;;;;;;;;iBAAsB,gBAAA,CAAiB,MAAA,EAAQ,SAAA,IAAa,MAAA,EAAQ,WAAA,GAAc,OAAA"}
@@ -1,6 +1,25 @@
1
- import { t as getRuntimeConfig } from "../_utils-DZA9nou3.mjs";
1
+ import { n as defineDrain, r as resolveAdapterConfig, t as httpPost } from "../_http-DVDwNag0.mjs";
2
2
 
3
3
  //#region src/adapters/axiom.ts
4
+ const AXIOM_FIELDS = [
5
+ {
6
+ key: "dataset",
7
+ env: ["NUXT_AXIOM_DATASET", "AXIOM_DATASET"]
8
+ },
9
+ {
10
+ key: "token",
11
+ env: ["NUXT_AXIOM_TOKEN", "AXIOM_TOKEN"]
12
+ },
13
+ {
14
+ key: "orgId",
15
+ env: ["NUXT_AXIOM_ORG_ID", "AXIOM_ORG_ID"]
16
+ },
17
+ {
18
+ key: "baseUrl",
19
+ env: ["NUXT_AXIOM_URL", "AXIOM_URL"]
20
+ },
21
+ { key: "timeout" }
22
+ ];
4
23
  /**
5
24
  * Create a drain function for sending logs to Axiom.
6
25
  *
@@ -22,29 +41,18 @@ import { t as getRuntimeConfig } from "../_utils-DZA9nou3.mjs";
22
41
  * ```
23
42
  */
24
43
  function createAxiomDrain(overrides) {
25
- return async (ctx) => {
26
- const contexts = Array.isArray(ctx) ? ctx : [ctx];
27
- if (contexts.length === 0) return;
28
- const runtimeConfig = getRuntimeConfig();
29
- const evlogAxiom = runtimeConfig?.evlog?.axiom;
30
- const rootAxiom = runtimeConfig?.axiom;
31
- const config = {
32
- dataset: overrides?.dataset ?? evlogAxiom?.dataset ?? rootAxiom?.dataset ?? process.env.NUXT_AXIOM_DATASET ?? process.env.AXIOM_DATASET,
33
- token: overrides?.token ?? evlogAxiom?.token ?? rootAxiom?.token ?? process.env.NUXT_AXIOM_TOKEN ?? process.env.AXIOM_TOKEN,
34
- orgId: overrides?.orgId ?? evlogAxiom?.orgId ?? rootAxiom?.orgId ?? process.env.NUXT_AXIOM_ORG_ID ?? process.env.AXIOM_ORG_ID,
35
- baseUrl: overrides?.baseUrl ?? evlogAxiom?.baseUrl ?? rootAxiom?.baseUrl ?? process.env.NUXT_AXIOM_URL ?? process.env.AXIOM_URL,
36
- timeout: overrides?.timeout ?? evlogAxiom?.timeout ?? rootAxiom?.timeout
37
- };
38
- if (!config.dataset || !config.token) {
39
- console.error("[evlog/axiom] Missing dataset or token. Set NUXT_AXIOM_TOKEN/NUXT_AXIOM_DATASET env vars or pass to createAxiomDrain()");
40
- return;
41
- }
42
- try {
43
- await sendBatchToAxiom(contexts.map((c) => c.event), config);
44
- } catch (error) {
45
- console.error("[evlog/axiom] Failed to send events to Axiom:", error);
46
- }
47
- };
44
+ return defineDrain({
45
+ name: "axiom",
46
+ resolve: () => {
47
+ const config = resolveAdapterConfig("axiom", AXIOM_FIELDS, overrides);
48
+ if (!config.dataset || !config.token) {
49
+ console.error("[evlog/axiom] Missing dataset or token. Set NUXT_AXIOM_TOKEN/NUXT_AXIOM_DATASET env vars or pass to createAxiomDrain()");
50
+ return null;
51
+ }
52
+ return config;
53
+ },
54
+ send: sendBatchToAxiom
55
+ });
48
56
  }
49
57
  /**
50
58
  * Send a single event to Axiom.
@@ -72,31 +80,19 @@ async function sendToAxiom(event, config) {
72
80
  * ```
73
81
  */
74
82
  async function sendBatchToAxiom(events, config) {
75
- const baseUrl = config.baseUrl ?? "https://api.axiom.co";
76
- const timeout = config.timeout ?? 5e3;
77
- const url = `${baseUrl}/v1/datasets/${encodeURIComponent(config.dataset)}/ingest`;
83
+ const url = `${config.baseUrl ?? "https://api.axiom.co"}/v1/datasets/${encodeURIComponent(config.dataset)}/ingest`;
78
84
  const headers = {
79
85
  "Content-Type": "application/json",
80
86
  "Authorization": `Bearer ${config.token}`
81
87
  };
82
88
  if (config.orgId) headers["X-Axiom-Org-Id"] = config.orgId;
83
- const controller = new AbortController();
84
- const timeoutId = setTimeout(() => controller.abort(), timeout);
85
- try {
86
- const response = await fetch(url, {
87
- method: "POST",
88
- headers,
89
- body: JSON.stringify(events),
90
- signal: controller.signal
91
- });
92
- if (!response.ok) {
93
- const text = await response.text().catch(() => "Unknown error");
94
- const safeText = text.length > 200 ? `${text.slice(0, 200)}...[truncated]` : text;
95
- throw new Error(`Axiom API error: ${response.status} ${response.statusText} - ${safeText}`);
96
- }
97
- } finally {
98
- clearTimeout(timeoutId);
99
- }
89
+ await httpPost({
90
+ url,
91
+ headers,
92
+ body: JSON.stringify(events),
93
+ timeout: config.timeout ?? 5e3,
94
+ label: "Axiom"
95
+ });
100
96
  }
101
97
 
102
98
  //#endregion