@cleverbrush/log 0.0.0-beta-20260424142030

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.
Files changed (43) hide show
  1. package/README.md +80 -0
  2. package/dist/Enricher.d.ts +8 -0
  3. package/dist/Filter.d.ts +7 -0
  4. package/dist/LogContext.d.ts +58 -0
  5. package/dist/LogEvent.d.ts +24 -0
  6. package/dist/LogLevel.d.ts +44 -0
  7. package/dist/Logger.d.ts +111 -0
  8. package/dist/LoggerPipeline.d.ts +48 -0
  9. package/dist/MessageTemplate.d.ts +58 -0
  10. package/dist/SelfLog.d.ts +37 -0
  11. package/dist/Sink.d.ts +14 -0
  12. package/dist/chunk-EU6TDBKQ.js +3 -0
  13. package/dist/chunk-EU6TDBKQ.js.map +1 -0
  14. package/dist/clickhouse.d.ts +1 -0
  15. package/dist/clickhouse.js +16 -0
  16. package/dist/clickhouse.js.map +1 -0
  17. package/dist/correlation.d.ts +22 -0
  18. package/dist/createLogger.d.ts +51 -0
  19. package/dist/di.d.ts +29 -0
  20. package/dist/enrichers/application.d.ts +8 -0
  21. package/dist/enrichers/caller.d.ts +10 -0
  22. package/dist/enrichers/correlationId.d.ts +8 -0
  23. package/dist/enrichers/environment.d.ts +8 -0
  24. package/dist/enrichers/hostname.d.ts +8 -0
  25. package/dist/enrichers/index.d.ts +6 -0
  26. package/dist/enrichers/processId.d.ts +8 -0
  27. package/dist/formatters/ClefFormatter.d.ts +33 -0
  28. package/dist/index.d.ts +29 -0
  29. package/dist/index.js +9 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/middleware/correlationId.d.ts +28 -0
  32. package/dist/middleware/requestLogging.d.ts +41 -0
  33. package/dist/samplingFilter.d.ts +23 -0
  34. package/dist/serialization.d.ts +25 -0
  35. package/dist/sinks/BatchingSink.d.ts +45 -0
  36. package/dist/sinks/ClickHouseSink.d.ts +78 -0
  37. package/dist/sinks/ConsoleSink.d.ts +29 -0
  38. package/dist/sinks/FileSink.d.ts +43 -0
  39. package/dist/sinks/SeqSink.d.ts +37 -0
  40. package/dist/sinks/createSink.d.ts +35 -0
  41. package/dist/sinks/index.d.ts +5 -0
  42. package/dist/useLogging.d.ts +35 -0
  43. package/package.json +69 -0
