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/README.md
CHANGED
|
@@ -571,7 +571,7 @@ When enabled:
|
|
|
571
571
|
3. `evlog:drain` hook is called with `source: 'client'`
|
|
572
572
|
4. External services receive the log
|
|
573
573
|
|
|
574
|
-
For a **framework-agnostic** batched HTTP drain (e.g. vanilla JS or custom endpoints), use `createHttpLogDrain` from [`evlog/http`](https://www.evlog.dev/
|
|
574
|
+
For a **framework-agnostic** batched HTTP drain (e.g. vanilla JS or custom endpoints), use `createHttpLogDrain` from [`evlog/http`](https://www.evlog.dev/extend/drain-pipeline#http-drain-browser-to-server). The legacy import path `evlog/browser` is deprecated and will be removed in the next major release.
|
|
575
575
|
|
|
576
576
|
## Structured Errors
|
|
577
577
|
|
|
@@ -758,7 +758,7 @@ export default defineEventHandler(async (event) => {
|
|
|
758
758
|
|
|
759
759
|
`AuditFields` is exported and merges with `BaseWideEvent` — augment it with `declare module` if you need extra typed fields. Audit events are always force-kept by tail sampling and get a deterministic `idempotencyKey` so retries are safe across drains.
|
|
760
760
|
|
|
761
|
-
See [the Audit Logs guide](https://evlog.dev/
|
|
761
|
+
See [the Audit Logs guide](https://evlog.dev/use-cases/audit/overview) for compliance, GDPR, and recipe details.
|
|
762
762
|
|
|
763
763
|
## AI SDK Integration
|
|
764
764
|
|
|
@@ -865,7 +865,7 @@ NUXT_DATADOG_SITE=datadoghq.eu
|
|
|
865
865
|
|
|
866
866
|
You can also use standard Datadog names: `DD_API_KEY` and `DD_SITE`.
|
|
867
867
|
|
|
868
|
-
Wide events are sent with a short **`message` line** (method, path, level) and full context under the **`evlog`** attribute (facets like `@evlog.path`). See the [Datadog adapter docs](https://www.evlog.dev/adapters/datadog).
|
|
868
|
+
Wide events are sent with a short **`message` line** (method, path, level) and full context under the **`evlog`** attribute (facets like `@evlog.path`). See the [Datadog adapter docs](https://www.evlog.dev/integrate/adapters/datadog).
|
|
869
869
|
|
|
870
870
|
### PostHog
|
|
871
871
|
|
|
@@ -955,7 +955,7 @@ export default defineNitroPlugin((nitroApp) => {
|
|
|
955
955
|
})
|
|
956
956
|
```
|
|
957
957
|
|
|
958
|
-
> See the [full documentation](https://evlog.
|
|
958
|
+
> See the [full documentation](https://www.evlog.dev/integrate/adapters/overview) for adapter configuration options, troubleshooting, and advanced patterns.
|
|
959
959
|
|
|
960
960
|
## Drain Pipeline
|
|
961
961
|
|
|
@@ -1163,7 +1163,7 @@ The framework emits **one wide event per HTTP request** when the response finish
|
|
|
1163
1163
|
| Express, Fastify, NestJS, SvelteKit, React Router, Elysia | Yes |
|
|
1164
1164
|
| Next.js `withEvlog` | Yes |
|
|
1165
1165
|
| Hono (`c.get('log')` only) | Not yet |
|
|
1166
|
-
| Nitro / Nuxt `useLogger(event)` | Not yet — use post-emit warnings; see [Wide events](https://evlog.dev/
|
|
1166
|
+
| Nitro / Nuxt `useLogger(event)` | Not yet — use post-emit warnings; see [Wide events](https://evlog.dev/learn/wide-events) |
|
|
1167
1167
|
|
|
1168
1168
|
```typescript
|
|
1169
1169
|
import { evlog, useLogger } from 'evlog/express'
|
|
@@ -1298,14 +1298,14 @@ try {
|
|
|
1298
1298
|
| **Fastify** | `app.register(evlog)` with `import { evlog } from 'evlog/fastify'` ([example](./examples/fastify)) |
|
|
1299
1299
|
| **Elysia** | `.use(evlog())` with `import { evlog } from 'evlog/elysia'` ([example](./examples/elysia)) |
|
|
1300
1300
|
| **Cloudflare Workers** | Manual setup with `import { initWorkersLogger, createWorkersLogger } from 'evlog/workers'` ([example](./examples/workers)) |
|
|
1301
|
-
| **Custom** | Build your own with `import { createMiddlewareLogger } from 'evlog/toolkit'` ([guide](https://evlog.dev/
|
|
1301
|
+
| **Custom** | Build your own with `import { createMiddlewareLogger } from 'evlog/toolkit'` ([guide](https://evlog.dev/extend/custom-framework)) |
|
|
1302
1302
|
| **Analog** | Nitro v2 module setup |
|
|
1303
1303
|
| **Vinxi** | Nitro v2 module setup |
|
|
1304
1304
|
| **SolidStart** | Nitro v2 module setup ([example](./examples/solidstart)) |
|
|
1305
1305
|
|
|
1306
1306
|
## Agent Skills
|
|
1307
1307
|
|
|
1308
|
-
evlog provides [Agent Skills](https://www.evlog.dev/
|
|
1308
|
+
evlog provides [Agent Skills](https://www.evlog.dev/reference/agent-skills) to help AI coding assistants understand and implement proper logging patterns in your codebase.
|
|
1309
1309
|
|
|
1310
1310
|
### Installation
|
|
1311
1311
|
|
package/dist/adapters/axiom.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as httpPost } from "../http-6umVAKDW.mjs";
|
|
2
|
+
import { i as resolveAdapterConfig, n as defineHttpDrain } from "../drain-X7_5szSI.mjs";
|
|
2
3
|
//#region src/adapters/axiom.ts
|
|
3
4
|
const AXIOM_FIELDS = [
|
|
4
5
|
{
|
|
@@ -111,7 +112,8 @@ async function sendBatchToAxiom(events, config) {
|
|
|
111
112
|
body: JSON.stringify(events),
|
|
112
113
|
timeout: config.timeout ?? 5e3,
|
|
113
114
|
retries: config.retries,
|
|
114
|
-
label: "Axiom"
|
|
115
|
+
label: "Axiom",
|
|
116
|
+
source: "axiom"
|
|
115
117
|
});
|
|
116
118
|
}
|
|
117
119
|
function resolveIngestUrl(config) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"axiom.mjs","names":[],"sources":["../../src/adapters/axiom.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'\n\ninterface BaseAxiomConfig {\n /** Axiom dataset name. */\n dataset: string\n /**\n * Axiom API key.\n *\n * @example `xaat-...`\n */\n apiKey: string\n /**\n * @deprecated Renamed to {@link BaseAxiomConfig.apiKey}. Will be removed in\n * the next major version. Pass `apiKey` instead.\n */\n token?: string\n /** Organization ID (required for Personal Access Tokens). */\n orgId?: 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\ninterface EdgeAxiomConfig {\n /**\n * Edge URL for Axiom ingest/query endpoints.\n * If no path is provided, uses /v1/ingest/{dataset}.\n * If a custom path is provided, it is used as-is (trailing slash trimmed).\n */\n edgeUrl: string\n /** Mutually exclusive with edgeUrl. */\n baseUrl?: never\n}\n\ninterface EndpointAxiomConfig {\n /** Base URL for Axiom API. Uses /v1/datasets/{dataset}/ingest. */\n baseUrl?: string\n /** Mutually exclusive with baseUrl. */\n edgeUrl?: never\n}\n\nexport type AxiomConfig = BaseAxiomConfig & (EdgeAxiomConfig | EndpointAxiomConfig)\n\ntype ResolvedAxiomConfig = BaseAxiomConfig & {\n edgeUrl?: string\n baseUrl?: string\n}\n\nconst AXIOM_FIELDS: ConfigField<ResolvedAxiomConfig>[] = [\n { key: 'dataset', env: ['NUXT_AXIOM_DATASET', 'AXIOM_DATASET'] },\n { key: 'apiKey', env: ['NUXT_AXIOM_API_KEY', 'AXIOM_API_KEY'] },\n // Deprecated env var names — resolved as a fallback for `apiKey` below.\n { key: 'token', env: ['NUXT_AXIOM_TOKEN', 'AXIOM_TOKEN'] },\n { key: 'orgId', env: ['NUXT_AXIOM_ORG_ID', 'AXIOM_ORG_ID'] },\n { key: 'edgeUrl', env: ['NUXT_AXIOM_EDGE_URL', 'AXIOM_EDGE_URL'] },\n { key: 'baseUrl', env: ['NUXT_AXIOM_URL', 'AXIOM_URL'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nlet warnedAboutToken = false\n\nfunction applyApiKeyAlias(config: ResolvedAxiomConfig): ResolvedAxiomConfig {\n if (!config.apiKey && config.token) {\n if (!warnedAboutToken) {\n warnedAboutToken = true\n console.warn('[evlog/axiom] `token` is deprecated, use `apiKey` instead. (Env: NUXT_AXIOM_TOKEN/AXIOM_TOKEN → NUXT_AXIOM_API_KEY/AXIOM_API_KEY.)')\n }\n config.apiKey = config.token\n }\n return config\n}\n\n/**\n * Create a drain function for sending logs to Axiom.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createAxiomDrain()\n * 2. runtimeConfig.evlog.axiom\n * 3. runtimeConfig.axiom\n * 4. Environment variables: NUXT_AXIOM_API_KEY, AXIOM_API_KEY (or legacy `*_TOKEN`)\n *\n * @example\n * ```ts\n * // Zero config — set NUXT_AXIOM_API_KEY and NUXT_AXIOM_DATASET\n * initLogger({ drain: createAxiomDrain() })\n *\n * // With overrides\n * initLogger({ drain: createAxiomDrain({ dataset: 'my-dataset' }) })\n * ```\n */\nexport function createAxiomDrain(overrides?: Partial<AxiomConfig>) {\n return defineHttpDrain<AxiomConfig>({\n name: 'axiom',\n resolve: async () => {\n const resolved = await resolveAdapterConfig<ResolvedAxiomConfig>(\n 'axiom',\n AXIOM_FIELDS,\n overrides as Partial<ResolvedAxiomConfig>,\n )\n const config = applyApiKeyAlias(resolved)\n if (!config.dataset || !config.apiKey) {\n console.error('[evlog/axiom] Missing dataset or apiKey. Set NUXT_AXIOM_API_KEY/NUXT_AXIOM_DATASET env vars or pass to createAxiomDrain()')\n return null\n }\n if (config.edgeUrl && config.baseUrl) {\n console.warn('[evlog/axiom] Both edgeUrl and baseUrl are set. edgeUrl takes precedence for ingest.')\n delete config.baseUrl\n }\n return config as AxiomConfig\n },\n encode: (events, config) => {\n const url = resolveIngestUrl(config)\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n }\n if (config.orgId) headers['X-Axiom-Org-Id'] = config.orgId\n return { url, headers, body: JSON.stringify(events) }\n },\n })\n}\n\n/**\n * Send a single event to Axiom.\n */\nexport async function sendToAxiom(event: WideEvent, config: AxiomConfig): Promise<void> {\n await sendBatchToAxiom([event], config)\n}\n\n/**\n * Send a batch of events to Axiom.\n */\nexport async function sendBatchToAxiom(events: WideEvent[], config: AxiomConfig): Promise<void> {\n const apiKey = config.apiKey ?? config.token\n if (!apiKey) {\n throw new Error('[evlog/axiom] Missing apiKey')\n }\n const url = resolveIngestUrl(config)\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n }\n if (config.orgId) headers['X-Axiom-Org-Id'] = config.orgId\n await httpPost({\n url,\n headers,\n body: JSON.stringify(events),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Axiom',\n })\n}\n\nfunction resolveIngestUrl(config: AxiomConfig): string {\n const encodedDataset = encodeURIComponent(config.dataset)\n\n if (!config.edgeUrl) {\n const baseUrl = config.baseUrl ?? 'https://api.axiom.co'\n return `${baseUrl}/v1/datasets/${encodedDataset}/ingest`\n }\n\n try {\n const parsed = new URL(config.edgeUrl)\n if (parsed.pathname === '' || parsed.pathname === '/') {\n parsed.pathname = `/v1/ingest/${encodedDataset}`\n return parsed.toString()\n }\n parsed.pathname = parsed.pathname.replace(/\\/+$/, '')\n return parsed.toString()\n } catch {\n console.warn(`[evlog/axiom] edgeUrl \"${config.edgeUrl}\" is not a valid URL, falling back to string concatenation.`)\n const trimmed = config.edgeUrl.replace(/\\/+$/, '')\n return `${trimmed}/v1/ingest/${encodedDataset}`\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"axiom.mjs","names":[],"sources":["../../src/adapters/axiom.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'\n\ninterface BaseAxiomConfig {\n /** Axiom dataset name. */\n dataset: string\n /**\n * Axiom API key.\n *\n * @example `xaat-...`\n */\n apiKey: string\n /**\n * @deprecated Renamed to {@link BaseAxiomConfig.apiKey}. Will be removed in\n * the next major version. Pass `apiKey` instead.\n */\n token?: string\n /** Organization ID (required for Personal Access Tokens). */\n orgId?: 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\ninterface EdgeAxiomConfig {\n /**\n * Edge URL for Axiom ingest/query endpoints.\n * If no path is provided, uses /v1/ingest/{dataset}.\n * If a custom path is provided, it is used as-is (trailing slash trimmed).\n */\n edgeUrl: string\n /** Mutually exclusive with edgeUrl. */\n baseUrl?: never\n}\n\ninterface EndpointAxiomConfig {\n /** Base URL for Axiom API. Uses /v1/datasets/{dataset}/ingest. */\n baseUrl?: string\n /** Mutually exclusive with baseUrl. */\n edgeUrl?: never\n}\n\nexport type AxiomConfig = BaseAxiomConfig & (EdgeAxiomConfig | EndpointAxiomConfig)\n\ntype ResolvedAxiomConfig = BaseAxiomConfig & {\n edgeUrl?: string\n baseUrl?: string\n}\n\nconst AXIOM_FIELDS: ConfigField<ResolvedAxiomConfig>[] = [\n { key: 'dataset', env: ['NUXT_AXIOM_DATASET', 'AXIOM_DATASET'] },\n { key: 'apiKey', env: ['NUXT_AXIOM_API_KEY', 'AXIOM_API_KEY'] },\n // Deprecated env var names — resolved as a fallback for `apiKey` below.\n { key: 'token', env: ['NUXT_AXIOM_TOKEN', 'AXIOM_TOKEN'] },\n { key: 'orgId', env: ['NUXT_AXIOM_ORG_ID', 'AXIOM_ORG_ID'] },\n { key: 'edgeUrl', env: ['NUXT_AXIOM_EDGE_URL', 'AXIOM_EDGE_URL'] },\n { key: 'baseUrl', env: ['NUXT_AXIOM_URL', 'AXIOM_URL'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nlet warnedAboutToken = false\n\nfunction applyApiKeyAlias(config: ResolvedAxiomConfig): ResolvedAxiomConfig {\n if (!config.apiKey && config.token) {\n if (!warnedAboutToken) {\n warnedAboutToken = true\n console.warn('[evlog/axiom] `token` is deprecated, use `apiKey` instead. (Env: NUXT_AXIOM_TOKEN/AXIOM_TOKEN → NUXT_AXIOM_API_KEY/AXIOM_API_KEY.)')\n }\n config.apiKey = config.token\n }\n return config\n}\n\n/**\n * Create a drain function for sending logs to Axiom.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createAxiomDrain()\n * 2. runtimeConfig.evlog.axiom\n * 3. runtimeConfig.axiom\n * 4. Environment variables: NUXT_AXIOM_API_KEY, AXIOM_API_KEY (or legacy `*_TOKEN`)\n *\n * @example\n * ```ts\n * // Zero config — set NUXT_AXIOM_API_KEY and NUXT_AXIOM_DATASET\n * initLogger({ drain: createAxiomDrain() })\n *\n * // With overrides\n * initLogger({ drain: createAxiomDrain({ dataset: 'my-dataset' }) })\n * ```\n */\nexport function createAxiomDrain(overrides?: Partial<AxiomConfig>) {\n return defineHttpDrain<AxiomConfig>({\n name: 'axiom',\n resolve: async () => {\n const resolved = await resolveAdapterConfig<ResolvedAxiomConfig>(\n 'axiom',\n AXIOM_FIELDS,\n overrides as Partial<ResolvedAxiomConfig>,\n )\n const config = applyApiKeyAlias(resolved)\n if (!config.dataset || !config.apiKey) {\n console.error('[evlog/axiom] Missing dataset or apiKey. Set NUXT_AXIOM_API_KEY/NUXT_AXIOM_DATASET env vars or pass to createAxiomDrain()')\n return null\n }\n if (config.edgeUrl && config.baseUrl) {\n console.warn('[evlog/axiom] Both edgeUrl and baseUrl are set. edgeUrl takes precedence for ingest.')\n delete config.baseUrl\n }\n return config as AxiomConfig\n },\n encode: (events, config) => {\n const url = resolveIngestUrl(config)\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n }\n if (config.orgId) headers['X-Axiom-Org-Id'] = config.orgId\n return { url, headers, body: JSON.stringify(events) }\n },\n })\n}\n\n/**\n * Send a single event to Axiom.\n */\nexport async function sendToAxiom(event: WideEvent, config: AxiomConfig): Promise<void> {\n await sendBatchToAxiom([event], config)\n}\n\n/**\n * Send a batch of events to Axiom.\n */\nexport async function sendBatchToAxiom(events: WideEvent[], config: AxiomConfig): Promise<void> {\n const apiKey = config.apiKey ?? config.token\n if (!apiKey) {\n throw new Error('[evlog/axiom] Missing apiKey')\n }\n const url = resolveIngestUrl(config)\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n }\n if (config.orgId) headers['X-Axiom-Org-Id'] = config.orgId\n await httpPost({\n url,\n headers,\n body: JSON.stringify(events),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Axiom',\n source: 'axiom',\n })\n}\n\nfunction resolveIngestUrl(config: AxiomConfig): string {\n const encodedDataset = encodeURIComponent(config.dataset)\n\n if (!config.edgeUrl) {\n const baseUrl = config.baseUrl ?? 'https://api.axiom.co'\n return `${baseUrl}/v1/datasets/${encodedDataset}/ingest`\n }\n\n try {\n const parsed = new URL(config.edgeUrl)\n if (parsed.pathname === '' || parsed.pathname === '/') {\n parsed.pathname = `/v1/ingest/${encodedDataset}`\n return parsed.toString()\n }\n parsed.pathname = parsed.pathname.replace(/\\/+$/, '')\n return parsed.toString()\n } catch {\n console.warn(`[evlog/axiom] edgeUrl \"${config.edgeUrl}\" is not a valid URL, falling back to string concatenation.`)\n const trimmed = config.edgeUrl.replace(/\\/+$/, '')\n return `${trimmed}/v1/ingest/${encodedDataset}`\n }\n}\n"],"mappings":";;;AAqDA,MAAM,eAAmD;CACvD;EAAE,KAAK;EAAW,KAAK,CAAC,sBAAsB,gBAAgB;EAAE;CAChE;EAAE,KAAK;EAAU,KAAK,CAAC,sBAAsB,gBAAgB;EAAE;CAE/D;EAAE,KAAK;EAAS,KAAK,CAAC,oBAAoB,cAAc;EAAE;CAC1D;EAAE,KAAK;EAAS,KAAK,CAAC,qBAAqB,eAAe;EAAE;CAC5D;EAAE,KAAK;EAAW,KAAK,CAAC,uBAAuB,iBAAiB;EAAE;CAClE;EAAE,KAAK;EAAW,KAAK,CAAC,kBAAkB,YAAY;EAAE;CACxD,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;AAED,IAAI,mBAAmB;AAEvB,SAAS,iBAAiB,QAAkD;AAC1E,KAAI,CAAC,OAAO,UAAU,OAAO,OAAO;AAClC,MAAI,CAAC,kBAAkB;AACrB,sBAAmB;AACnB,WAAQ,KAAK,qIAAqI;;AAEpJ,SAAO,SAAS,OAAO;;AAEzB,QAAO;;;;;;;;;;;;;;;;;;;;AAqBT,SAAgB,iBAAiB,WAAkC;AACjE,QAAO,gBAA6B;EAClC,MAAM;EACN,SAAS,YAAY;GAMnB,MAAM,SAAS,iBAAiB,MALT,qBACrB,SACA,cACA,UACD,CACwC;AACzC,OAAI,CAAC,OAAO,WAAW,CAAC,OAAO,QAAQ;AACrC,YAAQ,MAAM,4HAA4H;AAC1I,WAAO;;AAET,OAAI,OAAO,WAAW,OAAO,SAAS;AACpC,YAAQ,KAAK,uFAAuF;AACpG,WAAO,OAAO;;AAEhB,UAAO;;EAET,SAAS,QAAQ,WAAW;GAC1B,MAAM,MAAM,iBAAiB,OAAO;GACpC,MAAM,UAAkC;IACtC,gBAAgB;IAChB,iBAAiB,UAAU,OAAO;IACnC;AACD,OAAI,OAAO,MAAO,SAAQ,oBAAoB,OAAO;AACrD,UAAO;IAAE;IAAK;IAAS,MAAM,KAAK,UAAU,OAAO;IAAE;;EAExD,CAAC;;;;;AAMJ,eAAsB,YAAY,OAAkB,QAAoC;AACtF,OAAM,iBAAiB,CAAC,MAAM,EAAE,OAAO;;;;;AAMzC,eAAsB,iBAAiB,QAAqB,QAAoC;CAC9F,MAAM,SAAS,OAAO,UAAU,OAAO;AACvC,KAAI,CAAC,OACH,OAAM,IAAI,MAAM,+BAA+B;CAEjD,MAAM,MAAM,iBAAiB,OAAO;CACpC,MAAM,UAAkC;EACtC,gBAAgB;EAChB,iBAAiB,UAAU;EAC5B;AACD,KAAI,OAAO,MAAO,SAAQ,oBAAoB,OAAO;AACrD,OAAM,SAAS;EACb;EACA;EACA,MAAM,KAAK,UAAU,OAAO;EAC5B,SAAS,OAAO,WAAW;EAC3B,SAAS,OAAO;EAChB,OAAO;EACP,QAAQ;EACT,CAAC;;AAGJ,SAAS,iBAAiB,QAA6B;CACrD,MAAM,iBAAiB,mBAAmB,OAAO,QAAQ;AAEzD,KAAI,CAAC,OAAO,QAEV,QAAO,GADS,OAAO,WAAW,uBAChB,eAAe,eAAe;AAGlD,KAAI;EACF,MAAM,SAAS,IAAI,IAAI,OAAO,QAAQ;AACtC,MAAI,OAAO,aAAa,MAAM,OAAO,aAAa,KAAK;AACrD,UAAO,WAAW,cAAc;AAChC,UAAO,OAAO,UAAU;;AAE1B,SAAO,WAAW,OAAO,SAAS,QAAQ,QAAQ,GAAG;AACrD,SAAO,OAAO,UAAU;SAClB;AACN,UAAQ,KAAK,0BAA0B,OAAO,QAAQ,6DAA6D;AAEnH,SAAO,GADS,OAAO,QAAQ,QAAQ,QAAQ,GAC9B,CAAC,aAAa"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { I as DrainContext, ct as WideEvent } from "../audit-CC8nfazi.mjs";
|
|
2
2
|
//#region src/adapters/better-stack.d.ts
|
|
3
3
|
interface BetterStackConfig {
|
|
4
4
|
/** Better Stack API key (replaces deprecated `sourceToken`). */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as httpPost } from "../http-6umVAKDW.mjs";
|
|
2
|
+
import { i as resolveAdapterConfig, n as defineHttpDrain } from "../drain-X7_5szSI.mjs";
|
|
2
3
|
//#region src/adapters/better-stack.ts
|
|
3
4
|
const BETTER_STACK_FIELDS = [
|
|
4
5
|
{
|
|
@@ -96,7 +97,8 @@ async function sendBatchToBetterStack(events, config) {
|
|
|
96
97
|
body: JSON.stringify(events.map(toBetterStackEvent)),
|
|
97
98
|
timeout: config.timeout ?? 5e3,
|
|
98
99
|
retries: config.retries,
|
|
99
|
-
label: "Better Stack"
|
|
100
|
+
label: "Better Stack",
|
|
101
|
+
source: "better-stack"
|
|
100
102
|
});
|
|
101
103
|
}
|
|
102
104
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"better-stack.mjs","names":[],"sources":["../../src/adapters/better-stack.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineHttpDrain } from '../shared/drain'\nimport { httpPost } from '../shared/http'\n\nexport interface BetterStackConfig {\n /** Better Stack API key (replaces deprecated `sourceToken`). */\n apiKey: string\n /**\n * @deprecated Renamed to {@link BetterStackConfig.apiKey}. Will be removed\n * in the next major version. Pass `apiKey` instead.\n */\n sourceToken?: string\n /** Logtail ingestion endpoint. Default: https://in.logs.betterstack.com */\n endpoint?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\nconst BETTER_STACK_FIELDS: ConfigField<BetterStackConfig>[] = [\n { key: 'apiKey', env: ['NUXT_BETTER_STACK_API_KEY', 'BETTER_STACK_API_KEY'] },\n // Deprecated env var names — resolved as a fallback for `apiKey` below.\n { key: 'sourceToken', env: ['NUXT_BETTER_STACK_SOURCE_TOKEN', 'BETTER_STACK_SOURCE_TOKEN'] },\n { key: 'endpoint', env: ['NUXT_BETTER_STACK_ENDPOINT', 'BETTER_STACK_ENDPOINT'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nlet warnedAboutSourceToken = false\n\nfunction applyApiKeyAlias(config: BetterStackConfig): BetterStackConfig {\n if (!config.apiKey && config.sourceToken) {\n if (!warnedAboutSourceToken) {\n warnedAboutSourceToken = true\n console.warn('[evlog/better-stack] `sourceToken` is deprecated, use `apiKey` instead. (Env: NUXT_BETTER_STACK_SOURCE_TOKEN → NUXT_BETTER_STACK_API_KEY.)')\n }\n config.apiKey = config.sourceToken\n }\n return config\n}\n\n/**\n * Transform an evlog wide event into a Better Stack event.\n * Maps `timestamp` to `dt` (Better Stack's expected field).\n */\nexport function toBetterStackEvent(event: WideEvent): Record<string, unknown> {\n const { timestamp, ...rest } = event\n return { ...rest, dt: timestamp }\n}\n\n/**\n * Create a drain function for sending logs to Better Stack.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createBetterStackDrain()\n * 2. runtimeConfig.evlog.betterStack\n * 3. runtimeConfig.betterStack\n * 4. Environment variables: NUXT_BETTER_STACK_API_KEY, BETTER_STACK_API_KEY (or legacy `*_SOURCE_TOKEN`)\n *\n * @example\n * ```ts\n * initLogger({ drain: createBetterStackDrain() })\n *\n * initLogger({ drain: createBetterStackDrain({ apiKey: 'my-key' }) })\n * ```\n */\nexport function createBetterStackDrain(overrides?: Partial<BetterStackConfig>) {\n return defineHttpDrain<BetterStackConfig>({\n name: 'better-stack',\n resolve: async () => {\n const resolved = await resolveAdapterConfig<BetterStackConfig>('betterStack', BETTER_STACK_FIELDS, overrides)\n const config = applyApiKeyAlias(resolved as BetterStackConfig)\n if (!config.apiKey) {\n console.error('[evlog/better-stack] Missing apiKey. Set NUXT_BETTER_STACK_API_KEY env var or pass to createBetterStackDrain()')\n return null\n }\n return config\n },\n encode: (events, config) => ({\n url: (config.endpoint ?? 'https://in.logs.betterstack.com').replace(/\\/+$/, ''),\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n },\n body: JSON.stringify(events.map(toBetterStackEvent)),\n }),\n })\n}\n\n/**\n * Send a single event to Better Stack.\n */\nexport async function sendToBetterStack(event: WideEvent, config: BetterStackConfig): Promise<void> {\n await sendBatchToBetterStack([event], config)\n}\n\n/**\n * Send a batch of events to Better Stack.\n */\nexport async function sendBatchToBetterStack(events: WideEvent[], config: BetterStackConfig): Promise<void> {\n const apiKey = config.apiKey ?? config.sourceToken\n if (!apiKey) throw new Error('[evlog/better-stack] Missing apiKey')\n const endpoint = (config.endpoint ?? 'https://in.logs.betterstack.com').replace(/\\/+$/, '')\n\n await httpPost({\n url: endpoint,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify(events.map(toBetterStackEvent)),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Better Stack',\n })\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"better-stack.mjs","names":[],"sources":["../../src/adapters/better-stack.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'\n\nexport interface BetterStackConfig {\n /** Better Stack API key (replaces deprecated `sourceToken`). */\n apiKey: string\n /**\n * @deprecated Renamed to {@link BetterStackConfig.apiKey}. Will be removed\n * in the next major version. Pass `apiKey` instead.\n */\n sourceToken?: string\n /** Logtail ingestion endpoint. Default: https://in.logs.betterstack.com */\n endpoint?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\nconst BETTER_STACK_FIELDS: ConfigField<BetterStackConfig>[] = [\n { key: 'apiKey', env: ['NUXT_BETTER_STACK_API_KEY', 'BETTER_STACK_API_KEY'] },\n // Deprecated env var names — resolved as a fallback for `apiKey` below.\n { key: 'sourceToken', env: ['NUXT_BETTER_STACK_SOURCE_TOKEN', 'BETTER_STACK_SOURCE_TOKEN'] },\n { key: 'endpoint', env: ['NUXT_BETTER_STACK_ENDPOINT', 'BETTER_STACK_ENDPOINT'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nlet warnedAboutSourceToken = false\n\nfunction applyApiKeyAlias(config: BetterStackConfig): BetterStackConfig {\n if (!config.apiKey && config.sourceToken) {\n if (!warnedAboutSourceToken) {\n warnedAboutSourceToken = true\n console.warn('[evlog/better-stack] `sourceToken` is deprecated, use `apiKey` instead. (Env: NUXT_BETTER_STACK_SOURCE_TOKEN → NUXT_BETTER_STACK_API_KEY.)')\n }\n config.apiKey = config.sourceToken\n }\n return config\n}\n\n/**\n * Transform an evlog wide event into a Better Stack event.\n * Maps `timestamp` to `dt` (Better Stack's expected field).\n */\nexport function toBetterStackEvent(event: WideEvent): Record<string, unknown> {\n const { timestamp, ...rest } = event\n return { ...rest, dt: timestamp }\n}\n\n/**\n * Create a drain function for sending logs to Better Stack.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createBetterStackDrain()\n * 2. runtimeConfig.evlog.betterStack\n * 3. runtimeConfig.betterStack\n * 4. Environment variables: NUXT_BETTER_STACK_API_KEY, BETTER_STACK_API_KEY (or legacy `*_SOURCE_TOKEN`)\n *\n * @example\n * ```ts\n * initLogger({ drain: createBetterStackDrain() })\n *\n * initLogger({ drain: createBetterStackDrain({ apiKey: 'my-key' }) })\n * ```\n */\nexport function createBetterStackDrain(overrides?: Partial<BetterStackConfig>) {\n return defineHttpDrain<BetterStackConfig>({\n name: 'better-stack',\n resolve: async () => {\n const resolved = await resolveAdapterConfig<BetterStackConfig>('betterStack', BETTER_STACK_FIELDS, overrides)\n const config = applyApiKeyAlias(resolved as BetterStackConfig)\n if (!config.apiKey) {\n console.error('[evlog/better-stack] Missing apiKey. Set NUXT_BETTER_STACK_API_KEY env var or pass to createBetterStackDrain()')\n return null\n }\n return config\n },\n encode: (events, config) => ({\n url: (config.endpoint ?? 'https://in.logs.betterstack.com').replace(/\\/+$/, ''),\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.apiKey}`,\n },\n body: JSON.stringify(events.map(toBetterStackEvent)),\n }),\n })\n}\n\n/**\n * Send a single event to Better Stack.\n */\nexport async function sendToBetterStack(event: WideEvent, config: BetterStackConfig): Promise<void> {\n await sendBatchToBetterStack([event], config)\n}\n\n/**\n * Send a batch of events to Better Stack.\n */\nexport async function sendBatchToBetterStack(events: WideEvent[], config: BetterStackConfig): Promise<void> {\n const apiKey = config.apiKey ?? config.sourceToken\n if (!apiKey) throw new Error('[evlog/better-stack] Missing apiKey')\n const endpoint = (config.endpoint ?? 'https://in.logs.betterstack.com').replace(/\\/+$/, '')\n\n await httpPost({\n url: endpoint,\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n },\n body: JSON.stringify(events.map(toBetterStackEvent)),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Better Stack',\n source: 'better-stack',\n })\n}\n"],"mappings":";;;AAsBA,MAAM,sBAAwD;CAC5D;EAAE,KAAK;EAAU,KAAK,CAAC,6BAA6B,uBAAuB;EAAE;CAE7E;EAAE,KAAK;EAAe,KAAK,CAAC,kCAAkC,4BAA4B;EAAE;CAC5F;EAAE,KAAK;EAAY,KAAK,CAAC,8BAA8B,wBAAwB;EAAE;CACjF,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;AAED,IAAI,yBAAyB;AAE7B,SAAS,iBAAiB,QAA8C;AACtE,KAAI,CAAC,OAAO,UAAU,OAAO,aAAa;AACxC,MAAI,CAAC,wBAAwB;AAC3B,4BAAyB;AACzB,WAAQ,KAAK,6IAA6I;;AAE5J,SAAO,SAAS,OAAO;;AAEzB,QAAO;;;;;;AAOT,SAAgB,mBAAmB,OAA2C;CAC5E,MAAM,EAAE,WAAW,GAAG,SAAS;AAC/B,QAAO;EAAE,GAAG;EAAM,IAAI;EAAW;;;;;;;;;;;;;;;;;;AAmBnC,SAAgB,uBAAuB,WAAwC;AAC7E,QAAO,gBAAmC;EACxC,MAAM;EACN,SAAS,YAAY;GAEnB,MAAM,SAAS,iBAAiB,MADT,qBAAwC,eAAe,qBAAqB,UAAU,CAC/C;AAC9D,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,iHAAiH;AAC/H,WAAO;;AAET,UAAO;;EAET,SAAS,QAAQ,YAAY;GAC3B,MAAM,OAAO,YAAY,mCAAmC,QAAQ,QAAQ,GAAG;GAC/E,SAAS;IACP,gBAAgB;IAChB,iBAAiB,UAAU,OAAO;IACnC;GACD,MAAM,KAAK,UAAU,OAAO,IAAI,mBAAmB,CAAC;GACrD;EACF,CAAC;;;;;AAMJ,eAAsB,kBAAkB,OAAkB,QAA0C;AAClG,OAAM,uBAAuB,CAAC,MAAM,EAAE,OAAO;;;;;AAM/C,eAAsB,uBAAuB,QAAqB,QAA0C;CAC1G,MAAM,SAAS,OAAO,UAAU,OAAO;AACvC,KAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,sCAAsC;AAGnE,OAAM,SAAS;EACb,MAHgB,OAAO,YAAY,mCAAmC,QAAQ,QAAQ,GAGzE;EACb,SAAS;GACP,gBAAgB;GAChB,iBAAiB,UAAU;GAC5B;EACD,MAAM,KAAK,UAAU,OAAO,IAAI,mBAAmB,CAAC;EACpD,SAAS,OAAO,WAAW;EAC3B,SAAS,OAAO;EAChB,OAAO;EACP,QAAQ;EACT,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { I as DrainContext, ct as WideEvent } from "../audit-CC8nfazi.mjs";
|
|
2
2
|
//#region src/adapters/datadog.d.ts
|
|
3
3
|
interface DatadogConfig {
|
|
4
4
|
/** Datadog API key with Logs intake permission */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as httpPost } from "../http-6umVAKDW.mjs";
|
|
2
|
+
import { i as resolveAdapterConfig, n as defineHttpDrain } from "../drain-X7_5szSI.mjs";
|
|
2
3
|
//#region src/adapters/datadog.ts
|
|
3
4
|
const DATADOG_FIELDS = [
|
|
4
5
|
{
|
|
@@ -169,7 +170,8 @@ async function sendBatchToDatadog(events, config) {
|
|
|
169
170
|
body: JSON.stringify(events.map(toDatadogLog)),
|
|
170
171
|
timeout: config.timeout ?? 5e3,
|
|
171
172
|
retries: config.retries,
|
|
172
|
-
label: "Datadog"
|
|
173
|
+
label: "Datadog",
|
|
174
|
+
source: "datadog"
|
|
173
175
|
});
|
|
174
176
|
}
|
|
175
177
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"datadog.mjs","names":[],"sources":["../../src/adapters/datadog.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'\n\nexport interface DatadogConfig {\n /** Datadog API key with Logs intake permission */\n apiKey: string\n /**\n * Datadog site hostname (e.g. `datadoghq.com`, `datadoghq.eu`, `us3.datadoghq.com`, `ddog-gov.com`).\n * Ignored when `intakeUrl` is set. Default: `datadoghq.com`\n */\n site?: string\n /**\n * Full Logs HTTP intake URL. When set, overrides the URL derived from `site`.\n * Default: `https://http-intake.logs.${site}/api/v2/logs`\n */\n intakeUrl?: 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\nconst DATADOG_FIELDS: ConfigField<DatadogConfig>[] = [\n { key: 'apiKey', env: ['NUXT_DATADOG_API_KEY', 'DATADOG_API_KEY', 'DD_API_KEY'] },\n { key: 'site', env: ['NUXT_DATADOG_SITE', 'DATADOG_SITE', 'DD_SITE'] },\n { key: 'intakeUrl', env: ['NUXT_DATADOG_LOGS_URL', 'DATADOG_LOGS_URL'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nconst DEFAULT_SITE = 'datadoghq.com'\n\n/**\n * Datadog treats **`status`** as log severity. evlog uses **`status`** for HTTP response codes on the wide event and\n * inside **`error`** (structured errors). Rename every **numeric** `status` at any depth to **`httpStatusCode`** so\n * nothing in the payload collides with reserved severity when Datadog processes attributes.\n *\n * Does not mutate the original {@link WideEvent} (builds new objects).\n */\nexport function sanitizeWideEventForDatadog(event: WideEvent): Record<string, unknown> {\n return deepRenameNumericHttpStatus(event as Record<string, unknown>) as Record<string, unknown>\n}\n\nfunction deepRenameNumericHttpStatus(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return value\n if (Array.isArray(value)) return value.map(deepRenameNumericHttpStatus)\n const obj = value as Record<string, unknown>\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(obj)) {\n if (k === 'status' && typeof v === 'number') {\n out.httpStatusCode = v\n } else {\n out[k] = deepRenameNumericHttpStatus(v)\n }\n }\n return out\n}\n\n/**\n * Single-line summary for Datadog’s `message` column (Live Tail / Explorer list view).\n * Full context stays under {@link toDatadogLog}'s `evlog` object.\n */\nexport function formatDatadogMessageLine(event: WideEvent): string {\n const levelU = event.level.toUpperCase()\n const method = typeof event.method === 'string' ? event.method : ''\n const path = typeof event.path === 'string' ? event.path : ''\n const code = typeof event.status === 'number' ? event.status : undefined\n\n const head = [levelU, method, path].filter(p => p.length > 0).join(' ')\n let line = code !== undefined\n ? (head ? `${head} (${code})` : `${levelU} (${code})`)\n : (head || levelU)\n\n if (!method && !path && line === levelU && event.service) {\n line = `${levelU} ${event.service}`\n }\n return line\n}\n\n/**\n * Severity for Datadog’s reserved `status` field (drives Live Tail coloring and facets).\n *\n * Uses the wide event’s **`level`** first (`log.error()` / `log.warn()`). If the level is\n * still `info`, falls back to the HTTP **`status`** on the wide event (`status: 4xx` → `warn`,\n * `5xx` → `error`) so client/server error responses are visible even when no `log.error()`\n * ran. Purely business errors on **HTTP 200** only change Datadog if you call `log.error()`.\n */\nexport function resolveDatadogLogStatus(event: WideEvent): 'error' | 'warn' | 'info' | 'debug' {\n if (event.level === 'error') return 'error'\n if (event.level === 'warn') return 'warn'\n if (event.level === 'debug') return 'debug'\n const code = typeof event.status === 'number' ? event.status : undefined\n if (code !== undefined && code >= 500) return 'error'\n if (code !== undefined && code >= 400) return 'warn'\n return 'info'\n}\n\n/**\n * Map an evlog wide event to a [Datadog Logs API v2](https://docs.datadoghq.com/api/latest/logs/) log object.\n *\n * Shape:\n * - **`message`** — short line for the list view (`formatDatadogMessageLine`)\n * - **`evlog`** — full sanitized wide event (HTTP codes as `httpStatusCode`); use facets like `@evlog.path`\n * - **`status`**, **`service`**, **`ddsource`**, **`ddtags`**, **`timestamp`** — Datadog standard fields\n */\nexport function toDatadogLog(event: WideEvent): Record<string, unknown> {\n const ms = Date.parse(event.timestamp)\n const tags = [`env:${event.environment}`]\n const versionTag = event.version\n if (versionTag !== undefined && versionTag !== null && versionTag !== '') {\n tags.push(`version:${String(versionTag)}`)\n }\n\n return {\n message: formatDatadogMessageLine(event),\n evlog: sanitizeWideEventForDatadog(event),\n service: event.service,\n status: resolveDatadogLogStatus(event),\n ddsource: 'evlog',\n ddtags: tags.join(','),\n ...(Number.isFinite(ms) ? { timestamp: ms } : {}),\n }\n}\n\n/**\n * Resolve the Logs intake URL from configuration.\n */\nexport function resolveDatadogIntakeUrl(config: Pick<DatadogConfig, 'site' | 'intakeUrl'>): string {\n if (config.intakeUrl) {\n return config.intakeUrl.replace(/\\/+$/, '')\n }\n const site = (config.site ?? DEFAULT_SITE).replace(/^\\./, '').replace(/\\/+$/, '')\n return `https://http-intake.logs.${site}/api/v2/logs`\n}\n\n/**\n * Create a drain function for sending logs to Datadog via the HTTP Logs intake API.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to `createDatadogDrain()`\n * 2. `runtimeConfig.evlog.datadog`\n * 3. `runtimeConfig.datadog`\n * 4. Environment variables: `NUXT_DATADOG_*`, `DATADOG_*`, and common `DD_*` aliases\n *\n * @example\n * ```ts\n * // Zero config — set DD_API_KEY (or NUXT_DATADOG_API_KEY) and optionally DD_SITE\n * nitroApp.hooks.hook('evlog:drain', createDatadogDrain())\n *\n * nitroApp.hooks.hook('evlog:drain', createDatadogDrain({\n * site: 'datadoghq.eu',\n * }))\n * ```\n */\nexport function createDatadogDrain(overrides?: Partial<DatadogConfig>) {\n return defineHttpDrain<DatadogConfig>({\n name: 'datadog',\n resolve: async () => {\n const config = await resolveAdapterConfig<DatadogConfig>('datadog', DATADOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/datadog] Missing API key. Set NUXT_DATADOG_API_KEY, DATADOG_API_KEY, or DD_API_KEY, or pass apiKey to createDatadogDrain()')\n return null\n }\n return config as DatadogConfig\n },\n encode: (events, config) => ({\n url: resolveDatadogIntakeUrl(config),\n headers: {\n 'Content-Type': 'application/json',\n 'DD-API-KEY': config.apiKey,\n },\n body: JSON.stringify(events.map(toDatadogLog)),\n }),\n })\n}\n\n/**\n * Send a single wide event to Datadog.\n */\nexport async function sendToDatadog(event: WideEvent, config: DatadogConfig): Promise<void> {\n await sendBatchToDatadog([event], config)\n}\n\n/**\n * Send a batch of wide events to Datadog in one request.\n */\nexport async function sendBatchToDatadog(events: WideEvent[], config: DatadogConfig): Promise<void> {\n if (events.length === 0) return\n\n const url = resolveDatadogIntakeUrl(config)\n\n await httpPost({\n url,\n headers: {\n 'Content-Type': 'application/json',\n 'DD-API-KEY': config.apiKey,\n },\n body: JSON.stringify(events.map(toDatadogLog)),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Datadog',\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"datadog.mjs","names":[],"sources":["../../src/adapters/datadog.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'\n\nexport interface DatadogConfig {\n /** Datadog API key with Logs intake permission */\n apiKey: string\n /**\n * Datadog site hostname (e.g. `datadoghq.com`, `datadoghq.eu`, `us3.datadoghq.com`, `ddog-gov.com`).\n * Ignored when `intakeUrl` is set. Default: `datadoghq.com`\n */\n site?: string\n /**\n * Full Logs HTTP intake URL. When set, overrides the URL derived from `site`.\n * Default: `https://http-intake.logs.${site}/api/v2/logs`\n */\n intakeUrl?: 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\nconst DATADOG_FIELDS: ConfigField<DatadogConfig>[] = [\n { key: 'apiKey', env: ['NUXT_DATADOG_API_KEY', 'DATADOG_API_KEY', 'DD_API_KEY'] },\n { key: 'site', env: ['NUXT_DATADOG_SITE', 'DATADOG_SITE', 'DD_SITE'] },\n { key: 'intakeUrl', env: ['NUXT_DATADOG_LOGS_URL', 'DATADOG_LOGS_URL'] },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nconst DEFAULT_SITE = 'datadoghq.com'\n\n/**\n * Datadog treats **`status`** as log severity. evlog uses **`status`** for HTTP response codes on the wide event and\n * inside **`error`** (structured errors). Rename every **numeric** `status` at any depth to **`httpStatusCode`** so\n * nothing in the payload collides with reserved severity when Datadog processes attributes.\n *\n * Does not mutate the original {@link WideEvent} (builds new objects).\n */\nexport function sanitizeWideEventForDatadog(event: WideEvent): Record<string, unknown> {\n return deepRenameNumericHttpStatus(event as Record<string, unknown>) as Record<string, unknown>\n}\n\nfunction deepRenameNumericHttpStatus(value: unknown): unknown {\n if (value === null || typeof value !== 'object') return value\n if (Array.isArray(value)) return value.map(deepRenameNumericHttpStatus)\n const obj = value as Record<string, unknown>\n const out: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(obj)) {\n if (k === 'status' && typeof v === 'number') {\n out.httpStatusCode = v\n } else {\n out[k] = deepRenameNumericHttpStatus(v)\n }\n }\n return out\n}\n\n/**\n * Single-line summary for Datadog’s `message` column (Live Tail / Explorer list view).\n * Full context stays under {@link toDatadogLog}'s `evlog` object.\n */\nexport function formatDatadogMessageLine(event: WideEvent): string {\n const levelU = event.level.toUpperCase()\n const method = typeof event.method === 'string' ? event.method : ''\n const path = typeof event.path === 'string' ? event.path : ''\n const code = typeof event.status === 'number' ? event.status : undefined\n\n const head = [levelU, method, path].filter(p => p.length > 0).join(' ')\n let line = code !== undefined\n ? (head ? `${head} (${code})` : `${levelU} (${code})`)\n : (head || levelU)\n\n if (!method && !path && line === levelU && event.service) {\n line = `${levelU} ${event.service}`\n }\n return line\n}\n\n/**\n * Severity for Datadog’s reserved `status` field (drives Live Tail coloring and facets).\n *\n * Uses the wide event’s **`level`** first (`log.error()` / `log.warn()`). If the level is\n * still `info`, falls back to the HTTP **`status`** on the wide event (`status: 4xx` → `warn`,\n * `5xx` → `error`) so client/server error responses are visible even when no `log.error()`\n * ran. Purely business errors on **HTTP 200** only change Datadog if you call `log.error()`.\n */\nexport function resolveDatadogLogStatus(event: WideEvent): 'error' | 'warn' | 'info' | 'debug' {\n if (event.level === 'error') return 'error'\n if (event.level === 'warn') return 'warn'\n if (event.level === 'debug') return 'debug'\n const code = typeof event.status === 'number' ? event.status : undefined\n if (code !== undefined && code >= 500) return 'error'\n if (code !== undefined && code >= 400) return 'warn'\n return 'info'\n}\n\n/**\n * Map an evlog wide event to a [Datadog Logs API v2](https://docs.datadoghq.com/api/latest/logs/) log object.\n *\n * Shape:\n * - **`message`** — short line for the list view (`formatDatadogMessageLine`)\n * - **`evlog`** — full sanitized wide event (HTTP codes as `httpStatusCode`); use facets like `@evlog.path`\n * - **`status`**, **`service`**, **`ddsource`**, **`ddtags`**, **`timestamp`** — Datadog standard fields\n */\nexport function toDatadogLog(event: WideEvent): Record<string, unknown> {\n const ms = Date.parse(event.timestamp)\n const tags = [`env:${event.environment}`]\n const versionTag = event.version\n if (versionTag !== undefined && versionTag !== null && versionTag !== '') {\n tags.push(`version:${String(versionTag)}`)\n }\n\n return {\n message: formatDatadogMessageLine(event),\n evlog: sanitizeWideEventForDatadog(event),\n service: event.service,\n status: resolveDatadogLogStatus(event),\n ddsource: 'evlog',\n ddtags: tags.join(','),\n ...(Number.isFinite(ms) ? { timestamp: ms } : {}),\n }\n}\n\n/**\n * Resolve the Logs intake URL from configuration.\n */\nexport function resolveDatadogIntakeUrl(config: Pick<DatadogConfig, 'site' | 'intakeUrl'>): string {\n if (config.intakeUrl) {\n return config.intakeUrl.replace(/\\/+$/, '')\n }\n const site = (config.site ?? DEFAULT_SITE).replace(/^\\./, '').replace(/\\/+$/, '')\n return `https://http-intake.logs.${site}/api/v2/logs`\n}\n\n/**\n * Create a drain function for sending logs to Datadog via the HTTP Logs intake API.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to `createDatadogDrain()`\n * 2. `runtimeConfig.evlog.datadog`\n * 3. `runtimeConfig.datadog`\n * 4. Environment variables: `NUXT_DATADOG_*`, `DATADOG_*`, and common `DD_*` aliases\n *\n * @example\n * ```ts\n * // Zero config — set DD_API_KEY (or NUXT_DATADOG_API_KEY) and optionally DD_SITE\n * nitroApp.hooks.hook('evlog:drain', createDatadogDrain())\n *\n * nitroApp.hooks.hook('evlog:drain', createDatadogDrain({\n * site: 'datadoghq.eu',\n * }))\n * ```\n */\nexport function createDatadogDrain(overrides?: Partial<DatadogConfig>) {\n return defineHttpDrain<DatadogConfig>({\n name: 'datadog',\n resolve: async () => {\n const config = await resolveAdapterConfig<DatadogConfig>('datadog', DATADOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/datadog] Missing API key. Set NUXT_DATADOG_API_KEY, DATADOG_API_KEY, or DD_API_KEY, or pass apiKey to createDatadogDrain()')\n return null\n }\n return config as DatadogConfig\n },\n encode: (events, config) => ({\n url: resolveDatadogIntakeUrl(config),\n headers: {\n 'Content-Type': 'application/json',\n 'DD-API-KEY': config.apiKey,\n },\n body: JSON.stringify(events.map(toDatadogLog)),\n }),\n })\n}\n\n/**\n * Send a single wide event to Datadog.\n */\nexport async function sendToDatadog(event: WideEvent, config: DatadogConfig): Promise<void> {\n await sendBatchToDatadog([event], config)\n}\n\n/**\n * Send a batch of wide events to Datadog in one request.\n */\nexport async function sendBatchToDatadog(events: WideEvent[], config: DatadogConfig): Promise<void> {\n if (events.length === 0) return\n\n const url = resolveDatadogIntakeUrl(config)\n\n await httpPost({\n url,\n headers: {\n 'Content-Type': 'application/json',\n 'DD-API-KEY': config.apiKey,\n },\n body: JSON.stringify(events.map(toDatadogLog)),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Datadog',\n source: 'datadog',\n })\n}\n"],"mappings":"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"}
|
package/dist/adapters/fs.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { I as DrainContext, K as LogLevel, ct as WideEvent } from "../audit-CC8nfazi.mjs";
|
|
2
2
|
//#region src/adapters/fs.d.ts
|
|
3
3
|
interface FsConfig {
|
|
4
4
|
/** Directory for log files. Default: `.evlog/logs` */
|
|
@@ -32,6 +32,68 @@ declare function writeBatchToFs(events: WideEvent[], config: FsConfig): Promise<
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
declare function createFsDrain(overrides?: Partial<FsConfig>): (ctx: DrainContext | DrainContext[]) => Promise<void>;
|
|
35
|
+
/** Options accepted by {@link readFsLogs}. */
|
|
36
|
+
interface ReadFsLogsOptions {
|
|
37
|
+
/** Directory to read from. Default: `.evlog/logs` */
|
|
38
|
+
dir?: string;
|
|
39
|
+
/** Only yield events with `event.timestamp >= since`. */
|
|
40
|
+
since?: Date | string;
|
|
41
|
+
/** Only yield events with `event.timestamp <= until`. */
|
|
42
|
+
until?: Date | string;
|
|
43
|
+
/** Filter by event level. */
|
|
44
|
+
level?: LogLevel | LogLevel[];
|
|
45
|
+
/** Custom predicate — return `false` to skip the event. */
|
|
46
|
+
filter?: (event: WideEvent) => boolean;
|
|
47
|
+
}
|
|
48
|
+
/** Options accepted by {@link tailFsLogs}. */
|
|
49
|
+
interface TailFsLogsOptions extends ReadFsLogsOptions {
|
|
50
|
+
/**
|
|
51
|
+
* Polling interval (ms) used to detect new bytes / new files.
|
|
52
|
+
* @default 500
|
|
53
|
+
*/
|
|
54
|
+
pollIntervalMs?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Skip existing events and only yield events appended after the tailer
|
|
57
|
+
* starts. Useful for "live tail" UX.
|
|
58
|
+
* @default false
|
|
59
|
+
*/
|
|
60
|
+
fromEnd?: boolean;
|
|
61
|
+
/** Stop tailing when this signal aborts. */
|
|
62
|
+
signal?: AbortSignal;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Read past events from the local file system drain (NDJSON). Files are
|
|
66
|
+
* iterated in chronological order; events are yielded as they appear in
|
|
67
|
+
* each file.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* import { readFsLogs } from 'evlog/fs'
|
|
72
|
+
*
|
|
73
|
+
* for await (const event of readFsLogs({ since: '2026-01-01', level: 'error' })) {
|
|
74
|
+
* console.log(event)
|
|
75
|
+
* }
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
declare function readFsLogs(options?: ReadFsLogsOptions): AsyncGenerator<WideEvent>;
|
|
79
|
+
/**
|
|
80
|
+
* Follow the local file system drain in real time. Yields existing events
|
|
81
|
+
* (unless `fromEnd: true`) then keeps yielding new events as they are
|
|
82
|
+
* appended. Automatically picks up newly created daily files.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* import { tailFsLogs } from 'evlog/fs'
|
|
87
|
+
*
|
|
88
|
+
* const ac = new AbortController()
|
|
89
|
+
* setTimeout(() => ac.abort(), 60_000)
|
|
90
|
+
*
|
|
91
|
+
* for await (const event of tailFsLogs({ signal: ac.signal })) {
|
|
92
|
+
* console.log('live:', event.action ?? event.message)
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
declare function tailFsLogs(options?: TailFsLogsOptions): AsyncGenerator<WideEvent>;
|
|
35
97
|
//#endregion
|
|
36
|
-
export { FsConfig, createFsDrain, writeBatchToFs, writeToFs };
|
|
98
|
+
export { FsConfig, ReadFsLogsOptions, TailFsLogsOptions, createFsDrain, readFsLogs, tailFsLogs, writeBatchToFs, writeToFs };
|
|
37
99
|
//# sourceMappingURL=fs.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.d.mts","names":[],"sources":["../../src/adapters/fs.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"fs.d.mts","names":[],"sources":["../../src/adapters/fs.ts"],"mappings":";;UASiB,QAAA;;EAEf,GAAA;EAFe;EAIf,QAAA;;EAEA,cAAA;EAJA;EAMA,MAAA;AAAA;AAAA,iBA+EoB,SAAA,CAAU,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,QAAA,GAAW,OAAA;AAAA,iBAI/C,cAAA,CAAe,MAAA,EAAQ,SAAA,IAAa,MAAA,EAAQ,QAAA,GAAW,OAAA;;AAJ7E;;;;;;;;;;;;;;;AAIA;;;iBAqCgB,aAAA,CAAc,SAAA,GAAY,OAAA,CAAQ,QAAA,KAAS,GAAA,EAAV,YAAA,GAAU,YAAA,OAAA,OAAA;;UAiB1C,iBAAA;EAtDmE;EAwDlF,GAAA;EAxD2C;EA0D3C,KAAA,GAAQ,IAAA;EA1DwD;EA4DhE,KAAA,GAAQ,IAAA;EA5DmE;EA8D3E,KAAA,GAAQ,QAAA,GAAW,QAAA;EA9D+D;EAgElF,MAAA,IAAU,KAAA,EAAO,SAAA;AAAA;;UAIF,iBAAA,SAA0B,iBAAA;EA/BD;;;;EAoCxC,cAAA;EApCyD;;;;;EA0CzD,OAAA;EA1CyD;EA4CzD,MAAA,GAAS,WAAA;AAAA;;AA3BX;;;;;;;;;;;;;iBAmIuB,UAAA,CAAW,OAAA,GAAS,iBAAA,GAAyB,cAAA,CAAe,SAAA;;;;;;;;;;AArHnF;;;;;;;;iBA8MuB,UAAA,CAAW,OAAA,GAAS,iBAAA,GAAyB,cAAA,CAAe,SAAA"}
|
package/dist/adapters/fs.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as resolveAdapterConfig, t as defineDrain } from "../drain-X7_5szSI.mjs";
|
|
2
2
|
import { join, sep } from "node:path";
|
|
3
|
-
import { appendFile, mkdir, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { appendFile, mkdir, open, readdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import { createReadStream } from "node:fs";
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
4
6
|
//#region src/adapters/fs.ts
|
|
5
7
|
const FS_FIELDS = [
|
|
6
8
|
{
|
|
@@ -112,7 +114,224 @@ function createFsDrain(overrides) {
|
|
|
112
114
|
send: writeBatchToFs
|
|
113
115
|
});
|
|
114
116
|
}
|
|
117
|
+
function isLogFilename(filename) {
|
|
118
|
+
return /^\d{4}-\d{2}-\d{2}(\.\d+)?\.jsonl$/.test(filename);
|
|
119
|
+
}
|
|
120
|
+
function compareLogFiles(a, b) {
|
|
121
|
+
const pa = parseLogFilename(a);
|
|
122
|
+
const pb = parseLogFilename(b);
|
|
123
|
+
return pa.date.localeCompare(pb.date) || pa.index - pb.index;
|
|
124
|
+
}
|
|
125
|
+
function normalizeTimestamp(value) {
|
|
126
|
+
if (!value) return void 0;
|
|
127
|
+
const ts = (value instanceof Date ? value : new Date(value)).getTime();
|
|
128
|
+
return Number.isNaN(ts) ? void 0 : ts;
|
|
129
|
+
}
|
|
130
|
+
function buildFilter(options) {
|
|
131
|
+
const since = normalizeTimestamp(options.since);
|
|
132
|
+
const until = normalizeTimestamp(options.until);
|
|
133
|
+
const levels = options.level ? new Set(Array.isArray(options.level) ? options.level : [options.level]) : void 0;
|
|
134
|
+
const custom = options.filter;
|
|
135
|
+
return (event) => {
|
|
136
|
+
if (levels && !levels.has(event.level)) return false;
|
|
137
|
+
if (since !== void 0 || until !== void 0) {
|
|
138
|
+
const ts = typeof event.timestamp === "string" ? Date.parse(event.timestamp) : NaN;
|
|
139
|
+
if (Number.isNaN(ts)) return false;
|
|
140
|
+
if (since !== void 0 && ts < since) return false;
|
|
141
|
+
if (until !== void 0 && ts > until) return false;
|
|
142
|
+
}
|
|
143
|
+
if (custom && !custom(event)) return false;
|
|
144
|
+
return true;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
async function listLogFiles(dir) {
|
|
148
|
+
let files;
|
|
149
|
+
try {
|
|
150
|
+
files = await readdir(dir);
|
|
151
|
+
} catch {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
return files.filter(isLogFilename).sort(compareLogFiles);
|
|
155
|
+
}
|
|
156
|
+
function fileDateMs(filename) {
|
|
157
|
+
const { date } = parseLogFilename(filename);
|
|
158
|
+
return date ? Date.parse(`${date}T00:00:00.000Z`) : NaN;
|
|
159
|
+
}
|
|
160
|
+
function fileWithinRange(filename, since, until) {
|
|
161
|
+
if (since === void 0 && until === void 0) return true;
|
|
162
|
+
const dayStart = fileDateMs(filename);
|
|
163
|
+
if (Number.isNaN(dayStart)) return true;
|
|
164
|
+
const dayEnd = dayStart + 1440 * 60 * 1e3 - 1;
|
|
165
|
+
if (since !== void 0 && dayEnd < since) return false;
|
|
166
|
+
if (until !== void 0 && dayStart > until) return false;
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
async function* iterateFile(filePath) {
|
|
170
|
+
const stream = createReadStream(filePath, { encoding: "utf-8" });
|
|
171
|
+
const rl = createInterface({
|
|
172
|
+
input: stream,
|
|
173
|
+
crlfDelay: Infinity
|
|
174
|
+
});
|
|
175
|
+
try {
|
|
176
|
+
for await (const line of rl) {
|
|
177
|
+
const trimmed = line.trim();
|
|
178
|
+
if (!trimmed) continue;
|
|
179
|
+
try {
|
|
180
|
+
yield JSON.parse(trimmed);
|
|
181
|
+
} catch {}
|
|
182
|
+
}
|
|
183
|
+
} finally {
|
|
184
|
+
rl.close();
|
|
185
|
+
stream.destroy();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Read past events from the local file system drain (NDJSON). Files are
|
|
190
|
+
* iterated in chronological order; events are yielded as they appear in
|
|
191
|
+
* each file.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* import { readFsLogs } from 'evlog/fs'
|
|
196
|
+
*
|
|
197
|
+
* for await (const event of readFsLogs({ since: '2026-01-01', level: 'error' })) {
|
|
198
|
+
* console.log(event)
|
|
199
|
+
* }
|
|
200
|
+
* ```
|
|
201
|
+
*/
|
|
202
|
+
async function* readFsLogs(options = {}) {
|
|
203
|
+
const dir = options.dir ?? ".evlog/logs";
|
|
204
|
+
const sinceMs = normalizeTimestamp(options.since);
|
|
205
|
+
const untilMs = normalizeTimestamp(options.until);
|
|
206
|
+
const predicate = buildFilter(options);
|
|
207
|
+
const files = await listLogFiles(dir);
|
|
208
|
+
for (const filename of files) {
|
|
209
|
+
if (!fileWithinRange(filename, sinceMs, untilMs)) continue;
|
|
210
|
+
for await (const event of iterateFile(join(dir, filename))) if (predicate(event)) yield event;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
async function safeStatSize(filePath) {
|
|
214
|
+
try {
|
|
215
|
+
return (await stat(filePath)).size;
|
|
216
|
+
} catch {
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async function readAppendedLines(filePath, fromOffset, carry) {
|
|
221
|
+
const size = await safeStatSize(filePath);
|
|
222
|
+
if (size <= fromOffset) return {
|
|
223
|
+
events: [],
|
|
224
|
+
offset: fromOffset,
|
|
225
|
+
carry
|
|
226
|
+
};
|
|
227
|
+
const handle = await open(filePath, "r");
|
|
228
|
+
try {
|
|
229
|
+
const length = size - fromOffset;
|
|
230
|
+
const buf = Buffer.alloc(length);
|
|
231
|
+
await handle.read(buf, 0, length, fromOffset);
|
|
232
|
+
const chunk = carry + buf.toString("utf-8");
|
|
233
|
+
const newlineIdx = chunk.lastIndexOf("\n");
|
|
234
|
+
if (newlineIdx === -1) return {
|
|
235
|
+
events: [],
|
|
236
|
+
offset: size,
|
|
237
|
+
carry: chunk
|
|
238
|
+
};
|
|
239
|
+
const complete = chunk.slice(0, newlineIdx);
|
|
240
|
+
const remainder = chunk.slice(newlineIdx + 1);
|
|
241
|
+
return {
|
|
242
|
+
events: complete.split("\n").map((l) => l.trim()).filter(Boolean),
|
|
243
|
+
offset: size,
|
|
244
|
+
carry: remainder
|
|
245
|
+
};
|
|
246
|
+
} finally {
|
|
247
|
+
await handle.close();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
function delay(ms, signal) {
|
|
251
|
+
return new Promise((resolve) => {
|
|
252
|
+
const timer = setTimeout(() => {
|
|
253
|
+
if (signal) signal.removeEventListener("abort", onAbort);
|
|
254
|
+
resolve();
|
|
255
|
+
}, ms);
|
|
256
|
+
const onAbort = () => {
|
|
257
|
+
clearTimeout(timer);
|
|
258
|
+
resolve();
|
|
259
|
+
};
|
|
260
|
+
if (signal) {
|
|
261
|
+
if (signal.aborted) {
|
|
262
|
+
clearTimeout(timer);
|
|
263
|
+
resolve();
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Follow the local file system drain in real time. Yields existing events
|
|
272
|
+
* (unless `fromEnd: true`) then keeps yielding new events as they are
|
|
273
|
+
* appended. Automatically picks up newly created daily files.
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* ```ts
|
|
277
|
+
* import { tailFsLogs } from 'evlog/fs'
|
|
278
|
+
*
|
|
279
|
+
* const ac = new AbortController()
|
|
280
|
+
* setTimeout(() => ac.abort(), 60_000)
|
|
281
|
+
*
|
|
282
|
+
* for await (const event of tailFsLogs({ signal: ac.signal })) {
|
|
283
|
+
* console.log('live:', event.action ?? event.message)
|
|
284
|
+
* }
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
async function* tailFsLogs(options = {}) {
|
|
288
|
+
const dir = options.dir ?? ".evlog/logs";
|
|
289
|
+
const interval = Math.max(50, options.pollIntervalMs ?? 500);
|
|
290
|
+
const { signal } = options;
|
|
291
|
+
const predicate = buildFilter(options);
|
|
292
|
+
const offsets = /* @__PURE__ */ new Map();
|
|
293
|
+
const carries = /* @__PURE__ */ new Map();
|
|
294
|
+
if (options.fromEnd) {
|
|
295
|
+
const files = await listLogFiles(dir);
|
|
296
|
+
for (const filename of files) {
|
|
297
|
+
offsets.set(filename, await safeStatSize(join(dir, filename)));
|
|
298
|
+
carries.set(filename, "");
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
for await (const event of readFsLogs(options)) {
|
|
302
|
+
if (signal?.aborted) return;
|
|
303
|
+
yield event;
|
|
304
|
+
}
|
|
305
|
+
const files = await listLogFiles(dir);
|
|
306
|
+
for (const filename of files) if (!offsets.has(filename)) {
|
|
307
|
+
offsets.set(filename, await safeStatSize(join(dir, filename)));
|
|
308
|
+
carries.set(filename, "");
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
while (true) {
|
|
312
|
+
if (signal?.aborted) return;
|
|
313
|
+
await delay(interval, signal);
|
|
314
|
+
if (signal?.aborted) return;
|
|
315
|
+
const files = await listLogFiles(dir);
|
|
316
|
+
for (const filename of files) {
|
|
317
|
+
if (!offsets.has(filename)) {
|
|
318
|
+
offsets.set(filename, 0);
|
|
319
|
+
carries.set(filename, "");
|
|
320
|
+
}
|
|
321
|
+
const { events, offset, carry: newCarry } = await readAppendedLines(join(dir, filename), offsets.get(filename), carries.get(filename) ?? "");
|
|
322
|
+
offsets.set(filename, offset);
|
|
323
|
+
carries.set(filename, newCarry);
|
|
324
|
+
for (const line of events) {
|
|
325
|
+
if (signal?.aborted) return;
|
|
326
|
+
try {
|
|
327
|
+
const event = JSON.parse(line);
|
|
328
|
+
if (predicate(event)) yield event;
|
|
329
|
+
} catch {}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
115
334
|
//#endregion
|
|
116
|
-
export { createFsDrain, writeBatchToFs, writeToFs };
|
|
335
|
+
export { createFsDrain, readFsLogs, tailFsLogs, writeBatchToFs, writeToFs };
|
|
117
336
|
|
|
118
337
|
//# sourceMappingURL=fs.mjs.map
|