evlog 1.6.0 → 1.8.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 (136) hide show
  1. package/README.md +96 -0
  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 +17 -15
  7. package/dist/adapters/axiom.d.mts.map +1 -0
  8. package/dist/adapters/axiom.mjs +91 -50
  9. package/dist/adapters/axiom.mjs.map +1 -0
  10. package/dist/adapters/better-stack.d.mts +63 -0
  11. package/dist/adapters/better-stack.d.mts.map +1 -0
  12. package/dist/adapters/better-stack.mjs +98 -0
  13. package/dist/adapters/better-stack.mjs.map +1 -0
  14. package/dist/adapters/otlp.d.mts +32 -30
  15. package/dist/adapters/otlp.d.mts.map +1 -0
  16. package/dist/adapters/otlp.mjs +181 -181
  17. package/dist/adapters/otlp.mjs.map +1 -0
  18. package/dist/adapters/posthog.d.mts +54 -19
  19. package/dist/adapters/posthog.d.mts.map +1 -0
  20. package/dist/adapters/posthog.mjs +156 -63
  21. package/dist/adapters/posthog.mjs.map +1 -0
  22. package/dist/adapters/sentry.d.mts +25 -23
  23. package/dist/adapters/sentry.d.mts.map +1 -0
  24. package/dist/adapters/sentry.mjs +198 -153
  25. package/dist/adapters/sentry.mjs.map +1 -0
  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/enrichers.d.mts +74 -0
  31. package/dist/enrichers.d.mts.map +1 -0
  32. package/dist/enrichers.mjs +172 -0
  33. package/dist/enrichers.mjs.map +1 -0
  34. package/dist/error.d.mts +24 -22
  35. package/dist/error.d.mts.map +1 -0
  36. package/dist/error.mjs +107 -76
  37. package/dist/error.mjs.map +1 -0
  38. package/dist/index.d.mts +6 -5
  39. package/dist/index.mjs +6 -5
  40. package/dist/logger.d.mts +11 -5
  41. package/dist/logger.d.mts.map +1 -0
  42. package/dist/logger.mjs +255 -186
  43. package/dist/logger.mjs.map +1 -0
  44. package/dist/nitro/errorHandler.d.mts +4 -2
  45. package/dist/nitro/errorHandler.d.mts.map +1 -0
  46. package/dist/nitro/errorHandler.mjs +38 -38
  47. package/dist/nitro/errorHandler.mjs.map +1 -0
  48. package/dist/nitro/module.d.mts +11 -0
  49. package/dist/nitro/module.d.mts.map +1 -0
  50. package/dist/nitro/module.mjs +23 -0
  51. package/dist/nitro/module.mjs.map +1 -0
  52. package/dist/nitro/plugin.d.mts +4 -2
  53. package/dist/nitro/plugin.d.mts.map +1 -0
  54. package/dist/nitro/plugin.mjs +135 -140
  55. package/dist/nitro/plugin.mjs.map +1 -0
  56. package/dist/nitro/v3/errorHandler.d.mts +24 -0
  57. package/dist/nitro/v3/errorHandler.d.mts.map +1 -0
  58. package/dist/nitro/v3/errorHandler.mjs +36 -0
  59. package/dist/nitro/v3/errorHandler.mjs.map +1 -0
  60. package/dist/nitro/v3/index.d.mts +4 -0
  61. package/dist/nitro/v3/index.mjs +4 -0
  62. package/dist/nitro/v3/module.d.mts +10 -0
  63. package/dist/nitro/v3/module.d.mts.map +1 -0
  64. package/dist/nitro/v3/module.mjs +22 -0
  65. package/dist/nitro/v3/module.mjs.map +1 -0
  66. package/dist/nitro/v3/plugin.d.mts +14 -0
  67. package/dist/nitro/v3/plugin.d.mts.map +1 -0
  68. package/dist/nitro/v3/plugin.mjs +157 -0
  69. package/dist/nitro/v3/plugin.mjs.map +1 -0
  70. package/dist/nitro/v3/useLogger.d.mts +24 -0
  71. package/dist/nitro/v3/useLogger.d.mts.map +1 -0
  72. package/dist/nitro/v3/useLogger.mjs +27 -0
  73. package/dist/nitro/v3/useLogger.mjs.map +1 -0
  74. package/dist/nitro-D57TWGyN.mjs +73 -0
  75. package/dist/nitro-D57TWGyN.mjs.map +1 -0
  76. package/dist/nitro-D81NBVPi.d.mts +42 -0
  77. package/dist/nitro-D81NBVPi.d.mts.map +1 -0
  78. package/dist/nuxt/module.d.mts +155 -168
  79. package/dist/nuxt/module.d.mts.map +1 -0
  80. package/dist/nuxt/module.mjs +75 -65
  81. package/dist/nuxt/module.mjs.map +1 -0
  82. package/dist/pipeline.d.mts +46 -0
  83. package/dist/pipeline.d.mts.map +1 -0
  84. package/dist/pipeline.mjs +122 -0
  85. package/dist/pipeline.mjs.map +1 -0
  86. package/dist/runtime/client/log.d.mts +12 -7
  87. package/dist/runtime/client/log.d.mts.map +1 -0
  88. package/dist/runtime/client/log.mjs +72 -64
  89. package/dist/runtime/client/log.mjs.map +1 -0
  90. package/dist/runtime/client/plugin.d.mts +3 -1
  91. package/dist/runtime/client/plugin.d.mts.map +1 -0
  92. package/dist/runtime/client/plugin.mjs +14 -12
  93. package/dist/runtime/client/plugin.mjs.map +1 -0
  94. package/dist/runtime/server/routes/_evlog/ingest.post.d.mts +4 -2
  95. package/dist/runtime/server/routes/_evlog/ingest.post.d.mts.map +1 -0
  96. package/dist/runtime/server/routes/_evlog/ingest.post.mjs +113 -76
  97. package/dist/runtime/server/routes/_evlog/ingest.post.mjs.map +1 -0
  98. package/dist/runtime/server/useLogger.d.mts +14 -3
  99. package/dist/runtime/server/useLogger.d.mts.map +1 -0
  100. package/dist/runtime/server/useLogger.mjs +39 -10
  101. package/dist/runtime/server/useLogger.mjs.map +1 -0
  102. package/dist/runtime/utils/parseError.d.mts +5 -3
  103. package/dist/runtime/utils/parseError.d.mts.map +1 -0
  104. package/dist/runtime/utils/parseError.mjs +25 -26
  105. package/dist/runtime/utils/parseError.mjs.map +1 -0
  106. package/dist/types.d.mts +378 -246
  107. package/dist/types.d.mts.map +1 -0
  108. package/dist/types.mjs +1 -1
  109. package/dist/utils.d.mts +19 -14
  110. package/dist/utils.d.mts.map +1 -0
  111. package/dist/utils.mjs +59 -50
  112. package/dist/utils.mjs.map +1 -0
  113. package/dist/workers.d.mts +10 -9
  114. package/dist/workers.d.mts.map +1 -0
  115. package/dist/workers.mjs +68 -39
  116. package/dist/workers.mjs.map +1 -0
  117. package/package.json +55 -10
  118. package/dist/adapters/axiom.d.ts +0 -62
  119. package/dist/adapters/otlp.d.ts +0 -83
  120. package/dist/adapters/posthog.d.ts +0 -72
  121. package/dist/adapters/sentry.d.ts +0 -78
  122. package/dist/error.d.ts +0 -63
  123. package/dist/index.d.ts +0 -5
  124. package/dist/logger.d.ts +0 -40
  125. package/dist/nitro/errorHandler.d.ts +0 -13
  126. package/dist/nitro/plugin.d.ts +0 -5
  127. package/dist/nuxt/module.d.ts +0 -171
  128. package/dist/runtime/client/log.d.ts +0 -10
  129. package/dist/runtime/client/plugin.d.ts +0 -3
  130. package/dist/runtime/server/routes/_evlog/ingest.post.d.ts +0 -5
  131. package/dist/runtime/server/useLogger.d.ts +0 -28
  132. package/dist/runtime/utils/parseError.d.ts +0 -5
  133. package/dist/shared/evlog.Bc35pxiY.mjs +0 -10
  134. package/dist/types.d.ts +0 -364
  135. package/dist/utils.d.ts +0 -29
  136. package/dist/workers.d.ts +0 -45
