evlog 2.1.0 → 2.3.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/dist/elysia/index.d.mts +75 -0
- package/dist/elysia/index.d.mts.map +1 -0
- package/dist/elysia/index.mjs +62 -0
- package/dist/elysia/index.mjs.map +1 -0
- package/dist/express/index.d.mts +69 -0
- package/dist/express/index.d.mts.map +1 -0
- package/dist/express/index.mjs +66 -0
- package/dist/express/index.mjs.map +1 -0
- package/dist/headers-CXOd5EyZ.mjs +141 -0
- package/dist/headers-CXOd5EyZ.mjs.map +1 -0
- package/dist/hono/index.d.mts +68 -0
- package/dist/hono/index.d.mts.map +1 -0
- package/dist/hono/index.mjs +48 -0
- package/dist/hono/index.mjs.map +1 -0
- package/dist/nitro/module.d.mts +1 -1
- package/dist/nitro/v3/module.d.mts +1 -1
- package/dist/{nitro-mfub2f8G.d.mts → nitro-Nxg6qcXd.d.mts} +1 -1
- package/dist/{nitro-mfub2f8G.d.mts.map → nitro-Nxg6qcXd.d.mts.map} +1 -1
- package/dist/nuxt/module.mjs +1 -1
- package/package.json +46 -5
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { DrainContext, EnrichContext, RequestLogger, RouteConfig, TailSamplingContext } from "../types.mjs";
|
|
2
|
+
import { Elysia } from "elysia";
|
|
3
|
+
|
|
4
|
+
//#region src/elysia/index.d.ts
|
|
5
|
+
interface EvlogElysiaOptions {
|
|
6
|
+
/** Route patterns to include in logging (glob). If not set, all routes are logged */
|
|
7
|
+
include?: string[];
|
|
8
|
+
/** Route patterns to exclude from logging. Exclusions take precedence over inclusions */
|
|
9
|
+
exclude?: string[];
|
|
10
|
+
/** Route-specific service configuration */
|
|
11
|
+
routes?: Record<string, RouteConfig>;
|
|
12
|
+
/**
|
|
13
|
+
* Drain callback called with every emitted event.
|
|
14
|
+
* Use with drain adapters (Axiom, OTLP, Sentry, etc.) or custom endpoints.
|
|
15
|
+
*/
|
|
16
|
+
drain?: (ctx: DrainContext) => void | Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Enrich callback called after emit, before drain.
|
|
19
|
+
* Use to add derived context (geo, deployment info, user agent, etc.).
|
|
20
|
+
*/
|
|
21
|
+
enrich?: (ctx: EnrichContext) => void | Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Custom tail sampling callback.
|
|
24
|
+
* Set `ctx.shouldKeep = true` to force-keep the log regardless of head sampling.
|
|
25
|
+
*/
|
|
26
|
+
keep?: (ctx: TailSamplingContext) => void | Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the request-scoped logger from anywhere in the call stack.
|
|
30
|
+
* Must be called inside a request handled by the `evlog()` plugin.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { useLogger } from 'evlog/elysia'
|
|
35
|
+
*
|
|
36
|
+
* function findUser(id: string) {
|
|
37
|
+
* const log = useLogger()
|
|
38
|
+
* log.set({ user: { id } })
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
declare function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T>;
|
|
43
|
+
declare function evlog(options?: EvlogElysiaOptions): Elysia<"", {
|
|
44
|
+
decorator: {};
|
|
45
|
+
store: {};
|
|
46
|
+
derive: {
|
|
47
|
+
readonly log: RequestLogger<Record<string, unknown>>;
|
|
48
|
+
};
|
|
49
|
+
resolve: {};
|
|
50
|
+
}, {
|
|
51
|
+
typebox: {};
|
|
52
|
+
error: {};
|
|
53
|
+
}, {
|
|
54
|
+
schema: {};
|
|
55
|
+
standaloneSchema: {};
|
|
56
|
+
macro: {};
|
|
57
|
+
macroFn: {};
|
|
58
|
+
parser: {};
|
|
59
|
+
response: {};
|
|
60
|
+
}, {}, {
|
|
61
|
+
derive: {};
|
|
62
|
+
resolve: {};
|
|
63
|
+
schema: {};
|
|
64
|
+
standaloneSchema: {};
|
|
65
|
+
response: {};
|
|
66
|
+
}, {
|
|
67
|
+
derive: {};
|
|
68
|
+
resolve: {};
|
|
69
|
+
schema: {};
|
|
70
|
+
standaloneSchema: {};
|
|
71
|
+
response: {};
|
|
72
|
+
}>;
|
|
73
|
+
//#endregion
|
|
74
|
+
export { EvlogElysiaOptions, evlog, useLogger };
|
|
75
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/elysia/index.ts"],"mappings":";;;;UAQiB,kBAAA;;EAEf,OAAA;EAFiC;EAIjC,OAAA;EAEwB;EAAxB,MAAA,GAAS,MAAA,SAAe,WAAA;EAKV;;;;EAAd,KAAA,IAAS,GAAA,EAAK,YAAA,YAAwB,OAAA;EAUM;;;;EAL5C,MAAA,IAAU,GAAA,EAAK,aAAA,YAAyB,OAAA;EAVxC;;;;EAeA,IAAA,IAAQ,GAAA,EAAK,mBAAA,YAA+B,OAAA;AAAA;;;;;;;;;;;;AAiB9C;;;iBAAgB,SAAA,oBAA6B,MAAA,kBAAA,CAAA,GAA4B,aAAA,CAAc,CAAA;AAAA,iBAuCvE,KAAA,CAAM,OAAA,GAAS,kBAAA,GAAuB,MAAA"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { r as createMiddlewareLogger, t as extractSafeHeaders } from "../headers-CXOd5EyZ.mjs";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
import { Elysia } from "elysia";
|
|
4
|
+
|
|
5
|
+
//#region src/elysia/index.ts
|
|
6
|
+
const storage = new AsyncLocalStorage();
|
|
7
|
+
/**
|
|
8
|
+
* Get the request-scoped logger from anywhere in the call stack.
|
|
9
|
+
* Must be called inside a request handled by the `evlog()` plugin.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { useLogger } from 'evlog/elysia'
|
|
14
|
+
*
|
|
15
|
+
* function findUser(id: string) {
|
|
16
|
+
* const log = useLogger()
|
|
17
|
+
* log.set({ user: { id } })
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
function useLogger() {
|
|
22
|
+
const logger = storage.getStore();
|
|
23
|
+
if (!logger) throw new Error("[evlog] useLogger() was called outside of an evlog plugin context. Make sure app.use(evlog()) is registered before your routes.");
|
|
24
|
+
return logger;
|
|
25
|
+
}
|
|
26
|
+
function evlog(options = {}) {
|
|
27
|
+
const emitted = /* @__PURE__ */ new WeakSet();
|
|
28
|
+
const requestState = /* @__PURE__ */ new WeakMap();
|
|
29
|
+
return new Elysia({ name: "evlog" }).derive({ as: "global" }, ({ request }) => {
|
|
30
|
+
const url = new URL(request.url);
|
|
31
|
+
const { logger, finish, skipped } = createMiddlewareLogger({
|
|
32
|
+
method: request.method,
|
|
33
|
+
path: url.pathname,
|
|
34
|
+
requestId: request.headers.get("x-request-id") || crypto.randomUUID(),
|
|
35
|
+
headers: extractSafeHeaders(request.headers),
|
|
36
|
+
...options
|
|
37
|
+
});
|
|
38
|
+
storage.enterWith(logger);
|
|
39
|
+
requestState.set(request, {
|
|
40
|
+
finish,
|
|
41
|
+
skipped
|
|
42
|
+
});
|
|
43
|
+
return { log: logger };
|
|
44
|
+
}).onAfterHandle({ as: "global" }, async ({ request, set }) => {
|
|
45
|
+
const state = requestState.get(request);
|
|
46
|
+
if (!state || state.skipped || emitted.has(request)) return;
|
|
47
|
+
emitted.add(request);
|
|
48
|
+
await state.finish({ status: set.status || 200 });
|
|
49
|
+
}).onError({ as: "global" }, async ({ request, error }) => {
|
|
50
|
+
const state = requestState.get(request);
|
|
51
|
+
if (!state || state.skipped || emitted.has(request)) return;
|
|
52
|
+
emitted.add(request);
|
|
53
|
+
const logger = storage.getStore();
|
|
54
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
55
|
+
if (logger) logger.error(err);
|
|
56
|
+
await state.finish({ error: err });
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//#endregion
|
|
61
|
+
export { evlog, useLogger };
|
|
62
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/elysia/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport { Elysia } from 'elysia'\nimport type { DrainContext, EnrichContext, RequestLogger, RouteConfig, TailSamplingContext } from '../types'\nimport { createMiddlewareLogger } from '../shared/middleware'\nimport { extractSafeHeaders } from '../shared/headers'\n\nconst storage = new AsyncLocalStorage<RequestLogger>()\n\nexport interface EvlogElysiaOptions {\n /** Route patterns to include in logging (glob). If not set, all routes are logged */\n include?: string[]\n /** Route patterns to exclude from logging. Exclusions take precedence over inclusions */\n exclude?: string[]\n /** Route-specific service configuration */\n routes?: Record<string, RouteConfig>\n /**\n * Drain callback called with every emitted event.\n * Use with drain adapters (Axiom, OTLP, Sentry, etc.) or custom endpoints.\n */\n drain?: (ctx: DrainContext) => void | Promise<void>\n /**\n * Enrich callback called after emit, before drain.\n * Use to add derived context (geo, deployment info, user agent, etc.).\n */\n enrich?: (ctx: EnrichContext) => void | Promise<void>\n /**\n * Custom tail sampling callback.\n * Set `ctx.shouldKeep = true` to force-keep the log regardless of head sampling.\n */\n keep?: (ctx: TailSamplingContext) => void | Promise<void>\n}\n\n/**\n * Get the request-scoped logger from anywhere in the call stack.\n * Must be called inside a request handled by the `evlog()` plugin.\n *\n * @example\n * ```ts\n * import { useLogger } from 'evlog/elysia'\n *\n * function findUser(id: string) {\n * const log = useLogger()\n * log.set({ user: { id } })\n * }\n * ```\n */\nexport function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T> {\n const logger = storage.getStore()\n if (!logger) {\n throw new Error(\n '[evlog] useLogger() was called outside of an evlog plugin context. '\n + 'Make sure app.use(evlog()) is registered before your routes.',\n )\n }\n return logger as RequestLogger<T>\n}\n\n/**\n * Create an evlog plugin for Elysia.\n *\n * @example\n * ```ts\n * import { Elysia } from 'elysia'\n * import { evlog } from 'evlog/elysia'\n * import { createAxiomDrain } from 'evlog/axiom'\n *\n * const app = new Elysia()\n * .use(evlog({\n * drain: createAxiomDrain(),\n * enrich: (ctx) => {\n * ctx.event.region = process.env.FLY_REGION\n * },\n * }))\n * .get('/health', ({ log }) => {\n * log.set({ route: 'health' })\n * return { ok: true }\n * })\n * .listen(3000)\n * ```\n */\ninterface RequestState {\n finish: (opts?: { status?: number; error?: Error }) => Promise<unknown>\n skipped: boolean\n}\n\nexport function evlog(options: EvlogElysiaOptions = {}) {\n const emitted = new WeakSet<Request>()\n const requestState = new WeakMap<Request, RequestState>()\n\n return new Elysia({ name: 'evlog' })\n .derive({ as: 'global' }, ({ request }) => {\n const url = new URL(request.url)\n\n const { logger, finish, skipped } = createMiddlewareLogger({\n method: request.method,\n path: url.pathname,\n requestId: request.headers.get('x-request-id') || crypto.randomUUID(),\n headers: extractSafeHeaders(request.headers),\n ...options,\n })\n\n storage.enterWith(logger)\n requestState.set(request, { finish, skipped })\n\n return { log: logger }\n })\n .onAfterHandle({ as: 'global' }, async ({ request, set }) => {\n const state = requestState.get(request)\n if (!state || state.skipped || emitted.has(request)) return\n emitted.add(request)\n await state.finish({ status: set.status as number || 200 })\n })\n .onError({ as: 'global' }, async ({ request, error }) => {\n const state = requestState.get(request)\n if (!state || state.skipped || emitted.has(request)) return\n emitted.add(request)\n const logger = storage.getStore()\n const err = error instanceof Error ? error : new Error(String(error))\n if (logger) logger.error(err)\n await state.finish({ error: err })\n })\n}\n"],"mappings":";;;;;AAMA,MAAM,UAAU,IAAI,mBAAkC;;;;;;;;;;;;;;;AAwCtD,SAAgB,YAA0E;CACxF,MAAM,SAAS,QAAQ,UAAU;AACjC,KAAI,CAAC,OACH,OAAM,IAAI,MACR,kIAED;AAEH,QAAO;;AA+BT,SAAgB,MAAM,UAA8B,EAAE,EAAE;CACtD,MAAM,0BAAU,IAAI,SAAkB;CACtC,MAAM,+BAAe,IAAI,SAAgC;AAEzD,QAAO,IAAI,OAAO,EAAE,MAAM,SAAS,CAAC,CACjC,OAAO,EAAE,IAAI,UAAU,GAAG,EAAE,cAAc;EACzC,MAAM,MAAM,IAAI,IAAI,QAAQ,IAAI;EAEhC,MAAM,EAAE,QAAQ,QAAQ,YAAY,uBAAuB;GACzD,QAAQ,QAAQ;GAChB,MAAM,IAAI;GACV,WAAW,QAAQ,QAAQ,IAAI,eAAe,IAAI,OAAO,YAAY;GACrE,SAAS,mBAAmB,QAAQ,QAAQ;GAC5C,GAAG;GACJ,CAAC;AAEF,UAAQ,UAAU,OAAO;AACzB,eAAa,IAAI,SAAS;GAAE;GAAQ;GAAS,CAAC;AAE9C,SAAO,EAAE,KAAK,QAAQ;GACtB,CACD,cAAc,EAAE,IAAI,UAAU,EAAE,OAAO,EAAE,SAAS,UAAU;EAC3D,MAAM,QAAQ,aAAa,IAAI,QAAQ;AACvC,MAAI,CAAC,SAAS,MAAM,WAAW,QAAQ,IAAI,QAAQ,CAAE;AACrD,UAAQ,IAAI,QAAQ;AACpB,QAAM,MAAM,OAAO,EAAE,QAAQ,IAAI,UAAoB,KAAK,CAAC;GAC3D,CACD,QAAQ,EAAE,IAAI,UAAU,EAAE,OAAO,EAAE,SAAS,YAAY;EACvD,MAAM,QAAQ,aAAa,IAAI,QAAQ;AACvC,MAAI,CAAC,SAAS,MAAM,WAAW,QAAQ,IAAI,QAAQ,CAAE;AACrD,UAAQ,IAAI,QAAQ;EACpB,MAAM,SAAS,QAAQ,UAAU;EACjC,MAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC;AACrE,MAAI,OAAQ,QAAO,MAAM,IAAI;AAC7B,QAAM,MAAM,OAAO,EAAE,OAAO,KAAK,CAAC;GAClC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { DrainContext, EnrichContext, RequestLogger, RouteConfig, TailSamplingContext } from "../types.mjs";
|
|
2
|
+
import { RequestHandler } from "express";
|
|
3
|
+
|
|
4
|
+
//#region src/express/index.d.ts
|
|
5
|
+
interface EvlogExpressOptions {
|
|
6
|
+
/** Route patterns to include in logging (glob). If not set, all routes are logged */
|
|
7
|
+
include?: string[];
|
|
8
|
+
/** Route patterns to exclude from logging. Exclusions take precedence over inclusions */
|
|
9
|
+
exclude?: string[];
|
|
10
|
+
/** Route-specific service configuration */
|
|
11
|
+
routes?: Record<string, RouteConfig>;
|
|
12
|
+
/**
|
|
13
|
+
* Drain callback called with every emitted event.
|
|
14
|
+
* Use with drain adapters (Axiom, OTLP, Sentry, etc.) or custom endpoints.
|
|
15
|
+
*/
|
|
16
|
+
drain?: (ctx: DrainContext) => void | Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Enrich callback called after emit, before drain.
|
|
19
|
+
* Use to add derived context (geo, deployment info, user agent, etc.).
|
|
20
|
+
*/
|
|
21
|
+
enrich?: (ctx: EnrichContext) => void | Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Custom tail sampling callback.
|
|
24
|
+
* Set `ctx.shouldKeep = true` to force-keep the log regardless of head sampling.
|
|
25
|
+
*/
|
|
26
|
+
keep?: (ctx: TailSamplingContext) => void | Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
declare module 'express-serve-static-core' {
|
|
29
|
+
interface Request {
|
|
30
|
+
log: RequestLogger;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get the request-scoped logger from anywhere in the call stack.
|
|
35
|
+
* Must be called inside a request handled by the `evlog()` middleware.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* import { useLogger } from 'evlog/express'
|
|
40
|
+
*
|
|
41
|
+
* function findUser(id: string) {
|
|
42
|
+
* const log = useLogger()
|
|
43
|
+
* log.set({ user: { id } })
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
declare function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T>;
|
|
48
|
+
/**
|
|
49
|
+
* Create an evlog middleware for Express.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* import express from 'express'
|
|
54
|
+
* import { evlog } from 'evlog/express'
|
|
55
|
+
* import { createAxiomDrain } from 'evlog/axiom'
|
|
56
|
+
*
|
|
57
|
+
* const app = express()
|
|
58
|
+
* app.use(evlog({
|
|
59
|
+
* drain: createAxiomDrain(),
|
|
60
|
+
* enrich: (ctx) => {
|
|
61
|
+
* ctx.event.region = process.env.FLY_REGION
|
|
62
|
+
* },
|
|
63
|
+
* }))
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
declare function evlog(options?: EvlogExpressOptions): RequestHandler;
|
|
67
|
+
//#endregion
|
|
68
|
+
export { EvlogExpressOptions, evlog, useLogger };
|
|
69
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/express/index.ts"],"mappings":";;;;UAQiB,mBAAA;;EAEf,OAAA;EAFkC;EAIlC,OAAA;EAEwB;EAAxB,MAAA,GAAS,MAAA,SAAe,WAAA;EAKV;;;;EAAd,KAAA,IAAS,GAAA,EAAK,YAAA,YAAwB,OAAA;EAUM;;;;EAL5C,MAAA,IAAU,GAAA,EAAK,aAAA,YAAyB,OAAA;EAVxC;;;;EAeA,IAAA,IAAQ,GAAA,EAAK,mBAAA,YAA+B,OAAA;AAAA;AAAA;EAAA,UAIlC,OAAA;IACR,GAAA,EAAK,aAAA;EAAA;AAAA;;;;;;;AAJR;;;;;;;;iBAsBe,SAAA,oBAA6B,MAAA,kBAAA,CAAA,GAA4B,aAAA,CAAc,CAAA;;AAAvF;;;;;;;;;;;;;;AA6BA;;;iBAAgB,KAAA,CAAM,OAAA,GAAS,mBAAA,GAA2B,cAAA"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { n as extractSafeNodeHeaders, r as createMiddlewareLogger } from "../headers-CXOd5EyZ.mjs";
|
|
2
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
+
|
|
4
|
+
//#region src/express/index.ts
|
|
5
|
+
const storage = new AsyncLocalStorage();
|
|
6
|
+
/**
|
|
7
|
+
* Get the request-scoped logger from anywhere in the call stack.
|
|
8
|
+
* Must be called inside a request handled by the `evlog()` middleware.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { useLogger } from 'evlog/express'
|
|
13
|
+
*
|
|
14
|
+
* function findUser(id: string) {
|
|
15
|
+
* const log = useLogger()
|
|
16
|
+
* log.set({ user: { id } })
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function useLogger() {
|
|
21
|
+
const logger = storage.getStore();
|
|
22
|
+
if (!logger) throw new Error("[evlog] useLogger() was called outside of an evlog middleware context. Make sure app.use(evlog()) is registered before your routes.");
|
|
23
|
+
return logger;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create an evlog middleware for Express.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* import express from 'express'
|
|
31
|
+
* import { evlog } from 'evlog/express'
|
|
32
|
+
* import { createAxiomDrain } from 'evlog/axiom'
|
|
33
|
+
*
|
|
34
|
+
* const app = express()
|
|
35
|
+
* app.use(evlog({
|
|
36
|
+
* drain: createAxiomDrain(),
|
|
37
|
+
* enrich: (ctx) => {
|
|
38
|
+
* ctx.event.region = process.env.FLY_REGION
|
|
39
|
+
* },
|
|
40
|
+
* }))
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
function evlog(options = {}) {
|
|
44
|
+
return (req, res, next) => {
|
|
45
|
+
const { logger, finish, skipped } = createMiddlewareLogger({
|
|
46
|
+
method: req.method,
|
|
47
|
+
path: req.path,
|
|
48
|
+
requestId: req.get("x-request-id") || crypto.randomUUID(),
|
|
49
|
+
headers: extractSafeNodeHeaders(req.headers),
|
|
50
|
+
...options
|
|
51
|
+
});
|
|
52
|
+
if (skipped) {
|
|
53
|
+
next();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
req.log = logger;
|
|
57
|
+
res.on("finish", () => {
|
|
58
|
+
finish({ status: res.statusCode }).catch(() => {});
|
|
59
|
+
});
|
|
60
|
+
storage.run(logger, () => next());
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
export { evlog, useLogger };
|
|
66
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/express/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport type { Request, Response, NextFunction, RequestHandler } from 'express'\nimport type { DrainContext, EnrichContext, RequestLogger, RouteConfig, TailSamplingContext } from '../types'\nimport { createMiddlewareLogger } from '../shared/middleware'\nimport { extractSafeNodeHeaders } from '../shared/headers'\n\nconst storage = new AsyncLocalStorage<RequestLogger>()\n\nexport interface EvlogExpressOptions {\n /** Route patterns to include in logging (glob). If not set, all routes are logged */\n include?: string[]\n /** Route patterns to exclude from logging. Exclusions take precedence over inclusions */\n exclude?: string[]\n /** Route-specific service configuration */\n routes?: Record<string, RouteConfig>\n /**\n * Drain callback called with every emitted event.\n * Use with drain adapters (Axiom, OTLP, Sentry, etc.) or custom endpoints.\n */\n drain?: (ctx: DrainContext) => void | Promise<void>\n /**\n * Enrich callback called after emit, before drain.\n * Use to add derived context (geo, deployment info, user agent, etc.).\n */\n enrich?: (ctx: EnrichContext) => void | Promise<void>\n /**\n * Custom tail sampling callback.\n * Set `ctx.shouldKeep = true` to force-keep the log regardless of head sampling.\n */\n keep?: (ctx: TailSamplingContext) => void | Promise<void>\n}\n\ndeclare module 'express-serve-static-core' {\n interface Request {\n log: RequestLogger\n }\n}\n\n/**\n * Get the request-scoped logger from anywhere in the call stack.\n * Must be called inside a request handled by the `evlog()` middleware.\n *\n * @example\n * ```ts\n * import { useLogger } from 'evlog/express'\n *\n * function findUser(id: string) {\n * const log = useLogger()\n * log.set({ user: { id } })\n * }\n * ```\n */\nexport function useLogger<T extends object = Record<string, unknown>>(): RequestLogger<T> {\n const logger = storage.getStore()\n if (!logger) {\n throw new Error(\n '[evlog] useLogger() was called outside of an evlog middleware context. '\n + 'Make sure app.use(evlog()) is registered before your routes.',\n )\n }\n return logger as RequestLogger<T>\n}\n\n/**\n * Create an evlog middleware for Express.\n *\n * @example\n * ```ts\n * import express from 'express'\n * import { evlog } from 'evlog/express'\n * import { createAxiomDrain } from 'evlog/axiom'\n *\n * const app = express()\n * app.use(evlog({\n * drain: createAxiomDrain(),\n * enrich: (ctx) => {\n * ctx.event.region = process.env.FLY_REGION\n * },\n * }))\n * ```\n */\nexport function evlog(options: EvlogExpressOptions = {}): RequestHandler {\n return (req: Request, res: Response, next: NextFunction) => {\n const { logger, finish, skipped } = createMiddlewareLogger({\n method: req.method,\n path: req.path,\n requestId: req.get('x-request-id') || crypto.randomUUID(),\n headers: extractSafeNodeHeaders(req.headers),\n ...options,\n })\n\n if (skipped) {\n next()\n return\n }\n\n req.log = logger\n\n res.on('finish', () => {\n finish({ status: res.statusCode }).catch(() => {})\n })\n\n storage.run(logger, () => next())\n }\n}\n"],"mappings":";;;;AAMA,MAAM,UAAU,IAAI,mBAAkC;;;;;;;;;;;;;;;AA8CtD,SAAgB,YAA0E;CACxF,MAAM,SAAS,QAAQ,UAAU;AACjC,KAAI,CAAC,OACH,OAAM,IAAI,MACR,sIAED;AAEH,QAAO;;;;;;;;;;;;;;;;;;;;AAqBT,SAAgB,MAAM,UAA+B,EAAE,EAAkB;AACvE,SAAQ,KAAc,KAAe,SAAuB;EAC1D,MAAM,EAAE,QAAQ,QAAQ,YAAY,uBAAuB;GACzD,QAAQ,IAAI;GACZ,MAAM,IAAI;GACV,WAAW,IAAI,IAAI,eAAe,IAAI,OAAO,YAAY;GACzD,SAAS,uBAAuB,IAAI,QAAQ;GAC5C,GAAG;GACJ,CAAC;AAEF,MAAI,SAAS;AACX,SAAM;AACN;;AAGF,MAAI,MAAM;AAEV,MAAI,GAAG,gBAAgB;AACrB,UAAO,EAAE,QAAQ,IAAI,YAAY,CAAC,CAAC,YAAY,GAAG;IAClD;AAEF,UAAQ,IAAI,cAAc,MAAM,CAAC"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { filterSafeHeaders } from "./utils.mjs";
|
|
2
|
+
import { createRequestLogger, isEnabled, shouldKeep } from "./logger.mjs";
|
|
3
|
+
import { n as shouldLog, t as getServiceForPath } from "./routes-BNbrnm14.mjs";
|
|
4
|
+
import { t as extractErrorStatus } from "./nitro-Da8tEfJ3.mjs";
|
|
5
|
+
|
|
6
|
+
//#region src/shared/middleware.ts
|
|
7
|
+
const noopResult = {
|
|
8
|
+
logger: {
|
|
9
|
+
set() {},
|
|
10
|
+
error() {},
|
|
11
|
+
info() {},
|
|
12
|
+
warn() {},
|
|
13
|
+
emit() {
|
|
14
|
+
return null;
|
|
15
|
+
},
|
|
16
|
+
getContext() {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
finish: () => Promise.resolve(null),
|
|
21
|
+
skipped: true
|
|
22
|
+
};
|
|
23
|
+
async function runEnrichAndDrain(emittedEvent, options, requestInfo, responseStatus) {
|
|
24
|
+
if (options.enrich) {
|
|
25
|
+
const enrichCtx = {
|
|
26
|
+
event: emittedEvent,
|
|
27
|
+
request: requestInfo,
|
|
28
|
+
headers: options.headers,
|
|
29
|
+
response: { status: responseStatus }
|
|
30
|
+
};
|
|
31
|
+
try {
|
|
32
|
+
await options.enrich(enrichCtx);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error("[evlog] enrich failed:", err);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (options.drain) {
|
|
38
|
+
const drainCtx = {
|
|
39
|
+
event: emittedEvent,
|
|
40
|
+
request: requestInfo,
|
|
41
|
+
headers: options.headers
|
|
42
|
+
};
|
|
43
|
+
try {
|
|
44
|
+
await options.drain(drainCtx);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error("[evlog] drain failed:", err);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Create a middleware-aware request logger with full lifecycle management.
|
|
52
|
+
*
|
|
53
|
+
* Handles the complete pipeline shared across all framework integrations:
|
|
54
|
+
* route filtering, logger creation, service overrides, duration tracking,
|
|
55
|
+
* tail sampling evaluation, event emission, enrichment, and draining.
|
|
56
|
+
*
|
|
57
|
+
* Framework adapters only need to:
|
|
58
|
+
* 1. Extract method/path/requestId/headers from the framework request
|
|
59
|
+
* 2. Call `createMiddlewareLogger()` with those + user options
|
|
60
|
+
* 3. Check `skipped` — if true, skip to next middleware
|
|
61
|
+
* 4. Store `logger` in framework-specific context (e.g., `c.set('log', logger)`)
|
|
62
|
+
* 5. Call `finish({ status })` or `finish({ error })` at response end
|
|
63
|
+
*/
|
|
64
|
+
function createMiddlewareLogger(options) {
|
|
65
|
+
if (!isEnabled()) return noopResult;
|
|
66
|
+
const { method, path, requestId, include, exclude, routes, keep } = options;
|
|
67
|
+
if (!shouldLog(path, include, exclude)) return noopResult;
|
|
68
|
+
const resolvedRequestId = requestId || crypto.randomUUID();
|
|
69
|
+
const logger = createRequestLogger({
|
|
70
|
+
method,
|
|
71
|
+
path,
|
|
72
|
+
requestId: resolvedRequestId
|
|
73
|
+
});
|
|
74
|
+
const routeService = getServiceForPath(path, routes);
|
|
75
|
+
if (routeService) logger.set({ service: routeService });
|
|
76
|
+
const startTime = Date.now();
|
|
77
|
+
const requestInfo = {
|
|
78
|
+
method,
|
|
79
|
+
path,
|
|
80
|
+
requestId: resolvedRequestId
|
|
81
|
+
};
|
|
82
|
+
const finish = async (opts) => {
|
|
83
|
+
const { status, error } = opts ?? {};
|
|
84
|
+
if (error) {
|
|
85
|
+
logger.error(error);
|
|
86
|
+
const errorStatus = extractErrorStatus(error);
|
|
87
|
+
logger.set({ status: errorStatus });
|
|
88
|
+
} else if (status !== void 0) logger.set({ status });
|
|
89
|
+
const durationMs = Date.now() - startTime;
|
|
90
|
+
const resolvedStatus = error ? extractErrorStatus(error) : status ?? logger.getContext().status;
|
|
91
|
+
const tailCtx = {
|
|
92
|
+
status: resolvedStatus,
|
|
93
|
+
duration: durationMs,
|
|
94
|
+
path,
|
|
95
|
+
method,
|
|
96
|
+
context: logger.getContext(),
|
|
97
|
+
shouldKeep: false
|
|
98
|
+
};
|
|
99
|
+
if (keep) await keep(tailCtx);
|
|
100
|
+
const forceKeep = tailCtx.shouldKeep || shouldKeep(tailCtx);
|
|
101
|
+
const emittedEvent = logger.emit({ _forceKeep: forceKeep });
|
|
102
|
+
if (emittedEvent && (options.enrich || options.drain)) await runEnrichAndDrain(emittedEvent, options, requestInfo, resolvedStatus);
|
|
103
|
+
return emittedEvent;
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
logger,
|
|
107
|
+
finish,
|
|
108
|
+
skipped: false
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/shared/headers.ts
|
|
114
|
+
/**
|
|
115
|
+
* Extract headers from a Web API `Headers` object and filter out sensitive ones.
|
|
116
|
+
* Works with any runtime that supports the standard `Headers` API (Hono, Elysia,
|
|
117
|
+
* Nitro v3, Cloudflare Workers, Bun, Deno, etc.).
|
|
118
|
+
*/
|
|
119
|
+
function extractSafeHeaders(headers) {
|
|
120
|
+
const raw = {};
|
|
121
|
+
headers.forEach((value, key) => {
|
|
122
|
+
raw[key] = value;
|
|
123
|
+
});
|
|
124
|
+
return filterSafeHeaders(raw);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Extract headers from Node.js `IncomingHttpHeaders` and filter out sensitive ones.
|
|
128
|
+
* Works with Express, Fastify, and any Node.js HTTP server using `req.headers`.
|
|
129
|
+
*/
|
|
130
|
+
function extractSafeNodeHeaders(headers) {
|
|
131
|
+
const raw = {};
|
|
132
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
133
|
+
if (value === void 0) continue;
|
|
134
|
+
raw[key] = Array.isArray(value) ? value.join(", ") : value;
|
|
135
|
+
}
|
|
136
|
+
return filterSafeHeaders(raw);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//#endregion
|
|
140
|
+
export { extractSafeNodeHeaders as n, createMiddlewareLogger as r, extractSafeHeaders as t };
|
|
141
|
+
//# sourceMappingURL=headers-CXOd5EyZ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headers-CXOd5EyZ.mjs","names":[],"sources":["../src/shared/middleware.ts","../src/shared/headers.ts"],"sourcesContent":["import type { DrainContext, EnrichContext, RequestLogger, RouteConfig, TailSamplingContext, WideEvent } from '../types'\nimport { createRequestLogger, isEnabled, shouldKeep } from '../logger'\nimport { extractErrorStatus } from '../nitro'\nimport { shouldLog, getServiceForPath } from './routes'\n\n/**\n * Base options shared by all framework integrations.\n * Each framework adapter spreads its user-facing options into this.\n */\nexport interface MiddlewareLoggerOptions {\n method: string\n path: string\n requestId?: string\n include?: string[]\n exclude?: string[]\n routes?: Record<string, RouteConfig>\n /** Tail sampling callback — set `ctx.shouldKeep = true` to force-keep */\n keep?: (ctx: TailSamplingContext) => void | Promise<void>\n /** Drain callback — send events to external services */\n drain?: (ctx: DrainContext) => void | Promise<void>\n /** Enrich callback — add derived context after emit, before drain */\n enrich?: (ctx: EnrichContext) => void | Promise<void>\n /** Pre-filtered safe request headers (used for enrich/drain context) */\n headers?: Record<string, string>\n}\n\nexport interface MiddlewareLoggerResult {\n logger: RequestLogger\n finish: (opts?: { status?: number; error?: Error }) => Promise<WideEvent | null>\n skipped: boolean\n}\n\nconst noopResult: MiddlewareLoggerResult = {\n logger: {\n set() {},\n error() {},\n info() {},\n warn() {},\n emit() {\n return null \n },\n getContext() {\n return {} \n },\n },\n finish: () => Promise.resolve(null),\n skipped: true,\n}\n\nasync function runEnrichAndDrain(\n emittedEvent: WideEvent,\n options: MiddlewareLoggerOptions,\n requestInfo: { method: string; path: string; requestId?: string },\n responseStatus?: number,\n): Promise<void> {\n if (options.enrich) {\n const enrichCtx: EnrichContext = {\n event: emittedEvent,\n request: requestInfo,\n headers: options.headers,\n response: { status: responseStatus },\n }\n try {\n await options.enrich(enrichCtx)\n } catch (err) {\n console.error('[evlog] enrich failed:', err)\n }\n }\n\n if (options.drain) {\n const drainCtx: DrainContext = {\n event: emittedEvent,\n request: requestInfo,\n headers: options.headers,\n }\n try {\n await options.drain(drainCtx)\n } catch (err) {\n console.error('[evlog] drain failed:', err)\n }\n }\n}\n\n/**\n * Create a middleware-aware request logger with full lifecycle management.\n *\n * Handles the complete pipeline shared across all framework integrations:\n * route filtering, logger creation, service overrides, duration tracking,\n * tail sampling evaluation, event emission, enrichment, and draining.\n *\n * Framework adapters only need to:\n * 1. Extract method/path/requestId/headers from the framework request\n * 2. Call `createMiddlewareLogger()` with those + user options\n * 3. Check `skipped` — if true, skip to next middleware\n * 4. Store `logger` in framework-specific context (e.g., `c.set('log', logger)`)\n * 5. Call `finish({ status })` or `finish({ error })` at response end\n */\nexport function createMiddlewareLogger(options: MiddlewareLoggerOptions): MiddlewareLoggerResult {\n if (!isEnabled()) return noopResult\n\n const { method, path, requestId, include, exclude, routes, keep } = options\n\n if (!shouldLog(path, include, exclude)) {\n return noopResult\n }\n\n const resolvedRequestId = requestId || crypto.randomUUID()\n\n const logger = createRequestLogger({\n method,\n path,\n requestId: resolvedRequestId,\n })\n\n const routeService = getServiceForPath(path, routes)\n if (routeService) {\n logger.set({ service: routeService })\n }\n\n const startTime = Date.now()\n const requestInfo = { method, path, requestId: resolvedRequestId }\n\n const finish = async (opts?: { status?: number; error?: Error }): Promise<WideEvent | null> => {\n const { status, error } = opts ?? {}\n\n if (error) {\n logger.error(error)\n const errorStatus = extractErrorStatus(error)\n logger.set({ status: errorStatus })\n } else if (status !== undefined) {\n logger.set({ status })\n }\n\n const durationMs = Date.now() - startTime\n\n const resolvedStatus = error\n ? extractErrorStatus(error)\n : status ?? (logger.getContext().status as number | undefined)\n\n const tailCtx: TailSamplingContext = {\n status: resolvedStatus,\n duration: durationMs,\n path,\n method,\n context: logger.getContext(),\n shouldKeep: false,\n }\n\n if (keep) {\n await keep(tailCtx)\n }\n\n const forceKeep = tailCtx.shouldKeep || shouldKeep(tailCtx)\n const emittedEvent = logger.emit({ _forceKeep: forceKeep })\n\n if (emittedEvent && (options.enrich || options.drain)) {\n await runEnrichAndDrain(emittedEvent, options, requestInfo, resolvedStatus)\n }\n\n return emittedEvent\n }\n\n return { logger, finish, skipped: false }\n}\n","import { filterSafeHeaders } from '../utils'\n\n/**\n * Extract headers from a Web API `Headers` object and filter out sensitive ones.\n * Works with any runtime that supports the standard `Headers` API (Hono, Elysia,\n * Nitro v3, Cloudflare Workers, Bun, Deno, etc.).\n */\nexport function extractSafeHeaders(headers: Headers): Record<string, string> {\n const raw: Record<string, string> = {}\n headers.forEach((value, key) => {\n raw[key] = value\n })\n return filterSafeHeaders(raw)\n}\n\n/**\n * Extract headers from Node.js `IncomingHttpHeaders` and filter out sensitive ones.\n * Works with Express, Fastify, and any Node.js HTTP server using `req.headers`.\n */\nexport function extractSafeNodeHeaders(headers: Record<string, string | string[] | undefined>): Record<string, string> {\n const raw: Record<string, string> = {}\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue\n raw[key] = Array.isArray(value) ? value.join(', ') : value\n }\n return filterSafeHeaders(raw)\n}\n"],"mappings":";;;;;;AAgCA,MAAM,aAAqC;CACzC,QAAQ;EACN,MAAM;EACN,QAAQ;EACR,OAAO;EACP,OAAO;EACP,OAAO;AACL,UAAO;;EAET,aAAa;AACX,UAAO,EAAE;;EAEZ;CACD,cAAc,QAAQ,QAAQ,KAAK;CACnC,SAAS;CACV;AAED,eAAe,kBACb,cACA,SACA,aACA,gBACe;AACf,KAAI,QAAQ,QAAQ;EAClB,MAAM,YAA2B;GAC/B,OAAO;GACP,SAAS;GACT,SAAS,QAAQ;GACjB,UAAU,EAAE,QAAQ,gBAAgB;GACrC;AACD,MAAI;AACF,SAAM,QAAQ,OAAO,UAAU;WACxB,KAAK;AACZ,WAAQ,MAAM,0BAA0B,IAAI;;;AAIhD,KAAI,QAAQ,OAAO;EACjB,MAAM,WAAyB;GAC7B,OAAO;GACP,SAAS;GACT,SAAS,QAAQ;GAClB;AACD,MAAI;AACF,SAAM,QAAQ,MAAM,SAAS;WACtB,KAAK;AACZ,WAAQ,MAAM,yBAAyB,IAAI;;;;;;;;;;;;;;;;;;AAmBjD,SAAgB,uBAAuB,SAA0D;AAC/F,KAAI,CAAC,WAAW,CAAE,QAAO;CAEzB,MAAM,EAAE,QAAQ,MAAM,WAAW,SAAS,SAAS,QAAQ,SAAS;AAEpE,KAAI,CAAC,UAAU,MAAM,SAAS,QAAQ,CACpC,QAAO;CAGT,MAAM,oBAAoB,aAAa,OAAO,YAAY;CAE1D,MAAM,SAAS,oBAAoB;EACjC;EACA;EACA,WAAW;EACZ,CAAC;CAEF,MAAM,eAAe,kBAAkB,MAAM,OAAO;AACpD,KAAI,aACF,QAAO,IAAI,EAAE,SAAS,cAAc,CAAC;CAGvC,MAAM,YAAY,KAAK,KAAK;CAC5B,MAAM,cAAc;EAAE;EAAQ;EAAM,WAAW;EAAmB;CAElE,MAAM,SAAS,OAAO,SAAyE;EAC7F,MAAM,EAAE,QAAQ,UAAU,QAAQ,EAAE;AAEpC,MAAI,OAAO;AACT,UAAO,MAAM,MAAM;GACnB,MAAM,cAAc,mBAAmB,MAAM;AAC7C,UAAO,IAAI,EAAE,QAAQ,aAAa,CAAC;aAC1B,WAAW,OACpB,QAAO,IAAI,EAAE,QAAQ,CAAC;EAGxB,MAAM,aAAa,KAAK,KAAK,GAAG;EAEhC,MAAM,iBAAiB,QACnB,mBAAmB,MAAM,GACzB,UAAW,OAAO,YAAY,CAAC;EAEnC,MAAM,UAA+B;GACnC,QAAQ;GACR,UAAU;GACV;GACA;GACA,SAAS,OAAO,YAAY;GAC5B,YAAY;GACb;AAED,MAAI,KACF,OAAM,KAAK,QAAQ;EAGrB,MAAM,YAAY,QAAQ,cAAc,WAAW,QAAQ;EAC3D,MAAM,eAAe,OAAO,KAAK,EAAE,YAAY,WAAW,CAAC;AAE3D,MAAI,iBAAiB,QAAQ,UAAU,QAAQ,OAC7C,OAAM,kBAAkB,cAAc,SAAS,aAAa,eAAe;AAG7E,SAAO;;AAGT,QAAO;EAAE;EAAQ;EAAQ,SAAS;EAAO;;;;;;;;;;AC3J3C,SAAgB,mBAAmB,SAA0C;CAC3E,MAAM,MAA8B,EAAE;AACtC,SAAQ,SAAS,OAAO,QAAQ;AAC9B,MAAI,OAAO;GACX;AACF,QAAO,kBAAkB,IAAI;;;;;;AAO/B,SAAgB,uBAAuB,SAAgF;CACrH,MAAM,MAA8B,EAAE;AACtC,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,EAAE;AAClD,MAAI,UAAU,OAAW;AACzB,MAAI,OAAO,MAAM,QAAQ,MAAM,GAAG,MAAM,KAAK,KAAK,GAAG;;AAEvD,QAAO,kBAAkB,IAAI"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { DrainContext, EnrichContext, RequestLogger, RouteConfig, TailSamplingContext } from "../types.mjs";
|
|
2
|
+
import { MiddlewareHandler } from "hono";
|
|
3
|
+
|
|
4
|
+
//#region src/hono/index.d.ts
|
|
5
|
+
interface EvlogHonoOptions {
|
|
6
|
+
/** Route patterns to include in logging (glob). If not set, all routes are logged */
|
|
7
|
+
include?: string[];
|
|
8
|
+
/** Route patterns to exclude from logging. Exclusions take precedence over inclusions */
|
|
9
|
+
exclude?: string[];
|
|
10
|
+
/** Route-specific service configuration */
|
|
11
|
+
routes?: Record<string, RouteConfig>;
|
|
12
|
+
/**
|
|
13
|
+
* Drain callback called with every emitted event.
|
|
14
|
+
* Use with drain adapters (Axiom, OTLP, Sentry, etc.) or custom endpoints.
|
|
15
|
+
*/
|
|
16
|
+
drain?: (ctx: DrainContext) => void | Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Enrich callback called after emit, before drain.
|
|
19
|
+
* Use to add derived context (geo, deployment info, user agent, etc.).
|
|
20
|
+
*/
|
|
21
|
+
enrich?: (ctx: EnrichContext) => void | Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Custom tail sampling callback.
|
|
24
|
+
* Set `ctx.shouldKeep = true` to force-keep the log regardless of head sampling.
|
|
25
|
+
*/
|
|
26
|
+
keep?: (ctx: TailSamplingContext) => void | Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Hono variables type for typed `c.get('log')` access.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const app = new Hono<EvlogVariables>()
|
|
34
|
+
* app.use(evlog())
|
|
35
|
+
* app.get('/api/users', (c) => {
|
|
36
|
+
* const log = c.get('log')
|
|
37
|
+
* log.set({ users: { count: 42 } })
|
|
38
|
+
* return c.json({ users: [] })
|
|
39
|
+
* })
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
type EvlogVariables = {
|
|
43
|
+
Variables: {
|
|
44
|
+
log: RequestLogger;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Create an evlog middleware for Hono.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* import { Hono } from 'hono'
|
|
53
|
+
* import { evlog, type EvlogVariables } from 'evlog/hono'
|
|
54
|
+
* import { createAxiomDrain } from 'evlog/axiom'
|
|
55
|
+
*
|
|
56
|
+
* const app = new Hono<EvlogVariables>()
|
|
57
|
+
* app.use(evlog({
|
|
58
|
+
* drain: createAxiomDrain(),
|
|
59
|
+
* enrich: (ctx) => {
|
|
60
|
+
* ctx.event.region = process.env.FLY_REGION
|
|
61
|
+
* },
|
|
62
|
+
* }))
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
declare function evlog(options?: EvlogHonoOptions): MiddlewareHandler;
|
|
66
|
+
//#endregion
|
|
67
|
+
export { EvlogHonoOptions, EvlogVariables, evlog };
|
|
68
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/hono/index.ts"],"mappings":";;;;UAKiB,gBAAA;;EAEf,OAAA;EAF+B;EAI/B,OAAA;EAEwB;EAAxB,MAAA,GAAS,MAAA,SAAe,WAAA;EAKV;;;;EAAd,KAAA,IAAS,GAAA,EAAK,YAAA,YAAwB,OAAA;EAUM;;;;EAL5C,MAAA,IAAU,GAAA,EAAK,aAAA,YAAyB,OAAA;EAVxC;;;;EAeA,IAAA,IAAQ,GAAA,EAAK,mBAAA,YAA+B,OAAA;AAAA;;;;;;;;;;;;AAiB9C;;;KAAY,cAAA;EAAmB,SAAA;IAAa,GAAA,EAAK,aAAA;EAAA;AAAA;;AAoBjD;;;;;;;;;;;;;;;;;iBAAgB,KAAA,CAAM,OAAA,GAAS,gBAAA,GAAwB,iBAAA"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { r as createMiddlewareLogger, t as extractSafeHeaders } from "../headers-CXOd5EyZ.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/hono/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* Create an evlog middleware for Hono.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { Hono } from 'hono'
|
|
10
|
+
* import { evlog, type EvlogVariables } from 'evlog/hono'
|
|
11
|
+
* import { createAxiomDrain } from 'evlog/axiom'
|
|
12
|
+
*
|
|
13
|
+
* const app = new Hono<EvlogVariables>()
|
|
14
|
+
* app.use(evlog({
|
|
15
|
+
* drain: createAxiomDrain(),
|
|
16
|
+
* enrich: (ctx) => {
|
|
17
|
+
* ctx.event.region = process.env.FLY_REGION
|
|
18
|
+
* },
|
|
19
|
+
* }))
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
function evlog(options = {}) {
|
|
23
|
+
return async (c, next) => {
|
|
24
|
+
const { logger, finish, skipped } = createMiddlewareLogger({
|
|
25
|
+
method: c.req.method,
|
|
26
|
+
path: c.req.path,
|
|
27
|
+
requestId: c.req.header("x-request-id") || crypto.randomUUID(),
|
|
28
|
+
headers: extractSafeHeaders(c.req.raw.headers),
|
|
29
|
+
...options
|
|
30
|
+
});
|
|
31
|
+
if (skipped) {
|
|
32
|
+
await next();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
c.set("log", logger);
|
|
36
|
+
try {
|
|
37
|
+
await next();
|
|
38
|
+
await finish({ status: c.res.status });
|
|
39
|
+
} catch (error) {
|
|
40
|
+
await finish({ error });
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { evlog };
|
|
48
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/hono/index.ts"],"sourcesContent":["import type { MiddlewareHandler } from 'hono'\nimport type { DrainContext, EnrichContext, RequestLogger, RouteConfig, TailSamplingContext } from '../types'\nimport { createMiddlewareLogger } from '../shared/middleware'\nimport { extractSafeHeaders } from '../shared/headers'\n\nexport interface EvlogHonoOptions {\n /** Route patterns to include in logging (glob). If not set, all routes are logged */\n include?: string[]\n /** Route patterns to exclude from logging. Exclusions take precedence over inclusions */\n exclude?: string[]\n /** Route-specific service configuration */\n routes?: Record<string, RouteConfig>\n /**\n * Drain callback called with every emitted event.\n * Use with drain adapters (Axiom, OTLP, Sentry, etc.) or custom endpoints.\n */\n drain?: (ctx: DrainContext) => void | Promise<void>\n /**\n * Enrich callback called after emit, before drain.\n * Use to add derived context (geo, deployment info, user agent, etc.).\n */\n enrich?: (ctx: EnrichContext) => void | Promise<void>\n /**\n * Custom tail sampling callback.\n * Set `ctx.shouldKeep = true` to force-keep the log regardless of head sampling.\n */\n keep?: (ctx: TailSamplingContext) => void | Promise<void>\n}\n\n/**\n * Hono variables type for typed `c.get('log')` access.\n *\n * @example\n * ```ts\n * const app = new Hono<EvlogVariables>()\n * app.use(evlog())\n * app.get('/api/users', (c) => {\n * const log = c.get('log')\n * log.set({ users: { count: 42 } })\n * return c.json({ users: [] })\n * })\n * ```\n */\nexport type EvlogVariables = { Variables: { log: RequestLogger } }\n\n/**\n * Create an evlog middleware for Hono.\n *\n * @example\n * ```ts\n * import { Hono } from 'hono'\n * import { evlog, type EvlogVariables } from 'evlog/hono'\n * import { createAxiomDrain } from 'evlog/axiom'\n *\n * const app = new Hono<EvlogVariables>()\n * app.use(evlog({\n * drain: createAxiomDrain(),\n * enrich: (ctx) => {\n * ctx.event.region = process.env.FLY_REGION\n * },\n * }))\n * ```\n */\nexport function evlog(options: EvlogHonoOptions = {}): MiddlewareHandler {\n return async (c, next) => {\n const { logger, finish, skipped } = createMiddlewareLogger({\n method: c.req.method,\n path: c.req.path,\n requestId: c.req.header('x-request-id') || crypto.randomUUID(),\n headers: extractSafeHeaders(c.req.raw.headers),\n ...options,\n })\n\n if (skipped) {\n await next()\n return\n }\n\n c.set('log', logger)\n\n try {\n await next()\n await finish({ status: c.res.status })\n } catch (error) {\n await finish({ error: error as Error })\n throw error\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA+DA,SAAgB,MAAM,UAA4B,EAAE,EAAqB;AACvE,QAAO,OAAO,GAAG,SAAS;EACxB,MAAM,EAAE,QAAQ,QAAQ,YAAY,uBAAuB;GACzD,QAAQ,EAAE,IAAI;GACd,MAAM,EAAE,IAAI;GACZ,WAAW,EAAE,IAAI,OAAO,eAAe,IAAI,OAAO,YAAY;GAC9D,SAAS,mBAAmB,EAAE,IAAI,IAAI,QAAQ;GAC9C,GAAG;GACJ,CAAC;AAEF,MAAI,SAAS;AACX,SAAM,MAAM;AACZ;;AAGF,IAAE,IAAI,OAAO,OAAO;AAEpB,MAAI;AACF,SAAM,MAAM;AACZ,SAAM,OAAO,EAAE,QAAQ,EAAE,IAAI,QAAQ,CAAC;WAC/B,OAAO;AACd,SAAM,OAAO,EAAS,OAAgB,CAAC;AACvC,SAAM"}
|
package/dist/nitro/module.d.mts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nitro-
|
|
1
|
+
{"version":3,"file":"nitro-Nxg6qcXd.d.mts","names":[],"sources":["../src/nitro.ts"],"mappings":";;;UAIiB,kBAAA;EAAA;;;;EAKf,OAAA;EA8BwB;;;EAzBxB,GAAA,GAAM,OAAA,CAAQ,kBAAA;EA8BW;;;;EAxBzB,MAAA;EAAA;;;;;EAOA,OAAA;EAiBA;;;;;EAVA,OAAA;;;;EAKA,MAAA,GAAS,MAAA,SAAe,WAAA;;;;EAKxB,QAAA,GAAW,cAAA;AAAA"}
|
package/dist/nuxt/module.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evlog",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Wide event logging library with structured error handling. Inspired by LoggingSucks.",
|
|
5
5
|
"author": "HugoRCD <contact@hrcd.fr>",
|
|
6
6
|
"homepage": "https://evlog.dev",
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
"nitro",
|
|
21
21
|
"nextjs",
|
|
22
22
|
"tanstack-start",
|
|
23
|
+
"express",
|
|
24
|
+
"hono",
|
|
25
|
+
"elysia",
|
|
23
26
|
"typescript"
|
|
24
27
|
],
|
|
25
28
|
"license": "MIT",
|
|
@@ -85,6 +88,18 @@
|
|
|
85
88
|
"./next/client": {
|
|
86
89
|
"types": "./dist/next/client.d.mts",
|
|
87
90
|
"import": "./dist/next/client.mjs"
|
|
91
|
+
},
|
|
92
|
+
"./hono": {
|
|
93
|
+
"types": "./dist/hono/index.d.mts",
|
|
94
|
+
"import": "./dist/hono/index.mjs"
|
|
95
|
+
},
|
|
96
|
+
"./express": {
|
|
97
|
+
"types": "./dist/express/index.d.mts",
|
|
98
|
+
"import": "./dist/express/index.mjs"
|
|
99
|
+
},
|
|
100
|
+
"./elysia": {
|
|
101
|
+
"types": "./dist/elysia/index.d.mts",
|
|
102
|
+
"import": "./dist/elysia/index.mjs"
|
|
88
103
|
}
|
|
89
104
|
},
|
|
90
105
|
"main": "./dist/index.mjs",
|
|
@@ -135,6 +150,15 @@
|
|
|
135
150
|
],
|
|
136
151
|
"next/client": [
|
|
137
152
|
"./dist/next/client.d.mts"
|
|
153
|
+
],
|
|
154
|
+
"hono": [
|
|
155
|
+
"./dist/hono/index.d.mts"
|
|
156
|
+
],
|
|
157
|
+
"express": [
|
|
158
|
+
"./dist/express/index.d.mts"
|
|
159
|
+
],
|
|
160
|
+
"elysia": [
|
|
161
|
+
"./dist/elysia/index.d.mts"
|
|
138
162
|
]
|
|
139
163
|
}
|
|
140
164
|
},
|
|
@@ -154,19 +178,24 @@
|
|
|
154
178
|
"typecheck": "echo 'Typecheck handled by build'"
|
|
155
179
|
},
|
|
156
180
|
"devDependencies": {
|
|
157
|
-
"ufo": "^1.6.3",
|
|
158
181
|
"@nuxt/devtools": "^3.2.2",
|
|
159
182
|
"@nuxt/schema": "^4.3.1",
|
|
160
183
|
"@nuxt/test-utils": "^4.0.0",
|
|
184
|
+
"@types/express": "^5.0.6",
|
|
185
|
+
"@types/supertest": "^7.2.0",
|
|
161
186
|
"changelogen": "^0.6.2",
|
|
162
187
|
"consola": "^3.4.2",
|
|
188
|
+
"elysia": "^1.4.27",
|
|
189
|
+
"express": "^5.2.1",
|
|
163
190
|
"h3": "^1.15.5",
|
|
164
|
-
"happy-dom": "^20.7.
|
|
191
|
+
"happy-dom": "^20.7.2",
|
|
165
192
|
"nitro": "^3.0.1-alpha.2",
|
|
166
193
|
"nitropack": "^2.13.1",
|
|
167
194
|
"nuxt": "^4.3.1",
|
|
195
|
+
"supertest": "^7.2.2",
|
|
168
196
|
"tsdown": "^0.20.3",
|
|
169
|
-
"typescript": "^5.9.3"
|
|
197
|
+
"typescript": "^5.9.3",
|
|
198
|
+
"ufo": "^1.6.3"
|
|
170
199
|
},
|
|
171
200
|
"peerDependencies": {
|
|
172
201
|
"@nuxt/kit": "^4.3.1",
|
|
@@ -175,7 +204,10 @@
|
|
|
175
204
|
"ofetch": "^1.5.1",
|
|
176
205
|
"nitro": "^3.0.1-alpha.2",
|
|
177
206
|
"next": ">=16.1.6",
|
|
178
|
-
"react": ">=19.2.4"
|
|
207
|
+
"react": ">=19.2.4",
|
|
208
|
+
"hono": "",
|
|
209
|
+
"express": ">=4.21.0",
|
|
210
|
+
"elysia": ">=1.0.0"
|
|
179
211
|
},
|
|
180
212
|
"peerDependenciesMeta": {
|
|
181
213
|
"@nuxt/kit": {
|
|
@@ -198,6 +230,15 @@
|
|
|
198
230
|
},
|
|
199
231
|
"react": {
|
|
200
232
|
"optional": true
|
|
233
|
+
},
|
|
234
|
+
"hono": {
|
|
235
|
+
"optional": true
|
|
236
|
+
},
|
|
237
|
+
"express": {
|
|
238
|
+
"optional": true
|
|
239
|
+
},
|
|
240
|
+
"elysia": {
|
|
241
|
+
"optional": true
|
|
201
242
|
}
|
|
202
243
|
}
|
|
203
244
|
}
|