evlog 1.10.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/adapters/axiom.d.mts +18 -3
- package/dist/adapters/axiom.d.mts.map +1 -1
- package/dist/adapters/axiom.mjs +25 -1
- package/dist/adapters/axiom.mjs.map +1 -1
- package/dist/adapters/posthog.d.mts +40 -29
- package/dist/adapters/posthog.d.mts.map +1 -1
- package/dist/adapters/posthog.mjs +71 -54
- package/dist/adapters/posthog.mjs.map +1 -1
- package/dist/nitro/plugin.mjs +1 -0
- package/dist/nitro/plugin.mjs.map +1 -1
- package/dist/nitro/v3/index.d.mts +2 -1
- package/dist/nitro/v3/index.mjs +2 -1
- package/dist/nitro/v3/middleware.d.mts +25 -0
- package/dist/nitro/v3/middleware.d.mts.map +1 -0
- package/dist/nitro/v3/middleware.mjs +45 -0
- package/dist/nitro/v3/middleware.mjs.map +1 -0
- package/dist/nitro/v3/plugin.mjs +7 -3
- package/dist/nitro/v3/plugin.mjs.map +1 -1
- package/dist/nuxt/module.d.mts +31 -11
- package/dist/nuxt/module.d.mts.map +1 -1
- package/dist/nuxt/module.mjs +1 -1
- package/dist/nuxt/module.mjs.map +1 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -996,7 +996,7 @@ try {
|
|
|
996
996
|
| **Analog** | Nitro v2 module setup |
|
|
997
997
|
| **Vinxi** | Nitro v2 module setup |
|
|
998
998
|
| **SolidStart** | Nitro v2 module setup ([example](./examples/solidstart)) |
|
|
999
|
-
| **TanStack Start** | Nitro
|
|
999
|
+
| **TanStack Start** | Nitro v3 module setup ([example](./examples/tanstack-start)) |
|
|
1000
1000
|
|
|
1001
1001
|
## Agent Skills
|
|
1002
1002
|
|
|
@@ -2,18 +2,33 @@ import { DrainContext, WideEvent } from "../types.mjs";
|
|
|
2
2
|
import "../index.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/adapters/axiom.d.ts
|
|
5
|
-
interface
|
|
5
|
+
interface BaseAxiomConfig {
|
|
6
6
|
/** Axiom dataset name */
|
|
7
7
|
dataset: string;
|
|
8
8
|
/** Axiom API token */
|
|
9
9
|
token: string;
|
|
10
10
|
/** Organization ID (required for Personal Access Tokens) */
|
|
11
11
|
orgId?: string;
|
|
12
|
-
/** Base URL for Axiom API. Default: https://api.axiom.co */
|
|
13
|
-
baseUrl?: string;
|
|
14
12
|
/** Request timeout in milliseconds. Default: 5000 */
|
|
15
13
|
timeout?: number;
|
|
16
14
|
}
|
|
15
|
+
interface EdgeAxiomConfig {
|
|
16
|
+
/**
|
|
17
|
+
* Edge URL for Axiom ingest/query endpoints.
|
|
18
|
+
* If no path is provided, uses /v1/ingest/{dataset}.
|
|
19
|
+
* If a custom path is provided, it is used as-is (trailing slash trimmed).
|
|
20
|
+
*/
|
|
21
|
+
edgeUrl: string;
|
|
22
|
+
/** Mutually exclusive with edgeUrl. */
|
|
23
|
+
baseUrl?: never;
|
|
24
|
+
}
|
|
25
|
+
interface EndpointAxiomConfig {
|
|
26
|
+
/** Base URL for Axiom API. Uses /v1/datasets/{dataset}/ingest. */
|
|
27
|
+
baseUrl?: string;
|
|
28
|
+
/** Mutually exclusive with baseUrl. */
|
|
29
|
+
edgeUrl?: never;
|
|
30
|
+
}
|
|
31
|
+
type AxiomConfig = BaseAxiomConfig & (EdgeAxiomConfig | EndpointAxiomConfig);
|
|
17
32
|
/**
|
|
18
33
|
* Create a drain function for sending logs to Axiom.
|
|
19
34
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"axiom.d.mts","names":[],"sources":["../../src/adapters/axiom.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"axiom.d.mts","names":[],"sources":["../../src/adapters/axiom.ts"],"mappings":";;;;UAMU,eAAA;;EAER,OAAA;EAFQ;EAIR,KAAA;;EAEA,KAAA;EAJA;EAMA,OAAA;AAAA;AAAA,UAGQ,eAAA;EAHD;;AAAA;;;EASP,OAAA;EAEO;EAAP,OAAA;AAAA;AAAA,UAGQ,mBAAA;EAER;EAAA,OAAA;EAKU;EAHV,OAAA;AAAA;AAAA,KAGU,WAAA,GAAc,eAAA,IAAmB,eAAA,GAAkB,mBAAA;;;;;;;;;;AAoC/D;;;;;;;;;;;iBAAgB,gBAAA,CAAiB,SAAA,GAAY,OAAA,CAAQ,WAAA,KAAY,GAAA,EAAb,YAAA,GAAa,YAAA,OAAA,OAAA;;;;;;;;AAoCjE;;;;iBAAsB,WAAA,CAAY,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,WAAA,GAAc,OAAA;;;;;;;;;;;AAe1E;iBAAsB,gBAAA,CAAiB,MAAA,EAAQ,SAAA,IAAa,MAAA,EAAQ,WAAA,GAAc,OAAA"}
|
package/dist/adapters/axiom.mjs
CHANGED
|
@@ -14,6 +14,10 @@ const AXIOM_FIELDS = [
|
|
|
14
14
|
key: "orgId",
|
|
15
15
|
env: ["NUXT_AXIOM_ORG_ID", "AXIOM_ORG_ID"]
|
|
16
16
|
},
|
|
17
|
+
{
|
|
18
|
+
key: "edgeUrl",
|
|
19
|
+
env: ["NUXT_AXIOM_EDGE_URL", "AXIOM_EDGE_URL"]
|
|
20
|
+
},
|
|
17
21
|
{
|
|
18
22
|
key: "baseUrl",
|
|
19
23
|
env: ["NUXT_AXIOM_URL", "AXIOM_URL"]
|
|
@@ -49,6 +53,10 @@ function createAxiomDrain(overrides) {
|
|
|
49
53
|
console.error("[evlog/axiom] Missing dataset or token. Set NUXT_AXIOM_TOKEN/NUXT_AXIOM_DATASET env vars or pass to createAxiomDrain()");
|
|
50
54
|
return null;
|
|
51
55
|
}
|
|
56
|
+
if (config.edgeUrl && config.baseUrl) {
|
|
57
|
+
console.warn("[evlog/axiom] Both edgeUrl and baseUrl are set. edgeUrl takes precedence for ingest.");
|
|
58
|
+
delete config.baseUrl;
|
|
59
|
+
}
|
|
52
60
|
return config;
|
|
53
61
|
},
|
|
54
62
|
send: sendBatchToAxiom
|
|
@@ -80,7 +88,7 @@ async function sendToAxiom(event, config) {
|
|
|
80
88
|
* ```
|
|
81
89
|
*/
|
|
82
90
|
async function sendBatchToAxiom(events, config) {
|
|
83
|
-
const url =
|
|
91
|
+
const url = resolveIngestUrl(config);
|
|
84
92
|
const headers = {
|
|
85
93
|
"Content-Type": "application/json",
|
|
86
94
|
"Authorization": `Bearer ${config.token}`
|
|
@@ -94,6 +102,22 @@ async function sendBatchToAxiom(events, config) {
|
|
|
94
102
|
label: "Axiom"
|
|
95
103
|
});
|
|
96
104
|
}
|
|
105
|
+
function resolveIngestUrl(config) {
|
|
106
|
+
const encodedDataset = encodeURIComponent(config.dataset);
|
|
107
|
+
if (!config.edgeUrl) return `${config.baseUrl ?? "https://api.axiom.co"}/v1/datasets/${encodedDataset}/ingest`;
|
|
108
|
+
try {
|
|
109
|
+
const parsed = new URL(config.edgeUrl);
|
|
110
|
+
if (parsed.pathname === "" || parsed.pathname === "/") {
|
|
111
|
+
parsed.pathname = `/v1/ingest/${encodedDataset}`;
|
|
112
|
+
return parsed.toString();
|
|
113
|
+
}
|
|
114
|
+
parsed.pathname = parsed.pathname.replace(/\/+$/, "");
|
|
115
|
+
return parsed.toString();
|
|
116
|
+
} catch {
|
|
117
|
+
console.warn(`[evlog/axiom] edgeUrl "${config.edgeUrl}" is not a valid URL, falling back to string concatenation.`);
|
|
118
|
+
return `${config.edgeUrl.replace(/\/+$/, "")}/v1/ingest/${encodedDataset}`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
97
121
|
|
|
98
122
|
//#endregion
|
|
99
123
|
export { createAxiomDrain, sendBatchToAxiom, sendToAxiom };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"axiom.mjs","names":[],"sources":["../../src/adapters/axiom.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\n\
|
|
1
|
+
{"version":3,"file":"axiom.mjs","names":[],"sources":["../../src/adapters/axiom.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\n\ninterface BaseAxiomConfig {\n /** Axiom dataset name */\n dataset: string\n /** Axiom API token */\n token: string\n /** Organization ID (required for Personal Access Tokens) */\n orgId?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n}\n\ninterface EdgeAxiomConfig {\n /**\n * Edge URL for Axiom ingest/query endpoints.\n * If no path is provided, uses /v1/ingest/{dataset}.\n * If a custom path is provided, it is used as-is (trailing slash trimmed).\n */\n edgeUrl: string\n /** Mutually exclusive with edgeUrl. */\n baseUrl?: never\n}\n\ninterface EndpointAxiomConfig {\n /** Base URL for Axiom API. Uses /v1/datasets/{dataset}/ingest. */\n baseUrl?: string\n /** Mutually exclusive with baseUrl. */\n edgeUrl?: never\n}\n\nexport type AxiomConfig = BaseAxiomConfig & (EdgeAxiomConfig | EndpointAxiomConfig)\n\ntype ResolvedAxiomConfig = BaseAxiomConfig & {\n edgeUrl?: string\n baseUrl?: string\n}\n\nconst AXIOM_FIELDS: ConfigField<ResolvedAxiomConfig>[] = [\n { key: 'dataset', env: ['NUXT_AXIOM_DATASET', 'AXIOM_DATASET'] },\n { key: 'token', env: ['NUXT_AXIOM_TOKEN', 'AXIOM_TOKEN'] },\n { key: 'orgId', env: ['NUXT_AXIOM_ORG_ID', 'AXIOM_ORG_ID'] },\n { key: 'edgeUrl', env: ['NUXT_AXIOM_EDGE_URL', 'AXIOM_EDGE_URL'] },\n { key: 'baseUrl', env: ['NUXT_AXIOM_URL', 'AXIOM_URL'] },\n { key: 'timeout' },\n]\n\n/**\n * Create a drain function for sending logs to Axiom.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createAxiomDrain()\n * 2. runtimeConfig.evlog.axiom\n * 3. runtimeConfig.axiom\n * 4. Environment variables: NUXT_AXIOM_*, AXIOM_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_AXIOM_TOKEN and NUXT_AXIOM_DATASET env vars\n * nitroApp.hooks.hook('evlog:drain', createAxiomDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createAxiomDrain({\n * dataset: 'my-dataset',\n * }))\n * ```\n */\nexport function createAxiomDrain(overrides?: Partial<AxiomConfig>) {\n return defineDrain<AxiomConfig>({\n name: 'axiom',\n resolve: () => {\n const config = resolveAdapterConfig<ResolvedAxiomConfig>(\n 'axiom',\n AXIOM_FIELDS,\n overrides as Partial<ResolvedAxiomConfig>,\n )\n if (!config.dataset || !config.token) {\n console.error('[evlog/axiom] Missing dataset or token. Set NUXT_AXIOM_TOKEN/NUXT_AXIOM_DATASET env vars or pass to createAxiomDrain()')\n return null\n }\n\n if (config.edgeUrl && config.baseUrl) {\n console.warn('[evlog/axiom] Both edgeUrl and baseUrl are set. edgeUrl takes precedence for ingest.')\n delete config.baseUrl\n }\n\n return config as AxiomConfig\n },\n send: sendBatchToAxiom,\n })\n}\n\n/**\n * Send a single event to Axiom.\n *\n * @example\n * ```ts\n * await sendToAxiom(event, {\n * dataset: 'my-logs',\n * token: process.env.AXIOM_TOKEN!,\n * })\n * ```\n */\nexport async function sendToAxiom(event: WideEvent, config: AxiomConfig): Promise<void> {\n await sendBatchToAxiom([event], config)\n}\n\n/**\n * Send a batch of events to Axiom.\n *\n * @example\n * ```ts\n * await sendBatchToAxiom(events, {\n * dataset: 'my-logs',\n * token: process.env.AXIOM_TOKEN!,\n * })\n * ```\n */\nexport async function sendBatchToAxiom(events: WideEvent[], config: AxiomConfig): Promise<void> {\n const url = resolveIngestUrl(config)\n\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.token}`,\n }\n\n if (config.orgId) {\n headers['X-Axiom-Org-Id'] = config.orgId\n }\n\n await httpPost({\n url,\n headers,\n body: JSON.stringify(events),\n timeout: config.timeout ?? 5000,\n label: 'Axiom',\n })\n}\n\nfunction resolveIngestUrl(config: AxiomConfig): string {\n const encodedDataset = encodeURIComponent(config.dataset)\n\n if (!config.edgeUrl) {\n const baseUrl = config.baseUrl ?? 'https://api.axiom.co'\n return `${baseUrl}/v1/datasets/${encodedDataset}/ingest`\n }\n\n try {\n const parsed = new URL(config.edgeUrl)\n\n if (parsed.pathname === '' || parsed.pathname === '/') {\n parsed.pathname = `/v1/ingest/${encodedDataset}`\n return parsed.toString()\n }\n\n parsed.pathname = parsed.pathname.replace(/\\/+$/, '')\n return parsed.toString()\n } catch {\n console.warn(`[evlog/axiom] edgeUrl \"${config.edgeUrl}\" is not a valid URL, falling back to string concatenation.`)\n const trimmed = config.edgeUrl.replace(/\\/+$/, '')\n return `${trimmed}/v1/ingest/${encodedDataset}`\n }\n}\n"],"mappings":";;;AA0CA,MAAM,eAAmD;CACvD;EAAE,KAAK;EAAW,KAAK,CAAC,sBAAsB,gBAAgB;EAAE;CAChE;EAAE,KAAK;EAAS,KAAK,CAAC,oBAAoB,cAAc;EAAE;CAC1D;EAAE,KAAK;EAAS,KAAK,CAAC,qBAAqB,eAAe;EAAE;CAC5D;EAAE,KAAK;EAAW,KAAK,CAAC,uBAAuB,iBAAiB;EAAE;CAClE;EAAE,KAAK;EAAW,KAAK,CAAC,kBAAkB,YAAY;EAAE;CACxD,EAAE,KAAK,WAAW;CACnB;;;;;;;;;;;;;;;;;;;;;AAsBD,SAAgB,iBAAiB,WAAkC;AACjE,QAAO,YAAyB;EAC9B,MAAM;EACN,eAAe;GACb,MAAM,SAAS,qBACb,SACA,cACA,UACD;AACD,OAAI,CAAC,OAAO,WAAW,CAAC,OAAO,OAAO;AACpC,YAAQ,MAAM,yHAAyH;AACvI,WAAO;;AAGT,OAAI,OAAO,WAAW,OAAO,SAAS;AACpC,YAAQ,KAAK,uFAAuF;AACpG,WAAO,OAAO;;AAGhB,UAAO;;EAET,MAAM;EACP,CAAC;;;;;;;;;;;;;AAcJ,eAAsB,YAAY,OAAkB,QAAoC;AACtF,OAAM,iBAAiB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;;AAczC,eAAsB,iBAAiB,QAAqB,QAAoC;CAC9F,MAAM,MAAM,iBAAiB,OAAO;CAEpC,MAAM,UAAkC;EACtC,gBAAgB;EAChB,iBAAiB,UAAU,OAAO;EACnC;AAED,KAAI,OAAO,MACT,SAAQ,oBAAoB,OAAO;AAGrC,OAAM,SAAS;EACb;EACA;EACA,MAAM,KAAK,UAAU,OAAO;EAC5B,SAAS,OAAO,WAAW;EAC3B,OAAO;EACR,CAAC;;AAGJ,SAAS,iBAAiB,QAA6B;CACrD,MAAM,iBAAiB,mBAAmB,OAAO,QAAQ;AAEzD,KAAI,CAAC,OAAO,QAEV,QAAO,GADS,OAAO,WAAW,uBAChB,eAAe,eAAe;AAGlD,KAAI;EACF,MAAM,SAAS,IAAI,IAAI,OAAO,QAAQ;AAEtC,MAAI,OAAO,aAAa,MAAM,OAAO,aAAa,KAAK;AACrD,UAAO,WAAW,cAAc;AAChC,UAAO,OAAO,UAAU;;AAG1B,SAAO,WAAW,OAAO,SAAS,QAAQ,QAAQ,GAAG;AACrD,SAAO,OAAO,UAAU;SAClB;AACN,UAAQ,KAAK,0BAA0B,OAAO,QAAQ,6DAA6D;AAEnH,SAAO,GADS,OAAO,QAAQ,QAAQ,QAAQ,GAAG,CAChC,aAAa"}
|
|
@@ -7,12 +7,14 @@ interface PostHogConfig {
|
|
|
7
7
|
apiKey: string;
|
|
8
8
|
/** PostHog host URL. Default: https://us.i.posthog.com */
|
|
9
9
|
host?: string;
|
|
10
|
+
/** Request timeout in milliseconds. Default: 5000 */
|
|
11
|
+
timeout?: number;
|
|
12
|
+
}
|
|
13
|
+
interface PostHogEventsConfig extends PostHogConfig {
|
|
10
14
|
/** PostHog event name. Default: evlog_wide_event */
|
|
11
15
|
eventName?: string;
|
|
12
16
|
/** Override distinct_id (defaults to event.service) */
|
|
13
17
|
distinctId?: string;
|
|
14
|
-
/** Request timeout in milliseconds. Default: 5000 */
|
|
15
|
-
timeout?: number;
|
|
16
18
|
}
|
|
17
19
|
/** PostHog event structure for the batch API */
|
|
18
20
|
interface PostHogEvent {
|
|
@@ -22,11 +24,11 @@ interface PostHogEvent {
|
|
|
22
24
|
properties: Record<string, unknown>;
|
|
23
25
|
}
|
|
24
26
|
/**
|
|
25
|
-
* Convert a WideEvent to a PostHog event format.
|
|
27
|
+
* Convert a WideEvent to a PostHog custom event format.
|
|
26
28
|
*/
|
|
27
|
-
declare function toPostHogEvent(event: WideEvent, config:
|
|
29
|
+
declare function toPostHogEvent(event: WideEvent, config: PostHogEventsConfig): PostHogEvent;
|
|
28
30
|
/**
|
|
29
|
-
* Create a drain function for sending logs to PostHog.
|
|
31
|
+
* Create a drain function for sending logs to PostHog Logs via OTLP.
|
|
30
32
|
*
|
|
31
33
|
* Configuration priority (highest to lowest):
|
|
32
34
|
* 1. Overrides passed to createPostHogDrain()
|
|
@@ -48,7 +50,7 @@ declare function toPostHogEvent(event: WideEvent, config: PostHogConfig): PostHo
|
|
|
48
50
|
*/
|
|
49
51
|
declare function createPostHogDrain(overrides?: Partial<PostHogConfig>): (ctx: DrainContext | DrainContext[]) => Promise<void>;
|
|
50
52
|
/**
|
|
51
|
-
* Send a single event to PostHog.
|
|
53
|
+
* Send a single event to PostHog Logs via OTLP.
|
|
52
54
|
*
|
|
53
55
|
* @example
|
|
54
56
|
* ```ts
|
|
@@ -59,7 +61,7 @@ declare function createPostHogDrain(overrides?: Partial<PostHogConfig>): (ctx: D
|
|
|
59
61
|
*/
|
|
60
62
|
declare function sendToPostHog(event: WideEvent, config: PostHogConfig): Promise<void>;
|
|
61
63
|
/**
|
|
62
|
-
* Send a batch of events to PostHog.
|
|
64
|
+
* Send a batch of events to PostHog Logs via OTLP.
|
|
63
65
|
*
|
|
64
66
|
* @example
|
|
65
67
|
* ```ts
|
|
@@ -69,39 +71,48 @@ declare function sendToPostHog(event: WideEvent, config: PostHogConfig): Promise
|
|
|
69
71
|
* ```
|
|
70
72
|
*/
|
|
71
73
|
declare function sendBatchToPostHog(events: WideEvent[], config: PostHogConfig): Promise<void>;
|
|
72
|
-
interface PostHogLogsConfig {
|
|
73
|
-
/** PostHog project API key */
|
|
74
|
-
apiKey: string;
|
|
75
|
-
/** PostHog host URL. Default: https://us.i.posthog.com */
|
|
76
|
-
host?: string;
|
|
77
|
-
/** Request timeout in milliseconds. Default: 5000 */
|
|
78
|
-
timeout?: number;
|
|
79
|
-
}
|
|
80
74
|
/**
|
|
81
|
-
* Create a drain function for sending logs to PostHog
|
|
75
|
+
* Create a drain function for sending logs to PostHog as custom events.
|
|
82
76
|
*
|
|
83
|
-
* PostHog
|
|
84
|
-
*
|
|
77
|
+
* Uses PostHog's `/batch/` API. Consider using `createPostHogDrain()` instead
|
|
78
|
+
* which uses PostHog Logs (OTLP) and is significantly cheaper.
|
|
85
79
|
*
|
|
86
80
|
* Configuration priority (highest to lowest):
|
|
87
|
-
* 1. Overrides passed to
|
|
81
|
+
* 1. Overrides passed to createPostHogEventsDrain()
|
|
88
82
|
* 2. runtimeConfig.evlog.posthog
|
|
89
83
|
* 3. runtimeConfig.posthog
|
|
90
|
-
* 4. Environment variables:
|
|
84
|
+
* 4. Environment variables: NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY, NUXT_POSTHOG_HOST/POSTHOG_HOST
|
|
91
85
|
*
|
|
92
86
|
* @example
|
|
93
87
|
* ```ts
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* // With overrides
|
|
98
|
-
* nitroApp.hooks.hook('evlog:drain', createPostHogLogsDrain({
|
|
99
|
-
* apiKey: 'phc_...',
|
|
100
|
-
* host: 'https://eu.i.posthog.com',
|
|
88
|
+
* nitroApp.hooks.hook('evlog:drain', createPostHogEventsDrain({
|
|
89
|
+
* eventName: 'server_request',
|
|
101
90
|
* }))
|
|
102
91
|
* ```
|
|
103
92
|
*/
|
|
104
|
-
declare function
|
|
93
|
+
declare function createPostHogEventsDrain(overrides?: Partial<PostHogEventsConfig>): (ctx: DrainContext | DrainContext[]) => Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Send a single event to PostHog as a custom event.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* await sendToPostHogEvents(event, {
|
|
100
|
+
* apiKey: process.env.POSTHOG_API_KEY!,
|
|
101
|
+
* })
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
declare function sendToPostHogEvents(event: WideEvent, config: PostHogEventsConfig): Promise<void>;
|
|
105
|
+
/**
|
|
106
|
+
* Send a batch of events to PostHog as custom events via the `/batch/` API.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* await sendBatchToPostHogEvents(events, {
|
|
111
|
+
* apiKey: process.env.POSTHOG_API_KEY!,
|
|
112
|
+
* })
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
declare function sendBatchToPostHogEvents(events: WideEvent[], config: PostHogEventsConfig): Promise<void>;
|
|
105
116
|
//#endregion
|
|
106
|
-
export { PostHogConfig, PostHogEvent,
|
|
117
|
+
export { PostHogConfig, PostHogEvent, PostHogEventsConfig, createPostHogDrain, createPostHogEventsDrain, sendBatchToPostHog, sendBatchToPostHogEvents, sendToPostHog, sendToPostHogEvents, toPostHogEvent };
|
|
107
118
|
//# sourceMappingURL=posthog.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"posthog.d.mts","names":[],"sources":["../../src/adapters/posthog.ts"],"mappings":";;;;UAQiB,aAAA;;EAEf,MAAA;EAFe;EAIf,IAAA;;EAEA,
|
|
1
|
+
{"version":3,"file":"posthog.d.mts","names":[],"sources":["../../src/adapters/posthog.ts"],"mappings":";;;;UAQiB,aAAA;;EAEf,MAAA;EAFe;EAIf,IAAA;;EAEA,OAAA;AAAA;AAAA,UAGe,mBAAA,SAA4B,aAAA;EAH3C;EAKA,SAAA;EALO;EAOP,UAAA;AAAA;;UAIe,YAAA;EACf,KAAA;EACA,WAAA;EACA,SAAA;EACA,UAAA,EAAY,MAAA;AAAA;AAJd;;;AAAA,iBAmCgB,cAAA,CAAe,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,mBAAA,GAAsB,YAAA;;;;;;;;AAA/E;;;;;;;;;;;;;;iBAwCgB,kBAAA,CAAmB,SAAA,GAAY,OAAA,CAAQ,aAAA,KAAc,GAAA,EAAf,YAAA,GAAe,YAAA,OAAA,OAAA;AAArE;;;;;;;;;;AAAA,iBAyBsB,aAAA,CAAc,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,aAAA,GAAgB,OAAA;;;;;;;;;AAA9E;;iBAcsB,kBAAA,CAAmB,MAAA,EAAQ,SAAA,IAAa,MAAA,EAAQ,aAAA,GAAgB,OAAA;;;;;;;;;;;;;AAAtF;;;;;;;iBA4BgB,wBAAA,CAAyB,SAAA,GAAY,OAAA,CAAQ,mBAAA,KAAoB,GAAA,EAArB,YAAA,GAAqB,YAAA,OAAA,OAAA;;;;;;;;AAAjF;;;iBAyBsB,mBAAA,CAAoB,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,mBAAA,GAAsB,OAAA;;;;;;;;;;;iBAcpE,wBAAA,CAAyB,MAAA,EAAQ,SAAA,IAAa,MAAA,EAAQ,mBAAA,GAAsB,OAAA"}
|
|
@@ -11,12 +11,25 @@ const POSTHOG_FIELDS = [
|
|
|
11
11
|
key: "host",
|
|
12
12
|
env: ["NUXT_POSTHOG_HOST", "POSTHOG_HOST"]
|
|
13
13
|
},
|
|
14
|
-
{ key: "eventName" },
|
|
15
|
-
{ key: "distinctId" },
|
|
16
14
|
{ key: "timeout" }
|
|
17
15
|
];
|
|
16
|
+
const POSTHOG_EVENTS_FIELDS = [
|
|
17
|
+
...POSTHOG_FIELDS,
|
|
18
|
+
{ key: "eventName" },
|
|
19
|
+
{ key: "distinctId" }
|
|
20
|
+
];
|
|
21
|
+
function resolveHost(config) {
|
|
22
|
+
return (config.host ?? "https://us.i.posthog.com").replace(/\/$/, "");
|
|
23
|
+
}
|
|
24
|
+
function toOTLPConfig(config) {
|
|
25
|
+
return {
|
|
26
|
+
endpoint: `${resolveHost(config)}/i`,
|
|
27
|
+
headers: { Authorization: `Bearer ${config.apiKey}` },
|
|
28
|
+
timeout: config.timeout
|
|
29
|
+
};
|
|
30
|
+
}
|
|
18
31
|
/**
|
|
19
|
-
* Convert a WideEvent to a PostHog event format.
|
|
32
|
+
* Convert a WideEvent to a PostHog custom event format.
|
|
20
33
|
*/
|
|
21
34
|
function toPostHogEvent(event, config) {
|
|
22
35
|
const { timestamp, level, service, ...rest } = event;
|
|
@@ -32,7 +45,7 @@ function toPostHogEvent(event, config) {
|
|
|
32
45
|
};
|
|
33
46
|
}
|
|
34
47
|
/**
|
|
35
|
-
* Create a drain function for sending logs to PostHog.
|
|
48
|
+
* Create a drain function for sending logs to PostHog Logs via OTLP.
|
|
36
49
|
*
|
|
37
50
|
* Configuration priority (highest to lowest):
|
|
38
51
|
* 1. Overrides passed to createPostHogDrain()
|
|
@@ -67,7 +80,7 @@ function createPostHogDrain(overrides) {
|
|
|
67
80
|
});
|
|
68
81
|
}
|
|
69
82
|
/**
|
|
70
|
-
* Send a single event to PostHog.
|
|
83
|
+
* Send a single event to PostHog Logs via OTLP.
|
|
71
84
|
*
|
|
72
85
|
* @example
|
|
73
86
|
* ```ts
|
|
@@ -80,7 +93,7 @@ async function sendToPostHog(event, config) {
|
|
|
80
93
|
await sendBatchToPostHog([event], config);
|
|
81
94
|
}
|
|
82
95
|
/**
|
|
83
|
-
* Send a batch of events to PostHog.
|
|
96
|
+
* Send a batch of events to PostHog Logs via OTLP.
|
|
84
97
|
*
|
|
85
98
|
* @example
|
|
86
99
|
* ```ts
|
|
@@ -91,76 +104,80 @@ async function sendToPostHog(event, config) {
|
|
|
91
104
|
*/
|
|
92
105
|
async function sendBatchToPostHog(events, config) {
|
|
93
106
|
if (events.length === 0) return;
|
|
94
|
-
|
|
95
|
-
const batch = events.map((event) => toPostHogEvent(event, config));
|
|
96
|
-
const payload = {
|
|
97
|
-
api_key: config.apiKey,
|
|
98
|
-
batch
|
|
99
|
-
};
|
|
100
|
-
await httpPost({
|
|
101
|
-
url,
|
|
102
|
-
headers: { "Content-Type": "application/json" },
|
|
103
|
-
body: JSON.stringify(payload),
|
|
104
|
-
timeout: config.timeout ?? 5e3,
|
|
105
|
-
label: "PostHog"
|
|
106
|
-
});
|
|
107
|
+
await sendBatchToOTLP(events, toOTLPConfig(config));
|
|
107
108
|
}
|
|
108
|
-
const POSTHOG_LOGS_FIELDS = [
|
|
109
|
-
{
|
|
110
|
-
key: "apiKey",
|
|
111
|
-
env: ["NUXT_POSTHOG_API_KEY", "POSTHOG_API_KEY"]
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
key: "host",
|
|
115
|
-
env: ["NUXT_POSTHOG_HOST", "POSTHOG_HOST"]
|
|
116
|
-
},
|
|
117
|
-
{ key: "timeout" }
|
|
118
|
-
];
|
|
119
109
|
/**
|
|
120
|
-
* Create a drain function for sending logs to PostHog
|
|
110
|
+
* Create a drain function for sending logs to PostHog as custom events.
|
|
121
111
|
*
|
|
122
|
-
* PostHog
|
|
123
|
-
*
|
|
112
|
+
* Uses PostHog's `/batch/` API. Consider using `createPostHogDrain()` instead
|
|
113
|
+
* which uses PostHog Logs (OTLP) and is significantly cheaper.
|
|
124
114
|
*
|
|
125
115
|
* Configuration priority (highest to lowest):
|
|
126
|
-
* 1. Overrides passed to
|
|
116
|
+
* 1. Overrides passed to createPostHogEventsDrain()
|
|
127
117
|
* 2. runtimeConfig.evlog.posthog
|
|
128
118
|
* 3. runtimeConfig.posthog
|
|
129
|
-
* 4. Environment variables:
|
|
119
|
+
* 4. Environment variables: NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY, NUXT_POSTHOG_HOST/POSTHOG_HOST
|
|
130
120
|
*
|
|
131
121
|
* @example
|
|
132
122
|
* ```ts
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* // With overrides
|
|
137
|
-
* nitroApp.hooks.hook('evlog:drain', createPostHogLogsDrain({
|
|
138
|
-
* apiKey: 'phc_...',
|
|
139
|
-
* host: 'https://eu.i.posthog.com',
|
|
123
|
+
* nitroApp.hooks.hook('evlog:drain', createPostHogEventsDrain({
|
|
124
|
+
* eventName: 'server_request',
|
|
140
125
|
* }))
|
|
141
126
|
* ```
|
|
142
127
|
*/
|
|
143
|
-
function
|
|
128
|
+
function createPostHogEventsDrain(overrides) {
|
|
144
129
|
return defineDrain({
|
|
145
|
-
name: "posthog",
|
|
130
|
+
name: "posthog-events",
|
|
146
131
|
resolve: () => {
|
|
147
|
-
const config = resolveAdapterConfig("posthog",
|
|
132
|
+
const config = resolveAdapterConfig("posthog", POSTHOG_EVENTS_FIELDS, overrides);
|
|
148
133
|
if (!config.apiKey) {
|
|
149
|
-
console.error("[evlog/posthog] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to
|
|
134
|
+
console.error("[evlog/posthog-events] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to createPostHogEventsDrain()");
|
|
150
135
|
return null;
|
|
151
136
|
}
|
|
152
137
|
return config;
|
|
153
138
|
},
|
|
154
|
-
send:
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
139
|
+
send: sendBatchToPostHogEvents
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Send a single event to PostHog as a custom event.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* await sendToPostHogEvents(event, {
|
|
148
|
+
* apiKey: process.env.POSTHOG_API_KEY!,
|
|
149
|
+
* })
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
async function sendToPostHogEvents(event, config) {
|
|
153
|
+
await sendBatchToPostHogEvents([event], config);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Send a batch of events to PostHog as custom events via the `/batch/` API.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* await sendBatchToPostHogEvents(events, {
|
|
161
|
+
* apiKey: process.env.POSTHOG_API_KEY!,
|
|
162
|
+
* })
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
async function sendBatchToPostHogEvents(events, config) {
|
|
166
|
+
if (events.length === 0) return;
|
|
167
|
+
const url = `${resolveHost(config)}/batch/`;
|
|
168
|
+
const batch = events.map((event) => toPostHogEvent(event, config));
|
|
169
|
+
await httpPost({
|
|
170
|
+
url,
|
|
171
|
+
headers: { "Content-Type": "application/json" },
|
|
172
|
+
body: JSON.stringify({
|
|
173
|
+
api_key: config.apiKey,
|
|
174
|
+
batch
|
|
175
|
+
}),
|
|
176
|
+
timeout: config.timeout ?? 5e3,
|
|
177
|
+
label: "PostHog"
|
|
161
178
|
});
|
|
162
179
|
}
|
|
163
180
|
|
|
164
181
|
//#endregion
|
|
165
|
-
export { createPostHogDrain,
|
|
182
|
+
export { createPostHogDrain, createPostHogEventsDrain, sendBatchToPostHog, sendBatchToPostHogEvents, sendToPostHog, sendToPostHogEvents, toPostHogEvent };
|
|
166
183
|
//# sourceMappingURL=posthog.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"posthog.mjs","names":[],"sources":["../../src/adapters/posthog.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\nimport { sendBatchToOTLP } from './otlp'\nimport type { OTLPConfig } from './otlp'\n\nexport interface PostHogConfig {\n /** PostHog project API key */\n apiKey: string\n /** PostHog host URL. Default: https://us.i.posthog.com */\n host?: string\n /** PostHog event name. Default: evlog_wide_event */\n eventName?: string\n /** Override distinct_id (defaults to event.service) */\n distinctId?: string\n
|
|
1
|
+
{"version":3,"file":"posthog.mjs","names":[],"sources":["../../src/adapters/posthog.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from './_config'\nimport { resolveAdapterConfig } from './_config'\nimport { defineDrain } from './_drain'\nimport { httpPost } from './_http'\nimport { sendBatchToOTLP } from './otlp'\nimport type { OTLPConfig } from './otlp'\n\nexport interface PostHogConfig {\n /** PostHog project API key */\n apiKey: string\n /** PostHog host URL. Default: https://us.i.posthog.com */\n host?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n}\n\nexport interface PostHogEventsConfig extends PostHogConfig {\n /** PostHog event name. Default: evlog_wide_event */\n eventName?: string\n /** Override distinct_id (defaults to event.service) */\n distinctId?: string\n}\n\n/** PostHog event structure for the batch API */\nexport interface PostHogEvent {\n event: string\n distinct_id: string\n timestamp: string\n properties: Record<string, unknown>\n}\n\nconst POSTHOG_FIELDS: ConfigField<PostHogConfig>[] = [\n { key: 'apiKey', env: ['NUXT_POSTHOG_API_KEY', 'POSTHOG_API_KEY'] },\n { key: 'host', env: ['NUXT_POSTHOG_HOST', 'POSTHOG_HOST'] },\n { key: 'timeout' },\n]\n\nconst POSTHOG_EVENTS_FIELDS: ConfigField<PostHogEventsConfig>[] = [\n ...POSTHOG_FIELDS,\n { key: 'eventName' },\n { key: 'distinctId' },\n]\n\nfunction resolveHost(config: PostHogConfig): string {\n return (config.host ?? 'https://us.i.posthog.com').replace(/\\/$/, '')\n}\n\nfunction toOTLPConfig(config: PostHogConfig): OTLPConfig {\n const host = resolveHost(config)\n return {\n endpoint: `${host}/i`,\n headers: { Authorization: `Bearer ${config.apiKey}` },\n timeout: config.timeout,\n }\n}\n\n/**\n * Convert a WideEvent to a PostHog custom event format.\n */\nexport function toPostHogEvent(event: WideEvent, config: PostHogEventsConfig): PostHogEvent {\n const { timestamp, level, service, ...rest } = event\n\n return {\n event: config.eventName ?? 'evlog_wide_event',\n distinct_id: config.distinctId ?? (typeof event.userId === 'string' ? event.userId : undefined) ?? service,\n timestamp,\n properties: {\n level,\n service,\n ...rest,\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// PostHog Logs (OTLP) — default\n// ---------------------------------------------------------------------------\n\n/**\n * Create a drain function for sending logs to PostHog Logs via OTLP.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createPostHogDrain()\n * 2. runtimeConfig.evlog.posthog\n * 3. runtimeConfig.posthog\n * 4. Environment variables: NUXT_POSTHOG_*, POSTHOG_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_POSTHOG_API_KEY env var\n * nitroApp.hooks.hook('evlog:drain', createPostHogDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createPostHogDrain({\n * apiKey: 'phc_...',\n * host: 'https://eu.i.posthog.com',\n * }))\n * ```\n */\nexport function createPostHogDrain(overrides?: Partial<PostHogConfig>) {\n return defineDrain<PostHogConfig>({\n name: 'posthog',\n resolve: () => {\n const config = resolveAdapterConfig<PostHogConfig>('posthog', POSTHOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to createPostHogDrain()')\n return null\n }\n return config as PostHogConfig\n },\n send: sendBatchToPostHog,\n })\n}\n\n/**\n * Send a single event to PostHog Logs via OTLP.\n *\n * @example\n * ```ts\n * await sendToPostHog(event, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendToPostHog(event: WideEvent, config: PostHogConfig): Promise<void> {\n await sendBatchToPostHog([event], config)\n}\n\n/**\n * Send a batch of events to PostHog Logs via OTLP.\n *\n * @example\n * ```ts\n * await sendBatchToPostHog(events, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendBatchToPostHog(events: WideEvent[], config: PostHogConfig): Promise<void> {\n if (events.length === 0) return\n await sendBatchToOTLP(events, toOTLPConfig(config))\n}\n\n// ---------------------------------------------------------------------------\n// PostHog Events (custom events via /batch/)\n// ---------------------------------------------------------------------------\n\n/**\n * Create a drain function for sending logs to PostHog as custom events.\n *\n * Uses PostHog's `/batch/` API. Consider using `createPostHogDrain()` instead\n * which uses PostHog Logs (OTLP) and is significantly cheaper.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createPostHogEventsDrain()\n * 2. runtimeConfig.evlog.posthog\n * 3. runtimeConfig.posthog\n * 4. Environment variables: NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY, NUXT_POSTHOG_HOST/POSTHOG_HOST\n *\n * @example\n * ```ts\n * nitroApp.hooks.hook('evlog:drain', createPostHogEventsDrain({\n * eventName: 'server_request',\n * }))\n * ```\n */\nexport function createPostHogEventsDrain(overrides?: Partial<PostHogEventsConfig>) {\n return defineDrain<PostHogEventsConfig>({\n name: 'posthog-events',\n resolve: () => {\n const config = resolveAdapterConfig<PostHogEventsConfig>('posthog', POSTHOG_EVENTS_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog-events] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to createPostHogEventsDrain()')\n return null\n }\n return config as PostHogEventsConfig\n },\n send: sendBatchToPostHogEvents,\n })\n}\n\n/**\n * Send a single event to PostHog as a custom event.\n *\n * @example\n * ```ts\n * await sendToPostHogEvents(event, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendToPostHogEvents(event: WideEvent, config: PostHogEventsConfig): Promise<void> {\n await sendBatchToPostHogEvents([event], config)\n}\n\n/**\n * Send a batch of events to PostHog as custom events via the `/batch/` API.\n *\n * @example\n * ```ts\n * await sendBatchToPostHogEvents(events, {\n * apiKey: process.env.POSTHOG_API_KEY!,\n * })\n * ```\n */\nexport async function sendBatchToPostHogEvents(events: WideEvent[], config: PostHogEventsConfig): Promise<void> {\n if (events.length === 0) return\n\n const url = `${resolveHost(config)}/batch/`\n const batch = events.map(event => toPostHogEvent(event, config))\n\n await httpPost({\n url,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ api_key: config.apiKey, batch }),\n timeout: config.timeout ?? 5000,\n label: 'PostHog',\n })\n}\n"],"mappings":";;;;AAgCA,MAAM,iBAA+C;CACnD;EAAE,KAAK;EAAU,KAAK,CAAC,wBAAwB,kBAAkB;EAAE;CACnE;EAAE,KAAK;EAAQ,KAAK,CAAC,qBAAqB,eAAe;EAAE;CAC3D,EAAE,KAAK,WAAW;CACnB;AAED,MAAM,wBAA4D;CAChE,GAAG;CACH,EAAE,KAAK,aAAa;CACpB,EAAE,KAAK,cAAc;CACtB;AAED,SAAS,YAAY,QAA+B;AAClD,SAAQ,OAAO,QAAQ,4BAA4B,QAAQ,OAAO,GAAG;;AAGvE,SAAS,aAAa,QAAmC;AAEvD,QAAO;EACL,UAAU,GAFC,YAAY,OAAO,CAEZ;EAClB,SAAS,EAAE,eAAe,UAAU,OAAO,UAAU;EACrD,SAAS,OAAO;EACjB;;;;;AAMH,SAAgB,eAAe,OAAkB,QAA2C;CAC1F,MAAM,EAAE,WAAW,OAAO,SAAS,GAAG,SAAS;AAE/C,QAAO;EACL,OAAO,OAAO,aAAa;EAC3B,aAAa,OAAO,eAAe,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS,WAAc;EACnG;EACA,YAAY;GACV;GACA;GACA,GAAG;GACJ;EACF;;;;;;;;;;;;;;;;;;;;;;;AA4BH,SAAgB,mBAAmB,WAAoC;AACrE,QAAO,YAA2B;EAChC,MAAM;EACN,eAAe;GACb,MAAM,SAAS,qBAAoC,WAAW,gBAAgB,UAAU;AACxF,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,mHAAmH;AACjI,WAAO;;AAET,UAAO;;EAET,MAAM;EACP,CAAC;;;;;;;;;;;;AAaJ,eAAsB,cAAc,OAAkB,QAAsC;AAC1F,OAAM,mBAAmB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;AAa3C,eAAsB,mBAAmB,QAAqB,QAAsC;AAClG,KAAI,OAAO,WAAW,EAAG;AACzB,OAAM,gBAAgB,QAAQ,aAAa,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;AA0BrD,SAAgB,yBAAyB,WAA0C;AACjF,QAAO,YAAiC;EACtC,MAAM;EACN,eAAe;GACb,MAAM,SAAS,qBAA0C,WAAW,uBAAuB,UAAU;AACrG,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,gIAAgI;AAC9I,WAAO;;AAET,UAAO;;EAET,MAAM;EACP,CAAC;;;;;;;;;;;;AAaJ,eAAsB,oBAAoB,OAAkB,QAA4C;AACtG,OAAM,yBAAyB,CAAC,MAAM,EAAE,OAAO;;;;;;;;;;;;AAajD,eAAsB,yBAAyB,QAAqB,QAA4C;AAC9G,KAAI,OAAO,WAAW,EAAG;CAEzB,MAAM,MAAM,GAAG,YAAY,OAAO,CAAC;CACnC,MAAM,QAAQ,OAAO,KAAI,UAAS,eAAe,OAAO,OAAO,CAAC;AAEhE,OAAM,SAAS;EACb;EACA,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU;GAAE,SAAS,OAAO;GAAQ;GAAO,CAAC;EACvD,SAAS,OAAO,WAAW;EAC3B,OAAO;EACR,CAAC"}
|
package/dist/nitro/plugin.mjs
CHANGED
|
@@ -62,6 +62,7 @@ async function callEnrichAndDrain(nitroApp, emittedEvent, event) {
|
|
|
62
62
|
});
|
|
63
63
|
const waitUntilCtx = event.context.cloudflare?.context ?? event.context;
|
|
64
64
|
if (typeof waitUntilCtx?.waitUntil === "function") waitUntilCtx.waitUntil(drainPromise);
|
|
65
|
+
else await drainPromise;
|
|
65
66
|
}
|
|
66
67
|
var plugin_default = defineNitroPlugin(async (nitroApp) => {
|
|
67
68
|
let evlogConfig;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.mjs","names":[],"sources":["../../src/nitro/plugin.ts"],"sourcesContent":["import type { NitroApp } from 'nitropack/types'\n// Import from specific subpaths to avoid the barrel 'nitropack/runtime' which\n// re-exports from internal/app.mjs — that file imports #nitro-internal-virtual/*\n// modules that only exist inside rollup builds and crash when loaded externally\n// (nitropack dev loads plugins outside the bundle via Worker threads).\nimport { defineNitroPlugin } from 'nitropack/runtime/internal/plugin'\nimport { getHeaders } from 'h3'\nimport { createRequestLogger, initLogger, isEnabled } from '../logger'\nimport { shouldLog, getServiceForPath, extractErrorStatus } from '../nitro'\nimport type { EvlogConfig } from '../nitro'\nimport type { EnrichContext, RequestLogger, ServerEvent, TailSamplingContext, WideEvent } from '../types'\nimport { filterSafeHeaders } from '../utils'\n\nfunction getSafeHeaders(event: ServerEvent): Record<string, string> {\n const allHeaders = getHeaders(event as Parameters<typeof getHeaders>[0])\n return filterSafeHeaders(allHeaders)\n}\n\nfunction getSafeResponseHeaders(event: ServerEvent): Record<string, string> | undefined {\n const headers: Record<string, string> = {}\n const nodeRes = event.node?.res as { getHeaders?: () => Record<string, unknown> } | undefined\n\n if (nodeRes?.getHeaders) {\n for (const [key, value] of Object.entries(nodeRes.getHeaders())) {\n if (value === undefined) continue\n headers[key] = Array.isArray(value) ? value.join(', ') : String(value)\n }\n }\n\n if (event.response?.headers) {\n event.response.headers.forEach((value, key) => {\n headers[key] = value\n })\n }\n\n if (Object.keys(headers).length === 0) return undefined\n return filterSafeHeaders(headers)\n}\n\nfunction getResponseStatus(event: ServerEvent): number {\n // Node.js style\n if (event.node?.res?.statusCode) {\n return event.node.res.statusCode\n }\n\n // Web Standard\n if (event.response?.status) {\n return event.response.status\n }\n\n // Context-based\n if (typeof event.context.status === 'number') {\n return event.context.status\n }\n\n return 200\n}\n\nfunction buildHookContext(event: ServerEvent): Omit<EnrichContext, 'event'> {\n const responseHeaders = getSafeResponseHeaders(event)\n return {\n request: { method: event.method, path: event.path },\n headers: getSafeHeaders(event),\n response: {\n status: getResponseStatus(event),\n headers: responseHeaders,\n },\n }\n}\n\nasync function callEnrichAndDrain(\n nitroApp: NitroApp,\n emittedEvent: WideEvent | null,\n event: ServerEvent,\n): Promise<void> {\n if (!emittedEvent) return\n\n const hookContext = buildHookContext(event)\n\n try {\n await nitroApp.hooks.callHook('evlog:enrich', { event: emittedEvent, ...hookContext })\n } catch (err) {\n console.error('[evlog] enrich failed:', err)\n }\n\n const drainPromise = nitroApp.hooks.callHook('evlog:drain', {\n event: emittedEvent,\n request: hookContext.request,\n headers: hookContext.headers,\n }).catch((err) => {\n console.error('[evlog] drain failed:', err)\n })\n\n // Use waitUntil if available (Cloudflare Workers, Vercel Edge)\n // This ensures drains complete before the runtime terminates\n const waitUntilCtx = event.context.cloudflare?.context ?? event.context\n if (typeof waitUntilCtx?.waitUntil === 'function') {\n waitUntilCtx.waitUntil(drainPromise)\n }\n}\n\nexport default defineNitroPlugin(async (nitroApp) => {\n // Config resolution: process.env bridge first (always set by the module),\n // then lazy useRuntimeConfig() for production builds where env may not persist.\n let evlogConfig: EvlogConfig | undefined\n if (process.env.__EVLOG_CONFIG) {\n evlogConfig = JSON.parse(process.env.__EVLOG_CONFIG)\n } else {\n try {\n // nitropack/runtime/internal/config imports virtual modules —\n // only works inside rollup-bundled output (production builds).\n const { useRuntimeConfig } = await import('nitropack/runtime/internal/config')\n evlogConfig = (useRuntimeConfig() as Record<string, any>).evlog\n } catch {\n // Expected in dev mode — virtual modules unavailable outside rollup\n }\n }\n\n initLogger({\n enabled: evlogConfig?.enabled,\n env: evlogConfig?.env,\n pretty: evlogConfig?.pretty,\n sampling: evlogConfig?.sampling,\n })\n\n if (!isEnabled()) return\n\n nitroApp.hooks.hook('request', (event) => {\n const e = event as ServerEvent\n\n // Skip logging for routes not matching include/exclude patterns\n if (!shouldLog(e.path, evlogConfig?.include, evlogConfig?.exclude)) {\n return\n }\n\n // Store start time for duration calculation in tail sampling\n e.context._evlogStartTime = Date.now()\n\n let requestIdOverride: string | undefined = undefined\n if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') {\n const cfRay = getSafeHeaders(e)?.['cf-ray']\n if (cfRay) requestIdOverride = cfRay\n }\n\n const requestLog = createRequestLogger({\n method: e.method,\n path: e.path,\n requestId: requestIdOverride || e.context.requestId || crypto.randomUUID(),\n })\n\n // Apply route-based service configuration if a matching route is found\n const routeService = getServiceForPath(e.path, evlogConfig?.routes)\n if (routeService) {\n requestLog.set({ service: routeService })\n }\n\n e.context.log = requestLog\n })\n\n nitroApp.hooks.hook('error', async (error, { event }) => {\n const e = event as ServerEvent | undefined\n if (!e) return\n\n const requestLog = e.context.log as RequestLogger | undefined\n if (requestLog) {\n requestLog.error(error as Error)\n\n const errorStatus = extractErrorStatus(error)\n requestLog.set({ status: errorStatus })\n\n // Build tail sampling context\n const startTime = e.context._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const tailCtx: TailSamplingContext = {\n status: errorStatus,\n duration: durationMs,\n path: e.path,\n method: e.method,\n context: requestLog.getContext(),\n shouldKeep: false,\n }\n\n // Call evlog:emit:keep hook\n await nitroApp.hooks.callHook('evlog:emit:keep', tailCtx)\n\n e.context._evlogEmitted = true\n\n const emittedEvent = requestLog.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(nitroApp, emittedEvent, e)\n }\n })\n\n nitroApp.hooks.hook('afterResponse', async (event) => {\n const e = event as ServerEvent\n // Skip if already emitted by error hook\n if (e.context._evlogEmitted) return\n\n const requestLog = e.context.log as RequestLogger | undefined\n if (requestLog) {\n const status = getResponseStatus(e)\n requestLog.set({ status })\n\n const startTime = e.context._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const tailCtx: TailSamplingContext = {\n status,\n duration: durationMs,\n path: e.path,\n method: e.method,\n context: requestLog.getContext(),\n shouldKeep: false,\n }\n\n await nitroApp.hooks.callHook('evlog:emit:keep', tailCtx)\n\n const emittedEvent = requestLog.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(nitroApp, emittedEvent, e)\n }\n })\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.mjs","names":[],"sources":["../../src/nitro/plugin.ts"],"sourcesContent":["import type { NitroApp } from 'nitropack/types'\n// Import from specific subpaths to avoid the barrel 'nitropack/runtime' which\n// re-exports from internal/app.mjs — that file imports #nitro-internal-virtual/*\n// modules that only exist inside rollup builds and crash when loaded externally\n// (nitropack dev loads plugins outside the bundle via Worker threads).\nimport { defineNitroPlugin } from 'nitropack/runtime/internal/plugin'\nimport { getHeaders } from 'h3'\nimport { createRequestLogger, initLogger, isEnabled } from '../logger'\nimport { shouldLog, getServiceForPath, extractErrorStatus } from '../nitro'\nimport type { EvlogConfig } from '../nitro'\nimport type { EnrichContext, RequestLogger, ServerEvent, TailSamplingContext, WideEvent } from '../types'\nimport { filterSafeHeaders } from '../utils'\n\nfunction getSafeHeaders(event: ServerEvent): Record<string, string> {\n const allHeaders = getHeaders(event as Parameters<typeof getHeaders>[0])\n return filterSafeHeaders(allHeaders)\n}\n\nfunction getSafeResponseHeaders(event: ServerEvent): Record<string, string> | undefined {\n const headers: Record<string, string> = {}\n const nodeRes = event.node?.res as { getHeaders?: () => Record<string, unknown> } | undefined\n\n if (nodeRes?.getHeaders) {\n for (const [key, value] of Object.entries(nodeRes.getHeaders())) {\n if (value === undefined) continue\n headers[key] = Array.isArray(value) ? value.join(', ') : String(value)\n }\n }\n\n if (event.response?.headers) {\n event.response.headers.forEach((value, key) => {\n headers[key] = value\n })\n }\n\n if (Object.keys(headers).length === 0) return undefined\n return filterSafeHeaders(headers)\n}\n\nfunction getResponseStatus(event: ServerEvent): number {\n // Node.js style\n if (event.node?.res?.statusCode) {\n return event.node.res.statusCode\n }\n\n // Web Standard\n if (event.response?.status) {\n return event.response.status\n }\n\n // Context-based\n if (typeof event.context.status === 'number') {\n return event.context.status\n }\n\n return 200\n}\n\nfunction buildHookContext(event: ServerEvent): Omit<EnrichContext, 'event'> {\n const responseHeaders = getSafeResponseHeaders(event)\n return {\n request: { method: event.method, path: event.path },\n headers: getSafeHeaders(event),\n response: {\n status: getResponseStatus(event),\n headers: responseHeaders,\n },\n }\n}\n\nasync function callEnrichAndDrain(\n nitroApp: NitroApp,\n emittedEvent: WideEvent | null,\n event: ServerEvent,\n): Promise<void> {\n if (!emittedEvent) return\n\n const hookContext = buildHookContext(event)\n\n try {\n await nitroApp.hooks.callHook('evlog:enrich', { event: emittedEvent, ...hookContext })\n } catch (err) {\n console.error('[evlog] enrich failed:', err)\n }\n\n const drainPromise = nitroApp.hooks.callHook('evlog:drain', {\n event: emittedEvent,\n request: hookContext.request,\n headers: hookContext.headers,\n }).catch((err) => {\n console.error('[evlog] drain failed:', err)\n })\n\n // Use waitUntil if available (Cloudflare Workers, Vercel Edge, etc.)\n // This keeps the runtime alive for background work without blocking the response\n const waitUntilCtx = event.context.cloudflare?.context ?? event.context\n if (typeof waitUntilCtx?.waitUntil === 'function') {\n waitUntilCtx.waitUntil(drainPromise)\n } else {\n // Fallback: await drain to prevent lost logs in serverless environments\n // (e.g. Vercel Fluid Compute). On the normal path this runs from\n // afterResponse (response already sent); on the error path it may run\n // before the error response is finalized.\n await drainPromise\n }\n}\n\nexport default defineNitroPlugin(async (nitroApp) => {\n // Config resolution: process.env bridge first (always set by the module),\n // then lazy useRuntimeConfig() for production builds where env may not persist.\n let evlogConfig: EvlogConfig | undefined\n if (process.env.__EVLOG_CONFIG) {\n evlogConfig = JSON.parse(process.env.__EVLOG_CONFIG)\n } else {\n try {\n // nitropack/runtime/internal/config imports virtual modules —\n // only works inside rollup-bundled output (production builds).\n const { useRuntimeConfig } = await import('nitropack/runtime/internal/config')\n evlogConfig = (useRuntimeConfig() as Record<string, any>).evlog\n } catch {\n // Expected in dev mode — virtual modules unavailable outside rollup\n }\n }\n\n initLogger({\n enabled: evlogConfig?.enabled,\n env: evlogConfig?.env,\n pretty: evlogConfig?.pretty,\n sampling: evlogConfig?.sampling,\n })\n\n if (!isEnabled()) return\n\n nitroApp.hooks.hook('request', (event) => {\n const e = event as ServerEvent\n\n // Skip logging for routes not matching include/exclude patterns\n if (!shouldLog(e.path, evlogConfig?.include, evlogConfig?.exclude)) {\n return\n }\n\n // Store start time for duration calculation in tail sampling\n e.context._evlogStartTime = Date.now()\n\n let requestIdOverride: string | undefined = undefined\n if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') {\n const cfRay = getSafeHeaders(e)?.['cf-ray']\n if (cfRay) requestIdOverride = cfRay\n }\n\n const requestLog = createRequestLogger({\n method: e.method,\n path: e.path,\n requestId: requestIdOverride || e.context.requestId || crypto.randomUUID(),\n })\n\n // Apply route-based service configuration if a matching route is found\n const routeService = getServiceForPath(e.path, evlogConfig?.routes)\n if (routeService) {\n requestLog.set({ service: routeService })\n }\n\n e.context.log = requestLog\n })\n\n nitroApp.hooks.hook('error', async (error, { event }) => {\n const e = event as ServerEvent | undefined\n if (!e) return\n\n const requestLog = e.context.log as RequestLogger | undefined\n if (requestLog) {\n requestLog.error(error as Error)\n\n const errorStatus = extractErrorStatus(error)\n requestLog.set({ status: errorStatus })\n\n // Build tail sampling context\n const startTime = e.context._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const tailCtx: TailSamplingContext = {\n status: errorStatus,\n duration: durationMs,\n path: e.path,\n method: e.method,\n context: requestLog.getContext(),\n shouldKeep: false,\n }\n\n // Call evlog:emit:keep hook\n await nitroApp.hooks.callHook('evlog:emit:keep', tailCtx)\n\n e.context._evlogEmitted = true\n\n const emittedEvent = requestLog.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(nitroApp, emittedEvent, e)\n }\n })\n\n nitroApp.hooks.hook('afterResponse', async (event) => {\n const e = event as ServerEvent\n // Skip if already emitted by error hook\n if (e.context._evlogEmitted) return\n\n const requestLog = e.context.log as RequestLogger | undefined\n if (requestLog) {\n const status = getResponseStatus(e)\n requestLog.set({ status })\n\n const startTime = e.context._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const tailCtx: TailSamplingContext = {\n status,\n duration: durationMs,\n path: e.path,\n method: e.method,\n context: requestLog.getContext(),\n shouldKeep: false,\n }\n\n await nitroApp.hooks.callHook('evlog:emit:keep', tailCtx)\n\n const emittedEvent = requestLog.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(nitroApp, emittedEvent, e)\n }\n })\n})\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import "../../nitro-CrFBjY1Y.mjs";
|
|
2
2
|
import evlog from "./module.mjs";
|
|
3
3
|
import { useLogger } from "./useLogger.mjs";
|
|
4
|
-
|
|
4
|
+
import { evlogErrorHandler } from "./middleware.mjs";
|
|
5
|
+
export { evlog as default, evlogErrorHandler, useLogger };
|
package/dist/nitro/v3/index.mjs
CHANGED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//#region src/nitro-v3/middleware.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Server middleware handler that catches EvlogError and returns a structured JSON response.
|
|
4
|
+
*
|
|
5
|
+
* Frameworks like TanStack Start catch thrown errors before Nitro's error handler,
|
|
6
|
+
* stripping the `data` field (containing `why`, `fix`, `link`). This middleware
|
|
7
|
+
* intercepts EvlogErrors first, attaches them to the wide event, and throws a
|
|
8
|
+
* `Response` that the framework passes through directly.
|
|
9
|
+
*
|
|
10
|
+
* @example TanStack Start
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { createMiddleware } from '@tanstack/react-start'
|
|
13
|
+
* import { evlogErrorHandler } from 'evlog/nitro/v3'
|
|
14
|
+
*
|
|
15
|
+
* const evlogMiddleware = createMiddleware().server(evlogErrorHandler)
|
|
16
|
+
*
|
|
17
|
+
* export const Route = createRootRoute({
|
|
18
|
+
* server: { middleware: [evlogMiddleware] },
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare function evlogErrorHandler<T>(next: (...args: any[]) => Promise<T>): Promise<T>;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { evlogErrorHandler };
|
|
25
|
+
//# sourceMappingURL=middleware.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.mts","names":[],"sources":["../../../src/nitro-v3/middleware.ts"],"mappings":";;AAyBA;;;;;;;;;;;;;;;;;;;iBAAsB,iBAAA,GAAA,CAAqB,IAAA,MAAU,IAAA,YAAgB,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { EvlogError } from "../../error.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/nitro-v3/middleware.ts
|
|
4
|
+
/**
|
|
5
|
+
* Server middleware handler that catches EvlogError and returns a structured JSON response.
|
|
6
|
+
*
|
|
7
|
+
* Frameworks like TanStack Start catch thrown errors before Nitro's error handler,
|
|
8
|
+
* stripping the `data` field (containing `why`, `fix`, `link`). This middleware
|
|
9
|
+
* intercepts EvlogErrors first, attaches them to the wide event, and throws a
|
|
10
|
+
* `Response` that the framework passes through directly.
|
|
11
|
+
*
|
|
12
|
+
* @example TanStack Start
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { createMiddleware } from '@tanstack/react-start'
|
|
15
|
+
* import { evlogErrorHandler } from 'evlog/nitro/v3'
|
|
16
|
+
*
|
|
17
|
+
* const evlogMiddleware = createMiddleware().server(evlogErrorHandler)
|
|
18
|
+
*
|
|
19
|
+
* export const Route = createRootRoute({
|
|
20
|
+
* server: { middleware: [evlogMiddleware] },
|
|
21
|
+
* })
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
async function evlogErrorHandler(next) {
|
|
25
|
+
try {
|
|
26
|
+
return await next();
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (error instanceof EvlogError || error && typeof error === "object" && error.name === "EvlogError") {
|
|
29
|
+
const evlogError = error;
|
|
30
|
+
try {
|
|
31
|
+
const { useRequest } = await import("nitro/context");
|
|
32
|
+
(useRequest().context?.log)?.error(evlogError);
|
|
33
|
+
} catch {}
|
|
34
|
+
throw new Response(JSON.stringify(evlogError.toJSON()), {
|
|
35
|
+
status: evlogError.status || 500,
|
|
36
|
+
headers: { "Content-Type": "application/json" }
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
export { evlogErrorHandler };
|
|
45
|
+
//# sourceMappingURL=middleware.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.mjs","names":[],"sources":["../../../src/nitro-v3/middleware.ts"],"sourcesContent":["import type { RequestLogger } from '../types'\nimport { EvlogError } from '../error'\nimport { serializeEvlogErrorResponse } from '../nitro'\n\n/**\n * Server middleware handler that catches EvlogError and returns a structured JSON response.\n *\n * Frameworks like TanStack Start catch thrown errors before Nitro's error handler,\n * stripping the `data` field (containing `why`, `fix`, `link`). This middleware\n * intercepts EvlogErrors first, attaches them to the wide event, and throws a\n * `Response` that the framework passes through directly.\n *\n * @example TanStack Start\n * ```ts\n * import { createMiddleware } from '@tanstack/react-start'\n * import { evlogErrorHandler } from 'evlog/nitro/v3'\n *\n * const evlogMiddleware = createMiddleware().server(evlogErrorHandler)\n *\n * export const Route = createRootRoute({\n * server: { middleware: [evlogMiddleware] },\n * })\n * ```\n */\n\nexport async function evlogErrorHandler<T>(next: (...args: any[]) => Promise<T>): Promise<T> {\n try {\n return await next()\n } catch (error: unknown) {\n if (error instanceof EvlogError || (error && typeof error === 'object' && (error as Error).name === 'EvlogError')) {\n const evlogError = error as EvlogError\n\n try {\n const { useRequest } = await import('nitro/context')\n const req = useRequest()\n const log = req.context?.log as RequestLogger | undefined\n log?.error(evlogError)\n } catch {\n // ignore\n }\n\n // Throw as Response so frameworks (TanStack Start, SolidStart, etc.) pass it through\n throw new Response(JSON.stringify(evlogError.toJSON()), {\n status: evlogError.status || 500,\n headers: { 'Content-Type': 'application/json' },\n })\n }\n throw error\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAyBA,eAAsB,kBAAqB,MAAkD;AAC3F,KAAI;AACF,SAAO,MAAM,MAAM;UACZ,OAAgB;AACvB,MAAI,iBAAiB,cAAe,SAAS,OAAO,UAAU,YAAa,MAAgB,SAAS,cAAe;GACjH,MAAM,aAAa;AAEnB,OAAI;IACF,MAAM,EAAE,eAAe,MAAM,OAAO;AAGpC,KAFY,YAAY,CACR,SAAS,MACpB,MAAM,WAAW;WAChB;AAKR,SAAM,IAAI,SAAS,KAAK,UAAU,WAAW,QAAQ,CAAC,EAAE;IACtD,QAAQ,WAAW,UAAU;IAC7B,SAAS,EAAE,gBAAgB,oBAAoB;IAChD,CAAC;;AAEJ,QAAM"}
|
package/dist/nitro/v3/plugin.mjs
CHANGED
|
@@ -41,7 +41,7 @@ function buildHookContext(event, res) {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
|
-
function callDrainHook(hooks, emittedEvent, event, hookContext) {
|
|
44
|
+
async function callDrainHook(hooks, emittedEvent, event, hookContext) {
|
|
45
45
|
if (!emittedEvent) return;
|
|
46
46
|
let drainPromise;
|
|
47
47
|
try {
|
|
@@ -49,11 +49,15 @@ function callDrainHook(hooks, emittedEvent, event, hookContext) {
|
|
|
49
49
|
event: emittedEvent,
|
|
50
50
|
request: hookContext.request,
|
|
51
51
|
headers: hookContext.headers
|
|
52
|
+
})?.catch?.((err) => {
|
|
53
|
+
console.error("[evlog] drain failed:", err);
|
|
52
54
|
});
|
|
53
55
|
} catch (err) {
|
|
54
56
|
console.error("[evlog] drain failed:", err);
|
|
55
57
|
}
|
|
56
|
-
if (drainPromise
|
|
58
|
+
if (!drainPromise) return;
|
|
59
|
+
if (typeof event.req.waitUntil === "function") event.req.waitUntil(drainPromise);
|
|
60
|
+
else await drainPromise;
|
|
57
61
|
}
|
|
58
62
|
async function callEnrichAndDrain(hooks, emittedEvent, event, res) {
|
|
59
63
|
if (!emittedEvent) return;
|
|
@@ -66,7 +70,7 @@ async function callEnrichAndDrain(hooks, emittedEvent, event, res) {
|
|
|
66
70
|
} catch (err) {
|
|
67
71
|
console.error("[evlog] enrich failed:", err);
|
|
68
72
|
}
|
|
69
|
-
callDrainHook(hooks, emittedEvent, event, hookContext);
|
|
73
|
+
await callDrainHook(hooks, emittedEvent, event, hookContext);
|
|
70
74
|
}
|
|
71
75
|
/**
|
|
72
76
|
* Nitro v3 plugin entry point.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.mjs","names":[],"sources":["../../../src/nitro-v3/plugin.ts"],"sourcesContent":["import { definePlugin } from 'nitro'\nimport { useRuntimeConfig } from 'nitro/runtime-config'\nimport type { CaptureError } from 'nitro/types'\nimport type { HTTPEvent } from 'nitro/h3'\nimport { parseURL } from 'ufo'\nimport { createRequestLogger, initLogger, isEnabled } from '../logger'\nimport { shouldLog, getServiceForPath, extractErrorStatus } from '../nitro'\nimport type { EvlogConfig } from '../nitro'\nimport type { EnrichContext, RequestLogger, TailSamplingContext, WideEvent } from '../types'\nimport { filterSafeHeaders } from '../utils'\n\n// Nitro v3 doesn't fully export hook types yet\n// https://github.com/nitrojs/nitro/blob/8882bc9e1dbf2d342e73097f22a2156f70f50575/src/types/runtime/nitro.ts#L48-L53\ninterface NitroV3Hooks {\n close: () => void\n error: CaptureError\n request: (event: HTTPEvent) => void | Promise<void>\n response: (res: Response, event: HTTPEvent) => void | Promise<void>\n 'evlog:emit:keep': (ctx: TailSamplingContext) => void | Promise<void>\n 'evlog:enrich': (ctx: EnrichContext) => void | Promise<void>\n 'evlog:drain': (ctx: { event: WideEvent; request?: { method?: string; path: string; requestId?: string }; headers?: Record<string, string> }) => void | Promise<void>\n}\n\ntype Hooks = {\n hook: <T extends keyof NitroV3Hooks>(name: T, listener: NitroV3Hooks[T]) => void\n callHook: <T extends keyof NitroV3Hooks>(name: T, ...args: Parameters<NitroV3Hooks[T]>) => Promise<void>\n}\n\nfunction getContext(event: HTTPEvent): Record<string, unknown> {\n if (!event.req.context) {\n event.req.context = {}\n }\n return event.req.context\n}\n\nfunction getSafeRequestHeaders(event: HTTPEvent): Record<string, string> {\n const headers: Record<string, string> = {}\n event.req.headers.forEach((value, key) => {\n headers[key] = value\n })\n return filterSafeHeaders(headers)\n}\n\nfunction getSafeResponseHeaders(res: Response): Record<string, string> | undefined {\n const headers: Record<string, string> = {}\n res.headers.forEach((value, key) => {\n headers[key] = value\n })\n if (Object.keys(headers).length === 0) return undefined\n return filterSafeHeaders(headers)\n}\n\nfunction buildHookContext(\n event: HTTPEvent,\n res?: Response,\n): Omit<EnrichContext, 'event'> {\n const { pathname } = parseURL(event.req.url)\n const responseHeaders = res ? getSafeResponseHeaders(res) : undefined\n return {\n request: { method: event.req.method, path: pathname },\n headers: getSafeRequestHeaders(event),\n response: {\n status: res?.status ?? 200,\n headers: responseHeaders,\n },\n }\n}\n\nfunction callDrainHook(\n hooks: Hooks,\n emittedEvent: WideEvent | null,\n event: HTTPEvent,\n hookContext: Omit<EnrichContext, 'event'>,\n): void {\n if (!emittedEvent) return\n let drainPromise: Promise<any> | undefined\n try {\n drainPromise = hooks.callHook('evlog:drain', {\n event: emittedEvent,\n request: hookContext.request,\n headers: hookContext.headers,\n })\n } catch (err) {\n console.error('[evlog] drain failed:', err)\n }\n\n // Use waitUntil if available (srvx native — Cloudflare Workers, Vercel Edge, etc.)\n // This ensures drains complete before the runtime terminates\n if (drainPromise && typeof event.req.waitUntil === 'function') {\n event.req.waitUntil(drainPromise)\n }\n}\n\nasync function callEnrichAndDrain(\n hooks: Hooks,\n emittedEvent: WideEvent | null,\n event: HTTPEvent,\n res?: Response,\n): Promise<void> {\n if (!emittedEvent) return\n\n const hookContext = buildHookContext(event, res)\n\n try {\n await hooks.callHook('evlog:enrich', { event: emittedEvent, ...hookContext })\n } catch (err) {\n console.error('[evlog] enrich failed:', err)\n }\n\n callDrainHook(hooks, emittedEvent, event, hookContext)\n}\n\n/**\n * Nitro v3 plugin entry point.\n *\n * Usage in Nitro v3:\n * ```ts\n * // plugins/evlog.ts\n * export { default } from 'evlog/nitro/v3'\n * ```\n */\nexport default definePlugin((nitroApp) => {\n // In production builds the plugin is bundled and useRuntimeConfig()\n // resolves the virtual module correctly. In dev mode the plugin is\n // loaded externally so useRuntimeConfig() returns a stub — fall back\n // to the env var bridge set by the module.\n const evlogConfig = (useRuntimeConfig().evlog ?? (process.env.__EVLOG_CONFIG ? JSON.parse(process.env.__EVLOG_CONFIG) : undefined)) as EvlogConfig | undefined\n\n initLogger({\n enabled: evlogConfig?.enabled,\n env: evlogConfig?.env,\n pretty: evlogConfig?.pretty,\n sampling: evlogConfig?.sampling,\n })\n\n if (!isEnabled()) return\n\n const hooks = nitroApp.hooks as unknown as Hooks\n\n hooks.hook('request', (event) => {\n const { pathname } = parseURL(event.req.url)\n\n // Skip logging for routes not matching include/exclude patterns\n if (!shouldLog(pathname, evlogConfig?.include, evlogConfig?.exclude)) {\n return\n }\n\n const ctx = getContext(event)\n\n // Store start time for duration calculation in tail sampling\n ctx._evlogStartTime = Date.now()\n\n let requestIdOverride: string | undefined = undefined\n if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') {\n const cfRay = event.req.headers.get('cf-ray')\n if (cfRay) requestIdOverride = cfRay\n }\n\n const log = createRequestLogger({\n method: event.req.method,\n path: pathname,\n requestId: requestIdOverride || ctx.requestId as string | undefined || crypto.randomUUID(),\n })\n\n // Apply route-based service configuration if a matching route is found\n const routeService = getServiceForPath(pathname, evlogConfig?.routes)\n if (routeService) {\n log.set({ service: routeService })\n }\n\n ctx.log = log\n })\n\n hooks.hook('response', async (res, event) => {\n const ctx = event.req.context\n // Skip if already emitted by error hook\n if (ctx?._evlogEmitted) return\n\n const log = ctx?.log as RequestLogger | undefined\n if (!log || !ctx) return\n\n const { status } = res\n log.set({ status })\n\n const startTime = ctx._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const { pathname } = parseURL(event.req.url)\n\n const tailCtx: TailSamplingContext = {\n status,\n duration: durationMs,\n path: pathname,\n method: event.req.method,\n context: log.getContext(),\n shouldKeep: false,\n }\n\n await hooks.callHook('evlog:emit:keep', tailCtx)\n\n const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(hooks, emittedEvent, event, res)\n })\n\n hooks.hook('error', async (error, { event }) => {\n if (!event) return\n const e = event as HTTPEvent\n\n const ctx = e.req.context\n const log = ctx?.log as RequestLogger | undefined\n if (!log || !ctx) return\n\n // Check if error.cause is an EvlogError (thrown errors get wrapped in HTTPError by nitro)\n const actualError = (error.cause as Error)?.name === 'EvlogError' \n ? error.cause as Error \n : error as Error\n\n log.error(actualError)\n\n const errorStatus = extractErrorStatus(actualError)\n log.set({ status: errorStatus })\n\n const { pathname } = parseURL(e.req.url)\n const startTime = ctx._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const tailCtx: TailSamplingContext = {\n status: errorStatus,\n duration: durationMs,\n path: pathname,\n method: e.req.method,\n context: log.getContext(),\n shouldKeep: false,\n }\n\n await hooks.callHook('evlog:emit:keep', tailCtx)\n\n ctx._evlogEmitted = true\n\n const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(hooks, emittedEvent, e)\n })\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.mjs","names":[],"sources":["../../../src/nitro-v3/plugin.ts"],"sourcesContent":["import { definePlugin } from 'nitro'\nimport { useRuntimeConfig } from 'nitro/runtime-config'\nimport type { CaptureError } from 'nitro/types'\nimport type { HTTPEvent } from 'nitro/h3'\nimport { parseURL } from 'ufo'\nimport { createRequestLogger, initLogger, isEnabled } from '../logger'\nimport { shouldLog, getServiceForPath, extractErrorStatus } from '../nitro'\nimport type { EvlogConfig } from '../nitro'\nimport type { EnrichContext, RequestLogger, TailSamplingContext, WideEvent } from '../types'\nimport { filterSafeHeaders } from '../utils'\n\n// Nitro v3 doesn't fully export hook types yet\n// https://github.com/nitrojs/nitro/blob/8882bc9e1dbf2d342e73097f22a2156f70f50575/src/types/runtime/nitro.ts#L48-L53\ninterface NitroV3Hooks {\n close: () => void\n error: CaptureError\n request: (event: HTTPEvent) => void | Promise<void>\n response: (res: Response, event: HTTPEvent) => void | Promise<void>\n 'evlog:emit:keep': (ctx: TailSamplingContext) => void | Promise<void>\n 'evlog:enrich': (ctx: EnrichContext) => void | Promise<void>\n 'evlog:drain': (ctx: { event: WideEvent; request?: { method?: string; path: string; requestId?: string }; headers?: Record<string, string> }) => void | Promise<void>\n}\n\ntype Hooks = {\n hook: <T extends keyof NitroV3Hooks>(name: T, listener: NitroV3Hooks[T]) => void\n callHook: <T extends keyof NitroV3Hooks>(name: T, ...args: Parameters<NitroV3Hooks[T]>) => Promise<void>\n}\n\nfunction getContext(event: HTTPEvent): Record<string, unknown> {\n if (!event.req.context) {\n event.req.context = {}\n }\n return event.req.context\n}\n\nfunction getSafeRequestHeaders(event: HTTPEvent): Record<string, string> {\n const headers: Record<string, string> = {}\n event.req.headers.forEach((value, key) => {\n headers[key] = value\n })\n return filterSafeHeaders(headers)\n}\n\nfunction getSafeResponseHeaders(res: Response): Record<string, string> | undefined {\n const headers: Record<string, string> = {}\n res.headers.forEach((value, key) => {\n headers[key] = value\n })\n if (Object.keys(headers).length === 0) return undefined\n return filterSafeHeaders(headers)\n}\n\nfunction buildHookContext(\n event: HTTPEvent,\n res?: Response,\n): Omit<EnrichContext, 'event'> {\n const { pathname } = parseURL(event.req.url)\n const responseHeaders = res ? getSafeResponseHeaders(res) : undefined\n return {\n request: { method: event.req.method, path: pathname },\n headers: getSafeRequestHeaders(event),\n response: {\n status: res?.status ?? 200,\n headers: responseHeaders,\n },\n }\n}\n\nasync function callDrainHook(\n hooks: Hooks,\n emittedEvent: WideEvent | null,\n event: HTTPEvent,\n hookContext: Omit<EnrichContext, 'event'>,\n): Promise<void> {\n if (!emittedEvent) return\n\n let drainPromise: Promise<unknown> | undefined\n try {\n const result = hooks.callHook('evlog:drain', {\n event: emittedEvent,\n request: hookContext.request,\n headers: hookContext.headers,\n })\n drainPromise = result?.catch?.((err: unknown) => {\n console.error('[evlog] drain failed:', err)\n })\n } catch (err) {\n console.error('[evlog] drain failed:', err)\n }\n\n if (!drainPromise) return\n\n // Use waitUntil if available (srvx native — Cloudflare Workers, Vercel Edge, etc.)\n // This keeps the runtime alive for background work without blocking the response\n if (typeof event.req.waitUntil === 'function') {\n event.req.waitUntil(drainPromise)\n } else {\n // Fallback: await drain to prevent lost logs in serverless environments\n // (e.g. Vercel Fluid Compute). On the normal path this runs from the\n // response hook (response already sent); on the error path it may run\n // before the error response is finalized.\n await drainPromise\n }\n}\n\nasync function callEnrichAndDrain(\n hooks: Hooks,\n emittedEvent: WideEvent | null,\n event: HTTPEvent,\n res?: Response,\n): Promise<void> {\n if (!emittedEvent) return\n\n const hookContext = buildHookContext(event, res)\n\n try {\n await hooks.callHook('evlog:enrich', { event: emittedEvent, ...hookContext })\n } catch (err) {\n console.error('[evlog] enrich failed:', err)\n }\n\n await callDrainHook(hooks, emittedEvent, event, hookContext)\n}\n\n/**\n * Nitro v3 plugin entry point.\n *\n * Usage in Nitro v3:\n * ```ts\n * // plugins/evlog.ts\n * export { default } from 'evlog/nitro/v3'\n * ```\n */\nexport default definePlugin((nitroApp) => {\n // In production builds the plugin is bundled and useRuntimeConfig()\n // resolves the virtual module correctly. In dev mode the plugin is\n // loaded externally so useRuntimeConfig() returns a stub — fall back\n // to the env var bridge set by the module.\n const evlogConfig = (useRuntimeConfig().evlog ?? (process.env.__EVLOG_CONFIG ? JSON.parse(process.env.__EVLOG_CONFIG) : undefined)) as EvlogConfig | undefined\n\n initLogger({\n enabled: evlogConfig?.enabled,\n env: evlogConfig?.env,\n pretty: evlogConfig?.pretty,\n sampling: evlogConfig?.sampling,\n })\n\n if (!isEnabled()) return\n\n const hooks = nitroApp.hooks as unknown as Hooks\n\n hooks.hook('request', (event) => {\n const { pathname } = parseURL(event.req.url)\n\n // Skip logging for routes not matching include/exclude patterns\n if (!shouldLog(pathname, evlogConfig?.include, evlogConfig?.exclude)) {\n return\n }\n\n const ctx = getContext(event)\n\n // Store start time for duration calculation in tail sampling\n ctx._evlogStartTime = Date.now()\n\n let requestIdOverride: string | undefined = undefined\n if (globalThis.navigator?.userAgent === 'Cloudflare-Workers') {\n const cfRay = event.req.headers.get('cf-ray')\n if (cfRay) requestIdOverride = cfRay\n }\n\n const log = createRequestLogger({\n method: event.req.method,\n path: pathname,\n requestId: requestIdOverride || ctx.requestId as string | undefined || crypto.randomUUID(),\n })\n\n // Apply route-based service configuration if a matching route is found\n const routeService = getServiceForPath(pathname, evlogConfig?.routes)\n if (routeService) {\n log.set({ service: routeService })\n }\n\n ctx.log = log\n })\n\n hooks.hook('response', async (res, event) => {\n const ctx = event.req.context\n // Skip if already emitted by error hook\n if (ctx?._evlogEmitted) return\n\n const log = ctx?.log as RequestLogger | undefined\n if (!log || !ctx) return\n\n const { status } = res\n log.set({ status })\n\n const startTime = ctx._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const { pathname } = parseURL(event.req.url)\n\n const tailCtx: TailSamplingContext = {\n status,\n duration: durationMs,\n path: pathname,\n method: event.req.method,\n context: log.getContext(),\n shouldKeep: false,\n }\n\n await hooks.callHook('evlog:emit:keep', tailCtx)\n\n const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(hooks, emittedEvent, event, res)\n })\n\n hooks.hook('error', async (error, { event }) => {\n if (!event) return\n const e = event as HTTPEvent\n\n const ctx = e.req.context\n const log = ctx?.log as RequestLogger | undefined\n if (!log || !ctx) return\n\n // Check if error.cause is an EvlogError (thrown errors get wrapped in HTTPError by nitro)\n const actualError = (error.cause as Error)?.name === 'EvlogError' \n ? error.cause as Error \n : error as Error\n\n log.error(actualError)\n\n const errorStatus = extractErrorStatus(actualError)\n log.set({ status: errorStatus })\n\n const { pathname } = parseURL(e.req.url)\n const startTime = ctx._evlogStartTime as number | undefined\n const durationMs = startTime ? Date.now() - startTime : undefined\n\n const tailCtx: TailSamplingContext = {\n status: errorStatus,\n duration: durationMs,\n path: pathname,\n method: e.req.method,\n context: log.getContext(),\n shouldKeep: false,\n }\n\n await hooks.callHook('evlog:emit:keep', tailCtx)\n\n ctx._evlogEmitted = true\n\n const emittedEvent = log.emit({ _forceKeep: tailCtx.shouldKeep })\n await callEnrichAndDrain(hooks, emittedEvent, e)\n })\n})\n"],"mappings":"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"}
|
package/dist/nuxt/module.d.mts
CHANGED
|
@@ -2,6 +2,33 @@ import { EnvironmentContext, RouteConfig, SamplingConfig, TransportConfig } from
|
|
|
2
2
|
import * as _nuxt_schema0 from "@nuxt/schema";
|
|
3
3
|
|
|
4
4
|
//#region src/nuxt/module.d.ts
|
|
5
|
+
interface ModuleAxiomBaseConfig {
|
|
6
|
+
/** Axiom dataset name */
|
|
7
|
+
dataset: string;
|
|
8
|
+
/** Axiom API token */
|
|
9
|
+
token: string;
|
|
10
|
+
/** Organization ID (required for Personal Access Tokens) */
|
|
11
|
+
orgId?: string;
|
|
12
|
+
/** Request timeout in milliseconds. Default: 5000 */
|
|
13
|
+
timeout?: number;
|
|
14
|
+
}
|
|
15
|
+
interface ModuleAxiomEdgeConfig {
|
|
16
|
+
/**
|
|
17
|
+
* Edge URL for Axiom ingest/query endpoints.
|
|
18
|
+
* If no path is provided, uses /v1/ingest/{dataset}.
|
|
19
|
+
* If a custom path is provided, it is used as-is (trailing slash trimmed).
|
|
20
|
+
*/
|
|
21
|
+
edgeUrl: string;
|
|
22
|
+
/** Mutually exclusive with edgeUrl. */
|
|
23
|
+
baseUrl?: never;
|
|
24
|
+
}
|
|
25
|
+
interface ModuleAxiomEndpointConfig {
|
|
26
|
+
/** Base URL for Axiom API. Uses /v1/datasets/{dataset}/ingest. */
|
|
27
|
+
baseUrl?: string;
|
|
28
|
+
/** Mutually exclusive with baseUrl. */
|
|
29
|
+
edgeUrl?: never;
|
|
30
|
+
}
|
|
31
|
+
type ModuleAxiomConfig = ModuleAxiomBaseConfig & (ModuleAxiomEdgeConfig | ModuleAxiomEndpointConfig);
|
|
5
32
|
interface ModuleOptions {
|
|
6
33
|
/**
|
|
7
34
|
* Enable or disable all logging globally.
|
|
@@ -87,13 +114,7 @@ interface ModuleOptions {
|
|
|
87
114
|
* }
|
|
88
115
|
* ```
|
|
89
116
|
*/
|
|
90
|
-
axiom?:
|
|
91
|
-
/** Axiom dataset name */dataset: string; /** Axiom API token */
|
|
92
|
-
token: string; /** Organization ID (required for Personal Access Tokens) */
|
|
93
|
-
orgId?: string; /** Base URL for Axiom API. Default: https://api.axiom.co */
|
|
94
|
-
baseUrl?: string; /** Request timeout in milliseconds. Default: 5000 */
|
|
95
|
-
timeout?: number;
|
|
96
|
-
};
|
|
117
|
+
axiom?: ModuleAxiomConfig;
|
|
97
118
|
/**
|
|
98
119
|
* OTLP adapter configuration.
|
|
99
120
|
* When configured, use `createOTLPDrain()` from `evlog/otlp` to send logs.
|
|
@@ -117,7 +138,8 @@ interface ModuleOptions {
|
|
|
117
138
|
};
|
|
118
139
|
/**
|
|
119
140
|
* PostHog adapter configuration.
|
|
120
|
-
* When configured, use `createPostHogDrain()` from `evlog/posthog` to send logs
|
|
141
|
+
* When configured, use `createPostHogDrain()` from `evlog/posthog` to send logs
|
|
142
|
+
* via PostHog Logs (OTLP).
|
|
121
143
|
*
|
|
122
144
|
* @example
|
|
123
145
|
* ```ts
|
|
@@ -128,9 +150,7 @@ interface ModuleOptions {
|
|
|
128
150
|
*/
|
|
129
151
|
posthog?: {
|
|
130
152
|
/** PostHog project API key */apiKey: string; /** PostHog host URL. Default: https://us.i.posthog.com */
|
|
131
|
-
host?: string; /**
|
|
132
|
-
eventName?: string; /** Override distinct_id (defaults to event.service) */
|
|
133
|
-
distinctId?: string; /** Request timeout in milliseconds. Default: 5000 */
|
|
153
|
+
host?: string; /** Request timeout in milliseconds. Default: 5000 */
|
|
134
154
|
timeout?: number;
|
|
135
155
|
};
|
|
136
156
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.mts","names":[],"sources":["../../src/nuxt/module.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"module.d.mts","names":[],"sources":["../../src/nuxt/module.ts"],"mappings":";;;;UAaU,qBAAA;;EAER,OAAA;EAFQ;EAIR,KAAA;;EAEA,KAAA;EAJA;EAMA,OAAA;AAAA;AAAA,UAGQ,qBAAA;EAHD;;AAAA;;;EASP,OAAA;EAEO;EAAP,OAAA;AAAA;AAAA,UAGQ,yBAAA;EAER;EAAA,OAAA;EAKG;EAHH,OAAA;AAAA;AAAA,KAGG,iBAAA,GAAoB,qBAAA,IAAyB,qBAAA,GAAwB,yBAAA;AAAA,UAEzD,aAAA;EAFyD;;;;;EAQxE,OAAA;EARiG;;AAEnG;EAWE,GAAA,GAAM,OAAA,CAAQ,kBAAA;;;;;EAMd,MAAA;EAiDW;;;;;;EAzCX,OAAA;EAyIe;;;;;;EAjIf,OAAA;EAAA;;;;;;;;;;;;;EAeA,MAAA,GAAS,MAAA,SAAe,WAAA;EAmED;;;;;;;;;;;;;;;;EAjDvB,QAAA,GAAW,cAAA;EA0GF;AACV;;;;;;;;;;EA9FC,SAAA,GAAY,eAAA;;;;;;;;;;;;;EAcZ,KAAA,GAAQ,iBAAA;;;;;;;;;;;;;;;EAgBR,IAAA;2DAEE,QAAA;IAEA,WAAA;IAEA,kBAAA,GAAqB,MAAA;IAErB,OAAA,GAAU,MAAA;IAEV,OAAA;EAAA;;;;;;;;;;;;;EAeF,OAAA;kCAEE,MAAA;IAEA,IAAA;IAEA,OAAA;EAAA;;;;;;;;;;;;EAcF,MAAA;qBAEE,GAAA;IAEA,WAAA;IAEA,OAAA;IAEA,IAAA,GAAO,MAAA;IAEP,OAAA;EAAA;;;;;;EAQF,SAAA;AAAA;AAAA,cACD,QAAA"}
|
package/dist/nuxt/module.mjs
CHANGED
package/dist/nuxt/module.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.mjs","names":[],"sources":["../../package.json","../../src/nuxt/module.ts"],"sourcesContent":["","import {\n addImports,\n addPlugin,\n addServerHandler,\n addServerImports,\n addServerPlugin,\n createResolver,\n defineNuxtModule,\n} from '@nuxt/kit'\nimport type { NitroConfig } from 'nitropack'\nimport type { EnvironmentContext, RouteConfig, SamplingConfig, TransportConfig } from '../types'\nimport { name, version } from '../../package.json'\n\nexport interface ModuleOptions {\n /**\n * Enable or disable all logging globally.\n * When false, all emits, tagged logs, and request logger operations become no-ops.\n * @default true\n */\n enabled?: boolean\n\n /**\n * Environment context overrides.\n */\n env?: Partial<EnvironmentContext>\n\n /**\n * Enable pretty printing.\n * @default true in development, false in production\n */\n pretty?: boolean\n\n /**\n * Route patterns to include in logging.\n * Supports glob patterns like '/api/**'.\n * If not set, all routes are logged.\n * @example ['/api/**', '/auth/**']\n */\n include?: string[]\n\n /**\n * Route patterns to exclude from logging.\n * Supports glob patterns like '/api/_nuxt_icon/**'.\n * Exclusions take precedence over inclusions.\n * @example ['/api/_nuxt_icon/**', '/health']\n */\n exclude?: string[]\n\n /**\n * Route-specific service configuration.\n * Allows setting different service names for different routes.\n * Patterns are matched using glob syntax.\n *\n * @example\n * ```ts\n * routes: {\n * '/api/foo/**': { service: 'service1' },\n * '/api/bar/**': { service: 'service2' }\n * }\n * ```\n */\n routes?: Record<string, RouteConfig>\n\n /**\n * Sampling configuration for filtering logs.\n * Allows configuring what percentage of logs to keep per level.\n *\n * @example\n * ```ts\n * sampling: {\n * rates: {\n * info: 10, // Keep 10% of info logs\n * warn: 50, // Keep 50% of warning logs\n * debug: 5, // Keep 5% of debug logs\n * error: 100, // Always keep errors (default)\n * }\n * }\n * ```\n */\n sampling?: SamplingConfig\n\n /**\n * Transport configuration for sending client logs to the server.\n *\n * @example\n * ```ts\n * transport: {\n * enabled: true, // Send logs to server API\n * endpoint: '/api/_evlog/ingest' // Custom endpoint\n * }\n * ```\n */\n transport?: TransportConfig\n\n /**\n * Axiom adapter configuration.\n * When configured, use `createAxiomDrain()` from `evlog/axiom` to send logs.\n *\n * @example\n * ```ts\n * axiom: {\n * dataset: 'my-app-logs',\n * token: process.env.AXIOM_TOKEN,\n * }\n * ```\n */\n axiom?:
|
|
1
|
+
{"version":3,"file":"module.mjs","names":[],"sources":["../../package.json","../../src/nuxt/module.ts"],"sourcesContent":["","import {\n addImports,\n addPlugin,\n addServerHandler,\n addServerImports,\n addServerPlugin,\n createResolver,\n defineNuxtModule,\n} from '@nuxt/kit'\nimport type { NitroConfig } from 'nitropack'\nimport type { EnvironmentContext, RouteConfig, SamplingConfig, TransportConfig } from '../types'\nimport { name, version } from '../../package.json'\n\ninterface ModuleAxiomBaseConfig {\n /** Axiom dataset name */\n dataset: string\n /** Axiom API token */\n token: string\n /** Organization ID (required for Personal Access Tokens) */\n orgId?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n}\n\ninterface ModuleAxiomEdgeConfig {\n /**\n * Edge URL for Axiom ingest/query endpoints.\n * If no path is provided, uses /v1/ingest/{dataset}.\n * If a custom path is provided, it is used as-is (trailing slash trimmed).\n */\n edgeUrl: string\n /** Mutually exclusive with edgeUrl. */\n baseUrl?: never\n}\n\ninterface ModuleAxiomEndpointConfig {\n /** Base URL for Axiom API. Uses /v1/datasets/{dataset}/ingest. */\n baseUrl?: string\n /** Mutually exclusive with baseUrl. */\n edgeUrl?: never\n}\n\ntype ModuleAxiomConfig = ModuleAxiomBaseConfig & (ModuleAxiomEdgeConfig | ModuleAxiomEndpointConfig)\n\nexport interface ModuleOptions {\n /**\n * Enable or disable all logging globally.\n * When false, all emits, tagged logs, and request logger operations become no-ops.\n * @default true\n */\n enabled?: boolean\n\n /**\n * Environment context overrides.\n */\n env?: Partial<EnvironmentContext>\n\n /**\n * Enable pretty printing.\n * @default true in development, false in production\n */\n pretty?: boolean\n\n /**\n * Route patterns to include in logging.\n * Supports glob patterns like '/api/**'.\n * If not set, all routes are logged.\n * @example ['/api/**', '/auth/**']\n */\n include?: string[]\n\n /**\n * Route patterns to exclude from logging.\n * Supports glob patterns like '/api/_nuxt_icon/**'.\n * Exclusions take precedence over inclusions.\n * @example ['/api/_nuxt_icon/**', '/health']\n */\n exclude?: string[]\n\n /**\n * Route-specific service configuration.\n * Allows setting different service names for different routes.\n * Patterns are matched using glob syntax.\n *\n * @example\n * ```ts\n * routes: {\n * '/api/foo/**': { service: 'service1' },\n * '/api/bar/**': { service: 'service2' }\n * }\n * ```\n */\n routes?: Record<string, RouteConfig>\n\n /**\n * Sampling configuration for filtering logs.\n * Allows configuring what percentage of logs to keep per level.\n *\n * @example\n * ```ts\n * sampling: {\n * rates: {\n * info: 10, // Keep 10% of info logs\n * warn: 50, // Keep 50% of warning logs\n * debug: 5, // Keep 5% of debug logs\n * error: 100, // Always keep errors (default)\n * }\n * }\n * ```\n */\n sampling?: SamplingConfig\n\n /**\n * Transport configuration for sending client logs to the server.\n *\n * @example\n * ```ts\n * transport: {\n * enabled: true, // Send logs to server API\n * endpoint: '/api/_evlog/ingest' // Custom endpoint\n * }\n * ```\n */\n transport?: TransportConfig\n\n /**\n * Axiom adapter configuration.\n * When configured, use `createAxiomDrain()` from `evlog/axiom` to send logs.\n *\n * @example\n * ```ts\n * axiom: {\n * dataset: 'my-app-logs',\n * token: process.env.AXIOM_TOKEN,\n * }\n * ```\n */\n axiom?: ModuleAxiomConfig\n\n /**\n * OTLP adapter configuration.\n * When configured, use `createOTLPDrain()` from `evlog/otlp` to send logs.\n *\n * @example\n * ```ts\n * otlp: {\n * endpoint: 'http://localhost:4318',\n * headers: {\n * 'Authorization': `Basic ${process.env.GRAFANA_TOKEN}`,\n * },\n * }\n * ```\n */\n otlp?: {\n /** OTLP HTTP endpoint (e.g., http://localhost:4318) */\n endpoint: string\n /** Override service name (defaults to event.service) */\n serviceName?: string\n /** Additional resource attributes */\n resourceAttributes?: Record<string, string | number | boolean>\n /** Custom headers (e.g., for authentication) */\n headers?: Record<string, string>\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n }\n\n /**\n * PostHog adapter configuration.\n * When configured, use `createPostHogDrain()` from `evlog/posthog` to send logs\n * via PostHog Logs (OTLP).\n *\n * @example\n * ```ts\n * posthog: {\n * apiKey: process.env.POSTHOG_API_KEY,\n * }\n * ```\n */\n posthog?: {\n /** PostHog project API key */\n apiKey: string\n /** PostHog host URL. Default: https://us.i.posthog.com */\n host?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n }\n\n /**\n * Sentry adapter configuration.\n * When configured, use `createSentryDrain()` from `evlog/sentry` to send logs.\n *\n * @example\n * ```ts\n * sentry: {\n * dsn: process.env.SENTRY_DSN,\n * }\n * ```\n */\n sentry?: {\n /** Sentry DSN */\n dsn: string\n /** Environment override (defaults to event.environment) */\n environment?: string\n /** Release version override (defaults to event.version) */\n release?: string\n /** Additional tags to attach as attributes */\n tags?: Record<string, string>\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n }\n\n /**\n * How long to retain events before cleanup (used by @evlog/nuxthub).\n * Supports \"30d\" (days), \"24h\" (hours), \"60m\" (minutes).\n * @default '30d'\n */\n retention?: string\n}\n\nexport default defineNuxtModule<ModuleOptions>({\n meta: {\n name,\n version,\n configKey: name,\n docs: 'https://evlog.dev',\n },\n defaults: {},\n setup(options, nuxt) {\n const resolver = createResolver(import.meta.url)\n\n const transportEnabled = options.transport?.enabled ?? false\n const transportEndpoint = options.transport?.endpoint ?? '/api/_evlog/ingest'\n\n // Register custom error handler for proper EvlogError serialization\n // Only set if not already configured to avoid overwriting user's custom handler\n // @ts-expect-error nitro:config hook exists but is not in NuxtHooks type\n nuxt.hook('nitro:config', (nitroConfig: NitroConfig) => {\n nitroConfig.errorHandler = nitroConfig.errorHandler || resolver.resolve('../nitro/errorHandler')\n })\n\n nuxt.options.runtimeConfig.evlog = options\n nuxt.options.runtimeConfig.public.evlog = {\n enabled: options.enabled ?? true,\n pretty: options.pretty,\n transport: {\n enabled: transportEnabled,\n endpoint: transportEndpoint,\n },\n }\n\n if (transportEnabled) {\n addServerHandler({\n route: transportEndpoint,\n method: 'post',\n handler: resolver.resolve('../runtime/server/routes/_evlog/ingest.post'),\n })\n }\n\n addServerPlugin(resolver.resolve('../nitro/plugin'))\n\n addPlugin({\n src: resolver.resolve('../runtime/client/plugin'),\n mode: 'client',\n })\n\n addImports([\n {\n name: 'log',\n from: resolver.resolve('../runtime/client/log'),\n },\n {\n name: 'setIdentity',\n from: resolver.resolve('../runtime/client/log'),\n },\n {\n name: 'clearIdentity',\n from: resolver.resolve('../runtime/client/log'),\n },\n {\n name: 'createEvlogError',\n from: resolver.resolve('../error'),\n },\n {\n name: 'parseError',\n from: resolver.resolve('../runtime/utils/parseError'),\n },\n ])\n\n addServerImports([\n {\n name: 'useLogger',\n from: resolver.resolve('../runtime/server/useLogger'),\n },\n {\n name: 'log',\n from: resolver.resolve('../logger'),\n },\n {\n name: 'createEvlogError',\n from: resolver.resolve('../error'),\n },\n ])\n },\n})\n"],"mappings":";;;;;;;;AC2NA,qBAAe,iBAAgC;CAC7C,MAAM;EACJ;EACA;EACA,WAAW;EACX,MAAM;EACP;CACD,UAAU,EAAE;CACZ,MAAM,SAAS,MAAM;EACnB,MAAM,WAAW,eAAe,OAAO,KAAK,IAAI;EAEhD,MAAM,mBAAmB,QAAQ,WAAW,WAAW;EACvD,MAAM,oBAAoB,QAAQ,WAAW,YAAY;AAKzD,OAAK,KAAK,iBAAiB,gBAA6B;AACtD,eAAY,eAAe,YAAY,gBAAgB,SAAS,QAAQ,wBAAwB;IAChG;AAEF,OAAK,QAAQ,cAAc,QAAQ;AACnC,OAAK,QAAQ,cAAc,OAAO,QAAQ;GACxC,SAAS,QAAQ,WAAW;GAC5B,QAAQ,QAAQ;GAChB,WAAW;IACT,SAAS;IACT,UAAU;IACX;GACF;AAED,MAAI,iBACF,kBAAiB;GACf,OAAO;GACP,QAAQ;GACR,SAAS,SAAS,QAAQ,8CAA8C;GACzE,CAAC;AAGJ,kBAAgB,SAAS,QAAQ,kBAAkB,CAAC;AAEpD,YAAU;GACR,KAAK,SAAS,QAAQ,2BAA2B;GACjD,MAAM;GACP,CAAC;AAEF,aAAW;GACT;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,wBAAwB;IAChD;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,wBAAwB;IAChD;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,wBAAwB;IAChD;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,WAAW;IACnC;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,8BAA8B;IACtD;GACF,CAAC;AAEF,mBAAiB;GACf;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,8BAA8B;IACtD;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,YAAY;IACpC;GACD;IACE,MAAM;IACN,MAAM,SAAS,QAAQ,WAAW;IACnC;GACF,CAAC;;CAEL,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evlog",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Wide event logging library with structured error handling. Inspired by LoggingSucks.",
|
|
5
5
|
"author": "HugoRCD <contact@hrcd.fr>",
|
|
6
6
|
"homepage": "https://evlog.dev",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"nuxt",
|
|
20
20
|
"nitro",
|
|
21
21
|
"nextjs",
|
|
22
|
+
"tanstack-start",
|
|
22
23
|
"typescript"
|
|
23
24
|
],
|
|
24
25
|
"license": "MIT",
|
|
@@ -160,7 +161,7 @@
|
|
|
160
161
|
"changelogen": "^0.6.2",
|
|
161
162
|
"consola": "^3.4.2",
|
|
162
163
|
"h3": "^1.15.5",
|
|
163
|
-
"happy-dom": "^20.
|
|
164
|
+
"happy-dom": "^20.7.0",
|
|
164
165
|
"nitro": "^3.0.1-alpha.2",
|
|
165
166
|
"nitropack": "^2.13.1",
|
|
166
167
|
"nuxt": "^4.3.1",
|
|
@@ -173,8 +174,8 @@
|
|
|
173
174
|
"nitropack": "^2.13.1",
|
|
174
175
|
"ofetch": "^1.5.1",
|
|
175
176
|
"nitro": "^3.0.1-alpha.2",
|
|
176
|
-
"next": ">=
|
|
177
|
-
"react": ">=
|
|
177
|
+
"next": ">=16.1.6",
|
|
178
|
+
"react": ">=19.2.4"
|
|
178
179
|
},
|
|
179
180
|
"peerDependenciesMeta": {
|
|
180
181
|
"@nuxt/kit": {
|