package/README.md CHANGED
@@ -390,6 +390,44 @@ Notes:
390
390
  - `request.cf` is included (colo, country, asn) unless disabled
391
391
  - Use `headerAllowlist` to avoid logging sensitive headers
392
392
 
393
+ ## Enrichment Hook
394
+
395
+ Use the `evlog:enrich` hook to add derived context after emit, before drain.
396
+
397
+ ```typescript
398
+ // server/plugins/evlog-enrich.ts
399
+ export default defineNitroPlugin((nitroApp) => {
400
+ nitroApp.hooks.hook('evlog:enrich', (ctx) => {
401
+ ctx.event.deploymentId = process.env.DEPLOYMENT_ID
402
+ })
403
+ })
404
+ ```
405
+
406
+ ### Built-in Enrichers
407
+
408
+ ```typescript
409
+ // server/plugins/evlog-enrich.ts
410
+ import {
411
+ createGeoEnricher,
412
+ createRequestSizeEnricher,
413
+ createTraceContextEnricher,
414
+ createUserAgentEnricher,
415
+ } from 'evlog/enrichers'
416
+
417
+ export default defineNitroPlugin((nitroApp) => {
418
+ const enrich = [
419
+ createUserAgentEnricher(),
420
+ createGeoEnricher(),
421
+ createRequestSizeEnricher(),
422
+ createTraceContextEnricher(),
423
+ ]
424
+
425
+ nitroApp.hooks.hook('evlog:enrich', (ctx) => {
426
+ for (const enricher of enrich) enricher(ctx)
427
+ })
428
+ })
429
+ ```
430
+
393
431
  ## Adapters
