evlog 2.14.0 → 2.15.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/LICENSE +21 -0
- package/README.md +49 -19
- package/dist/adapters/axiom.d.mts +18 -27
- package/dist/adapters/axiom.d.mts.map +1 -1
- package/dist/adapters/axiom.mjs +40 -30
- package/dist/adapters/axiom.mjs.map +1 -1
- package/dist/adapters/better-stack.d.mts +11 -24
- package/dist/adapters/better-stack.d.mts.map +1 -1
- package/dist/adapters/better-stack.mjs +34 -29
- package/dist/adapters/better-stack.mjs.map +1 -1
- package/dist/adapters/datadog.d.mts +1 -1
- package/dist/adapters/datadog.d.mts.map +1 -1
- package/dist/adapters/datadog.mjs +10 -4
- package/dist/adapters/datadog.mjs.map +1 -1
- package/dist/adapters/fs.d.mts +2 -2
- package/dist/adapters/fs.d.mts.map +1 -1
- package/dist/adapters/fs.mjs +19 -7
- package/dist/adapters/fs.mjs.map +1 -1
- package/dist/adapters/hyperdx.d.mts +1 -1
- package/dist/adapters/hyperdx.mjs +1 -2
- package/dist/adapters/hyperdx.mjs.map +1 -1
- package/dist/adapters/otlp.d.mts +1 -1
- package/dist/adapters/otlp.d.mts.map +1 -1
- package/dist/adapters/otlp.mjs +36 -31
- package/dist/adapters/otlp.mjs.map +1 -1
- package/dist/adapters/posthog.d.mts +50 -70
- package/dist/adapters/posthog.d.mts.map +1 -1
- package/dist/adapters/posthog.mjs +50 -85
- package/dist/adapters/posthog.mjs.map +1 -1
- package/dist/adapters/sentry.d.mts +1 -1
- package/dist/adapters/sentry.d.mts.map +1 -1
- package/dist/adapters/sentry.mjs +15 -5
- package/dist/adapters/sentry.mjs.map +1 -1
- package/dist/ai/index.d.mts +1 -1
- package/dist/{audit-d9esRZOK.mjs → audit--n0QRR2Y.mjs} +202 -20
- package/dist/audit--n0QRR2Y.mjs.map +1 -0
- package/dist/{audit-mUutdf6A.d.mts → audit-CJl-wZ10.d.mts} +144 -2
- package/dist/audit-CJl-wZ10.d.mts.map +1 -0
- package/dist/better-auth/index.d.mts +1 -1
- package/dist/better-auth/index.mjs.map +1 -1
- package/dist/browser.d.mts +1 -1
- package/dist/define-D6OJdSUH.mjs +63 -0
- package/dist/define-D6OJdSUH.mjs.map +1 -0
- package/dist/define-Fp8TrdEB.d.mts +57 -0
- package/dist/define-Fp8TrdEB.d.mts.map +1 -0
- package/dist/{dist-Do8P4zWd.mjs → dist-BIlS38vi.mjs} +1 -1
- package/dist/dist-BIlS38vi.mjs.map +1 -0
- package/dist/drain-ByWUeOQC.mjs +160 -0
- package/dist/drain-ByWUeOQC.mjs.map +1 -0
- package/dist/elysia/index.d.mts +25 -2
- package/dist/elysia/index.d.mts.map +1 -1
- package/dist/elysia/index.mjs +53 -20
- package/dist/elysia/index.mjs.map +1 -1
- package/dist/enricher-BA6viylF.mjs +95 -0
- package/dist/enricher-BA6viylF.mjs.map +1 -0
- package/dist/enricher-CLSnrzrr.d.mts +42 -0
- package/dist/enricher-CLSnrzrr.d.mts.map +1 -0
- package/dist/enrichers.d.mts +16 -9
- package/dist/enrichers.d.mts.map +1 -1
- package/dist/enrichers.mjs +81 -64
- package/dist/enrichers.mjs.map +1 -1
- package/dist/{error-D1FZI2Kd.d.mts → error-C-66_G2M.d.mts} +2 -2
- package/dist/{error-D1FZI2Kd.d.mts.map → error-C-66_G2M.d.mts.map} +1 -1
- package/dist/error.d.mts +1 -1
- package/dist/{errors-BJRXUfMg.mjs → errors-BQgyQ9xe.mjs} +1 -1
- package/dist/{errors-BJRXUfMg.mjs.map → errors-BQgyQ9xe.mjs.map} +1 -1
- package/dist/{errors-NIXCyk6I.d.mts → errors-DQoYsDW1.d.mts} +2 -2
- package/dist/{errors-NIXCyk6I.d.mts.map → errors-DQoYsDW1.d.mts.map} +1 -1
- package/dist/event-ef-5Dbxg.mjs +53 -0
- package/dist/event-ef-5Dbxg.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 +17 -15
- package/dist/express/index.mjs.map +1 -1
- package/dist/fastify/index.d.mts +2 -2
- package/dist/fastify/index.d.mts.map +1 -1
- package/dist/fastify/index.mjs +19 -20
- package/dist/fastify/index.mjs.map +1 -1
- package/dist/fork-D44V93-K.mjs +227 -0
- package/dist/fork-D44V93-K.mjs.map +1 -0
- package/dist/{headers-D74M0wsg.mjs → headers-CU-QqnYg.mjs} +19 -2
- package/dist/headers-CU-QqnYg.mjs.map +1 -0
- package/dist/hono/index.d.mts +2 -2
- package/dist/hono/index.d.mts.map +1 -1
- package/dist/hono/index.mjs +14 -10
- package/dist/hono/index.mjs.map +1 -1
- package/dist/http.d.mts +1 -1
- package/dist/http.d.mts.map +1 -1
- package/dist/http.mjs +3 -2
- package/dist/http.mjs.map +1 -1
- package/dist/index.d.mts +8 -7
- package/dist/index.mjs +3 -2
- package/dist/integration-Bz8X6_Lb.mjs +75 -0
- package/dist/integration-Bz8X6_Lb.mjs.map +1 -0
- package/dist/{logger-b3epPH0N.d.mts → logger-Brt5-WMK.d.mts} +28 -3
- package/dist/logger-Brt5-WMK.d.mts.map +1 -0
- package/dist/logger.d.mts +2 -2
- package/dist/logger.mjs +2 -2
- package/dist/middleware-CGM-bOvE.d.mts +72 -0
- package/dist/middleware-CGM-bOvE.d.mts.map +1 -0
- package/dist/nestjs/index.d.mts +2 -2
- package/dist/nestjs/index.mjs +3 -4
- 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/index.mjs.map +1 -1
- package/dist/next/instrumentation.d.mts +1 -1
- package/dist/next/instrumentation.mjs +1 -1
- package/dist/next/instrumentation.mjs.map +1 -1
- package/dist/nitro/errorHandler.mjs +2 -2
- package/dist/nitro/errorHandler.mjs.map +1 -1
- package/dist/nitro/module.d.mts +2 -2
- package/dist/nitro/plugin.mjs +21 -11
- package/dist/nitro/plugin.mjs.map +1 -1
- package/dist/nitro/v3/errorHandler.mjs +3 -3
- package/dist/nitro/v3/index.d.mts +2 -2
- package/dist/nitro/v3/middleware.mjs.map +1 -1
- package/dist/nitro/v3/module.d.mts +1 -1
- package/dist/nitro/v3/plugin.mjs +29 -17
- package/dist/nitro/v3/plugin.mjs.map +1 -1
- package/dist/nitro/v3/useLogger.d.mts +1 -1
- package/dist/nitro/v3/useLogger.mjs.map +1 -1
- package/dist/{nitro-DenB86W6.d.mts → nitro-DHPb9dXG.d.mts} +2 -2
- package/dist/{nitro-DenB86W6.d.mts.map → nitro-DHPb9dXG.d.mts.map} +1 -1
- package/dist/{nitro-OmT_M4Pb.mjs → nitro-DavLelNz.mjs} +2 -2
- package/dist/nitro-DavLelNz.mjs.map +1 -0
- package/dist/{nitroConfigBridge-C37lXaNm.mjs → nitroConfigBridge-aZ1e5upQ.mjs} +1 -1
- package/dist/nitroConfigBridge-aZ1e5upQ.mjs.map +1 -0
- package/dist/nuxt/module.d.mts +1 -1
- package/dist/nuxt/module.mjs +2 -2
- package/dist/{parseError-BR9pocvY.d.mts → parseError-B1zJZvQ5.d.mts} +2 -2
- package/dist/parseError-B1zJZvQ5.d.mts.map +1 -0
- package/dist/pipeline.mjs.map +1 -1
- package/dist/react-router/index.d.mts +2 -2
- package/dist/react-router/index.mjs +3 -4
- package/dist/react-router/index.mjs.map +1 -1
- package/dist/{routes-CGPmbzCZ.mjs → routes-B48wm7Pb.mjs} +1 -1
- package/dist/{routes-CGPmbzCZ.mjs.map → routes-B48wm7Pb.mjs.map} +1 -1
- package/dist/runtime/client/log.d.mts +1 -1
- package/dist/runtime/client/log.mjs +2 -2
- package/dist/runtime/client/log.mjs.map +1 -1
- package/dist/runtime/client/plugin.mjs.map +1 -1
- package/dist/runtime/server/routes/_evlog/ingest.post.mjs +21 -10
- package/dist/runtime/server/routes/_evlog/ingest.post.mjs.map +1 -1
- package/dist/runtime/server/useLogger.d.mts +1 -1
- package/dist/runtime/server/useLogger.mjs.map +1 -1
- package/dist/runtime/utils/parseError.d.mts +2 -2
- package/dist/runtime/utils/parseError.mjs +1 -1
- package/dist/{_severity-CQijvfhU.mjs → severity-BYWZ96Sb.mjs} +6 -2
- package/dist/severity-BYWZ96Sb.mjs.map +1 -0
- package/dist/{source-location-DRvDDqfq.mjs → source-location-Dco0cRTz.mjs} +3 -3
- package/dist/source-location-Dco0cRTz.mjs.map +1 -0
- package/dist/storage-BT-3fT1-.mjs +27 -0
- package/dist/storage-BT-3fT1-.mjs.map +1 -0
- package/dist/sveltekit/index.d.mts +2 -2
- package/dist/sveltekit/index.mjs +5 -6
- package/dist/sveltekit/index.mjs.map +1 -1
- package/dist/toolkit.d.mts +288 -12
- package/dist/toolkit.d.mts.map +1 -1
- package/dist/toolkit.mjs +13 -7
- package/dist/types.d.mts +1 -1
- package/dist/{useLogger-C56tDPwf.d.mts → useLogger-Cb1R6bQE.d.mts} +2 -2
- package/dist/{useLogger-C56tDPwf.d.mts.map → useLogger-Cb1R6bQE.d.mts.map} +1 -1
- package/dist/{utils-DzGCLRFe.d.mts → utils-gQCeZMbg.d.mts} +4 -3
- package/dist/utils-gQCeZMbg.d.mts.map +1 -0
- package/dist/utils.d.mts +2 -2
- package/dist/utils.mjs +7 -1
- package/dist/utils.mjs.map +1 -1
- package/dist/vite/index.d.mts +1 -1
- package/dist/vite/index.mjs +1 -1
- package/dist/vite/index.mjs.map +1 -1
- package/dist/workers.d.mts +48 -4
- package/dist/workers.d.mts.map +1 -1
- package/dist/workers.mjs +32 -5
- package/dist/workers.mjs.map +1 -1
- package/package.json +38 -39
- package/dist/_drain-CmCtsuF6.mjs +0 -23
- package/dist/_drain-CmCtsuF6.mjs.map +0 -1
- package/dist/_http-CHSsrWDJ.mjs +0 -71
- package/dist/_http-CHSsrWDJ.mjs.map +0 -1
- package/dist/_severity-CQijvfhU.mjs.map +0 -1
- package/dist/audit-d9esRZOK.mjs.map +0 -1
- package/dist/audit-mUutdf6A.d.mts.map +0 -1
- package/dist/dist-Do8P4zWd.mjs.map +0 -1
- package/dist/fork-CTJXnpl8.mjs +0 -72
- package/dist/fork-CTJXnpl8.mjs.map +0 -1
- package/dist/headers-D74M0wsg.mjs.map +0 -1
- package/dist/logger-b3epPH0N.d.mts.map +0 -1
- package/dist/middleware-BWOJ7JI0.mjs +0 -123
- package/dist/middleware-BWOJ7JI0.mjs.map +0 -1
- package/dist/middleware-BYf26Lfu.d.mts +0 -93
- package/dist/middleware-BYf26Lfu.d.mts.map +0 -1
- package/dist/nitro-OmT_M4Pb.mjs.map +0 -1
- package/dist/nitroConfigBridge-C37lXaNm.mjs.map +0 -1
- package/dist/parseError-BR9pocvY.d.mts.map +0 -1
- package/dist/source-location-DRvDDqfq.mjs.map +0 -1
- package/dist/storage-CFGTn37X.mjs +0 -46
- package/dist/storage-CFGTn37X.mjs.map +0 -1
- package/dist/utils-DzGCLRFe.d.mts.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drain-ByWUeOQC.mjs","names":[],"sources":["../src/shared/config.ts","../src/shared/http.ts","../src/shared/drain.ts"],"sourcesContent":["import { getNitroRuntimeConfigRecord } from './nitroConfigBridge'\n\n/** Read the full Nitro `useRuntimeConfig()` record (or `undefined` outside Nitro). */\nexport function getRuntimeConfig(): Promise<Record<string, any> | undefined> {\n return getNitroRuntimeConfigRecord()\n}\n\n/**\n * Description of a single adapter config field. `env` is the ordered list of\n * environment variables to fall back to, e.g. `['NUXT_AXIOM_TOKEN', 'AXIOM_TOKEN']`.\n */\nexport interface ConfigField<T> {\n key: keyof T & string\n env?: string[]\n}\n\n/**\n * Resolve adapter configuration with the standard priority chain:\n *\n * 1. `overrides` passed to the drain factory\n * 2. `runtimeConfig.evlog.{namespace}.{key}` (Nitro)\n * 3. `runtimeConfig.{namespace}.{key}` (Nitro)\n * 4. `process.env[envKey]` for each env in `field.env`\n */\nexport async function resolveAdapterConfig<T>(\n namespace: string,\n fields: ConfigField<T>[],\n overrides?: Partial<T>,\n): Promise<Partial<T>> {\n const runtimeConfig = shouldProbeRuntimeConfig(fields, overrides)\n ? await getRuntimeConfig()\n : undefined\n const evlogNs = runtimeConfig?.evlog?.[namespace]\n const rootNs = runtimeConfig?.[namespace]\n\n const config: Record<string, unknown> = {}\n\n for (const { key, env } of fields) {\n config[key] =\n overrides?.[key]\n ?? evlogNs?.[key]\n ?? rootNs?.[key]\n ?? resolveEnv(env)\n }\n\n return config as Partial<T>\n}\n\n// Avoid the Nitro virtual-module import when env/overrides already resolve\n// every env-backed field — optional tuning fields (timeout, retries) should\n// not trigger a runtime probe in non-Nitro runtimes.\nfunction shouldProbeRuntimeConfig<T>(\n fields: ConfigField<T>[],\n overrides?: Partial<T>,\n): boolean {\n return fields.some(({ key, env }) => {\n if (overrides?.[key] !== undefined) return false\n if (!env) return false\n return resolveEnv(env) === undefined\n })\n}\n\nfunction resolveEnv(envKeys?: string[]): string | undefined {\n if (!envKeys) return undefined\n for (const key of envKeys) {\n const val = process.env[key]\n if (val) return val\n }\n return undefined\n}\n","/**\n * Minimal HTTP transport for drain adapters: abort-based timeouts, exponential\n * backoff on `5xx` / network errors, response bodies truncated in error messages.\n */\n\nexport interface HttpPostOptions {\n url: string\n /** Caller is responsible for `Content-Type`. */\n headers: Record<string, string>\n /** Pre-serialized request body. */\n body: string\n /** Abort the request after this many milliseconds. */\n timeout: number\n /** Prefix used in error messages. */\n label: string\n /**\n * Retries network errors, aborts, and `5xx` responses with exponential backoff.\n * @default 2\n */\n retries?: number\n}\n\nfunction isRetryable(error: unknown): boolean {\n if (error instanceof DOMException && error.name === 'AbortError') return true\n if (error instanceof TypeError) return true\n if (error instanceof Error) {\n const match = error.message.match(/API error: (\\d+)/)\n if (match) return Number.parseInt(match[1]) >= 500\n }\n return false\n}\n\n/**\n * POST a body with timeout + retry. Throws label-prefixed errors with a\n * truncated response body. Safe to call from any drain `send()`.\n */\nexport async function httpPost({ url, headers, body, timeout, label, retries = 2 }: HttpPostOptions): Promise<void> {\n const normalizedRetries = Number.isFinite(retries) && retries >= 0 ? Math.floor(retries) : 2\n\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt <= normalizedRetries; attempt++) {\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n headers,\n body,\n signal: controller.signal,\n })\n\n if (!response.ok) {\n const text = await response.text().catch(() => 'Unknown error')\n const safeText = text.length > 200 ? `${text.slice(0, 200)}...[truncated]` : text\n throw new Error(`${label} API error: ${response.status} ${response.statusText} - ${safeText}`)\n }\n\n clearTimeout(timeoutId)\n return\n } catch (error) {\n clearTimeout(timeoutId)\n\n if (error instanceof DOMException && error.name === 'AbortError') {\n lastError = new Error(`${label} request timed out after ${timeout}ms`)\n } else {\n lastError = error as Error\n }\n\n if (!isRetryable(error) || attempt === normalizedRetries) {\n throw lastError\n }\n\n await new Promise<void>(r => setTimeout(r, 200 * 2 ** attempt))\n }\n }\n\n throw lastError!\n}\n","import type { DrainContext, WideEvent } from '../types'\nimport { httpPost } from './http'\n\n/**\n * Drain definition backed by an arbitrary `send` function. Use this for\n * non-HTTP transports (filesystem, in-memory queue, native SDK). For HTTP\n * backends, use `defineHttpDrain` instead.\n */\nexport interface DrainOptions<TConfig> {\n /** Stable identifier used in error logs. */\n name: string\n /** Return `null` to skip draining (e.g. missing API key in dev). */\n resolve: () => TConfig | null | Promise<TConfig | null>\n send: (events: WideEvent[], config: TConfig) => Promise<void>\n}\n\n/**\n * Build a drain callback. Errors raised by `send` are logged with the drain\n * name and swallowed, so a failing drain never breaks the request pipeline.\n *\n * @example\n * ```ts\n * export function createMyDrain(overrides?: Partial<MyConfig>) {\n * return defineDrain<MyConfig>({\n * name: 'my-drain',\n * resolve: () => ({ url: process.env.MY_URL ?? null }),\n * send: async (events, config) => { ... },\n * })\n * }\n * ```\n */\nexport function defineDrain<TConfig>(options: DrainOptions<TConfig>): (ctx: DrainContext | DrainContext[]) => Promise<void> {\n return async (ctx: DrainContext | DrainContext[]) => {\n const contexts = Array.isArray(ctx) ? ctx : [ctx]\n if (contexts.length === 0) return\n\n const config = await options.resolve()\n if (!config) return\n\n try {\n await options.send(contexts.map(c => c.event), config)\n } catch (error) {\n console.error(`[evlog/${options.name}] Failed to send events:`, error)\n }\n }\n}\n\nexport interface HttpDrainRequest {\n url: string\n /** Caller is responsible for `Content-Type`. */\n headers: Record<string, string>\n body: string\n}\n\n/** Adapters only need to ship config + `encode()` — no manual `fetch`. */\nexport interface HttpDrainOptions<TConfig> {\n /** Stable identifier used in error logs. */\n name: string\n /** Return `null` to skip draining (e.g. missing API key in dev). */\n resolve: () => TConfig | null | Promise<TConfig | null>\n /** Return `null` to skip the batch without raising. */\n encode: (events: WideEvent[], config: TConfig) => HttpDrainRequest | null\n /** @default 5000 */\n timeout?: number\n /** @default 2 */\n retries?: number\n /** Read the timeout off the resolved config (falls back to `timeout`). */\n resolveTimeout?: (config: TConfig) => number | undefined\n /** Read the retry count off the resolved config (falls back to `retries`). */\n resolveRetries?: (config: TConfig) => number | undefined\n}\n\nconst DEFAULT_HTTP_TIMEOUT = 5000\n\n/**\n * Build an HTTP drain. Timeouts/retries are resolved from the config (with\n * overrides via `resolveTimeout` / `resolveRetries`) and forwarded to\n * {@link httpPost}.\n *\n * @example\n * ```ts\n * export function createMyDrain(overrides?: Partial<MyConfig>) {\n * return defineHttpDrain<MyConfig>({\n * name: 'my',\n * resolve: async () => {\n * const cfg = await resolveAdapterConfig<MyConfig>('my', FIELDS, overrides)\n * return cfg.apiKey ? cfg as MyConfig : null\n * },\n * encode: (events, config) => ({\n * url: `${config.endpoint ?? 'https://api.my.com'}/ingest`,\n * headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${config.apiKey}` },\n * body: JSON.stringify(events),\n * }),\n * })\n * }\n * ```\n */\nexport function defineHttpDrain<TConfig>(options: HttpDrainOptions<TConfig>): (ctx: DrainContext | DrainContext[]) => Promise<void> {\n return defineDrain<TConfig>({\n name: options.name,\n resolve: options.resolve,\n send: async (events, config) => {\n if (events.length === 0) return\n const request = options.encode(events, config)\n if (!request) return\n const timeout = options.resolveTimeout?.(config)\n ?? (config as { timeout?: number }).timeout\n ?? options.timeout\n ?? DEFAULT_HTTP_TIMEOUT\n const retries = options.resolveRetries?.(config)\n ?? (config as { retries?: number }).retries\n ?? options.retries\n await httpPost({\n url: request.url,\n headers: request.headers,\n body: request.body,\n timeout,\n retries,\n label: options.name,\n })\n },\n })\n}\n"],"mappings":"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"}
|
package/dist/elysia/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Y as RequestLogger } from "../audit-
|
|
2
|
-
import { t as BaseEvlogOptions } from "../middleware-
|
|
1
|
+
import { Y as RequestLogger } from "../audit-CJl-wZ10.mjs";
|
|
2
|
+
import { t as BaseEvlogOptions } from "../middleware-CGM-bOvE.mjs";
|
|
3
3
|
import { Elysia } from "elysia";
|
|
4
4
|
|
|
5
5
|
//#region src/elysia/index.d.ts
|
|
@@ -23,6 +23,29 @@ type EvlogElysiaOptions = BaseEvlogOptions;
|
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
25
|
declare function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T>;
|
|
26
|
+
/**
|
|
27
|
+
* Create an evlog plugin for Elysia.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* import { Elysia } from 'elysia'
|
|
32
|
+
* import { evlog } from 'evlog/elysia'
|
|
33
|
+
* import { createAxiomDrain } from 'evlog/axiom'
|
|
34
|
+
*
|
|
35
|
+
* const app = new Elysia()
|
|
36
|
+
* .use(evlog({
|
|
37
|
+
* drain: createAxiomDrain(),
|
|
38
|
+
* enrich: (ctx) => {
|
|
39
|
+
* ctx.event.region = process.env.FLY_REGION
|
|
40
|
+
* },
|
|
41
|
+
* }))
|
|
42
|
+
* .get('/health', ({ log }) => {
|
|
43
|
+
* log.set({ route: 'health' })
|
|
44
|
+
* return { ok: true }
|
|
45
|
+
* })
|
|
46
|
+
* .listen(3000)
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
26
49
|
declare function evlog(options?: EvlogElysiaOptions): Elysia<"", {
|
|
27
50
|
decorator: {};
|
|
28
51
|
store: {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/elysia/index.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/elysia/index.ts"],"mappings":";;;;;KAWY,kBAAA,GAAqB,gBAAA;;AAAjC;;;;;AAoBA;;;;;;;;;;;;iBAAgB,SAAA,oBAA6B,MAAA,kBAAA,CAAA,GAA4B,aAAA,CAAc,CAAA;;AAuEvF;;;;;;;;;;;;;;;;;;;;;;iBAAgB,KAAA,CAAM,OAAA,GAAS,kBAAA,GAAuB,MAAA"}
|
package/dist/elysia/index.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as
|
|
3
|
-
import { t as attachForkToLogger } from "../fork-CTJXnpl8.mjs";
|
|
1
|
+
import { t as attachForkToLogger } from "../fork-D44V93-K.mjs";
|
|
2
|
+
import { t as defineFrameworkIntegration } from "../integration-Bz8X6_Lb.mjs";
|
|
4
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
4
|
import { Elysia } from "elysia";
|
|
6
5
|
//#region src/elysia/index.ts
|
|
@@ -29,29 +28,63 @@ function useLogger() {
|
|
|
29
28
|
if (!logger || !activeLoggers.has(logger)) throw new Error("[evlog] useLogger() was called outside of an evlog plugin context. Make sure app.use(evlog()) is registered before your routes.");
|
|
30
29
|
return logger;
|
|
31
30
|
}
|
|
31
|
+
const integration = defineFrameworkIntegration({
|
|
32
|
+
name: "elysia",
|
|
33
|
+
extractRequest: ({ request, path, headers }) => ({
|
|
34
|
+
method: request.method,
|
|
35
|
+
path,
|
|
36
|
+
headers,
|
|
37
|
+
requestId: headers["x-request-id"]
|
|
38
|
+
}),
|
|
39
|
+
attachLogger: ({ request, path, headers }, logger) => {
|
|
40
|
+
attachForkToLogger(storage, logger, {
|
|
41
|
+
method: request.method,
|
|
42
|
+
path,
|
|
43
|
+
requestId: headers["x-request-id"]
|
|
44
|
+
}, {
|
|
45
|
+
onChildEnter: (child) => {
|
|
46
|
+
activeLoggers.add(child);
|
|
47
|
+
},
|
|
48
|
+
onChildExit: (child) => {
|
|
49
|
+
activeLoggers.delete(child);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
activeLoggers.add(logger);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* Create an evlog plugin for Elysia.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* import { Elysia } from 'elysia'
|
|
61
|
+
* import { evlog } from 'evlog/elysia'
|
|
62
|
+
* import { createAxiomDrain } from 'evlog/axiom'
|
|
63
|
+
*
|
|
64
|
+
* const app = new Elysia()
|
|
65
|
+
* .use(evlog({
|
|
66
|
+
* drain: createAxiomDrain(),
|
|
67
|
+
* enrich: (ctx) => {
|
|
68
|
+
* ctx.event.region = process.env.FLY_REGION
|
|
69
|
+
* },
|
|
70
|
+
* }))
|
|
71
|
+
* .get('/health', ({ log }) => {
|
|
72
|
+
* log.set({ route: 'health' })
|
|
73
|
+
* return { ok: true }
|
|
74
|
+
* })
|
|
75
|
+
* .listen(3000)
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
32
78
|
function evlog(options = {}) {
|
|
33
79
|
const emitted = /* @__PURE__ */ new WeakSet();
|
|
34
80
|
const requestState = /* @__PURE__ */ new WeakMap();
|
|
35
81
|
return new Elysia({ name: "evlog" }).derive({ as: "global" }, ({ request, path, headers }) => {
|
|
36
|
-
const
|
|
37
|
-
|
|
82
|
+
const ctx = {
|
|
83
|
+
request,
|
|
38
84
|
path,
|
|
39
|
-
|
|
40
|
-
headers: filterSafeHeaders(headers),
|
|
41
|
-
...options
|
|
85
|
+
headers
|
|
42
86
|
};
|
|
43
|
-
const { logger, finish, skipped } =
|
|
44
|
-
if (!skipped) {
|
|
45
|
-
attachForkToLogger(storage, logger, middlewareOpts, {
|
|
46
|
-
onChildEnter: (child) => {
|
|
47
|
-
activeLoggers.add(child);
|
|
48
|
-
},
|
|
49
|
-
onChildExit: (child) => {
|
|
50
|
-
activeLoggers.delete(child);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
activeLoggers.add(logger);
|
|
54
|
-
}
|
|
87
|
+
const { logger, finish, skipped } = integration.start(ctx, options);
|
|
55
88
|
storage.enterWith(logger);
|
|
56
89
|
requestState.set(request, {
|
|
57
90
|
finish,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/elysia/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport { Elysia } from 'elysia'\nimport type { RequestLogger } from '../types'\nimport {
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/elysia/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport { Elysia } from 'elysia'\nimport type { RequestLogger } from '../types'\nimport { defineFrameworkIntegration } from '../shared/integration'\nimport type { BaseEvlogOptions } from '../shared/middleware'\nimport { attachForkToLogger } from '../shared/fork'\n\nconst storage = new AsyncLocalStorage<RequestLogger>()\n\nconst activeLoggers = new WeakSet<RequestLogger>()\n\nexport type EvlogElysiaOptions = BaseEvlogOptions\n\n/**\n * Get the request-scoped logger from anywhere in the call stack.\n * Must be called inside a request handled by the `evlog()` plugin.\n *\n * Unlike other frameworks, Elysia uses `storage.enterWith()` which persists\n * beyond the request lifecycle. This accessor additionally checks `activeLoggers`\n * to ensure the logger belongs to an in-flight request.\n *\n * @example\n * ```ts\n * import { useLogger } from 'evlog/elysia'\n *\n * function findUser(id: string) {\n * const log = useLogger()\n * log.set({ user: { id } })\n * }\n * ```\n */\nexport function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T> {\n const logger = storage.getStore()\n if (!logger || !activeLoggers.has(logger)) {\n throw new Error(\n '[evlog] useLogger() was called outside of an evlog plugin context. '\n + 'Make sure app.use(evlog()) is registered before your routes.',\n )\n }\n return logger as RequestLogger<T>\n}\n\ninterface ElysiaContext {\n request: Request\n path: string\n headers: Record<string, string>\n}\n\nconst integration = defineFrameworkIntegration<ElysiaContext>({\n name: 'elysia',\n extractRequest: ({ request, path, headers }) => ({\n method: request.method,\n path,\n headers,\n requestId: headers['x-request-id'],\n }),\n attachLogger: ({ request, path, headers }, logger) => {\n attachForkToLogger(storage, logger, {\n method: request.method,\n path,\n requestId: headers['x-request-id'],\n }, {\n onChildEnter: (child) => {\n activeLoggers.add(child)\n },\n onChildExit: (child) => {\n activeLoggers.delete(child)\n },\n })\n activeLoggers.add(logger)\n },\n})\n\ninterface RequestState {\n finish: (opts?: { status?: number; error?: Error }) => Promise<unknown>\n skipped: boolean\n logger: RequestLogger\n}\n\n/**\n * Create an evlog plugin for Elysia.\n *\n * @example\n * ```ts\n * import { Elysia } from 'elysia'\n * import { evlog } from 'evlog/elysia'\n * import { createAxiomDrain } from 'evlog/axiom'\n *\n * const app = new Elysia()\n * .use(evlog({\n * drain: createAxiomDrain(),\n * enrich: (ctx) => {\n * ctx.event.region = process.env.FLY_REGION\n * },\n * }))\n * .get('/health', ({ log }) => {\n * log.set({ route: 'health' })\n * return { ok: true }\n * })\n * .listen(3000)\n * ```\n */\nexport function evlog(options: EvlogElysiaOptions = {}) {\n const emitted = new WeakSet<Request>()\n const requestState = new WeakMap<Request, RequestState>()\n\n return new Elysia({ name: 'evlog' })\n .derive({ as: 'global' }, ({ request, path, headers }) => {\n const ctx: ElysiaContext = { request, path, headers: headers as Record<string, string> }\n const { logger, finish, skipped } = integration.start(ctx, options)\n storage.enterWith(logger)\n requestState.set(request, { finish, skipped, logger })\n return { log: logger }\n })\n .onAfterResponse({ as: 'global' }, async ({ request, set }) => {\n const state = requestState.get(request)\n if (!state || state.skipped || emitted.has(request)) return\n emitted.add(request)\n await state.finish({ status: set.status as number || 200 })\n activeLoggers.delete(state.logger)\n storage.enterWith(undefined as unknown as RequestLogger)\n })\n .onError({ as: 'global' }, async ({ request, error }) => {\n const state = requestState.get(request)\n if (!state || state.skipped || emitted.has(request)) return\n emitted.add(request)\n const err = error instanceof Error ? error : new Error(String(error))\n state.logger.error(err)\n await state.finish({ error: err })\n activeLoggers.delete(state.logger)\n storage.enterWith(undefined as unknown as RequestLogger)\n })\n}\n"],"mappings":";;;;;AAOA,MAAM,UAAU,IAAI,mBAAkC;AAEtD,MAAM,gCAAgB,IAAI,SAAwB;;;;;;;;;;;;;;;;;;;AAsBlD,SAAgB,YAA0E;CACxF,MAAM,SAAS,QAAQ,UAAU;AACjC,KAAI,CAAC,UAAU,CAAC,cAAc,IAAI,OAAO,CACvC,OAAM,IAAI,MACR,kIAED;AAEH,QAAO;;AAST,MAAM,cAAc,2BAA0C;CAC5D,MAAM;CACN,iBAAiB,EAAE,SAAS,MAAM,eAAe;EAC/C,QAAQ,QAAQ;EAChB;EACA;EACA,WAAW,QAAQ;EACpB;CACD,eAAe,EAAE,SAAS,MAAM,WAAW,WAAW;AACpD,qBAAmB,SAAS,QAAQ;GAClC,QAAQ,QAAQ;GAChB;GACA,WAAW,QAAQ;GACpB,EAAE;GACD,eAAe,UAAU;AACvB,kBAAc,IAAI,MAAM;;GAE1B,cAAc,UAAU;AACtB,kBAAc,OAAO,MAAM;;GAE9B,CAAC;AACF,gBAAc,IAAI,OAAO;;CAE5B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AA+BF,SAAgB,MAAM,UAA8B,EAAE,EAAE;CACtD,MAAM,0BAAU,IAAI,SAAkB;CACtC,MAAM,+BAAe,IAAI,SAAgC;AAEzD,QAAO,IAAI,OAAO,EAAE,MAAM,SAAS,CAAC,CACjC,OAAO,EAAE,IAAI,UAAU,GAAG,EAAE,SAAS,MAAM,cAAc;EACxD,MAAM,MAAqB;GAAE;GAAS;GAAe;GAAmC;EACxF,MAAM,EAAE,QAAQ,QAAQ,YAAY,YAAY,MAAM,KAAK,QAAQ;AACnE,UAAQ,UAAU,OAAO;AACzB,eAAa,IAAI,SAAS;GAAE;GAAQ;GAAS;GAAQ,CAAC;AACtD,SAAO,EAAE,KAAK,QAAQ;GACtB,CACD,gBAAgB,EAAE,IAAI,UAAU,EAAE,OAAO,EAAE,SAAS,UAAU;EAC7D,MAAM,QAAQ,aAAa,IAAI,QAAQ;AACvC,MAAI,CAAC,SAAS,MAAM,WAAW,QAAQ,IAAI,QAAQ,CAAE;AACrD,UAAQ,IAAI,QAAQ;AACpB,QAAM,MAAM,OAAO,EAAE,QAAQ,IAAI,UAAoB,KAAK,CAAC;AAC3D,gBAAc,OAAO,MAAM,OAAO;AAClC,UAAQ,UAAU,KAAA,EAAsC;GACxD,CACD,QAAQ,EAAE,IAAI,UAAU,EAAE,OAAO,EAAE,SAAS,YAAY;EACvD,MAAM,QAAQ,aAAa,IAAI,QAAQ;AACvC,MAAI,CAAC,SAAS,MAAM,WAAW,QAAQ,IAAI,QAAQ,CAAE;AACrD,UAAQ,IAAI,QAAQ;EACpB,MAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AACrE,QAAM,OAAO,MAAM,IAAI;AACvB,QAAM,MAAM,OAAO,EAAE,OAAO,KAAK,CAAC;AAClC,gBAAc,OAAO,MAAM,OAAO;AAClC,UAAQ,UAAU,KAAA,EAAsC;GACxD"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { t as mergeEventField } from "./event-ef-5Dbxg.mjs";
|
|
2
|
+
//#region src/shared/compose.ts
|
|
3
|
+
/**
|
|
4
|
+
* Compose enricher callbacks into one. Runs in registration order; errors are
|
|
5
|
+
* caught per-callback so one buggy enricher never blocks the others.
|
|
6
|
+
*/
|
|
7
|
+
function composeEnrichers(enrichers, options = {}) {
|
|
8
|
+
const label = options.name ?? "compose-enrichers";
|
|
9
|
+
return async (ctx) => {
|
|
10
|
+
for (const enricher of enrichers) try {
|
|
11
|
+
await enricher(ctx);
|
|
12
|
+
} catch (err) {
|
|
13
|
+
console.error(`[evlog/${label}] enrich failed:`, err);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Fan out to multiple drains concurrently (`Promise.allSettled`). A slow
|
|
19
|
+
* Sentry drain never blocks an Axiom drain on the same event.
|
|
20
|
+
*/
|
|
21
|
+
function composeDrains(drains, options = {}) {
|
|
22
|
+
const label = options.name ?? "compose-drains";
|
|
23
|
+
return async (ctx) => {
|
|
24
|
+
if (drains.length === 0) return;
|
|
25
|
+
await Promise.allSettled(drains.map(async (drain) => {
|
|
26
|
+
try {
|
|
27
|
+
await drain(ctx);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
console.error(`[evlog/${label}] drain failed:`, err);
|
|
30
|
+
}
|
|
31
|
+
}));
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Compose tail-sampling `keep` callbacks. `ctx.shouldKeep` is true after the
|
|
36
|
+
* run if any callback set it. Errors are isolated.
|
|
37
|
+
*/
|
|
38
|
+
function composeKeep(keepers, options = {}) {
|
|
39
|
+
const label = options.name ?? "compose-keep";
|
|
40
|
+
return async (ctx) => {
|
|
41
|
+
for (const keep of keepers) try {
|
|
42
|
+
await keep(ctx);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error(`[evlog/${label}] keep failed:`, err);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Merge plugin lists. Later registrations override earlier ones by `name`. */
|
|
49
|
+
function composePlugins(...lists) {
|
|
50
|
+
const merged = /* @__PURE__ */ new Map();
|
|
51
|
+
for (const list of lists) {
|
|
52
|
+
if (!list) continue;
|
|
53
|
+
for (const plugin of list) merged.set(plugin.name, plugin);
|
|
54
|
+
}
|
|
55
|
+
return Array.from(merged.values());
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/shared/enricher.ts
|
|
59
|
+
/**
|
|
60
|
+
* Build an enricher: skips when `compute` returns `undefined`, merges with
|
|
61
|
+
* {@link mergeEventField} respecting `overwrite`, and isolates errors under
|
|
62
|
+
* `[evlog/{name}]`.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* export const tenantEnricher = defineEnricher<{ id: string }>({
|
|
67
|
+
* name: 'tenant',
|
|
68
|
+
* field: 'tenant',
|
|
69
|
+
* compute({ headers }) {
|
|
70
|
+
* const id = getHeader(headers, 'x-tenant-id')
|
|
71
|
+
* return id ? { id } : undefined
|
|
72
|
+
* },
|
|
73
|
+
* })
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
function defineEnricher(def, options = {}) {
|
|
77
|
+
const { name, field, compute } = def;
|
|
78
|
+
return (ctx) => {
|
|
79
|
+
let computed;
|
|
80
|
+
try {
|
|
81
|
+
computed = compute(ctx);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.error(`[evlog/${name}] enrich failed:`, err);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (computed === void 0) return;
|
|
87
|
+
if (!field) return;
|
|
88
|
+
const target = ctx.event[field];
|
|
89
|
+
ctx.event[field] = mergeEventField(target, computed, options.overwrite);
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//#endregion
|
|
93
|
+
export { composePlugins as a, composeKeep as i, composeDrains as n, composeEnrichers as r, defineEnricher as t };
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=enricher-BA6viylF.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enricher-BA6viylF.mjs","names":[],"sources":["../src/shared/compose.ts","../src/shared/enricher.ts"],"sourcesContent":["import type { DrainContext, EnrichContext, TailSamplingContext } from '../types'\nimport type { EvlogPlugin } from './plugin'\n\n/**\n * Compose enricher callbacks into one. Runs in registration order; errors are\n * caught per-callback so one buggy enricher never blocks the others.\n */\nexport function composeEnrichers(\n enrichers: Array<(ctx: EnrichContext) => void | Promise<void>>,\n options: { name?: string } = {},\n): (ctx: EnrichContext) => Promise<void> {\n const label = options.name ?? 'compose-enrichers'\n return async (ctx) => {\n for (const enricher of enrichers) {\n try {\n await enricher(ctx)\n } catch (err) {\n console.error(`[evlog/${label}] enrich failed:`, err)\n }\n }\n }\n}\n\n/**\n * Fan out to multiple drains concurrently (`Promise.allSettled`). A slow\n * Sentry drain never blocks an Axiom drain on the same event.\n */\nexport function composeDrains(\n drains: Array<(ctx: DrainContext) => void | Promise<void>>,\n options: { name?: string } = {},\n): (ctx: DrainContext) => Promise<void> {\n const label = options.name ?? 'compose-drains'\n return async (ctx) => {\n if (drains.length === 0) return\n await Promise.allSettled(\n drains.map(async (drain) => {\n try {\n await drain(ctx)\n } catch (err) {\n console.error(`[evlog/${label}] drain failed:`, err)\n }\n }),\n )\n }\n}\n\n/**\n * Compose tail-sampling `keep` callbacks. `ctx.shouldKeep` is true after the\n * run if any callback set it. Errors are isolated.\n */\nexport function composeKeep(\n keepers: Array<(ctx: TailSamplingContext) => void | Promise<void>>,\n options: { name?: string } = {},\n): (ctx: TailSamplingContext) => Promise<void> {\n const label = options.name ?? 'compose-keep'\n return async (ctx) => {\n for (const keep of keepers) {\n try {\n await keep(ctx)\n } catch (err) {\n console.error(`[evlog/${label}] keep failed:`, err)\n }\n }\n }\n}\n\n/** Merge plugin lists. Later registrations override earlier ones by `name`. */\nexport function composePlugins(...lists: Array<EvlogPlugin[] | undefined>): EvlogPlugin[] {\n const merged = new Map<string, EvlogPlugin>()\n for (const list of lists) {\n if (!list) continue\n for (const plugin of list) {\n merged.set(plugin.name, plugin)\n }\n }\n return Array.from(merged.values())\n}\n","import type { EnrichContext, WideEvent } from '../types'\nimport { mergeEventField } from './event'\n\nexport interface EnricherOptions {\n /**\n * Replace existing event fields with the computed value. Defaults to `false`\n * so user-provided context (e.g. `log.set({ geo: ... })`) wins.\n */\n overwrite?: boolean\n}\n\nexport interface EnricherDefinition<T extends object> {\n /** Stable identifier used in error logs. */\n name: string\n /**\n * Top-level event field to merge into. Omit when the enricher writes to\n * multiple fields and handles its own merging inside `compute`.\n */\n field?: keyof WideEvent & string\n /** Return `undefined` to skip enrichment (e.g. when a required header is missing). */\n compute: (ctx: EnrichContext) => T | undefined\n}\n\n/**\n * Build an enricher: skips when `compute` returns `undefined`, merges with\n * {@link mergeEventField} respecting `overwrite`, and isolates errors under\n * `[evlog/{name}]`.\n *\n * @example\n * ```ts\n * export const tenantEnricher = defineEnricher<{ id: string }>({\n * name: 'tenant',\n * field: 'tenant',\n * compute({ headers }) {\n * const id = getHeader(headers, 'x-tenant-id')\n * return id ? { id } : undefined\n * },\n * })\n * ```\n */\nexport function defineEnricher<T extends object>(\n def: EnricherDefinition<T>,\n options: EnricherOptions = {},\n): (ctx: EnrichContext) => void {\n const { name, field, compute } = def\n return (ctx) => {\n let computed: T | undefined\n try {\n computed = compute(ctx)\n } catch (err) {\n console.error(`[evlog/${name}] enrich failed:`, err)\n return\n }\n if (computed === undefined) return\n if (!field) return\n const target = ctx.event[field]\n ctx.event[field] = mergeEventField<T>(target, computed, options.overwrite)\n }\n}\n"],"mappings":";;;;;;AAOA,SAAgB,iBACd,WACA,UAA6B,EAAE,EACQ;CACvC,MAAM,QAAQ,QAAQ,QAAQ;AAC9B,QAAO,OAAO,QAAQ;AACpB,OAAK,MAAM,YAAY,UACrB,KAAI;AACF,SAAM,SAAS,IAAI;WACZ,KAAK;AACZ,WAAQ,MAAM,UAAU,MAAM,mBAAmB,IAAI;;;;;;;;AAU7D,SAAgB,cACd,QACA,UAA6B,EAAE,EACO;CACtC,MAAM,QAAQ,QAAQ,QAAQ;AAC9B,QAAO,OAAO,QAAQ;AACpB,MAAI,OAAO,WAAW,EAAG;AACzB,QAAM,QAAQ,WACZ,OAAO,IAAI,OAAO,UAAU;AAC1B,OAAI;AACF,UAAM,MAAM,IAAI;YACT,KAAK;AACZ,YAAQ,MAAM,UAAU,MAAM,kBAAkB,IAAI;;IAEtD,CACH;;;;;;;AAQL,SAAgB,YACd,SACA,UAA6B,EAAE,EACc;CAC7C,MAAM,QAAQ,QAAQ,QAAQ;AAC9B,QAAO,OAAO,QAAQ;AACpB,OAAK,MAAM,QAAQ,QACjB,KAAI;AACF,SAAM,KAAK,IAAI;WACR,KAAK;AACZ,WAAQ,MAAM,UAAU,MAAM,iBAAiB,IAAI;;;;;AAO3D,SAAgB,eAAe,GAAG,OAAwD;CACxF,MAAM,yBAAS,IAAI,KAA0B;AAC7C,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,CAAC,KAAM;AACX,OAAK,MAAM,UAAU,KACnB,QAAO,IAAI,OAAO,MAAM,OAAO;;AAGnC,QAAO,MAAM,KAAK,OAAO,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;ACnCpC,SAAgB,eACd,KACA,UAA2B,EAAE,EACC;CAC9B,MAAM,EAAE,MAAM,OAAO,YAAY;AACjC,SAAQ,QAAQ;EACd,IAAI;AACJ,MAAI;AACF,cAAW,QAAQ,IAAI;WAChB,KAAK;AACZ,WAAQ,MAAM,UAAU,KAAK,mBAAmB,IAAI;AACpD;;AAEF,MAAI,aAAa,KAAA,EAAW;AAC5B,MAAI,CAAC,MAAO;EACZ,MAAM,SAAS,IAAI,MAAM;AACzB,MAAI,MAAM,SAAS,gBAAmB,QAAQ,UAAU,QAAQ,UAAU"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { I as EnrichContext, it as WideEvent } from "./audit-CJl-wZ10.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/shared/enricher.d.ts
|
|
4
|
+
interface EnricherOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Replace existing event fields with the computed value. Defaults to `false`
|
|
7
|
+
* so user-provided context (e.g. `log.set({ geo: ... })`) wins.
|
|
8
|
+
*/
|
|
9
|
+
overwrite?: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface EnricherDefinition<T extends object> {
|
|
12
|
+
/** Stable identifier used in error logs. */
|
|
13
|
+
name: string;
|
|
14
|
+
/**
|
|
15
|
+
* Top-level event field to merge into. Omit when the enricher writes to
|
|
16
|
+
* multiple fields and handles its own merging inside `compute`.
|
|
17
|
+
*/
|
|
18
|
+
field?: keyof WideEvent & string;
|
|
19
|
+
/** Return `undefined` to skip enrichment (e.g. when a required header is missing). */
|
|
20
|
+
compute: (ctx: EnrichContext) => T | undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build an enricher: skips when `compute` returns `undefined`, merges with
|
|
24
|
+
* {@link mergeEventField} respecting `overwrite`, and isolates errors under
|
|
25
|
+
* `[evlog/{name}]`.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* export const tenantEnricher = defineEnricher<{ id: string }>({
|
|
30
|
+
* name: 'tenant',
|
|
31
|
+
* field: 'tenant',
|
|
32
|
+
* compute({ headers }) {
|
|
33
|
+
* const id = getHeader(headers, 'x-tenant-id')
|
|
34
|
+
* return id ? { id } : undefined
|
|
35
|
+
* },
|
|
36
|
+
* })
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare function defineEnricher<T extends object>(def: EnricherDefinition<T>, options?: EnricherOptions): (ctx: EnrichContext) => void;
|
|
40
|
+
//#endregion
|
|
41
|
+
export { EnricherOptions as n, defineEnricher as r, EnricherDefinition as t };
|
|
42
|
+
//# sourceMappingURL=enricher-CLSnrzrr.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enricher-CLSnrzrr.d.mts","names":[],"sources":["../src/shared/enricher.ts"],"mappings":";;;UAGiB,eAAA;;AAAjB;;;EAKE,SAAA;AAAA;AAAA,UAGe,kBAAA;EAAkB;EAEjC,IAAA;EAKc;;;;EAAd,KAAA,SAAc,SAAA;EAPoB;EASlC,OAAA,GAAU,GAAA,EAAK,aAAA,KAAkB,CAAA;AAAA;;;;;;;;AAoBnC;;;;;;;;;;iBAAgB,cAAA,kBAAA,CACd,GAAA,EAAK,kBAAA,CAAmB,CAAA,GACxB,OAAA,GAAS,eAAA,IACP,GAAA,EAAK,aAAA"}
|
package/dist/enrichers.d.mts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import { I as EnrichContext } from "./audit-
|
|
1
|
+
import { I as EnrichContext } from "./audit-CJl-wZ10.mjs";
|
|
2
|
+
import { n as EnricherOptions } from "./enricher-CLSnrzrr.mjs";
|
|
2
3
|
|
|
3
4
|
//#region src/enrichers/index.d.ts
|
|
4
|
-
interface EnricherOptions {
|
|
5
|
-
/**
|
|
6
|
-
* When true, overwrite any existing fields in the event.
|
|
7
|
-
* Defaults to false to preserve user-provided data.
|
|
8
|
-
*/
|
|
9
|
-
overwrite?: boolean;
|
|
10
|
-
}
|
|
11
5
|
interface UserAgentInfo {
|
|
12
6
|
raw: string;
|
|
13
7
|
browser?: {
|
|
@@ -69,6 +63,19 @@ declare function createRequestSizeEnricher(options?: EnricherOptions): (ctx: Enr
|
|
|
69
63
|
* Also sets `event.traceId` and `event.spanId` at the top level.
|
|
70
64
|
*/
|
|
71
65
|
declare function createTraceContextEnricher(options?: EnricherOptions): (ctx: EnrichContext) => void;
|
|
66
|
+
/**
|
|
67
|
+
* Compose every built-in enricher into a single async enricher, in the order
|
|
68
|
+
* `userAgent → geo → requestSize → traceContext`.
|
|
69
|
+
*
|
|
70
|
+
* Drop-in shorthand for the most common middleware setup:
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* import { createDefaultEnrichers } from 'evlog/enrichers'
|
|
74
|
+
*
|
|
75
|
+
* app.use(evlog({ enrich: createDefaultEnrichers() }))
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
declare function createDefaultEnrichers(options?: EnricherOptions): (ctx: EnrichContext) => Promise<void>;
|
|
72
79
|
//#endregion
|
|
73
|
-
export { EnricherOptions, GeoInfo, RequestSizeInfo, TraceContextInfo, UserAgentInfo, createGeoEnricher, createRequestSizeEnricher, createTraceContextEnricher, createUserAgentEnricher };
|
|
80
|
+
export { type EnricherOptions, GeoInfo, RequestSizeInfo, TraceContextInfo, UserAgentInfo, createDefaultEnrichers, createGeoEnricher, createRequestSizeEnricher, createTraceContextEnricher, createUserAgentEnricher };
|
|
74
81
|
//# sourceMappingURL=enrichers.d.mts.map
|
package/dist/enrichers.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enrichers.d.mts","names":[],"sources":["../src/enrichers/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"enrichers.d.mts","names":[],"sources":["../src/enrichers/index.ts"],"mappings":";;;;UAOiB,aAAA;EACf,GAAA;EACA,OAAA;IAAY,IAAA;IAAc,OAAA;EAAA;EAC1B,EAAA;IAAO,IAAA;IAAc,OAAA;EAAA;EACrB,MAAA;IAAW,IAAA;EAAA;AAAA;AAAA,UAGI,OAAA;EACf,OAAA;EACA,MAAA;EACA,UAAA;EACA,IAAA;EACA,QAAA;EACA,SAAA;AAAA;AAAA,UAGe,eAAA;EACf,YAAA;EACA,aAAA;AAAA;AAAA,UAGe,gBAAA;EACf,WAAA;EACA,UAAA;EACA,OAAA;EACA,MAAA;AAAA;;AAJF;;;iBAwEgB,uBAAA,CAAwB,OAAA,GAAS,eAAA,IAAwB,GAAA,EAAK,aAAA;;;;;;;AAA9E;;;;;;iBAuBgB,iBAAA,CAAkB,OAAA,GAAS,eAAA,IAAwB,GAAA,EAAK,aAAA;;;;AAAxE;iBAuBgB,yBAAA,CAA0B,OAAA,GAAS,eAAA,IAAwB,GAAA,EAAK,aAAA;;;;;;iBAkBhE,0BAAA,CAA2B,OAAA,GAAS,eAAA,IAAwB,GAAA,EAAK,aAAA;;;AAlBjF;;;;;;;;;;iBA+DgB,sBAAA,CAAuB,OAAA,GAAS,eAAA,IAAwB,GAAA,EAAK,aAAA,KAAkB,OAAA"}
|
package/dist/enrichers.mjs
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
|
+
import { i as normalizeNumber, r as getHeader } from "./headers-CU-QqnYg.mjs";
|
|
2
|
+
import { r as composeEnrichers, t as defineEnricher } from "./enricher-BA6viylF.mjs";
|
|
1
3
|
//#region src/enrichers/index.ts
|
|
2
|
-
function getHeader(headers, name) {
|
|
3
|
-
if (!headers) return void 0;
|
|
4
|
-
if (headers[name] !== void 0) return headers[name];
|
|
5
|
-
const lowerName = name.toLowerCase();
|
|
6
|
-
if (headers[lowerName] !== void 0) return headers[lowerName];
|
|
7
|
-
for (const [key, value] of Object.entries(headers)) if (key.toLowerCase() === lowerName) return value;
|
|
8
|
-
}
|
|
9
4
|
function parseUserAgent(ua) {
|
|
10
5
|
const lower = ua.toLowerCase();
|
|
11
6
|
let deviceType = { type: "unknown" };
|
|
@@ -75,29 +70,19 @@ function parseTraceparent(traceparent) {
|
|
|
75
70
|
spanId: match[2]
|
|
76
71
|
};
|
|
77
72
|
}
|
|
78
|
-
function mergeEventField(existing, computed, overwrite) {
|
|
79
|
-
if (overwrite || existing === void 0 || existing === null || typeof existing !== "object") return computed;
|
|
80
|
-
return {
|
|
81
|
-
...computed,
|
|
82
|
-
...existing
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
function normalizeNumber(value) {
|
|
86
|
-
if (!value) return void 0;
|
|
87
|
-
const parsed = Number(value);
|
|
88
|
-
return Number.isFinite(parsed) ? parsed : void 0;
|
|
89
|
-
}
|
|
90
73
|
/**
|
|
91
74
|
* Enrich events with parsed user agent data.
|
|
92
75
|
* Sets `event.userAgent` with `UserAgentInfo` shape: `{ raw, browser?, os?, device? }`.
|
|
93
76
|
*/
|
|
94
77
|
function createUserAgentEnricher(options = {}) {
|
|
95
|
-
return (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
78
|
+
return defineEnricher({
|
|
79
|
+
name: "userAgent",
|
|
80
|
+
field: "userAgent",
|
|
81
|
+
compute: ({ headers }) => {
|
|
82
|
+
const ua = getHeader(headers, "user-agent");
|
|
83
|
+
return ua ? parseUserAgent(ua) : void 0;
|
|
84
|
+
}
|
|
85
|
+
}, options);
|
|
101
86
|
}
|
|
102
87
|
/**
|
|
103
88
|
* Enrich events with geo data from platform headers.
|
|
@@ -112,36 +97,41 @@ function createUserAgentEnricher(options = {}) {
|
|
|
112
97
|
* or use a Workers middleware to copy `cf` properties into custom headers.
|
|
113
98
|
*/
|
|
114
99
|
function createGeoEnricher(options = {}) {
|
|
115
|
-
return (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
100
|
+
return defineEnricher({
|
|
101
|
+
name: "geo",
|
|
102
|
+
field: "geo",
|
|
103
|
+
compute: ({ headers }) => {
|
|
104
|
+
if (!headers) return void 0;
|
|
105
|
+
const geo = {
|
|
106
|
+
country: getHeader(headers, "x-vercel-ip-country") ?? getHeader(headers, "cf-ipcountry"),
|
|
107
|
+
region: getHeader(headers, "x-vercel-ip-country-region") ?? getHeader(headers, "cf-region"),
|
|
108
|
+
regionCode: getHeader(headers, "x-vercel-ip-country-region-code") ?? getHeader(headers, "cf-region-code"),
|
|
109
|
+
city: getHeader(headers, "x-vercel-ip-city") ?? getHeader(headers, "cf-city"),
|
|
110
|
+
latitude: normalizeNumber(getHeader(headers, "x-vercel-ip-latitude") ?? getHeader(headers, "cf-latitude")),
|
|
111
|
+
longitude: normalizeNumber(getHeader(headers, "x-vercel-ip-longitude") ?? getHeader(headers, "cf-longitude"))
|
|
112
|
+
};
|
|
113
|
+
return Object.values(geo).every((value) => value === void 0) ? void 0 : geo;
|
|
114
|
+
}
|
|
115
|
+
}, options);
|
|
129
116
|
}
|
|
130
117
|
/**
|
|
131
118
|
* Enrich events with request/response payload sizes.
|
|
132
119
|
* Sets `event.requestSize` with `RequestSizeInfo` shape: `{ requestBytes?, responseBytes? }`.
|
|
133
120
|
*/
|
|
134
121
|
function createRequestSizeEnricher(options = {}) {
|
|
135
|
-
return (
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
requestBytes,
|
|
140
|
-
responseBytes
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
122
|
+
return defineEnricher({
|
|
123
|
+
name: "requestSize",
|
|
124
|
+
field: "requestSize",
|
|
125
|
+
compute: ({ headers, response }) => {
|
|
126
|
+
const requestBytes = normalizeNumber(getHeader(headers, "content-length"));
|
|
127
|
+
const responseBytes = normalizeNumber(getHeader(response?.headers, "content-length"));
|
|
128
|
+
if (requestBytes === void 0 && responseBytes === void 0) return void 0;
|
|
129
|
+
return {
|
|
130
|
+
requestBytes,
|
|
131
|
+
responseBytes
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}, options);
|
|
145
135
|
}
|
|
146
136
|
/**
|
|
147
137
|
* Enrich events with W3C trace context data.
|
|
@@ -149,24 +139,51 @@ function createRequestSizeEnricher(options = {}) {
|
|
|
149
139
|
* Also sets `event.traceId` and `event.spanId` at the top level.
|
|
150
140
|
*/
|
|
151
141
|
function createTraceContextEnricher(options = {}) {
|
|
142
|
+
const enricher = defineEnricher({
|
|
143
|
+
name: "traceContext",
|
|
144
|
+
field: "traceContext",
|
|
145
|
+
compute: ({ event, headers }) => {
|
|
146
|
+
const traceparent = getHeader(headers, "traceparent");
|
|
147
|
+
const tracestate = getHeader(headers, "tracestate");
|
|
148
|
+
if (!traceparent && !tracestate) return void 0;
|
|
149
|
+
const parsed = traceparent ? parseTraceparent(traceparent) : void 0;
|
|
150
|
+
return {
|
|
151
|
+
traceparent,
|
|
152
|
+
tracestate,
|
|
153
|
+
traceId: parsed?.traceId ?? event.traceId,
|
|
154
|
+
spanId: parsed?.spanId ?? event.spanId
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}, options);
|
|
152
158
|
return (ctx) => {
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
if (!
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
traceparent,
|
|
159
|
-
tracestate,
|
|
160
|
-
traceId: parsed?.traceId ?? ctx.event.traceId,
|
|
161
|
-
spanId: parsed?.spanId ?? ctx.event.spanId
|
|
162
|
-
};
|
|
163
|
-
const mergedTraceContext = mergeEventField(ctx.event.traceContext, incomingTraceContext, options.overwrite);
|
|
164
|
-
ctx.event.traceContext = mergedTraceContext;
|
|
165
|
-
if (mergedTraceContext.traceId && (options.overwrite || ctx.event.traceId === void 0)) ctx.event.traceId = mergedTraceContext.traceId;
|
|
166
|
-
if (mergedTraceContext.spanId && (options.overwrite || ctx.event.spanId === void 0)) ctx.event.spanId = mergedTraceContext.spanId;
|
|
159
|
+
enricher(ctx);
|
|
160
|
+
const merged = ctx.event.traceContext;
|
|
161
|
+
if (!merged) return;
|
|
162
|
+
if (merged.traceId && (options.overwrite || ctx.event.traceId === void 0)) ctx.event.traceId = merged.traceId;
|
|
163
|
+
if (merged.spanId && (options.overwrite || ctx.event.spanId === void 0)) ctx.event.spanId = merged.spanId;
|
|
167
164
|
};
|
|
168
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Compose every built-in enricher into a single async enricher, in the order
|
|
168
|
+
* `userAgent → geo → requestSize → traceContext`.
|
|
169
|
+
*
|
|
170
|
+
* Drop-in shorthand for the most common middleware setup:
|
|
171
|
+
*
|
|
172
|
+
* ```ts
|
|
173
|
+
* import { createDefaultEnrichers } from 'evlog/enrichers'
|
|
174
|
+
*
|
|
175
|
+
* app.use(evlog({ enrich: createDefaultEnrichers() }))
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
function createDefaultEnrichers(options = {}) {
|
|
179
|
+
return composeEnrichers([
|
|
180
|
+
createUserAgentEnricher(options),
|
|
181
|
+
createGeoEnricher(options),
|
|
182
|
+
createRequestSizeEnricher(options),
|
|
183
|
+
createTraceContextEnricher(options)
|
|
184
|
+
], { name: "default-enrichers" });
|
|
185
|
+
}
|
|
169
186
|
//#endregion
|
|
170
|
-
export { createGeoEnricher, createRequestSizeEnricher, createTraceContextEnricher, createUserAgentEnricher };
|
|
187
|
+
export { createDefaultEnrichers, createGeoEnricher, createRequestSizeEnricher, createTraceContextEnricher, createUserAgentEnricher };
|
|
171
188
|
|
|
172
189
|
//# sourceMappingURL=enrichers.mjs.map
|
package/dist/enrichers.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enrichers.mjs","names":[],"sources":["../src/enrichers/index.ts"],"sourcesContent":["import type { EnrichContext } from '../types'\n\nexport interface EnricherOptions {\n /**\n * When true, overwrite any existing fields in the event.\n * Defaults to false to preserve user-provided data.\n */\n overwrite?: boolean\n}\n\nexport interface UserAgentInfo {\n raw: string\n browser?: { name: string; version?: string }\n os?: { name: string; version?: string }\n device?: { type: 'mobile' | 'tablet' | 'desktop' | 'bot' | 'unknown' }\n}\n\nexport interface GeoInfo {\n country?: string\n region?: string\n regionCode?: string\n city?: string\n latitude?: number\n longitude?: number\n}\n\nexport interface RequestSizeInfo {\n requestBytes?: number\n responseBytes?: number\n}\n\nexport interface TraceContextInfo {\n traceparent?: string\n tracestate?: string\n traceId?: string\n spanId?: string\n}\n\nfunction getHeader(headers: Record<string, string> | undefined, name: string): string | undefined {\n if (!headers) return undefined\n if (headers[name] !== undefined) return headers[name]\n const lowerName = name.toLowerCase()\n if (headers[lowerName] !== undefined) return headers[lowerName]\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === lowerName) return value\n }\n return undefined\n}\n\nfunction parseUserAgent(ua: string): UserAgentInfo {\n const lower = ua.toLowerCase()\n\n let deviceType: UserAgentInfo['device'] = { type: 'unknown' }\n if (/bot|crawl|spider|slurp|bingpreview/.test(lower)) {\n deviceType = { type: 'bot' }\n } else if (/ipad|tablet/.test(lower)) {\n deviceType = { type: 'tablet' }\n } else if (/mobi|iphone|android/.test(lower)) {\n deviceType = { type: 'mobile' }\n } else if (ua.length > 0) {\n deviceType = { type: 'desktop' }\n }\n\n const browserMatchers: Array<{ name: string, regex: RegExp }> = [\n { name: 'Edge', regex: /edg\\/([\\d.]+)/i },\n { name: 'Chrome', regex: /chrome\\/([\\d.]+)/i },\n { name: 'Firefox', regex: /firefox\\/([\\d.]+)/i },\n { name: 'Safari', regex: /version\\/([\\d.]+).*safari/i },\n ]\n\n let browser: UserAgentInfo['browser']\n for (const matcher of browserMatchers) {\n const match = ua.match(matcher.regex)\n if (match) {\n browser = { name: matcher.name, version: match[1] }\n break\n }\n }\n\n let os: UserAgentInfo['os']\n if (/windows nt/i.test(ua)) {\n const match = ua.match(/windows nt ([\\d.]+)/i)\n os = { name: 'Windows', version: match?.[1] }\n } else if (/mac os x/i.test(ua) && !/iphone|ipad|ipod/i.test(ua)) {\n const match = ua.match(/mac os x ([\\d_]+)/i)\n os = { name: 'macOS', version: match?.[1]?.replace(/_/g, '.') }\n } else if (/iphone|ipad|ipod/i.test(ua)) {\n const match = ua.match(/os ([\\d_]+)/i)\n os = { name: 'iOS', version: match?.[1]?.replace(/_/g, '.') }\n } else if (/android/i.test(ua)) {\n const match = ua.match(/android ([\\d.]+)/i)\n os = { name: 'Android', version: match?.[1] }\n } else if (/linux/i.test(ua)) {\n os = { name: 'Linux' }\n }\n\n return {\n raw: ua,\n browser,\n os,\n device: deviceType,\n }\n}\n\nfunction parseTraceparent(traceparent: string): Pick<TraceContextInfo, 'traceId' | 'spanId'> | undefined {\n const match = traceparent.match(/^[\\da-f]{2}-([\\da-f]{32})-([\\da-f]{16})-[\\da-f]{2}$/i)\n if (!match) return undefined\n return { traceId: match[1], spanId: match[2] }\n}\n\nfunction mergeEventField<T extends object>(\n existing: unknown,\n computed: T,\n overwrite?: boolean,\n): T {\n if (overwrite || existing === undefined || existing === null || typeof existing !== 'object') {\n return computed\n }\n return { ...computed, ...(existing as T) }\n}\n\nfunction normalizeNumber(value: string | undefined): number | undefined {\n if (!value) return undefined\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : undefined\n}\n\n/**\n * Enrich events with parsed user agent data.\n * Sets `event.userAgent` with `UserAgentInfo` shape: `{ raw, browser?, os?, device? }`.\n */\nexport function createUserAgentEnricher(options: EnricherOptions = {}): (ctx: EnrichContext) => void {\n return (ctx) => {\n const ua = getHeader(ctx.headers, 'user-agent')\n if (!ua) return\n const info = parseUserAgent(ua)\n ctx.event.userAgent = mergeEventField<UserAgentInfo>(ctx.event.userAgent, info, options.overwrite)\n }\n}\n\n/**\n * Enrich events with geo data from platform headers.\n * Sets `event.geo` with `GeoInfo` shape: `{ country?, region?, regionCode?, city?, latitude?, longitude? }`.\n *\n * Supports Vercel (`x-vercel-ip-*`) headers out of the box.\n *\n * **Cloudflare note:** Only `cf-ipcountry` is an actual HTTP header added by Cloudflare.\n * The `cf-region`, `cf-city`, `cf-latitude`, `cf-longitude` headers are NOT standard\n * Cloudflare headers — they are properties of `request.cf` which is not exposed as HTTP\n * headers. For full geo data on Cloudflare, write a custom enricher that reads `request.cf`\n * or use a Workers middleware to copy `cf` properties into custom headers.\n */\nexport function createGeoEnricher(options: EnricherOptions = {}): (ctx: EnrichContext) => void {\n return (ctx) => {\n const { headers } = ctx\n if (!headers) return\n\n const geo: GeoInfo = {\n country: getHeader(headers, 'x-vercel-ip-country') ?? getHeader(headers, 'cf-ipcountry'),\n region: getHeader(headers, 'x-vercel-ip-country-region') ?? getHeader(headers, 'cf-region'),\n regionCode: getHeader(headers, 'x-vercel-ip-country-region-code') ?? getHeader(headers, 'cf-region-code'),\n city: getHeader(headers, 'x-vercel-ip-city') ?? getHeader(headers, 'cf-city'),\n latitude: normalizeNumber(getHeader(headers, 'x-vercel-ip-latitude') ?? getHeader(headers, 'cf-latitude')),\n longitude: normalizeNumber(getHeader(headers, 'x-vercel-ip-longitude') ?? getHeader(headers, 'cf-longitude')),\n }\n\n if (Object.values(geo).every(value => value === undefined)) return\n ctx.event.geo = mergeEventField<GeoInfo>(ctx.event.geo, geo, options.overwrite)\n }\n}\n\n/**\n * Enrich events with request/response payload sizes.\n * Sets `event.requestSize` with `RequestSizeInfo` shape: `{ requestBytes?, responseBytes? }`.\n */\nexport function createRequestSizeEnricher(options: EnricherOptions = {}): (ctx: EnrichContext) => void {\n return (ctx) => {\n const requestBytes = normalizeNumber(getHeader(ctx.headers, 'content-length'))\n const responseBytes = normalizeNumber(getHeader(ctx.response?.headers, 'content-length'))\n\n const sizes: RequestSizeInfo = {\n requestBytes,\n responseBytes,\n }\n\n if (requestBytes === undefined && responseBytes === undefined) return\n ctx.event.requestSize = mergeEventField<RequestSizeInfo>(ctx.event.requestSize, sizes, options.overwrite)\n }\n}\n\n/**\n * Enrich events with W3C trace context data.\n * Sets `event.traceContext` with `TraceContextInfo` shape: `{ traceparent?, tracestate?, traceId?, spanId? }`.\n * Also sets `event.traceId` and `event.spanId` at the top level.\n */\nexport function createTraceContextEnricher(options: EnricherOptions = {}): (ctx: EnrichContext) => void {\n return (ctx) => {\n const traceparent = getHeader(ctx.headers, 'traceparent')\n const tracestate = getHeader(ctx.headers, 'tracestate')\n if (!traceparent && !tracestate) return\n\n const parsed = traceparent ? parseTraceparent(traceparent) : undefined\n const incomingTraceContext: TraceContextInfo = {\n traceparent,\n tracestate,\n traceId: parsed?.traceId ?? (ctx.event.traceId as string | undefined),\n spanId: parsed?.spanId ?? (ctx.event.spanId as string | undefined),\n }\n\n const mergedTraceContext = mergeEventField<TraceContextInfo>(\n ctx.event.traceContext,\n incomingTraceContext,\n options.overwrite,\n )\n ctx.event.traceContext = mergedTraceContext\n\n if (mergedTraceContext.traceId && (options.overwrite || ctx.event.traceId === undefined)) {\n ctx.event.traceId = mergedTraceContext.traceId\n }\n if (mergedTraceContext.spanId && (options.overwrite || ctx.event.spanId === undefined)) {\n ctx.event.spanId = mergedTraceContext.spanId\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"enrichers.mjs","names":[],"sources":["../src/enrichers/index.ts"],"sourcesContent":["import type { EnrichContext } from '../types'\nimport { composeEnrichers } from '../shared/compose'\nimport { defineEnricher, type EnricherOptions } from '../shared/enricher'\nimport { getHeader, normalizeNumber } from '../shared/headers'\n\nexport type { EnricherOptions }\n\nexport interface UserAgentInfo {\n raw: string\n browser?: { name: string; version?: string }\n os?: { name: string; version?: string }\n device?: { type: 'mobile' | 'tablet' | 'desktop' | 'bot' | 'unknown' }\n}\n\nexport interface GeoInfo {\n country?: string\n region?: string\n regionCode?: string\n city?: string\n latitude?: number\n longitude?: number\n}\n\nexport interface RequestSizeInfo {\n requestBytes?: number\n responseBytes?: number\n}\n\nexport interface TraceContextInfo {\n traceparent?: string\n tracestate?: string\n traceId?: string\n spanId?: string\n}\n\nfunction parseUserAgent(ua: string): UserAgentInfo {\n const lower = ua.toLowerCase()\n\n let deviceType: UserAgentInfo['device'] = { type: 'unknown' }\n if (/bot|crawl|spider|slurp|bingpreview/.test(lower)) {\n deviceType = { type: 'bot' }\n } else if (/ipad|tablet/.test(lower)) {\n deviceType = { type: 'tablet' }\n } else if (/mobi|iphone|android/.test(lower)) {\n deviceType = { type: 'mobile' }\n } else if (ua.length > 0) {\n deviceType = { type: 'desktop' }\n }\n\n const browserMatchers: Array<{ name: string, regex: RegExp }> = [\n { name: 'Edge', regex: /edg\\/([\\d.]+)/i },\n { name: 'Chrome', regex: /chrome\\/([\\d.]+)/i },\n { name: 'Firefox', regex: /firefox\\/([\\d.]+)/i },\n { name: 'Safari', regex: /version\\/([\\d.]+).*safari/i },\n ]\n\n let browser: UserAgentInfo['browser']\n for (const matcher of browserMatchers) {\n const match = ua.match(matcher.regex)\n if (match) {\n browser = { name: matcher.name, version: match[1] }\n break\n }\n }\n\n let os: UserAgentInfo['os']\n if (/windows nt/i.test(ua)) {\n const match = ua.match(/windows nt ([\\d.]+)/i)\n os = { name: 'Windows', version: match?.[1] }\n } else if (/mac os x/i.test(ua) && !/iphone|ipad|ipod/i.test(ua)) {\n const match = ua.match(/mac os x ([\\d_]+)/i)\n os = { name: 'macOS', version: match?.[1]?.replace(/_/g, '.') }\n } else if (/iphone|ipad|ipod/i.test(ua)) {\n const match = ua.match(/os ([\\d_]+)/i)\n os = { name: 'iOS', version: match?.[1]?.replace(/_/g, '.') }\n } else if (/android/i.test(ua)) {\n const match = ua.match(/android ([\\d.]+)/i)\n os = { name: 'Android', version: match?.[1] }\n } else if (/linux/i.test(ua)) {\n os = { name: 'Linux' }\n }\n\n return {\n raw: ua,\n browser,\n os,\n device: deviceType,\n }\n}\n\nfunction parseTraceparent(traceparent: string): Pick<TraceContextInfo, 'traceId' | 'spanId'> | undefined {\n const match = traceparent.match(/^[\\da-f]{2}-([\\da-f]{32})-([\\da-f]{16})-[\\da-f]{2}$/i)\n if (!match) return undefined\n return { traceId: match[1], spanId: match[2] }\n}\n\n/**\n * Enrich events with parsed user agent data.\n * Sets `event.userAgent` with `UserAgentInfo` shape: `{ raw, browser?, os?, device? }`.\n */\nexport function createUserAgentEnricher(options: EnricherOptions = {}): (ctx: EnrichContext) => void {\n return defineEnricher<UserAgentInfo>({\n name: 'userAgent',\n field: 'userAgent',\n compute: ({ headers }) => {\n const ua = getHeader(headers, 'user-agent')\n return ua ? parseUserAgent(ua) : undefined\n },\n }, options)\n}\n\n/**\n * Enrich events with geo data from platform headers.\n * Sets `event.geo` with `GeoInfo` shape: `{ country?, region?, regionCode?, city?, latitude?, longitude? }`.\n *\n * Supports Vercel (`x-vercel-ip-*`) headers out of the box.\n *\n * **Cloudflare note:** Only `cf-ipcountry` is an actual HTTP header added by Cloudflare.\n * The `cf-region`, `cf-city`, `cf-latitude`, `cf-longitude` headers are NOT standard\n * Cloudflare headers — they are properties of `request.cf` which is not exposed as HTTP\n * headers. For full geo data on Cloudflare, write a custom enricher that reads `request.cf`\n * or use a Workers middleware to copy `cf` properties into custom headers.\n */\nexport function createGeoEnricher(options: EnricherOptions = {}): (ctx: EnrichContext) => void {\n return defineEnricher<GeoInfo>({\n name: 'geo',\n field: 'geo',\n compute: ({ headers }) => {\n if (!headers) return undefined\n const geo: GeoInfo = {\n country: getHeader(headers, 'x-vercel-ip-country') ?? getHeader(headers, 'cf-ipcountry'),\n region: getHeader(headers, 'x-vercel-ip-country-region') ?? getHeader(headers, 'cf-region'),\n regionCode: getHeader(headers, 'x-vercel-ip-country-region-code') ?? getHeader(headers, 'cf-region-code'),\n city: getHeader(headers, 'x-vercel-ip-city') ?? getHeader(headers, 'cf-city'),\n latitude: normalizeNumber(getHeader(headers, 'x-vercel-ip-latitude') ?? getHeader(headers, 'cf-latitude')),\n longitude: normalizeNumber(getHeader(headers, 'x-vercel-ip-longitude') ?? getHeader(headers, 'cf-longitude')),\n }\n return Object.values(geo).every(value => value === undefined) ? undefined : geo\n },\n }, options)\n}\n\n/**\n * Enrich events with request/response payload sizes.\n * Sets `event.requestSize` with `RequestSizeInfo` shape: `{ requestBytes?, responseBytes? }`.\n */\nexport function createRequestSizeEnricher(options: EnricherOptions = {}): (ctx: EnrichContext) => void {\n return defineEnricher<RequestSizeInfo>({\n name: 'requestSize',\n field: 'requestSize',\n compute: ({ headers, response }) => {\n const requestBytes = normalizeNumber(getHeader(headers, 'content-length'))\n const responseBytes = normalizeNumber(getHeader(response?.headers, 'content-length'))\n if (requestBytes === undefined && responseBytes === undefined) return undefined\n return { requestBytes, responseBytes }\n },\n }, options)\n}\n\n/**\n * Enrich events with W3C trace context data.\n * Sets `event.traceContext` with `TraceContextInfo` shape: `{ traceparent?, tracestate?, traceId?, spanId? }`.\n * Also sets `event.traceId` and `event.spanId` at the top level.\n */\nexport function createTraceContextEnricher(options: EnricherOptions = {}): (ctx: EnrichContext) => void {\n const enricher = defineEnricher<TraceContextInfo>({\n name: 'traceContext',\n field: 'traceContext',\n compute: ({ event, headers }) => {\n const traceparent = getHeader(headers, 'traceparent')\n const tracestate = getHeader(headers, 'tracestate')\n if (!traceparent && !tracestate) return undefined\n const parsed = traceparent ? parseTraceparent(traceparent) : undefined\n return {\n traceparent,\n tracestate,\n traceId: parsed?.traceId ?? (event.traceId as string | undefined),\n spanId: parsed?.spanId ?? (event.spanId as string | undefined),\n }\n },\n }, options)\n\n // Trace context also pins traceId/spanId at the top level for transports that\n // expect them outside the nested object.\n return (ctx) => {\n enricher(ctx)\n const merged = ctx.event.traceContext as TraceContextInfo | undefined\n if (!merged) return\n if (merged.traceId && (options.overwrite || ctx.event.traceId === undefined)) {\n ctx.event.traceId = merged.traceId\n }\n if (merged.spanId && (options.overwrite || ctx.event.spanId === undefined)) {\n ctx.event.spanId = merged.spanId\n }\n }\n}\n\n/**\n * Compose every built-in enricher into a single async enricher, in the order\n * `userAgent → geo → requestSize → traceContext`.\n *\n * Drop-in shorthand for the most common middleware setup:\n *\n * ```ts\n * import { createDefaultEnrichers } from 'evlog/enrichers'\n *\n * app.use(evlog({ enrich: createDefaultEnrichers() }))\n * ```\n */\nexport function createDefaultEnrichers(options: EnricherOptions = {}): (ctx: EnrichContext) => Promise<void> {\n return composeEnrichers(\n [\n createUserAgentEnricher(options),\n createGeoEnricher(options),\n createRequestSizeEnricher(options),\n createTraceContextEnricher(options),\n ],\n { name: 'default-enrichers' },\n )\n}\n"],"mappings":"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"}
|