diff --git a/.agents/skills/create-adapter/SKILL.md b/.agents/skills/create-adapter/SKILL.md index 993f8dbf..96dd7896 100644 --- a/.agents/skills/create-adapter/SKILL.md +++ b/.agents/skills/create-adapter/SKILL.md @@ -59,7 +59,7 @@ Create `packages/evlog/src/adapters/{name}.ts`. Read [references/adapter-templat The contract is now `defineHttpDrain({ resolve, encode })`. You only ship two pieces of logic: -1. **`resolve()`** — produce a fully-resolved config or `null` to skip. Use `resolveAdapterConfig` for the standard precedence (overrides → `runtimeConfig.evlog.{name}` → `runtimeConfig.{name}` → `NUXT_{NAME}_*` → `{NAME}_*`). +1. **`resolve()`** — produce a fully-resolved config or `null` to skip. Use `resolveAdapterConfig` for the standard precedence (overrides → `runtimeConfig.evlog.{name}` → `runtimeConfig.{name}` → env vars). List `NUXT_{NAME}_*` before `{NAME}_*` in `ConfigField.env` for silent Nuxt compat; show only `{NAME}_*` in user-facing messages via `formatPublicEnvKeys`. 2. **`encode(events, config)`** — produce `{ url, headers, body }` for a batch of events (or `null` to skip). HTTP transport, retries, timeout, and error logging are handled by `defineHttpDrain`. Key rules: diff --git a/.agents/skills/create-adapter/references/adapter-template.md b/.agents/skills/create-adapter/references/adapter-template.md index d3227908..93999f92 100644 --- a/.agents/skills/create-adapter/references/adapter-template.md +++ b/.agents/skills/create-adapter/references/adapter-template.md @@ -7,7 +7,7 @@ Replace `{Name}`, `{name}`, and `{NAME}` with the actual service name. ```typescript import type { WideEvent } from '../types' import type { ConfigField } from '../shared/config' -import { resolveAdapterConfig } from '../shared/config' +import { formatPublicEnvKeys, resolveAdapterConfig } from '../shared/config' import { defineHttpDrain } from '../shared/drain' // --- 1. Config Interface ------------------------------------------------- @@ -99,13 +99,13 @@ export async function sendBatchTo{Name}( * 1. Overrides passed to create{Name}Drain() * 2. runtimeConfig.evlog.{name} * 3. runtimeConfig.{name} - * 4. Environment variables: NUXT_{NAME}_*, {NAME}_* + * 4. Environment variables: {NAME}_* * * @example * ```ts * import { create{Name}Drain } from 'evlog/{name}' * - * // Zero config — set NUXT_{NAME}_API_KEY env var + * // Zero config — set {NAME}_API_KEY env var * defineEvlog({ drain: create{Name}Drain() }) * * // With overrides @@ -119,7 +119,7 @@ export function create{Name}Drain(overrides?: Partial<{Name}Config>) { resolve: async () => { const config = await resolveAdapterConfig<{Name}Config>('{name}', FIELDS, overrides) if (!config.apiKey) { - console.error('[evlog/{name}] Missing apiKey. Set NUXT_{NAME}_API_KEY env var or pass to create{Name}Drain()') + console.error(`[evlog/{name}] Missing apiKey. Set ${formatPublicEnvKeys(['NUXT_{NAME}_API_KEY', '{NAME}_API_KEY'])} env var or pass to create{Name}Drain()`) return null } return config as {Name}Config diff --git a/.changeset/adapter-env-messages-dx.md b/.changeset/adapter-env-messages-dx.md new file mode 100644 index 00000000..a173da74 --- /dev/null +++ b/.changeset/adapter-env-messages-dx.md @@ -0,0 +1,7 @@ +--- +"evlog": patch +--- + +Adapter error and deprecation messages now show canonical environment variable names only (`BETTER_STACK_API_KEY`, `AXIOM_API_KEY`, `SENTRY_DSN`, etc.). `NUXT_*` aliases still resolve silently for backward compatibility, but are no longer mentioned in console output or documentation. + +The OTLP adapter now also accepts the shorter `OTLP_ENDPOINT` / `OTLP_HEADERS` env vars as aliases for the standard `OTEL_EXPORTER_OTLP_ENDPOINT` / `OTEL_EXPORTER_OTLP_HEADERS`. diff --git a/apps/docs/content/3.integrate/adapters/01.overview.md b/apps/docs/content/3.integrate/adapters/01.overview.md index 311da5c7..873599f2 100644 --- a/apps/docs/content/3.integrate/adapters/01.overview.md +++ b/apps/docs/content/3.integrate/adapters/01.overview.md @@ -347,29 +347,29 @@ Every adapter receives a `DrainContext` with: All adapters support automatic configuration via environment variables. No code changes needed when deploying to different environments. -Each adapter reads from `NUXT_*` prefixed variables (for Nuxt/Nitro runtimeConfig) and unprefixed fallbacks (for any framework): +Each adapter reads from standard environment variables — the same names work in every framework: ```bash [.env] -# Axiom (NUXT_AXIOM_* or AXIOM_*) +# Axiom AXIOM_API_KEY=xaat-xxx AXIOM_DATASET=my-logs -# OTLP (NUXT_OTLP_* or OTEL_*) +# OTLP OTLP_ENDPOINT=https://otlp.example.com -# HyperDX (NUXT_HYPERDX_* or HYPERDX_*) +# HyperDX HYPERDX_API_KEY= -# PostHog (NUXT_POSTHOG_* or POSTHOG_*) +# PostHog POSTHOG_API_KEY=phc_xxx -# Sentry (NUXT_SENTRY_* or SENTRY_*) +# Sentry SENTRY_DSN=https://key@o0.ingest.sentry.io/123 -# Better Stack (NUXT_BETTER_STACK_* or BETTER_STACK_*) +# Better Stack BETTER_STACK_API_KEY=your-source-token -# Datadog (NUXT_DATADOG_* or DATADOG_* or DD_*) +# Datadog DD_API_KEY=your-api-key DD_SITE=datadoghq.eu ``` diff --git a/apps/docs/content/3.integrate/adapters/cloud/01.axiom.md b/apps/docs/content/3.integrate/adapters/cloud/01.axiom.md index 702481de..6adda1f3 100644 --- a/apps/docs/content/3.integrate/adapters/cloud/01.axiom.md +++ b/apps/docs/content/3.integrate/adapters/cloud/01.axiom.md @@ -132,21 +132,17 @@ The adapter reads configuration from multiple sources (highest priority first): 1. **Overrides** passed to `createAxiomDrain()` 2. **Runtime config** at `runtimeConfig.axiom` (Nuxt/Nitro only) -3. **Environment variables** (`AXIOM_*` or `NUXT_AXIOM_*`) +3. **Environment variables** (`AXIOM_*`) ### Environment Variables -| Variable | Nuxt alias | Description | -|----------|------------|-------------| -| `AXIOM_API_KEY` | `NUXT_AXIOM_API_KEY` | Axiom API token with ingest permissions | -| `AXIOM_DATASET` | `NUXT_AXIOM_DATASET` | Dataset name to ingest logs into | -| `AXIOM_ORG_ID` | `NUXT_AXIOM_ORG_ID` | Organization ID (required for Personal Access Tokens) | -| `AXIOM_EDGE_URL` | `NUXT_AXIOM_EDGE_URL` | Edge base URL for ingest/query (for edge deployments) | -| `AXIOM_URL` | `NUXT_AXIOM_URL` | API base URL (legacy/default ingest endpoint) | - -::callout{icon="i-lucide-info" color="info"} -In Nuxt/Nitro, use the `NUXT_` prefix so values are available via `useRuntimeConfig()`. In all other frameworks, use the unprefixed variables. -:: +| Variable | Description | +|----------|-------------| +| `AXIOM_API_KEY` | Axiom API token with ingest permissions | +| `AXIOM_DATASET` | Dataset name to ingest logs into | +| `AXIOM_ORG_ID` | Organization ID (required for Personal Access Tokens) | +| `AXIOM_EDGE_URL` | Edge base URL for ingest/query (for edge deployments) | +| `AXIOM_URL` | API base URL (legacy/default ingest endpoint) | ### Runtime Config (Nuxt only) @@ -156,8 +152,8 @@ Configure via `nuxt.config.ts` for type-safe configuration: export default defineNuxtConfig({ runtimeConfig: { axiom: { - apiKey: '', // Set via NUXT_AXIOM_API_KEY - dataset: '', // Set via NUXT_AXIOM_DATASET + apiKey: '', // Set via AXIOM_API_KEY + dataset: '', // Set via AXIOM_DATASET }, }, }) @@ -213,7 +209,7 @@ evlog sends structured wide events that are perfect for Axiom's APL query langua ### Missing dataset or apiKey error ```text [Console] -[evlog/axiom] Missing dataset or apiKey. Set NUXT_AXIOM_API_KEY/NUXT_AXIOM_DATASET env vars or pass to createAxiomDrain() +[evlog/axiom] Missing dataset or apiKey. Set AXIOM_API_KEY/AXIOM_DATASET env vars or pass to createAxiomDrain() ``` Make sure your environment variables are set and the server was restarted after adding them. diff --git a/apps/docs/content/3.integrate/adapters/cloud/02.otlp.md b/apps/docs/content/3.integrate/adapters/cloud/02.otlp.md index 0793e54c..b477935c 100644 --- a/apps/docs/content/3.integrate/adapters/cloud/02.otlp.md +++ b/apps/docs/content/3.integrate/adapters/cloud/02.otlp.md @@ -133,20 +133,11 @@ The adapter reads configuration from multiple sources (highest priority first): ### Environment Variables -| Variable | Nuxt alias | Description | -|----------|------------|-------------| -| `OTLP_ENDPOINT` | `NUXT_OTLP_ENDPOINT` | OTLP HTTP endpoint (e.g., `http://localhost:4318`) | -| `OTLP_SERVICE_NAME` | `NUXT_OTLP_SERVICE_NAME` | Override service name | -| `OTLP_HEADERS` | `NUXT_OTLP_HEADERS` | Custom headers (format: `Key=Value,Key2=Value2`) | -| `OTLP_AUTH` | `NUXT_OTLP_AUTH` | Shortcut for `Authorization` header | - -Standard OpenTelemetry variables are also supported as fallbacks: - | Variable | Description | |----------|-------------| -| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP endpoint | -| `OTEL_EXPORTER_OTLP_HEADERS` | Headers in OTEL format | -| `OTEL_SERVICE_NAME` | Service name | +| `OTLP_ENDPOINT` | OTLP HTTP endpoint (e.g., `http://localhost:4318`). The standard `OTEL_EXPORTER_OTLP_ENDPOINT` also works. | +| `OTLP_HEADERS` | Headers as `key=value` pairs, comma-separated. The standard `OTEL_EXPORTER_OTLP_HEADERS` also works. | +| `OTEL_SERVICE_NAME` | Service name override | ### Runtime Config (Nuxt only) @@ -154,7 +145,7 @@ Standard OpenTelemetry variables are also supported as fallbacks: export default defineNuxtConfig({ runtimeConfig: { otlp: { - endpoint: '', // Set via NUXT_OTLP_ENDPOINT + endpoint: '', // Set via OTLP_ENDPOINT (or OTEL_EXPORTER_OTLP_ENDPOINT) }, }, }) diff --git a/apps/docs/content/3.integrate/adapters/cloud/03.posthog.md b/apps/docs/content/3.integrate/adapters/cloud/03.posthog.md index 7806f13b..56142c72 100644 --- a/apps/docs/content/3.integrate/adapters/cloud/03.posthog.md +++ b/apps/docs/content/3.integrate/adapters/cloud/03.posthog.md @@ -128,14 +128,14 @@ The adapter reads configuration from multiple sources (highest priority first): 1. **Overrides** passed to `createPostHogDrain()` 2. **Runtime config** at `runtimeConfig.posthog` (Nuxt/Nitro only) -3. **Environment variables** (`POSTHOG_*` or `NUXT_POSTHOG_*`) +3. **Environment variables** (`POSTHOG_*`) ### Environment Variables -| Variable | Nuxt alias | Description | -|----------|------------|-------------| -| `POSTHOG_API_KEY` | `NUXT_POSTHOG_API_KEY` | Project API key (starts with `phc_`) | -| `POSTHOG_HOST` | `NUXT_POSTHOG_HOST` | PostHog host URL (for EU or self-hosted) | +| Variable | Description | +|----------|-------------| +| `POSTHOG_API_KEY` | Project API key (starts with `phc_`) | +| `POSTHOG_HOST` | PostHog host URL (for EU or self-hosted) | ### Runtime Config (Nuxt only) @@ -145,8 +145,8 @@ Configure via `nuxt.config.ts` for type-safe configuration: export default defineNuxtConfig({ runtimeConfig: { posthog: { - apiKey: '', // Set via NUXT_POSTHOG_API_KEY - host: '', // Set via NUXT_POSTHOG_HOST + apiKey: '', // Set via POSTHOG_API_KEY + host: '', // Set via POSTHOG_HOST }, }, }) diff --git a/apps/docs/content/3.integrate/adapters/cloud/04.sentry.md b/apps/docs/content/3.integrate/adapters/cloud/04.sentry.md index 34260a0f..c4165dce 100644 --- a/apps/docs/content/3.integrate/adapters/cloud/04.sentry.md +++ b/apps/docs/content/3.integrate/adapters/cloud/04.sentry.md @@ -127,15 +127,15 @@ The adapter reads configuration from multiple sources (highest priority first): 1. **Overrides** passed to `createSentryDrain()` 2. **Runtime config** at `runtimeConfig.sentry` (Nuxt/Nitro only) -3. **Environment variables** (`SENTRY_*` or `NUXT_SENTRY_*`) +3. **Environment variables** (`SENTRY_*`) ### Environment Variables -| Variable | Nuxt alias | Description | -|----------|------------|-------------| -| `SENTRY_DSN` | `NUXT_SENTRY_DSN` | Sentry DSN (required) | -| `SENTRY_ENVIRONMENT` | `NUXT_SENTRY_ENVIRONMENT` | Environment name override | -| `SENTRY_RELEASE` | `NUXT_SENTRY_RELEASE` | Release version override | +| Variable | Description | +|----------|-------------| +| `SENTRY_DSN` | Sentry DSN (required) | +| `SENTRY_ENVIRONMENT` | Environment name override | +| `SENTRY_RELEASE` | Release version override | ### Runtime Config (Nuxt only) @@ -146,7 +146,7 @@ export default defineNuxtConfig({ modules: ['evlog/nuxt'], evlog: { sentry: { - dsn: '', // Set via NUXT_SENTRY_DSN + dsn: '', // Set via SENTRY_DSN environment: 'production', release: '1.0.0', }, diff --git a/apps/docs/content/3.integrate/adapters/cloud/05.better-stack.md b/apps/docs/content/3.integrate/adapters/cloud/05.better-stack.md index 54f363d2..3688351b 100644 --- a/apps/docs/content/3.integrate/adapters/cloud/05.better-stack.md +++ b/apps/docs/content/3.integrate/adapters/cloud/05.better-stack.md @@ -67,7 +67,7 @@ BETTER_STACK_API_KEY=your-source-token-here ``` ::callout{icon="i-lucide-info" color="info"} -In Better Stack's dashboard this credential is called a **source token**. evlog names the config field `apiKey` for consistency across adapters. Legacy `sourceToken` / `BETTER_STACK_SOURCE_TOKEN` still work until the next major release. +In Better Stack's dashboard this credential is called a **source token**. evlog names the config field `apiKey` for consistency across adapters. The legacy `sourceToken` field still works until the next major release. :: ### 3. Wire the drain to your framework @@ -131,14 +131,14 @@ The adapter reads configuration from multiple sources (highest priority first): 1. **Overrides** passed to `createBetterStackDrain()` 2. **Runtime config** at `runtimeConfig.betterStack` (Nuxt/Nitro only) -3. **Environment variables** (`BETTER_STACK_*` or `NUXT_BETTER_STACK_*`) +3. **Environment variables** (`BETTER_STACK_*`) ### Environment Variables -| Variable | Nuxt alias | Description | -|----------|------------|-------------| -| `BETTER_STACK_API_KEY` | `NUXT_BETTER_STACK_API_KEY` | Better Stack source token (required) | -| `BETTER_STACK_ENDPOINT` | `NUXT_BETTER_STACK_ENDPOINT` | Custom ingestion endpoint | +| Variable | Description | +|----------|-------------| +| `BETTER_STACK_API_KEY` | Better Stack source token (required) | +| `BETTER_STACK_ENDPOINT` | Custom ingestion endpoint | ### Runtime Config (Nuxt only) @@ -148,7 +148,7 @@ Configure via `nuxt.config.ts` for type-safe configuration: export default defineNuxtConfig({ runtimeConfig: { betterStack: { - apiKey: '', // Set via NUXT_BETTER_STACK_API_KEY + apiKey: '', // Set via BETTER_STACK_API_KEY }, }, }) @@ -198,7 +198,7 @@ Better Stack provides a powerful log search interface: ### Missing apiKey error ```text [Console] -[evlog/better-stack] Missing apiKey. Set NUXT_BETTER_STACK_API_KEY env var or pass to createBetterStackDrain() +[evlog/better-stack] Missing apiKey. Set BETTER_STACK_API_KEY env var or pass to createBetterStackDrain() ``` Make sure your environment variable is set and the server was restarted after adding it. diff --git a/apps/docs/content/3.integrate/adapters/cloud/06.datadog.md b/apps/docs/content/3.integrate/adapters/cloud/06.datadog.md index 7ead26fe..d712a990 100644 --- a/apps/docs/content/3.integrate/adapters/cloud/06.datadog.md +++ b/apps/docs/content/3.integrate/adapters/cloud/06.datadog.md @@ -134,11 +134,11 @@ The adapter reads configuration from multiple sources (highest priority first): ### Environment Variables -| Variable | Nuxt alias | Description | -|----------|------------|-------------| -| `DD_API_KEY` | `NUXT_DATADOG_API_KEY` | Datadog API key (required). Also: `DATADOG_API_KEY` | -| `DD_SITE` | `NUXT_DATADOG_SITE` | Site hostname (e.g. `datadoghq.com`, `datadoghq.eu`, `us3.datadoghq.com`). Also: `DATADOG_SITE` | -| `DATADOG_LOGS_URL` | `NUXT_DATADOG_LOGS_URL` | Full intake URL — overrides URL derived from `site` | +| Variable | Description | +|----------|-------------| +| `DD_API_KEY` | Datadog API key (required). Also: `DATADOG_API_KEY` | +| `DD_SITE` | Site hostname (e.g. `datadoghq.com`, `datadoghq.eu`, `us3.datadoghq.com`). Also: `DATADOG_SITE` | +| `DATADOG_LOGS_URL` | Full intake URL — overrides URL derived from `site` | ### Runtime Config (Nuxt only) @@ -146,7 +146,7 @@ The adapter reads configuration from multiple sources (highest priority first): export default defineNuxtConfig({ runtimeConfig: { datadog: { - apiKey: '', // Set via NUXT_DATADOG_API_KEY or DD_API_KEY + apiKey: '', // Set via DD_API_KEY or DATADOG_API_KEY site: 'datadoghq.eu', }, }, @@ -202,7 +202,7 @@ Plain-text lines in Live Tail (e.g. “Form field is empty”) usually come from ### Missing API key ```text [Console] -[evlog/datadog] Missing API key. Set NUXT_DATADOG_API_KEY, DATADOG_API_KEY, or DD_API_KEY... +[evlog/datadog] Missing API key. Set DATADOG_API_KEY, DD_API_KEY... ``` Set `DD_API_KEY` (or unprefixed `DATADOG_API_KEY`) and restart the process. @@ -213,7 +213,7 @@ The API key may lack log ingestion permission or belong to the wrong organizatio ### Wrong region / site -If logs never appear, confirm `DD_SITE` matches your Datadog account (e.g. EU: `datadoghq.eu`). For a custom intake URL, set `DATADOG_LOGS_URL` / `NUXT_DATADOG_LOGS_URL`. +If logs never appear, confirm `DD_SITE` matches your Datadog account (e.g. EU: `datadoghq.eu`). For a custom intake URL, set `DATADOG_LOGS_URL`. ## Direct API usage diff --git a/apps/docs/content/3.integrate/adapters/cloud/07.hyperdx.md b/apps/docs/content/3.integrate/adapters/cloud/07.hyperdx.md index edb960da..1881997d 100644 --- a/apps/docs/content/3.integrate/adapters/cloud/07.hyperdx.md +++ b/apps/docs/content/3.integrate/adapters/cloud/07.hyperdx.md @@ -126,15 +126,15 @@ The adapter reads configuration from multiple sources (highest priority first): 1. **Overrides** passed to `createHyperDXDrain()` 2. **Runtime config** at `runtimeConfig.evlog.hyperdx` or `runtimeConfig.hyperdx` (Nuxt/Nitro only) -3. **Environment variables** (`HYPERDX_*` or `NUXT_HYPERDX_*`) +3. **Environment variables** (`HYPERDX_*`) ### Environment Variables -| Variable | Nuxt alias | Description | -|----------|------------|-------------| -| `HYPERDX_API_KEY` | `NUXT_HYPERDX_API_KEY` | Ingestion API key (sent as the `authorization` header) | -| `HYPERDX_OTLP_ENDPOINT` | `NUXT_HYPERDX_OTLP_ENDPOINT` | OTLP HTTP base URL (default: `https://in-otel.hyperdx.io`) | -| `HYPERDX_SERVICE_NAME` | `NUXT_HYPERDX_SERVICE_NAME` | Override `service.name` | +| Variable | Description | +|----------|-------------| +| `HYPERDX_API_KEY` | Ingestion API key (sent as the `authorization` header) | +| `HYPERDX_OTLP_ENDPOINT` | OTLP HTTP base URL (default: `https://in-otel.hyperdx.io`) | +| `HYPERDX_SERVICE_NAME` | Override `service.name` | The following variable is also read when resolving `serviceName` (same as the OTLP adapter): @@ -142,10 +142,6 @@ The following variable is also read when resolving `serviceName` (same as the OT |----------|-------------| | `OTEL_SERVICE_NAME` | Fallback for service name (HyperDX SDK examples use this) | -::callout{icon="i-lucide-info" color="info"} -In Nuxt/Nitro, use the `NUXT_` prefix so values are available via `useRuntimeConfig()`. In all other frameworks, use the unprefixed variables. -:: - ### Runtime Config (Nuxt only) Configure via `nuxt.config.ts` for type-safe configuration: @@ -154,8 +150,8 @@ Configure via `nuxt.config.ts` for type-safe configuration: export default defineNuxtConfig({ runtimeConfig: { hyperdx: { - apiKey: '', // Set via NUXT_HYPERDX_API_KEY - // endpoint: '', // Set via NUXT_HYPERDX_OTLP_ENDPOINT + apiKey: '', // Set via HYPERDX_API_KEY + // endpoint: '', // Set via HYPERDX_OTLP_ENDPOINT }, }, }) @@ -236,7 +232,7 @@ Use the HyperDX UI to search and explore wide events: ### Missing apiKey error ```text [Console] -[evlog/hyperdx] Missing apiKey. Set HYPERDX_API_KEY or NUXT_HYPERDX_API_KEY, or pass to createHyperDXDrain() +[evlog/hyperdx] Missing apiKey. Set HYPERDX_API_KEY, or pass to createHyperDXDrain() ``` Make sure your environment variables are set and the server was restarted after adding them. diff --git a/apps/docs/content/3.integrate/adapters/self-hosted/03.memory.md b/apps/docs/content/3.integrate/adapters/self-hosted/03.memory.md index 4e2e6359..56026835 100644 --- a/apps/docs/content/3.integrate/adapters/self-hosted/03.memory.md +++ b/apps/docs/content/3.integrate/adapters/self-hosted/03.memory.md @@ -158,10 +158,10 @@ createMemoryDrain({ store: 'my-service' }) ### Environment Variables -| Variable | Nuxt alias | Description | -|----------|------------|-------------| -| `EVLOG_MEMORY_STORE` | `NUXT_EVLOG_MEMORY_STORE` | Named buffer key (default: `'default'`) | -| `EVLOG_MEMORY_MAX_EVENTS` | `NUXT_EVLOG_MEMORY_MAX_EVENTS` | Ring buffer size (default: `1000`) | +| Variable | Description | +|----------|-------------| +| `EVLOG_MEMORY_STORE` | Named buffer key (default: `'default'`) | +| `EVLOG_MEMORY_MAX_EVENTS` | Ring buffer size (default: `1000`) | Configuration priority matches other adapters: overrides → `runtimeConfig.evlog.memory` → env vars. diff --git a/apps/docs/content/5.extend/8.custom-drains.md b/apps/docs/content/5.extend/8.custom-drains.md index 433d5451..9cb106d3 100644 --- a/apps/docs/content/5.extend/8.custom-drains.md +++ b/apps/docs/content/5.extend/8.custom-drains.md @@ -145,8 +145,7 @@ export function createLokiDrain(overrides?: { url?: string, token?: string }) { 1. Explicit `overrides` passed to your factory 2. `runtimeConfig.evlog.` (Nuxt/Nitro) 3. `runtimeConfig.` (legacy Nuxt/Nitro) -4. `NUXT__` env vars -5. `_` env vars +4. `_` env vars (list `NUXT__` in `ConfigField.env` for silent Nuxt compat; show only `_` in error messages via `formatPublicEnvKeys`) Field names should follow the project conventions: `apiKey`, `endpoint`, `serviceName`, `timeout`. If you're renaming an existing field (e.g. `token` → `apiKey`), keep both as `ConfigField` entries for one major version — see `axiom.ts` and `better-stack.ts` for the deprecation pattern. diff --git a/apps/docs/skills/review-logging-patterns/SKILL.md b/apps/docs/skills/review-logging-patterns/SKILL.md index a1d02819..51ebe54e 100644 --- a/apps/docs/skills/review-logging-patterns/SKILL.md +++ b/apps/docs/skills/review-logging-patterns/SKILL.md @@ -819,12 +819,12 @@ All options work in Nuxt (`evlog` key), Nitro (passed to `evlog()`), Next.js (`c | HyperDX | `evlog/hyperdx` | `HYPERDX_API_KEY` (optional `HYPERDX_OTLP_ENDPOINT`; defaults to `https://in-otel.hyperdx.io`) | | PostHog | `evlog/posthog` | `POSTHOG_API_KEY`, `POSTHOG_HOST` | | Sentry | `evlog/sentry` | `SENTRY_DSN` | -| Better Stack | `evlog/better-stack` | `BETTER_STACK_SOURCE_TOKEN` | +| Better Stack | `evlog/better-stack` | `BETTER_STACK_API_KEY` | | Datadog | `evlog/datadog` | `DD_API_KEY` or `DATADOG_API_KEY`, optional `DD_SITE` / `DATADOG_LOGS_URL` | | File System | `evlog/fs` | None (local file system) | | HTTP (browser ingest) | `evlog/http` | None (configure `endpoint` in code). `evlog/browser` is deprecated; same API, removed next major | -In Nuxt/Nitro, use the `NUXT_` prefix (e.g., `NUXT_AXIOM_API_KEY`) so values are available via `useRuntimeConfig()`. All adapters also read unprefixed variables as fallback. +Use canonical env var names (e.g. `AXIOM_API_KEY`, `BETTER_STACK_API_KEY`) — the same names work in every framework. Setup pattern per framework: diff --git a/packages/evlog/README.md b/packages/evlog/README.md index 91e4c9ae..f0361342 100644 --- a/packages/evlog/README.md +++ b/packages/evlog/README.md @@ -853,8 +853,8 @@ export default defineNitroPlugin((nitroApp) => { Set environment variables: ```bash -NUXT_AXIOM_API_KEY=xaat-your-token -NUXT_AXIOM_DATASET=your-dataset +AXIOM_API_KEY=xaat-your-token +AXIOM_DATASET=your-dataset ``` ### OTLP (OpenTelemetry) @@ -873,7 +873,7 @@ export default defineNitroPlugin((nitroApp) => { Set environment variables: ```bash -NUXT_OTLP_ENDPOINT=http://localhost:4318 +OTLP_ENDPOINT=http://localhost:4318 ``` ### Datadog @@ -890,13 +890,11 @@ export default defineNitroPlugin((nitroApp) => { Set environment variables: ```bash -NUXT_DATADOG_API_KEY=your-api-key +DD_API_KEY=your-api-key # Optional — defaults to datadoghq.com -NUXT_DATADOG_SITE=datadoghq.eu +DD_SITE=datadoghq.eu ``` -You can also use standard Datadog names: `DD_API_KEY` and `DD_SITE`. - 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). ### PostHog @@ -913,8 +911,8 @@ export default defineNitroPlugin((nitroApp) => { Set environment variables: ```bash -NUXT_POSTHOG_API_KEY=phc_your-key -NUXT_POSTHOG_HOST=https://us.i.posthog.com # Optional: for EU or self-hosted +POSTHOG_API_KEY=phc_your-key +POSTHOG_HOST=https://us.i.posthog.com # Optional: for EU or self-hosted ``` ### Sentry @@ -931,7 +929,7 @@ export default defineNitroPlugin((nitroApp) => { Set environment variables: ```bash -NUXT_SENTRY_DSN=https://public@o0.ingest.sentry.io/123 +SENTRY_DSN=https://public@o0.ingest.sentry.io/123 ``` ### Better Stack @@ -948,7 +946,7 @@ export default defineNitroPlugin((nitroApp) => { Set environment variables: ```bash -NUXT_BETTER_STACK_API_KEY=your-source-token +BETTER_STACK_API_KEY=your-source-token ``` ### HyperDX @@ -965,9 +963,9 @@ export default defineNitroPlugin((nitroApp) => { Set environment variables: ```bash -NUXT_HYPERDX_API_KEY=your-api-key +HYPERDX_API_KEY=your-api-key # Optional — defaults to https://in-otel.hyperdx.io -NUXT_HYPERDX_ENDPOINT=https://in-otel.hyperdx.io +HYPERDX_OTLP_ENDPOINT=https://in-otel.hyperdx.io ``` ### File System @@ -986,7 +984,7 @@ export default defineNitroPlugin((nitroApp) => { Set environment variables: ```bash -NUXT_EVLOG_FS_DIR=.evlog/logs +EVLOG_FS_DIR=.evlog/logs ``` ### Memory @@ -1005,8 +1003,8 @@ export default defineNitroPlugin((nitroApp) => { Optional environment variables: ```bash -NUXT_EVLOG_MEMORY_STORE=default -NUXT_EVLOG_MEMORY_MAX_EVENTS=1000 +EVLOG_MEMORY_STORE=default +EVLOG_MEMORY_MAX_EVENTS=1000 ``` Pair with `readMemoryLogs()` for dev-only agent access over HTTP. See the [Memory adapter docs](https://www.evlog.dev/integrate/adapters/self-hosted/memory). diff --git a/packages/evlog/src/adapters/axiom.ts b/packages/evlog/src/adapters/axiom.ts index a60f6335..2a2ac038 100644 --- a/packages/evlog/src/adapters/axiom.ts +++ b/packages/evlog/src/adapters/axiom.ts @@ -1,6 +1,6 @@ import type { WideEvent } from '../types' import type { ConfigField } from '../shared/config' -import { applyDeprecatedAlias, resolveAdapterConfig } from '../shared/config' +import { applyDeprecatedAlias, formatPublicEnvKeys, resolveAdapterConfig } from '../shared/config' import { defineHttpDrain } from '../shared/drain' import { httpPost } from '../shared/http' @@ -68,7 +68,6 @@ function applyApiKeyAlias(config: Partial): Partial): Partial) { ) const config = applyApiKeyAlias(resolved) if (!config.dataset || !config.apiKey) { - console.error('[evlog/axiom] Missing dataset or apiKey. Set NUXT_AXIOM_API_KEY/NUXT_AXIOM_DATASET env vars or pass to createAxiomDrain()') + console.error(`[evlog/axiom] Missing dataset or apiKey. Set ${formatPublicEnvKeys(['NUXT_AXIOM_API_KEY', 'AXIOM_API_KEY'], ['NUXT_AXIOM_DATASET', 'AXIOM_DATASET'])} env vars or pass to createAxiomDrain()`) return null } if (config.edgeUrl && config.baseUrl) { diff --git a/packages/evlog/src/adapters/better-stack.ts b/packages/evlog/src/adapters/better-stack.ts index d94d7a5a..083cfb16 100644 --- a/packages/evlog/src/adapters/better-stack.ts +++ b/packages/evlog/src/adapters/better-stack.ts @@ -1,6 +1,6 @@ import type { WideEvent } from '../types' import type { ConfigField } from '../shared/config' -import { applyDeprecatedAlias, resolveAdapterConfig } from '../shared/config' +import { applyDeprecatedAlias, formatPublicEnvKeys, resolveAdapterConfig } from '../shared/config' import { defineHttpDrain } from '../shared/drain' import { httpPost } from '../shared/http' @@ -34,7 +34,6 @@ function applyApiKeyAlias(config: BetterStackConfig): BetterStackConfig { adapter: 'better-stack', from: 'sourceToken', to: 'apiKey', - envHint: 'Env: NUXT_BETTER_STACK_SOURCE_TOKEN/BETTER_STACK_SOURCE_TOKEN → NUXT_BETTER_STACK_API_KEY/BETTER_STACK_API_KEY.', }) } @@ -54,7 +53,7 @@ export function toBetterStackEvent(event: WideEvent): Record { * 1. Overrides passed to createBetterStackDrain() * 2. runtimeConfig.evlog.betterStack * 3. runtimeConfig.betterStack - * 4. Environment variables: NUXT_BETTER_STACK_API_KEY, BETTER_STACK_API_KEY (or legacy `*_SOURCE_TOKEN`) + * 4. Environment variables: BETTER_STACK_API_KEY (or legacy `BETTER_STACK_SOURCE_TOKEN`) * * @example * ```ts @@ -70,7 +69,7 @@ export function createBetterStackDrain(overrides?: Partial) { const resolved = await resolveAdapterConfig('betterStack', BETTER_STACK_FIELDS, overrides) const config = applyApiKeyAlias(resolved as BetterStackConfig) if (!config.apiKey) { - console.error('[evlog/better-stack] Missing apiKey. Set NUXT_BETTER_STACK_API_KEY env var or pass to createBetterStackDrain()') + console.error(`[evlog/better-stack] Missing apiKey. Set ${formatPublicEnvKeys(['NUXT_BETTER_STACK_API_KEY', 'BETTER_STACK_API_KEY'])} env var or pass to createBetterStackDrain()`) return null } return config diff --git a/packages/evlog/src/adapters/datadog.ts b/packages/evlog/src/adapters/datadog.ts index a17e0ae0..3f0175b1 100644 --- a/packages/evlog/src/adapters/datadog.ts +++ b/packages/evlog/src/adapters/datadog.ts @@ -1,6 +1,6 @@ import type { WideEvent } from '../types' import type { ConfigField } from '../shared/config' -import { resolveAdapterConfig } from '../shared/config' +import { formatPublicEnvKeys, resolveAdapterConfig } from '../shared/config' import { defineHttpDrain } from '../shared/drain' import { httpPost } from '../shared/http' @@ -143,11 +143,11 @@ export function resolveDatadogIntakeUrl(config: Pick) { resolve: async () => { const config = await resolveAdapterConfig('datadog', DATADOG_FIELDS, overrides) if (!config.apiKey) { - console.error('[evlog/datadog] Missing API key. Set NUXT_DATADOG_API_KEY, DATADOG_API_KEY, or DD_API_KEY, or pass apiKey to createDatadogDrain()') + console.error(`[evlog/datadog] Missing API key. Set ${formatPublicEnvKeys(['NUXT_DATADOG_API_KEY', 'DATADOG_API_KEY', 'DD_API_KEY'])}, or pass apiKey to createDatadogDrain()`) return null } return config as DatadogConfig diff --git a/packages/evlog/src/adapters/hyperdx.ts b/packages/evlog/src/adapters/hyperdx.ts index ea4754ab..b8e38f7c 100644 --- a/packages/evlog/src/adapters/hyperdx.ts +++ b/packages/evlog/src/adapters/hyperdx.ts @@ -1,6 +1,6 @@ import type { WideEvent } from '../types' import type { ConfigField } from '../shared/config' -import { resolveAdapterConfig } from '../shared/config' +import { formatPublicEnvKeys, resolveAdapterConfig } from '../shared/config' import { defineDrain } from '../shared/drain' import type { OTLPConfig } from './otlp' import { sendBatchToOTLP } from './otlp' @@ -70,7 +70,7 @@ export function toHyperDXOTLPConfig(config: HyperDXConfig): OTLPConfig { * 1. Overrides passed to `createHyperDXDrain()` * 2. `runtimeConfig.evlog.hyperdx` * 3. `runtimeConfig.hyperdx` - * 4. Environment variables: `NUXT_HYPERDX_*`, `HYPERDX_*` (and `OTEL_SERVICE_NAME` for service name) + * 4. Environment variables: `HYPERDX_*` (and `OTEL_SERVICE_NAME` for service name) * * @example * ```ts @@ -84,7 +84,7 @@ export function createHyperDXDrain(overrides?: Partial) { resolve: async () => { const config = await resolveAdapterConfig('hyperdx', HYPERDX_FIELDS, overrides) if (!config.apiKey) { - console.error('[evlog/hyperdx] Missing apiKey. Set HYPERDX_API_KEY or NUXT_HYPERDX_API_KEY, or pass to createHyperDXDrain()') + console.error(`[evlog/hyperdx] Missing apiKey. Set ${formatPublicEnvKeys(['NUXT_HYPERDX_API_KEY', 'HYPERDX_API_KEY'])}, or pass to createHyperDXDrain()`) return null } return config as HyperDXConfig diff --git a/packages/evlog/src/adapters/memory.ts b/packages/evlog/src/adapters/memory.ts index b77cbe81..5f71f142 100644 --- a/packages/evlog/src/adapters/memory.ts +++ b/packages/evlog/src/adapters/memory.ts @@ -72,8 +72,7 @@ export function writeToMemory(events: WideEvent[], config: MemoryConfig): void { * Configuration priority (highest to lowest): * 1. Overrides passed to `createMemoryDrain()` * 2. `runtimeConfig.evlog.memory` / `runtimeConfig.memory` (Nitro) - * 3. Environment variables: `NUXT_EVLOG_MEMORY_STORE`, `EVLOG_MEMORY_STORE`, - * `NUXT_EVLOG_MEMORY_MAX_EVENTS`, `EVLOG_MEMORY_MAX_EVENTS` + * 3. Environment variables: `EVLOG_MEMORY_STORE`, `EVLOG_MEMORY_MAX_EVENTS` * * @example * ```ts diff --git a/packages/evlog/src/adapters/otlp.ts b/packages/evlog/src/adapters/otlp.ts index 353cbf1c..4fc17948 100644 --- a/packages/evlog/src/adapters/otlp.ts +++ b/packages/evlog/src/adapters/otlp.ts @@ -1,6 +1,6 @@ import type { WideEvent } from '../types' import type { ConfigField } from '../shared/config' -import { resolveAdapterConfig } from '../shared/config' +import { formatPublicEnvKeys, resolveAdapterConfig } from '../shared/config' import { defineHttpDrain } from '../shared/drain' import { toOtlpAttributeValue } from '../shared/event' import { httpPost } from '../shared/http' @@ -61,7 +61,7 @@ interface ExportLogsServiceRequest { } const OTLP_FIELDS: ConfigField[] = [ - { key: 'endpoint', env: ['NUXT_OTLP_ENDPOINT', 'OTEL_EXPORTER_OTLP_ENDPOINT'] }, + { key: 'endpoint', env: ['NUXT_OTLP_ENDPOINT', 'OTEL_EXPORTER_OTLP_ENDPOINT', 'OTLP_ENDPOINT'] }, { key: 'serviceName', env: ['NUXT_OTLP_SERVICE_NAME', 'OTEL_SERVICE_NAME'] }, { key: 'headers' }, { key: 'resourceAttributes' }, @@ -182,10 +182,10 @@ function buildResourceAttributes( /** * Build headers from OTEL env vars. - * Kept inline as OTLP-specific (parses OTEL_EXPORTER_OTLP_HEADERS=key=val,key=val). + * Kept inline as OTLP-specific (parses OTEL_EXPORTER_OTLP_HEADERS / OTLP_HEADERS as key=val,key=val). */ function getHeadersFromEnv(): Record | undefined { - const headersEnv = process.env.OTEL_EXPORTER_OTLP_HEADERS || process.env.NUXT_OTLP_HEADERS + const headersEnv = process.env.OTEL_EXPORTER_OTLP_HEADERS || process.env.OTLP_HEADERS || process.env.NUXT_OTLP_HEADERS if (headersEnv) { const headers: Record = {} const decoded = decodeURIComponent(headersEnv) @@ -215,9 +215,9 @@ function getHeadersFromEnv(): Record | undefined { * * Configuration priority (highest to lowest): * 1. Overrides passed to createOTLPDrain() - * 2. runtimeConfig.evlog.otlp (NUXT_EVLOG_OTLP_*) - * 3. runtimeConfig.otlp (NUXT_OTLP_*) - * 4. Environment variables: OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME + * 2. runtimeConfig.evlog.otlp + * 3. runtimeConfig.otlp + * 4. Environment variables: OTEL_EXPORTER_OTLP_ENDPOINT (or OTLP_ENDPOINT), OTEL_SERVICE_NAME * * @example * ```ts @@ -242,7 +242,7 @@ export function createOTLPDrain(overrides?: Partial) { } if (!config.endpoint) { - console.error('[evlog/otlp] Missing endpoint. Set NUXT_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT env var, or pass to createOTLPDrain()') + console.error(`[evlog/otlp] Missing endpoint. Set ${formatPublicEnvKeys(['NUXT_OTLP_ENDPOINT', 'OTEL_EXPORTER_OTLP_ENDPOINT', 'OTLP_ENDPOINT'])} env var, or pass to createOTLPDrain()`) return null } return config as OTLPConfig diff --git a/packages/evlog/src/adapters/posthog.ts b/packages/evlog/src/adapters/posthog.ts index 8d2b4cc2..3aefd23e 100644 --- a/packages/evlog/src/adapters/posthog.ts +++ b/packages/evlog/src/adapters/posthog.ts @@ -1,6 +1,6 @@ import type { WideEvent } from '../types' import type { ConfigField } from '../shared/config' -import { resolveAdapterConfig } from '../shared/config' +import { formatPublicEnvKeys, resolveAdapterConfig } from '../shared/config' import { defineDrain, defineHttpDrain } from '../shared/drain' import { httpPost } from '../shared/http' import { sendBatchToOTLP } from './otlp' @@ -109,7 +109,7 @@ export function toPostHogEvent(event: WideEvent, config: PostHogConfig): PostHog * 1. Overrides passed to createPostHogDrain() * 2. runtimeConfig.evlog.posthog * 3. runtimeConfig.posthog - * 4. Environment variables: NUXT_POSTHOG_*, POSTHOG_* + * 4. Environment variables: POSTHOG_* * * @example * ```ts @@ -129,7 +129,7 @@ export function createPostHogDrain(overrides?: Partial) { resolve: async () => { const config = await resolveAdapterConfig('posthog', POSTHOG_FIELDS, overrides) if (!config.apiKey) { - console.error('[evlog/posthog-events] Missing apiKey. Set NUXT_POSTHOG_API_KEY env var or pass to createPostHogDrain({ mode: \'events\' })') + console.error(`[evlog/posthog-events] Missing apiKey. Set ${formatPublicEnvKeys(['NUXT_POSTHOG_API_KEY', 'POSTHOG_API_KEY'])} env var or pass to createPostHogDrain({ mode: 'events' })`) return null } return config as PostHogConfig @@ -150,7 +150,7 @@ export function createPostHogDrain(overrides?: Partial) { resolve: async () => { const config = await resolveAdapterConfig('posthog', POSTHOG_FIELDS, overrides) if (!config.apiKey) { - console.error('[evlog/posthog] Missing apiKey. Set NUXT_POSTHOG_API_KEY env var or pass to createPostHogDrain()') + console.error(`[evlog/posthog] Missing apiKey. Set ${formatPublicEnvKeys(['NUXT_POSTHOG_API_KEY', 'POSTHOG_API_KEY'])} env var or pass to createPostHogDrain()`) return null } return config as PostHogConfig diff --git a/packages/evlog/src/adapters/sentry.ts b/packages/evlog/src/adapters/sentry.ts index d52cf847..0b5ed647 100644 --- a/packages/evlog/src/adapters/sentry.ts +++ b/packages/evlog/src/adapters/sentry.ts @@ -1,6 +1,6 @@ import type { WideEvent } from '../types' import type { ConfigField } from '../shared/config' -import { resolveAdapterConfig } from '../shared/config' +import { formatPublicEnvKeys, resolveAdapterConfig } from '../shared/config' import { defineHttpDrain } from '../shared/drain' import { httpPost } from '../shared/http' import { OTEL_SEVERITY_NUMBER } from '../shared/severity' @@ -208,11 +208,11 @@ function buildEnvelopeBody(logs: SentryLog[], dsn: string): string { * 1. Overrides passed to createSentryDrain() * 2. runtimeConfig.evlog.sentry * 3. runtimeConfig.sentry - * 4. Environment variables: NUXT_SENTRY_*, SENTRY_* + * 4. Environment variables: SENTRY_* * * @example * ```ts - * // Zero config - just set NUXT_SENTRY_DSN env var + * // Zero config - just set SENTRY_DSN env var * nitroApp.hooks.hook('evlog:drain', createSentryDrain()) * * // With overrides @@ -227,7 +227,7 @@ export function createSentryDrain(overrides?: Partial) { resolve: async () => { const config = await resolveAdapterConfig('sentry', SENTRY_FIELDS, overrides) if (!config.dsn) { - console.error('[evlog/sentry] Missing DSN. Set NUXT_SENTRY_DSN/SENTRY_DSN env var or pass to createSentryDrain()') + console.error(`[evlog/sentry] Missing DSN. Set ${formatPublicEnvKeys(['NUXT_SENTRY_DSN', 'SENTRY_DSN'])} env var or pass to createSentryDrain()`) return null } return config as SentryConfig diff --git a/packages/evlog/src/shared/config.ts b/packages/evlog/src/shared/config.ts index de9f8014..80bf6426 100644 --- a/packages/evlog/src/shared/config.ts +++ b/packages/evlog/src/shared/config.ts @@ -46,6 +46,22 @@ export async function resolveAdapterConfig( return config as Partial } +/** + * Format environment variable names for user-facing messages. + * Drops internal `NUXT_*` aliases; joins alternatives with `, ` and fields with `/`. + */ +export function formatPublicEnvKeys(...envLists: (string[] | undefined)[]): string { + return envLists + .map((list) => { + const all = (list ?? []).filter((k): k is string => !!k) + const publicKeys = all.filter(k => !k.startsWith('NUXT_')) + const keys = [...new Set(publicKeys.length > 0 ? publicKeys : all)] + return keys.join(', ') + }) + .filter(part => part.length > 0) + .join('/') +} + const warnedDeprecatedAliases = new Set() /** @@ -54,7 +70,11 @@ const warnedDeprecatedAliases = new Set() */ export function applyDeprecatedAlias( config: T, - opts: { adapter: string, from: keyof T & string, to: keyof T & string, envHint?: string }, + opts: { + adapter: string + from: keyof T & string + to: keyof T & string + }, ): T { const record = config as Record if (record[opts.to] === undefined || record[opts.to] === null) { @@ -63,7 +83,7 @@ export function applyDeprecatedAlias( const warnKey = `${opts.adapter}:${opts.from}` if (!warnedDeprecatedAliases.has(warnKey)) { warnedDeprecatedAliases.add(warnKey) - console.warn(`[evlog/${opts.adapter}] \`${opts.from}\` is deprecated, use \`${opts.to}\` instead.${opts.envHint ? ` (${opts.envHint})` : ''}`) + console.warn(`[evlog/${opts.adapter}] \`${opts.from}\` is deprecated, use \`${opts.to}\` instead.`) } record[opts.to] = fromValue } diff --git a/packages/evlog/test/adapters/axiom.test.ts b/packages/evlog/test/adapters/axiom.test.ts index 8b983e3e..89b3e473 100644 --- a/packages/evlog/test/adapters/axiom.test.ts +++ b/packages/evlog/test/adapters/axiom.test.ts @@ -272,12 +272,22 @@ describe('axiom adapter', () => { const drain = createAxiomDrain() await drain(createDrainContext()) expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining('[evlog/axiom] Missing dataset or apiKey'), + expect.stringContaining('Set AXIOM_API_KEY/AXIOM_DATASET env vars'), ) expect(fetchSpy).not.toHaveBeenCalled() }) + it('warns when legacy token alias is used', async () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + const drain = createAxiomDrain({ token: 'legacy-key', dataset: 'logs' }) + await drain(createDrainContext()) + expect(warnSpy).toHaveBeenCalledWith( + '[evlog/axiom] `token` is deprecated, use `apiKey` instead.', + ) + }) + it('accepts legacy token alias', async () => { + vi.spyOn(console, 'warn').mockImplementation(() => {}) const drain = createAxiomDrain({ token: 'legacy-key', dataset: 'logs' }) await drain(createDrainContext()) const [, options] = fetchSpy.mock.calls[0] as [string, RequestInit] diff --git a/packages/evlog/test/adapters/better-stack.test.ts b/packages/evlog/test/adapters/better-stack.test.ts index 9ed65543..d1d51540 100644 --- a/packages/evlog/test/adapters/better-stack.test.ts +++ b/packages/evlog/test/adapters/better-stack.test.ts @@ -223,12 +223,22 @@ describe('better-stack adapter', () => { const drain = createBetterStackDrain() await drain(createDrainContext()) expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining('[evlog/better-stack] Missing apiKey'), + expect.stringContaining('Set BETTER_STACK_API_KEY env var'), ) expect(fetchSpy).not.toHaveBeenCalled() }) + it('warns when legacy sourceToken alias is used', async () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + const drain = createBetterStackDrain({ sourceToken: 'legacy-key' }) + await drain(createDrainContext()) + expect(warnSpy).toHaveBeenCalledWith( + '[evlog/better-stack] `sourceToken` is deprecated, use `apiKey` instead.', + ) + }) + it('accepts legacy sourceToken alias', async () => { + vi.spyOn(console, 'warn').mockImplementation(() => {}) const drain = createBetterStackDrain({ sourceToken: 'legacy-key' }) await drain(createDrainContext()) const [, options] = fetchSpy.mock.calls[0] as [string, RequestInit] diff --git a/packages/evlog/test/adapters/otlp.test.ts b/packages/evlog/test/adapters/otlp.test.ts index 53701ad3..0aface96 100644 --- a/packages/evlog/test/adapters/otlp.test.ts +++ b/packages/evlog/test/adapters/otlp.test.ts @@ -431,20 +431,25 @@ describe('otlp adapter', () => { }) let origNuxtOtlpEndpoint: string | undefined + let origOtelOtlpEndpoint: string | undefined let origOtlpEndpoint: string | undefined beforeEach(() => { origNuxtOtlpEndpoint = process.env.NUXT_OTLP_ENDPOINT - origOtlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT + origOtelOtlpEndpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT + origOtlpEndpoint = process.env.OTLP_ENDPOINT delete process.env.NUXT_OTLP_ENDPOINT delete process.env.OTEL_EXPORTER_OTLP_ENDPOINT + delete process.env.OTLP_ENDPOINT }) afterEach(() => { if (origNuxtOtlpEndpoint === undefined) delete process.env.NUXT_OTLP_ENDPOINT else process.env.NUXT_OTLP_ENDPOINT = origNuxtOtlpEndpoint - if (origOtlpEndpoint === undefined) delete process.env.OTEL_EXPORTER_OTLP_ENDPOINT - else process.env.OTEL_EXPORTER_OTLP_ENDPOINT = origOtlpEndpoint + if (origOtelOtlpEndpoint === undefined) delete process.env.OTEL_EXPORTER_OTLP_ENDPOINT + else process.env.OTEL_EXPORTER_OTLP_ENDPOINT = origOtelOtlpEndpoint + if (origOtlpEndpoint === undefined) delete process.env.OTLP_ENDPOINT + else process.env.OTLP_ENDPOINT = origOtlpEndpoint }) it('returns a callable drain that posts events', async () => { @@ -453,6 +458,13 @@ describe('otlp adapter', () => { expect(fetchSpy).toHaveBeenCalledOnce() }) + it('resolves the endpoint from the OTLP_ENDPOINT alias', async () => { + process.env.OTLP_ENDPOINT = 'http://localhost:4318' + const drain = createOTLPDrain() + await drain(createDrainContext()) + expect(fetchSpy).toHaveBeenCalledOnce() + }) + it('logs error and skips fetch when endpoint is missing', async () => { const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) const drain = createOTLPDrain() diff --git a/packages/evlog/test/shared/config.test.ts b/packages/evlog/test/shared/config.test.ts new file mode 100644 index 00000000..229b98d5 --- /dev/null +++ b/packages/evlog/test/shared/config.test.ts @@ -0,0 +1,72 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { applyDeprecatedAlias, formatPublicEnvKeys } from '../../src/shared/config' + +describe('formatPublicEnvKeys', () => { + it('drops NUXT_* aliases and keeps canonical names', () => { + expect(formatPublicEnvKeys(['NUXT_BETTER_STACK_API_KEY', 'BETTER_STACK_API_KEY'])) + .toBe('BETTER_STACK_API_KEY') + }) + + it('joins alternatives with commas and required fields with slashes', () => { + expect(formatPublicEnvKeys( + ['NUXT_AXIOM_API_KEY', 'AXIOM_API_KEY'], + ['NUXT_AXIOM_DATASET', 'AXIOM_DATASET'], + )).toBe('AXIOM_API_KEY/AXIOM_DATASET') + }) + + it('lists multiple aliases for the same field', () => { + expect(formatPublicEnvKeys(['NUXT_DATADOG_API_KEY', 'DATADOG_API_KEY', 'DD_API_KEY'])) + .toBe('DATADOG_API_KEY, DD_API_KEY') + }) + + it('falls back to NUXT_* when no public keys exist', () => { + expect(formatPublicEnvKeys(['NUXT_ONLY_KEY'])) + .toBe('NUXT_ONLY_KEY') + }) +}) + +describe('applyDeprecatedAlias', () => { + afterEach(() => { + vi.restoreAllMocks() + }) + + it('warns about the deprecated config field only', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + applyDeprecatedAlias( + { sourceToken: 'legacy' }, + { + adapter: 'test-config-warn', + from: 'sourceToken', + to: 'apiKey', + }, + ) + expect(warnSpy).toHaveBeenCalledWith( + '[evlog/test-config-warn] `sourceToken` is deprecated, use `apiKey` instead.', + ) + }) + + it('copies the deprecated value onto the replacement', () => { + vi.spyOn(console, 'warn').mockImplementation(() => {}) + const result = applyDeprecatedAlias( + { token: 'xaat-legacy' }, + { + adapter: 'test-config-copy', + from: 'token', + to: 'apiKey', + }, + ) + expect(result).toEqual({ token: 'xaat-legacy', apiKey: 'xaat-legacy' }) + }) + + it('warns only once per adapter/field pair', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + const opts = { + adapter: 'test-config-once', + from: 'sourceToken' as const, + to: 'apiKey' as const, + } + applyDeprecatedAlias({ sourceToken: 'a' }, opts) + applyDeprecatedAlias({ sourceToken: 'b' }, opts) + expect(warnSpy).toHaveBeenCalledOnce() + }) +}) diff --git a/packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap b/packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap index 5bcfde30..26c2b58c 100644 --- a/packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap +++ b/packages/evlog/test/toolkit/__snapshots__/api-surface.test.ts.snap @@ -241,6 +241,7 @@ exports[`public API surface > matches snapshot for all subpath exports 1`] = ` "extractSafeHeaders", "extractSafeNodeHeaders", "forkBackgroundLogger", + "formatPublicEnvKeys", "getEmptyPluginRunner", "getHeader", "getRuntimeConfig",