394
432
 
395
433
  Send your logs to external observability platforms with built-in adapters.
@@ -486,6 +524,63 @@ export default defineNitroPlugin((nitroApp) => {
486
524
 
487
525
  > See the [full documentation](https://evlog.hrcd.fr/adapters/overview) for adapter configuration options, troubleshooting, and advanced patterns.
488
526
 
527
+ ## Drain Pipeline
528
+
529
+ For production use, wrap your drain adapter with `createDrainPipeline` to get **batching**, **retry with backoff**, and **buffer overflow protection**.
530
+
531
+ Without a pipeline, each event triggers a separate network call. The pipeline buffers events and sends them in batches, reducing overhead and handling transient failures automatically.
532
+
533
+ ```typescript
534
+ // server/plugins/evlog-drain.ts
535
+ import type { DrainContext } from 'evlog'
536
+ import { createDrainPipeline } from 'evlog/pipeline'
537
+ import { createAxiomDrain } from 'evlog/axiom'
538
+
539
+ export default defineNitroPlugin((nitroApp) => {
540
+ const pipeline = createDrainPipeline<DrainContext>({
541
+ batch: { size: 50, intervalMs: 5000 },
542
+ retry: { maxAttempts: 3, backoff: 'exponential', initialDelayMs: 1000 },
543
+ onDropped: (events, error) => {
544
+ console.error(`[evlog] Dropped ${events.length} events:`, error?.message)
545
+ },
546
+ })
547
+
548
+ const drain = pipeline(createAxiomDrain())
549
+
550
+ nitroApp.hooks.hook('evlog:drain', drain)
551
+ nitroApp.hooks.hook('close', () => drain.flush())
552
+ })
553
+ ```
554
+
555
+ ### How it works
556
+
557
+ 1. Events are buffered in memory as they arrive
558
+ 2. A batch is flushed when either the **batch size** is reached or the **interval** expires (whichever comes first)
559
+ 3. If the drain function fails, the batch is retried with the configured **backoff strategy**
560
+ 4. If all retries are exhausted, `onDropped` is called with the lost events
561
+ 5. If the buffer exceeds `maxBufferSize`, the oldest events are dropped to prevent memory leaks
562
+
563
+ ### Options
564
+
565
+ | Option | Default | Description |
566
+ |--------|---------|-------------|
567
+ | `batch.size` | `50` | Maximum events per batch |
568
+ | `batch.intervalMs` | `5000` | Max time (ms) before flushing a partial batch |
569
+ | `retry.maxAttempts` | `3` | Total attempts (including first) |
570
+ | `retry.backoff` | `'exponential'` | `'exponential'` \| `'linear'` \| `'fixed'` |
571
+ | `retry.initialDelayMs` | `1000` | Base delay for first retry |
572
+ | `retry.maxDelayMs` | `30000` | Upper bound for any retry delay |
573
+ | `maxBufferSize` | `1000` | Max buffered events before dropping oldest |
574
+ | `onDropped` | — | Callback when events are dropped |
575
+
576
+ ### Returned drain function
577
+
578
+ The function returned by `pipeline(drain)` is hook-compatible and exposes:
579
+
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
583
+
489
584
  ## API Reference
490
585
 
491
586
  ### `initLogger(config)`
@@ -494,6 +589,7 @@ Initialize the logger. Required for standalone usage, automatic with Nuxt/Nitro
494
589
 
495
590
  ```typescript
496
591
  initLogger({
592
+ enabled: boolean // (optional) Enable/disable all logging (default: true)
497
593
  env: {
498
594
  service: string // Service name
499
595
  environment: string // 'production' | 'development' | 'test'
@@ -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,16 +1,18 @@
1
- import { DrainContext, WideEvent } from '../types.mjs';
1
+ import { DrainContext, WideEvent } from "../types.mjs";
2
+ import "../index.mjs";
2
3
 
4
+ //#region src/adapters/axiom.d.ts
3
5
  interface AxiomConfig {
4
- /** Axiom dataset name */
5
- dataset: string;
6
- /** Axiom API token */
7
- token: string;
8
- /** Organization ID (required for Personal Access Tokens) */
9
- orgId?: string;
10
- /** Base URL for Axiom API. Default: https://api.axiom.co */
11
- baseUrl?: string;
12
- /** Request timeout in milliseconds. Default: 5000 */
13
- timeout?: number;
6
+ /** Axiom dataset name */
7
+ dataset: string;
8
+ /** Axiom API token */
9
+ token: string;
10
+ /** Organization ID (required for Personal Access Tokens) */
11
+ orgId?: string;
12
+ /** Base URL for Axiom API. Default: https://api.axiom.co */
13
+ baseUrl?: string;
14
+ /** Request timeout in milliseconds. Default: 5000 */
15
+ timeout?: number;
14
16
  }
15
17
  /**
16
18
  * Create a drain function for sending logs to Axiom.
@@ -32,7 +34,7 @@ interface AxiomConfig {
32
34
  * }))
33
35
  * ```
34
36
  */
35
- declare function createAxiomDrain(overrides?: Partial<AxiomConfig>): (ctx: DrainContext) => Promise<void>;
37
+ declare function createAxiomDrain(overrides?: Partial<AxiomConfig>): (ctx: DrainContext | DrainContext[]) => Promise<void>;
36
38
  /**
37
39
  * Send a single event to Axiom.
38
40
  *
@@ -57,6 +59,6 @@ declare function sendToAxiom(event: WideEvent, config: AxiomConfig): Promise<voi
57
59
  * ```
58
60
  */
59
61
  declare function sendBatchToAxiom(events: WideEvent[], config: AxiomConfig): Promise<void>;
60
-
61
- export { createAxiomDrain, sendBatchToAxiom, sendToAxiom };
62
- export type { AxiomConfig };
62
+ //#endregion
63
+ export { AxiomConfig, createAxiomDrain, sendBatchToAxiom, sendToAxiom };
64
+ //# sourceMappingURL=axiom.d.mts.map
@@ -0,0 +1 @@
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,59 +1,100 @@
1
- import { g as getRuntimeConfig } from '../shared/evlog.Bc35pxiY.mjs';
1
+ import { n as defineDrain, r as resolveAdapterConfig, t as httpPost } from "../_http-DVDwNag0.mjs";
2
2
 
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
+ ];
23
+ /**
24
+ * Create a drain function for sending logs to Axiom.
25
+ *
26
+ * Configuration priority (highest to lowest):
27
+ * 1. Overrides passed to createAxiomDrain()
28
+ * 2. runtimeConfig.evlog.axiom
29
+ * 3. runtimeConfig.axiom
30
+ * 4. Environment variables: NUXT_AXIOM_*, AXIOM_*
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * // Zero config - just set NUXT_AXIOM_TOKEN and NUXT_AXIOM_DATASET env vars
35
+ * nitroApp.hooks.hook('evlog:drain', createAxiomDrain())
36
+ *
37
+ * // With overrides
38
+ * nitroApp.hooks.hook('evlog:drain', createAxiomDrain({
39
+ * dataset: 'my-dataset',
40
+ * }))
41
+ * ```
42
+ */
3
43
  function createAxiomDrain(overrides) {
4
- return async (ctx) => {
5
- const runtimeConfig = getRuntimeConfig();
6
- const evlogAxiom = runtimeConfig?.evlog?.axiom;
7
- const rootAxiom = runtimeConfig?.axiom;
8
- const config = {
9
- dataset: overrides?.dataset ?? evlogAxiom?.dataset ?? rootAxiom?.dataset ?? process.env.NUXT_AXIOM_DATASET ?? process.env.AXIOM_DATASET,
10
- token: overrides?.token ?? evlogAxiom?.token ?? rootAxiom?.token ?? process.env.NUXT_AXIOM_TOKEN ?? process.env.AXIOM_TOKEN,
11
- orgId: overrides?.orgId ?? evlogAxiom?.orgId ?? rootAxiom?.orgId ?? process.env.NUXT_AXIOM_ORG_ID ?? process.env.AXIOM_ORG_ID,
12
- baseUrl: overrides?.baseUrl ?? evlogAxiom?.baseUrl ?? rootAxiom?.baseUrl ?? process.env.NUXT_AXIOM_URL ?? process.env.AXIOM_URL,
13
- timeout: overrides?.timeout ?? evlogAxiom?.timeout ?? rootAxiom?.timeout
14
- };
15
- if (!config.dataset || !config.token) {
16
- console.error("[evlog/axiom] Missing dataset or token. Set NUXT_AXIOM_TOKEN/NUXT_AXIOM_DATASET env vars or pass to createAxiomDrain()");
17
- return;
18
- }
19
- try {
20
- await sendToAxiom(ctx.event, config);
21
- } catch (error) {
22
- console.error("[evlog/axiom] Failed to send event:", error);
23
- }
24
- };
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
+ });
25
56
  }
