@ekkolyth/logging 0.1.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.
@@ -0,0 +1,7 @@
1
+ import { a as Window, i as RateLimited, n as Logger, r as LoggerOptions, t as LogOutput } from "./logger-V7XF9Wuo.js";
2
+ import { n as LogFormat, r as LogLevel, t as LogAttributes } from "./types-CieIrR8p.js";
3
+ //#region src/browser.d.ts
4
+ declare function createLogger(options?: LoggerOptions): Logger;
5
+ //#endregion
6
+ export { type LogAttributes, type LogFormat, type LogLevel, type LogOutput, type Logger, type LoggerOptions, type RateLimited, type Window, createLogger };
7
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1,27 @@
1
+ import { n as resolveFormat, r as resolveLevel, t as resolveColors } from "./env-Dur55C_F.js";
2
+ import { t as buildEngine } from "./logger-BxxJhfg8.js";
3
+ //#region src/browser.ts
4
+ function createLogger(options = {}) {
5
+ const level = resolveLevel(options.level, {});
6
+ const format = resolveFormat(options.format, {}, true);
7
+ const colors = resolveColors(options.colors, {}, false);
8
+ const defaultOutput = (line) => {
9
+ console.log(line.endsWith("\n") ? line.slice(0, -1) : line);
10
+ };
11
+ return buildEngine({
12
+ bindings: options.bindings ?? {},
13
+ colors,
14
+ format,
15
+ level,
16
+ onInternalError: options.onInternalError,
17
+ output: options.output ?? defaultOutput,
18
+ redact: options.redact ?? [],
19
+ scope: options.scope,
20
+ service: options.service,
21
+ stderrWrite: (message) => console.error(message)
22
+ });
23
+ }
24
+ //#endregion
25
+ export { createLogger };
26
+
27
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","names":[],"sources":["../src/browser.ts"],"sourcesContent":["import { resolveColors, resolveFormat, resolveLevel } from './env'\nimport {\n buildEngine,\n type Logger,\n type LoggerOptions,\n type LogOutput,\n} from './logger'\n\nfunction createLogger(options: LoggerOptions = {}): Logger {\n // browsers never inspect process.env — an empty env source plus fixed\n // detection preferences reproduces \"auto selects uncolored pretty output\"\n const level = resolveLevel(options.level, {})\n const format = resolveFormat(options.format, {}, true)\n const colors = resolveColors(options.colors, {}, false)\n\n const defaultOutput: LogOutput = (line) => {\n console.log(line.endsWith('\\n') ? line.slice(0, -1) : line)\n }\n\n return buildEngine({\n bindings: options.bindings ?? {},\n colors,\n format,\n level,\n onInternalError: options.onInternalError,\n output: options.output ?? defaultOutput,\n redact: options.redact ?? [],\n scope: options.scope,\n service: options.service,\n stderrWrite: (message) => console.error(message),\n })\n}\n\nexport type { Logger, LoggerOptions, LogOutput, RateLimited } from './logger'\nexport type { Window } from './throttler'\nexport type { LogAttributes, LogFormat, LogLevel } from './types'\nexport { createLogger }\n"],"mappings":";;;AAQA,SAAS,aAAa,UAAyB,CAAC,GAAW;CAGvD,MAAM,QAAQ,aAAa,QAAQ,OAAO,CAAC,CAAC;CAC5C,MAAM,SAAS,cAAc,QAAQ,QAAQ,CAAC,GAAG,IAAI;CACrD,MAAM,SAAS,cAAc,QAAQ,QAAQ,CAAC,GAAG,KAAK;CAEtD,MAAM,iBAA4B,SAAS;EACvC,QAAQ,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,GAAG,EAAE,IAAI,IAAI;CAC9D;CAEA,OAAO,YAAY;EACf,UAAU,QAAQ,YAAY,CAAC;EAC/B;EACA;EACA;EACA,iBAAiB,QAAQ;EACzB,QAAQ,QAAQ,UAAU;EAC1B,QAAQ,QAAQ,UAAU,CAAC;EAC3B,OAAO,QAAQ;EACf,SAAS,QAAQ;EACjB,cAAc,YAAY,QAAQ,MAAM,OAAO;CACnD,CAAC;AACL"}
@@ -0,0 +1,25 @@
1
+ import { n as setContextProvider } from "./context-seam-DzbwDxiY.js";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ //#region src/context/context-store.ts
4
+ const storage = new AsyncLocalStorage();
5
+ function runWithContext(bindings, callback) {
6
+ const merged = {
7
+ ...storage.getStore(),
8
+ ...bindings
9
+ };
10
+ return storage.run(merged, callback);
11
+ }
12
+ function getStore() {
13
+ return storage.getStore();
14
+ }
15
+ //#endregion
16
+ //#region src/context/index.ts
17
+ setContextProvider(getStore);
18
+ function currentContext() {
19
+ const store = getStore();
20
+ return store ? { ...store } : void 0;
21
+ }
22
+ //#endregion
23
+ export { runWithContext as n, currentContext as t };
24
+
25
+ //# sourceMappingURL=context-BU9CEJZ3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-BU9CEJZ3.js","names":[],"sources":["../src/context/context-store.ts","../src/context/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport type { LogAttributes } from '../types'\n\nconst storage = new AsyncLocalStorage<LogAttributes>()\n\nfunction runWithContext<T>(\n bindings: LogAttributes,\n callback: () => T | Promise<T>\n): T | Promise<T> {\n const merged = { ...storage.getStore(), ...bindings }\n return storage.run(merged, callback)\n}\n\nfunction getStore(): LogAttributes | undefined {\n return storage.getStore()\n}\n\nexport { getStore, runWithContext }\n","import { setContextProvider } from '../context-seam'\nimport type { LogAttributes } from '../types'\nimport { getStore, runWithContext } from './context-store'\n\nsetContextProvider(getStore)\n\nfunction currentContext(): Readonly<LogAttributes> | undefined {\n const store = getStore()\n return store ? { ...store } : undefined\n}\n\nexport { currentContext, runWithContext }\n"],"mappings":";;;AAGA,MAAM,UAAU,IAAI,kBAAiC;AAErD,SAAS,eACL,UACA,UACc;CACd,MAAM,SAAS;EAAE,GAAG,QAAQ,SAAS;EAAG,GAAG;CAAS;CACpD,OAAO,QAAQ,IAAI,QAAQ,QAAQ;AACvC;AAEA,SAAS,WAAsC;CAC3C,OAAO,QAAQ,SAAS;AAC5B;;;ACXA,mBAAmB,QAAQ;AAE3B,SAAS,iBAAsD;CAC3D,MAAM,QAAQ,SAAS;CACvB,OAAO,QAAQ,EAAE,GAAG,MAAM,IAAI,KAAA;AAClC"}
@@ -0,0 +1,12 @@
1
+ //#region src/context-seam.ts
2
+ let provider;
3
+ function setContextProvider(next) {
4
+ provider = next;
5
+ }
6
+ function activeContext() {
7
+ return provider?.();
8
+ }
9
+ //#endregion
10
+ export { setContextProvider as n, activeContext as t };
11
+
12
+ //# sourceMappingURL=context-seam-DzbwDxiY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-seam-DzbwDxiY.js","names":[],"sources":["../src/context-seam.ts"],"sourcesContent":["import type { LogAttributes } from './types'\n\ntype ContextProvider = () => LogAttributes | undefined\n\nlet provider: ContextProvider | undefined\n\n// internal only — not re-exported by index.ts/browser.ts. The Node-only\n// `/context` entry calls this at module load to install its\n// AsyncLocalStorage-backed provider; the browser entry graph never imports\n// that module, so node:async_hooks never reaches it through this seam.\nfunction setContextProvider(next: ContextProvider | undefined): void {\n provider = next\n}\n\nfunction activeContext(): LogAttributes | undefined {\n return provider?.()\n}\n\nexport { activeContext, setContextProvider }\n"],"mappings":";AAIA,IAAI;AAMJ,SAAS,mBAAmB,MAAyC;CACjE,WAAW;AACf;AAEA,SAAS,gBAA2C;CAChD,OAAO,WAAW;AACtB"}
@@ -0,0 +1,7 @@
1
+ import { t as LogAttributes } from "./types-CieIrR8p.js";
2
+ //#region src/context/browser.d.ts
3
+ declare function runWithContext<T>(_bindings: LogAttributes, _callback: () => T | Promise<T>): T | Promise<T>;
4
+ declare function currentContext(): Readonly<LogAttributes> | undefined;
5
+ //#endregion
6
+ export { currentContext, runWithContext };
7
+ //# sourceMappingURL=context.browser.d.ts.map
@@ -0,0 +1,14 @@
1
+ //#region src/context/browser.ts
2
+ function throwUnsupported() {
3
+ throw new Error("@ekkolyth/logging/context is not supported in the browser — use log.with(bindings) instead");
4
+ }
5
+ function runWithContext(_bindings, _callback) {
6
+ throwUnsupported();
7
+ }
8
+ function currentContext() {
9
+ throwUnsupported();
10
+ }
11
+ //#endregion
12
+ export { currentContext, runWithContext };
13
+
14
+ //# sourceMappingURL=context.browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.browser.js","names":[],"sources":["../src/context/browser.ts"],"sourcesContent":["import type { LogAttributes } from '../types'\n\nfunction throwUnsupported(): never {\n throw new Error(\n '@ekkolyth/logging/context is not supported in the browser — use log.with(bindings) instead'\n )\n}\n\nfunction runWithContext<T>(\n _bindings: LogAttributes,\n _callback: () => T | Promise<T>\n): T | Promise<T> {\n throwUnsupported()\n}\n\nfunction currentContext(): Readonly<LogAttributes> | undefined {\n throwUnsupported()\n}\n\nexport { currentContext, runWithContext }\n"],"mappings":";AAEA,SAAS,mBAA0B;CAC/B,MAAM,IAAI,MACN,4FACJ;AACJ;AAEA,SAAS,eACL,WACA,WACc;CACd,iBAAiB;AACrB;AAEA,SAAS,iBAAsD;CAC3D,iBAAiB;AACrB"}
@@ -0,0 +1,9 @@
1
+ import { t as LogAttributes } from "./types-CieIrR8p.js";
2
+ //#region src/context/context-store.d.ts
3
+ declare function runWithContext<T>(bindings: LogAttributes, callback: () => T | Promise<T>): T | Promise<T>;
4
+ //#endregion
5
+ //#region src/context/index.d.ts
6
+ declare function currentContext(): Readonly<LogAttributes> | undefined;
7
+ //#endregion
8
+ export { currentContext, runWithContext };
9
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1,2 @@
1
+ import { n as runWithContext, t as currentContext } from "./context-BU9CEJZ3.js";
2
+ export { currentContext, runWithContext };
@@ -0,0 +1,50 @@
1
+ //#region src/env.ts
2
+ const LEVELS = [
3
+ "debug",
4
+ "info",
5
+ "warn",
6
+ "error",
7
+ "silent"
8
+ ];
9
+ const FORMATS = [
10
+ "auto",
11
+ "json",
12
+ "pretty"
13
+ ];
14
+ const assertLevel = (value) => {
15
+ if (!LEVELS.includes(value)) throw new TypeError(`@ekkolyth/logging: invalid level "${value}"`);
16
+ return value;
17
+ };
18
+ const assertFormat = (value) => {
19
+ if (!FORMATS.includes(value)) throw new TypeError(`@ekkolyth/logging: invalid format "${value}"`);
20
+ return value;
21
+ };
22
+ function resolveLevel(explicit, env) {
23
+ if (explicit !== void 0) return assertLevel(explicit);
24
+ const fromEnv = env.LOG_LEVEL;
25
+ if (fromEnv !== void 0 && fromEnv !== "") return assertLevel(fromEnv);
26
+ return "info";
27
+ }
28
+ function resolveFormat(explicit, env, preferPretty) {
29
+ if (explicit !== void 0) {
30
+ const valid = assertFormat(explicit);
31
+ if (valid !== "auto") return valid;
32
+ }
33
+ const fromEnv = env.LOG_FORMAT;
34
+ if (fromEnv !== void 0 && fromEnv !== "") {
35
+ const valid = assertFormat(fromEnv);
36
+ if (valid !== "auto") return valid;
37
+ }
38
+ return preferPretty ? "pretty" : "json";
39
+ }
40
+ function resolveColors(explicit, env, preferColor) {
41
+ if (typeof explicit === "boolean") return explicit;
42
+ if (env.NO_COLOR !== void 0) return false;
43
+ const forceColor = env.FORCE_COLOR;
44
+ if (forceColor !== void 0) return forceColor !== "0";
45
+ return preferColor;
46
+ }
47
+ //#endregion
48
+ export { resolveFormat as n, resolveLevel as r, resolveColors as t };
49
+
50
+ //# sourceMappingURL=env-Dur55C_F.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-Dur55C_F.js","names":[],"sources":["../src/env.ts"],"sourcesContent":["import type { LogFormat, LogLevel } from './types'\n\ntype ColorOption = 'auto' | boolean | undefined\ntype EnvSource = Record<string, string | undefined>\n\nconst LEVELS: readonly LogLevel[] = ['debug', 'info', 'warn', 'error', 'silent']\nconst FORMATS: readonly LogFormat[] = ['auto', 'json', 'pretty']\n\nconst assertLevel = (value: string): LogLevel => {\n if (!(LEVELS as readonly string[]).includes(value)) {\n throw new TypeError(`@ekkolyth/logging: invalid level \"${value}\"`)\n }\n return value as LogLevel\n}\n\nconst assertFormat = (value: string): LogFormat => {\n if (!(FORMATS as readonly string[]).includes(value)) {\n throw new TypeError(`@ekkolyth/logging: invalid format \"${value}\"`)\n }\n return value as LogFormat\n}\n\nfunction resolveLevel(\n explicit: LogLevel | undefined,\n env: EnvSource\n): LogLevel {\n if (explicit !== undefined) return assertLevel(explicit)\n // set-but-empty means unset: compose interpolates an omitted host var\n // to '', and an empty value carries no intent\n const fromEnv = env.LOG_LEVEL\n if (fromEnv !== undefined && fromEnv !== '') return assertLevel(fromEnv)\n return 'info'\n}\n\nfunction resolveFormat(\n explicit: LogFormat | undefined,\n env: EnvSource,\n preferPretty: boolean\n): Exclude<LogFormat, 'auto'> {\n if (explicit !== undefined) {\n const valid = assertFormat(explicit)\n if (valid !== 'auto') return valid\n }\n const fromEnv = env.LOG_FORMAT\n if (fromEnv !== undefined && fromEnv !== '') {\n const valid = assertFormat(fromEnv)\n if (valid !== 'auto') return valid\n }\n return preferPretty ? 'pretty' : 'json'\n}\n\nfunction resolveColors(\n explicit: ColorOption,\n env: EnvSource,\n preferColor: boolean\n): boolean {\n if (typeof explicit === 'boolean') return explicit\n if (env.NO_COLOR !== undefined) return false\n const forceColor = env.FORCE_COLOR\n if (forceColor !== undefined) return forceColor !== '0'\n return preferColor\n}\n\nexport type { ColorOption, EnvSource }\nexport { resolveColors, resolveFormat, resolveLevel }\n"],"mappings":";AAKA,MAAM,SAA8B;CAAC;CAAS;CAAQ;CAAQ;CAAS;AAAQ;AAC/E,MAAM,UAAgC;CAAC;CAAQ;CAAQ;AAAQ;AAE/D,MAAM,eAAe,UAA4B;CAC7C,IAAI,CAAE,OAA6B,SAAS,KAAK,GAC7C,MAAM,IAAI,UAAU,qCAAqC,MAAM,EAAE;CAErE,OAAO;AACX;AAEA,MAAM,gBAAgB,UAA6B;CAC/C,IAAI,CAAE,QAA8B,SAAS,KAAK,GAC9C,MAAM,IAAI,UAAU,sCAAsC,MAAM,EAAE;CAEtE,OAAO;AACX;AAEA,SAAS,aACL,UACA,KACQ;CACR,IAAI,aAAa,KAAA,GAAW,OAAO,YAAY,QAAQ;CAGvD,MAAM,UAAU,IAAI;CACpB,IAAI,YAAY,KAAA,KAAa,YAAY,IAAI,OAAO,YAAY,OAAO;CACvE,OAAO;AACX;AAEA,SAAS,cACL,UACA,KACA,cAC0B;CAC1B,IAAI,aAAa,KAAA,GAAW;EACxB,MAAM,QAAQ,aAAa,QAAQ;EACnC,IAAI,UAAU,QAAQ,OAAO;CACjC;CACA,MAAM,UAAU,IAAI;CACpB,IAAI,YAAY,KAAA,KAAa,YAAY,IAAI;EACzC,MAAM,QAAQ,aAAa,OAAO;EAClC,IAAI,UAAU,QAAQ,OAAO;CACjC;CACA,OAAO,eAAe,WAAW;AACrC;AAEA,SAAS,cACL,UACA,KACA,aACO;CACP,IAAI,OAAO,aAAa,WAAW,OAAO;CAC1C,IAAI,IAAI,aAAa,KAAA,GAAW,OAAO;CACvC,MAAM,aAAa,IAAI;CACvB,IAAI,eAAe,KAAA,GAAW,OAAO,eAAe;CACpD,OAAO;AACX"}
@@ -0,0 +1,16 @@
1
+ import { n as Logger } from "./logger-V7XF9Wuo.js";
2
+ import { n as FetchOptions, t as FetchLike } from "./outbound-CQkyIyxd.js";
3
+ //#region src/http/browser.d.ts
4
+ type RequestHandler = (request: Request) => Response | Promise<Response>;
5
+ interface HandlerOptions {
6
+ clientIp?: (request: Request) => string | undefined;
7
+ exclude?: (request: Request) => boolean;
8
+ route?: (request: Request) => string | undefined;
9
+ sampleRate?: number;
10
+ slowThresholdMs?: number;
11
+ }
12
+ declare function wrapHandler(_log: Logger, _handler: RequestHandler, _options?: HandlerOptions): RequestHandler;
13
+ declare const wrapFetch: (log: Logger, fetchFn: FetchLike, options?: FetchOptions) => FetchLike;
14
+ //#endregion
15
+ export { type FetchLike, type FetchOptions, type HandlerOptions, type RequestHandler, wrapFetch, wrapHandler };
16
+ //# sourceMappingURL=http.browser.d.ts.map
@@ -0,0 +1,23 @@
1
+ import { n as getBindings } from "./logger-BxxJhfg8.js";
2
+ import { t as createWrapFetch } from "./outbound-YYy-EfTF.js";
3
+ //#region src/http/browser.ts
4
+ function throwUnsupported() {
5
+ throw new Error("@ekkolyth/logging/http wrapHandler is not supported in the browser — concurrent request context has no platform adapter there. Use wrapFetch instead.");
6
+ }
7
+ function wrapHandler(_log, _handler, _options) {
8
+ return throwUnsupported();
9
+ }
10
+ function bindingsAsAmbient(log) {
11
+ const bindings = getBindings(log);
12
+ const asString = (value) => typeof value === "string" ? value : void 0;
13
+ return {
14
+ requestId: asString(bindings.request_id),
15
+ traceId: asString(bindings.trace_id),
16
+ spanId: asString(bindings.span_id)
17
+ };
18
+ }
19
+ const wrapFetch = createWrapFetch(bindingsAsAmbient);
20
+ //#endregion
21
+ export { wrapFetch, wrapHandler };
22
+
23
+ //# sourceMappingURL=http.browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.browser.js","names":[],"sources":["../src/http/browser.ts"],"sourcesContent":["import { getBindings, type Logger } from '../logger'\nimport type { AmbientCorrelation } from './correlation'\nimport { createWrapFetch, type FetchLike, type FetchOptions } from './outbound'\n\ntype RequestHandler = (request: Request) => Response | Promise<Response>\n\ninterface HandlerOptions {\n clientIp?: (request: Request) => string | undefined\n exclude?: (request: Request) => boolean\n route?: (request: Request) => string | undefined\n sampleRate?: number\n slowThresholdMs?: number\n}\n\nfunction throwUnsupported(): never {\n throw new Error(\n '@ekkolyth/logging/http wrapHandler is not supported in the browser — concurrent request context has no platform adapter there. Use wrapFetch instead.'\n )\n}\n\n// Concurrent request context has no platform adapter in the browser, so\n// wrapHandler always throws — it never touches its arguments.\nfunction wrapHandler(\n _log: Logger,\n _handler: RequestHandler,\n _options?: HandlerOptions\n): RequestHandler {\n return throwUnsupported()\n}\n\nfunction bindingsAsAmbient(log: Logger): AmbientCorrelation {\n const bindings = getBindings(log)\n const asString = (value: unknown): string | undefined =>\n typeof value === 'string' ? value : undefined\n return {\n requestId: asString(bindings.request_id),\n traceId: asString(bindings.trace_id),\n spanId: asString(bindings.span_id),\n }\n}\n\n// Browser wrapFetch has no ambient async context, so it derives correlation\n// from explicit request headers and the bound attributes of the logger\n// passed to it (log.with({ request_id, trace_id, span_id })).\nconst wrapFetch = createWrapFetch(bindingsAsAmbient)\n\nexport type { FetchLike, FetchOptions, HandlerOptions, RequestHandler }\nexport { wrapFetch, wrapHandler }\n"],"mappings":";;;AAcA,SAAS,mBAA0B;CAC/B,MAAM,IAAI,MACN,uJACJ;AACJ;AAIA,SAAS,YACL,MACA,UACA,UACc;CACd,OAAO,iBAAiB;AAC5B;AAEA,SAAS,kBAAkB,KAAiC;CACxD,MAAM,WAAW,YAAY,GAAG;CAChC,MAAM,YAAY,UACd,OAAO,UAAU,WAAW,QAAQ,KAAA;CACxC,OAAO;EACH,WAAW,SAAS,SAAS,UAAU;EACvC,SAAS,SAAS,SAAS,QAAQ;EACnC,QAAQ,SAAS,SAAS,OAAO;CACrC;AACJ;AAKA,MAAM,YAAY,gBAAgB,iBAAiB"}
package/dist/http.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { n as Logger } from "./logger-V7XF9Wuo.js";
2
+ import { n as FetchOptions, t as FetchLike } from "./outbound-CQkyIyxd.js";
3
+ //#region src/http/index.d.ts
4
+ type RequestHandler = (request: Request) => Response | Promise<Response>;
5
+ interface HandlerOptions {
6
+ clientIp?: (request: Request) => string | undefined;
7
+ exclude?: (request: Request) => boolean;
8
+ /**
9
+ * Drops repeat fast-2xx completion records sharing method|path|status
10
+ * inside the window; warn, error, and slow records always emit.
11
+ * undefined means ten minutes, 0 disables.
12
+ */
13
+ quietWindowMs?: number;
14
+ route?: (request: Request) => string | undefined;
15
+ sampleRate?: number;
16
+ slowThresholdMs?: number;
17
+ }
18
+ declare const wrapFetch: (log: Logger, fetchFn: FetchLike, options?: FetchOptions) => FetchLike;
19
+ declare function wrapHandler(log: Logger, handler: RequestHandler, options?: HandlerOptions): RequestHandler;
20
+ //#endregion
21
+ export { type FetchLike, type FetchOptions, type HandlerOptions, type RequestHandler, wrapFetch, wrapHandler };
22
+ //# sourceMappingURL=http.d.ts.map
package/dist/http.js ADDED
@@ -0,0 +1,112 @@
1
+ import { t as Throttler } from "./throttler-s0TCHZNK.js";
2
+ import { n as runWithContext } from "./context-BU9CEJZ3.js";
3
+ import { a as shouldSample, i as resolveSlowThresholdMs, n as samplingIdentifier, o as deriveInboundCorrelation, r as resolveSampleRate, t as createWrapFetch } from "./outbound-YYy-EfTF.js";
4
+ import { AsyncLocalStorage } from "node:async_hooks";
5
+ //#region src/http/index.ts
6
+ const DEFAULT_QUIET_WINDOW_MS = 6e5;
7
+ const correlationStore = new AsyncLocalStorage();
8
+ const wrapFetch = createWrapFetch(() => correlationStore.getStore());
9
+ function echoRequestId(response, requestId) {
10
+ try {
11
+ response.headers.set("x-request-id", requestId);
12
+ return response;
13
+ } catch {}
14
+ try {
15
+ const rebuilt = new Response(response.body, response);
16
+ rebuilt.headers.set("x-request-id", requestId);
17
+ return rebuilt;
18
+ } catch {
19
+ return response;
20
+ }
21
+ }
22
+ function wrapHandler(log, handler, options = {}) {
23
+ const sampleRate = resolveSampleRate(options.sampleRate);
24
+ const slowThresholdMs = resolveSlowThresholdMs(options.slowThresholdMs);
25
+ const quiet = new Throttler(options.quietWindowMs ?? DEFAULT_QUIET_WINDOW_MS);
26
+ return (request) => {
27
+ const correlation = deriveInboundCorrelation(request.headers);
28
+ const excluded = options.exclude?.(request) ?? false;
29
+ const start = performance.now();
30
+ return runWithContext({
31
+ request_id: correlation.requestId,
32
+ span_id: correlation.spanId,
33
+ trace_id: correlation.traceId
34
+ }, () => correlationStore.run(correlation, async () => {
35
+ let response;
36
+ let failure;
37
+ let didFail = false;
38
+ try {
39
+ response = await handler(request);
40
+ } catch (error) {
41
+ failure = error;
42
+ didFail = true;
43
+ }
44
+ const durationMs = performance.now() - start;
45
+ if (!excluded) emitInboundRecord({
46
+ correlation,
47
+ didFail,
48
+ durationMs,
49
+ failure,
50
+ log,
51
+ options,
52
+ quiet,
53
+ request,
54
+ response,
55
+ sampleRate,
56
+ slowThresholdMs
57
+ });
58
+ if (didFail) throw failure;
59
+ return echoRequestId(response, correlation.requestId);
60
+ }));
61
+ };
62
+ }
63
+ function classifyLevel(input) {
64
+ const { correlation, didFail, durationMs, sampleRate, slowThresholdMs, status } = input;
65
+ if (didFail) return "error";
66
+ if (status >= 500) return "error";
67
+ if (status >= 400) return "warn";
68
+ if (durationMs >= slowThresholdMs) return "warn";
69
+ if (!shouldSample(samplingIdentifier(correlation), sampleRate)) return void 0;
70
+ return "info";
71
+ }
72
+ function emitInboundRecord(input) {
73
+ const { correlation, didFail, durationMs, failure, log, options, quiet, request, response, sampleRate, slowThresholdMs } = input;
74
+ const status = didFail ? 500 : response?.status ?? 500;
75
+ const level = classifyLevel({
76
+ correlation,
77
+ didFail,
78
+ durationMs,
79
+ sampleRate,
80
+ slowThresholdMs,
81
+ status
82
+ });
83
+ if (level === void 0) return;
84
+ if (level === "info" && !quiet.shouldEmit(request.method, new URL(request.url).pathname, String(status))) return;
85
+ const attributes = {
86
+ method: request.method,
87
+ path: new URL(request.url).pathname
88
+ };
89
+ const route = options.route?.(request);
90
+ if (route) attributes.route = route;
91
+ attributes.status = status;
92
+ attributes.duration_ms = Math.trunc(durationMs);
93
+ const sizeHeader = response?.headers.get("content-length");
94
+ if (sizeHeader !== null && sizeHeader !== void 0) {
95
+ const size = Number(sizeHeader);
96
+ if (Number.isFinite(size)) attributes.response_size_bytes = size;
97
+ }
98
+ attributes.request_id = correlation.requestId;
99
+ attributes.trace_id = correlation.traceId;
100
+ attributes.span_id = correlation.spanId;
101
+ const ip = options.clientIp?.(request);
102
+ if (ip) attributes.ip = ip;
103
+ const userAgent = request.headers.get("user-agent");
104
+ if (userAgent) attributes.user_agent = userAgent;
105
+ if (level === "info" && sampleRate < 1) attributes.sample_rate = sampleRate;
106
+ if (didFail) attributes.error = failure;
107
+ log[level]("request completed", attributes);
108
+ }
109
+ //#endregion
110
+ export { wrapFetch, wrapHandler };
111
+
112
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","names":[],"sources":["../src/http/index.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\n// importing runWithContext also runs ../context's module-load side effect\n// (setContextProvider(getStore)), which is what lets an application log\n// call anywhere inside a wrapped request pick up request_id/trace_id/\n// span_id even when the app never imports @ekkolyth/logging/context itself.\nimport { runWithContext } from '../context'\nimport type { Logger } from '../logger'\nimport { Throttler } from '../throttler'\nimport { type Correlation, deriveInboundCorrelation } from './correlation'\nimport {\n createWrapFetch,\n type FetchLike,\n type FetchOptions,\n type Level,\n samplingIdentifier,\n} from './outbound'\nimport {\n resolveSampleRate,\n resolveSlowThresholdMs,\n shouldSample,\n} from './sampling'\n\ntype RequestHandler = (request: Request) => Response | Promise<Response>\n\ninterface HandlerOptions {\n clientIp?: (request: Request) => string | undefined\n exclude?: (request: Request) => boolean\n /**\n * Drops repeat fast-2xx completion records sharing method|path|status\n * inside the window; warn, error, and slow records always emit.\n * undefined means ten minutes, 0 disables.\n */\n quietWindowMs?: number\n route?: (request: Request) => string | undefined\n sampleRate?: number\n slowThresholdMs?: number\n}\n\nconst DEFAULT_QUIET_WINDOW_MS = 600_000\n\n// carries the full correlation (including flags/tracestate, which never\n// appear on an ordinary log line) between a wrapHandler invocation and any\n// wrapFetch call nested inside it — separate from the logging context,\n// which only ever exposes request_id/trace_id/span_id.\nconst correlationStore = new AsyncLocalStorage<Correlation>()\n\nconst wrapFetch = createWrapFetch(() => correlationStore.getStore())\n\n// Some Response instances guard their headers against mutation (a response\n// straight out of an upstream fetch(), Response.redirect(), Response.error()).\n// Rebuild a mutable copy rather than crash on the echo; if even the rebuild\n// fails (e.g. the body stream is already locked), fall back to the original\n// response untouched rather than losing it or throwing out of the handler.\nfunction echoRequestId(response: Response, requestId: string): Response {\n try {\n response.headers.set('x-request-id', requestId)\n return response\n } catch {\n // fall through to the rebuild path below\n }\n try {\n const rebuilt = new Response(response.body, response)\n rebuilt.headers.set('x-request-id', requestId)\n return rebuilt\n } catch {\n return response\n }\n}\n\nfunction wrapHandler(\n log: Logger,\n handler: RequestHandler,\n options: HandlerOptions = {}\n): RequestHandler {\n const sampleRate = resolveSampleRate(options.sampleRate)\n const slowThresholdMs = resolveSlowThresholdMs(options.slowThresholdMs)\n const quiet = new Throttler(\n options.quietWindowMs ?? DEFAULT_QUIET_WINDOW_MS\n )\n\n return (request: Request) => {\n const correlation = deriveInboundCorrelation(request.headers)\n const excluded = options.exclude?.(request) ?? false\n const start = performance.now()\n\n return runWithContext(\n {\n request_id: correlation.requestId,\n span_id: correlation.spanId,\n trace_id: correlation.traceId,\n },\n () =>\n correlationStore.run(correlation, async () => {\n let response: Response | undefined\n let failure: unknown\n let didFail = false\n try {\n response = await handler(request)\n } catch (error) {\n failure = error\n didFail = true\n }\n const durationMs = performance.now() - start\n\n if (!excluded) {\n emitInboundRecord({\n correlation,\n didFail,\n durationMs,\n failure,\n log,\n options,\n quiet,\n request,\n response,\n sampleRate,\n slowThresholdMs,\n })\n }\n\n if (didFail) throw failure\n return echoRequestId(\n response as Response,\n correlation.requestId\n )\n })\n )\n }\n}\n\nfunction classifyLevel(input: {\n correlation: Pick<Correlation, 'requestId' | 'traceId'>\n didFail: boolean\n durationMs: number\n sampleRate: number\n slowThresholdMs: number\n status: number\n}): Level | undefined {\n const {\n correlation,\n didFail,\n durationMs,\n sampleRate,\n slowThresholdMs,\n status,\n } = input\n if (didFail) return 'error'\n if (status >= 500) return 'error'\n if (status >= 400) return 'warn'\n if (durationMs >= slowThresholdMs) return 'warn'\n if (!shouldSample(samplingIdentifier(correlation), sampleRate))\n return undefined\n return 'info'\n}\n\nfunction emitInboundRecord(input: {\n correlation: Correlation\n didFail: boolean\n durationMs: number\n failure: unknown\n log: Logger\n options: HandlerOptions\n quiet: Throttler\n request: Request\n response: Response | undefined\n sampleRate: number\n slowThresholdMs: number\n}): void {\n const {\n correlation,\n didFail,\n durationMs,\n failure,\n log,\n options,\n quiet,\n request,\n response,\n sampleRate,\n slowThresholdMs,\n } = input\n const status = didFail ? 500 : (response?.status ?? 500)\n\n const level = classifyLevel({\n correlation,\n didFail,\n durationMs,\n sampleRate,\n slowThresholdMs,\n status,\n })\n if (level === undefined) return\n // same policy as the old request logger: only the fast-2xx class is\n // quieted; warn, error, and slow records always emit\n if (\n level === 'info' &&\n !quiet.shouldEmit(\n request.method,\n new URL(request.url).pathname,\n String(status)\n )\n )\n return\n\n const attributes: Record<string, unknown> = {\n method: request.method,\n path: new URL(request.url).pathname,\n }\n\n const route = options.route?.(request)\n if (route) attributes.route = route\n\n attributes.status = status\n attributes.duration_ms = Math.trunc(durationMs)\n\n const sizeHeader = response?.headers.get('content-length')\n if (sizeHeader !== null && sizeHeader !== undefined) {\n const size = Number(sizeHeader)\n if (Number.isFinite(size)) attributes.response_size_bytes = size\n }\n\n attributes.request_id = correlation.requestId\n attributes.trace_id = correlation.traceId\n attributes.span_id = correlation.spanId\n\n const ip = options.clientIp?.(request)\n if (ip) attributes.ip = ip\n\n const userAgent = request.headers.get('user-agent')\n if (userAgent) attributes.user_agent = userAgent\n\n if (level === 'info' && sampleRate < 1) attributes.sample_rate = sampleRate\n if (didFail) attributes.error = failure\n\n log[level]('request completed', attributes)\n}\n\nexport type { FetchLike, FetchOptions, HandlerOptions, RequestHandler }\nexport { wrapFetch, wrapHandler }\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { a as Window, i as RateLimited, n as Logger, r as LoggerOptions, t as LogOutput } from "./logger-V7XF9Wuo.js";
2
+ import { n as LogFormat, r as LogLevel, t as LogAttributes } from "./types-CieIrR8p.js";
3
+ //#region src/index.d.ts
4
+ declare function createLogger(options?: LoggerOptions): Logger;
5
+ //#endregion
6
+ export { type LogAttributes, type LogFormat, type LogLevel, type LogOutput, type Logger, type LoggerOptions, type RateLimited, type Window, createLogger };
7
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,63 @@
1
+ import { n as resolveFormat, r as resolveLevel, t as resolveColors } from "./env-Dur55C_F.js";
2
+ import { t as buildEngine } from "./logger-BxxJhfg8.js";
3
+ import { appendFileSync, openSync } from "node:fs";
4
+ //#region src/file-output.ts
5
+ const writers = /* @__PURE__ */ new Map();
6
+ function resolveFileWriter(service, env) {
7
+ if (!service || env.LOG_SOURCE !== "file") return null;
8
+ for (const entry of (env.LOG_FILE_MAP ?? "").split(",")) {
9
+ const separator = entry.indexOf(":");
10
+ if (separator === -1) continue;
11
+ if (entry.slice(0, separator).trim() !== service) continue;
12
+ const path = entry.slice(separator + 1).trim();
13
+ if (!path) return null;
14
+ const cached = writers.get(path);
15
+ if (cached) return cached;
16
+ try {
17
+ const fd = openSync(path, "a");
18
+ const writer = (line) => {
19
+ try {
20
+ appendFileSync(fd, line);
21
+ } catch {}
22
+ };
23
+ writers.set(path, writer);
24
+ return writer;
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+ return null;
30
+ }
31
+ //#endregion
32
+ //#region src/index.ts
33
+ function createLogger(options = {}) {
34
+ const env = typeof process !== "undefined" && process.env ? process.env : {};
35
+ const isTTY = typeof process !== "undefined" && Boolean(process.stdout?.isTTY);
36
+ const level = resolveLevel(options.level, env);
37
+ const format = resolveFormat(options.format, env, isTTY);
38
+ const colors = resolveColors(options.colors, env, isTTY);
39
+ const toFile = resolveFileWriter(options.service, env);
40
+ const defaultOutput = (line) => {
41
+ if (typeof process !== "undefined" && process.stdout) process.stdout.write(line);
42
+ if (toFile) toFile(line);
43
+ };
44
+ const stderrWrite = (message) => {
45
+ if (typeof process !== "undefined" && process.stderr) process.stderr.write(message);
46
+ };
47
+ return buildEngine({
48
+ bindings: options.bindings ?? {},
49
+ colors,
50
+ format,
51
+ level,
52
+ onInternalError: options.onInternalError,
53
+ output: options.output ?? defaultOutput,
54
+ redact: options.redact ?? [],
55
+ scope: options.scope,
56
+ service: options.service,
57
+ stderrWrite
58
+ });
59
+ }
60
+ //#endregion
61
+ export { createLogger };
62
+
63
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/file-output.ts","../src/index.ts"],"sourcesContent":["import { appendFileSync, openSync } from 'node:fs'\n\ntype FileWriter = (line: string) => void\n\n// one handle per path for the process lifetime, shared by every logger that\n// resolves to it — never closed, same as stdout\nconst writers = new Map<string, FileWriter>()\n\n// LOG_SOURCE=file mirrors each service's lines into a file alongside stdout so\n// a log viewer can read processes that are not containers in local dev.\n// LOG_FILE_MAP is \"service:path\" pairs keyed by the logger's own service name.\nfunction resolveFileWriter(\n service: string | undefined,\n env: Record<string, string | undefined>\n): FileWriter | null {\n if (!service || env.LOG_SOURCE !== 'file') return null\n\n for (const entry of (env.LOG_FILE_MAP ?? '').split(',')) {\n const separator = entry.indexOf(':')\n if (separator === -1) continue\n if (entry.slice(0, separator).trim() !== service) continue\n\n const path = entry.slice(separator + 1).trim()\n if (!path) return null\n\n const cached = writers.get(path)\n if (cached) return cached\n try {\n const fd = openSync(path, 'a')\n const writer: FileWriter = (line) => {\n try {\n appendFileSync(fd, line)\n } catch {\n // disk full / fd revoked — a failed mirror never costs the\n // caller their log line on stdout\n }\n }\n writers.set(path, writer)\n return writer\n } catch {\n return null\n }\n }\n return null\n}\n\nexport type { FileWriter }\nexport { resolveFileWriter }\n","import { resolveColors, resolveFormat, resolveLevel } from './env'\nimport { resolveFileWriter } from './file-output'\nimport {\n buildEngine,\n type Logger,\n type LoggerOptions,\n type LogOutput,\n} from './logger'\n\nfunction createLogger(options: LoggerOptions = {}): Logger {\n const env = typeof process !== 'undefined' && process.env ? process.env : {}\n const isTTY =\n typeof process !== 'undefined' && Boolean(process.stdout?.isTTY)\n\n const level = resolveLevel(options.level, env)\n const format = resolveFormat(options.format, env, isTTY)\n const colors = resolveColors(options.colors, env, isTTY)\n\n // resolved against the real stdout, so mirroring to a file never changes\n // the format or colors the terminal gets\n const toFile = resolveFileWriter(options.service, env)\n const defaultOutput: LogOutput = (line) => {\n if (typeof process !== 'undefined' && process.stdout) {\n process.stdout.write(line)\n }\n if (toFile) toFile(line)\n }\n const stderrWrite = (message: string): void => {\n if (typeof process !== 'undefined' && process.stderr) {\n process.stderr.write(message)\n }\n }\n\n return buildEngine({\n bindings: options.bindings ?? {},\n colors,\n format,\n level,\n onInternalError: options.onInternalError,\n output: options.output ?? defaultOutput,\n redact: options.redact ?? [],\n scope: options.scope,\n service: options.service,\n stderrWrite,\n })\n}\n\nexport type { Logger, LoggerOptions, LogOutput, RateLimited } from './logger'\nexport type { Window } from './throttler'\nexport type { LogAttributes, LogFormat, LogLevel } from './types'\nexport { createLogger }\n"],"mappings":";;;;AAMA,MAAM,0BAAU,IAAI,IAAwB;AAK5C,SAAS,kBACL,SACA,KACiB;CACjB,IAAI,CAAC,WAAW,IAAI,eAAe,QAAQ,OAAO;CAElD,KAAK,MAAM,UAAU,IAAI,gBAAgB,GAAA,CAAI,MAAM,GAAG,GAAG;EACrD,MAAM,YAAY,MAAM,QAAQ,GAAG;EACnC,IAAI,cAAc,IAAI;EACtB,IAAI,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,KAAK,MAAM,SAAS;EAElD,MAAM,OAAO,MAAM,MAAM,YAAY,CAAC,CAAC,CAAC,KAAK;EAC7C,IAAI,CAAC,MAAM,OAAO;EAElB,MAAM,SAAS,QAAQ,IAAI,IAAI;EAC/B,IAAI,QAAQ,OAAO;EACnB,IAAI;GACA,MAAM,KAAK,SAAS,MAAM,GAAG;GAC7B,MAAM,UAAsB,SAAS;IACjC,IAAI;KACA,eAAe,IAAI,IAAI;IAC3B,QAAQ,CAGR;GACJ;GACA,QAAQ,IAAI,MAAM,MAAM;GACxB,OAAO;EACX,QAAQ;GACJ,OAAO;EACX;CACJ;CACA,OAAO;AACX;;;ACnCA,SAAS,aAAa,UAAyB,CAAC,GAAW;CACvD,MAAM,MAAM,OAAO,YAAY,eAAe,QAAQ,MAAM,QAAQ,MAAM,CAAC;CAC3E,MAAM,QACF,OAAO,YAAY,eAAe,QAAQ,QAAQ,QAAQ,KAAK;CAEnE,MAAM,QAAQ,aAAa,QAAQ,OAAO,GAAG;CAC7C,MAAM,SAAS,cAAc,QAAQ,QAAQ,KAAK,KAAK;CACvD,MAAM,SAAS,cAAc,QAAQ,QAAQ,KAAK,KAAK;CAIvD,MAAM,SAAS,kBAAkB,QAAQ,SAAS,GAAG;CACrD,MAAM,iBAA4B,SAAS;EACvC,IAAI,OAAO,YAAY,eAAe,QAAQ,QAC1C,QAAQ,OAAO,MAAM,IAAI;EAE7B,IAAI,QAAQ,OAAO,IAAI;CAC3B;CACA,MAAM,eAAe,YAA0B;EAC3C,IAAI,OAAO,YAAY,eAAe,QAAQ,QAC1C,QAAQ,OAAO,MAAM,OAAO;CAEpC;CAEA,OAAO,YAAY;EACf,UAAU,QAAQ,YAAY,CAAC;EAC/B;EACA;EACA;EACA,iBAAiB,QAAQ;EACzB,QAAQ,QAAQ,UAAU;EAC1B,QAAQ,QAAQ,UAAU,CAAC;EAC3B,OAAO,QAAQ;EACf,SAAS,QAAQ;EACjB;CACJ,CAAC;AACL"}