evlog 1.6.0 → 1.8.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 +96 -0
- package/dist/_http-DVDwNag0.mjs +76 -0
- package/dist/_http-DVDwNag0.mjs.map +1 -0
- package/dist/_severity-CXfyvxQi.mjs +17 -0
- package/dist/_severity-CXfyvxQi.mjs.map +1 -0
- package/dist/adapters/axiom.d.mts +17 -15
- package/dist/adapters/axiom.d.mts.map +1 -0
- package/dist/adapters/axiom.mjs +91 -50
- package/dist/adapters/axiom.mjs.map +1 -0
- package/dist/adapters/better-stack.d.mts +63 -0
- package/dist/adapters/better-stack.d.mts.map +1 -0
- package/dist/adapters/better-stack.mjs +98 -0
- package/dist/adapters/better-stack.mjs.map +1 -0
- package/dist/adapters/otlp.d.mts +32 -30
- package/dist/adapters/otlp.d.mts.map +1 -0
- package/dist/adapters/otlp.mjs +181 -181
- package/dist/adapters/otlp.mjs.map +1 -0
- package/dist/adapters/posthog.d.mts +54 -19
- package/dist/adapters/posthog.d.mts.map +1 -0
- package/dist/adapters/posthog.mjs +156 -63
- package/dist/adapters/posthog.mjs.map +1 -0
- package/dist/adapters/sentry.d.mts +25 -23
- package/dist/adapters/sentry.d.mts.map +1 -0
- package/dist/adapters/sentry.mjs +198 -153
- package/dist/adapters/sentry.mjs.map +1 -0
- package/dist/browser.d.mts +63 -0
- package/dist/browser.d.mts.map +1 -0
- package/dist/browser.mjs +95 -0
- package/dist/browser.mjs.map +1 -0
- package/dist/enrichers.d.mts +74 -0
- package/dist/enrichers.d.mts.map +1 -0
- package/dist/enrichers.mjs +172 -0
- package/dist/enrichers.mjs.map +1 -0
- package/dist/error.d.mts +24 -22
- package/dist/error.d.mts.map +1 -0
- package/dist/error.mjs +107 -76
- package/dist/error.mjs.map +1 -0
- package/dist/index.d.mts +6 -5
- package/dist/index.mjs +6 -5
- package/dist/logger.d.mts +11 -5
- package/dist/logger.d.mts.map +1 -0
- package/dist/logger.mjs +255 -186
- package/dist/logger.mjs.map +1 -0
- package/dist/nitro/errorHandler.d.mts +4 -2
- package/dist/nitro/errorHandler.d.mts.map +1 -0
- package/dist/nitro/errorHandler.mjs +38 -38
- package/dist/nitro/errorHandler.mjs.map +1 -0
- package/dist/nitro/module.d.mts +11 -0
- package/dist/nitro/module.d.mts.map +1 -0
- package/dist/nitro/module.mjs +23 -0
- package/dist/nitro/module.mjs.map +1 -0
- package/dist/nitro/plugin.d.mts +4 -2
- package/dist/nitro/plugin.d.mts.map +1 -0
- package/dist/nitro/plugin.mjs +135 -140
- package/dist/nitro/plugin.mjs.map +1 -0
- package/dist/nitro/v3/errorHandler.d.mts +24 -0
- package/dist/nitro/v3/errorHandler.d.mts.map +1 -0
- package/dist/nitro/v3/errorHandler.mjs +36 -0
- package/dist/nitro/v3/errorHandler.mjs.map +1 -0
- package/dist/nitro/v3/index.d.mts +4 -0
- package/dist/nitro/v3/index.mjs +4 -0
- package/dist/nitro/v3/module.d.mts +10 -0
- package/dist/nitro/v3/module.d.mts.map +1 -0
- package/dist/nitro/v3/module.mjs +22 -0
- package/dist/nitro/v3/module.mjs.map +1 -0
- package/dist/nitro/v3/plugin.d.mts +14 -0
- package/dist/nitro/v3/plugin.d.mts.map +1 -0
- package/dist/nitro/v3/plugin.mjs +157 -0
- package/dist/nitro/v3/plugin.mjs.map +1 -0
- package/dist/nitro/v3/useLogger.d.mts +24 -0
- package/dist/nitro/v3/useLogger.d.mts.map +1 -0
- package/dist/nitro/v3/useLogger.mjs +27 -0
- package/dist/nitro/v3/useLogger.mjs.map +1 -0
- package/dist/nitro-D57TWGyN.mjs +73 -0
- package/dist/nitro-D57TWGyN.mjs.map +1 -0
- package/dist/nitro-D81NBVPi.d.mts +42 -0
- package/dist/nitro-D81NBVPi.d.mts.map +1 -0
- package/dist/nuxt/module.d.mts +155 -168
- package/dist/nuxt/module.d.mts.map +1 -0
- package/dist/nuxt/module.mjs +75 -65
- package/dist/nuxt/module.mjs.map +1 -0
- package/dist/pipeline.d.mts +46 -0
- package/dist/pipeline.d.mts.map +1 -0
- package/dist/pipeline.mjs +122 -0
- package/dist/pipeline.mjs.map +1 -0
- package/dist/runtime/client/log.d.mts +12 -7
- package/dist/runtime/client/log.d.mts.map +1 -0
- package/dist/runtime/client/log.mjs +72 -64
- package/dist/runtime/client/log.mjs.map +1 -0
- package/dist/runtime/client/plugin.d.mts +3 -1
- package/dist/runtime/client/plugin.d.mts.map +1 -0
- package/dist/runtime/client/plugin.mjs +14 -12
- package/dist/runtime/client/plugin.mjs.map +1 -0
- package/dist/runtime/server/routes/_evlog/ingest.post.d.mts +4 -2
- package/dist/runtime/server/routes/_evlog/ingest.post.d.mts.map +1 -0
- package/dist/runtime/server/routes/_evlog/ingest.post.mjs +113 -76
- package/dist/runtime/server/routes/_evlog/ingest.post.mjs.map +1 -0
- package/dist/runtime/server/useLogger.d.mts +14 -3
- package/dist/runtime/server/useLogger.d.mts.map +1 -0
- package/dist/runtime/server/useLogger.mjs +39 -10
- package/dist/runtime/server/useLogger.mjs.map +1 -0
- package/dist/runtime/utils/parseError.d.mts +5 -3
- package/dist/runtime/utils/parseError.d.mts.map +1 -0
- package/dist/runtime/utils/parseError.mjs +25 -26
- package/dist/runtime/utils/parseError.mjs.map +1 -0
- package/dist/types.d.mts +378 -246
- package/dist/types.d.mts.map +1 -0
- package/dist/types.mjs +1 -1
- package/dist/utils.d.mts +19 -14
- package/dist/utils.d.mts.map +1 -0
- package/dist/utils.mjs +59 -50
- package/dist/utils.mjs.map +1 -0
- package/dist/workers.d.mts +10 -9
- package/dist/workers.d.mts.map +1 -0
- package/dist/workers.mjs +68 -39
- package/dist/workers.mjs.map +1 -0
- package/package.json +55 -10
- package/dist/adapters/axiom.d.ts +0 -62
- package/dist/adapters/otlp.d.ts +0 -83
- package/dist/adapters/posthog.d.ts +0 -72
- package/dist/adapters/sentry.d.ts +0 -78
- package/dist/error.d.ts +0 -63
- package/dist/index.d.ts +0 -5
- package/dist/logger.d.ts +0 -40
- package/dist/nitro/errorHandler.d.ts +0 -13
- package/dist/nitro/plugin.d.ts +0 -5
- package/dist/nuxt/module.d.ts +0 -171
- package/dist/runtime/client/log.d.ts +0 -10
- package/dist/runtime/client/plugin.d.ts +0 -3
- package/dist/runtime/server/routes/_evlog/ingest.post.d.ts +0 -5
- package/dist/runtime/server/useLogger.d.ts +0 -28
- package/dist/runtime/utils/parseError.d.ts +0 -5
- package/dist/shared/evlog.Bc35pxiY.mjs +0 -10
- package/dist/types.d.ts +0 -364
- package/dist/utils.d.ts +0 -29
- package/dist/workers.d.ts +0 -45
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
//#region src/pipeline.ts
|
|
2
|
+
/**
|
|
3
|
+
* Create a drain pipeline that batches events, retries on failure, and manages buffer overflow.
|
|
4
|
+
*
|
|
5
|
+
* Returns a higher-order function: pass your drain adapter to get a hook-compatible function.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const pipeline = createDrainPipeline({ batch: { size: 50 } })
|
|
10
|
+
* const drain = pipeline(async (batch) => {
|
|
11
|
+
* await sendToBackend(batch)
|
|
12
|
+
* })
|
|
13
|
+
*
|
|
14
|
+
* // Use as a hook
|
|
15
|
+
* nitroApp.hooks.hook('evlog:drain', drain)
|
|
16
|
+
*
|
|
17
|
+
* // Flush on shutdown
|
|
18
|
+
* nitroApp.hooks.hook('close', () => drain.flush())
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
function createDrainPipeline(options) {
|
|
22
|
+
const batchSize = options?.batch?.size ?? 50;
|
|
23
|
+
const intervalMs = options?.batch?.intervalMs ?? 5e3;
|
|
24
|
+
const maxBufferSize = options?.maxBufferSize ?? 1e3;
|
|
25
|
+
const maxAttempts = options?.retry?.maxAttempts ?? 3;
|
|
26
|
+
const backoffStrategy = options?.retry?.backoff ?? "exponential";
|
|
27
|
+
const initialDelayMs = options?.retry?.initialDelayMs ?? 1e3;
|
|
28
|
+
const maxDelayMs = options?.retry?.maxDelayMs ?? 3e4;
|
|
29
|
+
const onDropped = options?.onDropped;
|
|
30
|
+
if (batchSize <= 0 || !Number.isFinite(batchSize)) throw new Error(`[evlog/pipeline] batch.size must be a positive finite number, got: ${batchSize}`);
|
|
31
|
+
if (intervalMs <= 0 || !Number.isFinite(intervalMs)) throw new Error(`[evlog/pipeline] batch.intervalMs must be a positive finite number, got: ${intervalMs}`);
|
|
32
|
+
if (maxBufferSize <= 0 || !Number.isFinite(maxBufferSize)) throw new Error(`[evlog/pipeline] maxBufferSize must be a positive finite number, got: ${maxBufferSize}`);
|
|
33
|
+
if (maxAttempts <= 0 || !Number.isFinite(maxAttempts)) throw new Error(`[evlog/pipeline] retry.maxAttempts must be a positive finite number, got: ${maxAttempts}`);
|
|
34
|
+
if (initialDelayMs < 0 || !Number.isFinite(initialDelayMs)) throw new Error(`[evlog/pipeline] retry.initialDelayMs must be a non-negative finite number, got: ${initialDelayMs}`);
|
|
35
|
+
if (maxDelayMs < 0 || !Number.isFinite(maxDelayMs)) throw new Error(`[evlog/pipeline] retry.maxDelayMs must be a non-negative finite number, got: ${maxDelayMs}`);
|
|
36
|
+
return (drain) => {
|
|
37
|
+
const buffer = [];
|
|
38
|
+
let timer = null;
|
|
39
|
+
let activeFlush = null;
|
|
40
|
+
function clearTimer() {
|
|
41
|
+
if (timer !== null) {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
timer = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function scheduleFlush() {
|
|
47
|
+
if (timer !== null || activeFlush) return;
|
|
48
|
+
timer = setTimeout(() => {
|
|
49
|
+
timer = null;
|
|
50
|
+
if (!activeFlush) startFlush();
|
|
51
|
+
}, intervalMs);
|
|
52
|
+
}
|
|
53
|
+
function getRetryDelay(attempt) {
|
|
54
|
+
let delay;
|
|
55
|
+
switch (backoffStrategy) {
|
|
56
|
+
case "linear":
|
|
57
|
+
delay = initialDelayMs * attempt;
|
|
58
|
+
break;
|
|
59
|
+
case "fixed":
|
|
60
|
+
delay = initialDelayMs;
|
|
61
|
+
break;
|
|
62
|
+
default:
|
|
63
|
+
delay = initialDelayMs * 2 ** (attempt - 1);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
return Math.min(delay, maxDelayMs);
|
|
67
|
+
}
|
|
68
|
+
async function sendWithRetry(batch) {
|
|
69
|
+
let lastError;
|
|
70
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
|
|
71
|
+
await drain(batch);
|
|
72
|
+
return;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
75
|
+
if (attempt < maxAttempts) await new Promise((r) => setTimeout(r, getRetryDelay(attempt)));
|
|
76
|
+
}
|
|
77
|
+
onDropped?.(batch, lastError);
|
|
78
|
+
}
|
|
79
|
+
async function drainBuffer() {
|
|
80
|
+
while (buffer.length > 0) await sendWithRetry(buffer.splice(0, batchSize));
|
|
81
|
+
}
|
|
82
|
+
function startFlush() {
|
|
83
|
+
if (activeFlush) return;
|
|
84
|
+
activeFlush = drainBuffer().finally(() => {
|
|
85
|
+
activeFlush = null;
|
|
86
|
+
if (buffer.length >= batchSize) startFlush();
|
|
87
|
+
else if (buffer.length > 0) scheduleFlush();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function push(ctx) {
|
|
91
|
+
if (buffer.length >= maxBufferSize) {
|
|
92
|
+
const dropped = buffer.splice(0, 1);
|
|
93
|
+
onDropped?.(dropped);
|
|
94
|
+
}
|
|
95
|
+
buffer.push(ctx);
|
|
96
|
+
if (buffer.length >= batchSize) {
|
|
97
|
+
clearTimer();
|
|
98
|
+
startFlush();
|
|
99
|
+
} else if (!activeFlush) scheduleFlush();
|
|
100
|
+
}
|
|
101
|
+
async function flush() {
|
|
102
|
+
clearTimer();
|
|
103
|
+
if (activeFlush) await activeFlush;
|
|
104
|
+
const snapshot = buffer.length;
|
|
105
|
+
if (snapshot > 0) {
|
|
106
|
+
const toFlush = buffer.splice(0, snapshot);
|
|
107
|
+
while (toFlush.length > 0) await sendWithRetry(toFlush.splice(0, batchSize));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const hookFn = push;
|
|
111
|
+
hookFn.flush = flush;
|
|
112
|
+
Object.defineProperty(hookFn, "pending", {
|
|
113
|
+
get: () => buffer.length,
|
|
114
|
+
enumerable: true
|
|
115
|
+
});
|
|
116
|
+
return hookFn;
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
//#endregion
|
|
121
|
+
export { createDrainPipeline };
|
|
122
|
+
//# sourceMappingURL=pipeline.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline.mjs","names":[],"sources":["../src/pipeline.ts"],"sourcesContent":["export interface DrainPipelineOptions<T = unknown> {\n batch?: {\n /** Maximum number of events per batch sent to the drain function. @default 50 */\n size?: number\n /** Maximum time (ms) an event can stay buffered before a flush is triggered, even if the batch is not full. @default 5000 */\n intervalMs?: number\n }\n retry?: {\n /** Total number of attempts (including the initial one) before dropping the batch. @default 3 */\n maxAttempts?: number\n /** Delay strategy between retry attempts. @default 'exponential' */\n backoff?: 'exponential' | 'linear' | 'fixed'\n /** Base delay (ms) for the first retry. Scaled by the backoff strategy on subsequent retries. @default 1000 */\n initialDelayMs?: number\n /** Upper bound (ms) for any single retry delay. @default 30000 */\n maxDelayMs?: number\n }\n /** Maximum number of events held in the buffer. When exceeded, the oldest event is dropped. @default 1000 */\n maxBufferSize?: number\n /** Called when a batch is dropped after all retry attempts are exhausted, or when the buffer overflows. */\n onDropped?: (events: T[], error?: Error) => void\n}\n\nexport interface PipelineDrainFn<T> {\n (ctx: T): void\n /** Flush all buffered events. Call on server shutdown. */\n flush: () => Promise<void>\n readonly pending: number\n}\n\n/**\n * Create a drain pipeline that batches events, retries on failure, and manages buffer overflow.\n *\n * Returns a higher-order function: pass your drain adapter to get a hook-compatible function.\n *\n * @example\n * ```ts\n * const pipeline = createDrainPipeline({ batch: { size: 50 } })\n * const drain = pipeline(async (batch) => {\n * await sendToBackend(batch)\n * })\n *\n * // Use as a hook\n * nitroApp.hooks.hook('evlog:drain', drain)\n *\n * // Flush on shutdown\n * nitroApp.hooks.hook('close', () => drain.flush())\n * ```\n */\nexport function createDrainPipeline<T = unknown>(options?: DrainPipelineOptions<T>): (drain: (batch: T[]) => void | Promise<void>) => PipelineDrainFn<T> {\n const batchSize = options?.batch?.size ?? 50\n const intervalMs = options?.batch?.intervalMs ?? 5000\n const maxBufferSize = options?.maxBufferSize ?? 1000\n const maxAttempts = options?.retry?.maxAttempts ?? 3\n const backoffStrategy = options?.retry?.backoff ?? 'exponential'\n const initialDelayMs = options?.retry?.initialDelayMs ?? 1000\n const maxDelayMs = options?.retry?.maxDelayMs ?? 30000\n const onDropped = options?.onDropped\n\n if (batchSize <= 0 || !Number.isFinite(batchSize)) {\n throw new Error(`[evlog/pipeline] batch.size must be a positive finite number, got: ${batchSize}`)\n }\n if (intervalMs <= 0 || !Number.isFinite(intervalMs)) {\n throw new Error(`[evlog/pipeline] batch.intervalMs must be a positive finite number, got: ${intervalMs}`)\n }\n if (maxBufferSize <= 0 || !Number.isFinite(maxBufferSize)) {\n throw new Error(`[evlog/pipeline] maxBufferSize must be a positive finite number, got: ${maxBufferSize}`)\n }\n if (maxAttempts <= 0 || !Number.isFinite(maxAttempts)) {\n throw new Error(`[evlog/pipeline] retry.maxAttempts must be a positive finite number, got: ${maxAttempts}`)\n }\n if (initialDelayMs < 0 || !Number.isFinite(initialDelayMs)) {\n throw new Error(`[evlog/pipeline] retry.initialDelayMs must be a non-negative finite number, got: ${initialDelayMs}`)\n }\n if (maxDelayMs < 0 || !Number.isFinite(maxDelayMs)) {\n throw new Error(`[evlog/pipeline] retry.maxDelayMs must be a non-negative finite number, got: ${maxDelayMs}`)\n }\n\n return (drain: (batch: T[]) => void | Promise<void>): PipelineDrainFn<T> => {\n const buffer: T[] = []\n let timer: ReturnType<typeof setTimeout> | null = null\n let activeFlush: Promise<void> | null = null\n\n function clearTimer(): void {\n if (timer !== null) {\n clearTimeout(timer)\n timer = null\n }\n }\n\n function scheduleFlush(): void {\n if (timer !== null || activeFlush) return\n timer = setTimeout(() => {\n timer = null\n if (!activeFlush) startFlush()\n }, intervalMs)\n }\n\n function getRetryDelay(attempt: number): number {\n let delay: number\n switch (backoffStrategy) {\n case 'linear':\n delay = initialDelayMs * attempt\n break\n case 'fixed':\n delay = initialDelayMs\n break\n case 'exponential':\n default:\n delay = initialDelayMs * 2 ** (attempt - 1)\n break\n }\n return Math.min(delay, maxDelayMs)\n }\n\n async function sendWithRetry(batch: T[]): Promise<void> {\n let lastError: Error | undefined\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n await drain(batch)\n return\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error))\n if (attempt < maxAttempts) {\n await new Promise<void>(r => setTimeout(r, getRetryDelay(attempt)))\n }\n }\n }\n onDropped?.(batch, lastError)\n }\n\n async function drainBuffer(): Promise<void> {\n while (buffer.length > 0) {\n const batch = buffer.splice(0, batchSize)\n await sendWithRetry(batch)\n }\n }\n\n function startFlush(): void {\n if (activeFlush) return\n activeFlush = drainBuffer().finally(() => {\n activeFlush = null\n if (buffer.length >= batchSize) {\n startFlush()\n } else if (buffer.length > 0) {\n scheduleFlush()\n }\n })\n }\n\n function push(ctx: T): void {\n if (buffer.length >= maxBufferSize) {\n const dropped = buffer.splice(0, 1)\n onDropped?.(dropped)\n }\n buffer.push(ctx)\n\n if (buffer.length >= batchSize) {\n clearTimer()\n startFlush()\n } else if (!activeFlush) {\n scheduleFlush()\n }\n }\n\n async function flush(): Promise<void> {\n clearTimer()\n if (activeFlush) {\n await activeFlush\n }\n // Snapshot the buffer length to avoid infinite loop if push() is called during flush\n const snapshot = buffer.length\n if (snapshot > 0) {\n const toFlush = buffer.splice(0, snapshot)\n while (toFlush.length > 0) {\n const batch = toFlush.splice(0, batchSize)\n await sendWithRetry(batch)\n }\n }\n }\n\n const hookFn = push as PipelineDrainFn<T>\n hookFn.flush = flush\n Object.defineProperty(hookFn, 'pending', {\n get: () => buffer.length,\n enumerable: true,\n })\n\n return hookFn\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Log, TransportConfig } from "../../types.mjs";
|
|
2
2
|
|
|
3
|
+
//#region src/runtime/client/log.d.ts
|
|
4
|
+
declare function setIdentity(identity: Record<string, unknown>): void;
|
|
5
|
+
declare function clearIdentity(): void;
|
|
3
6
|
declare function initLog(options?: {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
enabled?: boolean;
|
|
8
|
+
pretty?: boolean;
|
|
9
|
+
service?: string;
|
|
10
|
+
transport?: TransportConfig;
|
|
7
11
|
}): void;
|
|
8
|
-
declare const
|
|
9
|
-
|
|
10
|
-
export { initLog, log };
|
|
12
|
+
declare const _clientLog: Log;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { clearIdentity, initLog, _clientLog as log, setIdentity };
|
|
15
|
+
//# sourceMappingURL=log.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.mts","names":[],"sources":["../../../src/runtime/client/log.ts"],"mappings":";;;iBAYgB,WAAA,CAAY,QAAA,EAAU,MAAA;AAAA,iBAItB,aAAA,CAAA;AAAA,iBAWA,OAAA,CAAQ,OAAA;EAAW,OAAA;EAAmB,MAAA;EAAkB,OAAA;EAAkB,SAAA,GAAY,eAAA;AAAA;AAAA,cAgFhG,UAAA,EAAY,GAAA"}
|
|
@@ -1,84 +1,92 @@
|
|
|
1
|
-
import { getConsoleMethod } from
|
|
1
|
+
import { getConsoleMethod } from "../../utils.mjs";
|
|
2
2
|
|
|
3
|
+
//#region src/runtime/client/log.ts
|
|
3
4
|
const isClient = typeof window !== "undefined";
|
|
5
|
+
let clientEnabled = true;
|
|
4
6
|
let clientPretty = true;
|
|
5
7
|
let clientService = "client";
|
|
6
8
|
let transportEnabled = false;
|
|
7
9
|
let transportEndpoint = "/api/_evlog/ingest";
|
|
10
|
+
let identityContext = {};
|
|
11
|
+
function setIdentity(identity) {
|
|
12
|
+
identityContext = { ...identity };
|
|
13
|
+
}
|
|
14
|
+
function clearIdentity() {
|
|
15
|
+
identityContext = {};
|
|
16
|
+
}
|
|
8
17
|
const LEVEL_COLORS = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
error: "color: #ef4444; font-weight: bold",
|
|
19
|
+
warn: "color: #f59e0b; font-weight: bold",
|
|
20
|
+
info: "color: #06b6d4; font-weight: bold",
|
|
21
|
+
debug: "color: #6b7280; font-weight: bold"
|
|
13
22
|
};
|
|
14
23
|
function initLog(options = {}) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
clientEnabled = typeof options.enabled === "boolean" ? options.enabled : true;
|
|
25
|
+
clientPretty = typeof options.pretty === "boolean" ? options.pretty : true;
|
|
26
|
+
clientService = options.service ?? "client";
|
|
27
|
+
transportEnabled = options.transport?.enabled ?? false;
|
|
28
|
+
transportEndpoint = options.transport?.endpoint ?? "/api/_evlog/ingest";
|
|
19
29
|
}
|
|
20
30
|
async function sendToServer(event) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
31
|
+
if (!transportEnabled) return;
|
|
32
|
+
try {
|
|
33
|
+
await fetch(transportEndpoint, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
headers: { "Content-Type": "application/json" },
|
|
36
|
+
body: JSON.stringify(event),
|
|
37
|
+
keepalive: true,
|
|
38
|
+
credentials: "same-origin"
|
|
39
|
+
});
|
|
40
|
+
} catch {}
|
|
32
41
|
}
|
|
33
42
|
function emitLog(level, event) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
if (!clientEnabled) return;
|
|
44
|
+
const formatted = {
|
|
45
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
46
|
+
level,
|
|
47
|
+
service: clientService,
|
|
48
|
+
...identityContext,
|
|
49
|
+
...event
|
|
50
|
+
};
|
|
51
|
+
const method = getConsoleMethod(level);
|
|
52
|
+
if (clientPretty) {
|
|
53
|
+
const { level: lvl, service, ...rest } = formatted;
|
|
54
|
+
console[method](`%c[${service}]%c ${lvl}`, LEVEL_COLORS[lvl] || "", "color: inherit", rest);
|
|
55
|
+
} else console[method](JSON.stringify(formatted));
|
|
56
|
+
sendToServer(formatted);
|
|
48
57
|
}
|
|
49
58
|
function emitTaggedLog(level, tag, message) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
if (!clientEnabled) return;
|
|
60
|
+
if (clientPretty) {
|
|
61
|
+
console[getConsoleMethod(level)](`%c[${tag}]%c ${message}`, LEVEL_COLORS[level] || "", "color: inherit");
|
|
62
|
+
sendToServer({
|
|
63
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
64
|
+
level,
|
|
65
|
+
service: clientService,
|
|
66
|
+
...identityContext,
|
|
67
|
+
tag,
|
|
68
|
+
message
|
|
69
|
+
});
|
|
70
|
+
} else emitLog(level, {
|
|
71
|
+
tag,
|
|
72
|
+
message
|
|
73
|
+
});
|
|
62
74
|
}
|
|
63
75
|
function createLogMethod(level) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
} else if (typeof tagOrEvent === "object") {
|
|
71
|
-
emitLog(level, tagOrEvent);
|
|
72
|
-
} else {
|
|
73
|
-
emitTaggedLog(level, "log", String(tagOrEvent));
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
+
return function logMethod(tagOrEvent, message) {
|
|
77
|
+
if (!(import.meta.client ?? isClient)) return;
|
|
78
|
+
if (typeof tagOrEvent === "string" && message !== void 0) emitTaggedLog(level, tagOrEvent, message);
|
|
79
|
+
else if (typeof tagOrEvent === "object") emitLog(level, tagOrEvent);
|
|
80
|
+
else emitTaggedLog(level, "log", String(tagOrEvent));
|
|
81
|
+
};
|
|
76
82
|
}
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
const _clientLog = {
|
|
84
|
+
info: createLogMethod("info"),
|
|
85
|
+
error: createLogMethod("error"),
|
|
86
|
+
warn: createLogMethod("warn"),
|
|
87
|
+
debug: createLogMethod("debug")
|
|
82
88
|
};
|
|
83
89
|
|
|
84
|
-
|
|
90
|
+
//#endregion
|
|
91
|
+
export { clearIdentity, initLog, _clientLog as log, setIdentity };
|
|
92
|
+
//# sourceMappingURL=log.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.mjs","names":[],"sources":["../../../src/runtime/client/log.ts"],"sourcesContent":["import type { Log, LogLevel, TransportConfig } from '../../types'\nimport { getConsoleMethod } from '../../utils'\n\nconst isClient = typeof window !== 'undefined'\n\nlet clientEnabled = true\nlet clientPretty = true\nlet clientService = 'client'\nlet transportEnabled = false\nlet transportEndpoint = '/api/_evlog/ingest'\nlet identityContext: Record<string, unknown> = {}\n\nexport function setIdentity(identity: Record<string, unknown>): void {\n identityContext = { ...identity }\n}\n\nexport function clearIdentity(): void {\n identityContext = {}\n}\n\nconst LEVEL_COLORS: Record<string, string> = {\n error: 'color: #ef4444; font-weight: bold',\n warn: 'color: #f59e0b; font-weight: bold',\n info: 'color: #06b6d4; font-weight: bold',\n debug: 'color: #6b7280; font-weight: bold',\n}\n\nexport function initLog(options: { enabled?: boolean, pretty?: boolean, service?: string, transport?: TransportConfig } = {}): void {\n clientEnabled = typeof options.enabled === 'boolean' ? options.enabled : true\n clientPretty = typeof options.pretty === 'boolean' ? options.pretty : true\n clientService = options.service ?? 'client'\n transportEnabled = options.transport?.enabled ?? false\n transportEndpoint = options.transport?.endpoint ?? '/api/_evlog/ingest'\n}\n\nasync function sendToServer(event: Record<string, unknown>): Promise<void> {\n if (!transportEnabled) return\n\n try {\n await fetch(transportEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(event),\n keepalive: true,\n credentials: 'same-origin',\n })\n } catch {\n // Silently fail - don't break the app\n }\n}\n\nfunction emitLog(level: LogLevel, event: Record<string, unknown>): void {\n if (!clientEnabled) return\n\n const formatted = {\n timestamp: new Date().toISOString(),\n level,\n service: clientService,\n ...identityContext,\n ...event,\n }\n\n const method = getConsoleMethod(level)\n\n if (clientPretty) {\n const { level: lvl, service, ...rest } = formatted\n console[method](`%c[${service}]%c ${lvl}`, LEVEL_COLORS[lvl] || '', 'color: inherit', rest)\n } else {\n console[method](JSON.stringify(formatted))\n }\n\n sendToServer(formatted)\n}\n\nfunction emitTaggedLog(level: LogLevel, tag: string, message: string): void {\n if (!clientEnabled) return\n if (clientPretty) {\n console[getConsoleMethod(level)](`%c[${tag}]%c ${message}`, LEVEL_COLORS[level] || '', 'color: inherit')\n sendToServer({\n timestamp: new Date().toISOString(),\n level,\n service: clientService,\n ...identityContext,\n tag,\n message,\n })\n } else {\n emitLog(level, { tag, message })\n }\n}\n\nfunction createLogMethod(level: LogLevel) {\n return function logMethod(tagOrEvent: string | Record<string, unknown>, message?: string): void {\n if (!(import.meta.client ?? isClient)) {\n return\n }\n\n if (typeof tagOrEvent === 'string' && message !== undefined) {\n emitTaggedLog(level, tagOrEvent, message)\n } else if (typeof tagOrEvent === 'object') {\n emitLog(level, tagOrEvent)\n } else {\n emitTaggedLog(level, 'log', String(tagOrEvent))\n }\n }\n}\n\nconst _clientLog: Log = {\n info: createLogMethod('info'),\n error: createLogMethod('error'),\n warn: createLogMethod('warn'),\n debug: createLogMethod('debug'),\n}\n\nexport { _clientLog as log }\n"],"mappings":";;;AAGA,MAAM,WAAW,OAAO,WAAW;AAEnC,IAAI,gBAAgB;AACpB,IAAI,eAAe;AACnB,IAAI,gBAAgB;AACpB,IAAI,mBAAmB;AACvB,IAAI,oBAAoB;AACxB,IAAI,kBAA2C,EAAE;AAEjD,SAAgB,YAAY,UAAyC;AACnE,mBAAkB,EAAE,GAAG,UAAU;;AAGnC,SAAgB,gBAAsB;AACpC,mBAAkB,EAAE;;AAGtB,MAAM,eAAuC;CAC3C,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACR;AAED,SAAgB,QAAQ,UAAkG,EAAE,EAAQ;AAClI,iBAAgB,OAAO,QAAQ,YAAY,YAAY,QAAQ,UAAU;AACzE,gBAAe,OAAO,QAAQ,WAAW,YAAY,QAAQ,SAAS;AACtE,iBAAgB,QAAQ,WAAW;AACnC,oBAAmB,QAAQ,WAAW,WAAW;AACjD,qBAAoB,QAAQ,WAAW,YAAY;;AAGrD,eAAe,aAAa,OAA+C;AACzE,KAAI,CAAC,iBAAkB;AAEvB,KAAI;AACF,QAAM,MAAM,mBAAmB;GAC7B,QAAQ;GACR,SAAS,EAAE,gBAAgB,oBAAoB;GAC/C,MAAM,KAAK,UAAU,MAAM;GAC3B,WAAW;GACX,aAAa;GACd,CAAC;SACI;;AAKV,SAAS,QAAQ,OAAiB,OAAsC;AACtE,KAAI,CAAC,cAAe;CAEpB,MAAM,YAAY;EAChB,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC;EACA,SAAS;EACT,GAAG;EACH,GAAG;EACJ;CAED,MAAM,SAAS,iBAAiB,MAAM;AAEtC,KAAI,cAAc;EAChB,MAAM,EAAE,OAAO,KAAK,SAAS,GAAG,SAAS;AACzC,UAAQ,QAAQ,MAAM,QAAQ,MAAM,OAAO,aAAa,QAAQ,IAAI,kBAAkB,KAAK;OAE3F,SAAQ,QAAQ,KAAK,UAAU,UAAU,CAAC;AAG5C,cAAa,UAAU;;AAGzB,SAAS,cAAc,OAAiB,KAAa,SAAuB;AAC1E,KAAI,CAAC,cAAe;AACpB,KAAI,cAAc;AAChB,UAAQ,iBAAiB,MAAM,EAAE,MAAM,IAAI,MAAM,WAAW,aAAa,UAAU,IAAI,iBAAiB;AACxG,eAAa;GACX,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC;GACA,SAAS;GACT,GAAG;GACH;GACA;GACD,CAAC;OAEF,SAAQ,OAAO;EAAE;EAAK;EAAS,CAAC;;AAIpC,SAAS,gBAAgB,OAAiB;AACxC,QAAO,SAAS,UAAU,YAA8C,SAAwB;AAC9F,MAAI,EAAE,OAAO,KAAK,UAAU,UAC1B;AAGF,MAAI,OAAO,eAAe,YAAY,YAAY,OAChD,eAAc,OAAO,YAAY,QAAQ;WAChC,OAAO,eAAe,SAC/B,SAAQ,OAAO,WAAW;MAE1B,eAAc,OAAO,OAAO,OAAO,WAAW,CAAC;;;AAKrD,MAAM,aAAkB;CACtB,MAAM,gBAAgB,OAAO;CAC7B,OAAO,gBAAgB,QAAQ;CAC/B,MAAM,gBAAgB,OAAO;CAC7B,OAAO,gBAAgB,QAAQ;CAChC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.d.mts","names":[],"sources":["../../../src/runtime/client/plugin.ts"],"mappings":""}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { initLog } from
|
|
2
|
-
import { defineNuxtPlugin, useRuntimeConfig } from
|
|
3
|
-
import '../../utils.mjs';
|
|
1
|
+
import { initLog } from "./log.mjs";
|
|
2
|
+
import { defineNuxtPlugin, useRuntimeConfig } from "#app";
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
//#region src/runtime/client/plugin.ts
|
|
5
|
+
var plugin_default = defineNuxtPlugin(() => {
|
|
6
|
+
const evlogConfig = useRuntimeConfig().public?.evlog;
|
|
7
|
+
initLog({
|
|
8
|
+
enabled: evlogConfig?.enabled,
|
|
9
|
+
pretty: evlogConfig?.pretty ?? import.meta.dev,
|
|
10
|
+
service: "client",
|
|
11
|
+
transport: evlogConfig?.transport
|
|
12
|
+
});
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
//#endregion
|
|
16
|
+
export { plugin_default as default };
|
|
17
|
+
//# sourceMappingURL=plugin.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin.mjs","names":[],"sources":["../../../src/runtime/client/plugin.ts"],"sourcesContent":["import type { TransportConfig } from '../../types'\nimport { initLog } from './log'\nimport { defineNuxtPlugin, useRuntimeConfig } from '#app'\n\ninterface EvlogPublicConfig {\n enabled?: boolean\n pretty?: boolean\n transport?: TransportConfig\n}\n\nexport default defineNuxtPlugin(() => {\n const config = useRuntimeConfig()\n const evlogConfig = config.public?.evlog as EvlogPublicConfig | undefined\n\n initLog({\n enabled: evlogConfig?.enabled,\n pretty: evlogConfig?.pretty ?? import.meta.dev,\n service: 'client',\n transport: evlogConfig?.transport,\n })\n})\n"],"mappings":";;;;AAUA,qBAAe,uBAAuB;CAEpC,MAAM,cADS,kBAAkB,CACN,QAAQ;AAEnC,SAAQ;EACN,SAAS,aAAa;EACtB,QAAQ,aAAa,UAAU,OAAO,KAAK;EAC3C,SAAS;EACT,WAAW,aAAa;EACzB,CAAC;EACF"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import * as h3 from
|
|
1
|
+
import * as h3 from "h3";
|
|
2
2
|
|
|
3
|
+
//#region src/runtime/server/routes/_evlog/ingest.post.d.ts
|
|
3
4
|
declare const _default: h3.EventHandler<h3.EventHandlerRequest, Promise<null>>;
|
|
4
|
-
|
|
5
|
+
//#endregion
|
|
5
6
|
export { _default as default };
|
|
7
|
+
//# sourceMappingURL=ingest.post.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.post.d.mts","names":[],"sources":["../../../../../src/runtime/server/routes/_evlog/ingest.post.ts"],"mappings":""}
|
|
@@ -1,86 +1,123 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
1
|
+
import { filterSafeHeaders } from "../../../../utils.mjs";
|
|
2
|
+
import { getEnvironment } from "../../../../logger.mjs";
|
|
3
|
+
import { createError, defineEventHandler, getHeader, getHeaders, getRequestHost, readBody, setResponseStatus } from "h3";
|
|
4
|
+
import { useNitroApp } from "nitropack/runtime";
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
//#region src/runtime/server/routes/_evlog/ingest.post.ts
|
|
7
|
+
const VALID_LEVELS = [
|
|
8
|
+
"info",
|
|
9
|
+
"error",
|
|
10
|
+
"warn",
|
|
11
|
+
"debug"
|
|
12
|
+
];
|
|
7
13
|
function validateOrigin(event) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
const origin = getHeader(event, "origin");
|
|
15
|
+
const referer = getHeader(event, "referer");
|
|
16
|
+
const host = getRequestHost(event);
|
|
17
|
+
const requestOrigin = origin || (referer ? new URL(referer).origin : null);
|
|
18
|
+
if (!requestOrigin) throw createError({
|
|
19
|
+
statusCode: 403,
|
|
20
|
+
message: "Missing origin header"
|
|
21
|
+
});
|
|
22
|
+
if (new URL(requestOrigin).host !== host) throw createError({
|
|
23
|
+
statusCode: 403,
|
|
24
|
+
message: "Invalid origin"
|
|
25
|
+
});
|
|
19
26
|
}
|
|
20
27
|
const ISO_8601_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z$/;
|
|
21
28
|
function isValidISOTimestamp(value) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
if (!ISO_8601_REGEX.test(value)) return false;
|
|
30
|
+
const date = new Date(value);
|
|
31
|
+
return !Number.isNaN(date.getTime());
|
|
25
32
|
}
|
|
26
33
|
function validatePayload(body) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
34
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) throw createError({
|
|
35
|
+
statusCode: 400,
|
|
36
|
+
message: "Invalid request body"
|
|
37
|
+
});
|
|
38
|
+
const payload = body;
|
|
39
|
+
if (payload.timestamp === void 0 || payload.timestamp === null) throw createError({
|
|
40
|
+
statusCode: 400,
|
|
41
|
+
message: "Missing required field: timestamp"
|
|
42
|
+
});
|
|
43
|
+
const { timestamp: rawTimestamp } = payload;
|
|
44
|
+
let timestamp;
|
|
45
|
+
if (typeof rawTimestamp === "number") {
|
|
46
|
+
const minTimestamp = (/* @__PURE__ */ new Date("2000-01-01")).getTime();
|
|
47
|
+
const maxTimestamp = Date.now() + 1440 * 60 * 1e3;
|
|
48
|
+
if (rawTimestamp < minTimestamp || rawTimestamp > maxTimestamp) throw createError({
|
|
49
|
+
statusCode: 400,
|
|
50
|
+
message: "Invalid timestamp: value out of reasonable range"
|
|
51
|
+
});
|
|
52
|
+
timestamp = new Date(rawTimestamp).toISOString();
|
|
53
|
+
} else if (typeof rawTimestamp === "string") {
|
|
54
|
+
if (!isValidISOTimestamp(rawTimestamp)) throw createError({
|
|
55
|
+
statusCode: 400,
|
|
56
|
+
message: "Invalid timestamp: must be a valid ISO 8601 datetime string"
|
|
57
|
+
});
|
|
58
|
+
timestamp = rawTimestamp;
|
|
59
|
+
} else throw createError({
|
|
60
|
+
statusCode: 400,
|
|
61
|
+
message: "Invalid timestamp: must be string or number"
|
|
62
|
+
});
|
|
63
|
+
if (!payload.level || typeof payload.level !== "string") throw createError({
|
|
64
|
+
statusCode: 400,
|
|
65
|
+
message: "Missing required field: level"
|
|
66
|
+
});
|
|
67
|
+
if (!VALID_LEVELS.includes(payload.level)) throw createError({
|
|
68
|
+
statusCode: 400,
|
|
69
|
+
message: `Invalid level: must be one of ${VALID_LEVELS.join(", ")}`
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
...payload,
|
|
73
|
+
timestamp,
|
|
74
|
+
level: payload.level
|
|
75
|
+
};
|
|
62
76
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
function getSafeHeaders(event) {
|
|
78
|
+
return filterSafeHeaders(getHeaders(event));
|
|
79
|
+
}
|
|
80
|
+
var ingest_post_default = defineEventHandler(async (event) => {
|
|
81
|
+
validateOrigin(event);
|
|
82
|
+
const payload = validatePayload(await readBody(event));
|
|
83
|
+
const nitroApp = useNitroApp();
|
|
84
|
+
const env = getEnvironment();
|
|
85
|
+
const { service: _clientService, ...sanitizedPayload } = payload;
|
|
86
|
+
const wideEvent = {
|
|
87
|
+
...sanitizedPayload,
|
|
88
|
+
...env,
|
|
89
|
+
source: "client"
|
|
90
|
+
};
|
|
91
|
+
const headers = getSafeHeaders(event);
|
|
92
|
+
const request = {
|
|
93
|
+
method: "POST",
|
|
94
|
+
path: event.path
|
|
95
|
+
};
|
|
96
|
+
try {
|
|
97
|
+
await nitroApp.hooks.callHook("evlog:enrich", {
|
|
98
|
+
event: wideEvent,
|
|
99
|
+
request,
|
|
100
|
+
headers,
|
|
101
|
+
response: { status: 204 }
|
|
102
|
+
});
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error("[evlog] enrich failed:", err);
|
|
105
|
+
}
|
|
106
|
+
const drainPromise = nitroApp.hooks.callHook("evlog:drain", {
|
|
107
|
+
event: wideEvent,
|
|
108
|
+
request,
|
|
109
|
+
headers
|
|
110
|
+
}).catch((err) => {
|
|
111
|
+
console.error("[evlog] drain failed:", err);
|
|
112
|
+
});
|
|
113
|
+
const waitUntilCtx = event.context;
|
|
114
|
+
const cfCtx = waitUntilCtx.cloudflare?.context ?? waitUntilCtx;
|
|
115
|
+
if (typeof cfCtx.waitUntil === "function") cfCtx.waitUntil(drainPromise);
|
|
116
|
+
else await drainPromise;
|
|
117
|
+
setResponseStatus(event, 204);
|
|
118
|
+
return null;
|
|
84
119
|
});
|
|
85
120
|
|
|
86
|
-
|
|
121
|
+
//#endregion
|
|
122
|
+
export { ingest_post_default as default };
|
|
123
|
+
//# sourceMappingURL=ingest.post.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest.post.mjs","names":[],"sources":["../../../../../src/runtime/server/routes/_evlog/ingest.post.ts"],"sourcesContent":["import { createError, defineEventHandler, getHeader, getHeaders, getRequestHost, readBody, setResponseStatus } from 'h3'\nimport { useNitroApp } from 'nitropack/runtime'\nimport type { IngestPayload, WideEvent } from '../../../../types'\nimport { getEnvironment } from '../../../../logger'\nimport { filterSafeHeaders } from '../../../../utils'\n\nconst VALID_LEVELS = ['info', 'error', 'warn', 'debug'] as const\n\nfunction validateOrigin(event: Parameters<typeof defineEventHandler>[0] extends (e: infer E) => unknown ? E : never): void {\n const origin = getHeader(event, 'origin')\n const referer = getHeader(event, 'referer')\n const host = getRequestHost(event)\n\n const requestOrigin = origin || (referer ? new URL(referer).origin : null)\n\n if (!requestOrigin) {\n throw createError({ statusCode: 403, message: 'Missing origin header' })\n }\n\n const originHost = new URL(requestOrigin).host\n\n if (originHost !== host) {\n throw createError({ statusCode: 403, message: 'Invalid origin' })\n }\n}\n\n// ISO 8601 datetime pattern (e.g., 2024-01-31T14:00:00.000Z)\nconst ISO_8601_REGEX = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?Z$/\n\nfunction isValidISOTimestamp(value: string): boolean {\n if (!ISO_8601_REGEX.test(value)) return false\n const date = new Date(value)\n return !Number.isNaN(date.getTime())\n}\n\nfunction validatePayload(body: unknown): IngestPayload {\n if (!body || typeof body !== 'object' || Array.isArray(body)) {\n throw createError({ statusCode: 400, message: 'Invalid request body' })\n }\n\n const payload = body as Record<string, unknown>\n\n if (payload.timestamp === undefined || payload.timestamp === null) {\n throw createError({ statusCode: 400, message: 'Missing required field: timestamp' })\n }\n\n const { timestamp: rawTimestamp } = payload\n let timestamp: string\n if (typeof rawTimestamp === 'number') {\n const minTimestamp = new Date('2000-01-01').getTime()\n const maxTimestamp = Date.now() + 24 * 60 * 60 * 1000 // 1 day in the future\n if (rawTimestamp < minTimestamp || rawTimestamp > maxTimestamp) {\n throw createError({ statusCode: 400, message: 'Invalid timestamp: value out of reasonable range' })\n }\n timestamp = new Date(rawTimestamp).toISOString()\n } else if (typeof rawTimestamp === 'string') {\n if (!isValidISOTimestamp(rawTimestamp)) {\n throw createError({ statusCode: 400, message: 'Invalid timestamp: must be a valid ISO 8601 datetime string' })\n }\n timestamp = rawTimestamp\n } else {\n throw createError({ statusCode: 400, message: 'Invalid timestamp: must be string or number' })\n }\n\n if (!payload.level || typeof payload.level !== 'string') {\n throw createError({ statusCode: 400, message: 'Missing required field: level' })\n }\n\n if (!VALID_LEVELS.includes(payload.level as typeof VALID_LEVELS[number])) {\n throw createError({ statusCode: 400, message: `Invalid level: must be one of ${VALID_LEVELS.join(', ')}` })\n }\n\n return {\n ...payload,\n timestamp,\n level: payload.level as IngestPayload['level'],\n }\n}\n\nfunction getSafeHeaders(event: Parameters<typeof defineEventHandler>[0] extends (e: infer E) => unknown ? E : never): Record<string, string> {\n const allHeaders = getHeaders(event as Parameters<typeof getHeaders>[0])\n return filterSafeHeaders(allHeaders)\n}\n\nexport default defineEventHandler(async (event) => {\n validateOrigin(event)\n\n const body = await readBody(event)\n const payload = validatePayload(body)\n const nitroApp = useNitroApp()\n const env = getEnvironment()\n\n const { service: _clientService, ...sanitizedPayload } = payload as IngestPayload & { service?: unknown }\n\n const wideEvent: WideEvent = {\n ...sanitizedPayload,\n ...env,\n source: 'client',\n }\n\n const headers = getSafeHeaders(event)\n const request = { method: 'POST' as const, path: event.path }\n\n try {\n await nitroApp.hooks.callHook('evlog:enrich', {\n event: wideEvent,\n request,\n headers,\n response: { status: 204 },\n })\n } catch (err) {\n console.error('[evlog] enrich failed:', err)\n }\n\n const drainPromise = nitroApp.hooks.callHook('evlog:drain', {\n event: wideEvent,\n request,\n headers,\n }).catch((err) => {\n console.error('[evlog] drain failed:', err)\n })\n\n // Use waitUntil if available (Cloudflare Workers, Vercel Edge)\n // Otherwise, await the drain to prevent lost logs in serverless environments\n const waitUntilCtx = (event as unknown as { context: Record<string, unknown> }).context\n const cfCtx = (waitUntilCtx as { cloudflare?: { context?: { waitUntil?: (p: Promise<unknown>) => void } } }).cloudflare?.context ?? waitUntilCtx\n if (typeof (cfCtx as { waitUntil?: unknown }).waitUntil === 'function') {\n (cfCtx as { waitUntil: (p: Promise<unknown>) => void }).waitUntil(drainPromise)\n } else {\n await drainPromise\n }\n\n setResponseStatus(event, 204)\n return null\n})\n"],"mappings":"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"}
|