57
+ /**
58
+ * Send a single event to Axiom.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * await sendToAxiom(event, {
63
+ * dataset: 'my-logs',
64
+ * token: process.env.AXIOM_TOKEN!,
65
+ * })
66
+ * ```
67
+ */
26
68
  async function sendToAxiom(event, config) {
27
- await sendBatchToAxiom([event], config);
69
+ await sendBatchToAxiom([event], config);
28
70
  }
71
+ /**
72
+ * Send a batch of events to Axiom.
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * await sendBatchToAxiom(events, {
77
+ * dataset: 'my-logs',
78
+ * token: process.env.AXIOM_TOKEN!,
79
+ * })
80
+ * ```
81
+ */
29
82
  async function sendBatchToAxiom(events, config) {
30
- const baseUrl = config.baseUrl ?? "https://api.axiom.co";
31
- const timeout = config.timeout ?? 5e3;
32
- const url = `${baseUrl}/v1/datasets/${encodeURIComponent(config.dataset)}/ingest`;
33
- const headers = {
34
- "Content-Type": "application/json",
35
- "Authorization": `Bearer ${config.token}`
36
- };
37
- if (config.orgId) {
38
- headers["X-Axiom-Org-Id"] = config.orgId;
39
- }
40
- const controller = new AbortController();
41
- const timeoutId = setTimeout(() => controller.abort(), timeout);
42
- try {
43
- const response = await fetch(url, {
44
- method: "POST",
45
- headers,
46
- body: JSON.stringify(events),
47
- signal: controller.signal
48
- });
49
- if (!response.ok) {
50
- const text = await response.text().catch(() => "Unknown error");
51
- const safeText = text.length > 200 ? `${text.slice(0, 200)}...[truncated]` : text;
52
- throw new Error(`Axiom API error: ${response.status} ${response.statusText} - ${safeText}`);
53
- }
54
- } finally {
55
- clearTimeout(timeoutId);
56
- }
83
+ const url = `${config.baseUrl ?? "https://api.axiom.co"}/v1/datasets/${encodeURIComponent(config.dataset)}/ingest`;
84
+ const headers = {
85
+ "Content-Type": "application/json",
86
+ "Authorization": `Bearer ${config.token}`
87
+ };
88
+ if (config.orgId) headers["X-Axiom-Org-Id"] = config.orgId;
89
+ await httpPost({
90
+ url,
91
+ headers,
92
+ body: JSON.stringify(events),
93
+ timeout: config.timeout ?? 5e3,
94
+ label: "Axiom"
95
+ });
57
96
  }
