evlog 2.15.0 → 2.17.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 (169) hide show
  1. package/README.md +7 -7
  2. package/dist/adapters/axiom.d.mts +1 -1
  3. package/dist/adapters/axiom.mjs +4 -2
  4. package/dist/adapters/axiom.mjs.map +1 -1
  5. package/dist/adapters/better-stack.d.mts +1 -1
  6. package/dist/adapters/better-stack.mjs +4 -2
  7. package/dist/adapters/better-stack.mjs.map +1 -1
  8. package/dist/adapters/datadog.d.mts +1 -1
  9. package/dist/adapters/datadog.mjs +4 -2
  10. package/dist/adapters/datadog.mjs.map +1 -1
  11. package/dist/adapters/fs.d.mts +64 -2
  12. package/dist/adapters/fs.d.mts.map +1 -1
  13. package/dist/adapters/fs.mjs +222 -3
  14. package/dist/adapters/fs.mjs.map +1 -1
  15. package/dist/adapters/hyperdx.d.mts +1 -1
  16. package/dist/adapters/hyperdx.mjs +1 -1
  17. package/dist/adapters/otlp.d.mts +1 -1
  18. package/dist/adapters/otlp.mjs +6 -4
  19. package/dist/adapters/otlp.mjs.map +1 -1
  20. package/dist/adapters/posthog.d.mts +1 -1
  21. package/dist/adapters/posthog.mjs +4 -2
  22. package/dist/adapters/posthog.mjs.map +1 -1
  23. package/dist/adapters/sentry.d.mts +1 -1
  24. package/dist/adapters/sentry.mjs +5 -3
  25. package/dist/adapters/sentry.mjs.map +1 -1
  26. package/dist/ai/index.d.mts +15 -1
  27. package/dist/ai/index.d.mts.map +1 -1
  28. package/dist/ai/index.mjs +48 -16
  29. package/dist/ai/index.mjs.map +1 -1
  30. package/dist/{audit-CJl-wZ10.d.mts → audit-CC8nfazi.d.mts} +65 -3
  31. package/dist/{audit-CJl-wZ10.d.mts.map → audit-CC8nfazi.d.mts.map} +1 -1
  32. package/dist/{audit--n0QRR2Y.mjs → audit-pV5aLGP0.mjs} +2 -1
  33. package/dist/audit-pV5aLGP0.mjs.map +1 -0
  34. package/dist/better-auth/index.d.mts +14 -7
  35. package/dist/better-auth/index.d.mts.map +1 -1
  36. package/dist/better-auth/index.mjs +11 -1
  37. package/dist/better-auth/index.mjs.map +1 -1
  38. package/dist/browser.d.mts +1 -1
  39. package/dist/{define-Fp8TrdEB.d.mts → define-MSdhzmXn.d.mts} +3 -3
  40. package/dist/{define-Fp8TrdEB.d.mts.map → define-MSdhzmXn.d.mts.map} +1 -1
  41. package/dist/{dist-BIlS38vi.mjs → dist-H3GIh-KK.mjs} +1 -1
  42. package/dist/{dist-BIlS38vi.mjs.map → dist-H3GIh-KK.mjs.map} +1 -1
  43. package/dist/{drain-ByWUeOQC.mjs → drain-X7_5szSI.mjs} +6 -49
  44. package/dist/drain-X7_5szSI.mjs.map +1 -0
  45. package/dist/elysia/index.d.mts +2 -2
  46. package/dist/elysia/index.mjs +2 -2
  47. package/dist/{enricher-CLSnrzrr.d.mts → enricher-DxgML6IC.d.mts} +4 -4
  48. package/dist/{enricher-CLSnrzrr.d.mts.map → enricher-DxgML6IC.d.mts.map} +1 -1
  49. package/dist/{enricher-BA6viylF.mjs → enricher-N0erZS87.mjs} +2 -2
  50. package/dist/{enricher-BA6viylF.mjs.map → enricher-N0erZS87.mjs.map} +1 -1
  51. package/dist/enrichers.d.mts +2 -2
  52. package/dist/enrichers.mjs +1 -1
  53. package/dist/{error-C-66_G2M.d.mts → error-CpbbtyXL.d.mts} +7 -2
  54. package/dist/error-CpbbtyXL.d.mts.map +1 -0
  55. package/dist/error.d.mts +1 -1
  56. package/dist/error.mjs +8 -1
  57. package/dist/error.mjs.map +1 -1
  58. package/dist/{errors-DQoYsDW1.d.mts → errors-DySW1F9_.d.mts} +2 -2
  59. package/dist/{errors-DQoYsDW1.d.mts.map → errors-DySW1F9_.d.mts.map} +1 -1
  60. package/dist/{event-ef-5Dbxg.mjs → event-1BMl7o0k.mjs} +4 -2
  61. package/dist/event-1BMl7o0k.mjs.map +1 -0
  62. package/dist/express/index.d.mts +2 -2
  63. package/dist/express/index.d.mts.map +1 -1
  64. package/dist/express/index.mjs +5 -6
  65. package/dist/express/index.mjs.map +1 -1
  66. package/dist/fastify/index.d.mts +2 -2
  67. package/dist/fastify/index.mjs +2 -2
  68. package/dist/{fork-D44V93-K.mjs → fork-8u_zFOJq.mjs} +3 -3
  69. package/dist/{fork-D44V93-K.mjs.map → fork-8u_zFOJq.mjs.map} +1 -1
  70. package/dist/hono/index.d.mts +2 -2
  71. package/dist/hono/index.mjs +1 -1
  72. package/dist/http-6umVAKDW.mjs +82 -0
  73. package/dist/http-6umVAKDW.mjs.map +1 -0
  74. package/dist/http.d.mts +1 -1
  75. package/dist/http.mjs +1 -0
  76. package/dist/http.mjs.map +1 -1
  77. package/dist/index-o1_z4phv.d.mts +213 -0
  78. package/dist/index-o1_z4phv.d.mts.map +1 -0
  79. package/dist/index.d.mts +9 -8
  80. package/dist/index.mjs +210 -2
  81. package/dist/index.mjs.map +1 -0
  82. package/dist/{integration-Bz8X6_Lb.mjs → integration-DTZtjSqh.mjs} +2 -2
  83. package/dist/{integration-Bz8X6_Lb.mjs.map → integration-DTZtjSqh.mjs.map} +1 -1
  84. package/dist/{logger-Brt5-WMK.d.mts → logger-DntcxxHg.d.mts} +2 -2
  85. package/dist/{logger-Brt5-WMK.d.mts.map → logger-DntcxxHg.d.mts.map} +1 -1
  86. package/dist/logger.d.mts +1 -1
  87. package/dist/logger.mjs +1 -1
  88. package/dist/{middleware-CGM-bOvE.d.mts → middleware-U-lIAzHg.d.mts} +2 -2
  89. package/dist/{middleware-CGM-bOvE.d.mts.map → middleware-U-lIAzHg.d.mts.map} +1 -1
  90. package/dist/nestjs/index.d.mts +2 -2
  91. package/dist/nestjs/index.d.mts.map +1 -1
  92. package/dist/nestjs/index.mjs +4 -5
  93. package/dist/nestjs/index.mjs.map +1 -1
  94. package/dist/next/client.d.mts +1 -1
  95. package/dist/next/index.d.mts +4 -4
  96. package/dist/next/index.mjs +3 -3
  97. package/dist/next/instrumentation.d.mts +1 -1
  98. package/dist/next/instrumentation.mjs +1 -1
  99. package/dist/next/stream.d.mts +29 -0
  100. package/dist/next/stream.d.mts.map +1 -0
  101. package/dist/next/stream.mjs +78 -0
  102. package/dist/next/stream.mjs.map +1 -0
  103. package/dist/nitro/errorHandler.mjs +1 -1
  104. package/dist/nitro/module.d.mts +2 -2
  105. package/dist/nitro/plugin.mjs +12 -3
  106. package/dist/nitro/plugin.mjs.map +1 -1
  107. package/dist/nitro/v3/errorHandler.mjs +2 -2
  108. package/dist/nitro/v3/index.d.mts +2 -2
  109. package/dist/nitro/v3/module.d.mts +1 -1
  110. package/dist/nitro/v3/plugin.mjs +4 -4
  111. package/dist/nitro/v3/useLogger.d.mts +1 -1
  112. package/dist/{nitro-DavLelNz.mjs → nitro-DErMq_Zj.mjs} +1 -1
  113. package/dist/{nitro-DavLelNz.mjs.map → nitro-DErMq_Zj.mjs.map} +1 -1
  114. package/dist/{nitro-DHPb9dXG.d.mts → nitro-oZre8ab3.d.mts} +2 -2
  115. package/dist/{nitro-DHPb9dXG.d.mts.map → nitro-oZre8ab3.d.mts.map} +1 -1
  116. package/dist/{nitroConfigBridge-aZ1e5upQ.mjs → nitroConfigBridge-DKk7eOn-.mjs} +1 -1
  117. package/dist/{nitroConfigBridge-aZ1e5upQ.mjs.map → nitroConfigBridge-DKk7eOn-.mjs.map} +1 -1
  118. package/dist/nodeResponse-BkkionWl.mjs +42 -0
  119. package/dist/nodeResponse-BkkionWl.mjs.map +1 -0
  120. package/dist/nuxt/module.d.mts +28 -1
  121. package/dist/nuxt/module.d.mts.map +1 -1
  122. package/dist/nuxt/module.mjs +11 -4
  123. package/dist/nuxt/module.mjs.map +1 -1
  124. package/dist/package-v_MmOZeA.mjs +7 -0
  125. package/dist/package-v_MmOZeA.mjs.map +1 -0
  126. package/dist/{parseError-B1zJZvQ5.d.mts → parseError-yVZ58wIK.d.mts} +2 -2
  127. package/dist/parseError-yVZ58wIK.d.mts.map +1 -0
  128. package/dist/react-router/index.d.mts +2 -2
  129. package/dist/react-router/index.mjs +2 -2
  130. package/dist/{routes-B48wm7Pb.mjs → routes-CnIgYWf8.mjs} +1 -1
  131. package/dist/{routes-B48wm7Pb.mjs.map → routes-CnIgYWf8.mjs.map} +1 -1
  132. package/dist/runtime/client/log.d.mts +1 -1
  133. package/dist/runtime/server/routes/_evlog/ingest.post.mjs +1 -1
  134. package/dist/runtime/server/routes/_evlog/stream-info.get.d.mts +18 -0
  135. package/dist/runtime/server/routes/_evlog/stream-info.get.d.mts.map +1 -0
  136. package/dist/runtime/server/routes/_evlog/stream-info.get.mjs +28 -0
  137. package/dist/runtime/server/routes/_evlog/stream-info.get.mjs.map +1 -0
  138. package/dist/runtime/server/useLogger.d.mts +1 -1
  139. package/dist/runtime/utils/parseError.d.mts +2 -2
  140. package/dist/runtime/utils/parseError.mjs +8 -0
  141. package/dist/runtime/utils/parseError.mjs.map +1 -1
  142. package/dist/{severity-BYWZ96Sb.mjs → severity-R5Egq3qz.mjs} +1 -1
  143. package/dist/{severity-BYWZ96Sb.mjs.map → severity-R5Egq3qz.mjs.map} +1 -1
  144. package/dist/{storage-BT-3fT1-.mjs → storage-Dwinmg8P.mjs} +1 -1
  145. package/dist/{storage-BT-3fT1-.mjs.map → storage-Dwinmg8P.mjs.map} +1 -1
  146. package/dist/stream.d.mts +185 -0
  147. package/dist/stream.d.mts.map +1 -0
  148. package/dist/stream.mjs +374 -0
  149. package/dist/stream.mjs.map +1 -0
  150. package/dist/sveltekit/index.d.mts +2 -2
  151. package/dist/sveltekit/index.mjs +3 -3
  152. package/dist/toolkit.d.mts +43 -8
  153. package/dist/toolkit.d.mts.map +1 -1
  154. package/dist/toolkit.mjs +11 -10
  155. package/dist/types.d.mts +2 -2
  156. package/dist/{useLogger-Cb1R6bQE.d.mts → useLogger-BsPL4AQm.d.mts} +2 -2
  157. package/dist/{useLogger-Cb1R6bQE.d.mts.map → useLogger-BsPL4AQm.d.mts.map} +1 -1
  158. package/dist/{utils-gQCeZMbg.d.mts → utils-DLCeShxL.d.mts} +2 -2
  159. package/dist/{utils-gQCeZMbg.d.mts.map → utils-DLCeShxL.d.mts.map} +1 -1
  160. package/dist/utils.d.mts +1 -1
  161. package/dist/vite/index.d.mts +1 -1
  162. package/dist/workers.d.mts +1 -1
  163. package/dist/workers.mjs +1 -1
  164. package/package.json +17 -1
  165. package/dist/audit--n0QRR2Y.mjs.map +0 -1
  166. package/dist/drain-ByWUeOQC.mjs.map +0 -1
  167. package/dist/error-C-66_G2M.d.mts.map +0 -1
  168. package/dist/event-ef-5Dbxg.mjs.map +0 -1
  169. package/dist/parseError-B1zJZvQ5.d.mts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"fs.mjs","names":[],"sources":["../../src/adapters/fs.ts"],"sourcesContent":["import { appendFile, mkdir, readdir, stat, unlink, writeFile } from 'node:fs/promises'\nimport { join, sep } from 'node:path'\nimport type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineDrain } from '../shared/drain'\n\nexport interface FsConfig {\n /** Directory for log files. Default: `.evlog/logs` */\n dir: string\n /** Max number of log files to keep (auto-deletes oldest when exceeded) */\n maxFiles?: number\n /** Max bytes per file before rotating to a new suffixed file */\n maxSizePerFile?: number\n /** Pretty-print JSON instead of compact NDJSON */\n pretty: boolean\n}\n\nconst FS_FIELDS: ConfigField<FsConfig>[] = [\n { key: 'dir', env: ['NUXT_EVLOG_FS_DIR', 'EVLOG_FS_DIR'] },\n { key: 'maxFiles' },\n { key: 'maxSizePerFile' },\n { key: 'pretty' },\n]\n\nconst gitignoreWritten = new Set<string>()\n\nasync function ensureGitignore(dir: string): Promise<void> {\n const normalized = dir.replace(/[\\\\/]/g, sep)\n const segments = normalized.split(sep)\n const evlogIndex = segments.findIndex(s => s === '.evlog')\n const targetDir = evlogIndex !== -1 ? segments.slice(0, evlogIndex + 1).join(sep) : dir\n\n if (gitignoreWritten.has(targetDir)) return\n\n const gitignorePath = join(targetDir, '.gitignore')\n try {\n await stat(gitignorePath)\n } catch {\n await writeFile(gitignorePath, '*\\n', 'utf-8')\n }\n gitignoreWritten.add(targetDir)\n}\n\nfunction getDateString(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nasync function resolveFilePath(dir: string, maxSizePerFile?: number): Promise<string> {\n const date = getDateString()\n const basePath = join(dir, `${date}.jsonl`)\n\n if (!maxSizePerFile) return basePath\n\n try {\n const stats = await stat(basePath)\n if (stats.size < maxSizePerFile) return basePath\n } catch {\n return basePath\n }\n\n for (let i = 1; i < 1000; i++) {\n const rotatedPath = join(dir, `${date}.${i}.jsonl`)\n try {\n const stats = await stat(rotatedPath)\n if (stats.size < maxSizePerFile) return rotatedPath\n } catch {\n return rotatedPath\n }\n }\n\n return join(dir, `${date}.999.jsonl`)\n}\n\nfunction parseLogFilename(filename: string): { date: string; index: number } {\n const match = filename.match(/^(\\d{4}-\\d{2}-\\d{2})(?:\\.(\\d+))?\\.jsonl$/)\n if (!match) return { date: '', index: 0 }\n return { date: match[1], index: match[2] ? Number.parseInt(match[2], 10) : 0 }\n}\n\nasync function cleanupOldFiles(dir: string, maxFiles: number): Promise<void> {\n const files = await readdir(dir)\n const jsonlFiles = files.filter(f => f.endsWith('.jsonl')).sort((a, b) => {\n const pa = parseLogFilename(a)\n const pb = parseLogFilename(b)\n return pa.date.localeCompare(pb.date) || pa.index - pb.index\n })\n\n if (jsonlFiles.length <= maxFiles) return\n\n const toDelete = jsonlFiles.slice(0, jsonlFiles.length - maxFiles)\n await Promise.allSettled(toDelete.map(f => unlink(join(dir, f))))\n}\n\nexport async function writeToFs(event: WideEvent, config: FsConfig): Promise<void> {\n await writeBatchToFs([event], config)\n}\n\nexport async function writeBatchToFs(events: WideEvent[], config: FsConfig): Promise<void> {\n if (events.length === 0) return\n\n await mkdir(config.dir, { recursive: true })\n await ensureGitignore(config.dir)\n\n const filePath = await resolveFilePath(config.dir, config.maxSizePerFile)\n const lines = `${events\n .map(e => config.pretty ? JSON.stringify(e, null, 2) : JSON.stringify(e))\n .join('\\n') }\\n`\n\n await appendFile(filePath, lines, 'utf-8')\n\n if (config.maxFiles) {\n await cleanupOldFiles(config.dir, config.maxFiles)\n }\n}\n\n/**\n * Create a drain function that writes logs to the local file system as NDJSON.\n *\n * Files are organized by date (`2026-03-14.jsonl`) with optional size-based\n * rotation and automatic cleanup of old files.\n *\n * @example\n * ```ts\n * // Default: writes to .evlog/logs/\n * nitroApp.hooks.hook('evlog:drain', createFsDrain())\n *\n * // With options\n * nitroApp.hooks.hook('evlog:drain', createFsDrain({\n * dir: '.evlog/logs',\n * maxFiles: 7,\n * pretty: true,\n * }))\n * ```\n */\nexport function createFsDrain(overrides?: Partial<FsConfig>) {\n return defineDrain<FsConfig>({\n name: 'fs',\n resolve: async () => {\n const resolved = await resolveAdapterConfig<FsConfig>('fs', FS_FIELDS, overrides)\n return {\n dir: resolved.dir ?? '.evlog/logs',\n pretty: resolved.pretty ?? false,\n maxFiles: resolved.maxFiles,\n maxSizePerFile: resolved.maxSizePerFile,\n }\n },\n send: writeBatchToFs,\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"fs.mjs","names":[],"sources":["../../src/adapters/fs.ts"],"sourcesContent":["import { createReadStream } from 'node:fs'\nimport { appendFile, mkdir, open, readdir, stat, unlink, writeFile } from 'node:fs/promises'\nimport { join, sep } from 'node:path'\nimport { createInterface } from 'node:readline'\nimport type { LogLevel, WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineDrain } from '../shared/drain'\n\nexport interface FsConfig {\n /** Directory for log files. Default: `.evlog/logs` */\n dir: string\n /** Max number of log files to keep (auto-deletes oldest when exceeded) */\n maxFiles?: number\n /** Max bytes per file before rotating to a new suffixed file */\n maxSizePerFile?: number\n /** Pretty-print JSON instead of compact NDJSON */\n pretty: boolean\n}\n\nconst FS_FIELDS: ConfigField<FsConfig>[] = [\n { key: 'dir', env: ['NUXT_EVLOG_FS_DIR', 'EVLOG_FS_DIR'] },\n { key: 'maxFiles' },\n { key: 'maxSizePerFile' },\n { key: 'pretty' },\n]\n\nconst gitignoreWritten = new Set<string>()\n\nasync function ensureGitignore(dir: string): Promise<void> {\n const normalized = dir.replace(/[\\\\/]/g, sep)\n const segments = normalized.split(sep)\n const evlogIndex = segments.findIndex(s => s === '.evlog')\n const targetDir = evlogIndex !== -1 ? segments.slice(0, evlogIndex + 1).join(sep) : dir\n\n if (gitignoreWritten.has(targetDir)) return\n\n const gitignorePath = join(targetDir, '.gitignore')\n try {\n await stat(gitignorePath)\n } catch {\n await writeFile(gitignorePath, '*\\n', 'utf-8')\n }\n gitignoreWritten.add(targetDir)\n}\n\nfunction getDateString(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nasync function resolveFilePath(dir: string, maxSizePerFile?: number): Promise<string> {\n const date = getDateString()\n const basePath = join(dir, `${date}.jsonl`)\n\n if (!maxSizePerFile) return basePath\n\n try {\n const stats = await stat(basePath)\n if (stats.size < maxSizePerFile) return basePath\n } catch {\n return basePath\n }\n\n for (let i = 1; i < 1000; i++) {\n const rotatedPath = join(dir, `${date}.${i}.jsonl`)\n try {\n const stats = await stat(rotatedPath)\n if (stats.size < maxSizePerFile) return rotatedPath\n } catch {\n return rotatedPath\n }\n }\n\n return join(dir, `${date}.999.jsonl`)\n}\n\nfunction parseLogFilename(filename: string): { date: string; index: number } {\n const match = filename.match(/^(\\d{4}-\\d{2}-\\d{2})(?:\\.(\\d+))?\\.jsonl$/)\n if (!match) return { date: '', index: 0 }\n return { date: match[1], index: match[2] ? Number.parseInt(match[2], 10) : 0 }\n}\n\nasync function cleanupOldFiles(dir: string, maxFiles: number): Promise<void> {\n const files = await readdir(dir)\n const jsonlFiles = files.filter(f => f.endsWith('.jsonl')).sort((a, b) => {\n const pa = parseLogFilename(a)\n const pb = parseLogFilename(b)\n return pa.date.localeCompare(pb.date) || pa.index - pb.index\n })\n\n if (jsonlFiles.length <= maxFiles) return\n\n const toDelete = jsonlFiles.slice(0, jsonlFiles.length - maxFiles)\n await Promise.allSettled(toDelete.map(f => unlink(join(dir, f))))\n}\n\nexport async function writeToFs(event: WideEvent, config: FsConfig): Promise<void> {\n await writeBatchToFs([event], config)\n}\n\nexport async function writeBatchToFs(events: WideEvent[], config: FsConfig): Promise<void> {\n if (events.length === 0) return\n\n await mkdir(config.dir, { recursive: true })\n await ensureGitignore(config.dir)\n\n const filePath = await resolveFilePath(config.dir, config.maxSizePerFile)\n const lines = `${events\n .map(e => config.pretty ? JSON.stringify(e, null, 2) : JSON.stringify(e))\n .join('\\n') }\\n`\n\n await appendFile(filePath, lines, 'utf-8')\n\n if (config.maxFiles) {\n await cleanupOldFiles(config.dir, config.maxFiles)\n }\n}\n\n/**\n * Create a drain function that writes logs to the local file system as NDJSON.\n *\n * Files are organized by date (`2026-03-14.jsonl`) with optional size-based\n * rotation and automatic cleanup of old files.\n *\n * @example\n * ```ts\n * // Default: writes to .evlog/logs/\n * nitroApp.hooks.hook('evlog:drain', createFsDrain())\n *\n * // With options\n * nitroApp.hooks.hook('evlog:drain', createFsDrain({\n * dir: '.evlog/logs',\n * maxFiles: 7,\n * pretty: true,\n * }))\n * ```\n */\nexport function createFsDrain(overrides?: Partial<FsConfig>) {\n return defineDrain<FsConfig>({\n name: 'fs',\n resolve: async () => {\n const resolved = await resolveAdapterConfig<FsConfig>('fs', FS_FIELDS, overrides)\n return {\n dir: resolved.dir ?? '.evlog/logs',\n pretty: resolved.pretty ?? false,\n maxFiles: resolved.maxFiles,\n maxSizePerFile: resolved.maxSizePerFile,\n }\n },\n send: writeBatchToFs,\n })\n}\n\n/** Options accepted by {@link readFsLogs}. */\nexport interface ReadFsLogsOptions {\n /** Directory to read from. Default: `.evlog/logs` */\n dir?: string\n /** Only yield events with `event.timestamp >= since`. */\n since?: Date | string\n /** Only yield events with `event.timestamp <= until`. */\n until?: Date | string\n /** Filter by event level. */\n level?: LogLevel | LogLevel[]\n /** Custom predicate — return `false` to skip the event. */\n filter?: (event: WideEvent) => boolean\n}\n\n/** Options accepted by {@link tailFsLogs}. */\nexport interface TailFsLogsOptions extends ReadFsLogsOptions {\n /**\n * Polling interval (ms) used to detect new bytes / new files.\n * @default 500\n */\n pollIntervalMs?: number\n /**\n * Skip existing events and only yield events appended after the tailer\n * starts. Useful for \"live tail\" UX.\n * @default false\n */\n fromEnd?: boolean\n /** Stop tailing when this signal aborts. */\n signal?: AbortSignal\n}\n\ninterface ParsedFilename {\n date: string\n index: number\n}\n\nfunction isLogFilename(filename: string): boolean {\n return /^\\d{4}-\\d{2}-\\d{2}(\\.\\d+)?\\.jsonl$/.test(filename)\n}\n\nfunction compareLogFiles(a: string, b: string): number {\n const pa = parseLogFilename(a)\n const pb = parseLogFilename(b)\n return pa.date.localeCompare(pb.date) || pa.index - pb.index\n}\n\nfunction normalizeTimestamp(value: Date | string | undefined): number | undefined {\n if (!value) return undefined\n const date = value instanceof Date ? value : new Date(value)\n const ts = date.getTime()\n return Number.isNaN(ts) ? undefined : ts\n}\n\nfunction buildFilter(options: ReadFsLogsOptions): (event: WideEvent) => boolean {\n const since = normalizeTimestamp(options.since)\n const until = normalizeTimestamp(options.until)\n const levels = options.level\n ? new Set<LogLevel>(Array.isArray(options.level) ? options.level : [options.level])\n : undefined\n const custom = options.filter\n\n return (event: WideEvent) => {\n if (levels && !levels.has(event.level)) return false\n if (since !== undefined || until !== undefined) {\n const ts = typeof event.timestamp === 'string' ? Date.parse(event.timestamp) : Number.NaN\n if (Number.isNaN(ts)) return false\n if (since !== undefined && ts < since) return false\n if (until !== undefined && ts > until) return false\n }\n if (custom && !custom(event)) return false\n return true\n }\n}\n\nasync function listLogFiles(dir: string): Promise<string[]> {\n let files: string[]\n try {\n files = await readdir(dir)\n } catch {\n return []\n }\n return files.filter(isLogFilename).sort(compareLogFiles)\n}\n\nfunction fileDateMs(filename: string): number {\n const { date } = parseLogFilename(filename)\n return date ? Date.parse(`${date}T00:00:00.000Z`) : Number.NaN\n}\n\nfunction fileWithinRange(filename: string, since?: number, until?: number): boolean {\n if (since === undefined && until === undefined) return true\n const dayStart = fileDateMs(filename)\n if (Number.isNaN(dayStart)) return true\n const dayEnd = dayStart + 24 * 60 * 60 * 1000 - 1\n if (since !== undefined && dayEnd < since) return false\n if (until !== undefined && dayStart > until) return false\n return true\n}\n\nasync function* iterateFile(filePath: string): AsyncGenerator<WideEvent> {\n const stream = createReadStream(filePath, { encoding: 'utf-8' })\n const rl = createInterface({ input: stream, crlfDelay: Infinity })\n try {\n for await (const line of rl) {\n const trimmed = line.trim()\n if (!trimmed) continue\n try {\n yield JSON.parse(trimmed) as WideEvent\n } catch {\n // Skip malformed lines (partial writes, manual edits) silently.\n }\n }\n } finally {\n rl.close()\n stream.destroy()\n }\n}\n\n/**\n * Read past events from the local file system drain (NDJSON). Files are\n * iterated in chronological order; events are yielded as they appear in\n * each file.\n *\n * @example\n * ```ts\n * import { readFsLogs } from 'evlog/fs'\n *\n * for await (const event of readFsLogs({ since: '2026-01-01', level: 'error' })) {\n * console.log(event)\n * }\n * ```\n */\nexport async function* readFsLogs(options: ReadFsLogsOptions = {}): AsyncGenerator<WideEvent> {\n const dir = options.dir ?? '.evlog/logs'\n const sinceMs = normalizeTimestamp(options.since)\n const untilMs = normalizeTimestamp(options.until)\n const predicate = buildFilter(options)\n\n const files = await listLogFiles(dir)\n for (const filename of files) {\n if (!fileWithinRange(filename, sinceMs, untilMs)) continue\n for await (const event of iterateFile(join(dir, filename))) {\n if (predicate(event)) yield event\n }\n }\n}\n\nasync function safeStatSize(filePath: string): Promise<number> {\n try {\n const s = await stat(filePath)\n return s.size\n } catch {\n return 0\n }\n}\n\nasync function readAppendedLines(\n filePath: string,\n fromOffset: number,\n carry: string,\n): Promise<{ events: string[]; offset: number; carry: string }> {\n const size = await safeStatSize(filePath)\n if (size <= fromOffset) return { events: [], offset: fromOffset, carry }\n\n const handle = await open(filePath, 'r')\n try {\n const length = size - fromOffset\n const buf = Buffer.alloc(length)\n await handle.read(buf, 0, length, fromOffset)\n const chunk = carry + buf.toString('utf-8')\n const newlineIdx = chunk.lastIndexOf('\\n')\n if (newlineIdx === -1) {\n return { events: [], offset: size, carry: chunk }\n }\n const complete = chunk.slice(0, newlineIdx)\n const remainder = chunk.slice(newlineIdx + 1)\n const lines = complete.split('\\n').map(l => l.trim()).filter(Boolean)\n return { events: lines, offset: size, carry: remainder }\n } finally {\n await handle.close()\n }\n}\n\nfunction delay(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n if (signal) signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n const onAbort = () => {\n clearTimeout(timer)\n resolve()\n }\n if (signal) {\n if (signal.aborted) {\n clearTimeout(timer)\n resolve()\n return\n }\n signal.addEventListener('abort', onAbort, { once: true })\n }\n })\n}\n\n/**\n * Follow the local file system drain in real time. Yields existing events\n * (unless `fromEnd: true`) then keeps yielding new events as they are\n * appended. Automatically picks up newly created daily files.\n *\n * @example\n * ```ts\n * import { tailFsLogs } from 'evlog/fs'\n *\n * const ac = new AbortController()\n * setTimeout(() => ac.abort(), 60_000)\n *\n * for await (const event of tailFsLogs({ signal: ac.signal })) {\n * console.log('live:', event.action ?? event.message)\n * }\n * ```\n */\nexport async function* tailFsLogs(options: TailFsLogsOptions = {}): AsyncGenerator<WideEvent> {\n const dir = options.dir ?? '.evlog/logs'\n const interval = Math.max(50, options.pollIntervalMs ?? 500)\n const { signal } = options\n const predicate = buildFilter(options)\n\n const offsets = new Map<string, number>()\n const carries = new Map<string, string>()\n\n if (options.fromEnd) {\n const files = await listLogFiles(dir)\n for (const filename of files) {\n offsets.set(filename, await safeStatSize(join(dir, filename)))\n carries.set(filename, '')\n }\n } else {\n for await (const event of readFsLogs(options)) {\n if (signal?.aborted) return\n yield event\n }\n const files = await listLogFiles(dir)\n for (const filename of files) {\n if (!offsets.has(filename)) {\n offsets.set(filename, await safeStatSize(join(dir, filename)))\n carries.set(filename, '')\n }\n }\n }\n\n while (true) {\n if (signal?.aborted) return\n await delay(interval, signal)\n if (signal?.aborted) return\n\n const files = await listLogFiles(dir)\n\n for (const filename of files) {\n if (!offsets.has(filename)) {\n offsets.set(filename, 0)\n carries.set(filename, '')\n }\n const filePath = join(dir, filename)\n const fromOffset = offsets.get(filename)!\n const carry = carries.get(filename) ?? ''\n const { events, offset, carry: newCarry } = await readAppendedLines(filePath, fromOffset, carry)\n offsets.set(filename, offset)\n carries.set(filename, newCarry)\n\n for (const line of events) {\n if (signal?.aborted) return\n try {\n const event = JSON.parse(line) as WideEvent\n if (predicate(event)) yield event\n } catch {\n // Skip malformed lines.\n }\n }\n }\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { F as DrainContext, it as WideEvent } from "../audit-CJl-wZ10.mjs";
1
+ import { I as DrainContext, ct as WideEvent } from "../audit-CC8nfazi.mjs";
2
2
  import { OTLPConfig } from "./otlp.mjs";
3
3
 
4
4
  //#region src/adapters/hyperdx.d.ts
@@ -1,4 +1,4 @@
1
- import { a as resolveAdapterConfig, t as defineDrain } from "../drain-ByWUeOQC.mjs";
1
+ import { i as resolveAdapterConfig, t as defineDrain } from "../drain-X7_5szSI.mjs";
2
2
  import { sendBatchToOTLP } from "./otlp.mjs";
3
3
  //#region src/adapters/hyperdx.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { F as DrainContext, it as WideEvent } from "../audit-CJl-wZ10.mjs";
1
+ import { I as DrainContext, ct as WideEvent } from "../audit-CC8nfazi.mjs";
2
2
  //#region src/adapters/otlp.d.ts
3
3
  interface OTLPConfig {
4
4
  /** OTLP HTTP endpoint (e.g., http://localhost:4318) */
@@ -1,6 +1,7 @@
1
- import { a as resolveAdapterConfig, n as defineHttpDrain, r as httpPost } from "../drain-ByWUeOQC.mjs";
2
- import { n as toOtlpAttributeValue } from "../event-ef-5Dbxg.mjs";
3
- import { n as OTEL_SEVERITY_TEXT, t as OTEL_SEVERITY_NUMBER } from "../severity-BYWZ96Sb.mjs";
1
+ import { r as httpPost } from "../http-6umVAKDW.mjs";
2
+ import { i as resolveAdapterConfig, n as defineHttpDrain } from "../drain-X7_5szSI.mjs";
3
+ import { n as toOtlpAttributeValue } from "../event-1BMl7o0k.mjs";
4
+ import { n as OTEL_SEVERITY_TEXT, t as OTEL_SEVERITY_NUMBER } from "../severity-R5Egq3qz.mjs";
4
5
  //#region src/adapters/otlp.ts
5
6
  const OTLP_FIELDS = [
6
7
  {
@@ -198,7 +199,8 @@ async function sendBatchToOTLP(events, config) {
198
199
  body: JSON.stringify(payload),
199
200
  timeout: config.timeout ?? 5e3,
200
201
  retries: config.retries,
201
- label: "OTLP"
202
+ label: "OTLP",
203
+ source: "otlp"
202
204
  });
203
205
  }
204
206
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"otlp.mjs","names":[],"sources":["../../src/adapters/otlp.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineHttpDrain } from '../shared/drain'\nimport { toOtlpAttributeValue } from '../shared/event'\nimport { httpPost } from '../shared/http'\nimport { OTEL_SEVERITY_NUMBER, OTEL_SEVERITY_TEXT } from '../shared/severity'\n\nexport interface OTLPConfig {\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 /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\n/** OTLP Log Record structure */\nexport interface OTLPLogRecord {\n timeUnixNano: string\n severityNumber: number\n severityText: string\n body: { stringValue: string }\n attributes: Array<{\n key: string\n value: { stringValue?: string, intValue?: string, boolValue?: boolean }\n }>\n traceId?: string\n spanId?: string\n}\n\n/** OTLP Resource structure */\ninterface OTLPResource {\n attributes: Array<{\n key: string\n value: { stringValue?: string, intValue?: string, boolValue?: boolean }\n }>\n}\n\n/** OTLP Scope structure */\ninterface OTLPScope {\n name: string\n version?: string\n}\n\n/** OTLP ExportLogsServiceRequest structure */\ninterface ExportLogsServiceRequest {\n resourceLogs: Array<{\n resource: OTLPResource\n scopeLogs: Array<{\n scope: OTLPScope\n logRecords: OTLPLogRecord[]\n }>\n }>\n}\n\nconst OTLP_FIELDS: ConfigField<OTLPConfig>[] = [\n { key: 'endpoint', env: ['NUXT_OTLP_ENDPOINT', 'OTEL_EXPORTER_OTLP_ENDPOINT'] },\n { key: 'serviceName', env: ['NUXT_OTLP_SERVICE_NAME', 'OTEL_SERVICE_NAME'] },\n { key: 'headers' },\n { key: 'resourceAttributes' },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\n// Re-exposed under a local name to keep call-sites tight while delegating to\n// the shared OTLP attribute encoder in `evlog/toolkit`.\nconst toAttributeValue = toOtlpAttributeValue\n\n/**\n * Convert an evlog WideEvent to an OTLP LogRecord.\n */\nexport function toOTLPLogRecord(event: WideEvent): OTLPLogRecord {\n const timestamp = new Date(event.timestamp).getTime() * 1_000_000 // Convert to nanoseconds\n\n // Extract known fields, rest goes to attributes\n const { level, traceId, spanId, ...rest } = event\n // Remove base fields from rest (they're handled as resource attributes)\n delete (rest as Record<string, unknown>).timestamp\n delete (rest as Record<string, unknown>).service\n delete (rest as Record<string, unknown>).environment\n delete (rest as Record<string, unknown>).version\n delete (rest as Record<string, unknown>).commitHash\n delete (rest as Record<string, unknown>).region\n\n const attributes: OTLPLogRecord['attributes'] = []\n\n // Add all remaining event fields as attributes\n for (const [key, value] of Object.entries(rest)) {\n if (value !== undefined && value !== null) {\n attributes.push({\n key,\n value: toAttributeValue(value),\n })\n }\n }\n\n const record: OTLPLogRecord = {\n timeUnixNano: String(timestamp),\n severityNumber: OTEL_SEVERITY_NUMBER[level] ?? 9,\n severityText: OTEL_SEVERITY_TEXT[level] ?? 'INFO',\n body: { stringValue: JSON.stringify(event) },\n attributes,\n }\n\n // Add trace context if present\n if (typeof traceId === 'string') {\n record.traceId = traceId\n }\n if (typeof spanId === 'string') {\n record.spanId = spanId\n }\n\n return record\n}\n\n/**\n * Build OTLP resource attributes from event and config.\n */\nfunction buildResourceAttributes(\n event: WideEvent,\n config: OTLPConfig,\n): OTLPResource['attributes'] {\n const attributes: OTLPResource['attributes'] = []\n\n // Service name\n attributes.push({\n key: 'service.name',\n value: { stringValue: config.serviceName ?? event.service },\n })\n\n // Environment\n if (event.environment) {\n attributes.push({\n key: 'deployment.environment',\n value: { stringValue: event.environment },\n })\n }\n\n // Version\n if (event.version) {\n attributes.push({\n key: 'service.version',\n value: { stringValue: event.version },\n })\n }\n\n // Region\n if (event.region) {\n attributes.push({\n key: 'cloud.region',\n value: { stringValue: event.region },\n })\n }\n\n // Commit hash\n if (event.commitHash) {\n attributes.push({\n key: 'vcs.commit.id',\n value: { stringValue: event.commitHash },\n })\n }\n\n // Custom resource attributes from config\n if (config.resourceAttributes) {\n for (const [key, value] of Object.entries(config.resourceAttributes)) {\n attributes.push({\n key,\n value: toAttributeValue(value),\n })\n }\n }\n\n return attributes\n}\n\n/**\n * Build headers from OTEL env vars.\n * Kept inline as OTLP-specific (parses OTEL_EXPORTER_OTLP_HEADERS=key=val,key=val).\n */\nfunction getHeadersFromEnv(): Record<string, string> | undefined {\n const headersEnv = process.env.OTEL_EXPORTER_OTLP_HEADERS || process.env.NUXT_OTLP_HEADERS\n if (headersEnv) {\n const headers: Record<string, string> = {}\n const decoded = decodeURIComponent(headersEnv)\n for (const pair of decoded.split(',')) {\n const eqIndex = pair.indexOf('=')\n if (eqIndex > 0) {\n const key = pair.slice(0, eqIndex).trim()\n const value = pair.slice(eqIndex + 1).trim()\n if (key && value) {\n headers[key] = value\n }\n }\n }\n if (Object.keys(headers).length > 0) return headers\n }\n\n const auth = process.env.NUXT_OTLP_AUTH\n if (auth) {\n return { Authorization: auth }\n }\n\n return undefined\n}\n\n/**\n * Create a drain function for sending logs to an OTLP endpoint.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createOTLPDrain()\n * 2. runtimeConfig.evlog.otlp (NUXT_EVLOG_OTLP_*)\n * 3. runtimeConfig.otlp (NUXT_OTLP_*)\n * 4. Environment variables: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME\n *\n * @example\n * ```ts\n * // Zero config - reads from runtimeConfig or env vars\n * nitroApp.hooks.hook('evlog:drain', createOTLPDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createOTLPDrain({\n * endpoint: 'http://localhost:4318',\n * }))\n * ```\n */\nexport function createOTLPDrain(overrides?: Partial<OTLPConfig>) {\n return defineHttpDrain<OTLPConfig>({\n name: 'otlp',\n resolve: async () => {\n const config = await resolveAdapterConfig<OTLPConfig>('otlp', OTLP_FIELDS, overrides)\n\n // OTLP-specific: resolve headers from env if not provided via config\n if (!config.headers) {\n config.headers = getHeadersFromEnv()\n }\n\n if (!config.endpoint) {\n console.error('[evlog/otlp] Missing endpoint. Set NUXT_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT env var, or pass to createOTLPDrain()')\n return null\n }\n return config as OTLPConfig\n },\n encode: (events, config) => {\n if (events.length === 0) return null\n return {\n url: `${config.endpoint.replace(/\\/$/, '')}/v1/logs`,\n headers: {\n 'Content-Type': 'application/json',\n ...config.headers,\n },\n body: JSON.stringify(buildOTLPPayload(events, config)),\n }\n },\n })\n}\n\nfunction buildOTLPPayload(events: WideEvent[], config: OTLPConfig): ExportLogsServiceRequest {\n const grouped = new Map<string, WideEvent[]>()\n for (const event of events) {\n const key = `${event.service}::${event.environment}`\n const group = grouped.get(key)\n if (group) group.push(event)\n else grouped.set(key, [event])\n }\n return {\n resourceLogs: Array.from(grouped.values()).map(groupEvents => ({\n resource: { attributes: buildResourceAttributes(groupEvents[0]!, config) },\n scopeLogs: [\n {\n scope: { name: 'evlog', version: '1.0.0' },\n logRecords: groupEvents.map(toOTLPLogRecord),\n },\n ],\n })),\n }\n}\n\n/**\n * Send a single event to an OTLP endpoint.\n *\n * @example\n * ```ts\n * await sendToOTLP(event, {\n * endpoint: 'http://localhost:4318',\n * })\n * ```\n */\nexport async function sendToOTLP(event: WideEvent, config: OTLPConfig): Promise<void> {\n await sendBatchToOTLP([event], config)\n}\n\n/**\n * Send a batch of events to an OTLP endpoint.\n *\n * @example\n * ```ts\n * await sendBatchToOTLP(events, {\n * endpoint: 'http://localhost:4318',\n * })\n * ```\n */\nexport async function sendBatchToOTLP(events: WideEvent[], config: OTLPConfig): Promise<void> {\n if (events.length === 0) return\n\n const url = `${config.endpoint.replace(/\\/$/, '')}/v1/logs`\n const payload = buildOTLPPayload(events, config)\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...config.headers,\n }\n\n await httpPost({\n url,\n headers,\n body: JSON.stringify(payload),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'OTLP',\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"otlp.mjs","names":[],"sources":["../../src/adapters/otlp.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineHttpDrain } from '../shared/drain'\nimport { toOtlpAttributeValue } from '../shared/event'\nimport { httpPost } from '../shared/http'\nimport { OTEL_SEVERITY_NUMBER, OTEL_SEVERITY_TEXT } from '../shared/severity'\n\nexport interface OTLPConfig {\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 /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\n/** OTLP Log Record structure */\nexport interface OTLPLogRecord {\n timeUnixNano: string\n severityNumber: number\n severityText: string\n body: { stringValue: string }\n attributes: Array<{\n key: string\n value: { stringValue?: string, intValue?: string, boolValue?: boolean }\n }>\n traceId?: string\n spanId?: string\n}\n\n/** OTLP Resource structure */\ninterface OTLPResource {\n attributes: Array<{\n key: string\n value: { stringValue?: string, intValue?: string, boolValue?: boolean }\n }>\n}\n\n/** OTLP Scope structure */\ninterface OTLPScope {\n name: string\n version?: string\n}\n\n/** OTLP ExportLogsServiceRequest structure */\ninterface ExportLogsServiceRequest {\n resourceLogs: Array<{\n resource: OTLPResource\n scopeLogs: Array<{\n scope: OTLPScope\n logRecords: OTLPLogRecord[]\n }>\n }>\n}\n\nconst OTLP_FIELDS: ConfigField<OTLPConfig>[] = [\n { key: 'endpoint', env: ['NUXT_OTLP_ENDPOINT', 'OTEL_EXPORTER_OTLP_ENDPOINT'] },\n { key: 'serviceName', env: ['NUXT_OTLP_SERVICE_NAME', 'OTEL_SERVICE_NAME'] },\n { key: 'headers' },\n { key: 'resourceAttributes' },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\n// Re-exposed under a local name to keep call-sites tight while delegating to\n// the shared OTLP attribute encoder in `evlog/toolkit`.\nconst toAttributeValue = toOtlpAttributeValue\n\n/**\n * Convert an evlog WideEvent to an OTLP LogRecord.\n */\nexport function toOTLPLogRecord(event: WideEvent): OTLPLogRecord {\n const timestamp = new Date(event.timestamp).getTime() * 1_000_000 // Convert to nanoseconds\n\n // Extract known fields, rest goes to attributes\n const { level, traceId, spanId, ...rest } = event\n // Remove base fields from rest (they're handled as resource attributes)\n delete (rest as Record<string, unknown>).timestamp\n delete (rest as Record<string, unknown>).service\n delete (rest as Record<string, unknown>).environment\n delete (rest as Record<string, unknown>).version\n delete (rest as Record<string, unknown>).commitHash\n delete (rest as Record<string, unknown>).region\n\n const attributes: OTLPLogRecord['attributes'] = []\n\n // Add all remaining event fields as attributes\n for (const [key, value] of Object.entries(rest)) {\n if (value !== undefined && value !== null) {\n attributes.push({\n key,\n value: toAttributeValue(value),\n })\n }\n }\n\n const record: OTLPLogRecord = {\n timeUnixNano: String(timestamp),\n severityNumber: OTEL_SEVERITY_NUMBER[level] ?? 9,\n severityText: OTEL_SEVERITY_TEXT[level] ?? 'INFO',\n body: { stringValue: JSON.stringify(event) },\n attributes,\n }\n\n // Add trace context if present\n if (typeof traceId === 'string') {\n record.traceId = traceId\n }\n if (typeof spanId === 'string') {\n record.spanId = spanId\n }\n\n return record\n}\n\n/**\n * Build OTLP resource attributes from event and config.\n */\nfunction buildResourceAttributes(\n event: WideEvent,\n config: OTLPConfig,\n): OTLPResource['attributes'] {\n const attributes: OTLPResource['attributes'] = []\n\n // Service name\n attributes.push({\n key: 'service.name',\n value: { stringValue: config.serviceName ?? event.service },\n })\n\n // Environment\n if (event.environment) {\n attributes.push({\n key: 'deployment.environment',\n value: { stringValue: event.environment },\n })\n }\n\n // Version\n if (event.version) {\n attributes.push({\n key: 'service.version',\n value: { stringValue: event.version },\n })\n }\n\n // Region\n if (event.region) {\n attributes.push({\n key: 'cloud.region',\n value: { stringValue: event.region },\n })\n }\n\n // Commit hash\n if (event.commitHash) {\n attributes.push({\n key: 'vcs.commit.id',\n value: { stringValue: event.commitHash },\n })\n }\n\n // Custom resource attributes from config\n if (config.resourceAttributes) {\n for (const [key, value] of Object.entries(config.resourceAttributes)) {\n attributes.push({\n key,\n value: toAttributeValue(value),\n })\n }\n }\n\n return attributes\n}\n\n/**\n * Build headers from OTEL env vars.\n * Kept inline as OTLP-specific (parses OTEL_EXPORTER_OTLP_HEADERS=key=val,key=val).\n */\nfunction getHeadersFromEnv(): Record<string, string> | undefined {\n const headersEnv = process.env.OTEL_EXPORTER_OTLP_HEADERS || process.env.NUXT_OTLP_HEADERS\n if (headersEnv) {\n const headers: Record<string, string> = {}\n const decoded = decodeURIComponent(headersEnv)\n for (const pair of decoded.split(',')) {\n const eqIndex = pair.indexOf('=')\n if (eqIndex > 0) {\n const key = pair.slice(0, eqIndex).trim()\n const value = pair.slice(eqIndex + 1).trim()\n if (key && value) {\n headers[key] = value\n }\n }\n }\n if (Object.keys(headers).length > 0) return headers\n }\n\n const auth = process.env.NUXT_OTLP_AUTH\n if (auth) {\n return { Authorization: auth }\n }\n\n return undefined\n}\n\n/**\n * Create a drain function for sending logs to an OTLP endpoint.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createOTLPDrain()\n * 2. runtimeConfig.evlog.otlp (NUXT_EVLOG_OTLP_*)\n * 3. runtimeConfig.otlp (NUXT_OTLP_*)\n * 4. Environment variables: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME\n *\n * @example\n * ```ts\n * // Zero config - reads from runtimeConfig or env vars\n * nitroApp.hooks.hook('evlog:drain', createOTLPDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createOTLPDrain({\n * endpoint: 'http://localhost:4318',\n * }))\n * ```\n */\nexport function createOTLPDrain(overrides?: Partial<OTLPConfig>) {\n return defineHttpDrain<OTLPConfig>({\n name: 'otlp',\n resolve: async () => {\n const config = await resolveAdapterConfig<OTLPConfig>('otlp', OTLP_FIELDS, overrides)\n\n // OTLP-specific: resolve headers from env if not provided via config\n if (!config.headers) {\n config.headers = getHeadersFromEnv()\n }\n\n if (!config.endpoint) {\n console.error('[evlog/otlp] Missing endpoint. Set NUXT_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT env var, or pass to createOTLPDrain()')\n return null\n }\n return config as OTLPConfig\n },\n encode: (events, config) => {\n if (events.length === 0) return null\n return {\n url: `${config.endpoint.replace(/\\/$/, '')}/v1/logs`,\n headers: {\n 'Content-Type': 'application/json',\n ...config.headers,\n },\n body: JSON.stringify(buildOTLPPayload(events, config)),\n }\n },\n })\n}\n\nfunction buildOTLPPayload(events: WideEvent[], config: OTLPConfig): ExportLogsServiceRequest {\n const grouped = new Map<string, WideEvent[]>()\n for (const event of events) {\n const key = `${event.service}::${event.environment}`\n const group = grouped.get(key)\n if (group) group.push(event)\n else grouped.set(key, [event])\n }\n return {\n resourceLogs: Array.from(grouped.values()).map(groupEvents => ({\n resource: { attributes: buildResourceAttributes(groupEvents[0]!, config) },\n scopeLogs: [\n {\n scope: { name: 'evlog', version: '1.0.0' },\n logRecords: groupEvents.map(toOTLPLogRecord),\n },\n ],\n })),\n }\n}\n\n/**\n * Send a single event to an OTLP endpoint.\n *\n * @example\n * ```ts\n * await sendToOTLP(event, {\n * endpoint: 'http://localhost:4318',\n * })\n * ```\n */\nexport async function sendToOTLP(event: WideEvent, config: OTLPConfig): Promise<void> {\n await sendBatchToOTLP([event], config)\n}\n\n/**\n * Send a batch of events to an OTLP endpoint.\n *\n * @example\n * ```ts\n * await sendBatchToOTLP(events, {\n * endpoint: 'http://localhost:4318',\n * })\n * ```\n */\nexport async function sendBatchToOTLP(events: WideEvent[], config: OTLPConfig): Promise<void> {\n if (events.length === 0) return\n\n const url = `${config.endpoint.replace(/\\/$/, '')}/v1/logs`\n const payload = buildOTLPPayload(events, config)\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n ...config.headers,\n }\n\n await httpPost({\n url,\n headers,\n body: JSON.stringify(payload),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'OTLP',\n source: 'otlp',\n })\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { F as DrainContext, it as WideEvent } from "../audit-CJl-wZ10.mjs";
1
+ import { I as DrainContext, ct as WideEvent } from "../audit-CC8nfazi.mjs";
2
2
  //#region src/adapters/posthog.d.ts
3
3
  /**
4
4
  * Mode for {@link createPostHogDrain}.
@@ -1,4 +1,5 @@
1
- import { a as resolveAdapterConfig, n as defineHttpDrain, r as httpPost, t as defineDrain } from "../drain-ByWUeOQC.mjs";
1
+ import { r as httpPost } from "../http-6umVAKDW.mjs";
2
+ import { i as resolveAdapterConfig, n as defineHttpDrain, t as defineDrain } from "../drain-X7_5szSI.mjs";
2
3
  import { sendBatchToOTLP } from "./otlp.mjs";
3
4
  //#region src/adapters/posthog.ts
4
5
  const POSTHOG_FIELDS = [
@@ -142,7 +143,8 @@ async function sendBatchToPostHogEvents(events, config) {
142
143
  }),
143
144
  timeout: config.timeout ?? 5e3,
144
145
  retries: config.retries,
145
- label: "PostHog"
146
+ label: "PostHog",
147
+ source: "posthog"
146
148
  });
147
149
  }
148
150
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"posthog.mjs","names":[],"sources":["../../src/adapters/posthog.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineDrain, defineHttpDrain } from '../shared/drain'\nimport { httpPost } from '../shared/http'\nimport { sendBatchToOTLP } from './otlp'\nimport type { OTLPConfig } from './otlp'\n\n/**\n * Mode for {@link createPostHogDrain}.\n *\n * - `'logs'` (default) — sends events to PostHog Logs via OTLP. Cheapest path\n * and recommended for most teams.\n * - `'events'` — sends events to the `/batch/` API as custom PostHog events.\n * Useful when you want events to appear in PostHog product analytics\n * funnels/dashboards.\n */\nexport type PostHogMode = 'logs' | 'events'\n\nexport interface PostHogConfig {\n /** PostHog project API key */\n apiKey: string\n /** PostHog host URL. Default: https://us.i.posthog.com */\n host?: string\n /**\n * Send mode. `'logs'` (default) uses PostHog Logs (OTLP, cheapest);\n * `'events'` uses the `/batch/` API for custom PostHog events.\n * @default 'logs'\n */\n mode?: PostHogMode\n /**\n * PostHog event name when `mode === 'events'`. Ignored otherwise.\n * @default 'evlog_wide_event'\n */\n eventName?: string\n /**\n * Override `distinct_id` when `mode === 'events'`. Ignored otherwise.\n * Defaults to `event.userId` (when set) or `event.service`.\n */\n distinctId?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\n/**\n * @deprecated Use {@link PostHogConfig} with `mode: 'events'` instead.\n */\nexport type PostHogEventsConfig = PostHogConfig\n\n/** PostHog event structure for the batch API */\nexport interface PostHogEvent {\n event: string\n distinct_id: string\n timestamp: string\n properties: Record<string, unknown>\n}\n\nconst POSTHOG_FIELDS: ConfigField<PostHogConfig>[] = [\n { key: 'apiKey', env: ['NUXT_POSTHOG_API_KEY', 'POSTHOG_API_KEY'] },\n { key: 'host', env: ['NUXT_POSTHOG_HOST', 'POSTHOG_HOST'] },\n { key: 'mode' },\n { key: 'eventName' },\n { key: 'distinctId' },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nfunction resolveHost(config: PostHogConfig): string {\n return (config.host ?? 'https://us.i.posthog.com').replace(/\\/$/, '')\n}\n\nfunction toOTLPConfig(config: PostHogConfig): OTLPConfig {\n return {\n endpoint: `${resolveHost(config)}/i`,\n headers: { Authorization: `Bearer ${config.apiKey}` },\n timeout: config.timeout,\n retries: config.retries,\n }\n}\n\n/**\n * Convert a WideEvent to a PostHog custom event.\n */\nexport function toPostHogEvent(event: WideEvent, config: PostHogConfig): PostHogEvent {\n const { timestamp, level, service, ...rest } = event\n return {\n event: config.eventName ?? 'evlog_wide_event',\n distinct_id: config.distinctId\n ?? (typeof event.userId === 'string' ? event.userId : undefined)\n ?? service,\n timestamp,\n properties: {\n level,\n service,\n ...rest,\n },\n }\n}\n\n/**\n * Create a drain function for sending logs to PostHog.\n *\n * - Default `mode: 'logs'` — sends events to PostHog Logs via OTLP. Recommended.\n * - `mode: 'events'` — sends events to the `/batch/` API as custom events.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createPostHogDrain()\n * 2. runtimeConfig.evlog.posthog\n * 3. runtimeConfig.posthog\n * 4. Environment variables: NUXT_POSTHOG_*, POSTHOG_*\n *\n * @example\n * ```ts\n * // Default: PostHog Logs (OTLP)\n * initLogger({ drain: createPostHogDrain() })\n *\n * // Custom events\n * initLogger({ drain: createPostHogDrain({ mode: 'events', eventName: 'server_request' }) })\n * ```\n */\nexport function createPostHogDrain(overrides?: Partial<PostHogConfig>) {\n const mode: PostHogMode = overrides?.mode ?? 'logs'\n\n if (mode === 'events') {\n return defineHttpDrain<PostHogConfig>({\n name: 'posthog-events',\n resolve: async () => {\n const config = await resolveAdapterConfig<PostHogConfig>('posthog', POSTHOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog-events] Missing apiKey. Set NUXT_POSTHOG_API_KEY env var or pass to createPostHogDrain({ mode: \\'events\\' })')\n return null\n }\n return config as PostHogConfig\n },\n encode: (events, config) => ({\n url: `${resolveHost(config)}/batch/`,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n api_key: config.apiKey,\n batch: events.map(event => toPostHogEvent(event, config)),\n }),\n }),\n })\n }\n\n return defineDrain<PostHogConfig>({\n name: 'posthog',\n resolve: async () => {\n const config = await resolveAdapterConfig<PostHogConfig>('posthog', POSTHOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog] Missing apiKey. Set NUXT_POSTHOG_API_KEY env var or pass to createPostHogDrain()')\n return null\n }\n return config as PostHogConfig\n },\n send: async (events, config) => {\n if (events.length === 0) return\n await sendBatchToOTLP(events, toOTLPConfig(config))\n },\n })\n}\n\n/**\n * @deprecated Use {@link createPostHogDrain} with `mode: 'events'`.\n */\nexport function createPostHogEventsDrain(overrides?: Partial<PostHogConfig>) {\n return createPostHogDrain({ ...overrides, mode: 'events' })\n}\n\n/**\n * Send a single event to PostHog Logs via OTLP.\n */\nexport async function sendToPostHog(event: WideEvent, config: PostHogConfig): Promise<void> {\n await sendBatchToPostHog([event], config)\n}\n\n/**\n * Send a batch of events to PostHog Logs via OTLP.\n */\nexport async function sendBatchToPostHog(events: WideEvent[], config: PostHogConfig): Promise<void> {\n if (events.length === 0) return\n await sendBatchToOTLP(events, toOTLPConfig(config))\n}\n\n/**\n * Send a single event to PostHog via the custom-events `/batch/` API.\n */\nexport async function sendToPostHogEvents(event: WideEvent, config: PostHogConfig): Promise<void> {\n await sendBatchToPostHogEvents([event], config)\n}\n\n/**\n * Send a batch of events to PostHog via the custom-events `/batch/` API.\n */\nexport async function sendBatchToPostHogEvents(events: WideEvent[], config: PostHogConfig): Promise<void> {\n if (events.length === 0) return\n await httpPost({\n url: `${resolveHost(config)}/batch/`,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n api_key: config.apiKey,\n batch: events.map(event => toPostHogEvent(event, config)),\n }),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'PostHog',\n })\n}\n"],"mappings":";;;AA2DA,MAAM,iBAA+C;CACnD;EAAE,KAAK;EAAU,KAAK,CAAC,wBAAwB,kBAAkB;EAAE;CACnE;EAAE,KAAK;EAAQ,KAAK,CAAC,qBAAqB,eAAe;EAAE;CAC3D,EAAE,KAAK,QAAQ;CACf,EAAE,KAAK,aAAa;CACpB,EAAE,KAAK,cAAc;CACrB,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;AAED,SAAS,YAAY,QAA+B;AAClD,SAAQ,OAAO,QAAQ,4BAA4B,QAAQ,OAAO,GAAG;;AAGvE,SAAS,aAAa,QAAmC;AACvD,QAAO;EACL,UAAU,GAAG,YAAY,OAAO,CAAC;EACjC,SAAS,EAAE,eAAe,UAAU,OAAO,UAAU;EACrD,SAAS,OAAO;EAChB,SAAS,OAAO;EACjB;;;;;AAMH,SAAgB,eAAe,OAAkB,QAAqC;CACpF,MAAM,EAAE,WAAW,OAAO,SAAS,GAAG,SAAS;AAC/C,QAAO;EACL,OAAO,OAAO,aAAa;EAC3B,aAAa,OAAO,eACd,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS,KAAA,MACnD;EACL;EACA,YAAY;GACV;GACA;GACA,GAAG;GACJ;EACF;;;;;;;;;;;;;;;;;;;;;;;AAwBH,SAAgB,mBAAmB,WAAoC;AAGrE,MAF0B,WAAW,QAAQ,YAEhC,SACX,QAAO,gBAA+B;EACpC,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBAAoC,WAAW,gBAAgB,UAAU;AAC9F,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,4HAA8H;AAC5I,WAAO;;AAET,UAAO;;EAET,SAAS,QAAQ,YAAY;GAC3B,KAAK,GAAG,YAAY,OAAO,CAAC;GAC5B,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM,KAAK,UAAU;IACnB,SAAS,OAAO;IAChB,OAAO,OAAO,KAAI,UAAS,eAAe,OAAO,OAAO,CAAC;IAC1D,CAAC;GACH;EACF,CAAC;AAGJ,QAAO,YAA2B;EAChC,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBAAoC,WAAW,gBAAgB,UAAU;AAC9F,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,mGAAmG;AACjH,WAAO;;AAET,UAAO;;EAET,MAAM,OAAO,QAAQ,WAAW;AAC9B,OAAI,OAAO,WAAW,EAAG;AACzB,SAAM,gBAAgB,QAAQ,aAAa,OAAO,CAAC;;EAEtD,CAAC;;;;;AAMJ,SAAgB,yBAAyB,WAAoC;AAC3E,QAAO,mBAAmB;EAAE,GAAG;EAAW,MAAM;EAAU,CAAC;;;;;AAM7D,eAAsB,cAAc,OAAkB,QAAsC;AAC1F,OAAM,mBAAmB,CAAC,MAAM,EAAE,OAAO;;;;;AAM3C,eAAsB,mBAAmB,QAAqB,QAAsC;AAClG,KAAI,OAAO,WAAW,EAAG;AACzB,OAAM,gBAAgB,QAAQ,aAAa,OAAO,CAAC;;;;;AAMrD,eAAsB,oBAAoB,OAAkB,QAAsC;AAChG,OAAM,yBAAyB,CAAC,MAAM,EAAE,OAAO;;;;;AAMjD,eAAsB,yBAAyB,QAAqB,QAAsC;AACxG,KAAI,OAAO,WAAW,EAAG;AACzB,OAAM,SAAS;EACb,KAAK,GAAG,YAAY,OAAO,CAAC;EAC5B,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU;GACnB,SAAS,OAAO;GAChB,OAAO,OAAO,KAAI,UAAS,eAAe,OAAO,OAAO,CAAC;GAC1D,CAAC;EACF,SAAS,OAAO,WAAW;EAC3B,SAAS,OAAO;EAChB,OAAO;EACR,CAAC"}
1
+ {"version":3,"file":"posthog.mjs","names":[],"sources":["../../src/adapters/posthog.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineDrain, defineHttpDrain } from '../shared/drain'\nimport { httpPost } from '../shared/http'\nimport { sendBatchToOTLP } from './otlp'\nimport type { OTLPConfig } from './otlp'\n\n/**\n * Mode for {@link createPostHogDrain}.\n *\n * - `'logs'` (default) — sends events to PostHog Logs via OTLP. Cheapest path\n * and recommended for most teams.\n * - `'events'` — sends events to the `/batch/` API as custom PostHog events.\n * Useful when you want events to appear in PostHog product analytics\n * funnels/dashboards.\n */\nexport type PostHogMode = 'logs' | 'events'\n\nexport interface PostHogConfig {\n /** PostHog project API key */\n apiKey: string\n /** PostHog host URL. Default: https://us.i.posthog.com */\n host?: string\n /**\n * Send mode. `'logs'` (default) uses PostHog Logs (OTLP, cheapest);\n * `'events'` uses the `/batch/` API for custom PostHog events.\n * @default 'logs'\n */\n mode?: PostHogMode\n /**\n * PostHog event name when `mode === 'events'`. Ignored otherwise.\n * @default 'evlog_wide_event'\n */\n eventName?: string\n /**\n * Override `distinct_id` when `mode === 'events'`. Ignored otherwise.\n * Defaults to `event.userId` (when set) or `event.service`.\n */\n distinctId?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\n/**\n * @deprecated Use {@link PostHogConfig} with `mode: 'events'` instead.\n */\nexport type PostHogEventsConfig = PostHogConfig\n\n/** PostHog event structure for the batch API */\nexport interface PostHogEvent {\n event: string\n distinct_id: string\n timestamp: string\n properties: Record<string, unknown>\n}\n\nconst POSTHOG_FIELDS: ConfigField<PostHogConfig>[] = [\n { key: 'apiKey', env: ['NUXT_POSTHOG_API_KEY', 'POSTHOG_API_KEY'] },\n { key: 'host', env: ['NUXT_POSTHOG_HOST', 'POSTHOG_HOST'] },\n { key: 'mode' },\n { key: 'eventName' },\n { key: 'distinctId' },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nfunction resolveHost(config: PostHogConfig): string {\n return (config.host ?? 'https://us.i.posthog.com').replace(/\\/$/, '')\n}\n\nfunction toOTLPConfig(config: PostHogConfig): OTLPConfig {\n return {\n endpoint: `${resolveHost(config)}/i`,\n headers: { Authorization: `Bearer ${config.apiKey}` },\n timeout: config.timeout,\n retries: config.retries,\n }\n}\n\n/**\n * Convert a WideEvent to a PostHog custom event.\n */\nexport function toPostHogEvent(event: WideEvent, config: PostHogConfig): PostHogEvent {\n const { timestamp, level, service, ...rest } = event\n return {\n event: config.eventName ?? 'evlog_wide_event',\n distinct_id: config.distinctId\n ?? (typeof event.userId === 'string' ? event.userId : undefined)\n ?? service,\n timestamp,\n properties: {\n level,\n service,\n ...rest,\n },\n }\n}\n\n/**\n * Create a drain function for sending logs to PostHog.\n *\n * - Default `mode: 'logs'` — sends events to PostHog Logs via OTLP. Recommended.\n * - `mode: 'events'` — sends events to the `/batch/` API as custom events.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createPostHogDrain()\n * 2. runtimeConfig.evlog.posthog\n * 3. runtimeConfig.posthog\n * 4. Environment variables: NUXT_POSTHOG_*, POSTHOG_*\n *\n * @example\n * ```ts\n * // Default: PostHog Logs (OTLP)\n * initLogger({ drain: createPostHogDrain() })\n *\n * // Custom events\n * initLogger({ drain: createPostHogDrain({ mode: 'events', eventName: 'server_request' }) })\n * ```\n */\nexport function createPostHogDrain(overrides?: Partial<PostHogConfig>) {\n const mode: PostHogMode = overrides?.mode ?? 'logs'\n\n if (mode === 'events') {\n return defineHttpDrain<PostHogConfig>({\n name: 'posthog-events',\n resolve: async () => {\n const config = await resolveAdapterConfig<PostHogConfig>('posthog', POSTHOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog-events] Missing apiKey. Set NUXT_POSTHOG_API_KEY env var or pass to createPostHogDrain({ mode: \\'events\\' })')\n return null\n }\n return config as PostHogConfig\n },\n encode: (events, config) => ({\n url: `${resolveHost(config)}/batch/`,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n api_key: config.apiKey,\n batch: events.map(event => toPostHogEvent(event, config)),\n }),\n }),\n })\n }\n\n return defineDrain<PostHogConfig>({\n name: 'posthog',\n resolve: async () => {\n const config = await resolveAdapterConfig<PostHogConfig>('posthog', POSTHOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog] Missing apiKey. Set NUXT_POSTHOG_API_KEY env var or pass to createPostHogDrain()')\n return null\n }\n return config as PostHogConfig\n },\n send: async (events, config) => {\n if (events.length === 0) return\n await sendBatchToOTLP(events, toOTLPConfig(config))\n },\n })\n}\n\n/**\n * @deprecated Use {@link createPostHogDrain} with `mode: 'events'`.\n */\nexport function createPostHogEventsDrain(overrides?: Partial<PostHogConfig>) {\n return createPostHogDrain({ ...overrides, mode: 'events' })\n}\n\n/**\n * Send a single event to PostHog Logs via OTLP.\n */\nexport async function sendToPostHog(event: WideEvent, config: PostHogConfig): Promise<void> {\n await sendBatchToPostHog([event], config)\n}\n\n/**\n * Send a batch of events to PostHog Logs via OTLP.\n */\nexport async function sendBatchToPostHog(events: WideEvent[], config: PostHogConfig): Promise<void> {\n if (events.length === 0) return\n await sendBatchToOTLP(events, toOTLPConfig(config))\n}\n\n/**\n * Send a single event to PostHog via the custom-events `/batch/` API.\n */\nexport async function sendToPostHogEvents(event: WideEvent, config: PostHogConfig): Promise<void> {\n await sendBatchToPostHogEvents([event], config)\n}\n\n/**\n * Send a batch of events to PostHog via the custom-events `/batch/` API.\n */\nexport async function sendBatchToPostHogEvents(events: WideEvent[], config: PostHogConfig): Promise<void> {\n if (events.length === 0) return\n await httpPost({\n url: `${resolveHost(config)}/batch/`,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n api_key: config.apiKey,\n batch: events.map(event => toPostHogEvent(event, config)),\n }),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'PostHog',\n source: 'posthog',\n })\n}\n"],"mappings":";;;;AA2DA,MAAM,iBAA+C;CACnD;EAAE,KAAK;EAAU,KAAK,CAAC,wBAAwB,kBAAkB;EAAE;CACnE;EAAE,KAAK;EAAQ,KAAK,CAAC,qBAAqB,eAAe;EAAE;CAC3D,EAAE,KAAK,QAAQ;CACf,EAAE,KAAK,aAAa;CACpB,EAAE,KAAK,cAAc;CACrB,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;AAED,SAAS,YAAY,QAA+B;AAClD,SAAQ,OAAO,QAAQ,4BAA4B,QAAQ,OAAO,GAAG;;AAGvE,SAAS,aAAa,QAAmC;AACvD,QAAO;EACL,UAAU,GAAG,YAAY,OAAO,CAAC;EACjC,SAAS,EAAE,eAAe,UAAU,OAAO,UAAU;EACrD,SAAS,OAAO;EAChB,SAAS,OAAO;EACjB;;;;;AAMH,SAAgB,eAAe,OAAkB,QAAqC;CACpF,MAAM,EAAE,WAAW,OAAO,SAAS,GAAG,SAAS;AAC/C,QAAO;EACL,OAAO,OAAO,aAAa;EAC3B,aAAa,OAAO,eACd,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS,KAAA,MACnD;EACL;EACA,YAAY;GACV;GACA;GACA,GAAG;GACJ;EACF;;;;;;;;;;;;;;;;;;;;;;;AAwBH,SAAgB,mBAAmB,WAAoC;AAGrE,MAF0B,WAAW,QAAQ,YAEhC,SACX,QAAO,gBAA+B;EACpC,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBAAoC,WAAW,gBAAgB,UAAU;AAC9F,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,4HAA8H;AAC5I,WAAO;;AAET,UAAO;;EAET,SAAS,QAAQ,YAAY;GAC3B,KAAK,GAAG,YAAY,OAAO,CAAC;GAC5B,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM,KAAK,UAAU;IACnB,SAAS,OAAO;IAChB,OAAO,OAAO,KAAI,UAAS,eAAe,OAAO,OAAO,CAAC;IAC1D,CAAC;GACH;EACF,CAAC;AAGJ,QAAO,YAA2B;EAChC,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBAAoC,WAAW,gBAAgB,UAAU;AAC9F,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,mGAAmG;AACjH,WAAO;;AAET,UAAO;;EAET,MAAM,OAAO,QAAQ,WAAW;AAC9B,OAAI,OAAO,WAAW,EAAG;AACzB,SAAM,gBAAgB,QAAQ,aAAa,OAAO,CAAC;;EAEtD,CAAC;;;;;AAMJ,SAAgB,yBAAyB,WAAoC;AAC3E,QAAO,mBAAmB;EAAE,GAAG;EAAW,MAAM;EAAU,CAAC;;;;;AAM7D,eAAsB,cAAc,OAAkB,QAAsC;AAC1F,OAAM,mBAAmB,CAAC,MAAM,EAAE,OAAO;;;;;AAM3C,eAAsB,mBAAmB,QAAqB,QAAsC;AAClG,KAAI,OAAO,WAAW,EAAG;AACzB,OAAM,gBAAgB,QAAQ,aAAa,OAAO,CAAC;;;;;AAMrD,eAAsB,oBAAoB,OAAkB,QAAsC;AAChG,OAAM,yBAAyB,CAAC,MAAM,EAAE,OAAO;;;;;AAMjD,eAAsB,yBAAyB,QAAqB,QAAsC;AACxG,KAAI,OAAO,WAAW,EAAG;AACzB,OAAM,SAAS;EACb,KAAK,GAAG,YAAY,OAAO,CAAC;EAC5B,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU;GACnB,SAAS,OAAO;GAChB,OAAO,OAAO,KAAI,UAAS,eAAe,OAAO,OAAO,CAAC;GAC1D,CAAC;EACF,SAAS,OAAO,WAAW;EAC3B,SAAS,OAAO;EAChB,OAAO;EACP,QAAQ;EACT,CAAC"}
@@ -1,4 +1,4 @@
1
- import { F as DrainContext, it as WideEvent } from "../audit-CJl-wZ10.mjs";
1
+ import { I as DrainContext, ct as WideEvent } from "../audit-CC8nfazi.mjs";
2
2
  //#region src/adapters/sentry.d.ts
3
3
  interface SentryConfig {
4
4
  /** Sentry DSN */
@@ -1,5 +1,6 @@
1
- import { a as resolveAdapterConfig, n as defineHttpDrain, r as httpPost } from "../drain-ByWUeOQC.mjs";
2
- import { t as OTEL_SEVERITY_NUMBER } from "../severity-BYWZ96Sb.mjs";
1
+ import { r as httpPost } from "../http-6umVAKDW.mjs";
2
+ import { i as resolveAdapterConfig, n as defineHttpDrain } from "../drain-X7_5szSI.mjs";
3
+ import { t as OTEL_SEVERITY_NUMBER } from "../severity-R5Egq3qz.mjs";
3
4
  //#region src/adapters/sentry.ts
4
5
  const SENTRY_FIELDS = [
5
6
  {
@@ -224,7 +225,8 @@ async function sendBatchToSentry(events, config) {
224
225
  body,
225
226
  timeout: config.timeout ?? 5e3,
226
227
  retries: config.retries,
227
- label: "Sentry"
228
+ label: "Sentry",
229
+ source: "sentry"
228
230
  });
229
231
  }
230
232
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"sentry.mjs","names":[],"sources":["../../src/adapters/sentry.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineHttpDrain } from '../shared/drain'\nimport { httpPost } from '../shared/http'\nimport { OTEL_SEVERITY_NUMBER } from '../shared/severity'\n\nexport interface SentryConfig {\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 /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\n/** Sentry Log attribute value with type annotation */\nexport interface SentryAttributeValue {\n value: string | number | boolean\n type: 'string' | 'integer' | 'double' | 'boolean'\n}\n\n/** Sentry Structured Log payload */\nexport interface SentryLog {\n timestamp: number\n trace_id: string\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'\n body: string\n severity_number: number\n attributes?: Record<string, SentryAttributeValue>\n}\n\ninterface SentryDsnParts {\n publicKey: string\n secretKey?: string\n projectId: string\n origin: string\n basePath: string\n}\n\nconst SENTRY_FIELDS: ConfigField<SentryConfig>[] = [\n { key: 'dsn', env: ['NUXT_SENTRY_DSN', 'SENTRY_DSN'] },\n { key: 'environment', env: ['NUXT_SENTRY_ENVIRONMENT', 'SENTRY_ENVIRONMENT'] },\n { key: 'release', env: ['NUXT_SENTRY_RELEASE', 'SENTRY_RELEASE'] },\n { key: 'tags' },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nfunction parseSentryDsn(dsn: string): SentryDsnParts {\n const url = new URL(dsn)\n const publicKey = url.username\n if (!publicKey) {\n throw new Error('Invalid Sentry DSN: missing public key')\n }\n\n const secretKey = url.password || undefined\n\n const pathParts = url.pathname.split('/').filter(Boolean)\n const projectId = pathParts.pop()\n if (!projectId) {\n throw new Error('Invalid Sentry DSN: missing project ID')\n }\n\n const basePath = pathParts.length > 0 ? `/${pathParts.join('/')}` : ''\n\n return {\n publicKey,\n secretKey,\n projectId,\n origin: `${url.protocol}//${url.host}`,\n basePath,\n }\n}\n\nfunction getSentryEnvelopeUrl(dsn: string): { url: string, authHeader: string } {\n const { publicKey, secretKey, projectId, origin, basePath } = parseSentryDsn(dsn)\n const url = `${origin}${basePath}/api/${projectId}/envelope/`\n let authHeader = `Sentry sentry_version=7, sentry_key=${publicKey}, sentry_client=evlog`\n if (secretKey) {\n authHeader += `, sentry_secret=${secretKey}`\n }\n return { url, authHeader }\n}\n\nfunction createTraceId(): string {\n if (typeof globalThis.crypto?.randomUUID === 'function') {\n return globalThis.crypto.randomUUID().replace(/-/g, '')\n }\n\n return Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join('')\n}\n\nfunction getFirstStringValue(event: WideEvent, keys: string[]): string | undefined {\n for (const key of keys) {\n const value = event[key]\n if (typeof value === 'string' && value.length > 0) return value\n }\n return undefined\n}\n\nfunction toAttributeValue(value: unknown): SentryAttributeValue | undefined {\n if (value === null || value === undefined) {\n return undefined\n }\n if (typeof value === 'string') {\n return { value, type: 'string' }\n }\n if (typeof value === 'boolean') {\n return { value, type: 'boolean' }\n }\n if (typeof value === 'number') {\n if (Number.isInteger(value)) {\n return { value, type: 'integer' }\n }\n return { value, type: 'double' }\n }\n return { value: JSON.stringify(value), type: 'string' }\n}\n\nexport function toSentryLog(event: WideEvent, config: SentryConfig): SentryLog {\n const { timestamp, level, service, environment, version, ...rest } = event\n\n const body = getFirstStringValue(event, ['message', 'action', 'path'])\n ?? 'evlog wide event'\n\n const traceId = (typeof event.traceId === 'string' && event.traceId.length > 0)\n ? event.traceId\n : createTraceId()\n\n const attributes: Record<string, SentryAttributeValue> = {}\n\n const env = config.environment ?? environment\n if (env) {\n attributes['sentry.environment'] = { value: env, type: 'string' }\n }\n\n const rel = config.release ?? version\n if (typeof rel === 'string' && rel.length > 0) {\n attributes['sentry.release'] = { value: rel, type: 'string' }\n }\n\n attributes['service'] = { value: service, type: 'string' }\n\n if (config.tags) {\n for (const [key, value] of Object.entries(config.tags)) {\n attributes[key] = { value, type: 'string' }\n }\n }\n\n for (const [key, value] of Object.entries(rest)) {\n if (key === 'traceId' || key === 'spanId') continue\n if (value === undefined || value === null) continue\n const attr = toAttributeValue(value)\n if (attr) {\n attributes[key] = attr\n }\n }\n\n return {\n timestamp: new Date(timestamp).getTime() / 1000,\n trace_id: traceId,\n level: level as SentryLog['level'],\n body,\n severity_number: OTEL_SEVERITY_NUMBER[level] ?? 9,\n attributes,\n }\n}\n\n/**\n * Build the Sentry Envelope body for a list of logs.\n *\n * Envelope format (line-delimited):\n * - Line 1: Envelope headers (dsn, sent_at)\n * - Line 2: Item header (type: log, item_count, content_type)\n * - Line 3: Item payload ({\"items\": [...]})\n */\nfunction buildEnvelopeBody(logs: SentryLog[], dsn: string): string {\n const envelopeHeader = JSON.stringify({\n dsn,\n sent_at: new Date().toISOString(),\n })\n\n const itemHeader = JSON.stringify({\n type: 'log',\n item_count: logs.length,\n content_type: 'application/vnd.sentry.items.log+json',\n })\n\n const itemPayload = JSON.stringify({ items: logs })\n\n return `${envelopeHeader}\\n${itemHeader}\\n${itemPayload}\\n`\n}\n\n/**\n * Create a drain function for sending logs to Sentry.\n *\n * Sends wide events as Sentry Structured Logs, visible in Explore > Logs\n * in the Sentry dashboard.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createSentryDrain()\n * 2. runtimeConfig.evlog.sentry\n * 3. runtimeConfig.sentry\n * 4. Environment variables: NUXT_SENTRY_*, SENTRY_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_SENTRY_DSN env var\n * nitroApp.hooks.hook('evlog:drain', createSentryDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createSentryDrain({\n * dsn: 'https://public@o0.ingest.sentry.io/123',\n * }))\n * ```\n */\nexport function createSentryDrain(overrides?: Partial<SentryConfig>) {\n return defineHttpDrain<SentryConfig>({\n name: 'sentry',\n resolve: async () => {\n const config = await resolveAdapterConfig<SentryConfig>('sentry', SENTRY_FIELDS, overrides)\n if (!config.dsn) {\n console.error('[evlog/sentry] Missing DSN. Set NUXT_SENTRY_DSN/SENTRY_DSN env var or pass to createSentryDrain()')\n return null\n }\n return config as SentryConfig\n },\n encode: (events, config) => {\n const { url, authHeader } = getSentryEnvelopeUrl(config.dsn)\n const logs = events.map(event => toSentryLog(event, config))\n return {\n url,\n headers: {\n 'Content-Type': 'application/x-sentry-envelope',\n 'X-Sentry-Auth': authHeader,\n },\n body: buildEnvelopeBody(logs, config.dsn),\n }\n },\n })\n}\n\n/**\n * Send a single event to Sentry as a structured log.\n *\n * @example\n * ```ts\n * await sendToSentry(event, {\n * dsn: process.env.SENTRY_DSN!,\n * })\n * ```\n */\nexport async function sendToSentry(event: WideEvent, config: SentryConfig): Promise<void> {\n await sendBatchToSentry([event], config)\n}\n\n/**\n * Send a batch of events to Sentry as structured logs via the Envelope endpoint.\n *\n * @example\n * ```ts\n * await sendBatchToSentry(events, {\n * dsn: process.env.SENTRY_DSN!,\n * })\n * ```\n */\nexport async function sendBatchToSentry(events: WideEvent[], config: SentryConfig): Promise<void> {\n if (events.length === 0) return\n\n const { url, authHeader } = getSentryEnvelopeUrl(config.dsn)\n\n const logs = events.map(event => toSentryLog(event, config))\n const body = buildEnvelopeBody(logs, config.dsn)\n\n await httpPost({\n url,\n headers: {\n 'Content-Type': 'application/x-sentry-envelope',\n 'X-Sentry-Auth': authHeader,\n },\n body,\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Sentry',\n })\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sentry.mjs","names":[],"sources":["../../src/adapters/sentry.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineHttpDrain } from '../shared/drain'\nimport { httpPost } from '../shared/http'\nimport { OTEL_SEVERITY_NUMBER } from '../shared/severity'\n\nexport interface SentryConfig {\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 /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\n/** Sentry Log attribute value with type annotation */\nexport interface SentryAttributeValue {\n value: string | number | boolean\n type: 'string' | 'integer' | 'double' | 'boolean'\n}\n\n/** Sentry Structured Log payload */\nexport interface SentryLog {\n timestamp: number\n trace_id: string\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'\n body: string\n severity_number: number\n attributes?: Record<string, SentryAttributeValue>\n}\n\ninterface SentryDsnParts {\n publicKey: string\n secretKey?: string\n projectId: string\n origin: string\n basePath: string\n}\n\nconst SENTRY_FIELDS: ConfigField<SentryConfig>[] = [\n { key: 'dsn', env: ['NUXT_SENTRY_DSN', 'SENTRY_DSN'] },\n { key: 'environment', env: ['NUXT_SENTRY_ENVIRONMENT', 'SENTRY_ENVIRONMENT'] },\n { key: 'release', env: ['NUXT_SENTRY_RELEASE', 'SENTRY_RELEASE'] },\n { key: 'tags' },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nfunction parseSentryDsn(dsn: string): SentryDsnParts {\n const url = new URL(dsn)\n const publicKey = url.username\n if (!publicKey) {\n throw new Error('Invalid Sentry DSN: missing public key')\n }\n\n const secretKey = url.password || undefined\n\n const pathParts = url.pathname.split('/').filter(Boolean)\n const projectId = pathParts.pop()\n if (!projectId) {\n throw new Error('Invalid Sentry DSN: missing project ID')\n }\n\n const basePath = pathParts.length > 0 ? `/${pathParts.join('/')}` : ''\n\n return {\n publicKey,\n secretKey,\n projectId,\n origin: `${url.protocol}//${url.host}`,\n basePath,\n }\n}\n\nfunction getSentryEnvelopeUrl(dsn: string): { url: string, authHeader: string } {\n const { publicKey, secretKey, projectId, origin, basePath } = parseSentryDsn(dsn)\n const url = `${origin}${basePath}/api/${projectId}/envelope/`\n let authHeader = `Sentry sentry_version=7, sentry_key=${publicKey}, sentry_client=evlog`\n if (secretKey) {\n authHeader += `, sentry_secret=${secretKey}`\n }\n return { url, authHeader }\n}\n\nfunction createTraceId(): string {\n if (typeof globalThis.crypto?.randomUUID === 'function') {\n return globalThis.crypto.randomUUID().replace(/-/g, '')\n }\n\n return Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join('')\n}\n\nfunction getFirstStringValue(event: WideEvent, keys: string[]): string | undefined {\n for (const key of keys) {\n const value = event[key]\n if (typeof value === 'string' && value.length > 0) return value\n }\n return undefined\n}\n\nfunction toAttributeValue(value: unknown): SentryAttributeValue | undefined {\n if (value === null || value === undefined) {\n return undefined\n }\n if (typeof value === 'string') {\n return { value, type: 'string' }\n }\n if (typeof value === 'boolean') {\n return { value, type: 'boolean' }\n }\n if (typeof value === 'number') {\n if (Number.isInteger(value)) {\n return { value, type: 'integer' }\n }\n return { value, type: 'double' }\n }\n return { value: JSON.stringify(value), type: 'string' }\n}\n\nexport function toSentryLog(event: WideEvent, config: SentryConfig): SentryLog {\n const { timestamp, level, service, environment, version, ...rest } = event\n\n const body = getFirstStringValue(event, ['message', 'action', 'path'])\n ?? 'evlog wide event'\n\n const traceId = (typeof event.traceId === 'string' && event.traceId.length > 0)\n ? event.traceId\n : createTraceId()\n\n const attributes: Record<string, SentryAttributeValue> = {}\n\n const env = config.environment ?? environment\n if (env) {\n attributes['sentry.environment'] = { value: env, type: 'string' }\n }\n\n const rel = config.release ?? version\n if (typeof rel === 'string' && rel.length > 0) {\n attributes['sentry.release'] = { value: rel, type: 'string' }\n }\n\n attributes['service'] = { value: service, type: 'string' }\n\n if (config.tags) {\n for (const [key, value] of Object.entries(config.tags)) {\n attributes[key] = { value, type: 'string' }\n }\n }\n\n for (const [key, value] of Object.entries(rest)) {\n if (key === 'traceId' || key === 'spanId') continue\n if (value === undefined || value === null) continue\n const attr = toAttributeValue(value)\n if (attr) {\n attributes[key] = attr\n }\n }\n\n return {\n timestamp: new Date(timestamp).getTime() / 1000,\n trace_id: traceId,\n level: level as SentryLog['level'],\n body,\n severity_number: OTEL_SEVERITY_NUMBER[level] ?? 9,\n attributes,\n }\n}\n\n/**\n * Build the Sentry Envelope body for a list of logs.\n *\n * Envelope format (line-delimited):\n * - Line 1: Envelope headers (dsn, sent_at)\n * - Line 2: Item header (type: log, item_count, content_type)\n * - Line 3: Item payload ({\"items\": [...]})\n */\nfunction buildEnvelopeBody(logs: SentryLog[], dsn: string): string {\n const envelopeHeader = JSON.stringify({\n dsn,\n sent_at: new Date().toISOString(),\n })\n\n const itemHeader = JSON.stringify({\n type: 'log',\n item_count: logs.length,\n content_type: 'application/vnd.sentry.items.log+json',\n })\n\n const itemPayload = JSON.stringify({ items: logs })\n\n return `${envelopeHeader}\\n${itemHeader}\\n${itemPayload}\\n`\n}\n\n/**\n * Create a drain function for sending logs to Sentry.\n *\n * Sends wide events as Sentry Structured Logs, visible in Explore > Logs\n * in the Sentry dashboard.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createSentryDrain()\n * 2. runtimeConfig.evlog.sentry\n * 3. runtimeConfig.sentry\n * 4. Environment variables: NUXT_SENTRY_*, SENTRY_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_SENTRY_DSN env var\n * nitroApp.hooks.hook('evlog:drain', createSentryDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createSentryDrain({\n * dsn: 'https://public@o0.ingest.sentry.io/123',\n * }))\n * ```\n */\nexport function createSentryDrain(overrides?: Partial<SentryConfig>) {\n return defineHttpDrain<SentryConfig>({\n name: 'sentry',\n resolve: async () => {\n const config = await resolveAdapterConfig<SentryConfig>('sentry', SENTRY_FIELDS, overrides)\n if (!config.dsn) {\n console.error('[evlog/sentry] Missing DSN. Set NUXT_SENTRY_DSN/SENTRY_DSN env var or pass to createSentryDrain()')\n return null\n }\n return config as SentryConfig\n },\n encode: (events, config) => {\n const { url, authHeader } = getSentryEnvelopeUrl(config.dsn)\n const logs = events.map(event => toSentryLog(event, config))\n return {\n url,\n headers: {\n 'Content-Type': 'application/x-sentry-envelope',\n 'X-Sentry-Auth': authHeader,\n },\n body: buildEnvelopeBody(logs, config.dsn),\n }\n },\n })\n}\n\n/**\n * Send a single event to Sentry as a structured log.\n *\n * @example\n * ```ts\n * await sendToSentry(event, {\n * dsn: process.env.SENTRY_DSN!,\n * })\n * ```\n */\nexport async function sendToSentry(event: WideEvent, config: SentryConfig): Promise<void> {\n await sendBatchToSentry([event], config)\n}\n\n/**\n * Send a batch of events to Sentry as structured logs via the Envelope endpoint.\n *\n * @example\n * ```ts\n * await sendBatchToSentry(events, {\n * dsn: process.env.SENTRY_DSN!,\n * })\n * ```\n */\nexport async function sendBatchToSentry(events: WideEvent[], config: SentryConfig): Promise<void> {\n if (events.length === 0) return\n\n const { url, authHeader } = getSentryEnvelopeUrl(config.dsn)\n\n const logs = events.map(event => toSentryLog(event, config))\n const body = buildEnvelopeBody(logs, config.dsn)\n\n await httpPost({\n url,\n headers: {\n 'Content-Type': 'application/x-sentry-envelope',\n 'X-Sentry-Auth': authHeader,\n },\n body,\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Sentry',\n source: 'sentry',\n })\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { Y as RequestLogger } from "../audit-CJl-wZ10.mjs";
1
+ import { $ as RequestLogger } from "../audit-CC8nfazi.mjs";
2
2
  import { GatewayModelId, TelemetryIntegration } from "ai";
3
3
  import { LanguageModelV3, LanguageModelV3Middleware } from "@ai-sdk/provider";
4
4
 
@@ -328,6 +328,18 @@ declare function createAIMiddleware(log: RequestLogger, options?: AILoggerOption
328
328
  * ```
329
329
  */
330
330
  declare function createAILogger(log: RequestLogger, options?: AILoggerOptions): AILogger;
331
+ /**
332
+ * Snapshot of how much of each cumulative array we've already sent to the
333
+ * wide event. `flushState` reads it to compute the delta and updates it in
334
+ * place; passing zero-watermarks to `buildMetadata` yields a full snapshot.
335
+ */
336
+ interface Watermarks {
337
+ toolCalls: number;
338
+ toolCallInputs: number;
339
+ stepsUsage: number;
340
+ toolExecutions: number;
341
+ models: Set<string>;
342
+ }
331
343
  interface AccumulatorState {
332
344
  calls: number;
333
345
  steps: number;
@@ -353,6 +365,8 @@ interface AccumulatorState {
353
365
  embedding: AIEmbeddingData | undefined;
354
366
  costMap: Record<string, ModelCost> | undefined;
355
367
  subscribers: Set<AIMetadataListener>;
368
+ /** @internal What's already been written to the wide event. */
369
+ _flushed: Watermarks;
356
370
  /** @internal Logger reference for integration flush */
357
371
  _log?: RequestLogger;
358
372
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/ai/index.ts"],"mappings":";;;;;;;AAQA;UAAiB,iBAAA;;;;;EAKf,SAAA;EAM6B;;;AAM/B;;EANE,SAAA,IAAa,KAAA,WAAgB,QAAA;AAAA;;AAc/B;;UARiB,SAAA;EACf,KAAA;EACA,MAAA;AAAA;;;;UAMe,eAAA;EA2Bf;;;;;AAMF;;;EAxBE,UAAA,aAAuB,iBAAA;EAyBvB;;;;;;AASF;;;;;;;;;;AAUA;EA1BE,IAAA,GAAO,MAAA,SAAe,SAAA;AAAA;;;;UAMP,WAAA;EACf,KAAA;EACA,WAAA;EACA,YAAA;EACA,SAAA;AAAA;;;;UAMe,eAAA;EACf,IAAA;EACA,UAAA;EACA,OAAA;EACA,KAAA;AAAA;;;;UAMe,eAAA;EACf,KAAA;EACA,MAAA;EACA,UAAA;EACA,KAAA;AAAA;;;;;;;;;UAWe,WAAA;EACf,KAAA;EACA,KAAA;EACA,MAAA;EACA,QAAA;EACA,WAAA;EACA,YAAA;EACA,WAAA;EACA,eAAA;EACA,gBAAA;EACA,eAAA;EACA,YAAA;EACA,SAAA,cAAuB,KAAA;IAAQ,IAAA;IAAc,KAAA;EAAA;EAC7C,UAAA;EACA,KAAA;EACA,UAAA,GAAa,WAAA;EACb,cAAA;EACA,UAAA;EACA,eAAA;EACA,KAAA;EACA,KAAA,GAAQ,eAAA;EACR,eAAA;EACA,SAAA,GAAY,eAAA;EACZ,aAAA;AAAA;;;;;;;KASU,UAAA,GAAa,WAAA;;;;;KAMb,kBAAA,IAAsB,QAAA,EAAU,UAAA;AAAA,UAE3B,QAAA;EA0Cb;;;;;;;;;;;;;;AAgGH;;;;;;EArHC,IAAA,GAAO,KAAA,EAAO,eAAA,GAAkB,cAAA,KAAmB,eAAA;EA2HnD;;;;AA0DF;;;;;;;;;;;;;EAlKE,YAAA,GAAe,MAAA;IACb,KAAA;MAAS,MAAA;IAAA;IACT,KAAA;IACA,UAAA;IACA,KAAA;EAAA;EA4LmF;;;;;;;;;AAyCtF;;;;;;;;;;;;;;;;;;;;;;;;EAjMC,WAAA,QAAmB,UAAA;EA0MQ;;;;;;;;;;;;;;;;;;;EArL3B,gBAAA;EAkMwB;;;;;;;;AAqY1B;;;;;;;;;;;;;;;;;;;;EAziBE,QAAA,GAAW,QAAA,EAAU,kBAAA;;;;;EAMrB,MAAA,EAAQ,gBAAA;AAAA;AAAA,UAGA,gBAAA;EACR,WAAA;EACA,YAAA;EACA,eAAA;EACA,gBAAA;EACA,eAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;iBAyDc,kBAAA,CAAmB,GAAA,EAAK,aAAA,EAAe,OAAA,GAAU,eAAA,GAAkB,yBAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8BnE,cAAA,CAAe,GAAA,EAAK,aAAA,EAAe,OAAA,GAAU,eAAA,GAAkB,QAAA;AAAA,UA2CrE,gBAAA;EACR,KAAA;EACA,KAAA;EACA,KAAA,EAAO,gBAAA;EACP,MAAA;EACA,YAAA;EACA,YAAA;EACA,iBAAA,EAAmB,KAAA;IAAQ,IAAA;IAAc,KAAA;EAAA;EACzC,UAAA,EAAY,WAAA;EACZ,gBAAA;EACA,kBAAA;EACA,cAAA;EACA,SAAA;EACA,cAAA;EACA,UAAA;EACA,iBAAA,EAAmB,iBAAA;EACnB,cAAA,EAAgB,eAAA;EAChB,mBAAA;EACA,eAAA;EACA,SAAA,EAAW,eAAA;EACX,OAAA,EAAS,MAAA,SAAe,SAAA;EACxB,WAAA,EAAa,GAAA,CAAI,kBAAA;;EAEjB,IAAA,GAAO,aAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkYO,sBAAA,CACd,OAAA,EAAS,aAAA,GAAgB,QAAA,EACzB,OAAA,GAAU,eAAA,GACT,oBAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/ai/index.ts"],"mappings":"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"}
package/dist/ai/index.mjs CHANGED
@@ -109,6 +109,15 @@ function createAILogger(log, options) {
109
109
  _state: state
110
110
  };
111
111
  }
112
+ function freshWatermarks() {
113
+ return {
114
+ toolCalls: 0,
115
+ toolCallInputs: 0,
116
+ stepsUsage: 0,
117
+ toolExecutions: 0,
118
+ models: /* @__PURE__ */ new Set()
119
+ };
120
+ }
112
121
  function resolveToolInputs(raw) {
113
122
  if (!raw) return {
114
123
  enabled: false,
@@ -161,7 +170,8 @@ function createAccumulatorState(options) {
161
170
  totalDurationMs: void 0,
162
171
  embedding: void 0,
163
172
  costMap: options?.cost,
164
- subscribers: /* @__PURE__ */ new Set()
173
+ subscribers: /* @__PURE__ */ new Set(),
174
+ _flushed: freshWatermarks()
165
175
  };
166
176
  }
167
177
  function computeEstimatedCost(state) {
@@ -173,8 +183,15 @@ function computeEstimatedCost(state) {
173
183
  const total = state.usage.inputTokens / 1e6 * pricing.input + state.usage.outputTokens / 1e6 * pricing.output;
174
184
  return total > 0 ? Math.round(total * 1e6) / 1e6 : void 0;
175
185
  }
176
- function buildMetadata(state) {
177
- const uniqueModels = [...new Set(state.models)];
186
+ /**
187
+ * Build the `ai.*` payload from the accumulator. Pass `since` to emit
188
+ * only the array entries appended after that watermark (used by
189
+ * `flushState` to avoid quadratic growth on the wide event, since evlog's
190
+ * `mergeInto` concatenates arrays). The default — fresh watermarks —
191
+ * yields the full cumulative snapshot used by `getMetadata()` and
192
+ * delivered to `onUpdate` subscribers.
193
+ */
194
+ function buildMetadata(state, since = freshWatermarks()) {
178
195
  const lastModel = state.models[state.models.length - 1];
179
196
  const data = {
180
197
  calls: state.calls,
@@ -184,32 +201,37 @@ function buildMetadata(state) {
184
201
  };
185
202
  if (lastModel) data.model = lastModel;
186
203
  if (state.lastProvider) data.provider = state.lastProvider;
187
- if (uniqueModels.length > 1) data.models = uniqueModels;
188
204
  if (state.usage.cacheReadTokens > 0) data.cacheReadTokens = state.usage.cacheReadTokens;
189
205
  if (state.usage.cacheWriteTokens > 0) data.cacheWriteTokens = state.usage.cacheWriteTokens;
190
206
  if (state.usage.reasoningTokens > 0) data.reasoningTokens = state.usage.reasoningTokens;
191
207
  if (state.lastFinishReason) data.finishReason = state.lastFinishReason;
192
- if (state.toolInputs && state.allToolCallInputs.length > 0) data.toolCalls = state.allToolCallInputs.map((t) => ({ ...t }));
193
- else if (state.allToolCalls.length > 0) data.toolCalls = [...state.allToolCalls];
194
208
  if (state.lastResponseId) data.responseId = state.lastResponseId;
195
- if (state.steps > 1) {
196
- data.steps = state.steps;
197
- data.stepsUsage = state.stepsUsage.map((s) => ({
198
- ...s,
199
- ...s.toolCalls ? { toolCalls: [...s.toolCalls] } : {}
200
- }));
201
- }
202
209
  if (state.lastMsToFirstChunk !== void 0) data.msToFirstChunk = state.lastMsToFirstChunk;
203
210
  if (state.lastMsToFinish !== void 0) {
204
211
  data.msToFinish = state.lastMsToFinish;
205
212
  if (state.usage.outputTokens > 0 && state.lastMsToFinish > 0) data.tokensPerSecond = Math.round(state.usage.outputTokens / state.lastMsToFinish * 1e3);
206
213
  }
207
214
  if (state.lastError) data.error = state.lastError;
208
- if (state.toolExecutions.length > 0) data.tools = state.toolExecutions.map((t) => ({ ...t }));
209
215
  if (state.totalDurationMs !== void 0) data.totalDurationMs = state.totalDurationMs;
210
216
  if (state.embedding) data.embedding = { ...state.embedding };
211
217
  const cost = computeEstimatedCost(state);
212
218
  if (cost !== void 0) data.estimatedCost = cost;
219
+ if (state.toolInputs) {
220
+ if (state.allToolCallInputs.length > since.toolCallInputs) data.toolCalls = state.allToolCallInputs.slice(since.toolCallInputs).map((t) => ({ ...t }));
221
+ } else if (state.allToolCalls.length > since.toolCalls) data.toolCalls = state.allToolCalls.slice(since.toolCalls);
222
+ if (state.steps > 1 && state.stepsUsage.length > since.stepsUsage) {
223
+ data.steps = state.steps;
224
+ data.stepsUsage = state.stepsUsage.slice(since.stepsUsage).map((s) => ({
225
+ ...s,
226
+ ...s.toolCalls ? { toolCalls: [...s.toolCalls] } : {}
227
+ }));
228
+ }
229
+ if (state.toolExecutions.length > since.toolExecutions) data.tools = state.toolExecutions.slice(since.toolExecutions).map((t) => ({ ...t }));
230
+ const uniqueModels = new Set(state.models);
231
+ if (uniqueModels.size > 1) {
232
+ const newModels = [...uniqueModels].filter((m) => !since.models.has(m));
233
+ if (newModels.length > 0) data.models = newModels;
234
+ }
213
235
  return data;
214
236
  }
215
237
  function notifySubscribers(state, metadata) {
@@ -218,10 +240,20 @@ function notifySubscribers(state, metadata) {
218
240
  subscriber(metadata);
219
241
  } catch {}
220
242
  }
243
+ /**
244
+ * Push the accumulator's latest changes onto the wide event using delta
245
+ * semantics for arrays, then notify subscribers with the full snapshot.
246
+ */
221
247
  function flushState(log, state) {
222
- const data = buildMetadata(state);
248
+ const flushed = state._flushed;
249
+ const data = buildMetadata(state, flushed);
250
+ flushed.toolCalls = state.allToolCalls.length;
251
+ flushed.toolCallInputs = state.allToolCallInputs.length;
252
+ flushed.toolExecutions = state.toolExecutions.length;
253
+ if (data.stepsUsage) flushed.stepsUsage = state.stepsUsage.length;
254
+ if (data.models) for (const m of data.models) flushed.models.add(m);
223
255
  log.set({ ai: data });
224
- notifySubscribers(state, data);
256
+ if (state.subscribers.size > 0) notifySubscribers(state, buildMetadata(state));
225
257
  }
226
258
  function recordModel(state, provider, modelId, responseModelId) {
227
259
  const resolved = resolveProviderAndModel(provider, responseModelId ?? modelId);