evlog 2.14.1 → 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 +4 -4
- 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-DQoBo7Dl.mjs → audit--n0QRR2Y.mjs} +152 -13
- package/dist/audit--n0QRR2Y.mjs.map +1 -0
- package/dist/{audit-CTIviX3P.d.mts → audit-CJl-wZ10.d.mts} +134 -2
- package/dist/audit-CJl-wZ10.d.mts.map +1 -0
- package/dist/better-auth/index.d.mts +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-C7gSQVqk.d.mts → error-C-66_G2M.d.mts} +2 -2
- package/dist/{error-C7gSQVqk.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-4MPmTzjY.d.mts → errors-DQoYsDW1.d.mts} +2 -2
- package/dist/{errors-4MPmTzjY.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/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-DttRJRGa.d.mts → logger-Brt5-WMK.d.mts} +9 -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/instrumentation.d.mts +1 -1
- package/dist/next/instrumentation.mjs +1 -1
- package/dist/nitro/errorHandler.mjs +2 -2
- 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/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-CPPRCPbG.d.mts → nitro-DHPb9dXG.d.mts} +2 -2
- package/dist/{nitro-CPPRCPbG.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-o1GpZEOR.d.mts → parseError-B1zJZvQ5.d.mts} +2 -2
- package/dist/parseError-B1zJZvQ5.d.mts.map +1 -0
- 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/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/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-CyPP1sVB.d.mts → useLogger-Cb1R6bQE.d.mts} +2 -2
- package/dist/{useLogger-CyPP1sVB.d.mts.map → useLogger-Cb1R6bQE.d.mts.map} +1 -1
- package/dist/{utils-Dmin7wVL.d.mts → utils-gQCeZMbg.d.mts} +2 -2
- package/dist/{utils-Dmin7wVL.d.mts.map → utils-gQCeZMbg.d.mts.map} +1 -1
- package/dist/utils.d.mts +1 -1
- package/dist/vite/index.d.mts +1 -1
- package/dist/vite/index.mjs +1 -1
- package/dist/workers.d.mts +1 -1
- package/dist/workers.mjs +1 -1
- package/package.json +22 -19
- package/dist/_drain-CmCtsuF6.mjs +0 -23
- package/dist/_drain-CmCtsuF6.mjs.map +0 -1
- package/dist/_http-BY1e9pwC.mjs +0 -78
- package/dist/_http-BY1e9pwC.mjs.map +0 -1
- package/dist/_severity-CQijvfhU.mjs.map +0 -1
- package/dist/audit-CTIviX3P.d.mts.map +0 -1
- package/dist/audit-DQoBo7Dl.mjs.map +0 -1
- package/dist/dist-Do8P4zWd.mjs.map +0 -1
- package/dist/fork-D1j1Fuzy.mjs +0 -72
- package/dist/fork-D1j1Fuzy.mjs.map +0 -1
- package/dist/headers-D74M0wsg.mjs.map +0 -1
- package/dist/logger-DttRJRGa.d.mts.map +0 -1
- package/dist/middleware-CTnDsST-.d.mts +0 -93
- package/dist/middleware-CTnDsST-.d.mts.map +0 -1
- package/dist/middleware-oAccqyPp.mjs +0 -123
- package/dist/middleware-oAccqyPp.mjs.map +0 -1
- package/dist/nitro-OmT_M4Pb.mjs.map +0 -1
- package/dist/nitroConfigBridge-C37lXaNm.mjs.map +0 -1
- package/dist/parseError-o1GpZEOR.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
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as defineDrain } from "../_drain-CmCtsuF6.mjs";
|
|
1
|
+
import { a as resolveAdapterConfig, n as defineHttpDrain, r as httpPost, t as defineDrain } from "../drain-ByWUeOQC.mjs";
|
|
3
2
|
import { sendBatchToOTLP } from "./otlp.mjs";
|
|
4
3
|
//#region src/adapters/posthog.ts
|
|
5
4
|
const POSTHOG_FIELDS = [
|
|
@@ -11,14 +10,12 @@ const POSTHOG_FIELDS = [
|
|
|
11
10
|
key: "host",
|
|
12
11
|
env: ["NUXT_POSTHOG_HOST", "POSTHOG_HOST"]
|
|
13
12
|
},
|
|
13
|
+
{ key: "mode" },
|
|
14
|
+
{ key: "eventName" },
|
|
15
|
+
{ key: "distinctId" },
|
|
14
16
|
{ key: "timeout" },
|
|
15
17
|
{ key: "retries" }
|
|
16
18
|
];
|
|
17
|
-
const POSTHOG_EVENTS_FIELDS = [
|
|
18
|
-
...POSTHOG_FIELDS,
|
|
19
|
-
{ key: "eventName" },
|
|
20
|
-
{ key: "distinctId" }
|
|
21
|
-
];
|
|
22
19
|
function resolveHost(config) {
|
|
23
20
|
return (config.host ?? "https://us.i.posthog.com").replace(/\/$/, "");
|
|
24
21
|
}
|
|
@@ -31,7 +28,7 @@ function toOTLPConfig(config) {
|
|
|
31
28
|
};
|
|
32
29
|
}
|
|
33
30
|
/**
|
|
34
|
-
* Convert a WideEvent to a PostHog custom event
|
|
31
|
+
* Convert a WideEvent to a PostHog custom event.
|
|
35
32
|
*/
|
|
36
33
|
function toPostHogEvent(event, config) {
|
|
37
34
|
const { timestamp, level, service, ...rest } = event;
|
|
@@ -47,7 +44,10 @@ function toPostHogEvent(event, config) {
|
|
|
47
44
|
};
|
|
48
45
|
}
|
|
49
46
|
/**
|
|
50
|
-
* Create a drain function for sending logs to PostHog
|
|
47
|
+
* Create a drain function for sending logs to PostHog.
|
|
48
|
+
*
|
|
49
|
+
* - Default `mode: 'logs'` — sends events to PostHog Logs via OTLP. Recommended.
|
|
50
|
+
* - `mode: 'events'` — sends events to the `/batch/` API as custom events.
|
|
51
51
|
*
|
|
52
52
|
* Configuration priority (highest to lowest):
|
|
53
53
|
* 1. Overrides passed to createPostHogDrain()
|
|
@@ -57,123 +57,88 @@ function toPostHogEvent(event, config) {
|
|
|
57
57
|
*
|
|
58
58
|
* @example
|
|
59
59
|
* ```ts
|
|
60
|
-
* //
|
|
61
|
-
*
|
|
60
|
+
* // Default: PostHog Logs (OTLP)
|
|
61
|
+
* initLogger({ drain: createPostHogDrain() })
|
|
62
62
|
*
|
|
63
|
-
* //
|
|
64
|
-
*
|
|
65
|
-
* apiKey: 'phc_...',
|
|
66
|
-
* host: 'https://eu.i.posthog.com',
|
|
67
|
-
* }))
|
|
63
|
+
* // Custom events
|
|
64
|
+
* initLogger({ drain: createPostHogDrain({ mode: 'events', eventName: 'server_request' }) })
|
|
68
65
|
* ```
|
|
69
66
|
*/
|
|
70
67
|
function createPostHogDrain(overrides) {
|
|
68
|
+
if ((overrides?.mode ?? "logs") === "events") return defineHttpDrain({
|
|
69
|
+
name: "posthog-events",
|
|
70
|
+
resolve: async () => {
|
|
71
|
+
const config = await resolveAdapterConfig("posthog", POSTHOG_FIELDS, overrides);
|
|
72
|
+
if (!config.apiKey) {
|
|
73
|
+
console.error("[evlog/posthog-events] Missing apiKey. Set NUXT_POSTHOG_API_KEY env var or pass to createPostHogDrain({ mode: 'events' })");
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return config;
|
|
77
|
+
},
|
|
78
|
+
encode: (events, config) => ({
|
|
79
|
+
url: `${resolveHost(config)}/batch/`,
|
|
80
|
+
headers: { "Content-Type": "application/json" },
|
|
81
|
+
body: JSON.stringify({
|
|
82
|
+
api_key: config.apiKey,
|
|
83
|
+
batch: events.map((event) => toPostHogEvent(event, config))
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
});
|
|
71
87
|
return defineDrain({
|
|
72
88
|
name: "posthog",
|
|
73
89
|
resolve: async () => {
|
|
74
90
|
const config = await resolveAdapterConfig("posthog", POSTHOG_FIELDS, overrides);
|
|
75
91
|
if (!config.apiKey) {
|
|
76
|
-
console.error("[evlog/posthog] Missing apiKey. Set NUXT_POSTHOG_API_KEY
|
|
92
|
+
console.error("[evlog/posthog] Missing apiKey. Set NUXT_POSTHOG_API_KEY env var or pass to createPostHogDrain()");
|
|
77
93
|
return null;
|
|
78
94
|
}
|
|
79
95
|
return config;
|
|
80
96
|
},
|
|
81
|
-
send:
|
|
97
|
+
send: async (events, config) => {
|
|
98
|
+
if (events.length === 0) return;
|
|
99
|
+
await sendBatchToOTLP(events, toOTLPConfig(config));
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* @deprecated Use {@link createPostHogDrain} with `mode: 'events'`.
|
|
105
|
+
*/
|
|
106
|
+
function createPostHogEventsDrain(overrides) {
|
|
107
|
+
return createPostHogDrain({
|
|
108
|
+
...overrides,
|
|
109
|
+
mode: "events"
|
|
82
110
|
});
|
|
83
111
|
}
|
|
84
112
|
/**
|
|
85
113
|
* Send a single event to PostHog Logs via OTLP.
|
|
86
|
-
*
|
|
87
|
-
* @example
|
|
88
|
-
* ```ts
|
|
89
|
-
* await sendToPostHog(event, {
|
|
90
|
-
* apiKey: process.env.POSTHOG_API_KEY!,
|
|
91
|
-
* })
|
|
92
|
-
* ```
|
|
93
114
|
*/
|
|
94
115
|
async function sendToPostHog(event, config) {
|
|
95
116
|
await sendBatchToPostHog([event], config);
|
|
96
117
|
}
|
|
97
118
|
/**
|
|
98
119
|
* Send a batch of events to PostHog Logs via OTLP.
|
|
99
|
-
*
|
|
100
|
-
* @example
|
|
101
|
-
* ```ts
|
|
102
|
-
* await sendBatchToPostHog(events, {
|
|
103
|
-
* apiKey: process.env.POSTHOG_API_KEY!,
|
|
104
|
-
* })
|
|
105
|
-
* ```
|
|
106
120
|
*/
|
|
107
121
|
async function sendBatchToPostHog(events, config) {
|
|
108
122
|
if (events.length === 0) return;
|
|
109
123
|
await sendBatchToOTLP(events, toOTLPConfig(config));
|
|
110
124
|
}
|
|
111
125
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* Uses PostHog's `/batch/` API. Consider using `createPostHogDrain()` instead
|
|
115
|
-
* which uses PostHog Logs (OTLP) and is significantly cheaper.
|
|
116
|
-
*
|
|
117
|
-
* Configuration priority (highest to lowest):
|
|
118
|
-
* 1. Overrides passed to createPostHogEventsDrain()
|
|
119
|
-
* 2. runtimeConfig.evlog.posthog
|
|
120
|
-
* 3. runtimeConfig.posthog
|
|
121
|
-
* 4. Environment variables: NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY, NUXT_POSTHOG_HOST/POSTHOG_HOST
|
|
122
|
-
*
|
|
123
|
-
* @example
|
|
124
|
-
* ```ts
|
|
125
|
-
* nitroApp.hooks.hook('evlog:drain', createPostHogEventsDrain({
|
|
126
|
-
* eventName: 'server_request',
|
|
127
|
-
* }))
|
|
128
|
-
* ```
|
|
129
|
-
*/
|
|
130
|
-
function createPostHogEventsDrain(overrides) {
|
|
131
|
-
return defineDrain({
|
|
132
|
-
name: "posthog-events",
|
|
133
|
-
resolve: async () => {
|
|
134
|
-
const config = await resolveAdapterConfig("posthog", POSTHOG_EVENTS_FIELDS, overrides);
|
|
135
|
-
if (!config.apiKey) {
|
|
136
|
-
console.error("[evlog/posthog-events] Missing apiKey. Set NUXT_POSTHOG_API_KEY/POSTHOG_API_KEY env var or pass to createPostHogEventsDrain()");
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
return config;
|
|
140
|
-
},
|
|
141
|
-
send: sendBatchToPostHogEvents
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* Send a single event to PostHog as a custom event.
|
|
146
|
-
*
|
|
147
|
-
* @example
|
|
148
|
-
* ```ts
|
|
149
|
-
* await sendToPostHogEvents(event, {
|
|
150
|
-
* apiKey: process.env.POSTHOG_API_KEY!,
|
|
151
|
-
* })
|
|
152
|
-
* ```
|
|
126
|
+
* Send a single event to PostHog via the custom-events `/batch/` API.
|
|
153
127
|
*/
|
|
154
128
|
async function sendToPostHogEvents(event, config) {
|
|
155
129
|
await sendBatchToPostHogEvents([event], config);
|
|
156
130
|
}
|
|
157
131
|
/**
|
|
158
|
-
* Send a batch of events to PostHog
|
|
159
|
-
*
|
|
160
|
-
* @example
|
|
161
|
-
* ```ts
|
|
162
|
-
* await sendBatchToPostHogEvents(events, {
|
|
163
|
-
* apiKey: process.env.POSTHOG_API_KEY!,
|
|
164
|
-
* })
|
|
165
|
-
* ```
|
|
132
|
+
* Send a batch of events to PostHog via the custom-events `/batch/` API.
|
|
166
133
|
*/
|
|
167
134
|
async function sendBatchToPostHogEvents(events, config) {
|
|
168
135
|
if (events.length === 0) return;
|
|
169
|
-
const url = `${resolveHost(config)}/batch/`;
|
|
170
|
-
const batch = events.map((event) => toPostHogEvent(event, config));
|
|
171
136
|
await httpPost({
|
|
172
|
-
url
|
|
137
|
+
url: `${resolveHost(config)}/batch/`,
|
|
173
138
|
headers: { "Content-Type": "application/json" },
|
|
174
139
|
body: JSON.stringify({
|
|
175
140
|
api_key: config.apiKey,
|
|
176
|
-
batch
|
|
141
|
+
batch: events.map((event) => toPostHogEvent(event, config))
|
|
177
142
|
}),
|
|
178
143
|
timeout: config.timeout ?? 5e3,
|
|
179
144
|
retries: config.retries,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"posthog.mjs","names":[],"sources":["../../src/adapters/posthog.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '
|
|
1
|
+
{"version":3,"file":"posthog.mjs","names":[],"sources":["../../src/adapters/posthog.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineDrain, defineHttpDrain } from '../shared/drain'\nimport { httpPost } from '../shared/http'\nimport { sendBatchToOTLP } from './otlp'\nimport type { OTLPConfig } from './otlp'\n\n/**\n * Mode for {@link createPostHogDrain}.\n *\n * - `'logs'` (default) — sends events to PostHog Logs via OTLP. Cheapest path\n * and recommended for most teams.\n * - `'events'` — sends events to the `/batch/` API as custom PostHog events.\n * Useful when you want events to appear in PostHog product analytics\n * funnels/dashboards.\n */\nexport type PostHogMode = 'logs' | 'events'\n\nexport interface PostHogConfig {\n /** PostHog project API key */\n apiKey: string\n /** PostHog host URL. Default: https://us.i.posthog.com */\n host?: string\n /**\n * Send mode. `'logs'` (default) uses PostHog Logs (OTLP, cheapest);\n * `'events'` uses the `/batch/` API for custom PostHog events.\n * @default 'logs'\n */\n mode?: PostHogMode\n /**\n * PostHog event name when `mode === 'events'`. Ignored otherwise.\n * @default 'evlog_wide_event'\n */\n eventName?: string\n /**\n * Override `distinct_id` when `mode === 'events'`. Ignored otherwise.\n * Defaults to `event.userId` (when set) or `event.service`.\n */\n distinctId?: string\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\n/**\n * @deprecated Use {@link PostHogConfig} with `mode: 'events'` instead.\n */\nexport type PostHogEventsConfig = PostHogConfig\n\n/** PostHog event structure for the batch API */\nexport interface PostHogEvent {\n event: string\n distinct_id: string\n timestamp: string\n properties: Record<string, unknown>\n}\n\nconst POSTHOG_FIELDS: ConfigField<PostHogConfig>[] = [\n { key: 'apiKey', env: ['NUXT_POSTHOG_API_KEY', 'POSTHOG_API_KEY'] },\n { key: 'host', env: ['NUXT_POSTHOG_HOST', 'POSTHOG_HOST'] },\n { key: 'mode' },\n { key: 'eventName' },\n { key: 'distinctId' },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nfunction resolveHost(config: PostHogConfig): string {\n return (config.host ?? 'https://us.i.posthog.com').replace(/\\/$/, '')\n}\n\nfunction toOTLPConfig(config: PostHogConfig): OTLPConfig {\n return {\n endpoint: `${resolveHost(config)}/i`,\n headers: { Authorization: `Bearer ${config.apiKey}` },\n timeout: config.timeout,\n retries: config.retries,\n }\n}\n\n/**\n * Convert a WideEvent to a PostHog custom event.\n */\nexport function toPostHogEvent(event: WideEvent, config: PostHogConfig): PostHogEvent {\n const { timestamp, level, service, ...rest } = event\n return {\n event: config.eventName ?? 'evlog_wide_event',\n distinct_id: config.distinctId\n ?? (typeof event.userId === 'string' ? event.userId : undefined)\n ?? service,\n timestamp,\n properties: {\n level,\n service,\n ...rest,\n },\n }\n}\n\n/**\n * Create a drain function for sending logs to PostHog.\n *\n * - Default `mode: 'logs'` — sends events to PostHog Logs via OTLP. Recommended.\n * - `mode: 'events'` — sends events to the `/batch/` API as custom events.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createPostHogDrain()\n * 2. runtimeConfig.evlog.posthog\n * 3. runtimeConfig.posthog\n * 4. Environment variables: NUXT_POSTHOG_*, POSTHOG_*\n *\n * @example\n * ```ts\n * // Default: PostHog Logs (OTLP)\n * initLogger({ drain: createPostHogDrain() })\n *\n * // Custom events\n * initLogger({ drain: createPostHogDrain({ mode: 'events', eventName: 'server_request' }) })\n * ```\n */\nexport function createPostHogDrain(overrides?: Partial<PostHogConfig>) {\n const mode: PostHogMode = overrides?.mode ?? 'logs'\n\n if (mode === 'events') {\n return defineHttpDrain<PostHogConfig>({\n name: 'posthog-events',\n resolve: async () => {\n const config = await resolveAdapterConfig<PostHogConfig>('posthog', POSTHOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog-events] Missing apiKey. Set NUXT_POSTHOG_API_KEY env var or pass to createPostHogDrain({ mode: \\'events\\' })')\n return null\n }\n return config as PostHogConfig\n },\n encode: (events, config) => ({\n url: `${resolveHost(config)}/batch/`,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n api_key: config.apiKey,\n batch: events.map(event => toPostHogEvent(event, config)),\n }),\n }),\n })\n }\n\n return defineDrain<PostHogConfig>({\n name: 'posthog',\n resolve: async () => {\n const config = await resolveAdapterConfig<PostHogConfig>('posthog', POSTHOG_FIELDS, overrides)\n if (!config.apiKey) {\n console.error('[evlog/posthog] Missing apiKey. Set NUXT_POSTHOG_API_KEY env var or pass to createPostHogDrain()')\n return null\n }\n return config as PostHogConfig\n },\n send: async (events, config) => {\n if (events.length === 0) return\n await sendBatchToOTLP(events, toOTLPConfig(config))\n },\n })\n}\n\n/**\n * @deprecated Use {@link createPostHogDrain} with `mode: 'events'`.\n */\nexport function createPostHogEventsDrain(overrides?: Partial<PostHogConfig>) {\n return createPostHogDrain({ ...overrides, mode: 'events' })\n}\n\n/**\n * Send a single event to PostHog Logs via OTLP.\n */\nexport async function sendToPostHog(event: WideEvent, config: PostHogConfig): Promise<void> {\n await sendBatchToPostHog([event], config)\n}\n\n/**\n * Send a batch of events to PostHog Logs via OTLP.\n */\nexport async function sendBatchToPostHog(events: WideEvent[], config: PostHogConfig): Promise<void> {\n if (events.length === 0) return\n await sendBatchToOTLP(events, toOTLPConfig(config))\n}\n\n/**\n * Send a single event to PostHog via the custom-events `/batch/` API.\n */\nexport async function sendToPostHogEvents(event: WideEvent, config: PostHogConfig): Promise<void> {\n await sendBatchToPostHogEvents([event], config)\n}\n\n/**\n * Send a batch of events to PostHog via the custom-events `/batch/` API.\n */\nexport async function sendBatchToPostHogEvents(events: WideEvent[], config: PostHogConfig): Promise<void> {\n if (events.length === 0) return\n await httpPost({\n url: `${resolveHost(config)}/batch/`,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n api_key: config.apiKey,\n batch: events.map(event => toPostHogEvent(event, config)),\n }),\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'PostHog',\n })\n}\n"],"mappings":";;;AA2DA,MAAM,iBAA+C;CACnD;EAAE,KAAK;EAAU,KAAK,CAAC,wBAAwB,kBAAkB;EAAE;CACnE;EAAE,KAAK;EAAQ,KAAK,CAAC,qBAAqB,eAAe;EAAE;CAC3D,EAAE,KAAK,QAAQ;CACf,EAAE,KAAK,aAAa;CACpB,EAAE,KAAK,cAAc;CACrB,EAAE,KAAK,WAAW;CAClB,EAAE,KAAK,WAAW;CACnB;AAED,SAAS,YAAY,QAA+B;AAClD,SAAQ,OAAO,QAAQ,4BAA4B,QAAQ,OAAO,GAAG;;AAGvE,SAAS,aAAa,QAAmC;AACvD,QAAO;EACL,UAAU,GAAG,YAAY,OAAO,CAAC;EACjC,SAAS,EAAE,eAAe,UAAU,OAAO,UAAU;EACrD,SAAS,OAAO;EAChB,SAAS,OAAO;EACjB;;;;;AAMH,SAAgB,eAAe,OAAkB,QAAqC;CACpF,MAAM,EAAE,WAAW,OAAO,SAAS,GAAG,SAAS;AAC/C,QAAO;EACL,OAAO,OAAO,aAAa;EAC3B,aAAa,OAAO,eACd,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS,KAAA,MACnD;EACL;EACA,YAAY;GACV;GACA;GACA,GAAG;GACJ;EACF;;;;;;;;;;;;;;;;;;;;;;;AAwBH,SAAgB,mBAAmB,WAAoC;AAGrE,MAF0B,WAAW,QAAQ,YAEhC,SACX,QAAO,gBAA+B;EACpC,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBAAoC,WAAW,gBAAgB,UAAU;AAC9F,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,4HAA8H;AAC5I,WAAO;;AAET,UAAO;;EAET,SAAS,QAAQ,YAAY;GAC3B,KAAK,GAAG,YAAY,OAAO,CAAC;GAC5B,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM,KAAK,UAAU;IACnB,SAAS,OAAO;IAChB,OAAO,OAAO,KAAI,UAAS,eAAe,OAAO,OAAO,CAAC;IAC1D,CAAC;GACH;EACF,CAAC;AAGJ,QAAO,YAA2B;EAChC,MAAM;EACN,SAAS,YAAY;GACnB,MAAM,SAAS,MAAM,qBAAoC,WAAW,gBAAgB,UAAU;AAC9F,OAAI,CAAC,OAAO,QAAQ;AAClB,YAAQ,MAAM,mGAAmG;AACjH,WAAO;;AAET,UAAO;;EAET,MAAM,OAAO,QAAQ,WAAW;AAC9B,OAAI,OAAO,WAAW,EAAG;AACzB,SAAM,gBAAgB,QAAQ,aAAa,OAAO,CAAC;;EAEtD,CAAC;;;;;AAMJ,SAAgB,yBAAyB,WAAoC;AAC3E,QAAO,mBAAmB;EAAE,GAAG;EAAW,MAAM;EAAU,CAAC;;;;;AAM7D,eAAsB,cAAc,OAAkB,QAAsC;AAC1F,OAAM,mBAAmB,CAAC,MAAM,EAAE,OAAO;;;;;AAM3C,eAAsB,mBAAmB,QAAqB,QAAsC;AAClG,KAAI,OAAO,WAAW,EAAG;AACzB,OAAM,gBAAgB,QAAQ,aAAa,OAAO,CAAC;;;;;AAMrD,eAAsB,oBAAoB,OAAkB,QAAsC;AAChG,OAAM,yBAAyB,CAAC,MAAM,EAAE,OAAO;;;;;AAMjD,eAAsB,yBAAyB,QAAqB,QAAsC;AACxG,KAAI,OAAO,WAAW,EAAG;AACzB,OAAM,SAAS;EACb,KAAK,GAAG,YAAY,OAAO,CAAC;EAC5B,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU;GACnB,SAAS,OAAO;GAChB,OAAO,OAAO,KAAI,UAAS,eAAe,OAAO,OAAO,CAAC;GAC1D,CAAC;EACF,SAAS,OAAO,WAAW;EAC3B,SAAS,OAAO;EAChB,OAAO;EACR,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentry.d.mts","names":[],"sources":["../../src/adapters/sentry.ts"],"mappings":";;UAOiB,YAAA;;EAEf,GAAA;EAFe;EAIf,WAAA;;EAEA,OAAA;EAJA;EAMA,IAAA,GAAO,MAAA;EAFP;EAIA,OAAA;EAFO;EAIP,OAAA;AAAA;;UAIe,oBAAA;EACf,KAAA;EACA,IAAA;AAAA;;UAIe,SAAA;EACf,SAAA;EACA,QAAA;EACA,KAAA;EACA,IAAA;EACA,eAAA;EACA,UAAA,GAAa,MAAA,SAAe,oBAAA;AAAA;AAAA,iBA2Fd,WAAA,CAAY,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,YAAA,GAAe,SAAA;;;;;;;AAArE;;;;;;;;;;;;;;;AAiGA;;iBAAgB,iBAAA,CAAkB,SAAA,GAAY,OAAA,CAAQ,YAAA,KAAa,GAAA,EAAd,YAAA,GAAc,YAAA,OAAA,OAAA;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"sentry.d.mts","names":[],"sources":["../../src/adapters/sentry.ts"],"mappings":";;UAOiB,YAAA;;EAEf,GAAA;EAFe;EAIf,WAAA;;EAEA,OAAA;EAJA;EAMA,IAAA,GAAO,MAAA;EAFP;EAIA,OAAA;EAFO;EAIP,OAAA;AAAA;;UAIe,oBAAA;EACf,KAAA;EACA,IAAA;AAAA;;UAIe,SAAA;EACf,SAAA;EACA,QAAA;EACA,KAAA;EACA,IAAA;EACA,eAAA;EACA,UAAA,GAAa,MAAA,SAAe,oBAAA;AAAA;AAAA,iBA2Fd,WAAA,CAAY,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,YAAA,GAAe,SAAA;;;;;;;AAArE;;;;;;;;;;;;;;;AAiGA;;iBAAgB,iBAAA,CAAkB,SAAA,GAAY,OAAA,CAAQ,YAAA,KAAa,GAAA,EAAd,YAAA,GAAc,YAAA,OAAA,OAAA;;;;;;;;;;;iBAoC7C,YAAA,CAAa,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,YAAA,GAAe,OAAA;;;;;;AAA5E;;;;;iBAcsB,iBAAA,CAAkB,MAAA,EAAQ,SAAA,IAAa,MAAA,EAAQ,YAAA,GAAe,OAAA"}
|
package/dist/adapters/sentry.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as
|
|
3
|
-
import { t as OTEL_SEVERITY_NUMBER } from "../_severity-CQijvfhU.mjs";
|
|
1
|
+
import { a as resolveAdapterConfig, n as defineHttpDrain, r as httpPost } from "../drain-ByWUeOQC.mjs";
|
|
2
|
+
import { t as OTEL_SEVERITY_NUMBER } from "../severity-BYWZ96Sb.mjs";
|
|
4
3
|
//#region src/adapters/sentry.ts
|
|
5
4
|
const SENTRY_FIELDS = [
|
|
6
5
|
{
|
|
@@ -165,7 +164,7 @@ function buildEnvelopeBody(logs, dsn) {
|
|
|
165
164
|
* ```
|
|
166
165
|
*/
|
|
167
166
|
function createSentryDrain(overrides) {
|
|
168
|
-
return
|
|
167
|
+
return defineHttpDrain({
|
|
169
168
|
name: "sentry",
|
|
170
169
|
resolve: async () => {
|
|
171
170
|
const config = await resolveAdapterConfig("sentry", SENTRY_FIELDS, overrides);
|
|
@@ -175,7 +174,18 @@ function createSentryDrain(overrides) {
|
|
|
175
174
|
}
|
|
176
175
|
return config;
|
|
177
176
|
},
|
|
178
|
-
|
|
177
|
+
encode: (events, config) => {
|
|
178
|
+
const { url, authHeader } = getSentryEnvelopeUrl(config.dsn);
|
|
179
|
+
const logs = events.map((event) => toSentryLog(event, config));
|
|
180
|
+
return {
|
|
181
|
+
url,
|
|
182
|
+
headers: {
|
|
183
|
+
"Content-Type": "application/x-sentry-envelope",
|
|
184
|
+
"X-Sentry-Auth": authHeader
|
|
185
|
+
},
|
|
186
|
+
body: buildEnvelopeBody(logs, config.dsn)
|
|
187
|
+
};
|
|
188
|
+
}
|
|
179
189
|
});
|
|
180
190
|
}
|
|
181
191
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentry.mjs","names":[],"sources":["../../src/adapters/sentry.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 { OTEL_SEVERITY_NUMBER } from './_severity'\n\nexport interface SentryConfig {\n /** Sentry DSN */\n dsn: string\n /** Environment override (defaults to event.environment) */\n environment?: string\n /** Release version override (defaults to event.version) */\n release?: string\n /** Additional tags to attach as attributes */\n tags?: Record<string, string>\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\n/** Sentry Log attribute value with type annotation */\nexport interface SentryAttributeValue {\n value: string | number | boolean\n type: 'string' | 'integer' | 'double' | 'boolean'\n}\n\n/** Sentry Structured Log payload */\nexport interface SentryLog {\n timestamp: number\n trace_id: string\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'\n body: string\n severity_number: number\n attributes?: Record<string, SentryAttributeValue>\n}\n\ninterface SentryDsnParts {\n publicKey: string\n secretKey?: string\n projectId: string\n origin: string\n basePath: string\n}\n\nconst SENTRY_FIELDS: ConfigField<SentryConfig>[] = [\n { key: 'dsn', env: ['NUXT_SENTRY_DSN', 'SENTRY_DSN'] },\n { key: 'environment', env: ['NUXT_SENTRY_ENVIRONMENT', 'SENTRY_ENVIRONMENT'] },\n { key: 'release', env: ['NUXT_SENTRY_RELEASE', 'SENTRY_RELEASE'] },\n { key: 'tags' },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nfunction parseSentryDsn(dsn: string): SentryDsnParts {\n const url = new URL(dsn)\n const publicKey = url.username\n if (!publicKey) {\n throw new Error('Invalid Sentry DSN: missing public key')\n }\n\n const secretKey = url.password || undefined\n\n const pathParts = url.pathname.split('/').filter(Boolean)\n const projectId = pathParts.pop()\n if (!projectId) {\n throw new Error('Invalid Sentry DSN: missing project ID')\n }\n\n const basePath = pathParts.length > 0 ? `/${pathParts.join('/')}` : ''\n\n return {\n publicKey,\n secretKey,\n projectId,\n origin: `${url.protocol}//${url.host}`,\n basePath,\n }\n}\n\nfunction getSentryEnvelopeUrl(dsn: string): { url: string, authHeader: string } {\n const { publicKey, secretKey, projectId, origin, basePath } = parseSentryDsn(dsn)\n const url = `${origin}${basePath}/api/${projectId}/envelope/`\n let authHeader = `Sentry sentry_version=7, sentry_key=${publicKey}, sentry_client=evlog`\n if (secretKey) {\n authHeader += `, sentry_secret=${secretKey}`\n }\n return { url, authHeader }\n}\n\nfunction createTraceId(): string {\n if (typeof globalThis.crypto?.randomUUID === 'function') {\n return globalThis.crypto.randomUUID().replace(/-/g, '')\n }\n\n return Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join('')\n}\n\nfunction getFirstStringValue(event: WideEvent, keys: string[]): string | undefined {\n for (const key of keys) {\n const value = event[key]\n if (typeof value === 'string' && value.length > 0) return value\n }\n return undefined\n}\n\nfunction toAttributeValue(value: unknown): SentryAttributeValue | undefined {\n if (value === null || value === undefined) {\n return undefined\n }\n if (typeof value === 'string') {\n return { value, type: 'string' }\n }\n if (typeof value === 'boolean') {\n return { value, type: 'boolean' }\n }\n if (typeof value === 'number') {\n if (Number.isInteger(value)) {\n return { value, type: 'integer' }\n }\n return { value, type: 'double' }\n }\n return { value: JSON.stringify(value), type: 'string' }\n}\n\nexport function toSentryLog(event: WideEvent, config: SentryConfig): SentryLog {\n const { timestamp, level, service, environment, version, ...rest } = event\n\n const body = getFirstStringValue(event, ['message', 'action', 'path'])\n ?? 'evlog wide event'\n\n const traceId = (typeof event.traceId === 'string' && event.traceId.length > 0)\n ? event.traceId\n : createTraceId()\n\n const attributes: Record<string, SentryAttributeValue> = {}\n\n const env = config.environment ?? environment\n if (env) {\n attributes['sentry.environment'] = { value: env, type: 'string' }\n }\n\n const rel = config.release ?? version\n if (typeof rel === 'string' && rel.length > 0) {\n attributes['sentry.release'] = { value: rel, type: 'string' }\n }\n\n attributes['service'] = { value: service, type: 'string' }\n\n if (config.tags) {\n for (const [key, value] of Object.entries(config.tags)) {\n attributes[key] = { value, type: 'string' }\n }\n }\n\n for (const [key, value] of Object.entries(rest)) {\n if (key === 'traceId' || key === 'spanId') continue\n if (value === undefined || value === null) continue\n const attr = toAttributeValue(value)\n if (attr) {\n attributes[key] = attr\n }\n }\n\n return {\n timestamp: new Date(timestamp).getTime() / 1000,\n trace_id: traceId,\n level: level as SentryLog['level'],\n body,\n severity_number: OTEL_SEVERITY_NUMBER[level] ?? 9,\n attributes,\n }\n}\n\n/**\n * Build the Sentry Envelope body for a list of logs.\n *\n * Envelope format (line-delimited):\n * - Line 1: Envelope headers (dsn, sent_at)\n * - Line 2: Item header (type: log, item_count, content_type)\n * - Line 3: Item payload ({\"items\": [...]})\n */\nfunction buildEnvelopeBody(logs: SentryLog[], dsn: string): string {\n const envelopeHeader = JSON.stringify({\n dsn,\n sent_at: new Date().toISOString(),\n })\n\n const itemHeader = JSON.stringify({\n type: 'log',\n item_count: logs.length,\n content_type: 'application/vnd.sentry.items.log+json',\n })\n\n const itemPayload = JSON.stringify({ items: logs })\n\n return `${envelopeHeader}\\n${itemHeader}\\n${itemPayload}\\n`\n}\n\n/**\n * Create a drain function for sending logs to Sentry.\n *\n * Sends wide events as Sentry Structured Logs, visible in Explore > Logs\n * in the Sentry dashboard.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createSentryDrain()\n * 2. runtimeConfig.evlog.sentry\n * 3. runtimeConfig.sentry\n * 4. Environment variables: NUXT_SENTRY_*, SENTRY_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_SENTRY_DSN env var\n * nitroApp.hooks.hook('evlog:drain', createSentryDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createSentryDrain({\n * dsn: 'https://public@o0.ingest.sentry.io/123',\n * }))\n * ```\n */\nexport function createSentryDrain(overrides?: Partial<SentryConfig>) {\n return defineDrain<SentryConfig>({\n name: 'sentry',\n resolve: async () => {\n const config = await resolveAdapterConfig<SentryConfig>('sentry', SENTRY_FIELDS, overrides)\n if (!config.dsn) {\n console.error('[evlog/sentry] Missing DSN. Set NUXT_SENTRY_DSN/SENTRY_DSN env var or pass to createSentryDrain()')\n return null\n }\n return config as SentryConfig\n },\n send: sendBatchToSentry,\n })\n}\n\n/**\n * Send a single event to Sentry as a structured log.\n *\n * @example\n * ```ts\n * await sendToSentry(event, {\n * dsn: process.env.SENTRY_DSN!,\n * })\n * ```\n */\nexport async function sendToSentry(event: WideEvent, config: SentryConfig): Promise<void> {\n await sendBatchToSentry([event], config)\n}\n\n/**\n * Send a batch of events to Sentry as structured logs via the Envelope endpoint.\n *\n * @example\n * ```ts\n * await sendBatchToSentry(events, {\n * dsn: process.env.SENTRY_DSN!,\n * })\n * ```\n */\nexport async function sendBatchToSentry(events: WideEvent[], config: SentryConfig): Promise<void> {\n if (events.length === 0) return\n\n const { url, authHeader } = getSentryEnvelopeUrl(config.dsn)\n\n const logs = events.map(event => toSentryLog(event, config))\n const body = buildEnvelopeBody(logs, config.dsn)\n\n await httpPost({\n url,\n headers: {\n 'Content-Type': 'application/x-sentry-envelope',\n 'X-Sentry-Auth': authHeader,\n },\n body,\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Sentry',\n })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sentry.mjs","names":[],"sources":["../../src/adapters/sentry.ts"],"sourcesContent":["import type { WideEvent } from '../types'\nimport type { ConfigField } from '../shared/config'\nimport { resolveAdapterConfig } from '../shared/config'\nimport { defineHttpDrain } from '../shared/drain'\nimport { httpPost } from '../shared/http'\nimport { OTEL_SEVERITY_NUMBER } from '../shared/severity'\n\nexport interface SentryConfig {\n /** Sentry DSN */\n dsn: string\n /** Environment override (defaults to event.environment) */\n environment?: string\n /** Release version override (defaults to event.version) */\n release?: string\n /** Additional tags to attach as attributes */\n tags?: Record<string, string>\n /** Request timeout in milliseconds. Default: 5000 */\n timeout?: number\n /** Number of retry attempts on transient failures. Default: 2 */\n retries?: number\n}\n\n/** Sentry Log attribute value with type annotation */\nexport interface SentryAttributeValue {\n value: string | number | boolean\n type: 'string' | 'integer' | 'double' | 'boolean'\n}\n\n/** Sentry Structured Log payload */\nexport interface SentryLog {\n timestamp: number\n trace_id: string\n level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'\n body: string\n severity_number: number\n attributes?: Record<string, SentryAttributeValue>\n}\n\ninterface SentryDsnParts {\n publicKey: string\n secretKey?: string\n projectId: string\n origin: string\n basePath: string\n}\n\nconst SENTRY_FIELDS: ConfigField<SentryConfig>[] = [\n { key: 'dsn', env: ['NUXT_SENTRY_DSN', 'SENTRY_DSN'] },\n { key: 'environment', env: ['NUXT_SENTRY_ENVIRONMENT', 'SENTRY_ENVIRONMENT'] },\n { key: 'release', env: ['NUXT_SENTRY_RELEASE', 'SENTRY_RELEASE'] },\n { key: 'tags' },\n { key: 'timeout' },\n { key: 'retries' },\n]\n\nfunction parseSentryDsn(dsn: string): SentryDsnParts {\n const url = new URL(dsn)\n const publicKey = url.username\n if (!publicKey) {\n throw new Error('Invalid Sentry DSN: missing public key')\n }\n\n const secretKey = url.password || undefined\n\n const pathParts = url.pathname.split('/').filter(Boolean)\n const projectId = pathParts.pop()\n if (!projectId) {\n throw new Error('Invalid Sentry DSN: missing project ID')\n }\n\n const basePath = pathParts.length > 0 ? `/${pathParts.join('/')}` : ''\n\n return {\n publicKey,\n secretKey,\n projectId,\n origin: `${url.protocol}//${url.host}`,\n basePath,\n }\n}\n\nfunction getSentryEnvelopeUrl(dsn: string): { url: string, authHeader: string } {\n const { publicKey, secretKey, projectId, origin, basePath } = parseSentryDsn(dsn)\n const url = `${origin}${basePath}/api/${projectId}/envelope/`\n let authHeader = `Sentry sentry_version=7, sentry_key=${publicKey}, sentry_client=evlog`\n if (secretKey) {\n authHeader += `, sentry_secret=${secretKey}`\n }\n return { url, authHeader }\n}\n\nfunction createTraceId(): string {\n if (typeof globalThis.crypto?.randomUUID === 'function') {\n return globalThis.crypto.randomUUID().replace(/-/g, '')\n }\n\n return Array.from({ length: 32 }, () => Math.floor(Math.random() * 16).toString(16)).join('')\n}\n\nfunction getFirstStringValue(event: WideEvent, keys: string[]): string | undefined {\n for (const key of keys) {\n const value = event[key]\n if (typeof value === 'string' && value.length > 0) return value\n }\n return undefined\n}\n\nfunction toAttributeValue(value: unknown): SentryAttributeValue | undefined {\n if (value === null || value === undefined) {\n return undefined\n }\n if (typeof value === 'string') {\n return { value, type: 'string' }\n }\n if (typeof value === 'boolean') {\n return { value, type: 'boolean' }\n }\n if (typeof value === 'number') {\n if (Number.isInteger(value)) {\n return { value, type: 'integer' }\n }\n return { value, type: 'double' }\n }\n return { value: JSON.stringify(value), type: 'string' }\n}\n\nexport function toSentryLog(event: WideEvent, config: SentryConfig): SentryLog {\n const { timestamp, level, service, environment, version, ...rest } = event\n\n const body = getFirstStringValue(event, ['message', 'action', 'path'])\n ?? 'evlog wide event'\n\n const traceId = (typeof event.traceId === 'string' && event.traceId.length > 0)\n ? event.traceId\n : createTraceId()\n\n const attributes: Record<string, SentryAttributeValue> = {}\n\n const env = config.environment ?? environment\n if (env) {\n attributes['sentry.environment'] = { value: env, type: 'string' }\n }\n\n const rel = config.release ?? version\n if (typeof rel === 'string' && rel.length > 0) {\n attributes['sentry.release'] = { value: rel, type: 'string' }\n }\n\n attributes['service'] = { value: service, type: 'string' }\n\n if (config.tags) {\n for (const [key, value] of Object.entries(config.tags)) {\n attributes[key] = { value, type: 'string' }\n }\n }\n\n for (const [key, value] of Object.entries(rest)) {\n if (key === 'traceId' || key === 'spanId') continue\n if (value === undefined || value === null) continue\n const attr = toAttributeValue(value)\n if (attr) {\n attributes[key] = attr\n }\n }\n\n return {\n timestamp: new Date(timestamp).getTime() / 1000,\n trace_id: traceId,\n level: level as SentryLog['level'],\n body,\n severity_number: OTEL_SEVERITY_NUMBER[level] ?? 9,\n attributes,\n }\n}\n\n/**\n * Build the Sentry Envelope body for a list of logs.\n *\n * Envelope format (line-delimited):\n * - Line 1: Envelope headers (dsn, sent_at)\n * - Line 2: Item header (type: log, item_count, content_type)\n * - Line 3: Item payload ({\"items\": [...]})\n */\nfunction buildEnvelopeBody(logs: SentryLog[], dsn: string): string {\n const envelopeHeader = JSON.stringify({\n dsn,\n sent_at: new Date().toISOString(),\n })\n\n const itemHeader = JSON.stringify({\n type: 'log',\n item_count: logs.length,\n content_type: 'application/vnd.sentry.items.log+json',\n })\n\n const itemPayload = JSON.stringify({ items: logs })\n\n return `${envelopeHeader}\\n${itemHeader}\\n${itemPayload}\\n`\n}\n\n/**\n * Create a drain function for sending logs to Sentry.\n *\n * Sends wide events as Sentry Structured Logs, visible in Explore > Logs\n * in the Sentry dashboard.\n *\n * Configuration priority (highest to lowest):\n * 1. Overrides passed to createSentryDrain()\n * 2. runtimeConfig.evlog.sentry\n * 3. runtimeConfig.sentry\n * 4. Environment variables: NUXT_SENTRY_*, SENTRY_*\n *\n * @example\n * ```ts\n * // Zero config - just set NUXT_SENTRY_DSN env var\n * nitroApp.hooks.hook('evlog:drain', createSentryDrain())\n *\n * // With overrides\n * nitroApp.hooks.hook('evlog:drain', createSentryDrain({\n * dsn: 'https://public@o0.ingest.sentry.io/123',\n * }))\n * ```\n */\nexport function createSentryDrain(overrides?: Partial<SentryConfig>) {\n return defineHttpDrain<SentryConfig>({\n name: 'sentry',\n resolve: async () => {\n const config = await resolveAdapterConfig<SentryConfig>('sentry', SENTRY_FIELDS, overrides)\n if (!config.dsn) {\n console.error('[evlog/sentry] Missing DSN. Set NUXT_SENTRY_DSN/SENTRY_DSN env var or pass to createSentryDrain()')\n return null\n }\n return config as SentryConfig\n },\n encode: (events, config) => {\n const { url, authHeader } = getSentryEnvelopeUrl(config.dsn)\n const logs = events.map(event => toSentryLog(event, config))\n return {\n url,\n headers: {\n 'Content-Type': 'application/x-sentry-envelope',\n 'X-Sentry-Auth': authHeader,\n },\n body: buildEnvelopeBody(logs, config.dsn),\n }\n },\n })\n}\n\n/**\n * Send a single event to Sentry as a structured log.\n *\n * @example\n * ```ts\n * await sendToSentry(event, {\n * dsn: process.env.SENTRY_DSN!,\n * })\n * ```\n */\nexport async function sendToSentry(event: WideEvent, config: SentryConfig): Promise<void> {\n await sendBatchToSentry([event], config)\n}\n\n/**\n * Send a batch of events to Sentry as structured logs via the Envelope endpoint.\n *\n * @example\n * ```ts\n * await sendBatchToSentry(events, {\n * dsn: process.env.SENTRY_DSN!,\n * })\n * ```\n */\nexport async function sendBatchToSentry(events: WideEvent[], config: SentryConfig): Promise<void> {\n if (events.length === 0) return\n\n const { url, authHeader } = getSentryEnvelopeUrl(config.dsn)\n\n const logs = events.map(event => toSentryLog(event, config))\n const body = buildEnvelopeBody(logs, config.dsn)\n\n await httpPost({\n url,\n headers: {\n 'Content-Type': 'application/x-sentry-envelope',\n 'X-Sentry-Auth': authHeader,\n },\n body,\n timeout: config.timeout ?? 5000,\n retries: config.retries,\n label: 'Sentry',\n })\n}\n"],"mappings":"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"}
|
package/dist/ai/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { colors, cssColors, detectEnvironment, escapeFormatString, formatDuration, getConsoleMethod, getCssLevelColor, getLevelColor, isBrowser, isDev, isLevelEnabled, matchesPattern } from "./utils.mjs";
|
|
2
|
+
import { r as getHeader$1 } from "./headers-CU-QqnYg.mjs";
|
|
2
3
|
//#region src/redact.ts
|
|
3
4
|
const DEFAULT_REPLACEMENT = "[REDACTED]";
|
|
4
5
|
/**
|
|
@@ -188,6 +189,130 @@ function normalizeRedactConfig(raw) {
|
|
|
188
189
|
return resolveRedactConfig(config);
|
|
189
190
|
}
|
|
190
191
|
//#endregion
|
|
192
|
+
//#region src/shared/plugin.ts
|
|
193
|
+
/** Identity helper preserving plugin type inference. */
|
|
194
|
+
function definePlugin(plugin) {
|
|
195
|
+
return plugin;
|
|
196
|
+
}
|
|
197
|
+
/** Wrap a standalone drain callback as an {@link EvlogPlugin}. */
|
|
198
|
+
function drainPlugin(name, drain) {
|
|
199
|
+
return {
|
|
200
|
+
name,
|
|
201
|
+
drain
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/** Wrap a standalone enricher callback as an {@link EvlogPlugin}. */
|
|
205
|
+
function enricherPlugin(name, enrich) {
|
|
206
|
+
return {
|
|
207
|
+
name,
|
|
208
|
+
enrich
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function logPluginError(name, hook, err) {
|
|
212
|
+
console.error(`[evlog/${name}] ${hook} failed:`, err);
|
|
213
|
+
}
|
|
214
|
+
/** De-duplicates by `name` — last registration wins. */
|
|
215
|
+
function createPluginRunner(plugins = []) {
|
|
216
|
+
const byName = /* @__PURE__ */ new Map();
|
|
217
|
+
for (const plugin of plugins) byName.set(plugin.name, plugin);
|
|
218
|
+
const list = Array.from(byName.values());
|
|
219
|
+
return {
|
|
220
|
+
plugins: list,
|
|
221
|
+
hasEnrich: list.some((p) => typeof p.enrich === "function"),
|
|
222
|
+
hasDrain: list.some((p) => typeof p.drain === "function"),
|
|
223
|
+
hasKeep: list.some((p) => typeof p.keep === "function"),
|
|
224
|
+
hasRequestLifecycle: list.some((p) => typeof p.onRequestStart === "function" || typeof p.onRequestFinish === "function"),
|
|
225
|
+
hasClientLog: list.some((p) => typeof p.onClientLog === "function"),
|
|
226
|
+
hasExtendLogger: list.some((p) => typeof p.extendLogger === "function"),
|
|
227
|
+
applyExtendLogger(logger) {
|
|
228
|
+
for (const plugin of list) {
|
|
229
|
+
if (!plugin.extendLogger) continue;
|
|
230
|
+
try {
|
|
231
|
+
plugin.extendLogger(logger);
|
|
232
|
+
} catch (err) {
|
|
233
|
+
logPluginError(plugin.name, "extendLogger", err);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
runOnRequestStart(ctx) {
|
|
238
|
+
for (const plugin of list) {
|
|
239
|
+
if (!plugin.onRequestStart) continue;
|
|
240
|
+
try {
|
|
241
|
+
plugin.onRequestStart(ctx);
|
|
242
|
+
} catch (err) {
|
|
243
|
+
logPluginError(plugin.name, "onRequestStart", err);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
runOnRequestFinish(ctx) {
|
|
248
|
+
for (const plugin of list) {
|
|
249
|
+
if (!plugin.onRequestFinish) continue;
|
|
250
|
+
try {
|
|
251
|
+
plugin.onRequestFinish(ctx);
|
|
252
|
+
} catch (err) {
|
|
253
|
+
logPluginError(plugin.name, "onRequestFinish", err);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
async runEnrich(ctx) {
|
|
258
|
+
for (const plugin of list) {
|
|
259
|
+
if (!plugin.enrich) continue;
|
|
260
|
+
try {
|
|
261
|
+
await plugin.enrich(ctx);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
logPluginError(plugin.name, "enrich", err);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
async runDrain(ctx) {
|
|
268
|
+
const drains = list.filter((p) => typeof p.drain === "function");
|
|
269
|
+
if (drains.length === 0) return;
|
|
270
|
+
await Promise.allSettled(drains.map(async (plugin) => {
|
|
271
|
+
try {
|
|
272
|
+
await plugin.drain(ctx);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
logPluginError(plugin.name, "drain", err);
|
|
275
|
+
}
|
|
276
|
+
}));
|
|
277
|
+
},
|
|
278
|
+
async runKeep(ctx) {
|
|
279
|
+
for (const plugin of list) {
|
|
280
|
+
if (!plugin.keep) continue;
|
|
281
|
+
try {
|
|
282
|
+
await plugin.keep(ctx);
|
|
283
|
+
} catch (err) {
|
|
284
|
+
logPluginError(plugin.name, "keep", err);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
runOnClientLog(ctx) {
|
|
289
|
+
for (const plugin of list) {
|
|
290
|
+
if (!plugin.onClientLog) continue;
|
|
291
|
+
try {
|
|
292
|
+
plugin.onClientLog(ctx);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
logPluginError(plugin.name, "onClientLog", err);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
async runSetup(ctx) {
|
|
299
|
+
for (const plugin of list) {
|
|
300
|
+
if (!plugin.setup) continue;
|
|
301
|
+
try {
|
|
302
|
+
await plugin.setup(ctx);
|
|
303
|
+
} catch (err) {
|
|
304
|
+
logPluginError(plugin.name, "setup", err);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
const emptyRunner = createPluginRunner([]);
|
|
311
|
+
/** Shared no-op runner used when no plugins are registered. */
|
|
312
|
+
function getEmptyPluginRunner() {
|
|
313
|
+
return emptyRunner;
|
|
314
|
+
}
|
|
315
|
+
//#endregion
|
|
191
316
|
//#region src/logger.ts
|
|
192
317
|
function isPlainObject(val) {
|
|
193
318
|
return val !== null && typeof val === "object" && !Array.isArray(val);
|
|
@@ -226,6 +351,7 @@ let globalSilent = false;
|
|
|
226
351
|
/** Minimum level for the global `log` API only (`ownsEvent === false`). Default: all levels. */
|
|
227
352
|
let globalMinLevel = "debug";
|
|
228
353
|
let _locked = false;
|
|
354
|
+
let globalPluginRunner = getEmptyPluginRunner();
|
|
229
355
|
/**
|
|
230
356
|
* Initialize the logger with configuration.
|
|
231
357
|
* Call this once at application startup.
|
|
@@ -247,7 +373,18 @@ function initLogger(config = {}) {
|
|
|
247
373
|
globalRedact = resolveRedactConfig(config.redact ?? !isDev());
|
|
248
374
|
globalSilent = config.silent ?? false;
|
|
249
375
|
globalMinLevel = config.minLevel ?? "debug";
|
|
250
|
-
|
|
376
|
+
globalPluginRunner = config.plugins?.length ? createPluginRunner(config.plugins) : getEmptyPluginRunner();
|
|
377
|
+
if (globalPluginRunner.plugins.length > 0) globalPluginRunner.runSetup({ env: { ...globalEnv } });
|
|
378
|
+
const hasAnyDrain = !!globalDrain || globalPluginRunner.hasDrain;
|
|
379
|
+
if (globalSilent && !hasAnyDrain && !config._suppressDrainWarning) console.warn("[evlog] silent mode is enabled but no drain is configured. Events will be built and sampled but not output anywhere. Set a drain via initLogger({ drain }) or a framework hook (evlog:drain).");
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* @internal Get the globally registered plugin runner.
|
|
383
|
+
* Used by framework middleware so plugins also fire on routes that pre-date
|
|
384
|
+
* the middleware-level options.
|
|
385
|
+
*/
|
|
386
|
+
function getGlobalPluginRunner() {
|
|
387
|
+
return globalPluginRunner;
|
|
251
388
|
}
|
|
252
389
|
/**
|
|
253
390
|
* Check if logging is globally enabled.
|
|
@@ -331,11 +468,17 @@ function emitWideEvent(level, event, options = {}) {
|
|
|
331
468
|
if (!globalSilent) if (globalPretty) prettyPrintWideEvent(formatted);
|
|
332
469
|
else if (globalStringify) console[getConsoleMethod(level)](JSON.stringify(formatted));
|
|
333
470
|
else console[getConsoleMethod(level)](formatted);
|
|
334
|
-
if (
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
471
|
+
if (!deferDrain) {
|
|
472
|
+
const drainPromises = [];
|
|
473
|
+
if (globalDrain) drainPromises.push((async () => {
|
|
474
|
+
try {
|
|
475
|
+
await globalDrain({ event: formatted });
|
|
476
|
+
} catch (err) {
|
|
477
|
+
console.error("[evlog] drain failed:", err);
|
|
478
|
+
}
|
|
479
|
+
})());
|
|
480
|
+
if (globalPluginRunner.hasDrain) drainPromises.push(globalPluginRunner.runDrain({ event: formatted }));
|
|
481
|
+
if (drainPromises.length > 0 && waitUntil) waitUntil(Promise.all(drainPromises));
|
|
339
482
|
}
|
|
340
483
|
return formatted;
|
|
341
484
|
}
|
|
@@ -1262,11 +1405,7 @@ function auditEnricher(options = {}) {
|
|
|
1262
1405
|
};
|
|
1263
1406
|
}
|
|
1264
1407
|
function getHeader(headers, name) {
|
|
1265
|
-
|
|
1266
|
-
if (headers[name] !== void 0) return headers[name];
|
|
1267
|
-
const lower = name.toLowerCase();
|
|
1268
|
-
if (headers[lower] !== void 0) return headers[lower];
|
|
1269
|
-
for (const [k, v] of Object.entries(headers)) if (k.toLowerCase() === lower) return v;
|
|
1408
|
+
return getHeader$1(headers, name);
|
|
1270
1409
|
}
|
|
1271
1410
|
/**
|
|
1272
1411
|
* Wrap any drain so it only receives events that carry an `audit` field.
|
|
@@ -1478,6 +1617,6 @@ const auditRedactPreset = { paths: [
|
|
|
1478
1617
|
"audit.context.headers.cookie"
|
|
1479
1618
|
] };
|
|
1480
1619
|
//#endregion
|
|
1481
|
-
export {
|
|
1620
|
+
export { normalizeRedactConfig as A, lockLogger as C, drainPlugin as D, definePlugin as E, resolveRedactConfig as M, enricherPlugin as O, isLoggerLocked as S, createPluginRunner as T, getEnvironment as _, auditEnricher as a, initLogger as b, buildAuditFields as c, signed as d, withAudit as f, createRequestLogger as g, createLogger as h, auditDiff as i, redactEvent as j, getEmptyPluginRunner as k, defineAuditAction as l, _log as m, AuditDeniedError as n, auditOnly as o, withAuditMethods as p, audit as r, auditRedactPreset as s, AUDIT_SCHEMA_VERSION as t, mockAudit as u, getGlobalDrain as v, shouldKeep as w, isEnabled as x, getGlobalPluginRunner as y };
|
|
1482
1621
|
|
|
1483
|
-
//# sourceMappingURL=audit
|
|
1622
|
+
//# sourceMappingURL=audit--n0QRR2Y.mjs.map
|