@@ -0,0 +1,51 @@
1
+ import type { Enricher } from './Enricher.js';
2
+ import type { LogFilter } from './Filter.js';
3
+ import { Logger } from './Logger.js';
4
+ import { LogLevel, type LogLevelName } from './LogLevel.js';
5
+ import type { LogSink } from './Sink.js';
6
+ /**
7
+ * Configuration for creating a structured logger.
8
+ */
9
+ export interface LoggerConfig {
10
+ /** Minimum log level (name or numeric). @default 'information' */
11
+ minimumLevel?: LogLevelName | LogLevel;
12
+ /** Namespace-level overrides for minimum log level. */
13
+ levelOverrides?: Record<string, LogLevelName>;
14
+ /** Output sinks for log events. */
15
+ sinks: LogSink[];
16
+ /** Enrichers that add properties to every event. */
17
+ enrichers?: Enricher[];
18
+ /** Filters that determine which events pass through. */
19
+ filters?: LogFilter[];
20
+ /** Maximum queued events before dropping. @default 10000 */
21
+ maxQueueSize?: number;
22
+ /** Policy when queue is full. @default 'dropOldest' */
23
+ dropPolicy?: 'dropOldest' | 'dropNewest' | 'block';
24
+ /** Whether to hook `SIGTERM` and `beforeExit` for flush. @default false */
25
+ handleProcessExit?: boolean;
26
+ }
27
+ /**
28
+ * Creates a structured logger with the specified configuration.
29
+ *
30
+ * The logger uses fire-and-forget semantics — log methods are synchronous
31
+ * and push events into an internal async pipeline. Events flow through
32
+ * enrichers, filters, and level overrides before being dispatched to sinks.
33
+ *
34
+ * @param config - logger configuration including sinks, enrichers, and filters
35
+ * @returns a configured `Logger` instance that implements `AsyncDisposable`
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * const logger = createLogger({
40
+ * minimumLevel: 'information',
41
+ * sinks: [consoleSink({ theme: 'dark' })],
42
+ * enrichers: [hostnameEnricher()],
43
+ * });
44
+ *
45
+ * logger.info('Server started on port {Port}', { Port: 3000 });
46
+ * ```
47
+ *
48
+ * @see {@link Logger} for the full Logger API
49
+ * @see {@link LoggerConfig} for configuration options
50
+ */
51
+ export declare function createLogger(config: LoggerConfig): Logger;
package/dist/di.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ import type { Logger } from './Logger.js';
2
+ /**
3
+ * DI service key for the `Logger` instance.
4
+ *
5
+ * Uses a symbol-based key for use with `@cleverbrush/di` `ServiceCollection`.
6
+ * In the absence of `@cleverbrush/di`, this serves as a plain token.
7
+ */
8
+ export declare const ILogger: {
9
+ __brand: "ILogger";
10
+ };
11
+ /**
12
+ * Configures logging services in the DI container.
13
+ *
14
+ * Registers the root logger as a singleton and optionally sets up
15
+ * scoped loggers that auto-enrich with request context.
16
+ *
17
+ * @param services - the `ServiceCollection` to register with
18
+ * @param logger - the root logger instance
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * const server = new ServerBuilder()
23
+ * .services((svc) => {
24
+ * configureLogging(svc, logger);
25
+ * })
26
+ * .build();
27
+ * ```
28
+ */
29
+ export declare function configureLogging(services: any, logger: Logger): void;
@@ -0,0 +1,8 @@
1
+ import type { Enricher } from '../Enricher.js';
2
+ /**
3
+ * Enriches log events with the application name.
4
+ *
5
+ * @param application - the application name (e.g. `'order-service'`)
6
+ * @returns an enricher that adds `{ Application: '...' }`
7
+ */
8
+ export declare function applicationEnricher(application: string): Enricher;
@@ -0,0 +1,10 @@
1
+ import type { Enricher } from '../Enricher.js';
2
+ /**
3
+ * Enriches log events with the caller's source file and line number.
4
+ *
5
+ * **Warning:** This enricher uses `Error.captureStackTrace` internally,
6
+ * which is expensive. Use only when needed for debugging.
7
+ *
8
+ * @returns an enricher that adds `{ SourceFile: '...', SourceLine: number }`
9
+ */
10
+ export declare function callerEnricher(): Enricher;
@@ -0,0 +1,8 @@
1
+ import type { Enricher } from '../Enricher.js';
2
+ /**
3
+ * Enriches log events with the correlation ID from `AsyncLocalStorage`.
4
+ * Zero-cost when no context is active — simply returns the event unchanged.
5
+ *
6
+ * @returns an enricher that adds `{ CorrelationId: '...' }` if available
7
+ */
8
+ export declare function correlationIdEnricher(): Enricher;
@@ -0,0 +1,8 @@
1
+ import type { Enricher } from '../Enricher.js';
2
+ /**
3
+ * Enriches log events with the deployment environment name.
4
+ *
5
+ * @param environment - the environment name (e.g. `'production'`, `'staging'`)
6
+ * @returns an enricher that adds `{ Environment: '...' }`
7
+ */
8
+ export declare function environmentEnricher(environment: string): Enricher;
@@ -0,0 +1,8 @@
1
+ import type { Enricher } from '../Enricher.js';
2
+ /**
3
+ * Enriches log events with the machine hostname.
4
+ * The hostname is cached on first call.
5
+ *
6
+ * @returns an enricher that adds `{ Hostname: '...' }`
7
+ */
8
+ export declare function hostnameEnricher(): Enricher;
@@ -0,0 +1,6 @@
1
+ export { applicationEnricher } from './application.js';
2
+ export { callerEnricher } from './caller.js';
3
+ export { correlationIdEnricher } from './correlationId.js';
4
+ export { environmentEnricher } from './environment.js';
5
+ export { hostnameEnricher } from './hostname.js';
6
+ export { processIdEnricher } from './processId.js';
@@ -0,0 +1,8 @@
1
+ import type { Enricher } from '../Enricher.js';
2
+ /**
3
+ * Enriches log events with the current process ID.
4
+ * The PID is cached on first call.
5
+ *
6
+ * @returns an enricher that adds `{ ProcessId: number }`
7
+ */
8
+ export declare function processIdEnricher(): Enricher;
@@ -0,0 +1,33 @@
1
+ import type { LogEvent } from '../LogEvent.js';
2
+ import { type SerializationOptions } from '../serialization.js';
3
+ /**
4
+ * Formats a single `LogEvent` as a CLEF JSON string.
5
+ *
6
+ * CLEF (Compact Log Event Format) is the standard wire format for Seq
7
+ * and compatible with the Serilog ecosystem.
8
+ *
9
+ * @param event - the log event to format
10
+ * @param options - optional serialization limits
11
+ * @returns a single-line JSON string in CLEF format
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const line = formatClef(event);
16
+ * // '{"@t":"2026-04-20T14:30:00.123Z","@mt":"User {UserId} signed in","UserId":"usr_abc"}'
17
+ * ```
18
+ */
19
+ export declare function formatClef(event: LogEvent, options?: SerializationOptions): string;
20
+ /**
21
+ * Formats a batch of log events as newline-delimited CLEF.
22
+ *
23
+ * @param events - array of log events
24
+ * @param options - optional serialization limits
25
+ * @returns newline-delimited CLEF string
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * const payload = formatClefBatch(events);
30
+ * // Suitable for POST to Seq /ingest/clef
31
+ * ```
32
+ */
33
+ export declare function formatClefBatch(events: LogEvent[], options?: SerializationOptions): string;
@@ -0,0 +1,29 @@
1
+ export { extractCorrelationId, generateCorrelationId } from './correlation.js';
2
+ export { createLogger, type LoggerConfig } from './createLogger.js';
3
+ export { configureLogging, ILogger } from './di.js';
4
+ export type { Enricher } from './Enricher.js';
5
+ export { applicationEnricher } from './enrichers/application.js';
6
+ export { callerEnricher } from './enrichers/caller.js';
7
+ export { correlationIdEnricher } from './enrichers/correlationId.js';
8
+ export { environmentEnricher } from './enrichers/environment.js';
9
+ export { hostnameEnricher } from './enrichers/hostname.js';
10
+ export { processIdEnricher } from './enrichers/processId.js';
11
+ export type { LogFilter } from './Filter.js';
12
+ export { formatClef, formatClefBatch } from './formatters/ClefFormatter.js';
13
+ export { LogContext, type LogContextStore } from './LogContext.js';
14
+ export type { LogEvent } from './LogEvent.js';
15
+ export { Logger, type TypedTemplate } from './Logger.js';
16
+ export { LogLevel, type LogLevelName, levelToShortString, levelToString, parseLogLevel } from './LogLevel.js';
17
+ export { captureProperties, computeEventId, createLogEvent, parseTemplate, renderTemplate } from './MessageTemplate.js';
18
+ export { type CorrelationIdMiddlewareOptions, correlationIdMiddleware } from './middleware/correlationId.js';
19
+ export { type RequestLoggingOptions, requestLoggingMiddleware } from './middleware/requestLogging.js';
20
+ export { SelfLog } from './SelfLog.js';
21
+ export type { LogSink } from './Sink.js';
22
+ export { type SamplingRates, samplingFilter } from './samplingFilter.js';
23
+ export { type SerializationOptions, safeSerialize } from './serialization.js';
24
+ export { BatchingSink, type BatchingSinkOptions } from './sinks/BatchingSink.js';
25
+ export { type ConsoleSinkOptions, consoleSink } from './sinks/ConsoleSink.js';
26
+ export { type CreateSinkOptions, createSink } from './sinks/createSink.js';
27
+ export { type FileSinkOptions, fileSink, type RotationOptions } from './sinks/FileSink.js';
28
+ export { type SeqSinkOptions, seqSink } from './sinks/SeqSink.js';
29
+ export { useLogging } from './useLogging.js';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ import{a as l,b as g,c as Q,d as P,e as S,f as L,g as I}from"./chunk-EU6TDBKQ.js";import{randomUUID as K}from"crypto";function R(){let t=Date.now(),e=new Uint8Array(16),n=K().replace(/-/g,"");for(let o=0;o<16;o++)e[o]=parseInt(n.slice(o*2,o*2+2),16);e[0]=t/2**40&255,e[1]=t/2**32&255,e[2]=t/2**24&255,e[3]=t/2**16&255,e[4]=t/2**8&255,e[5]=t&255,e[6]=e[6]&15|112,e[8]=e[8]&63|128;let r=Array.from(e).map(o=>o.toString(16).padStart(2,"0")).join("");return[r.slice(0,8),r.slice(8,12),r.slice(12,16),r.slice(16,20),r.slice(20,32)].join("-")}function j(t){let e=O(t,"x-correlation-id");if(e)return e;let n=O(t,"x-request-id");if(n)return n;let r=O(t,"traceparent");if(r){let o=r.split("-");if(o.length>=2&&o[1])return o[1]}return R()}function O(t,e){let n=t[e]??t[e.toLowerCase()];return Array.isArray(n)?n[0]:n}var N=new Map,z=new Map;function H(t){let e=N.get(t);if(e)return e;let n=[],r=0,o=t.length;for(;r<o;){let s=t.indexOf("{",r);if(s===-1){n.push({type:"text",value:t.slice(r)});break}if(s+1<o&&t[s+1]==="{"){n.push({type:"text",value:t.slice(r,s+1)}),r=s+2;continue}s>r&&n.push({type:"text",value:t.slice(r,s)});let i=t.indexOf("}",s);if(i===-1){n.push({type:"text",value:t.slice(s)});break}let a=t.slice(s+1,i),c=!1;a.startsWith("@")&&(c=!0,a=a.slice(1)),n.push({type:"property",value:a,destructure:c}),r=i+1}return N.set(t,n),n}function W(t,e){let n="";for(let r of t)if(r.type==="text")n+=r.value;else{let o=e[r.value];o==null?n+=`{${r.destructure?"@":""}${r.value}}`:r.destructure?n+=JSON.stringify(S(o,{maxDepth:5})):typeof o=="object"&&typeof o.toString=="function"&&o.toString!==Object.prototype.toString?n+=o.toString():typeof o=="object"?n+=JSON.stringify(S(o,{maxDepth:5})):n+=String(o)}return n}function B(t,e){let n={};for(let r of t){if(r.type!=="property")continue;let o=e[r.value];r.destructure?n[r.value]=o:typeof o=="object"&&o!==null&&typeof o.toString=="function"&&o.toString!==Object.prototype.toString?n[r.value]=o.toString():n[r.value]=o}for(let r of Object.keys(e))r in n||(n[r]=e[r]);return n}function A(t){let e=z.get(t);if(e)return e;let n=2166136261;for(let o=0;o<t.length;o++)n^=t.charCodeAt(o),n=n*16777619>>>0;let r=n.toString(16).padStart(8,"0");return z.set(t,r),r}function M(t,e,n,r){let o=H(e),s=B(o,n),i=W(o,n),a=A(e);return{timestamp:new Date,level:t,messageTemplate:e,renderedMessage:i,properties:s,exception:r,eventId:a}}var b=class t{#t;#n;#r;constructor(e,n){this.#t=e,this.#n=n??{}}isEnabled(e){let n=this.#n.SourceContext;return this.#t.isEnabled(e,n)}forContext(e,n){let r=typeof e=="string"?{[e]:n}:e;return new t(this.#t,{...this.#n,...r})}setMinimumLevel(e){this.#t.minimumLevel=typeof e=="string"?g(e):e}watchLevel(e,n=3e4){this.#r&&clearInterval(this.#r),this.#r=setInterval(()=>{let r=process.env[e];if(r)try{this.setMinimumLevel(r.toLowerCase())}catch{}},n),this.#r.unref&&this.#r.unref()}trace(e,n){this.#e(l.Trace,void 0,e,n)}debug(e,n){this.#e(l.Debug,void 0,e,n)}info(e,n){this.#e(l.Information,void 0,e,n)}warn(e,n){this.#e(l.Warning,void 0,e,n)}error(e,n,r){e instanceof Error?this.#e(l.Error,e,n,r):this.#e(l.Error,void 0,e,n)}fatal(e,n,r){e instanceof Error?this.#e(l.Fatal,e,n,r):this.#e(l.Fatal,void 0,e,n)}async flush(){return this.#t.flush()}async dispose(){return this.#r&&clearInterval(this.#r),this.#t.dispose()}async[Symbol.asyncDispose](){return this.dispose()}#e(e,n,r,o){if(!this.isEnabled(e))return;let s,i=o??{};typeof r=="string"?s=r:s=r.template??r.serialize(i);let a={...this.#n,...i},c=M(e,s,a,n);this.#t.push(c)}};var C=class{#t;#n;#r;#e;#i;#l;#p;#o=[];#s=!1;#a=!1;constructor(e){if(this.#t=e.sinks,this.#n=e.enrichers??[],this.#r=e.filters??[],this.#i=e.minimumLevel,this.#l=e.maxQueueSize??1e4,this.#p=e.dropPolicy??"dropOldest",this.#e=new Map,e.levelOverrides)for(let[n,r]of Object.entries(e.levelOverrides))this.#e.set(n,g(r))}get minimumLevel(){return this.#i}set minimumLevel(e){this.#i=e}isEnabled(e,n){if(n&&this.#e.size>0){let r=this.#d(n);if(r!==void 0)return e>=r}return e>=this.#i}push(e){if(!this.#a){if(this.#o.length>=this.#l)switch(this.#p){case"dropOldest":this.#o.shift();break;case"dropNewest":return;case"block":break}this.#o.push(e),this.#c()}}async flush(){await this.#g();let e=[];for(let n of this.#t)n.flush&&e.push(n.flush().catch(r=>{L.write("Sink flush failed",r)}));await Promise.all(e)}async dispose(){if(this.#a)return;this.#a=!0,await this.flush();let e=[];for(let n of this.#t)e.push(Promise.resolve(n[Symbol.asyncDispose]()).catch(r=>{L.write("Sink dispose failed",r instanceof Error?r:void 0)}));await Promise.all(e)}#c(){this.#s||(this.#s=!0,queueMicrotask(()=>{this.#g().catch(e=>{L.write("Pipeline processing error",e)}).finally(()=>{this.#s=!1,this.#o.length>0&&this.#c()})}))}async#g(){if(this.#o.length===0)return;let e=this.#o.splice(0,this.#o.length),n=[];for(let o of e){for(let a of this.#n)try{o=a(o)}catch(c){L.write("Enricher failed",c)}let s=o.properties.SourceContext;if(s&&this.#e.size>0){let a=this.#d(s);if(a!==void 0&&o.level<a)continue}let i=!0;for(let a of this.#r)try{if(!a(o)){i=!1;break}}catch(c){L.write("Filter failed",c)}i&&n.push(o)}if(n.length===0)return;let r=[];for(let o of this.#t)r.push(o.emit(n).catch(s=>{L.write("Sink emit failed",s)}));await Promise.all(r)}#d(e){let n=this.#e.get(e);if(n!==void 0)return n;let r=0,o;for(let[s,i]of this.#e)e.startsWith(s)&&s.length>r&&(r=s.length,o=i);return o}};function J(t){if(!t.sinks||t.sinks.length===0)throw new Error("createLogger requires at least one sink");let e=typeof t.minimumLevel=="string"?g(t.minimumLevel):t.minimumLevel??l.Information,n=new C({minimumLevel:e,levelOverrides:t.levelOverrides,sinks:t.sinks,enrichers:t.enrichers,filters:t.filters,maxQueueSize:t.maxQueueSize,dropPolicy:t.dropPolicy}),r=new b(n);if(t.handleProcessExit){let o=()=>{r.dispose().catch(()=>{})};process.on("SIGTERM",o),process.on("beforeExit",o)}return r}var U=Symbol.for("ILogger");function G(t,e){typeof t.addSingleton=="function"&&t.addSingleton(U,()=>e)}function V(t){return e=>({...e,properties:{...e.properties,Application:t}})}function X(){return t=>{let e={};Error.captureStackTrace(e);let n=e.stack;if(!n)return t;let r=n.split(`
2
+ `);for(let o=1;o<r.length;o++){let s=r[o];if(!s.includes("/log/src/")&&!s.includes("@cleverbrush/log")){let i=s.match(/\(?(.*?):(\d+):\d+\)?$/);if(i)return{...t,properties:{...t.properties,SourceFile:i[1],SourceLine:parseInt(i[2],10)}};break}}return t}}import{AsyncLocalStorage as Y}from"async_hooks";var w=new Y,E={run(t,e){return w.run({logger:t},e)},current(){return w.getStore()?.logger},getStore(){return w.getStore()},enrichWith(t,e){let n=w.getStore();if(!n)throw new Error("LogContext.enrichWith() called outside of LogContext.run()");let r=n.logger.forContext(t);return w.run({...n,logger:r,properties:{...n.properties,...t}},e)},runWithCorrelationId(t,e){let n=w.getStore(),r=n?.logger;if(!r)throw new Error("LogContext.runWithCorrelationId() called outside of LogContext.run()");return w.run({...n,logger:r,correlationId:t},e)}};function Z(){return t=>{let e=E.getStore();return e?.correlationId?{...t,properties:{...t.properties,CorrelationId:e.correlationId}}:t}}function ee(t){return e=>({...e,properties:{...e.properties,Environment:t}})}import te from"os";function re(){let t;return e=>(t===void 0&&(t=te.hostname()),{...e,properties:{...e.properties,Hostname:t}})}function ne(){let t;return e=>(t===void 0&&(t=process.pid),{...e,properties:{...e.properties,ProcessId:t}})}var oe={[l.Trace]:"Verbose",[l.Debug]:"Debug",[l.Information]:void 0,[l.Warning]:"Warning",[l.Error]:"Error",[l.Fatal]:"Fatal"};function T(t,e){let n={"@t":t.timestamp.toISOString(),"@mt":t.messageTemplate};t.renderedMessage!==t.messageTemplate&&(n["@m"]=t.renderedMessage);let r=oe[t.level];if(r!==void 0&&(n["@l"]=r),t.exception&&(n["@x"]=t.exception.stack??t.exception.message),t.eventId&&(n["@i"]=t.eventId),t.properties)for(let[o,s]of Object.entries(t.properties))n[o]=S(s,e);return JSON.stringify(n)}function D(t,e){return t.map(n=>T(n,e)).join(`
3
+ `)}function q(t){let e=t?.responseHeader===!1?!1:t?.responseHeader??"X-Correlation-Id",n=t?.generate??R;return async(r,o)=>{let s=r.headers??{},i=j(s)||n();e!==!1&&(r.setHeader?r.setHeader(e,i):r.response?.setHeader&&r.response.setHeader(e,i)),r.items instanceof Map&&r.items.set("correlationId",i),E.getStore()?await E.runWithCorrelationId(i,()=>o()):await o()}}function $(t,e){let n=new Set((e?.excludePaths??[]).map(i=>typeof i=="string"?i:i.path)),r=e?.getLevel,o=e?.enrichRequest,s=e?.logRequestStart??!1;return async(i,a)=>{let c=i.url?.pathname??i.path??"";if(n.has(c)){await a();return}let y=Date.now(),v=i.method??"GET";s&&t.info("HTTP {Method} {Path} started",{Method:v,Path:c});let u=200;try{await a(),u=i.statusCode??i.response?.statusCode??200}catch(p){throw u=i.statusCode??i.response?.statusCode??500,p}finally{let p=Date.now()-y,d=r?r(u,p):u>=500?"error":u>=400?"warning":"information",h={Method:v,Path:c,StatusCode:u,Elapsed:p};if(i.headers?.["user-agent"]&&(h.UserAgent=i.headers["user-agent"]),o)try{let _=o(i);Object.assign(h,_)}catch{}let x=g(d),k=t.forContext("SourceContext","RequestLogging");x>=l.Error?k.error("HTTP {Method} {Path} responded {StatusCode} in {Elapsed}ms",h):x>=l.Warning?k.warn("HTTP {Method} {Path} responded {StatusCode} in {Elapsed}ms",h):k.info("HTTP {Method} {Path} responded {StatusCode} in {Elapsed}ms",h)}}}function ie(t){let e=new Map;for(let[n,r]of Object.entries(t))e.set(g(n),Math.max(0,Math.min(1,r)));return n=>{let r=e.get(n.level);return r===void 0?!0:Math.random()<r}}var se={[l.Trace]:"\x1B[90m",[l.Debug]:"\x1B[36m",[l.Information]:"\x1B[32m",[l.Warning]:"\x1B[33m",[l.Error]:"\x1B[31m",[l.Fatal]:"\x1B[35m"},ae={[l.Trace]:"\x1B[37m",[l.Debug]:"\x1B[34m",[l.Information]:"\x1B[32m",[l.Warning]:"\x1B[33m",[l.Error]:"\x1B[31m",[l.Fatal]:"\x1B[35m"},le="\x1B[0m";function pe(t){let e=t?.mode??"pretty",n=t?.theme??"dark",r=t?.minimumLevel?g(t.minimumLevel):void 0,o=n==="dark"?se:n==="light"?ae:void 0;return{async emit(s){for(let i of s){if(r!==void 0&&i.level<r)continue;let a=i.level>=l.Warning?process.stderr:process.stdout;if(e==="json")a.write(T(i)+`
4
+ `);else{let c=ce(i.timestamp),y=P(i.level),p=`${o?.[i.level]??""}[${c} ${y}]${o?le:""} ${i.renderedMessage}
5
+ `,d=i.properties;if(d&&Object.keys(d).length>0)for(let[h,x]of Object.entries(d)){let k=S(x,{maxDepth:3});p+=` ${h}: ${JSON.stringify(k)}
6
+ `}i.exception&&(p+=` ${i.exception.stack??i.exception.message}
7
+ `),a.write(p)}}},async[Symbol.asyncDispose](){}}}function ce(t){let e=t.getFullYear(),n=String(t.getMonth()+1).padStart(2,"0"),r=String(t.getDate()).padStart(2,"0"),o=String(t.getHours()).padStart(2,"0"),s=String(t.getMinutes()).padStart(2,"0"),i=String(t.getSeconds()).padStart(2,"0"),a=String(t.getMilliseconds()).padStart(3,"0");return`${e}-${n}-${r} ${o}:${s}:${i}.${a}`}function ge(t){let e=t.minimumLevel?g(t.minimumLevel):void 0;return{async emit(n){let r=e!==void 0?n.filter(o=>o.level>=e):n;r.length>0&&await t.emit(r)},flush:t.flush,async[Symbol.asyncDispose](){t.dispose&&await t.dispose()}}}import*as f from"fs";import*as m from"path";function de(t){let e=m.resolve(t.path),n=t.minimumLevel?g(t.minimumLevel):void 0,r=t.rotation,o=r?.retainCount??10,s=0,i=F(new Date,r?.interval),a;function c(){let p=m.dirname(e);f.existsSync(p)||f.mkdirSync(p,{recursive:!0})}function y(){if(!a||a.closed){c(),a=f.createWriteStream(e,{flags:"a"});try{s=f.statSync(e).size}catch{s=0}}return a}function v(){if(!r)return!1;let d=F(new Date,r.interval);return r.strategy==="time"?d!==i:r.strategy==="size"?r.maxBytes!==void 0&&s>=r.maxBytes:r.strategy==="hybrid"?d!==i||r.maxBytes!==void 0&&s>=r.maxBytes:!1}function u(){a&&(a.end(),a=void 0);let p=new Date().toISOString().replace(/[:.]/g,"-"),d=m.extname(e),x=`${e.slice(0,-d.length||void 0)}.${p}${d}`;try{f.existsSync(e)&&f.renameSync(e,x)}catch(k){L.write("File rotation failed",k)}s=0,i=F(new Date,r?.interval),fe(e,o)}return{async emit(p){for(let d of p){if(n!==void 0&&d.level<n)continue;v()&&u();let h=T(d)+`
8
+ `;y().write(h),s+=Buffer.byteLength(h)}},async flush(){a&&await new Promise((p,d)=>{a.once("drain",p),a.once("error",d),a.write("")&&p()})},async[Symbol.asyncDispose](){a&&(await new Promise(p=>{a.end(()=>p())}),a=void 0)}}}function F(t,e){let n=t.getFullYear(),r=String(t.getMonth()+1).padStart(2,"0"),o=String(t.getDate()).padStart(2,"0");if(e==="hourly"){let s=String(t.getHours()).padStart(2,"0");return`${n}-${r}-${o}-${s}`}return`${n}-${r}-${o}`}function fe(t,e){try{let n=m.dirname(t),r=m.basename(t),o=m.extname(r),s=r.slice(0,-o.length||void 0),i=f.readdirSync(n).filter(a=>a.startsWith(s+".")&&a!==r).sort();if(i.length>e){let a=i.slice(0,i.length-e);for(let c of a)f.unlinkSync(m.join(n,c))}}catch(n){L.write("Failed to prune old log files",n)}}function me(t){let n=`${t.serverUrl.replace(/\/$/,"")}/ingest/clef`,r,o=new I({batchSize:t.batchSize??100,flushInterval:t.flushInterval??2e3,maxRetries:t.maxRetries??5,retryDelay:t.retryDelay??1e3,emit:async s=>{let i=r!==void 0?s.filter(u=>u.level>=r):s;if(i.length===0)return;let a=D(i),c={"Content-Type":"application/vnd.serilog.clef"};t.apiKey&&(c["X-Seq-ApiKey"]=t.apiKey);let y=await fetch(n,{method:"POST",headers:c,body:a});if(!y.ok)throw new Error(`Seq ingestion failed: ${y.status} ${y.statusText}`);let v=y.headers.get("X-Seq-MinimumLevelAccepted");if(v)try{r=g(v.toLowerCase())}catch{}else r=void 0}});return{async emit(s){await o.emit(s)},async flush(){await o.flush()},async[Symbol.asyncDispose](){await o[Symbol.asyncDispose]()}}}function ue(t,e){return[q({responseHeader:e?.correlationResponseHeader}),$(t,e)]}export{I as BatchingSink,U as ILogger,E as LogContext,l as LogLevel,b as Logger,L as SelfLog,V as applicationEnricher,X as callerEnricher,B as captureProperties,A as computeEventId,G as configureLogging,pe as consoleSink,Z as correlationIdEnricher,q as correlationIdMiddleware,M as createLogEvent,J as createLogger,ge as createSink,ee as environmentEnricher,j as extractCorrelationId,de as fileSink,T as formatClef,D as formatClefBatch,R as generateCorrelationId,re as hostnameEnricher,P as levelToShortString,Q as levelToString,g as parseLogLevel,H as parseTemplate,ne as processIdEnricher,W as renderTemplate,$ as requestLoggingMiddleware,S as safeSerialize,ie as samplingFilter,me as seqSink,ue as useLogging};
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/correlation.ts","../src/MessageTemplate.ts","../src/Logger.ts","../src/LoggerPipeline.ts","../src/createLogger.ts","../src/di.ts","../src/enrichers/application.ts","../src/enrichers/caller.ts","../src/LogContext.ts","../src/enrichers/correlationId.ts","../src/enrichers/environment.ts","../src/enrichers/hostname.ts","../src/enrichers/processId.ts","../src/formatters/ClefFormatter.ts","../src/middleware/correlationId.ts","../src/middleware/requestLogging.ts","../src/samplingFilter.ts","../src/sinks/ConsoleSink.ts","../src/sinks/createSink.ts","../src/sinks/FileSink.ts","../src/sinks/SeqSink.ts","../src/useLogging.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\n/**\n * Generates a new correlation ID using UUID v7 (time-sortable).\n *\n * Falls back to UUID v4 if the runtime doesn't support v7.\n *\n * @returns a new UUID string suitable for correlation\n */\nexport function generateCorrelationId(): string {\n // UUID v7: timestamp-based, K-sortable\n // Node.js 19+ supports randomUUID(), use it as a base\n // Generate a time-based UUID v7\n const now = Date.now();\n const bytes = new Uint8Array(16);\n\n // Fill with random bytes\n const random = randomUUID().replace(/-/g, '');\n for (let i = 0; i < 16; i++) {\n bytes[i] = parseInt(random.slice(i * 2, i * 2 + 2), 16);\n }\n\n // Set timestamp in first 48 bits\n bytes[0] = (now / 2 ** 40) & 0xff;\n bytes[1] = (now / 2 ** 32) & 0xff;\n bytes[2] = (now / 2 ** 24) & 0xff;\n bytes[3] = (now / 2 ** 16) & 0xff;\n bytes[4] = (now / 2 ** 8) & 0xff;\n bytes[5] = now & 0xff;\n\n // Set version 7\n bytes[6] = (bytes[6] & 0x0f) | 0x70;\n // Set variant\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n\n const hex = Array.from(bytes)\n .map(b => b.toString(16).padStart(2, '0'))\n .join('');\n\n return [\n hex.slice(0, 8),\n hex.slice(8, 12),\n hex.slice(12, 16),\n hex.slice(16, 20),\n hex.slice(20, 32)\n ].join('-');\n}\n\n/**\n * Extracts a correlation ID from incoming HTTP request headers.\n *\n * Checks headers in priority order:\n * 1. `X-Correlation-Id`\n * 2. `X-Request-Id`\n * 3. `traceparent` (W3C Trace Context — extracts the trace-id segment)\n *\n * If no header is found, generates a new correlation ID.\n *\n * @param headers - request headers object\n * @returns a correlation ID string\n */\nexport function extractCorrelationId(\n headers: Record<string, string | string[] | undefined>\n): string {\n const correlationId = getHeader(headers, 'x-correlation-id');\n if (correlationId) return correlationId;\n\n const requestId = getHeader(headers, 'x-request-id');\n if (requestId) return requestId;\n\n const traceparent = getHeader(headers, 'traceparent');\n if (traceparent) {\n // W3C traceparent: version-trace_id-parent_id-trace_flags\n const parts = traceparent.split('-');\n if (parts.length >= 2 && parts[1]) {\n return parts[1];\n }\n }\n\n return generateCorrelationId();\n}\n\nfunction getHeader(\n headers: Record<string, string | string[] | undefined>,\n name: string\n): string | undefined {\n const value = headers[name] ?? headers[name.toLowerCase()];\n if (Array.isArray(value)) return value[0];\n return value;\n}\n","import type { LogEvent } from './LogEvent.js';\nimport type { LogLevel } from './LogLevel.js';\nimport { safeSerialize } from './serialization.js';\n\n// ---------------------------------------------------------------------------\n// Template parsing\n// ---------------------------------------------------------------------------\n\ninterface TemplateToken {\n type: 'text' | 'property';\n value: string;\n destructure?: boolean;\n}\n\nconst templateCache = new Map<string, TemplateToken[]>();\nconst eventIdCache = new Map<string, string>();\n\n/**\n * Parses a Serilog-style message template into tokens.\n *\n * Supports `{Property}` for scalar capture and `{@Property}` for\n * destructuring (full object structure preserved in properties).\n *\n * @param template - message template string with `{Property}` holes\n * @returns array of parsed tokens\n */\nexport function parseTemplate(template: string): TemplateToken[] {\n const cached = templateCache.get(template);\n if (cached) return cached;\n\n const tokens: TemplateToken[] = [];\n let i = 0;\n const len = template.length;\n\n while (i < len) {\n const braceStart = template.indexOf('{', i);\n if (braceStart === -1) {\n tokens.push({ type: 'text', value: template.slice(i) });\n break;\n }\n\n // Escaped brace: {{\n if (braceStart + 1 < len && template[braceStart + 1] === '{') {\n tokens.push({\n type: 'text',\n value: template.slice(i, braceStart + 1)\n });\n i = braceStart + 2;\n continue;\n }\n\n if (braceStart > i) {\n tokens.push({\n type: 'text',\n value: template.slice(i, braceStart)\n });\n }\n\n const braceEnd = template.indexOf('}', braceStart);\n if (braceEnd === -1) {\n tokens.push({\n type: 'text',\n value: template.slice(braceStart)\n });\n break;\n }\n\n let propName = template.slice(braceStart + 1, braceEnd);\n let destructure = false;\n if (propName.startsWith('@')) {\n destructure = true;\n propName = propName.slice(1);\n }\n\n tokens.push({ type: 'property', value: propName, destructure });\n i = braceEnd + 1;\n }\n\n templateCache.set(template, tokens);\n return tokens;\n}\n\n/**\n * Renders a parsed template into a human-readable string.\n *\n * For non-destructured properties, calls `toString()` if available on\n * the value. For destructured properties (`{@Prop}`), uses JSON.stringify.\n *\n * @param tokens - parsed template tokens\n * @param properties - property values to interpolate\n * @returns the rendered message string\n */\nexport function renderTemplate(\n tokens: TemplateToken[],\n properties: Record<string, unknown>\n): string {\n let result = '';\n for (const token of tokens) {\n if (token.type === 'text') {\n result += token.value;\n } else {\n const value = properties[token.value];\n if (value === undefined || value === null) {\n result += `{${token.destructure ? '@' : ''}${token.value}}`;\n } else if (token.destructure) {\n result += JSON.stringify(safeSerialize(value, { maxDepth: 5 }));\n } else if (\n typeof value === 'object' &&\n typeof (value as any).toString === 'function' &&\n (value as any).toString !== Object.prototype.toString\n ) {\n result += (value as any).toString();\n } else if (typeof value === 'object') {\n result += JSON.stringify(safeSerialize(value, { maxDepth: 5 }));\n } else {\n result += String(value);\n }\n }\n }\n return result;\n}\n\n/**\n * Captures structured properties from the template, applying destructure\n * semantics: `{@Prop}` keeps the full object, `{Prop}` calls `toString()`\n * on objects that have a custom `toString`.\n *\n * @param tokens - parsed template tokens\n * @param properties - raw property values\n * @returns property bag with appropriate serialization applied\n */\nexport function captureProperties(\n tokens: TemplateToken[],\n properties: Record<string, unknown>\n): Record<string, unknown> {\n const result: Record<string, unknown> = {};\n for (const token of tokens) {\n if (token.type !== 'property') continue;\n const value = properties[token.value];\n if (token.destructure) {\n result[token.value] = value;\n } else if (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as any).toString === 'function' &&\n (value as any).toString !== Object.prototype.toString\n ) {\n result[token.value] = (value as any).toString();\n } else {\n result[token.value] = value;\n }\n }\n // Include any extra properties not referenced in the template\n for (const key of Object.keys(properties)) {\n if (!(key in result)) {\n result[key] = properties[key];\n }\n }\n return result;\n}\n\n/**\n * Generates a deterministic hex event ID from a message template string.\n * Uses a simple FNV-1a hash for speed — no cryptographic requirements.\n *\n * @param template - the raw message template\n * @returns 8-character hex hash\n */\nexport function computeEventId(template: string): string {\n const cached = eventIdCache.get(template);\n if (cached) return cached;\n\n let hash = 0x811c9dc5;\n for (let i = 0; i < template.length; i++) {\n hash ^= template.charCodeAt(i);\n hash = (hash * 0x01000193) >>> 0;\n }\n const id = hash.toString(16).padStart(8, '0');\n eventIdCache.set(template, id);\n return id;\n}\n\n/**\n * Creates a complete `LogEvent` from a message template, properties,\n * and metadata.\n *\n * @param level - severity level\n * @param template - message template string with `{Property}` holes\n * @param properties - structured property values\n * @param exception - optional associated error\n * @returns a fully populated `LogEvent`\n */\nexport function createLogEvent(\n level: LogLevel,\n template: string,\n properties: Record<string, unknown>,\n exception?: Error\n): LogEvent {\n const tokens = parseTemplate(template);\n const captured = captureProperties(tokens, properties);\n const renderedMessage = renderTemplate(tokens, properties);\n const eventId = computeEventId(template);\n\n return {\n timestamp: new Date(),\n level,\n messageTemplate: template,\n renderedMessage,\n properties: captured,\n exception,\n eventId\n };\n}\n","import type { LoggerPipeline } from './LoggerPipeline.js';\nimport { LogLevel, type LogLevelName, parseLogLevel } from './LogLevel.js';\nimport { createLogEvent } from './MessageTemplate.js';\n\n/**\n * A typed message template created via `ParseStringSchemaBuilder`.\n *\n * When passed to a `Logger` log method, the logger uses `template` as the\n * `messageTemplate` (so events with the same shape are grouped in Seq /\n * ClickStack / ClickHouse) and derives the rendered message by interpolating\n * `template` with the supplied parameters.\n *\n * @example\n * ```ts\n * import { s } from '@cleverbrush/schema';\n *\n * // Build a reusable typed template once\n * const tmpl = s.parseString('Todo #{TodoId} \"{Title}\" created by {UserId}');\n *\n * // All log sites share the same messageTemplate → groupable in the UI\n * logger.info(tmpl, { TodoId: 1, Title: 'Buy milk', UserId: 'u-42' });\n * ```\n */\nexport interface TypedTemplate<T extends Record<string, unknown>> {\n serialize(params: T): string;\n /** The raw `{Property}` pattern string, used as `messageTemplate`. */\n readonly template?: string;\n}\n\n/**\n * Structured logger with per-level methods, child context support,\n * and `AsyncDisposable` for graceful shutdown.\n *\n * Log methods are synchronous and fire-and-forget — they push events\n * into an internal async microtask pipeline.\n *\n * Accepts both plain string templates and typed {@link TypedTemplate}\n * objects (produced by `ParseStringSchemaBuilder` from `@cleverbrush/schema`).\n * Typed templates carry a `template` property with the raw `{Property}` pattern,\n * which the logger uses as `messageTemplate` so all events of the same shape\n * are grouped correctly in Seq, ClickStack, ClickHouse, etc.\n *\n * @example\n * ```ts\n * logger.info('Server started on port {Port}', { Port: 3000 });\n *\n * const child = logger.forContext('SourceContext', 'OrderService');\n * child.info('Processing order {OrderId}', { OrderId: 42 });\n *\n * // Typed template — structured grouping\n * import { s } from '@cleverbrush/schema';\n * const tmpl = s.parseString('Order #{OrderId} placed by {UserId}');\n * child.info(tmpl, { OrderId: 1, UserId: 'u-99' });\n * ```\n */\nexport class Logger implements AsyncDisposable {\n readonly #pipeline: LoggerPipeline;\n readonly #contextProperties: Record<string, unknown>;\n #levelWatchInterval: ReturnType<typeof setInterval> | undefined;\n\n constructor(\n pipeline: LoggerPipeline,\n contextProperties?: Record<string, unknown>\n ) {\n this.#pipeline = pipeline;\n this.#contextProperties = contextProperties ?? {};\n }\n\n /**\n * Checks whether the given level is enabled for this logger.\n *\n * @param level - the log level to check\n * @returns `true` if events at this level would be processed\n */\n isEnabled(level: LogLevel): boolean {\n const sourceContext = this.#contextProperties.SourceContext as\n | string\n | undefined;\n return this.#pipeline.isEnabled(level, sourceContext);\n }\n\n /**\n * Creates a child logger with additional context properties.\n *\n * @param key - property name, or an object of key-value pairs\n * @param value - property value (when key is a string)\n * @returns a new `Logger` with merged context properties\n */\n forContext(key: string | Record<string, unknown>, value?: unknown): Logger {\n const extra = typeof key === 'string' ? { [key]: value } : key;\n return new Logger(this.#pipeline, {\n ...this.#contextProperties,\n ...extra\n });\n }\n\n /**\n * Changes the minimum log level at runtime.\n *\n * @param level - new minimum level (name string or numeric value)\n */\n setMinimumLevel(level: LogLevelName | LogLevel): void {\n this.#pipeline.minimumLevel =\n typeof level === 'string' ? parseLogLevel(level) : level;\n }\n\n /**\n * Polls an environment variable for log level changes.\n *\n * @param envVar - environment variable name to watch\n * @param intervalMs - polling interval in milliseconds (default: 30000)\n */\n watchLevel(envVar: string, intervalMs = 30_000): void {\n if (this.#levelWatchInterval) {\n clearInterval(this.#levelWatchInterval);\n }\n this.#levelWatchInterval = setInterval(() => {\n const val = process.env[envVar];\n if (val) {\n try {\n this.setMinimumLevel(val.toLowerCase() as LogLevelName);\n } catch {\n // ignore invalid values\n }\n }\n }, intervalMs);\n // Don't keep the process alive just for log level watching\n if (this.#levelWatchInterval.unref) {\n this.#levelWatchInterval.unref();\n }\n }\n\n // -----------------------------------------------------------------\n // Level methods\n // -----------------------------------------------------------------\n\n /** Log a trace-level message. */\n trace(template: string, properties?: Record<string, unknown>): void;\n trace<T extends Record<string, unknown>>(\n template: TypedTemplate<T>,\n properties: T\n ): void;\n trace(\n template: string | TypedTemplate<any>,\n properties?: Record<string, unknown>\n ): void {\n this.#write(LogLevel.Trace, undefined, template, properties);\n }\n\n /** Log a debug-level message. */\n debug(template: string, properties?: Record<string, unknown>): void;\n debug<T extends Record<string, unknown>>(\n template: TypedTemplate<T>,\n properties: T\n ): void;\n debug(\n template: string | TypedTemplate<any>,\n properties?: Record<string, unknown>\n ): void {\n this.#write(LogLevel.Debug, undefined, template, properties);\n }\n\n /** Log an information-level message. */\n info(template: string, properties?: Record<string, unknown>): void;\n info<T extends Record<string, unknown>>(\n template: TypedTemplate<T>,\n properties: T\n ): void;\n info(\n template: string | TypedTemplate<any>,\n properties?: Record<string, unknown>\n ): void {\n this.#write(LogLevel.Information, undefined, template, properties);\n }\n\n /** Log a warning-level message. */\n warn(template: string, properties?: Record<string, unknown>): void;\n warn<T extends Record<string, unknown>>(\n template: TypedTemplate<T>,\n properties: T\n ): void;\n warn(\n template: string | TypedTemplate<any>,\n properties?: Record<string, unknown>\n ): void {\n this.#write(LogLevel.Warning, undefined, template, properties);\n }\n\n /** Log an error-level message with an optional exception. */\n error(template: string, properties?: Record<string, unknown>): void;\n error(\n error: Error,\n template: string,\n properties?: Record<string, unknown>\n ): void;\n error<T extends Record<string, unknown>>(\n template: TypedTemplate<T>,\n properties: T\n ): void;\n error<T extends Record<string, unknown>>(\n error: Error,\n template: TypedTemplate<T>,\n properties: T\n ): void;\n error(\n errorOrTemplate: Error | string | TypedTemplate<any>,\n templateOrProps?: string | TypedTemplate<any> | Record<string, unknown>,\n properties?: Record<string, unknown>\n ): void {\n if (errorOrTemplate instanceof Error) {\n this.#write(\n LogLevel.Error,\n errorOrTemplate,\n templateOrProps as string | TypedTemplate<any>,\n properties\n );\n } else {\n this.#write(\n LogLevel.Error,\n undefined,\n errorOrTemplate,\n templateOrProps as Record<string, unknown>\n );\n }\n }\n\n /** Log a fatal-level message with an optional exception. */\n fatal(template: string, properties?: Record<string, unknown>): void;\n fatal(\n error: Error,\n template: string,\n properties?: Record<string, unknown>\n ): void;\n fatal<T extends Record<string, unknown>>(\n template: TypedTemplate<T>,\n properties: T\n ): void;\n fatal<T extends Record<string, unknown>>(\n error: Error,\n template: TypedTemplate<T>,\n properties: T\n ): void;\n fatal(\n errorOrTemplate: Error | string | TypedTemplate<any>,\n templateOrProps?: string | TypedTemplate<any> | Record<string, unknown>,\n properties?: Record<string, unknown>\n ): void {\n if (errorOrTemplate instanceof Error) {\n this.#write(\n LogLevel.Fatal,\n errorOrTemplate,\n templateOrProps as string | TypedTemplate<any>,\n properties\n );\n } else {\n this.#write(\n LogLevel.Fatal,\n undefined,\n errorOrTemplate,\n templateOrProps as Record<string, unknown>\n );\n }\n }\n\n // -----------------------------------------------------------------\n // Flush & Dispose\n // -----------------------------------------------------------------\n\n /** Flushes all pending events through the pipeline to sinks. */\n async flush(): Promise<void> {\n return this.#pipeline.flush();\n }\n\n /** Flushes all sinks and releases resources. */\n async dispose(): Promise<void> {\n if (this.#levelWatchInterval) {\n clearInterval(this.#levelWatchInterval);\n }\n return this.#pipeline.dispose();\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n return this.dispose();\n }\n\n // -----------------------------------------------------------------\n // Internal\n // -----------------------------------------------------------------\n\n #write(\n level: LogLevel,\n exception: Error | undefined,\n template: string | TypedTemplate<any>,\n properties?: Record<string, unknown>\n ): void {\n if (!this.isEnabled(level)) return;\n\n let templateStr: string;\n const props = properties ?? {};\n\n if (typeof template === 'string') {\n templateStr = template;\n } else {\n // Typed template via ParseStringSchemaBuilder.\n // Use the raw {Property} pattern as messageTemplate so logs with\n // the same shape can be grouped in observability tools; the\n // rendered message is derived by createLogEvent from the pattern.\n templateStr = template.template ?? template.serialize(props);\n }\n\n const mergedProps = {\n ...this.#contextProperties,\n ...props\n };\n\n const event = createLogEvent(\n level,\n typeof template === 'string' ? templateStr : templateStr,\n mergedProps,\n exception\n );\n\n this.#pipeline.push(event);\n }\n}\n","import type { Enricher } from './Enricher.js';\nimport type { LogFilter } from './Filter.js';\nimport type { LogEvent } from './LogEvent.js';\nimport { type LogLevel, type LogLevelName, parseLogLevel } from './LogLevel.js';\nimport { SelfLog } from './SelfLog.js';\nimport type { LogSink } from './Sink.js';\n\n/**\n * Configuration for the logger pipeline.\n */\nexport interface PipelineConfig {\n minimumLevel: LogLevel;\n levelOverrides?: Record<string, LogLevelName>;\n sinks: LogSink[];\n enrichers?: Enricher[];\n filters?: LogFilter[];\n maxQueueSize?: number;\n dropPolicy?: 'dropOldest' | 'dropNewest' | 'block';\n}\n\n/**\n * Internal pipeline that processes log events asynchronously.\n *\n * Events are pushed into a queue and processed via microtask. The\n * pipeline applies enrichers, filters, and level overrides before\n * fanning out to all configured sinks.\n */\nexport class LoggerPipeline {\n readonly #sinks: LogSink[];\n readonly #enrichers: Enricher[];\n readonly #filters: LogFilter[];\n readonly #levelOverrides: Map<string, LogLevel>;\n #minimumLevel: LogLevel;\n readonly #maxQueueSize: number;\n readonly #dropPolicy: 'dropOldest' | 'dropNewest' | 'block';\n readonly #queue: LogEvent[] = [];\n #flushing = false;\n #disposed = false;\n\n constructor(config: PipelineConfig) {\n this.#sinks = config.sinks;\n this.#enrichers = config.enrichers ?? [];\n this.#filters = config.filters ?? [];\n this.#minimumLevel = config.minimumLevel;\n this.#maxQueueSize = config.maxQueueSize ?? 10_000;\n this.#dropPolicy = config.dropPolicy ?? 'dropOldest';\n\n this.#levelOverrides = new Map();\n if (config.levelOverrides) {\n for (const [ns, level] of Object.entries(config.levelOverrides)) {\n this.#levelOverrides.set(ns, parseLogLevel(level));\n }\n }\n }\n\n get minimumLevel(): LogLevel {\n return this.#minimumLevel;\n }\n\n set minimumLevel(level: LogLevel) {\n this.#minimumLevel = level;\n }\n\n /**\n * Checks if the given level would pass the minimum level check\n * for the given source context.\n */\n isEnabled(level: LogLevel, sourceContext?: string): boolean {\n if (sourceContext && this.#levelOverrides.size > 0) {\n const overrideLevel = this.#findOverride(sourceContext);\n if (overrideLevel !== undefined) {\n return level >= overrideLevel;\n }\n }\n return level >= this.#minimumLevel;\n }\n\n /**\n * Enqueues a log event for processing.\n * Fire-and-forget — callers never await.\n */\n push(event: LogEvent): void {\n if (this.#disposed) return;\n\n if (this.#queue.length >= this.#maxQueueSize) {\n switch (this.#dropPolicy) {\n case 'dropOldest':\n this.#queue.shift();\n break;\n case 'dropNewest':\n return;\n case 'block':\n // In block mode, we still accept — real blocking\n // would require async log calls which we avoid.\n break;\n }\n }\n\n this.#queue.push(event);\n this.#scheduleFlush();\n }\n\n /**\n * Forces all queued events through the pipeline and into sinks.\n */\n async flush(): Promise<void> {\n await this.#processQueue();\n const flushPromises: Promise<void>[] = [];\n for (const sink of this.#sinks) {\n if (sink.flush) {\n flushPromises.push(\n sink.flush().catch(err => {\n SelfLog.write('Sink flush failed', err);\n })\n );\n }\n }\n await Promise.all(flushPromises);\n }\n\n /**\n * Flushes remaining events and disposes all sinks.\n */\n async dispose(): Promise<void> {\n if (this.#disposed) return;\n this.#disposed = true;\n\n await this.flush();\n\n const disposePromises: Promise<void>[] = [];\n for (const sink of this.#sinks) {\n disposePromises.push(\n Promise.resolve(sink[Symbol.asyncDispose]()).catch(\n (err: unknown) => {\n SelfLog.write(\n 'Sink dispose failed',\n err instanceof Error ? err : undefined\n );\n }\n )\n );\n }\n await Promise.all(disposePromises);\n }\n\n #scheduleFlush(): void {\n if (this.#flushing) return;\n this.#flushing = true;\n queueMicrotask(() => {\n this.#processQueue()\n .catch(err => {\n SelfLog.write('Pipeline processing error', err);\n })\n .finally(() => {\n this.#flushing = false;\n if (this.#queue.length > 0) {\n this.#scheduleFlush();\n }\n });\n });\n }\n\n async #processQueue(): Promise<void> {\n if (this.#queue.length === 0) return;\n\n const batch = this.#queue.splice(0, this.#queue.length);\n const processed: LogEvent[] = [];\n\n for (let event of batch) {\n // Apply enrichers\n for (const enricher of this.#enrichers) {\n try {\n event = enricher(event);\n } catch (err) {\n SelfLog.write('Enricher failed', err);\n }\n }\n\n // Check level overrides\n const sourceContext = event.properties.SourceContext as\n | string\n | undefined;\n if (sourceContext && this.#levelOverrides.size > 0) {\n const overrideLevel = this.#findOverride(sourceContext);\n if (\n overrideLevel !== undefined &&\n event.level < overrideLevel\n ) {\n continue;\n }\n }\n\n // Apply filters\n let pass = true;\n for (const filter of this.#filters) {\n try {\n if (!filter(event)) {\n pass = false;\n break;\n }\n } catch (err) {\n SelfLog.write('Filter failed', err);\n }\n }\n if (!pass) continue;\n\n processed.push(event);\n }\n\n if (processed.length === 0) return;\n\n // Fan-out to sinks\n const emitPromises: Promise<void>[] = [];\n for (const sink of this.#sinks) {\n emitPromises.push(\n sink.emit(processed).catch(err => {\n SelfLog.write('Sink emit failed', err);\n })\n );\n }\n await Promise.all(emitPromises);\n }\n\n #findOverride(sourceContext: string): LogLevel | undefined {\n // Exact match first\n const exact = this.#levelOverrides.get(sourceContext);\n if (exact !== undefined) return exact;\n\n // Prefix match — longest wins\n let bestLen = 0;\n let bestLevel: LogLevel | undefined;\n for (const [prefix, level] of this.#levelOverrides) {\n if (sourceContext.startsWith(prefix) && prefix.length > bestLen) {\n bestLen = prefix.length;\n bestLevel = level;\n }\n }\n return bestLevel;\n }\n}\n","import type { Enricher } from './Enricher.js';\nimport type { LogFilter } from './Filter.js';\nimport { Logger } from './Logger.js';\nimport { LoggerPipeline } from './LoggerPipeline.js';\nimport { LogLevel, type LogLevelName, parseLogLevel } from './LogLevel.js';\nimport type { LogSink } from './Sink.js';\n\n/**\n * Configuration for creating a structured logger.\n */\nexport interface LoggerConfig {\n /** Minimum log level (name or numeric). @default 'information' */\n minimumLevel?: LogLevelName | LogLevel;\n /** Namespace-level overrides for minimum log level. */\n levelOverrides?: Record<string, LogLevelName>;\n /** Output sinks for log events. */\n sinks: LogSink[];\n /** Enrichers that add properties to every event. */\n enrichers?: Enricher[];\n /** Filters that determine which events pass through. */\n filters?: LogFilter[];\n /** Maximum queued events before dropping. @default 10000 */\n maxQueueSize?: number;\n /** Policy when queue is full. @default 'dropOldest' */\n dropPolicy?: 'dropOldest' | 'dropNewest' | 'block';\n /** Whether to hook `SIGTERM` and `beforeExit` for flush. @default false */\n handleProcessExit?: boolean;\n}\n\n/**\n * Creates a structured logger with the specified configuration.\n *\n * The logger uses fire-and-forget semantics — log methods are synchronous\n * and push events into an internal async pipeline. Events flow through\n * enrichers, filters, and level overrides before being dispatched to sinks.\n *\n * @param config - logger configuration including sinks, enrichers, and filters\n * @returns a configured `Logger` instance that implements `AsyncDisposable`\n *\n * @example\n * ```ts\n * const logger = createLogger({\n * minimumLevel: 'information',\n * sinks: [consoleSink({ theme: 'dark' })],\n * enrichers: [hostnameEnricher()],\n * });\n *\n * logger.info('Server started on port {Port}', { Port: 3000 });\n * ```\n *\n * @see {@link Logger} for the full Logger API\n * @see {@link LoggerConfig} for configuration options\n */\nexport function createLogger(config: LoggerConfig): Logger {\n if (!config.sinks || config.sinks.length === 0) {\n throw new Error('createLogger requires at least one sink');\n }\n\n const minimumLevel =\n typeof config.minimumLevel === 'string'\n ? parseLogLevel(config.minimumLevel)\n : (config.minimumLevel ?? LogLevel.Information);\n\n const pipeline = new LoggerPipeline({\n minimumLevel,\n levelOverrides: config.levelOverrides,\n sinks: config.sinks,\n enrichers: config.enrichers,\n filters: config.filters,\n maxQueueSize: config.maxQueueSize,\n dropPolicy: config.dropPolicy\n });\n\n const logger = new Logger(pipeline);\n\n if (config.handleProcessExit) {\n const onExit = () => {\n logger.dispose().catch(() => {\n // Best-effort flush on exit\n });\n };\n process.on('SIGTERM', onExit);\n process.on('beforeExit', onExit);\n }\n\n return logger;\n}\n","import type { Logger } from './Logger.js';\n\n/**\n * DI service key for the `Logger` instance.\n *\n * Uses a symbol-based key for use with `@cleverbrush/di` `ServiceCollection`.\n * In the absence of `@cleverbrush/di`, this serves as a plain token.\n */\nexport const ILogger = Symbol.for('ILogger') as unknown as {\n __brand: 'ILogger';\n};\n\n/**\n * Configures logging services in the DI container.\n *\n * Registers the root logger as a singleton and optionally sets up\n * scoped loggers that auto-enrich with request context.\n *\n * @param services - the `ServiceCollection` to register with\n * @param logger - the root logger instance\n *\n * @example\n * ```ts\n * const server = new ServerBuilder()\n * .services((svc) => {\n * configureLogging(svc, logger);\n * })\n * .build();\n * ```\n */\nexport function configureLogging(services: any, logger: Logger): void {\n // Register as singleton — consumers get the same logger instance\n if (typeof services.addSingleton === 'function') {\n services.addSingleton(ILogger, () => logger);\n }\n}\n","import type { Enricher } from '../Enricher.js';\n\n/**\n * Enriches log events with the application name.\n *\n * @param application - the application name (e.g. `'order-service'`)\n * @returns an enricher that adds `{ Application: '...' }`\n */\nexport function applicationEnricher(application: string): Enricher {\n return event => ({\n ...event,\n properties: {\n ...event.properties,\n Application: application\n }\n });\n}\n","import type { Enricher } from '../Enricher.js';\n\n/**\n * Enriches log events with the caller's source file and line number.\n *\n * **Warning:** This enricher uses `Error.captureStackTrace` internally,\n * which is expensive. Use only when needed for debugging.\n *\n * @returns an enricher that adds `{ SourceFile: '...', SourceLine: number }`\n */\nexport function callerEnricher(): Enricher {\n return event => {\n const err: { stack?: string } = {};\n Error.captureStackTrace(err);\n const stack = err.stack;\n if (!stack) return event;\n\n // Skip internal frames to find the caller\n const lines = stack.split('\\n');\n // Find the first line that isn't from the log library itself\n for (let i = 1; i < lines.length; i++) {\n const line = lines[i];\n if (\n !line.includes('/log/src/') &&\n !line.includes('@cleverbrush/log')\n ) {\n const match = line.match(/\\(?(.*?):(\\d+):\\d+\\)?$/);\n if (match) {\n return {\n ...event,\n properties: {\n ...event.properties,\n SourceFile: match[1],\n SourceLine: parseInt(match[2], 10)\n }\n };\n }\n break;\n }\n }\n return event;\n };\n}\n","import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Logger } from './Logger.js';\n\n/**\n * Store shape for the ambient log context.\n */\nexport interface LogContextStore {\n logger: Logger;\n correlationId?: string;\n properties?: Record<string, unknown>;\n}\n\nconst storage = new AsyncLocalStorage<LogContextStore>();\n\n/**\n * Ambient logger context using `AsyncLocalStorage`.\n *\n * Zero overhead when not used — the `AsyncLocalStorage` instance\n * is only created once, and `getStore()` is a near-zero-cost operation.\n *\n * @example\n * ```ts\n * LogContext.run(logger, async () => {\n * const log = LogContext.current()!;\n * log.info('Inside context');\n * });\n * ```\n */\nexport const LogContext = {\n /**\n * Runs a callback with the given logger as the ambient context.\n *\n * @param logger - the logger to set as ambient\n * @param fn - the async function to run within the context\n * @returns the result of the callback\n */\n run<T>(logger: Logger, fn: () => T): T {\n return storage.run({ logger }, fn);\n },\n\n /**\n * Returns the ambient logger, or `undefined` if no context is active.\n */\n current(): Logger | undefined {\n return storage.getStore()?.logger;\n },\n\n /**\n * Returns the raw store, useful for enrichers to read correlation IDs.\n */\n getStore(): LogContextStore | undefined {\n return storage.getStore();\n },\n\n /**\n * Runs a callback with additional enrichment properties added\n * to the ambient logger context.\n *\n * @param properties - additional properties to add to the context logger\n * @param fn - the async function to run\n * @returns the result of the callback\n */\n enrichWith<T>(properties: Record<string, unknown>, fn: () => T): T {\n const current = storage.getStore();\n if (!current) {\n throw new Error(\n 'LogContext.enrichWith() called outside of LogContext.run()'\n );\n }\n const enrichedLogger = current.logger.forContext(properties);\n return storage.run(\n {\n ...current,\n logger: enrichedLogger,\n properties: {\n ...current.properties,\n ...properties\n }\n },\n fn\n );\n },\n\n /**\n * Runs a callback with a correlation ID set in the ambient context.\n *\n * @param correlationId - the correlation ID to set\n * @param fn - the async function to run\n * @returns the result of the callback\n */\n runWithCorrelationId<T>(correlationId: string, fn: () => T): T {\n const current = storage.getStore();\n const logger = current?.logger;\n if (!logger) {\n throw new Error(\n 'LogContext.runWithCorrelationId() called outside of LogContext.run()'\n );\n }\n return storage.run({ ...current, logger, correlationId }, fn);\n }\n};\n","import type { Enricher } from '../Enricher.js';\nimport { LogContext } from '../LogContext.js';\n\n/**\n * Enriches log events with the correlation ID from `AsyncLocalStorage`.\n * Zero-cost when no context is active — simply returns the event unchanged.\n *\n * @returns an enricher that adds `{ CorrelationId: '...' }` if available\n */\nexport function correlationIdEnricher(): Enricher {\n return event => {\n const store = LogContext.getStore();\n if (!store?.correlationId) return event;\n return {\n ...event,\n properties: {\n ...event.properties,\n CorrelationId: store.correlationId\n }\n };\n };\n}\n","import type { Enricher } from '../Enricher.js';\n\n/**\n * Enriches log events with the deployment environment name.\n *\n * @param environment - the environment name (e.g. `'production'`, `'staging'`)\n * @returns an enricher that adds `{ Environment: '...' }`\n */\nexport function environmentEnricher(environment: string): Enricher {\n return event => ({\n ...event,\n properties: {\n ...event.properties,\n Environment: environment\n }\n });\n}\n","import os from 'node:os';\nimport type { Enricher } from '../Enricher.js';\n\n/**\n * Enriches log events with the machine hostname.\n * The hostname is cached on first call.\n *\n * @returns an enricher that adds `{ Hostname: '...' }`\n */\nexport function hostnameEnricher(): Enricher {\n let hostname: string | undefined;\n return event => {\n if (hostname === undefined) {\n hostname = os.hostname();\n }\n return {\n ...event,\n properties: { ...event.properties, Hostname: hostname }\n };\n };\n}\n","import type { Enricher } from '../Enricher.js';\n\n/**\n * Enriches log events with the current process ID.\n * The PID is cached on first call.\n *\n * @returns an enricher that adds `{ ProcessId: number }`\n */\nexport function processIdEnricher(): Enricher {\n let pid: number | undefined;\n return event => {\n if (pid === undefined) {\n pid = process.pid;\n }\n return {\n ...event,\n properties: { ...event.properties, ProcessId: pid }\n };\n };\n}\n","import type { LogEvent } from '../LogEvent.js';\nimport { LogLevel } from '../LogLevel.js';\nimport { type SerializationOptions, safeSerialize } from '../serialization.js';\n\n/**\n * Maps internal log levels to CLEF/Serilog level names.\n * `Information` is omitted in CLEF (it's the default).\n */\nconst clefLevelMap: Record<LogLevel, string | undefined> = {\n [LogLevel.Trace]: 'Verbose',\n [LogLevel.Debug]: 'Debug',\n [LogLevel.Information]: undefined,\n [LogLevel.Warning]: 'Warning',\n [LogLevel.Error]: 'Error',\n [LogLevel.Fatal]: 'Fatal'\n};\n\n/**\n * Formats a single `LogEvent` as a CLEF JSON string.\n *\n * CLEF (Compact Log Event Format) is the standard wire format for Seq\n * and compatible with the Serilog ecosystem.\n *\n * @param event - the log event to format\n * @param options - optional serialization limits\n * @returns a single-line JSON string in CLEF format\n *\n * @example\n * ```ts\n * const line = formatClef(event);\n * // '{\"@t\":\"2026-04-20T14:30:00.123Z\",\"@mt\":\"User {UserId} signed in\",\"UserId\":\"usr_abc\"}'\n * ```\n */\nexport function formatClef(\n event: LogEvent,\n options?: SerializationOptions\n): string {\n const obj: Record<string, unknown> = {\n '@t': event.timestamp.toISOString(),\n '@mt': event.messageTemplate\n };\n\n // Only include @m if it differs from @mt\n if (event.renderedMessage !== event.messageTemplate) {\n obj['@m'] = event.renderedMessage;\n }\n\n // Omit @l for Information (CLEF default)\n const clefLevel = clefLevelMap[event.level];\n if (clefLevel !== undefined) {\n obj['@l'] = clefLevel;\n }\n\n // Exception stack trace\n if (event.exception) {\n obj['@x'] = event.exception.stack ?? event.exception.message;\n }\n\n // Event ID\n if (event.eventId) {\n obj['@i'] = event.eventId;\n }\n\n // Spread all properties as top-level CLEF fields\n if (event.properties) {\n for (const [key, value] of Object.entries(event.properties)) {\n obj[key] = safeSerialize(value, options);\n }\n }\n\n return JSON.stringify(obj);\n}\n\n/**\n * Formats a batch of log events as newline-delimited CLEF.\n *\n * @param events - array of log events\n * @param options - optional serialization limits\n * @returns newline-delimited CLEF string\n *\n * @example\n * ```ts\n * const payload = formatClefBatch(events);\n * // Suitable for POST to Seq /ingest/clef\n * ```\n */\nexport function formatClefBatch(\n events: LogEvent[],\n options?: SerializationOptions\n): string {\n return events.map(e => formatClef(e, options)).join('\\n');\n}\n","import { extractCorrelationId, generateCorrelationId } from '../correlation.js';\nimport { LogContext } from '../LogContext.js';\n\n/**\n * Correlation ID middleware configuration.\n */\nexport interface CorrelationIdMiddlewareOptions {\n /** Headers to read from incoming request (checked in order). @default ['X-Correlation-Id', 'X-Request-Id'] */\n requestHeaders?: string[];\n /** Header to set on the response. Set to `false` to skip setting a response header entirely. @default 'X-Correlation-Id' */\n responseHeader?: string | false;\n /** Custom ID generator. @default generateCorrelationId */\n generate?: () => string;\n}\n\n/**\n * Creates correlation ID middleware for `@cleverbrush/server`.\n *\n * Extracts a correlation ID from incoming request headers (or generates one),\n * sets it on the response, and stores it in the `LogContext` for enrichers.\n *\n * @param options - correlation ID configuration\n * @returns a middleware function\n *\n * @example\n * ```ts\n * server.use(correlationIdMiddleware({\n * requestHeaders: ['X-Correlation-Id', 'X-Request-Id'],\n * }));\n * ```\n */\nexport function correlationIdMiddleware(\n options?: CorrelationIdMiddlewareOptions\n) {\n const responseHeader =\n options?.responseHeader === false\n ? false\n : (options?.responseHeader ?? 'X-Correlation-Id');\n const generate = options?.generate ?? generateCorrelationId;\n\n return async (context: any, next: () => Promise<void>) => {\n const headers = context.headers ?? {};\n const correlationId = extractCorrelationId(headers) || generate();\n\n // Set on response if possible (skip if responseHeader is false)\n if (responseHeader !== false) {\n if (context.setHeader) {\n context.setHeader(responseHeader, correlationId);\n } else if (context.response?.setHeader) {\n context.response.setHeader(responseHeader, correlationId);\n }\n }\n\n // Store in context items for other middleware\n if (context.items instanceof Map) {\n context.items.set('correlationId', correlationId);\n }\n\n // Run the rest of the pipeline with the correlation ID in AsyncLocalStorage\n const store = LogContext.getStore();\n if (store) {\n await LogContext.runWithCorrelationId(correlationId, () => next());\n } else {\n await next();\n }\n };\n}\n","import type { Logger } from '../Logger.js';\nimport { LogLevel, type LogLevelName, parseLogLevel } from '../LogLevel.js';\n\n/**\n * Request logging middleware configuration.\n */\nexport interface RequestLoggingOptions {\n /** Customize log level based on status code and elapsed time. */\n getLevel?: (statusCode: number, elapsedMs: number) => LogLevelName;\n /** Paths to exclude from request logging. Accepts plain strings or objects with a `path` property (e.g. endpoint builders). */\n excludePaths?: (string | { readonly path: string })[];\n /** Extract custom properties from the request context. */\n enrichRequest?: (ctx: any) => Record<string, unknown>;\n /** Whether to log request bodies. @default false */\n logRequestBody?: boolean;\n /** Whether to log response bodies. @default false */\n logResponseBody?: boolean;\n /** Whether to log when request starts (in addition to completion). @default false */\n logRequestStart?: boolean;\n}\n\n/**\n * Creates request logging middleware for `@cleverbrush/server`.\n *\n * Logs HTTP request completion with method, path, status code, and\n * elapsed time. Uses the logger from the request context or falls\n * back to the provided logger.\n *\n * @param logger - the logger to use for request logging\n * @param options - request logging configuration\n * @returns a middleware function\n *\n * @example\n * ```ts\n * server.use(requestLoggingMiddleware(logger, {\n * excludePaths: ['/health', myEndpoint],\n * getLevel: (status) => status >= 500 ? 'error' : 'information',\n * }));\n * ```\n */\nexport function requestLoggingMiddleware(\n logger: Logger,\n options?: RequestLoggingOptions\n) {\n const excludePaths = new Set(\n (options?.excludePaths ?? []).map(p =>\n typeof p === 'string' ? p : p.path\n )\n );\n const getLevel = options?.getLevel;\n const enrichRequest = options?.enrichRequest;\n const logRequestStart = options?.logRequestStart ?? false;\n\n return async (context: any, next: () => Promise<void>) => {\n const pathname = context.url?.pathname ?? context.path ?? '';\n\n if (excludePaths.has(pathname)) {\n await next();\n return;\n }\n\n const start = Date.now();\n const method = context.method ?? 'GET';\n\n if (logRequestStart) {\n logger.info('HTTP {Method} {Path} started', {\n Method: method,\n Path: pathname\n });\n }\n\n let statusCode = 200;\n try {\n await next();\n statusCode =\n context.statusCode ?? context.response?.statusCode ?? 200;\n } catch (err) {\n statusCode =\n context.statusCode ?? context.response?.statusCode ?? 500;\n throw err;\n } finally {\n const elapsed = Date.now() - start;\n const levelName = getLevel\n ? getLevel(statusCode, elapsed)\n : statusCode >= 500\n ? 'error'\n : statusCode >= 400\n ? 'warning'\n : 'information';\n\n const properties: Record<string, unknown> = {\n Method: method,\n Path: pathname,\n StatusCode: statusCode,\n Elapsed: elapsed\n };\n\n if (context.headers?.['user-agent']) {\n properties.UserAgent = context.headers['user-agent'];\n }\n\n if (enrichRequest) {\n try {\n const extra = enrichRequest(context);\n Object.assign(properties, extra);\n } catch {\n // ignore enrichment errors\n }\n }\n\n const level = parseLogLevel(levelName);\n const requestLogger = logger.forContext(\n 'SourceContext',\n 'RequestLogging'\n );\n\n if (level >= LogLevel.Error) {\n requestLogger.error(\n 'HTTP {Method} {Path} responded {StatusCode} in {Elapsed}ms',\n properties\n );\n } else if (level >= LogLevel.Warning) {\n requestLogger.warn(\n 'HTTP {Method} {Path} responded {StatusCode} in {Elapsed}ms',\n properties\n );\n } else {\n requestLogger.info(\n 'HTTP {Method} {Path} responded {StatusCode} in {Elapsed}ms',\n properties\n );\n }\n }\n };\n}\n","import type { LogFilter } from './Filter.js';\nimport { type LogLevel, type LogLevelName, parseLogLevel } from './LogLevel.js';\n\n/**\n * Sampling rates per log level. Values are 0–1 where 1 = keep all, 0 = drop all.\n * Unspecified levels pass through unfiltered.\n */\nexport type SamplingRates = Partial<Record<LogLevelName, number>>;\n\n/**\n * Creates a sampling filter for high-throughput scenarios.\n *\n * Only a fraction of events at the specified levels are kept,\n * reducing log volume without losing visibility at higher severity levels.\n *\n * @param rates - sampling rates per log level (0–1)\n * @returns a `LogFilter` that randomly samples events\n *\n * @example\n * ```ts\n * const filter = samplingFilter({ debug: 0.01, trace: 0.001 });\n * // Keeps 1% of debug events, 0.1% of trace events\n * ```\n */\nexport function samplingFilter(rates: SamplingRates): LogFilter {\n const resolvedRates = new Map<LogLevel, number>();\n for (const [name, rate] of Object.entries(rates)) {\n resolvedRates.set(\n parseLogLevel(name),\n Math.max(0, Math.min(1, rate as number))\n );\n }\n\n return event => {\n const rate = resolvedRates.get(event.level);\n if (rate === undefined) return true; // unspecified levels pass through\n return Math.random() < rate;\n };\n}\n","import { formatClef } from '../formatters/ClefFormatter.js';\nimport type { LogEvent } from '../LogEvent.js';\nimport {\n LogLevel,\n type LogLevelName,\n levelToShortString,\n parseLogLevel\n} from '../LogLevel.js';\nimport type { LogSink } from '../Sink.js';\nimport { safeSerialize } from '../serialization.js';\n\n/**\n * Console sink configuration.\n */\nexport interface ConsoleSinkOptions {\n /** Output mode: `'pretty'` for colored human-readable, `'json'` for CLEF. @default 'pretty' */\n mode?: 'pretty' | 'json';\n /** Color theme for pretty mode. @default 'dark' */\n theme?: 'dark' | 'light' | 'none';\n /** Minimum level for this sink. @default undefined (uses pipeline level) */\n minimumLevel?: LogLevelName;\n}\n\nconst LEVEL_COLORS_DARK: Record<LogLevel, string> = {\n [LogLevel.Trace]: '\\x1b[90m', // gray\n [LogLevel.Debug]: '\\x1b[36m', // cyan\n [LogLevel.Information]: '\\x1b[32m', // green\n [LogLevel.Warning]: '\\x1b[33m', // yellow\n [LogLevel.Error]: '\\x1b[31m', // red\n [LogLevel.Fatal]: '\\x1b[35m' // magenta\n};\n\nconst LEVEL_COLORS_LIGHT: Record<LogLevel, string> = {\n [LogLevel.Trace]: '\\x1b[37m',\n [LogLevel.Debug]: '\\x1b[34m',\n [LogLevel.Information]: '\\x1b[32m',\n [LogLevel.Warning]: '\\x1b[33m',\n [LogLevel.Error]: '\\x1b[31m',\n [LogLevel.Fatal]: '\\x1b[35m'\n};\n\nconst RESET = '\\x1b[0m';\n\n/**\n * Creates a console sink for log output.\n *\n * Supports two modes:\n * - `'pretty'` — colored, human-readable output (default for development)\n * - `'json'` — CLEF JSON output (for production / container logs)\n *\n * @param options - console sink configuration\n * @returns a `LogSink` that writes to stdout/stderr\n *\n * @example\n * ```ts\n * const sink = consoleSink({ theme: 'dark', minimumLevel: 'debug' });\n * ```\n */\nexport function consoleSink(options?: ConsoleSinkOptions): LogSink {\n const mode = options?.mode ?? 'pretty';\n const theme = options?.theme ?? 'dark';\n const minLevel = options?.minimumLevel\n ? parseLogLevel(options.minimumLevel)\n : undefined;\n const colors =\n theme === 'dark'\n ? LEVEL_COLORS_DARK\n : theme === 'light'\n ? LEVEL_COLORS_LIGHT\n : undefined;\n\n return {\n async emit(events: LogEvent[]): Promise<void> {\n for (const event of events) {\n if (minLevel !== undefined && event.level < minLevel) {\n continue;\n }\n\n const output =\n event.level >= LogLevel.Warning\n ? process.stderr\n : process.stdout;\n\n if (mode === 'json') {\n output.write(formatClef(event) + '\\n');\n } else {\n const timestamp = formatTimestamp(event.timestamp);\n const levelStr = levelToShortString(event.level);\n const color = colors?.[event.level] ?? '';\n const reset = colors ? RESET : '';\n\n let line = `${color}[${timestamp} ${levelStr}]${reset} ${event.renderedMessage}\\n`;\n\n // Print properties (excluding common ones already in message)\n const props = event.properties;\n if (props && Object.keys(props).length > 0) {\n for (const [key, value] of Object.entries(props)) {\n const serialized = safeSerialize(value, {\n maxDepth: 3\n });\n line += ` ${key}: ${JSON.stringify(serialized)}\\n`;\n }\n }\n\n if (event.exception) {\n line += ` ${event.exception.stack ?? event.exception.message}\\n`;\n }\n\n output.write(line);\n }\n }\n },\n\n async [Symbol.asyncDispose](): Promise<void> {\n // Console doesn't need cleanup\n }\n };\n}\n\nfunction formatTimestamp(date: Date): string {\n const y = date.getFullYear();\n const mo = String(date.getMonth() + 1).padStart(2, '0');\n const d = String(date.getDate()).padStart(2, '0');\n const h = String(date.getHours()).padStart(2, '0');\n const mi = String(date.getMinutes()).padStart(2, '0');\n const s = String(date.getSeconds()).padStart(2, '0');\n const ms = String(date.getMilliseconds()).padStart(3, '0');\n return `${y}-${mo}-${d} ${h}:${mi}:${s}.${ms}`;\n}\n","import type { LogEvent } from '../LogEvent.js';\nimport { type LogLevelName, parseLogLevel } from '../LogLevel.js';\nimport type { LogSink } from '../Sink.js';\n\n/**\n * Configuration for a quick custom sink.\n */\nexport interface CreateSinkOptions {\n /** Minimum level for this sink. */\n minimumLevel?: LogLevelName;\n /** The emit function that writes events. */\n emit: (events: LogEvent[]) => Promise<void>;\n /** Optional flush function. */\n flush?: () => Promise<void>;\n /** Optional dispose function. */\n dispose?: () => Promise<void>;\n}\n\n/**\n * Creates a simple `LogSink` from an emit function.\n *\n * @param options - sink configuration with emit function\n * @returns a `LogSink` instance\n *\n * @example\n * ```ts\n * const sink = createSink({\n * minimumLevel: 'error',\n * emit: async (events) => {\n * for (const event of events) {\n * await sendAlert(event.renderedMessage);\n * }\n * },\n * });\n * ```\n */\nexport function createSink(options: CreateSinkOptions): LogSink {\n const minLevel = options.minimumLevel\n ? parseLogLevel(options.minimumLevel)\n : undefined;\n\n return {\n async emit(events: LogEvent[]): Promise<void> {\n const filtered =\n minLevel !== undefined\n ? events.filter(e => e.level >= minLevel!)\n : events;\n if (filtered.length > 0) {\n await options.emit(filtered);\n }\n },\n\n flush: options.flush,\n\n async [Symbol.asyncDispose](): Promise<void> {\n if (options.dispose) {\n await options.dispose();\n }\n }\n };\n}\n","import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { formatClef } from '../formatters/ClefFormatter.js';\nimport type { LogEvent } from '../LogEvent.js';\nimport { type LogLevelName, parseLogLevel } from '../LogLevel.js';\nimport { SelfLog } from '../SelfLog.js';\nimport type { LogSink } from '../Sink.js';\n\n/**\n * File rotation configuration.\n */\nexport interface RotationOptions {\n /** Rotation strategy. */\n strategy: 'size' | 'time' | 'hybrid';\n /** Time-based rotation interval (for `'time'` and `'hybrid'`). */\n interval?: 'hourly' | 'daily';\n /** Maximum file size in bytes before rotation (for `'size'` and `'hybrid'`). */\n maxBytes?: number;\n /** Number of rotated files to retain. @default 10 */\n retainCount?: number;\n}\n\n/**\n * File sink configuration.\n */\nexport interface FileSinkOptions {\n /** Path to the log file. */\n path: string;\n /** Minimum level for this sink. */\n minimumLevel?: LogLevelName;\n /** Rotation configuration. */\n rotation?: RotationOptions;\n}\n\n/**\n * Creates a file sink that writes CLEF-formatted log events.\n *\n * Supports size-based, time-based, and hybrid rotation strategies.\n *\n * @param options - file sink configuration\n * @returns a `LogSink` that appends to a file\n *\n * @example\n * ```ts\n * const sink = fileSink({\n * path: './logs/app.log',\n * rotation: { strategy: 'time', interval: 'daily', retainCount: 30 },\n * });\n * ```\n */\nexport function fileSink(options: FileSinkOptions): LogSink {\n const filePath = path.resolve(options.path);\n const minLevel = options.minimumLevel\n ? parseLogLevel(options.minimumLevel)\n : undefined;\n const rotation = options.rotation;\n const retainCount = rotation?.retainCount ?? 10;\n\n let currentSize = 0;\n let currentDate = getDateString(new Date(), rotation?.interval);\n let stream: fs.WriteStream | undefined;\n\n function ensureDir(): void {\n const dir = path.dirname(filePath);\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n }\n\n function getStream(): fs.WriteStream {\n if (!stream || stream.closed) {\n ensureDir();\n stream = fs.createWriteStream(filePath, {\n flags: 'a'\n });\n try {\n const stats = fs.statSync(filePath);\n currentSize = stats.size;\n } catch {\n currentSize = 0;\n }\n }\n return stream;\n }\n\n function shouldRotate(): boolean {\n if (!rotation) return false;\n\n const now = new Date();\n const nowDate = getDateString(now, rotation.interval);\n\n if (rotation.strategy === 'time') {\n return nowDate !== currentDate;\n }\n if (rotation.strategy === 'size') {\n return (\n rotation.maxBytes !== undefined &&\n currentSize >= rotation.maxBytes\n );\n }\n if (rotation.strategy === 'hybrid') {\n return (\n nowDate !== currentDate ||\n (rotation.maxBytes !== undefined &&\n currentSize >= rotation.maxBytes)\n );\n }\n return false;\n }\n\n function rotate(): void {\n if (stream) {\n stream.end();\n stream = undefined;\n }\n\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-');\n const ext = path.extname(filePath);\n const base = filePath.slice(0, -ext.length || undefined);\n const rotatedPath = `${base}.${timestamp}${ext}`;\n\n try {\n if (fs.existsSync(filePath)) {\n fs.renameSync(filePath, rotatedPath);\n }\n } catch (err) {\n SelfLog.write('File rotation failed', err);\n }\n\n currentSize = 0;\n currentDate = getDateString(new Date(), rotation?.interval);\n\n // Prune old files\n pruneOldFiles(filePath, retainCount);\n }\n\n return {\n async emit(events: LogEvent[]): Promise<void> {\n for (const event of events) {\n if (minLevel !== undefined && event.level < minLevel) {\n continue;\n }\n\n if (shouldRotate()) {\n rotate();\n }\n\n const line = formatClef(event) + '\\n';\n const s = getStream();\n s.write(line);\n currentSize += Buffer.byteLength(line);\n }\n },\n\n async flush(): Promise<void> {\n if (stream) {\n await new Promise<void>((resolve, reject) => {\n stream!.once('drain', resolve);\n stream!.once('error', reject);\n if (!stream!.write('')) {\n // Waiting for drain\n } else {\n resolve();\n }\n });\n }\n },\n\n async [Symbol.asyncDispose](): Promise<void> {\n if (stream) {\n await new Promise<void>(resolve => {\n stream!.end(() => resolve());\n });\n stream = undefined;\n }\n }\n };\n}\n\nfunction getDateString(date: Date, interval?: 'hourly' | 'daily'): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, '0');\n const d = String(date.getDate()).padStart(2, '0');\n if (interval === 'hourly') {\n const h = String(date.getHours()).padStart(2, '0');\n return `${y}-${m}-${d}-${h}`;\n }\n return `${y}-${m}-${d}`;\n}\n\nfunction pruneOldFiles(basePath: string, retainCount: number): void {\n try {\n const dir = path.dirname(basePath);\n const base = path.basename(basePath);\n const ext = path.extname(base);\n const nameWithoutExt = base.slice(0, -ext.length || undefined);\n\n const files = fs\n .readdirSync(dir)\n .filter(f => f.startsWith(nameWithoutExt + '.') && f !== base)\n .sort();\n\n if (files.length > retainCount) {\n const toDelete = files.slice(0, files.length - retainCount);\n for (const file of toDelete) {\n fs.unlinkSync(path.join(dir, file));\n }\n }\n } catch (err) {\n SelfLog.write('Failed to prune old log files', err);\n }\n}\n","import { formatClefBatch } from '../formatters/ClefFormatter.js';\nimport type { LogEvent } from '../LogEvent.js';\nimport { type LogLevel, parseLogLevel } from '../LogLevel.js';\nimport type { LogSink } from '../Sink.js';\nimport { BatchingSink } from './BatchingSink.js';\n\n/**\n * Seq sink configuration.\n */\nexport interface SeqSinkOptions {\n /** Base URL of the Seq server (e.g. `http://localhost:5341`). */\n serverUrl: string;\n /** Optional API key sent as `X-Seq-ApiKey` header. */\n apiKey?: string;\n /** Events per batch. @default 100 */\n batchSize?: number;\n /** Max milliseconds between flushes. @default 2000 */\n flushInterval?: number;\n /** Maximum retry attempts. @default 5 */\n maxRetries?: number;\n /** Initial retry delay in ms. @default 1000 */\n retryDelay?: number;\n}\n\n/**\n * Creates a sink that sends log events to a Seq server via HTTP in CLEF format.\n *\n * Events are batched and sent to `POST {serverUrl}/ingest/clef`. The sink\n * respects Seq's `MinimumLevelAccepted` response to dynamically reduce\n * bandwidth when the server applies level filtering.\n *\n * @param options - Seq connection and batching configuration\n * @returns a `LogSink` that batches and sends events to Seq\n *\n * @example\n * ```ts\n * const sink = seqSink({\n * serverUrl: 'https://seq.mycompany.com',\n * apiKey: process.env.SEQ_API_KEY,\n * });\n * ```\n */\nexport function seqSink(options: SeqSinkOptions): LogSink {\n const url = options.serverUrl.replace(/\\/$/, '');\n const ingestUrl = `${url}/ingest/clef`;\n let dynamicMinLevel: LogLevel | undefined;\n\n const batcher = new BatchingSink({\n batchSize: options.batchSize ?? 100,\n flushInterval: options.flushInterval ?? 2_000,\n maxRetries: options.maxRetries ?? 5,\n retryDelay: options.retryDelay ?? 1_000,\n emit: async (batch: LogEvent[]) => {\n // Apply dynamic level filtering\n const filtered =\n dynamicMinLevel !== undefined\n ? batch.filter(e => e.level >= dynamicMinLevel!)\n : batch;\n\n if (filtered.length === 0) return;\n\n const payload = formatClefBatch(filtered);\n const headers: Record<string, string> = {\n 'Content-Type': 'application/vnd.serilog.clef'\n };\n if (options.apiKey) {\n headers['X-Seq-ApiKey'] = options.apiKey;\n }\n\n const response = await fetch(ingestUrl, {\n method: 'POST',\n headers,\n body: payload\n });\n\n if (!response.ok) {\n throw new Error(\n `Seq ingestion failed: ${response.status} ${response.statusText}`\n );\n }\n\n // Check for MinimumLevelAccepted header\n const minLevelHeader = response.headers.get(\n 'X-Seq-MinimumLevelAccepted'\n );\n if (minLevelHeader) {\n try {\n dynamicMinLevel = parseLogLevel(\n minLevelHeader.toLowerCase()\n );\n } catch {\n // ignore invalid level\n }\n } else {\n dynamicMinLevel = undefined;\n }\n }\n });\n\n return {\n async emit(events: LogEvent[]): Promise<void> {\n await batcher.emit(events);\n },\n\n async flush(): Promise<void> {\n await batcher.flush();\n },\n\n async [Symbol.asyncDispose](): Promise<void> {\n await batcher[Symbol.asyncDispose]();\n }\n };\n}\n","import type { Logger } from './Logger.js';\nimport { correlationIdMiddleware } from './middleware/correlationId.js';\nimport {\n type RequestLoggingOptions,\n requestLoggingMiddleware\n} from './middleware/requestLogging.js';\n\nexport interface UseLoggingOptions extends RequestLoggingOptions {\n /**\n * Header name to echo the correlation ID back on the response.\n * Set to `false` to suppress the response header entirely — useful\n * when an OTel `X-Trace-Id` header already serves the traceability\n * purpose and a second ID would confuse consumers.\n *\n * @default 'X-Correlation-Id'\n */\n correlationResponseHeader?: string | false;\n}\n\n/**\n * Convenience function that returns correlation ID middleware and\n * request logging middleware, ready to spread into `ServerBuilder.use()`.\n *\n * @param logger - the root logger instance\n * @param options - request logging configuration\n * @returns an array of middleware functions\n *\n * @example\n * ```ts\n * const server = new ServerBuilder()\n * .use(...useLogging(logger, {\n * excludePaths: ['/health'],\n * }))\n * .build();\n * ```\n */\nexport function useLogging(\n logger: Logger,\n options?: UseLoggingOptions\n): [\n ReturnType<typeof correlationIdMiddleware>,\n ReturnType<typeof requestLoggingMiddleware>\n] {\n return [\n correlationIdMiddleware({\n responseHeader: options?.correlationResponseHeader\n }),\n requestLoggingMiddleware(logger, options)\n ];\n}\n"],"mappings":"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","names":["randomUUID","generateCorrelationId","now","bytes","random","i","hex","b","extractCorrelationId","headers","correlationId","getHeader","requestId","traceparent","parts","name","value","templateCache","eventIdCache","parseTemplate","template","cached","tokens","i","len","braceStart","braceEnd","propName","destructure","renderTemplate","properties","result","token","value","safeSerialize","captureProperties","key","computeEventId","hash","id","createLogEvent","level","exception","captured","renderedMessage","eventId","Logger","_Logger","#pipeline","#contextProperties","#levelWatchInterval","pipeline","contextProperties","level","sourceContext","key","value","extra","parseLogLevel","envVar","intervalMs","val","template","properties","#write","LogLevel","errorOrTemplate","templateOrProps","exception","templateStr","props","mergedProps","event","createLogEvent","LoggerPipeline","#sinks","#enrichers","#filters","#levelOverrides","#minimumLevel","#maxQueueSize","#dropPolicy","#queue","#flushing","#disposed","config","ns","level","parseLogLevel","sourceContext","overrideLevel","#findOverride","event","#scheduleFlush","#processQueue","flushPromises","sink","err","SelfLog","disposePromises","batch","processed","enricher","pass","filter","emitPromises","exact","bestLen","bestLevel","prefix","createLogger","config","minimumLevel","parseLogLevel","LogLevel","pipeline","LoggerPipeline","logger","Logger","onExit","ILogger","configureLogging","services","logger","applicationEnricher","application","event","callerEnricher","event","err","stack","lines","i","line","match","AsyncLocalStorage","storage","LogContext","logger","fn","properties","current","enrichedLogger","correlationId","correlationIdEnricher","event","store","LogContext","environmentEnricher","environment","event","os","hostnameEnricher","hostname","event","processIdEnricher","pid","event","clefLevelMap","LogLevel","formatClef","event","options","obj","clefLevel","key","value","safeSerialize","formatClefBatch","events","e","correlationIdMiddleware","options","responseHeader","generate","generateCorrelationId","context","next","headers","correlationId","extractCorrelationId","LogContext","requestLoggingMiddleware","logger","options","excludePaths","p","getLevel","enrichRequest","logRequestStart","context","next","pathname","start","method","statusCode","err","elapsed","levelName","properties","extra","level","parseLogLevel","requestLogger","LogLevel","samplingFilter","rates","resolvedRates","name","rate","parseLogLevel","event","LEVEL_COLORS_DARK","LogLevel","LEVEL_COLORS_LIGHT","RESET","consoleSink","options","mode","theme","minLevel","parseLogLevel","colors","events","event","output","formatClef","timestamp","formatTimestamp","levelStr","levelToShortString","line","props","key","value","serialized","safeSerialize","date","y","mo","d","h","mi","s","ms","createSink","options","minLevel","parseLogLevel","events","filtered","e","fs","path","fileSink","options","filePath","minLevel","parseLogLevel","rotation","retainCount","currentSize","currentDate","getDateString","stream","ensureDir","dir","getStream","shouldRotate","nowDate","rotate","timestamp","ext","rotatedPath","err","SelfLog","pruneOldFiles","events","event","line","formatClef","resolve","reject","date","interval","y","m","d","h","basePath","base","nameWithoutExt","files","f","toDelete","file","seqSink","options","ingestUrl","dynamicMinLevel","batcher","BatchingSink","batch","filtered","e","payload","formatClefBatch","headers","response","minLevelHeader","parseLogLevel","events","useLogging","logger","options","correlationIdMiddleware","requestLoggingMiddleware"]}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Correlation ID middleware configuration.
3
+ */
4
+ export interface CorrelationIdMiddlewareOptions {
5
+ /** Headers to read from incoming request (checked in order). @default ['X-Correlation-Id', 'X-Request-Id'] */
6
+ requestHeaders?: string[];
7
+ /** Header to set on the response. Set to `false` to skip setting a response header entirely. @default 'X-Correlation-Id' */
8
+ responseHeader?: string | false;
9
+ /** Custom ID generator. @default generateCorrelationId */
10
+ generate?: () => string;
11
+ }
12
+ /**
13
+ * Creates correlation ID middleware for `@cleverbrush/server`.
14
+ *
15
+ * Extracts a correlation ID from incoming request headers (or generates one),
16
+ * sets it on the response, and stores it in the `LogContext` for enrichers.
17
+ *
18
+ * @param options - correlation ID configuration
19
+ * @returns a middleware function
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * server.use(correlationIdMiddleware({
24
+ * requestHeaders: ['X-Correlation-Id', 'X-Request-Id'],
25
+ * }));
26
+ * ```
27
+ */
28
+ export declare function correlationIdMiddleware(options?: CorrelationIdMiddlewareOptions): (context: any, next: () => Promise<void>) => Promise<void>;
@@ -0,0 +1,41 @@
1
+ import type { Logger } from '../Logger.js';
2
+ import { type LogLevelName } from '../LogLevel.js';
3
+ /**
4
+ * Request logging middleware configuration.
5
+ */
6
+ export interface RequestLoggingOptions {
7
+ /** Customize log level based on status code and elapsed time. */
8
+ getLevel?: (statusCode: number, elapsedMs: number) => LogLevelName;
9
+ /** Paths to exclude from request logging. Accepts plain strings or objects with a `path` property (e.g. endpoint builders). */
10
+ excludePaths?: (string | {
11
+ readonly path: string;
12
+ })[];
13
+ /** Extract custom properties from the request context. */
14
+ enrichRequest?: (ctx: any) => Record<string, unknown>;
15
+ /** Whether to log request bodies. @default false */
16
+ logRequestBody?: boolean;
17
+ /** Whether to log response bodies. @default false */
18
+ logResponseBody?: boolean;
19
+ /** Whether to log when request starts (in addition to completion). @default false */
20
+ logRequestStart?: boolean;
21
+ }
22
+ /**
23
+ * Creates request logging middleware for `@cleverbrush/server`.
24
+ *
25
+ * Logs HTTP request completion with method, path, status code, and
26
+ * elapsed time. Uses the logger from the request context or falls
27
+ * back to the provided logger.
28
+ *
29
+ * @param logger - the logger to use for request logging
30
+ * @param options - request logging configuration
31
+ * @returns a middleware function
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * server.use(requestLoggingMiddleware(logger, {
36
+ * excludePaths: ['/health', myEndpoint],
37
+ * getLevel: (status) => status >= 500 ? 'error' : 'information',
38
+ * }));
39
+ * ```
40
+ */
41
+ export declare function requestLoggingMiddleware(logger: Logger, options?: RequestLoggingOptions): (context: any, next: () => Promise<void>) => Promise<void>;
@@ -0,0 +1,23 @@
1
+ import type { LogFilter } from './Filter.js';
2
+ import { type LogLevelName } from './LogLevel.js';
3
+ /**
4
+ * Sampling rates per log level. Values are 0–1 where 1 = keep all, 0 = drop all.
5
+ * Unspecified levels pass through unfiltered.
6
+ */
7
+ export type SamplingRates = Partial<Record<LogLevelName, number>>;
8
+ /**
9
+ * Creates a sampling filter for high-throughput scenarios.
10
+ *
11
+ * Only a fraction of events at the specified levels are kept,
12
+ * reducing log volume without losing visibility at higher severity levels.
13
+ *
14
+ * @param rates - sampling rates per log level (0–1)
15
+ * @returns a `LogFilter` that randomly samples events
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const filter = samplingFilter({ debug: 0.01, trace: 0.001 });
20
+ * // Keeps 1% of debug events, 0.1% of trace events
21
+ * ```
22
+ */
23
+ export declare function samplingFilter(rates: SamplingRates): LogFilter;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Options for the safe serializer.
3
+ */
4
+ export interface SerializationOptions {
5
+ /** Maximum nesting depth for objects/arrays. @default 10 */
6
+ maxDepth?: number;
7
+ /** Maximum length for string values before truncation. @default 32768 */
8
+ maxStringLength?: number;
9
+ }
10
+ /**
11
+ * Safely serializes a value for structured logging, handling:
12
+ * - Circular references → `"[Circular]"`
13
+ * - Depth limits → `"[Object]"` or `"[Array]"`
14
+ * - BigInt → string representation
15
+ * - Buffer → `"[Buffer(N bytes)]"`
16
+ * - Functions → `"[Function: name]"`
17
+ * - Symbols → `"[Symbol: description]"`
18
+ * - Error objects → `{ message, stack, name, ...ownProperties }`
19
+ * - Long strings → truncated with `"...(truncated)"`
20
+ *
21
+ * @param value - the value to serialize
22
+ * @param options - serialization limits
23
+ * @returns a JSON-safe representation of the value
24
+ */
25
+ export declare function safeSerialize(value: unknown, options?: SerializationOptions): unknown;