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.
- package/README.md +7 -7
- package/dist/adapters/axiom.d.mts +1 -1
- package/dist/adapters/axiom.mjs +4 -2
- package/dist/adapters/axiom.mjs.map +1 -1
- package/dist/adapters/better-stack.d.mts +1 -1
- package/dist/adapters/better-stack.mjs +4 -2
- package/dist/adapters/better-stack.mjs.map +1 -1
- package/dist/adapters/datadog.d.mts +1 -1
- package/dist/adapters/datadog.mjs +4 -2
- package/dist/adapters/datadog.mjs.map +1 -1
- package/dist/adapters/fs.d.mts +64 -2
- package/dist/adapters/fs.d.mts.map +1 -1
- package/dist/adapters/fs.mjs +222 -3
- package/dist/adapters/fs.mjs.map +1 -1
- package/dist/adapters/hyperdx.d.mts +1 -1
- package/dist/adapters/hyperdx.mjs +1 -1
- package/dist/adapters/otlp.d.mts +1 -1
- package/dist/adapters/otlp.mjs +6 -4
- package/dist/adapters/otlp.mjs.map +1 -1
- package/dist/adapters/posthog.d.mts +1 -1
- package/dist/adapters/posthog.mjs +4 -2
- package/dist/adapters/posthog.mjs.map +1 -1
- package/dist/adapters/sentry.d.mts +1 -1
- package/dist/adapters/sentry.mjs +5 -3
- package/dist/adapters/sentry.mjs.map +1 -1
- package/dist/ai/index.d.mts +15 -1
- package/dist/ai/index.d.mts.map +1 -1
- package/dist/ai/index.mjs +48 -16
- package/dist/ai/index.mjs.map +1 -1
- package/dist/{audit-CJl-wZ10.d.mts → audit-CC8nfazi.d.mts} +65 -3
- package/dist/{audit-CJl-wZ10.d.mts.map → audit-CC8nfazi.d.mts.map} +1 -1
- package/dist/{audit--n0QRR2Y.mjs → audit-pV5aLGP0.mjs} +2 -1
- package/dist/audit-pV5aLGP0.mjs.map +1 -0
- package/dist/better-auth/index.d.mts +14 -7
- package/dist/better-auth/index.d.mts.map +1 -1
- package/dist/better-auth/index.mjs +11 -1
- package/dist/better-auth/index.mjs.map +1 -1
- package/dist/browser.d.mts +1 -1
- package/dist/{define-Fp8TrdEB.d.mts → define-MSdhzmXn.d.mts} +3 -3
- package/dist/{define-Fp8TrdEB.d.mts.map → define-MSdhzmXn.d.mts.map} +1 -1
- package/dist/{dist-BIlS38vi.mjs → dist-H3GIh-KK.mjs} +1 -1
- package/dist/{dist-BIlS38vi.mjs.map → dist-H3GIh-KK.mjs.map} +1 -1
- package/dist/{drain-ByWUeOQC.mjs → drain-X7_5szSI.mjs} +6 -49
- package/dist/drain-X7_5szSI.mjs.map +1 -0
- package/dist/elysia/index.d.mts +2 -2
- package/dist/elysia/index.mjs +2 -2
- package/dist/{enricher-CLSnrzrr.d.mts → enricher-DxgML6IC.d.mts} +4 -4
- package/dist/{enricher-CLSnrzrr.d.mts.map → enricher-DxgML6IC.d.mts.map} +1 -1
- package/dist/{enricher-BA6viylF.mjs → enricher-N0erZS87.mjs} +2 -2
- package/dist/{enricher-BA6viylF.mjs.map → enricher-N0erZS87.mjs.map} +1 -1
- package/dist/enrichers.d.mts +2 -2
- package/dist/enrichers.mjs +1 -1
- package/dist/{error-C-66_G2M.d.mts → error-CpbbtyXL.d.mts} +7 -2
- package/dist/error-CpbbtyXL.d.mts.map +1 -0
- package/dist/error.d.mts +1 -1
- package/dist/error.mjs +8 -1
- package/dist/error.mjs.map +1 -1
- package/dist/{errors-DQoYsDW1.d.mts → errors-DySW1F9_.d.mts} +2 -2
- package/dist/{errors-DQoYsDW1.d.mts.map → errors-DySW1F9_.d.mts.map} +1 -1
- package/dist/{event-ef-5Dbxg.mjs → event-1BMl7o0k.mjs} +4 -2
- package/dist/event-1BMl7o0k.mjs.map +1 -0
- package/dist/express/index.d.mts +2 -2
- package/dist/express/index.d.mts.map +1 -1
- package/dist/express/index.mjs +5 -6
- package/dist/express/index.mjs.map +1 -1
- package/dist/fastify/index.d.mts +2 -2
- package/dist/fastify/index.mjs +2 -2
- package/dist/{fork-D44V93-K.mjs → fork-8u_zFOJq.mjs} +3 -3
- package/dist/{fork-D44V93-K.mjs.map → fork-8u_zFOJq.mjs.map} +1 -1
- package/dist/hono/index.d.mts +2 -2
- package/dist/hono/index.mjs +1 -1
- package/dist/http-6umVAKDW.mjs +82 -0
- package/dist/http-6umVAKDW.mjs.map +1 -0
- package/dist/http.d.mts +1 -1
- package/dist/http.mjs +1 -0
- package/dist/http.mjs.map +1 -1
- package/dist/index-o1_z4phv.d.mts +213 -0
- package/dist/index-o1_z4phv.d.mts.map +1 -0
- package/dist/index.d.mts +9 -8
- package/dist/index.mjs +210 -2
- package/dist/index.mjs.map +1 -0
- package/dist/{integration-Bz8X6_Lb.mjs → integration-DTZtjSqh.mjs} +2 -2
- package/dist/{integration-Bz8X6_Lb.mjs.map → integration-DTZtjSqh.mjs.map} +1 -1
- package/dist/{logger-Brt5-WMK.d.mts → logger-DntcxxHg.d.mts} +2 -2
- package/dist/{logger-Brt5-WMK.d.mts.map → logger-DntcxxHg.d.mts.map} +1 -1
- package/dist/logger.d.mts +1 -1
- package/dist/logger.mjs +1 -1
- package/dist/{middleware-CGM-bOvE.d.mts → middleware-U-lIAzHg.d.mts} +2 -2
- package/dist/{middleware-CGM-bOvE.d.mts.map → middleware-U-lIAzHg.d.mts.map} +1 -1
- package/dist/nestjs/index.d.mts +2 -2
- package/dist/nestjs/index.d.mts.map +1 -1
- package/dist/nestjs/index.mjs +4 -5
- package/dist/nestjs/index.mjs.map +1 -1
- package/dist/next/client.d.mts +1 -1
- package/dist/next/index.d.mts +4 -4
- package/dist/next/index.mjs +3 -3
- package/dist/next/instrumentation.d.mts +1 -1
- package/dist/next/instrumentation.mjs +1 -1
- package/dist/next/stream.d.mts +29 -0
- package/dist/next/stream.d.mts.map +1 -0
- package/dist/next/stream.mjs +78 -0
- package/dist/next/stream.mjs.map +1 -0
- package/dist/nitro/errorHandler.mjs +1 -1
- package/dist/nitro/module.d.mts +2 -2
- package/dist/nitro/plugin.mjs +12 -3
- package/dist/nitro/plugin.mjs.map +1 -1
- package/dist/nitro/v3/errorHandler.mjs +2 -2
- package/dist/nitro/v3/index.d.mts +2 -2
- package/dist/nitro/v3/module.d.mts +1 -1
- package/dist/nitro/v3/plugin.mjs +4 -4
- package/dist/nitro/v3/useLogger.d.mts +1 -1
- package/dist/{nitro-DavLelNz.mjs → nitro-DErMq_Zj.mjs} +1 -1
- package/dist/{nitro-DavLelNz.mjs.map → nitro-DErMq_Zj.mjs.map} +1 -1
- package/dist/{nitro-DHPb9dXG.d.mts → nitro-oZre8ab3.d.mts} +2 -2
- package/dist/{nitro-DHPb9dXG.d.mts.map → nitro-oZre8ab3.d.mts.map} +1 -1
- package/dist/{nitroConfigBridge-aZ1e5upQ.mjs → nitroConfigBridge-DKk7eOn-.mjs} +1 -1
- package/dist/{nitroConfigBridge-aZ1e5upQ.mjs.map → nitroConfigBridge-DKk7eOn-.mjs.map} +1 -1
- package/dist/nodeResponse-BkkionWl.mjs +42 -0
- package/dist/nodeResponse-BkkionWl.mjs.map +1 -0
- package/dist/nuxt/module.d.mts +28 -1
- package/dist/nuxt/module.d.mts.map +1 -1
- package/dist/nuxt/module.mjs +11 -4
- package/dist/nuxt/module.mjs.map +1 -1
- package/dist/package-v_MmOZeA.mjs +7 -0
- package/dist/package-v_MmOZeA.mjs.map +1 -0
- package/dist/{parseError-B1zJZvQ5.d.mts → parseError-yVZ58wIK.d.mts} +2 -2
- package/dist/parseError-yVZ58wIK.d.mts.map +1 -0
- package/dist/react-router/index.d.mts +2 -2
- package/dist/react-router/index.mjs +2 -2
- package/dist/{routes-B48wm7Pb.mjs → routes-CnIgYWf8.mjs} +1 -1
- package/dist/{routes-B48wm7Pb.mjs.map → routes-CnIgYWf8.mjs.map} +1 -1
- package/dist/runtime/client/log.d.mts +1 -1
- package/dist/runtime/server/routes/_evlog/ingest.post.mjs +1 -1
- package/dist/runtime/server/routes/_evlog/stream-info.get.d.mts +18 -0
- package/dist/runtime/server/routes/_evlog/stream-info.get.d.mts.map +1 -0
- package/dist/runtime/server/routes/_evlog/stream-info.get.mjs +28 -0
- package/dist/runtime/server/routes/_evlog/stream-info.get.mjs.map +1 -0
- package/dist/runtime/server/useLogger.d.mts +1 -1
- package/dist/runtime/utils/parseError.d.mts +2 -2
- package/dist/runtime/utils/parseError.mjs +8 -0
- package/dist/runtime/utils/parseError.mjs.map +1 -1
- package/dist/{severity-BYWZ96Sb.mjs → severity-R5Egq3qz.mjs} +1 -1
- package/dist/{severity-BYWZ96Sb.mjs.map → severity-R5Egq3qz.mjs.map} +1 -1
- package/dist/{storage-BT-3fT1-.mjs → storage-Dwinmg8P.mjs} +1 -1
- package/dist/{storage-BT-3fT1-.mjs.map → storage-Dwinmg8P.mjs.map} +1 -1
- package/dist/stream.d.mts +185 -0
- package/dist/stream.d.mts.map +1 -0
- package/dist/stream.mjs +374 -0
- package/dist/stream.mjs.map +1 -0
- package/dist/sveltekit/index.d.mts +2 -2
- package/dist/sveltekit/index.mjs +3 -3
- package/dist/toolkit.d.mts +43 -8
- package/dist/toolkit.d.mts.map +1 -1
- package/dist/toolkit.mjs +11 -10
- package/dist/types.d.mts +2 -2
- package/dist/{useLogger-Cb1R6bQE.d.mts → useLogger-BsPL4AQm.d.mts} +2 -2
- package/dist/{useLogger-Cb1R6bQE.d.mts.map → useLogger-BsPL4AQm.d.mts.map} +1 -1
- package/dist/{utils-gQCeZMbg.d.mts → utils-DLCeShxL.d.mts} +2 -2
- package/dist/{utils-gQCeZMbg.d.mts.map → utils-DLCeShxL.d.mts.map} +1 -1
- package/dist/utils.d.mts +1 -1
- package/dist/vite/index.d.mts +1 -1
- package/dist/workers.d.mts +1 -1
- package/dist/workers.mjs +1 -1
- package/package.json +17 -1
- package/dist/audit--n0QRR2Y.mjs.map +0 -1
- package/dist/drain-ByWUeOQC.mjs.map +0 -1
- package/dist/error-C-66_G2M.d.mts.map +0 -1
- package/dist/event-ef-5Dbxg.mjs.map +0 -1
- package/dist/parseError-B1zJZvQ5.d.mts.map +0 -1
package/dist/adapters/fs.mjs.map
CHANGED
|
@@ -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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"}
|
package/dist/adapters/otlp.d.mts
CHANGED
package/dist/adapters/otlp.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as
|
|
3
|
-
import { n as
|
|
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":";;;;AA8DA,MAAM,cAAyC;CAC7C;EAAE,KAAK;EAAY,KAAK,CAAC,sBAAsB,8BAA8B;EAAE;CAC/E;EAAE,KAAK;EAAe,KAAK,CAAC,0BAA0B,oBAAoB;EAAE;CAC5E,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,sBAAsB;CAC7B,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;AAID,MAAM,mBAAmB;;;;AAKzB,SAAgB,gBAAgB,OAAiC;CAC/D,MAAM,YAAY,IAAI,KAAK,MAAM,UAAU,CAAC,SAAS,GAAG;CAGxD,MAAM,EAAE,OAAO,SAAS,QAAQ,GAAG,SAAS;AAE5C,QAAQ,KAAiC;AACzC,QAAQ,KAAiC;AACzC,QAAQ,KAAiC;AACzC,QAAQ,KAAiC;AACzC,QAAQ,KAAiC;AACzC,QAAQ,KAAiC;CAEzC,MAAM,aAA0C,EAAE;AAGlD,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,CAC7C,KAAI,UAAU,KAAA,KAAa,UAAU,KACnC,YAAW,KAAK;EACd;EACA,OAAO,iBAAiB,MAAM;EAC/B,CAAC;CAIN,MAAM,SAAwB;EAC5B,cAAc,OAAO,UAAU;EAC/B,gBAAgB,qBAAqB,UAAU;EAC/C,cAAc,mBAAmB,UAAU;EAC3C,MAAM,EAAE,aAAa,KAAK,UAAU,MAAM,EAAE;EAC5C;EACD;AAGD,KAAI,OAAO,YAAY,SACrB,QAAO,UAAU;AAEnB,KAAI,OAAO,WAAW,SACpB,QAAO,SAAS;AAGlB,QAAO;;;;;AAMT,SAAS,wBACP,OACA,QAC4B;CAC5B,MAAM,aAAyC,EAAE;AAGjD,YAAW,KAAK;EACd,KAAK;EACL,OAAO,EAAE,aAAa,OAAO,eAAe,MAAM,SAAS;EAC5D,CAAC;AAGF,KAAI,MAAM,YACR,YAAW,KAAK;EACd,KAAK;EACL,OAAO,EAAE,aAAa,MAAM,aAAa;EAC1C,CAAC;AAIJ,KAAI,MAAM,QACR,YAAW,KAAK;EACd,KAAK;EACL,OAAO,EAAE,aAAa,MAAM,SAAS;EACtC,CAAC;AAIJ,KAAI,MAAM,OACR,YAAW,KAAK;EACd,KAAK;EACL,OAAO,EAAE,aAAa,MAAM,QAAQ;EACrC,CAAC;AAIJ,KAAI,MAAM,WACR,YAAW,KAAK;EACd,KAAK;EACL,OAAO,EAAE,aAAa,MAAM,YAAY;EACzC,CAAC;AAIJ,KAAI,OAAO,mBACT,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,mBAAmB,CAClE,YAAW,KAAK;EACd;EACA,OAAO,iBAAiB,MAAM;EAC/B,CAAC;AAIN,QAAO;;;;;;AAOT,SAAS,oBAAwD;CAC/D,MAAM,aAAa,QAAQ,IAAI,8BAA8B,QAAQ,IAAI;AACzE,KAAI,YAAY;EACd,MAAM,UAAkC,EAAE;EAC1C,MAAM,UAAU,mBAAmB,WAAW;AAC9C,OAAK,MAAM,QAAQ,QAAQ,MAAM,IAAI,EAAE;GACrC,MAAM,UAAU,KAAK,QAAQ,IAAI;AACjC,OAAI,UAAU,GAAG;IACf,MAAM,MAAM,KAAK,MAAM,GAAG,QAAQ,CAAC,MAAM;IACzC,MAAM,QAAQ,KAAK,MAAM,UAAU,EAAE,CAAC,MAAM;AAC5C,QAAI,OAAO,MACT,SAAQ,OAAO;;;AAIrB,MAAI,OAAO,KAAK,QAAQ,CAAC,SAAS,EAAG,QAAO;;CAG9C,MAAM,OAAO,QAAQ,IAAI;AACzB,KAAI,KACF,QAAO,EAAE,eAAe,MAAM;;;;;;;;;;;;;;;;;;;;;;AA0BlC,SAAgB,gBAAgB,WAAiC;AAC/D,QAAO,gBAA4B;EACjC,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBAAiC,QAAQ,aAAa,UAAU;AAGrF,OAAI,CAAC,OAAO,QACV,QAAO,UAAU,mBAAmB;AAGtC,OAAI,CAAC,OAAO,UAAU;AACpB,YAAQ,MAAM,6HAA6H;AAC3I,WAAO;;AAET,UAAO;;EAET,SAAS,QAAQ,WAAW;AAC1B,OAAI,OAAO,WAAW,EAAG,QAAO;AAChC,UAAO;IACL,KAAK,GAAG,OAAO,SAAS,QAAQ,OAAO,GAAG,CAAC;IAC3C,SAAS;KACP,gBAAgB;KAChB,GAAG,OAAO;KACX;IACD,MAAM,KAAK,UAAU,iBAAiB,QAAQ,OAAO,CAAC;IACvD;;EAEJ,CAAC;;AAGJ,SAAS,iBAAiB,QAAqB,QAA8C;CAC3F,MAAM,0BAAU,IAAI,KAA0B;AAC9C,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,MAAM,GAAG,MAAM,QAAQ,IAAI,MAAM;EACvC,MAAM,QAAQ,QAAQ,IAAI,IAAI;AAC9B,MAAI,MAAO,OAAM,KAAK,MAAM;MACvB,SAAQ,IAAI,KAAK,CAAC,MAAM,CAAC;;AAEhC,QAAO,EACL,cAAc,MAAM,KAAK,QAAQ,QAAQ,CAAC,CAAC,KAAI,iBAAgB;EAC7D,UAAU,EAAE,YAAY,wBAAwB,YAAY,IAAK,OAAO,EAAE;EAC1E,WAAW,CACT;GACE,OAAO;IAAE,MAAM;IAAS,SAAS;IAAS;GAC1C,YAAY,YAAY,IAAI,gBAAgB;GAC7C,CACF;EACF,EAAE,EACJ;;;;;;;;;;;;AAaH,eAAsB,WAAW,OAAkB,QAAmC;AACpF,OAAM,gBAAgB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;AAaxC,eAAsB,gBAAgB,QAAqB,QAAmC;AAC5F,KAAI,OAAO,WAAW,EAAG;CAEzB,MAAM,MAAM,GAAG,OAAO,SAAS,QAAQ,OAAO,GAAG,CAAC;CAClD,MAAM,UAAU,iBAAiB,QAAQ,OAAO;AAOhD,OAAM,SAAS;EACb;EACA,SAAA;GANA,gBAAgB;GAChB,GAAG,OAAO;GAKH;EACP,MAAM,KAAK,UAAU,QAAQ;EAC7B,SAAS,OAAO,WAAW;EAC3B,SAAS,OAAO;EAChB,OAAO;EACR,CAAC"}
|
|
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,5 @@
|
|
|
1
|
-
import {
|
|
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":"
|
|
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"}
|
package/dist/adapters/sentry.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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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"}
|
package/dist/ai/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
}
|
package/dist/ai/index.d.mts.map
CHANGED
|
@@ -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
|
|
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
|
-
|
|
177
|
-
|
|
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
|
|
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,
|
|
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);
|