evlog 2.0.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/{_severity-D_IU9-90.mjs → _severity-78FkT5MD.mjs} +1 -1
- package/dist/{_severity-D_IU9-90.mjs.map → _severity-78FkT5MD.mjs.map} +1 -1
- package/dist/adapters/axiom.d.mts +0 -2
- package/dist/adapters/axiom.d.mts.map +1 -1
- package/dist/adapters/better-stack.d.mts +0 -2
- package/dist/adapters/better-stack.d.mts.map +1 -1
- package/dist/adapters/otlp.d.mts +0 -2
- package/dist/adapters/otlp.d.mts.map +1 -1
- package/dist/adapters/otlp.mjs +1 -1
- package/dist/adapters/posthog.d.mts +0 -2
- package/dist/adapters/posthog.d.mts.map +1 -1
- package/dist/adapters/sentry.d.mts +0 -2
- package/dist/adapters/sentry.d.mts.map +1 -1
- package/dist/adapters/sentry.mjs +1 -1
- package/dist/dist-By0jiJRA.mjs +52 -0
- package/dist/dist-By0jiJRA.mjs.map +1 -0
- 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/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/logger.d.mts +14 -1
- package/dist/logger.d.mts.map +1 -1
- package/dist/logger.mjs +29 -14
- package/dist/logger.mjs.map +1 -1
- package/dist/next/client.d.mts +8 -0
- package/dist/next/client.d.mts.map +1 -1
- package/dist/next/client.mjs +3 -1
- package/dist/next/client.mjs.map +1 -1
- package/dist/next/index.d.mts +0 -1
- package/dist/next/index.d.mts.map +1 -1
- package/dist/nitro/module.d.mts +3 -2
- package/dist/nitro/module.d.mts.map +1 -1
- package/dist/nitro/module.mjs +1 -0
- package/dist/nitro/module.mjs.map +1 -1
- package/dist/nitro/v3/errorHandler.mjs +1 -1
- package/dist/nitro/v3/index.d.mts +0 -1
- package/dist/nitro/v3/module.d.mts +3 -2
- package/dist/nitro/v3/module.d.mts.map +1 -1
- package/dist/nitro/v3/module.mjs +2 -0
- package/dist/nitro/v3/module.mjs.map +1 -1
- package/dist/nitro/v3/plugin.mjs +1 -1
- package/dist/{nitro-CrFBjY1Y.d.mts → nitro-Nxg6qcXd.d.mts} +1 -1
- package/dist/{nitro-CrFBjY1Y.d.mts.map → nitro-Nxg6qcXd.d.mts.map} +1 -1
- package/dist/nuxt/module.d.mts +7 -0
- package/dist/nuxt/module.d.mts.map +1 -1
- package/dist/nuxt/module.mjs +2 -1
- package/dist/nuxt/module.mjs.map +1 -1
- package/dist/runtime/client/log.d.mts +1 -0
- package/dist/runtime/client/log.d.mts.map +1 -1
- package/dist/runtime/client/log.mjs +10 -6
- package/dist/runtime/client/log.mjs.map +1 -1
- package/dist/runtime/client/plugin.mjs +1 -0
- package/dist/runtime/client/plugin.mjs.map +1 -1
- package/package.json +47 -6
|
@@ -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/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BaseWideEvent, DeepPartial, DrainContext, EnrichContext, EnvironmentContext, ErrorOptions, FieldContext, H3EventContext, IngestPayload, InternalFields, Log, LogLevel, LoggerConfig, ParsedError, RequestLogger, RequestLoggerOptions, SamplingConfig, SamplingRates, ServerEvent, TailSamplingCondition, TailSamplingContext, TransportConfig, WideEvent } from "./types.mjs";
|
|
2
2
|
import { EvlogError, createError } from "./error.mjs";
|
|
3
|
-
import { createRequestLogger, getEnvironment, initLogger, isEnabled, log as _log, shouldKeep } from "./logger.mjs";
|
|
3
|
+
import { createLogger, createRequestLogger, getEnvironment, initLogger, isEnabled, log as _log, shouldKeep } from "./logger.mjs";
|
|
4
4
|
import { useLogger } from "./runtime/server/useLogger.mjs";
|
|
5
5
|
import { parseError } from "./runtime/utils/parseError.mjs";
|
|
6
|
-
export { type BaseWideEvent, type DeepPartial, type DrainContext, type EnrichContext, type EnvironmentContext, type ErrorOptions, EvlogError, type FieldContext, type H3EventContext, type IngestPayload, type InternalFields, type Log, type LogLevel, type LoggerConfig, type ParsedError, type RequestLogger, type RequestLoggerOptions, type SamplingConfig, type SamplingRates, type ServerEvent, type TailSamplingCondition, type TailSamplingContext, type TransportConfig, type WideEvent, createError, createError as createEvlogError, createRequestLogger, getEnvironment, initLogger, isEnabled, _log as log, parseError, shouldKeep, useLogger };
|
|
6
|
+
export { type BaseWideEvent, type DeepPartial, type DrainContext, type EnrichContext, type EnvironmentContext, type ErrorOptions, EvlogError, type FieldContext, type H3EventContext, type IngestPayload, type InternalFields, type Log, type LogLevel, type LoggerConfig, type ParsedError, type RequestLogger, type RequestLoggerOptions, type SamplingConfig, type SamplingRates, type ServerEvent, type TailSamplingCondition, type TailSamplingContext, type TransportConfig, type WideEvent, createError, createError as createEvlogError, createLogger, createRequestLogger, getEnvironment, initLogger, isEnabled, _log as log, parseError, shouldKeep, useLogger };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EvlogError, createError } from "./error.mjs";
|
|
2
|
-
import { createRequestLogger, getEnvironment, initLogger, isEnabled, log as _log, shouldKeep } from "./logger.mjs";
|
|
2
|
+
import { createLogger, createRequestLogger, getEnvironment, initLogger, isEnabled, log as _log, shouldKeep } from "./logger.mjs";
|
|
3
3
|
import { useLogger } from "./runtime/server/useLogger.mjs";
|
|
4
4
|
import { parseError } from "./runtime/utils/parseError.mjs";
|
|
5
5
|
|
|
6
|
-
export { EvlogError, createError, createError as createEvlogError, createRequestLogger, getEnvironment, initLogger, isEnabled, _log as log, parseError, shouldKeep, useLogger };
|
|
6
|
+
export { EvlogError, createError, createError as createEvlogError, createLogger, createRequestLogger, getEnvironment, initLogger, isEnabled, _log as log, parseError, shouldKeep, useLogger };
|
package/dist/logger.d.mts
CHANGED
|
@@ -25,8 +25,21 @@ declare function shouldKeep(ctx: TailSamplingContext): boolean;
|
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
27
|
declare const _log: Log;
|
|
28
|
+
/**
|
|
29
|
+
* Create a scoped logger for building wide events.
|
|
30
|
+
* Use this for any context: workflows, jobs, scripts, queues, etc.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* const log = createLogger({ jobId: job.id, queue: 'emails' })
|
|
35
|
+
* log.set({ batch: { size: 50, processed: 12 } })
|
|
36
|
+
* log.emit()
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
declare function createLogger<T extends object = Record<string, unknown>>(initialContext?: Record<string, unknown>): RequestLogger<T>;
|
|
28
40
|
/**
|
|
29
41
|
* Create a request-scoped logger for building wide events.
|
|
42
|
+
* Convenience wrapper around `createLogger` that pre-populates HTTP request fields.
|
|
30
43
|
*
|
|
31
44
|
* @example
|
|
32
45
|
* ```ts
|
|
@@ -42,5 +55,5 @@ declare function createRequestLogger<T extends object = Record<string, unknown>>
|
|
|
42
55
|
*/
|
|
43
56
|
declare function getEnvironment(): EnvironmentContext;
|
|
44
57
|
//#endregion
|
|
45
|
-
export { createRequestLogger, getEnvironment, initLogger, isEnabled, _log as log, shouldKeep };
|
|
58
|
+
export { createLogger, createRequestLogger, getEnvironment, initLogger, isEnabled, _log as log, shouldKeep };
|
|
46
59
|
//# sourceMappingURL=logger.d.mts.map
|
package/dist/logger.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.mts","names":[],"sources":["../src/logger.ts"],"mappings":";;;;;AAoCA;;iBAAgB,UAAA,CAAW,MAAA,GAAQ,YAAA;;;AAqBnC;iBAAgB,SAAA,CAAA;;;;AA8BhB;iBAAgB,UAAA,CAAW,GAAA,EAAK,mBAAA;;;;AAgB/B;;;;;AAkKD;cAjCM,IAAA,EAAM,GAAA;;;;;;;;;;;;iBAiCI,mBAAA,oBAAuC,MAAA,kBAAA,CAAyB,OAAA,GAAS,oBAAA,GAA4B,aAAA,CAAc,CAAA
|
|
1
|
+
{"version":3,"file":"logger.d.mts","names":[],"sources":["../src/logger.ts"],"mappings":";;;;;AAoCA;;iBAAgB,UAAA,CAAW,MAAA,GAAQ,YAAA;;;AAqBnC;iBAAgB,SAAA,CAAA;;;;AA8BhB;iBAAgB,UAAA,CAAW,GAAA,EAAK,mBAAA;;;;AAgB/B;;;;;AAkKD;cAjCM,IAAA,EAAM,GAAA;;;;;;;;;;;;iBAiCI,YAAA,oBAAgC,MAAA,kBAAA,CAAyB,cAAA,GAAgB,MAAA,oBAA+B,aAAA,CAAc,CAAA;;;AAsHtI;;;;;;;;;;iBAAgB,mBAAA,oBAAuC,MAAA,kBAAA,CAAyB,OAAA,GAAS,oBAAA,GAA4B,aAAA,CAAc,CAAA;;;;iBAWnH,cAAA,CAAA,GAAkB,kBAAA"}
|
package/dist/logger.mjs
CHANGED
|
@@ -179,27 +179,23 @@ const noopLogger = {
|
|
|
179
179
|
}
|
|
180
180
|
};
|
|
181
181
|
/**
|
|
182
|
-
* Create a
|
|
182
|
+
* Create a scoped logger for building wide events.
|
|
183
|
+
* Use this for any context: workflows, jobs, scripts, queues, etc.
|
|
183
184
|
*
|
|
184
185
|
* @example
|
|
185
186
|
* ```ts
|
|
186
|
-
* const log =
|
|
187
|
-
* log.set({
|
|
188
|
-
* log.set({ cart: { items: 3 } })
|
|
187
|
+
* const log = createLogger({ jobId: job.id, queue: 'emails' })
|
|
188
|
+
* log.set({ batch: { size: 50, processed: 12 } })
|
|
189
189
|
* log.emit()
|
|
190
190
|
* ```
|
|
191
191
|
*/
|
|
192
|
-
function
|
|
192
|
+
function createLogger(initialContext = {}) {
|
|
193
193
|
if (!globalEnabled) return noopLogger;
|
|
194
194
|
const startTime = Date.now();
|
|
195
|
-
let context = {
|
|
196
|
-
method: options.method,
|
|
197
|
-
path: options.path,
|
|
198
|
-
requestId: options.requestId
|
|
199
|
-
};
|
|
195
|
+
let context = { ...initialContext };
|
|
200
196
|
let hasError = false;
|
|
201
197
|
let hasWarn = false;
|
|
202
|
-
function
|
|
198
|
+
function addLog(level, message) {
|
|
203
199
|
const entry = {
|
|
204
200
|
level,
|
|
205
201
|
message,
|
|
@@ -234,7 +230,7 @@ function createRequestLogger(options = {}) {
|
|
|
234
230
|
}, context);
|
|
235
231
|
},
|
|
236
232
|
info(message, infoContext) {
|
|
237
|
-
|
|
233
|
+
addLog("info", message);
|
|
238
234
|
if (infoContext) {
|
|
239
235
|
const { requestLogs: _, ...rest } = infoContext;
|
|
240
236
|
context = deepDefaults(rest, context);
|
|
@@ -242,7 +238,7 @@ function createRequestLogger(options = {}) {
|
|
|
242
238
|
},
|
|
243
239
|
warn(message, warnContext) {
|
|
244
240
|
hasWarn = true;
|
|
245
|
-
|
|
241
|
+
addLog("warn", message);
|
|
246
242
|
if (warnContext) {
|
|
247
243
|
const { requestLogs: _, ...rest } = warnContext;
|
|
248
244
|
context = deepDefaults(rest, context);
|
|
@@ -276,6 +272,25 @@ function createRequestLogger(options = {}) {
|
|
|
276
272
|
};
|
|
277
273
|
}
|
|
278
274
|
/**
|
|
275
|
+
* Create a request-scoped logger for building wide events.
|
|
276
|
+
* Convenience wrapper around `createLogger` that pre-populates HTTP request fields.
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* ```ts
|
|
280
|
+
* const log = createRequestLogger({ method: 'POST', path: '/checkout' })
|
|
281
|
+
* log.set({ user: { id: '123' } })
|
|
282
|
+
* log.set({ cart: { items: 3 } })
|
|
283
|
+
* log.emit()
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
function createRequestLogger(options = {}) {
|
|
287
|
+
const initial = {};
|
|
288
|
+
if (options.method !== void 0) initial.method = options.method;
|
|
289
|
+
if (options.path !== void 0) initial.path = options.path;
|
|
290
|
+
if (options.requestId !== void 0) initial.requestId = options.requestId;
|
|
291
|
+
return createLogger(initial);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
279
294
|
* Get the current environment context.
|
|
280
295
|
*/
|
|
281
296
|
function getEnvironment() {
|
|
@@ -283,5 +298,5 @@ function getEnvironment() {
|
|
|
283
298
|
}
|
|
284
299
|
|
|
285
300
|
//#endregion
|
|
286
|
-
export { createRequestLogger, getEnvironment, initLogger, isEnabled, _log as log, shouldKeep };
|
|
301
|
+
export { createLogger, createRequestLogger, getEnvironment, initLogger, isEnabled, _log as log, shouldKeep };
|
|
287
302
|
//# sourceMappingURL=logger.mjs.map
|
package/dist/logger.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.mjs","names":[],"sources":["../src/logger.ts"],"sourcesContent":["import type { DrainContext, EnvironmentContext, FieldContext, Log, LogLevel, LoggerConfig, RequestLogger, RequestLoggerOptions, SamplingConfig, TailSamplingContext, WideEvent } from './types'\nimport { colors, detectEnvironment, formatDuration, getConsoleMethod, getLevelColor, isDev, matchesPattern } from './utils'\n\nfunction isPlainObject(val: unknown): val is Record<string, unknown> {\n return val !== null && typeof val === 'object' && !Array.isArray(val)\n}\n\nfunction deepDefaults(base: Record<string, unknown>, defaults: Record<string, unknown>): Record<string, unknown> {\n const result = { ...base }\n for (const key in defaults) {\n const baseVal = result[key]\n const defaultVal = defaults[key]\n if (baseVal === undefined || baseVal === null) {\n result[key] = defaultVal\n } else if (isPlainObject(baseVal) && isPlainObject(defaultVal)) {\n result[key] = deepDefaults(baseVal, defaultVal)\n }\n }\n return result\n}\n\nlet globalEnv: EnvironmentContext = {\n service: 'app',\n environment: 'development',\n}\n\nlet globalPretty = isDev()\nlet globalSampling: SamplingConfig = {}\nlet globalStringify = true\nlet globalDrain: ((ctx: DrainContext) => void | Promise<void>) | undefined\nlet globalEnabled = true\n\n/**\n * Initialize the logger with configuration.\n * Call this once at application startup.\n */\nexport function initLogger(config: LoggerConfig = {}): void {\n globalEnabled = config.enabled ?? true\n const detected = detectEnvironment()\n\n globalEnv = {\n service: config.env?.service ?? detected.service ?? 'app',\n environment: config.env?.environment ?? detected.environment ?? 'development',\n version: config.env?.version ?? detected.version,\n commitHash: config.env?.commitHash ?? detected.commitHash,\n region: config.env?.region ?? detected.region,\n }\n\n globalPretty = config.pretty ?? isDev()\n globalSampling = config.sampling ?? {}\n globalStringify = config.stringify ?? true\n globalDrain = config.drain\n}\n\n/**\n * Check if logging is globally enabled.\n */\nexport function isEnabled(): boolean {\n return globalEnabled\n}\n\n/**\n * Determine if a log at the given level should be emitted based on sampling config.\n * Error level defaults to 100% (always logged) unless explicitly configured otherwise.\n */\nfunction shouldSample(level: LogLevel): boolean {\n const { rates } = globalSampling\n if (!rates) {\n return true // No sampling configured, log everything\n }\n\n // Error defaults to 100% unless explicitly set\n const percentage = level === 'error' && rates.error === undefined\n ? 100\n : rates[level] ?? 100\n\n // 0% = never log, 100% = always log\n if (percentage <= 0) return false\n if (percentage >= 100) return true\n\n return Math.random() * 100 < percentage\n}\n\n/**\n * Evaluate tail sampling conditions to determine if a log should be force-kept.\n * Returns true if ANY condition matches (OR logic).\n */\nexport function shouldKeep(ctx: TailSamplingContext): boolean {\n const { keep } = globalSampling\n if (!keep?.length) return false\n\n return keep.some((condition) => {\n if (condition.status !== undefined && ctx.status !== undefined && ctx.status >= condition.status) {\n return true\n }\n if (condition.duration !== undefined && ctx.duration !== undefined && ctx.duration >= condition.duration) {\n return true\n }\n if (condition.path && ctx.path && matchesPattern(ctx.path, condition.path)) {\n return true\n }\n return false\n })\n}\n\nfunction emitWideEvent(level: LogLevel, event: Record<string, unknown>, skipSamplingCheck = false): WideEvent | null {\n if (!globalEnabled) return null\n\n if (!skipSamplingCheck && !shouldSample(level)) {\n return null\n }\n\n const formatted: WideEvent = {\n timestamp: new Date().toISOString(),\n level,\n ...globalEnv,\n ...event,\n }\n\n if (globalPretty) {\n prettyPrintWideEvent(formatted)\n } else if (globalStringify) {\n console[getConsoleMethod(level)](JSON.stringify(formatted))\n } else {\n console[getConsoleMethod(level)](formatted)\n }\n\n if (globalDrain) {\n Promise.resolve(globalDrain({ event: formatted })).catch((err) => {\n console.error('[evlog] drain failed:', err)\n })\n }\n\n return formatted\n}\n\nfunction emitTaggedLog(level: LogLevel, tag: string, message: string): void {\n if (!globalEnabled) return\n\n if (globalPretty) {\n if (!shouldSample(level)) {\n return\n }\n const color = getLevelColor(level)\n const timestamp = new Date().toISOString().slice(11, 23)\n console.log(`${colors.dim}${timestamp}${colors.reset} ${color}[${tag}]${colors.reset} ${message}`)\n return\n }\n emitWideEvent(level, { tag, message })\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null || value === undefined) {\n return String(value)\n }\n if (typeof value === 'object') {\n // Flatten object to key=value pairs\n const pairs: string[] = []\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (v !== undefined && v !== null) {\n if (typeof v === 'object') {\n // For nested objects, show as JSON\n pairs.push(`${k}=${JSON.stringify(v)}`)\n } else {\n pairs.push(`${k}=${v}`)\n }\n }\n }\n return pairs.join(' ')\n }\n return String(value)\n}\n\nfunction prettyPrintWideEvent(event: Record<string, unknown>): void {\n const { timestamp, level, service, environment, version, ...rest } = event\n const levelColor = getLevelColor(level as string)\n const ts = (timestamp as string).slice(11, 23)\n\n let header = `${colors.dim}${ts}${colors.reset} ${levelColor}${(level as string).toUpperCase()}${colors.reset}`\n header += ` ${colors.cyan}[${service}]${colors.reset}`\n\n if (rest.method && rest.path) {\n header += ` ${rest.method} ${rest.path}`\n delete rest.method\n delete rest.path\n }\n\n if (rest.status) {\n const statusColor = (rest.status as number) >= 400 ? colors.red : colors.green\n header += ` ${statusColor}${rest.status}${colors.reset}`\n delete rest.status\n }\n\n if (rest.duration) {\n header += ` ${colors.dim}in ${rest.duration}${colors.reset}`\n delete rest.duration\n }\n\n console.log(header)\n\n const entries = Object.entries(rest).filter(([_, v]) => v !== undefined)\n const lastIndex = entries.length - 1\n\n entries.forEach(([key, value], index) => {\n const isLast = index === lastIndex\n const prefix = isLast ? '└─' : '├─'\n const formatted = formatValue(value)\n console.log(` ${colors.dim}${prefix}${colors.reset} ${colors.cyan}${key}:${colors.reset} ${formatted}`)\n })\n}\n\nfunction createLogMethod(level: LogLevel) {\n return function logMethod(tagOrEvent: string | Record<string, unknown>, message?: string): void {\n if (typeof tagOrEvent === 'string' && message !== undefined) {\n emitTaggedLog(level, tagOrEvent, message)\n } else if (typeof tagOrEvent === 'object') {\n emitWideEvent(level, tagOrEvent)\n } else {\n emitTaggedLog(level, 'log', String(tagOrEvent))\n }\n }\n}\n\n/**\n * Simple logging API - as easy as console.log\n *\n * @example\n * ```ts\n * log.info('auth', 'User logged in')\n * log.error({ action: 'payment', error: 'failed' })\n * ```\n */\nconst _log: Log = {\n info: createLogMethod('info'),\n error: createLogMethod('error'),\n warn: createLogMethod('warn'),\n debug: createLogMethod('debug'),\n}\n\nexport { _log as log }\n\nconst noopLogger: RequestLogger = {\n set() {},\n error() {},\n info() {},\n warn() {},\n emit() {\n return null\n },\n getContext() {\n return {}\n },\n}\n\n/**\n * Create a request-scoped logger for building wide events.\n *\n * @example\n * ```ts\n * const log = createRequestLogger({ method: 'POST', path: '/checkout' })\n * log.set({ user: { id: '123' } })\n * log.set({ cart: { items: 3 } })\n * log.emit()\n * ```\n */\nexport function createRequestLogger<T extends object = Record<string, unknown>>(options: RequestLoggerOptions = {}): RequestLogger<T> {\n if (!globalEnabled) return noopLogger as RequestLogger<T>\n\n const startTime = Date.now()\n let context: Record<string, unknown> = {\n method: options.method,\n path: options.path,\n requestId: options.requestId,\n }\n let hasError = false\n let hasWarn = false\n\n function addRequestLog(level: 'info' | 'warn', message: string): void {\n const entry = {\n level,\n message,\n timestamp: new Date().toISOString(),\n }\n\n const requestLogs = Array.isArray(context.requestLogs)\n ? [...context.requestLogs, entry]\n : [entry]\n\n context = {\n ...context,\n requestLogs,\n }\n }\n\n return {\n set(data: FieldContext<T>): void {\n context = deepDefaults(data as Record<string, unknown>, context) as Record<string, unknown>\n },\n\n error(error: Error | string, errorContext?: FieldContext<T>): void {\n hasError = true\n const err = typeof error === 'string' ? new Error(error) : error\n\n const errorData = {\n ...(errorContext as Record<string, unknown>),\n error: {\n name: err.name,\n message: err.message,\n stack: err.stack,\n ...('status' in err && { status: (err as Record<string, unknown>).status }),\n ...('statusText' in err && { statusText: (err as Record<string, unknown>).statusText }),\n ...('statusCode' in err && { statusCode: (err as Record<string, unknown>).statusCode }),\n ...('statusMessage' in err && { statusMessage: (err as Record<string, unknown>).statusMessage }),\n ...('data' in err && { data: (err as Record<string, unknown>).data }),\n ...('cause' in err && { cause: (err as unknown as Record<string, unknown>).cause }),\n },\n }\n context = deepDefaults(errorData, context) as Record<string, unknown>\n },\n\n info(message: string, infoContext?: FieldContext<T>): void {\n addRequestLog('info', message)\n if (infoContext) {\n const { requestLogs: _, ...rest } = infoContext as Record<string, unknown>\n context = deepDefaults(rest, context) as Record<string, unknown>\n }\n },\n\n warn(message: string, warnContext?: FieldContext<T>): void {\n hasWarn = true\n addRequestLog('warn', message)\n if (warnContext) {\n const { requestLogs: _, ...rest } = warnContext as Record<string, unknown>\n context = deepDefaults(rest, context) as Record<string, unknown>\n }\n },\n\n emit(overrides?: FieldContext<T> & { _forceKeep?: boolean }): WideEvent | null {\n const durationMs = Date.now() - startTime\n const duration = formatDuration(durationMs)\n const level: LogLevel = hasError ? 'error' : hasWarn ? 'warn' : 'info'\n\n // Extract _forceKeep from overrides (set by evlog:emit:keep hook)\n const { _forceKeep, ...restOverrides } = (overrides ?? {}) as Record<string, unknown> & { _forceKeep?: boolean }\n\n // Build tail sampling context\n const tailCtx: TailSamplingContext = {\n status: (context.status ?? restOverrides.status) as number | undefined,\n duration: durationMs,\n path: context.path as string | undefined,\n method: context.method as string | undefined,\n context: { ...context, ...restOverrides },\n }\n\n // Tail sampling: force keep if hook or built-in conditions match\n const forceKeep = _forceKeep || shouldKeep(tailCtx)\n\n // Apply head sampling only if not force-kept\n if (!forceKeep && !shouldSample(level)) {\n return null\n }\n\n return emitWideEvent(level, {\n ...context,\n ...restOverrides,\n duration,\n }, true)\n },\n\n getContext(): FieldContext<T> & Record<string, unknown> {\n return { ...context }\n },\n }\n}\n\n/**\n * Get the current environment context.\n */\nexport function getEnvironment(): EnvironmentContext {\n return { ...globalEnv }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"logger.mjs","names":[],"sources":["../src/logger.ts"],"sourcesContent":["import type { DrainContext, EnvironmentContext, FieldContext, Log, LogLevel, LoggerConfig, RequestLogger, RequestLoggerOptions, SamplingConfig, TailSamplingContext, WideEvent } from './types'\nimport { colors, detectEnvironment, formatDuration, getConsoleMethod, getLevelColor, isDev, matchesPattern } from './utils'\n\nfunction isPlainObject(val: unknown): val is Record<string, unknown> {\n return val !== null && typeof val === 'object' && !Array.isArray(val)\n}\n\nfunction deepDefaults(base: Record<string, unknown>, defaults: Record<string, unknown>): Record<string, unknown> {\n const result = { ...base }\n for (const key in defaults) {\n const baseVal = result[key]\n const defaultVal = defaults[key]\n if (baseVal === undefined || baseVal === null) {\n result[key] = defaultVal\n } else if (isPlainObject(baseVal) && isPlainObject(defaultVal)) {\n result[key] = deepDefaults(baseVal, defaultVal)\n }\n }\n return result\n}\n\nlet globalEnv: EnvironmentContext = {\n service: 'app',\n environment: 'development',\n}\n\nlet globalPretty = isDev()\nlet globalSampling: SamplingConfig = {}\nlet globalStringify = true\nlet globalDrain: ((ctx: DrainContext) => void | Promise<void>) | undefined\nlet globalEnabled = true\n\n/**\n * Initialize the logger with configuration.\n * Call this once at application startup.\n */\nexport function initLogger(config: LoggerConfig = {}): void {\n globalEnabled = config.enabled ?? true\n const detected = detectEnvironment()\n\n globalEnv = {\n service: config.env?.service ?? detected.service ?? 'app',\n environment: config.env?.environment ?? detected.environment ?? 'development',\n version: config.env?.version ?? detected.version,\n commitHash: config.env?.commitHash ?? detected.commitHash,\n region: config.env?.region ?? detected.region,\n }\n\n globalPretty = config.pretty ?? isDev()\n globalSampling = config.sampling ?? {}\n globalStringify = config.stringify ?? true\n globalDrain = config.drain\n}\n\n/**\n * Check if logging is globally enabled.\n */\nexport function isEnabled(): boolean {\n return globalEnabled\n}\n\n/**\n * Determine if a log at the given level should be emitted based on sampling config.\n * Error level defaults to 100% (always logged) unless explicitly configured otherwise.\n */\nfunction shouldSample(level: LogLevel): boolean {\n const { rates } = globalSampling\n if (!rates) {\n return true // No sampling configured, log everything\n }\n\n // Error defaults to 100% unless explicitly set\n const percentage = level === 'error' && rates.error === undefined\n ? 100\n : rates[level] ?? 100\n\n // 0% = never log, 100% = always log\n if (percentage <= 0) return false\n if (percentage >= 100) return true\n\n return Math.random() * 100 < percentage\n}\n\n/**\n * Evaluate tail sampling conditions to determine if a log should be force-kept.\n * Returns true if ANY condition matches (OR logic).\n */\nexport function shouldKeep(ctx: TailSamplingContext): boolean {\n const { keep } = globalSampling\n if (!keep?.length) return false\n\n return keep.some((condition) => {\n if (condition.status !== undefined && ctx.status !== undefined && ctx.status >= condition.status) {\n return true\n }\n if (condition.duration !== undefined && ctx.duration !== undefined && ctx.duration >= condition.duration) {\n return true\n }\n if (condition.path && ctx.path && matchesPattern(ctx.path, condition.path)) {\n return true\n }\n return false\n })\n}\n\nfunction emitWideEvent(level: LogLevel, event: Record<string, unknown>, skipSamplingCheck = false): WideEvent | null {\n if (!globalEnabled) return null\n\n if (!skipSamplingCheck && !shouldSample(level)) {\n return null\n }\n\n const formatted: WideEvent = {\n timestamp: new Date().toISOString(),\n level,\n ...globalEnv,\n ...event,\n }\n\n if (globalPretty) {\n prettyPrintWideEvent(formatted)\n } else if (globalStringify) {\n console[getConsoleMethod(level)](JSON.stringify(formatted))\n } else {\n console[getConsoleMethod(level)](formatted)\n }\n\n if (globalDrain) {\n Promise.resolve(globalDrain({ event: formatted })).catch((err) => {\n console.error('[evlog] drain failed:', err)\n })\n }\n\n return formatted\n}\n\nfunction emitTaggedLog(level: LogLevel, tag: string, message: string): void {\n if (!globalEnabled) return\n\n if (globalPretty) {\n if (!shouldSample(level)) {\n return\n }\n const color = getLevelColor(level)\n const timestamp = new Date().toISOString().slice(11, 23)\n console.log(`${colors.dim}${timestamp}${colors.reset} ${color}[${tag}]${colors.reset} ${message}`)\n return\n }\n emitWideEvent(level, { tag, message })\n}\n\nfunction formatValue(value: unknown): string {\n if (value === null || value === undefined) {\n return String(value)\n }\n if (typeof value === 'object') {\n // Flatten object to key=value pairs\n const pairs: string[] = []\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n if (v !== undefined && v !== null) {\n if (typeof v === 'object') {\n // For nested objects, show as JSON\n pairs.push(`${k}=${JSON.stringify(v)}`)\n } else {\n pairs.push(`${k}=${v}`)\n }\n }\n }\n return pairs.join(' ')\n }\n return String(value)\n}\n\nfunction prettyPrintWideEvent(event: Record<string, unknown>): void {\n const { timestamp, level, service, environment, version, ...rest } = event\n const levelColor = getLevelColor(level as string)\n const ts = (timestamp as string).slice(11, 23)\n\n let header = `${colors.dim}${ts}${colors.reset} ${levelColor}${(level as string).toUpperCase()}${colors.reset}`\n header += ` ${colors.cyan}[${service}]${colors.reset}`\n\n if (rest.method && rest.path) {\n header += ` ${rest.method} ${rest.path}`\n delete rest.method\n delete rest.path\n }\n\n if (rest.status) {\n const statusColor = (rest.status as number) >= 400 ? colors.red : colors.green\n header += ` ${statusColor}${rest.status}${colors.reset}`\n delete rest.status\n }\n\n if (rest.duration) {\n header += ` ${colors.dim}in ${rest.duration}${colors.reset}`\n delete rest.duration\n }\n\n console.log(header)\n\n const entries = Object.entries(rest).filter(([_, v]) => v !== undefined)\n const lastIndex = entries.length - 1\n\n entries.forEach(([key, value], index) => {\n const isLast = index === lastIndex\n const prefix = isLast ? '└─' : '├─'\n const formatted = formatValue(value)\n console.log(` ${colors.dim}${prefix}${colors.reset} ${colors.cyan}${key}:${colors.reset} ${formatted}`)\n })\n}\n\nfunction createLogMethod(level: LogLevel) {\n return function logMethod(tagOrEvent: string | Record<string, unknown>, message?: string): void {\n if (typeof tagOrEvent === 'string' && message !== undefined) {\n emitTaggedLog(level, tagOrEvent, message)\n } else if (typeof tagOrEvent === 'object') {\n emitWideEvent(level, tagOrEvent)\n } else {\n emitTaggedLog(level, 'log', String(tagOrEvent))\n }\n }\n}\n\n/**\n * Simple logging API - as easy as console.log\n *\n * @example\n * ```ts\n * log.info('auth', 'User logged in')\n * log.error({ action: 'payment', error: 'failed' })\n * ```\n */\nconst _log: Log = {\n info: createLogMethod('info'),\n error: createLogMethod('error'),\n warn: createLogMethod('warn'),\n debug: createLogMethod('debug'),\n}\n\nexport { _log as log }\n\nconst noopLogger: RequestLogger = {\n set() {},\n error() {},\n info() {},\n warn() {},\n emit() {\n return null\n },\n getContext() {\n return {}\n },\n}\n\n/**\n * Create a scoped logger for building wide events.\n * Use this for any context: workflows, jobs, scripts, queues, etc.\n *\n * @example\n * ```ts\n * const log = createLogger({ jobId: job.id, queue: 'emails' })\n * log.set({ batch: { size: 50, processed: 12 } })\n * log.emit()\n * ```\n */\nexport function createLogger<T extends object = Record<string, unknown>>(initialContext: Record<string, unknown> = {}): RequestLogger<T> {\n if (!globalEnabled) return noopLogger as RequestLogger<T>\n\n const startTime = Date.now()\n let context: Record<string, unknown> = { ...initialContext }\n let hasError = false\n let hasWarn = false\n\n function addLog(level: 'info' | 'warn', message: string): void {\n const entry = {\n level,\n message,\n timestamp: new Date().toISOString(),\n }\n\n const requestLogs = Array.isArray(context.requestLogs)\n ? [...context.requestLogs, entry]\n : [entry]\n\n context = {\n ...context,\n requestLogs,\n }\n }\n\n return {\n set(data: FieldContext<T>): void {\n context = deepDefaults(data as Record<string, unknown>, context) as Record<string, unknown>\n },\n\n error(error: Error | string, errorContext?: FieldContext<T>): void {\n hasError = true\n const err = typeof error === 'string' ? new Error(error) : error\n\n const errorData = {\n ...(errorContext as Record<string, unknown>),\n error: {\n name: err.name,\n message: err.message,\n stack: err.stack,\n ...('status' in err && { status: (err as Record<string, unknown>).status }),\n ...('statusText' in err && { statusText: (err as Record<string, unknown>).statusText }),\n ...('statusCode' in err && { statusCode: (err as Record<string, unknown>).statusCode }),\n ...('statusMessage' in err && { statusMessage: (err as Record<string, unknown>).statusMessage }),\n ...('data' in err && { data: (err as Record<string, unknown>).data }),\n ...('cause' in err && { cause: (err as unknown as Record<string, unknown>).cause }),\n },\n }\n context = deepDefaults(errorData, context) as Record<string, unknown>\n },\n\n info(message: string, infoContext?: FieldContext<T>): void {\n addLog('info', message)\n if (infoContext) {\n const { requestLogs: _, ...rest } = infoContext as Record<string, unknown>\n context = deepDefaults(rest, context) as Record<string, unknown>\n }\n },\n\n warn(message: string, warnContext?: FieldContext<T>): void {\n hasWarn = true\n addLog('warn', message)\n if (warnContext) {\n const { requestLogs: _, ...rest } = warnContext as Record<string, unknown>\n context = deepDefaults(rest, context) as Record<string, unknown>\n }\n },\n\n emit(overrides?: FieldContext<T> & { _forceKeep?: boolean }): WideEvent | null {\n const durationMs = Date.now() - startTime\n const duration = formatDuration(durationMs)\n const level: LogLevel = hasError ? 'error' : hasWarn ? 'warn' : 'info'\n\n // Extract _forceKeep from overrides (set by evlog:emit:keep hook)\n const { _forceKeep, ...restOverrides } = (overrides ?? {}) as Record<string, unknown> & { _forceKeep?: boolean }\n\n // Build tail sampling context\n const tailCtx: TailSamplingContext = {\n status: (context.status ?? restOverrides.status) as number | undefined,\n duration: durationMs,\n path: context.path as string | undefined,\n method: context.method as string | undefined,\n context: { ...context, ...restOverrides },\n }\n\n // Tail sampling: force keep if hook or built-in conditions match\n const forceKeep = _forceKeep || shouldKeep(tailCtx)\n\n // Apply head sampling only if not force-kept\n if (!forceKeep && !shouldSample(level)) {\n return null\n }\n\n return emitWideEvent(level, {\n ...context,\n ...restOverrides,\n duration,\n }, true)\n },\n\n getContext(): FieldContext<T> & Record<string, unknown> {\n return { ...context }\n },\n }\n}\n\n/**\n * Create a request-scoped logger for building wide events.\n * Convenience wrapper around `createLogger` that pre-populates HTTP request fields.\n *\n * @example\n * ```ts\n * const log = createRequestLogger({ method: 'POST', path: '/checkout' })\n * log.set({ user: { id: '123' } })\n * log.set({ cart: { items: 3 } })\n * log.emit()\n * ```\n */\nexport function createRequestLogger<T extends object = Record<string, unknown>>(options: RequestLoggerOptions = {}): RequestLogger<T> {\n const initial: Record<string, unknown> = {}\n if (options.method !== undefined) initial.method = options.method\n if (options.path !== undefined) initial.path = options.path\n if (options.requestId !== undefined) initial.requestId = options.requestId\n return createLogger<T>(initial)\n}\n\n/**\n * Get the current environment context.\n */\nexport function getEnvironment(): EnvironmentContext {\n return { ...globalEnv }\n}\n"],"mappings":";;;AAGA,SAAS,cAAc,KAA8C;AACnE,QAAO,QAAQ,QAAQ,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,IAAI;;AAGvE,SAAS,aAAa,MAA+B,UAA4D;CAC/G,MAAM,SAAS,EAAE,GAAG,MAAM;AAC1B,MAAK,MAAM,OAAO,UAAU;EAC1B,MAAM,UAAU,OAAO;EACvB,MAAM,aAAa,SAAS;AAC5B,MAAI,YAAY,UAAa,YAAY,KACvC,QAAO,OAAO;WACL,cAAc,QAAQ,IAAI,cAAc,WAAW,CAC5D,QAAO,OAAO,aAAa,SAAS,WAAW;;AAGnD,QAAO;;AAGT,IAAI,YAAgC;CAClC,SAAS;CACT,aAAa;CACd;AAED,IAAI,eAAe,OAAO;AAC1B,IAAI,iBAAiC,EAAE;AACvC,IAAI,kBAAkB;AACtB,IAAI;AACJ,IAAI,gBAAgB;;;;;AAMpB,SAAgB,WAAW,SAAuB,EAAE,EAAQ;AAC1D,iBAAgB,OAAO,WAAW;CAClC,MAAM,WAAW,mBAAmB;AAEpC,aAAY;EACV,SAAS,OAAO,KAAK,WAAW,SAAS,WAAW;EACpD,aAAa,OAAO,KAAK,eAAe,SAAS,eAAe;EAChE,SAAS,OAAO,KAAK,WAAW,SAAS;EACzC,YAAY,OAAO,KAAK,cAAc,SAAS;EAC/C,QAAQ,OAAO,KAAK,UAAU,SAAS;EACxC;AAED,gBAAe,OAAO,UAAU,OAAO;AACvC,kBAAiB,OAAO,YAAY,EAAE;AACtC,mBAAkB,OAAO,aAAa;AACtC,eAAc,OAAO;;;;;AAMvB,SAAgB,YAAqB;AACnC,QAAO;;;;;;AAOT,SAAS,aAAa,OAA0B;CAC9C,MAAM,EAAE,UAAU;AAClB,KAAI,CAAC,MACH,QAAO;CAIT,MAAM,aAAa,UAAU,WAAW,MAAM,UAAU,SACpD,MACA,MAAM,UAAU;AAGpB,KAAI,cAAc,EAAG,QAAO;AAC5B,KAAI,cAAc,IAAK,QAAO;AAE9B,QAAO,KAAK,QAAQ,GAAG,MAAM;;;;;;AAO/B,SAAgB,WAAW,KAAmC;CAC5D,MAAM,EAAE,SAAS;AACjB,KAAI,CAAC,MAAM,OAAQ,QAAO;AAE1B,QAAO,KAAK,MAAM,cAAc;AAC9B,MAAI,UAAU,WAAW,UAAa,IAAI,WAAW,UAAa,IAAI,UAAU,UAAU,OACxF,QAAO;AAET,MAAI,UAAU,aAAa,UAAa,IAAI,aAAa,UAAa,IAAI,YAAY,UAAU,SAC9F,QAAO;AAET,MAAI,UAAU,QAAQ,IAAI,QAAQ,eAAe,IAAI,MAAM,UAAU,KAAK,CACxE,QAAO;AAET,SAAO;GACP;;AAGJ,SAAS,cAAc,OAAiB,OAAgC,oBAAoB,OAAyB;AACnH,KAAI,CAAC,cAAe,QAAO;AAE3B,KAAI,CAAC,qBAAqB,CAAC,aAAa,MAAM,CAC5C,QAAO;CAGT,MAAM,YAAuB;EAC3B,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC;EACA,GAAG;EACH,GAAG;EACJ;AAED,KAAI,aACF,sBAAqB,UAAU;UACtB,gBACT,SAAQ,iBAAiB,MAAM,EAAE,KAAK,UAAU,UAAU,CAAC;KAE3D,SAAQ,iBAAiB,MAAM,EAAE,UAAU;AAG7C,KAAI,YACF,SAAQ,QAAQ,YAAY,EAAE,OAAO,WAAW,CAAC,CAAC,CAAC,OAAO,QAAQ;AAChE,UAAQ,MAAM,yBAAyB,IAAI;GAC3C;AAGJ,QAAO;;AAGT,SAAS,cAAc,OAAiB,KAAa,SAAuB;AAC1E,KAAI,CAAC,cAAe;AAEpB,KAAI,cAAc;AAChB,MAAI,CAAC,aAAa,MAAM,CACtB;EAEF,MAAM,QAAQ,cAAc,MAAM;EAClC,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,IAAI,GAAG;AACxD,UAAQ,IAAI,GAAG,OAAO,MAAM,YAAY,OAAO,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,MAAM,GAAG,UAAU;AAClG;;AAEF,eAAc,OAAO;EAAE;EAAK;EAAS,CAAC;;AAGxC,SAAS,YAAY,OAAwB;AAC3C,KAAI,UAAU,QAAQ,UAAU,OAC9B,QAAO,OAAO,MAAM;AAEtB,KAAI,OAAO,UAAU,UAAU;EAE7B,MAAM,QAAkB,EAAE;AAC1B,OAAK,MAAM,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAiC,CACnE,KAAI,MAAM,UAAa,MAAM,KAC3B,KAAI,OAAO,MAAM,SAEf,OAAM,KAAK,GAAG,EAAE,GAAG,KAAK,UAAU,EAAE,GAAG;MAEvC,OAAM,KAAK,GAAG,EAAE,GAAG,IAAI;AAI7B,SAAO,MAAM,KAAK,IAAI;;AAExB,QAAO,OAAO,MAAM;;AAGtB,SAAS,qBAAqB,OAAsC;CAClE,MAAM,EAAE,WAAW,OAAO,SAAS,aAAa,SAAS,GAAG,SAAS;CACrE,MAAM,aAAa,cAAc,MAAgB;CACjD,MAAM,KAAM,UAAqB,MAAM,IAAI,GAAG;CAE9C,IAAI,SAAS,GAAG,OAAO,MAAM,KAAK,OAAO,MAAM,GAAG,aAAc,MAAiB,aAAa,GAAG,OAAO;AACxG,WAAU,IAAI,OAAO,KAAK,GAAG,QAAQ,GAAG,OAAO;AAE/C,KAAI,KAAK,UAAU,KAAK,MAAM;AAC5B,YAAU,IAAI,KAAK,OAAO,GAAG,KAAK;AAClC,SAAO,KAAK;AACZ,SAAO,KAAK;;AAGd,KAAI,KAAK,QAAQ;EACf,MAAM,cAAe,KAAK,UAAqB,MAAM,OAAO,MAAM,OAAO;AACzE,YAAU,IAAI,cAAc,KAAK,SAAS,OAAO;AACjD,SAAO,KAAK;;AAGd,KAAI,KAAK,UAAU;AACjB,YAAU,IAAI,OAAO,IAAI,KAAK,KAAK,WAAW,OAAO;AACrD,SAAO,KAAK;;AAGd,SAAQ,IAAI,OAAO;CAEnB,MAAM,UAAU,OAAO,QAAQ,KAAK,CAAC,QAAQ,CAAC,GAAG,OAAO,MAAM,OAAU;CACxE,MAAM,YAAY,QAAQ,SAAS;AAEnC,SAAQ,SAAS,CAAC,KAAK,QAAQ,UAAU;EAEvC,MAAM,SADS,UAAU,YACD,OAAO;EAC/B,MAAM,YAAY,YAAY,MAAM;AACpC,UAAQ,IAAI,KAAK,OAAO,MAAM,SAAS,OAAO,MAAM,GAAG,OAAO,OAAO,IAAI,GAAG,OAAO,MAAM,GAAG,YAAY;GACxG;;AAGJ,SAAS,gBAAgB,OAAiB;AACxC,QAAO,SAAS,UAAU,YAA8C,SAAwB;AAC9F,MAAI,OAAO,eAAe,YAAY,YAAY,OAChD,eAAc,OAAO,YAAY,QAAQ;WAChC,OAAO,eAAe,SAC/B,eAAc,OAAO,WAAW;MAEhC,eAAc,OAAO,OAAO,OAAO,WAAW,CAAC;;;;;;;;;;;;AAcrD,MAAM,OAAY;CAChB,MAAM,gBAAgB,OAAO;CAC7B,OAAO,gBAAgB,QAAQ;CAC/B,MAAM,gBAAgB,OAAO;CAC7B,OAAO,gBAAgB,QAAQ;CAChC;AAID,MAAM,aAA4B;CAChC,MAAM;CACN,QAAQ;CACR,OAAO;CACP,OAAO;CACP,OAAO;AACL,SAAO;;CAET,aAAa;AACX,SAAO,EAAE;;CAEZ;;;;;;;;;;;;AAaD,SAAgB,aAAyD,iBAA0C,EAAE,EAAoB;AACvI,KAAI,CAAC,cAAe,QAAO;CAE3B,MAAM,YAAY,KAAK,KAAK;CAC5B,IAAI,UAAmC,EAAE,GAAG,gBAAgB;CAC5D,IAAI,WAAW;CACf,IAAI,UAAU;CAEd,SAAS,OAAO,OAAwB,SAAuB;EAC7D,MAAM,QAAQ;GACZ;GACA;GACA,4BAAW,IAAI,MAAM,EAAC,aAAa;GACpC;EAED,MAAM,cAAc,MAAM,QAAQ,QAAQ,YAAY,GAClD,CAAC,GAAG,QAAQ,aAAa,MAAM,GAC/B,CAAC,MAAM;AAEX,YAAU;GACR,GAAG;GACH;GACD;;AAGH,QAAO;EACL,IAAI,MAA6B;AAC/B,aAAU,aAAa,MAAiC,QAAQ;;EAGlE,MAAM,OAAuB,cAAsC;AACjE,cAAW;GACX,MAAM,MAAM,OAAO,UAAU,WAAW,IAAI,MAAM,MAAM,GAAG;AAgB3D,aAAU,aAdQ;IAChB,GAAI;IACJ,OAAO;KACL,MAAM,IAAI;KACV,SAAS,IAAI;KACb,OAAO,IAAI;KACX,GAAI,YAAY,OAAO,EAAE,QAAS,IAAgC,QAAQ;KAC1E,GAAI,gBAAgB,OAAO,EAAE,YAAa,IAAgC,YAAY;KACtF,GAAI,gBAAgB,OAAO,EAAE,YAAa,IAAgC,YAAY;KACtF,GAAI,mBAAmB,OAAO,EAAE,eAAgB,IAAgC,eAAe;KAC/F,GAAI,UAAU,OAAO,EAAE,MAAO,IAAgC,MAAM;KACpE,GAAI,WAAW,OAAO,EAAE,OAAQ,IAA2C,OAAO;KACnF;IACF,EACiC,QAAQ;;EAG5C,KAAK,SAAiB,aAAqC;AACzD,UAAO,QAAQ,QAAQ;AACvB,OAAI,aAAa;IACf,MAAM,EAAE,aAAa,GAAG,GAAG,SAAS;AACpC,cAAU,aAAa,MAAM,QAAQ;;;EAIzC,KAAK,SAAiB,aAAqC;AACzD,aAAU;AACV,UAAO,QAAQ,QAAQ;AACvB,OAAI,aAAa;IACf,MAAM,EAAE,aAAa,GAAG,GAAG,SAAS;AACpC,cAAU,aAAa,MAAM,QAAQ;;;EAIzC,KAAK,WAA0E;GAC7E,MAAM,aAAa,KAAK,KAAK,GAAG;GAChC,MAAM,WAAW,eAAe,WAAW;GAC3C,MAAM,QAAkB,WAAW,UAAU,UAAU,SAAS;GAGhE,MAAM,EAAE,YAAY,GAAG,kBAAmB,aAAa,EAAE;GAGzD,MAAM,UAA+B;IACnC,QAAS,QAAQ,UAAU,cAAc;IACzC,UAAU;IACV,MAAM,QAAQ;IACd,QAAQ,QAAQ;IAChB,SAAS;KAAE,GAAG;KAAS,GAAG;KAAe;IAC1C;AAMD,OAAI,EAHc,cAAc,WAAW,QAAQ,KAGjC,CAAC,aAAa,MAAM,CACpC,QAAO;AAGT,UAAO,cAAc,OAAO;IAC1B,GAAG;IACH,GAAG;IACH;IACD,EAAE,KAAK;;EAGV,aAAwD;AACtD,UAAO,EAAE,GAAG,SAAS;;EAExB;;;;;;;;;;;;;;AAeH,SAAgB,oBAAgE,UAAgC,EAAE,EAAoB;CACpI,MAAM,UAAmC,EAAE;AAC3C,KAAI,QAAQ,WAAW,OAAW,SAAQ,SAAS,QAAQ;AAC3D,KAAI,QAAQ,SAAS,OAAW,SAAQ,OAAO,QAAQ;AACvD,KAAI,QAAQ,cAAc,OAAW,SAAQ,YAAY,QAAQ;AACjE,QAAO,aAAgB,QAAQ;;;;;AAMjC,SAAgB,iBAAqC;AACnD,QAAO,EAAE,GAAG,WAAW"}
|
package/dist/next/client.d.mts
CHANGED
|
@@ -23,6 +23,13 @@ interface EvlogProviderProps {
|
|
|
23
23
|
* @default true
|
|
24
24
|
*/
|
|
25
25
|
enabled?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Enable or disable browser console output.
|
|
28
|
+
* When false, logs are suppressed in the browser DevTools console
|
|
29
|
+
* but still sent to the server via transport (if enabled).
|
|
30
|
+
* @default true
|
|
31
|
+
*/
|
|
32
|
+
console?: boolean;
|
|
26
33
|
children: React.ReactNode;
|
|
27
34
|
}
|
|
28
35
|
/**
|
|
@@ -48,6 +55,7 @@ declare function EvlogProvider({
|
|
|
48
55
|
pretty,
|
|
49
56
|
transport,
|
|
50
57
|
enabled,
|
|
58
|
+
console: consoleOutput,
|
|
51
59
|
children
|
|
52
60
|
}: EvlogProviderProps): react.ReactNode;
|
|
53
61
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.mts","names":[],"sources":["../../src/next/client.ts"],"mappings":";;;;;UAQiB,kBAAA;;;AAAjB;;EAKE,OAAA;
|
|
1
|
+
{"version":3,"file":"client.d.mts","names":[],"sources":["../../src/next/client.ts"],"mappings":";;;;;UAQiB,kBAAA;;;AAAjB;;EAKE,OAAA;EA2ByB;;;;EArBzB,MAAA;EAWA;;;EANA,SAAA,GAAY,eAAA;EAgBI;;;AAsBlB;EAhCE,OAAA;;;;;;;EAQA,OAAA;EAEA,QAAA,EAAU,KAAA,CAAM,SAAA;AAAA;;;;;;;;;;;;;;;;;;;iBAsBF,aAAA,CAAA;EAAgB,OAAA;EAAS,MAAA;EAAQ,SAAA;EAAW,OAAA;EAAS,OAAA,EAAS,aAAA;EAAe;AAAA,GAAY,kBAAA,GAAkB,KAAA,CAAA,SAAA"}
|
package/dist/next/client.mjs
CHANGED
|
@@ -22,16 +22,18 @@ import { useEffect } from "react";
|
|
|
22
22
|
* }
|
|
23
23
|
* ```
|
|
24
24
|
*/
|
|
25
|
-
function EvlogProvider({ service, pretty, transport, enabled, children }) {
|
|
25
|
+
function EvlogProvider({ service, pretty, transport, enabled, console: consoleOutput, children }) {
|
|
26
26
|
useEffect(() => {
|
|
27
27
|
initLog({
|
|
28
28
|
enabled,
|
|
29
|
+
console: consoleOutput,
|
|
29
30
|
pretty,
|
|
30
31
|
service,
|
|
31
32
|
transport
|
|
32
33
|
});
|
|
33
34
|
}, [
|
|
34
35
|
enabled,
|
|
36
|
+
consoleOutput,
|
|
35
37
|
pretty,
|
|
36
38
|
service,
|
|
37
39
|
transport
|
package/dist/next/client.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.mjs","names":[],"sources":["../../src/next/client.ts"],"sourcesContent":["'use client'\n\nimport { useEffect } from 'react'\nimport type { TransportConfig } from '../types'\nimport { initLog, log, setIdentity, clearIdentity } from '../runtime/client/log'\n\nexport { log, setIdentity, clearIdentity } from '../runtime/client/log'\n\nexport interface EvlogProviderProps {\n /**\n * Service name for client-side logs.\n * @default 'client'\n */\n service?: string\n\n /**\n * Enable pretty printing in the browser console.\n * @default true\n */\n pretty?: boolean\n\n /**\n * Transport configuration for sending client logs to the server.\n */\n transport?: TransportConfig\n\n /**\n * Enable or disable client-side logging.\n * @default true\n */\n enabled?: boolean\n\n children: React.ReactNode\n}\n\n/**\n * React provider that initializes evlog client-side logging.\n * Place this in your root layout to enable client logging throughout your app.\n *\n * @example\n * ```tsx\n * // app/layout.tsx\n * import { EvlogProvider } from 'evlog/next/client'\n *\n * export default function Layout({ children }) {\n * return (\n * <EvlogProvider service=\"my-app\" transport={{ enabled: true }}>\n * {children}\n * </EvlogProvider>\n * )\n * }\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport function EvlogProvider({ service, pretty, transport, enabled, children }: EvlogProviderProps) {\n useEffect(() => {\n initLog({\n enabled,\n pretty,\n service,\n transport,\n })\n }, [enabled, pretty, service, transport])\n\n return children\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"sources":["../../src/next/client.ts"],"sourcesContent":["'use client'\n\nimport { useEffect } from 'react'\nimport type { TransportConfig } from '../types'\nimport { initLog, log, setIdentity, clearIdentity } from '../runtime/client/log'\n\nexport { log, setIdentity, clearIdentity } from '../runtime/client/log'\n\nexport interface EvlogProviderProps {\n /**\n * Service name for client-side logs.\n * @default 'client'\n */\n service?: string\n\n /**\n * Enable pretty printing in the browser console.\n * @default true\n */\n pretty?: boolean\n\n /**\n * Transport configuration for sending client logs to the server.\n */\n transport?: TransportConfig\n\n /**\n * Enable or disable client-side logging.\n * @default true\n */\n enabled?: boolean\n\n /**\n * Enable or disable browser console output.\n * When false, logs are suppressed in the browser DevTools console\n * but still sent to the server via transport (if enabled).\n * @default true\n */\n console?: boolean\n\n children: React.ReactNode\n}\n\n/**\n * React provider that initializes evlog client-side logging.\n * Place this in your root layout to enable client logging throughout your app.\n *\n * @example\n * ```tsx\n * // app/layout.tsx\n * import { EvlogProvider } from 'evlog/next/client'\n *\n * export default function Layout({ children }) {\n * return (\n * <EvlogProvider service=\"my-app\" transport={{ enabled: true }}>\n * {children}\n * </EvlogProvider>\n * )\n * }\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/naming-convention\nexport function EvlogProvider({ service, pretty, transport, enabled, console: consoleOutput, children }: EvlogProviderProps) {\n useEffect(() => {\n initLog({\n enabled,\n console: consoleOutput,\n pretty,\n service,\n transport,\n })\n }, [enabled, consoleOutput, pretty, service, transport])\n\n return children\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA8DA,SAAgB,cAAc,EAAE,SAAS,QAAQ,WAAW,SAAS,SAAS,eAAe,YAAgC;AAC3H,iBAAgB;AACd,UAAQ;GACN;GACA,SAAS;GACT;GACA;GACA;GACD,CAAC;IACD;EAAC;EAAS;EAAe;EAAQ;EAAS;EAAU,CAAC;AAExD,QAAO"}
|
package/dist/next/index.d.mts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { DrainContext, EnrichContext, EnvironmentContext, Log, RequestLogger, RouteConfig, SamplingConfig, TailSamplingContext } from "../types.mjs";
|
|
2
2
|
import { createError } from "../error.mjs";
|
|
3
3
|
import { log as _log } from "../logger.mjs";
|
|
4
|
-
import "../index.mjs";
|
|
5
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
5
|
|
|
7
6
|
//#region src/next/types.d.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/next/types.ts","../../src/next/storage.ts","../../src/next/middleware.ts","../../src/next/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/next/types.ts","../../src/next/storage.ts","../../src/next/middleware.ts","../../src/next/index.ts"],"mappings":";;;;;;UAEiB,gBAAA;;;;;EAKf,OAAA;EALe;;;EAUf,GAAA,GAAM,OAAA,CAAQ,kBAAA;EAAR;;;;EAMN,MAAA;EAoCsC;;;;EA9BtC,OAAA;EA2CmD;;;EAtCnD,QAAA,GAAW,cAAA;EAjBL;;;;;EAwBN,OAAA;EAAA;;;;;EAOA,OAAA;EAWc;;;EANd,MAAA,GAAS,MAAA,SAAe,WAAA;EAYT;;;;EANf,KAAA,IAAS,GAAA,EAAK,YAAA,YAAwB,OAAA;EAa9B;;;;EAPR,MAAA,IAAU,GAAA,EAAK,aAAA,YAAyB,OAAA;EAgBzB;;;;;EATf,IAAA,IAAQ,GAAA,EAAK,mBAAA,YAA+B,OAAA;;;AC/C9C;;EDqDE,SAAA;AAAA;AAAA,UAGe,qBAAA;ECxDwD;;;;ED6DvE,OAAA;EC7DuE;;;;EDmEvE,OAAA;AAAA;;;;;;;AArFF;;;;;;;;;;;iBCkBgB,SAAA,oBAA6B,MAAA,kBAAA,CAAA,GAA4B,aAAA,CAAc,CAAA;;;KCjBlF,WAAA;EACH,OAAA;IAAW,QAAA;EAAA;EACX,OAAA;IAAW,GAAA,CAAI,IAAA;EAAA;AAAA;AAAA,KAGZ,YAAA;EACH,OAAA;IAAW,GAAA,CAAI,IAAA,UAAc,KAAA;EAAA;AAAA;;;;;;;;;;;;;;iBAoBf,eAAA,CAAgB,MAAA,GAAS,qBAAA,IACzB,OAAA,EAAS,WAAA,KAAW,OAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCYpB,WAAA,CAAY,OAAA,GAAS,gBAAA;6DAWg8D,IAAA,EAAA,KAAA,KAAA,OAAA,SAAkC,IAAA,EAAA,KAAA,KAAA,OAAA,CAAA,OAAA,CAAA,OAAA"}
|
package/dist/nitro/module.d.mts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { useLogger } from "../runtime/server/useLogger.mjs";
|
|
2
|
-
import { t as NitroModuleOptions } from "../nitro-
|
|
2
|
+
import { t as NitroModuleOptions } from "../nitro-Nxg6qcXd.mjs";
|
|
3
|
+
import { Nitro } from "nitropack";
|
|
3
4
|
|
|
4
5
|
//#region src/nitro/module.d.ts
|
|
5
6
|
declare function evlog(options?: NitroModuleOptions): {
|
|
6
7
|
name: string;
|
|
7
|
-
setup(nitro:
|
|
8
|
+
setup(nitro: Nitro): void;
|
|
8
9
|
};
|
|
9
10
|
//#endregion
|
|
10
11
|
export { type NitroModuleOptions, evlog as default, useLogger };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.mts","names":[],"sources":["../../src/nitro/module.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"module.d.mts","names":[],"sources":["../../src/nitro/module.ts"],"mappings":";;;;;iBASwB,KAAA,CAAM,OAAA,GAAU,kBAAA;;eAGvB,KAAA;AAAA"}
|
package/dist/nitro/module.mjs
CHANGED
|
@@ -11,6 +11,7 @@ function evlog(options) {
|
|
|
11
11
|
nitro.options.plugins = nitro.options.plugins || [];
|
|
12
12
|
nitro.options.plugins.push(resolve(_dir, "plugin"));
|
|
13
13
|
if (!nitro.options.errorHandler) nitro.options.errorHandler = resolve(_dir, "errorHandler");
|
|
14
|
+
nitro.options.noExternals = true;
|
|
14
15
|
nitro.options.runtimeConfig = nitro.options.runtimeConfig || {};
|
|
15
16
|
nitro.options.runtimeConfig.evlog = options || {};
|
|
16
17
|
process.env.__EVLOG_CONFIG = JSON.stringify(options || {});
|