58
97
 
98
+ //#endregion
59
99
  export { createAxiomDrain, sendBatchToAxiom, sendToAxiom };
100
+ //# sourceMappingURL=axiom.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"axiom.mjs","names":[],"sources":["../../src/adapters/axiom.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\n\nexport interface AxiomConfig {\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\nconst AXIOM_FIELDS: ConfigField<AxiomConfig>[] = [\n { key: 'dataset', env: ['NUXT_AXIOM_DATASET', 'AXIOM_DATASET'] },\n { key: 'token', env: ['NUXT_AXIOM_TOKEN', 'AXIOM_TOKEN'] },\n { key: 'orgId', env: ['NUXT_AXIOM_ORG_ID', 'AXIOM_ORG_ID'] },\n { key: 'baseUrl', env: ['NUXT_AXIOM_URL', 'AXIOM_URL'] },\n { key: 'timeout' },\n]\n\n/**\n * Create a drain function for sending logs to Axiom.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createAxiomDrain()\n * 2. runtimeConfig.evlog.axiom\n * 3. runtimeConfig.axiom\n * 4. Environment variables: NUXT_AXIOM_*, AXIOM_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_AXIOM_TOKEN and NUXT_AXIOM_DATASET env vars\n * nitroApp.hooks.hook('evlog:drain', createAxiomDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createAxiomDrain({\n * dataset: 'my-dataset',\n * }))\n * ```\n */\nexport function createAxiomDrain(overrides?: Partial<AxiomConfig>) {\n return defineDrain<AxiomConfig>({\n name: 'axiom',\n resolve: () => {\n const config = resolveAdapterConfig<AxiomConfig>('axiom', AXIOM_FIELDS, overrides)\n if (!config.dataset || !config.token) {\n console.error('[evlog/axiom] Missing dataset or token. Set NUXT_AXIOM_TOKEN/NUXT_AXIOM_DATASET env vars or pass to createAxiomDrain()')\n return null\n }\n return config as AxiomConfig\n },\n send: sendBatchToAxiom,\n })\n}\n\n/**\n * Send a single event to Axiom.\n *\n * @example\n * ```ts\n * await sendToAxiom(event, {\n * dataset: 'my-logs',\n * token: process.env.AXIOM_TOKEN!,\n * })\n * ```\n */\nexport async function sendToAxiom(event: WideEvent, config: AxiomConfig): Promise<void> {\n await sendBatchToAxiom([event], config)\n}\n\n/**\n * Send a batch of events to Axiom.\n *\n * @example\n * ```ts\n * await sendBatchToAxiom(events, {\n * dataset: 'my-logs',\n * token: process.env.AXIOM_TOKEN!,\n * })\n * ```\n */\nexport async function sendBatchToAxiom(events: WideEvent[], config: AxiomConfig): Promise<void> {\n const baseUrl = config.baseUrl ?? 'https://api.axiom.co'\n const url = `${baseUrl}/v1/datasets/${encodeURIComponent(config.dataset)}/ingest`\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.token}`,\n }\n\n if (config.orgId) {\n headers['X-Axiom-Org-Id'] = config.orgId\n }\n\n await httpPost({\n url,\n headers,\n body: JSON.stringify(events),\n timeout: config.timeout ?? 5000,\n label: 'Axiom',\n })\n}\n"],"mappings":";;;AAmBA,MAAM,eAA2C;CAC/C;EAAE,KAAK;EAAW,KAAK,CAAC,sBAAsB,gBAAgB;EAAE;CAChE;EAAE,KAAK;EAAS,KAAK,CAAC,oBAAoB,cAAc;EAAE;CAC1D;EAAE,KAAK;EAAS,KAAK,CAAC,qBAAqB,eAAe;EAAE;CAC5D;EAAE,KAAK;EAAW,KAAK,CAAC,kBAAkB,YAAY;EAAE;CACxD,EAAE,KAAK,WAAW;CACnB;;;;;;;;;;;;;;;;;;;;;AAsBD,SAAgB,iBAAiB,WAAkC;AACjE,QAAO,YAAyB;EAC9B,MAAM;EACN,eAAe;GACb,MAAM,SAAS,qBAAkC,SAAS,cAAc,UAAU;AAClF,OAAI,CAAC,OAAO,WAAW,CAAC,OAAO,OAAO;AACpC,YAAQ,MAAM,yHAAyH;AACvI,WAAO;;AAET,UAAO;;EAET,MAAM;EACP,CAAC;;;;;;;;;;;;;AAcJ,eAAsB,YAAY,OAAkB,QAAoC;AACtF,OAAM,iBAAiB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;;AAczC,eAAsB,iBAAiB,QAAqB,QAAoC;CAE9F,MAAM,MAAM,GADI,OAAO,WAAW,uBACX,eAAe,mBAAmB,OAAO,QAAQ,CAAC;CAEzE,MAAM,UAAkC;EACtC,gBAAgB;EAChB,iBAAiB,UAAU,OAAO;EACnC;AAED,KAAI,OAAO,MACT,SAAQ,oBAAoB,OAAO;AAGrC,OAAM,SAAS;EACb;EACA;EACA,MAAM,KAAK,UAAU,OAAO;EAC5B,SAAS,OAAO,WAAW;EAC3B,OAAO;EACR,CAAC"}
@@ -0,0 +1,63 @@
1
+ import { DrainContext, WideEvent } from "../types.mjs";
2
+ import "../index.mjs";
3
+
4
+ //#region src/adapters/better-stack.d.ts
5
+ interface BetterStackConfig {
6
+ /** Better Stack source token */
7
+ sourceToken: string;
8
+ /** Logtail ingestion endpoint. Default: https://in.logs.betterstack.com */
9
+ endpoint?: string;
10
+ /** Request timeout in milliseconds. Default: 5000 */
11
+ timeout?: number;
12
+ }
13
+ /**
14
+ * Transform an evlog wide event into a Better Stack event.
15
+ * Maps `timestamp` to `dt` (Better Stack's expected field).
16
+ */
17
+ declare function toBetterStackEvent(event: WideEvent): Record<string, unknown>;
18
+ /**
19
+ * Create a drain function for sending logs to Better Stack.
20
+ *
21
+ * Configuration priority (highest to lowest):
22
+ * 1. Overrides passed to createBetterStackDrain()
23
+ * 2. runtimeConfig.evlog.betterStack
24
+ * 3. runtimeConfig.betterStack
25
+ * 4. Environment variables: NUXT_BETTER_STACK_*, BETTER_STACK_*
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // Zero config - just set NUXT_BETTER_STACK_SOURCE_TOKEN env var
30
+ * nitroApp.hooks.hook('evlog:drain', createBetterStackDrain())
31
+ *
32
+ * // With overrides
33
+ * nitroApp.hooks.hook('evlog:drain', createBetterStackDrain({
34
+ * sourceToken: 'my-token',
35
+ * }))
36
+ * ```
37
+ */
38
+ declare function createBetterStackDrain(overrides?: Partial<BetterStackConfig>): (ctx: DrainContext | DrainContext[]) => Promise<void>;
39
+ /**
40
+ * Send a single event to Better Stack.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * await sendToBetterStack(event, {
45
+ * sourceToken: process.env.BETTER_STACK_SOURCE_TOKEN!,
46
+ * })
47
+ * ```
48
+ */
49
+ declare function sendToBetterStack(event: WideEvent, config: BetterStackConfig): Promise<void>;
50
+ /**
51
+ * Send a batch of events to Better Stack.
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * await sendBatchToBetterStack(events, {
56
+ * sourceToken: process.env.BETTER_STACK_SOURCE_TOKEN!,
57
+ * })
58
+ * ```
59
+ */
60
+ declare function sendBatchToBetterStack(events: WideEvent[], config: BetterStackConfig): Promise<void>;
61
+ //#endregion
62
+ export { BetterStackConfig, createBetterStackDrain, sendBatchToBetterStack, sendToBetterStack, toBetterStackEvent };
63
+ //# sourceMappingURL=better-stack.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"better-stack.d.mts","names":[],"sources":["../../src/adapters/better-stack.ts"],"mappings":";;;;UAMiB,iBAAA;;EAEf,WAAA;EAFe;EAIf,QAAA;;EAEA,OAAA;AAAA;;;;;iBAac,kBAAA,CAAmB,KAAA,EAAO,SAAA,GAAY,MAAA;;;;;;;;;AAyBtD;;;;;;;;;;;;iBAAgB,sBAAA,CAAuB,SAAA,GAAY,OAAA,CAAQ,iBAAA,KAAkB,GAAA,EAAnB,YAAA,GAAmB,YAAA,OAAA,OAAA;;;;;;;AAyB7E;;;;iBAAsB,iBAAA,CAAkB,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,iBAAA,GAAoB,OAAA;;;;;;;;;;;iBAchE,sBAAA,CAAuB,MAAA,EAAQ,SAAA,IAAa,MAAA,EAAQ,iBAAA,GAAoB,OAAA"}
@@ -0,0 +1,98 @@
1
+ import { n as defineDrain, r as resolveAdapterConfig, t as httpPost } from "../_http-DVDwNag0.mjs";
2
+
3
+ //#region src/adapters/better-stack.ts
4
+ const BETTER_STACK_FIELDS = [
5
+ {
6
+ key: "sourceToken",
7
+ env: ["NUXT_BETTER_STACK_SOURCE_TOKEN", "BETTER_STACK_SOURCE_TOKEN"]
8
+ },
9
+ {
10
+ key: "endpoint",
11
+ env: ["NUXT_BETTER_STACK_ENDPOINT", "BETTER_STACK_ENDPOINT"]
12
+ },
13
+ { key: "timeout" }
14
+ ];
15
+ /**
16
+ * Transform an evlog wide event into a Better Stack event.
17
+ * Maps `timestamp` to `dt` (Better Stack's expected field).
18
+ */
19
+ function toBetterStackEvent(event) {
20
+ const { timestamp, ...rest } = event;
21
+ return {
22
+ ...rest,
23
+ dt: timestamp
24
+ };
25
+ }
26
+ /**
27
+ * Create a drain function for sending logs to Better Stack.
28
+ *
29
+ * Configuration priority (highest to lowest):
30
+ * 1. Overrides passed to createBetterStackDrain()
31
+ * 2. runtimeConfig.evlog.betterStack
32
+ * 3. runtimeConfig.betterStack
33
+ * 4. Environment variables: NUXT_BETTER_STACK_*, BETTER_STACK_*
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * // Zero config - just set NUXT_BETTER_STACK_SOURCE_TOKEN env var
38
+ * nitroApp.hooks.hook('evlog:drain', createBetterStackDrain())
39
+ *
40
+ * // With overrides
41
+ * nitroApp.hooks.hook('evlog:drain', createBetterStackDrain({
42
+ * sourceToken: 'my-token',
43
+ * }))
44
+ * ```
45
+ */
46
+ function createBetterStackDrain(overrides) {
47
+ return defineDrain({
48
+ name: "better-stack",
49
+ resolve: () => {
50
+ const config = resolveAdapterConfig("betterStack", BETTER_STACK_FIELDS, overrides);
51
+ if (!config.sourceToken) {
52
+ console.error("[evlog/better-stack] Missing source token. Set NUXT_BETTER_STACK_SOURCE_TOKEN env var or pass to createBetterStackDrain()");
53
+ return null;
54
+ }
55
+ return config;
56
+ },
57
+ send: sendBatchToBetterStack
58
+ });
59
+ }
60
+ /**
61
+ * Send a single event to Better Stack.
62
+ *
63
+ * @example
64
+ * ```ts
65
+ * await sendToBetterStack(event, {
66
+ * sourceToken: process.env.BETTER_STACK_SOURCE_TOKEN!,
67
+ * })
68
+ * ```
69
+ */
70
+ async function sendToBetterStack(event, config) {
71
+ await sendBatchToBetterStack([event], config);
72
+ }
73
+ /**
74
+ * Send a batch of events to Better Stack.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * await sendBatchToBetterStack(events, {
79
+ * sourceToken: process.env.BETTER_STACK_SOURCE_TOKEN!,
80
+ * })
81
+ * ```
82
+ */
83
+ async function sendBatchToBetterStack(events, config) {
84
+ await httpPost({
85
+ url: (config.endpoint ?? "https://in.logs.betterstack.com").replace(/\/+$/, ""),
86
+ headers: {
87
+ "Content-Type": "application/json",
88
+ "Authorization": `Bearer ${config.sourceToken}`
89
+ },
90
+ body: JSON.stringify(events.map(toBetterStackEvent)),
91
+ timeout: config.timeout ?? 5e3,
92
+ label: "Better Stack"
93
+ });
94
+ }
95
+
96
+ //#endregion
97
+ export { createBetterStackDrain, sendBatchToBetterStack, sendToBetterStack, toBetterStackEvent };
98
+ //# sourceMappingURL=better-stack.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"better-stack.mjs","names":[],"sources":["../../src/adapters/better-stack.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\n\nexport interface BetterStackConfig {\n /** Better Stack source token */\n sourceToken: string\n /** Logtail ingestion endpoint. Default: https://in.logs.betterstack.com */\n endpoint?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n}\n\nconst BETTER_STACK_FIELDS: ConfigField<BetterStackConfig>[] = [\n { key: 'sourceToken', env: ['NUXT_BETTER_STACK_SOURCE_TOKEN', 'BETTER_STACK_SOURCE_TOKEN'] },\n { key: 'endpoint', env: ['NUXT_BETTER_STACK_ENDPOINT', 'BETTER_STACK_ENDPOINT'] },\n { key: 'timeout' },\n]\n\n/**\n * Transform an evlog wide event into a Better Stack event.\n * Maps `timestamp` to `dt` (Better Stack's expected field).\n */\nexport function toBetterStackEvent(event: WideEvent): Record<string, unknown> {\n const { timestamp, ...rest } = event\n return { ...rest, dt: timestamp }\n}\n\n/**\n * Create a drain function for sending logs to Better Stack.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createBetterStackDrain()\n * 2. runtimeConfig.evlog.betterStack\n * 3. runtimeConfig.betterStack\n * 4. Environment variables: NUXT_BETTER_STACK_*, BETTER_STACK_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_BETTER_STACK_SOURCE_TOKEN env var\n * nitroApp.hooks.hook('evlog:drain', createBetterStackDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createBetterStackDrain({\n * sourceToken: 'my-token',\n * }))\n * ```\n */\nexport function createBetterStackDrain(overrides?: Partial<BetterStackConfig>) {\n return defineDrain<BetterStackConfig>({\n name: 'better-stack',\n resolve: () => {\n const config = resolveAdapterConfig<BetterStackConfig>('betterStack', BETTER_STACK_FIELDS, overrides)\n if (!config.sourceToken) {\n console.error('[evlog/better-stack] Missing source token. Set NUXT_BETTER_STACK_SOURCE_TOKEN env var or pass to createBetterStackDrain()')\n return null\n }\n return config as BetterStackConfig\n },\n send: sendBatchToBetterStack,\n })\n}\n\n/**\n * Send a single event to Better Stack.\n *\n * @example\n * ```ts\n * await sendToBetterStack(event, {\n * sourceToken: process.env.BETTER_STACK_SOURCE_TOKEN!,\n * })\n * ```\n */\nexport async function sendToBetterStack(event: WideEvent, config: BetterStackConfig): Promise<void> {\n await sendBatchToBetterStack([event], config)\n}\n\n/**\n * Send a batch of events to Better Stack.\n *\n * @example\n * ```ts\n * await sendBatchToBetterStack(events, {\n * sourceToken: process.env.BETTER_STACK_SOURCE_TOKEN!,\n * })\n * ```\n */\nexport async function sendBatchToBetterStack(events: WideEvent[], config: BetterStackConfig): Promise<void> {\n const endpoint = (config.endpoint ?? 'https://in.logs.betterstack.com').replace(/\\/+$/, '')\n\n await httpPost({\n url: endpoint,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.sourceToken}`,\n },\n body: JSON.stringify(events.map(toBetterStackEvent)),\n timeout: config.timeout ?? 5000,\n label: 'Better Stack',\n })\n}\n"],"mappings":";;;AAeA,MAAM,sBAAwD;CAC5D;EAAE,KAAK;EAAe,KAAK,CAAC,kCAAkC,4BAA4B;EAAE;CAC5F;EAAE,KAAK;EAAY,KAAK,CAAC,8BAA8B,wBAAwB;EAAE;CACjF,EAAE,KAAK,WAAW;CACnB;;;;;AAMD,SAAgB,mBAAmB,OAA2C;CAC5E,MAAM,EAAE,WAAW,GAAG,SAAS;AAC/B,QAAO;EAAE,GAAG;EAAM,IAAI;EAAW;;;;;;;;;;;;;;;;;;;;;;AAuBnC,SAAgB,uBAAuB,WAAwC;AAC7E,QAAO,YAA+B;EACpC,MAAM;EACN,eAAe;GACb,MAAM,SAAS,qBAAwC,eAAe,qBAAqB,UAAU;AACrG,OAAI,CAAC,OAAO,aAAa;AACvB,YAAQ,MAAM,4HAA4H;AAC1I,WAAO;;AAET,UAAO;;EAET,MAAM;EACP,CAAC;;;;;;;;;;;;AAaJ,eAAsB,kBAAkB,OAAkB,QAA0C;AAClG,OAAM,uBAAuB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;AAa/C,eAAsB,uBAAuB,QAAqB,QAA0C;AAG1G,OAAM,SAAS;EACb,MAHgB,OAAO,YAAY,mCAAmC,QAAQ,QAAQ,GAAG;EAIzF,SAAS;GACP,gBAAgB;GAChB,iBAAiB,UAAU,OAAO;GACnC;EACD,MAAM,KAAK,UAAU,OAAO,IAAI,mBAAmB,CAAC;EACpD,SAAS,OAAO,WAAW;EAC3B,OAAO;EACR,CAAC"}