@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
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # @cleverbrush/log
2
+
3
+ Enterprise structured logging for TypeScript — Serilog-style message templates, CLEF format, batching sinks with circuit breaking, ambient correlation IDs.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @cleverbrush/log
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```ts
14
+ import {
15
+ createLogger,
16
+ consoleSink,
17
+ hostnameEnricher,
18
+ processIdEnricher,
19
+ } from '@cleverbrush/log';
20
+
21
+ const logger = createLogger({
22
+ minimumLevel: 'information',
23
+ sinks: [consoleSink({ theme: 'dark' })],
24
+ enrichers: [hostnameEnricher(), processIdEnricher()],
25
+ });
26
+
27
+ logger.info('Server started on port {Port}', { Port: 3000 });
28
+ logger.error(new Error('oops'), 'Request failed for {UserId}', { UserId: 42 });
29
+
30
+ await logger.dispose();
31
+ ```
32
+
33
+ ## Features
34
+
35
+ - **Message Templates** — `{Named}` properties captured as structured data
36
+ - **Typed Templates** — `TypedTemplate<T>` via `@cleverbrush/schema` for compile-time checked, groupable log events
37
+ - **CLEF Format** — Compact Log Event Format for Seq, ClickHouse, etc.
38
+ - **Sinks** — Console, File (with rotation), Seq, ClickHouse, custom
39
+ - **Batching** — All network sinks batch with retry & circuit breaking
40
+ - **Enrichers** — hostname, processId, environment, application, correlationId, caller
41
+ - **Correlation IDs** — UUID v7, extracted from headers, propagated via AsyncLocalStorage
42
+ - **Middleware** — Request logging & correlation ID for `@cleverbrush/server`
43
+ - **DI** — `configureLogging()` for `@cleverbrush/di`
44
+ - **Sampling** — Per-level sampling filters
45
+
46
+ ## Typed Templates
47
+
48
+ Pass a `ParseStringSchemaBuilder` (from `@cleverbrush/schema`) directly to any log method. The logger uses the raw `{Property}` pattern as `messageTemplate` so all events of the same shape are grouped in Seq, ClickStack, ClickHouse, etc., while the rendered message is interpolated as usual.
49
+
50
+ ```ts
51
+ import { s } from '@cleverbrush/schema';
52
+ import { createLogger, consoleSink } from '@cleverbrush/log';
53
+
54
+ // Define once — compile-time checked parameter types
55
+ const TodoCreated = s.parseString('Todo #{TodoId} "{Title}" created by {UserId}');
56
+
57
+ const logger = createLogger({ sinks: [consoleSink()] });
58
+
59
+ // TypeScript enforces { TodoId, Title, UserId }
60
+ logger.info(TodoCreated, { TodoId: 1, Title: 'Buy milk', UserId: 'u-42' });
61
+ ```
62
+
63
+ ## Correlation Middleware
64
+
65
+ ```ts
66
+ import { useLogging } from '@cleverbrush/log';
67
+
68
+ // Returns [correlationIdMiddleware, requestLoggingMiddleware]
69
+ const [correlationId, requestLogging] = useLogging(logger, {
70
+ excludePaths: ['/health'],
71
+ // Set to false when OTel traceparent already provides traceability
72
+ correlationResponseHeader: false,
73
+ });
74
+ ```
75
+
76
+ `correlationResponseHeader: false` suppresses the `X-Correlation-Id` response header entirely — useful when `@cleverbrush/otel`'s tracing middleware already sets a `traceparent` / `traceresponse` header and a second ID would be redundant.
77
+
78
+ ## License
79
+
80
+ BSD-3-Clause
@@ -0,0 +1,8 @@
1
+ import type { LogEvent } from './LogEvent.js';
2
+ /**
3
+ * An enricher adds or transforms properties on a log event.
4
+ *
5
+ * Enrichers are pure functions that return a new event with additional
6
+ * properties — they must not mutate the input event.
7
+ */
8
+ export type Enricher = (event: LogEvent) => LogEvent;
@@ -0,0 +1,7 @@
1
+ import type { LogEvent } from './LogEvent.js';
2
+ /**
3
+ * A filter that determines whether a log event should be emitted.
4
+ *
5
+ * Return `true` to keep the event, `false` to discard it.
6
+ */
7
+ export type LogFilter = (event: LogEvent) => boolean;
@@ -0,0 +1,58 @@
1
+ import type { Logger } from './Logger.js';
2
+ /**
3
+ * Store shape for the ambient log context.
4
+ */
5
+ export interface LogContextStore {
6
+ logger: Logger;
7
+ correlationId?: string;
8
+ properties?: Record<string, unknown>;
9
+ }
10
+ /**
11
+ * Ambient logger context using `AsyncLocalStorage`.
12
+ *
13
+ * Zero overhead when not used — the `AsyncLocalStorage` instance
14
+ * is only created once, and `getStore()` is a near-zero-cost operation.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * LogContext.run(logger, async () => {
19
+ * const log = LogContext.current()!;
20
+ * log.info('Inside context');
21
+ * });
22
+ * ```
23
+ */
24
+ export declare const LogContext: {
25
+ /**
26
+ * Runs a callback with the given logger as the ambient context.
27
+ *
28
+ * @param logger - the logger to set as ambient
29
+ * @param fn - the async function to run within the context
30
+ * @returns the result of the callback
31
+ */
32
+ run<T>(logger: Logger, fn: () => T): T;
33
+ /**
34
+ * Returns the ambient logger, or `undefined` if no context is active.
35
+ */
36
+ current(): Logger | undefined;
37
+ /**
38
+ * Returns the raw store, useful for enrichers to read correlation IDs.
39
+ */
40
+ getStore(): LogContextStore | undefined;
41
+ /**
42
+ * Runs a callback with additional enrichment properties added
43
+ * to the ambient logger context.
44
+ *
45
+ * @param properties - additional properties to add to the context logger
46
+ * @param fn - the async function to run
47
+ * @returns the result of the callback
48
+ */
49
+ enrichWith<T>(properties: Record<string, unknown>, fn: () => T): T;
50
+ /**
51
+ * Runs a callback with a correlation ID set in the ambient context.
52
+ *
53
+ * @param correlationId - the correlation ID to set
54
+ * @param fn - the async function to run
55
+ * @returns the result of the callback
56
+ */
57
+ runWithCorrelationId<T>(correlationId: string, fn: () => T): T;
58
+ };
@@ -0,0 +1,24 @@
1
+ import type { LogLevel } from './LogLevel.js';
2
+ /**
3
+ * Represents a single structured log event in the pipeline.
4
+ *
5
+ * Log events carry both a human-readable rendered message and the original
6
+ * message template with structured property values, enabling both
7
+ * human-friendly display and machine-queryable structured search.
8
+ */
9
+ export interface LogEvent {
10
+ /** ISO timestamp when the event occurred. */
11
+ timestamp: Date;
12
+ /** Severity level of the event. */
13
+ level: LogLevel;
14
+ /** The raw message template with `{Property}` holes. */
15
+ messageTemplate: string;
16
+ /** The fully interpolated, human-readable message. */
17
+ renderedMessage: string;
18
+ /** Structured properties extracted from the template and enrichers. */
19
+ properties: Record<string, unknown>;
20
+ /** The exception associated with this event, if any. */
21
+ exception?: Error;
22
+ /** Deterministic hex hash of `messageTemplate` — maps to CLEF `@i`. */
23
+ eventId?: string;
24
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Log severity levels as a const object.
3
+ *
4
+ * Use as values (`LogLevel.Information`) or as a type (`LogLevel`).
5
+ * Levels are ordered numerically — higher values indicate greater severity.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * if (logger.isEnabled(LogLevel.Debug)) {
10
+ * logger.debug('Expensive computation: {@Result}', { Result: compute() });
11
+ * }
12
+ * ```
13
+ */
14
+ export declare const LogLevel: {
15
+ readonly Trace: 0;
16
+ readonly Debug: 1;
17
+ readonly Information: 2;
18
+ readonly Warning: 3;
19
+ readonly Error: 4;
20
+ readonly Fatal: 5;
21
+ };
22
+ export type LogLevel = (typeof LogLevel)[keyof typeof LogLevel];
23
+ /**
24
+ * Log level name strings for configuration and display.
25
+ */
26
+ export type LogLevelName = 'trace' | 'debug' | 'information' | 'warning' | 'error' | 'fatal';
27
+ /**
28
+ * Parses a log level name string to a `LogLevel` numeric value.
29
+ *
30
+ * @param name - case-insensitive level name
31
+ * @returns the numeric `LogLevel` value
32
+ */
33
+ export declare function parseLogLevel(name: string): LogLevel;
34
+ /**
35
+ * Converts a `LogLevel` numeric value to its string name.
36
+ *
37
+ * @param level - the numeric log level
38
+ * @returns the level name string
39
+ */
40
+ export declare function levelToString(level: LogLevel): LogLevelName;
41
+ /**
42
+ * Three-letter abbreviation for display in console output.
43
+ */
44
+ export declare function levelToShortString(level: LogLevel): string;
@@ -0,0 +1,111 @@
1
+ import type { LoggerPipeline } from './LoggerPipeline.js';
2
+ import { LogLevel, type LogLevelName } from './LogLevel.js';
3
+ /**
4
+ * A typed message template created via `ParseStringSchemaBuilder`.
5
+ *
6
+ * When passed to a `Logger` log method, the logger uses `template` as the
7
+ * `messageTemplate` (so events with the same shape are grouped in Seq /
8
+ * ClickStack / ClickHouse) and derives the rendered message by interpolating
9
+ * `template` with the supplied parameters.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { s } from '@cleverbrush/schema';
14
+ *
15
+ * // Build a reusable typed template once
16
+ * const tmpl = s.parseString('Todo #{TodoId} "{Title}" created by {UserId}');
17
+ *
18
+ * // All log sites share the same messageTemplate → groupable in the UI
19
+ * logger.info(tmpl, { TodoId: 1, Title: 'Buy milk', UserId: 'u-42' });
20
+ * ```
21
+ */
22
+ export interface TypedTemplate<T extends Record<string, unknown>> {
23
+ serialize(params: T): string;
24
+ /** The raw `{Property}` pattern string, used as `messageTemplate`. */
25
+ readonly template?: string;
26
+ }
27
+ /**
28
+ * Structured logger with per-level methods, child context support,
29
+ * and `AsyncDisposable` for graceful shutdown.
30
+ *
31
+ * Log methods are synchronous and fire-and-forget — they push events
32
+ * into an internal async microtask pipeline.
33
+ *
34
+ * Accepts both plain string templates and typed {@link TypedTemplate}
35
+ * objects (produced by `ParseStringSchemaBuilder` from `@cleverbrush/schema`).
36
+ * Typed templates carry a `template` property with the raw `{Property}` pattern,
37
+ * which the logger uses as `messageTemplate` so all events of the same shape
38
+ * are grouped correctly in Seq, ClickStack, ClickHouse, etc.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * logger.info('Server started on port {Port}', { Port: 3000 });
43
+ *
44
+ * const child = logger.forContext('SourceContext', 'OrderService');
45
+ * child.info('Processing order {OrderId}', { OrderId: 42 });
46
+ *
47
+ * // Typed template — structured grouping
48
+ * import { s } from '@cleverbrush/schema';
49
+ * const tmpl = s.parseString('Order #{OrderId} placed by {UserId}');
50
+ * child.info(tmpl, { OrderId: 1, UserId: 'u-99' });
51
+ * ```
52
+ */
53
+ export declare class Logger implements AsyncDisposable {
54
+ #private;
55
+ constructor(pipeline: LoggerPipeline, contextProperties?: Record<string, unknown>);
56
+ /**
57
+ * Checks whether the given level is enabled for this logger.
58
+ *
59
+ * @param level - the log level to check
60
+ * @returns `true` if events at this level would be processed
61
+ */
62
+ isEnabled(level: LogLevel): boolean;
63
+ /**
64
+ * Creates a child logger with additional context properties.
65
+ *
66
+ * @param key - property name, or an object of key-value pairs
67
+ * @param value - property value (when key is a string)
68
+ * @returns a new `Logger` with merged context properties
69
+ */
70
+ forContext(key: string | Record<string, unknown>, value?: unknown): Logger;
71
+ /**
72
+ * Changes the minimum log level at runtime.
73
+ *
74
+ * @param level - new minimum level (name string or numeric value)
75
+ */
76
+ setMinimumLevel(level: LogLevelName | LogLevel): void;
77
+ /**
78
+ * Polls an environment variable for log level changes.
79
+ *
80
+ * @param envVar - environment variable name to watch
81
+ * @param intervalMs - polling interval in milliseconds (default: 30000)
82
+ */
83
+ watchLevel(envVar: string, intervalMs?: number): void;
84
+ /** Log a trace-level message. */
85
+ trace(template: string, properties?: Record<string, unknown>): void;
86
+ trace<T extends Record<string, unknown>>(template: TypedTemplate<T>, properties: T): void;
87
+ /** Log a debug-level message. */
88
+ debug(template: string, properties?: Record<string, unknown>): void;
89
+ debug<T extends Record<string, unknown>>(template: TypedTemplate<T>, properties: T): void;
90
+ /** Log an information-level message. */
91
+ info(template: string, properties?: Record<string, unknown>): void;
92
+ info<T extends Record<string, unknown>>(template: TypedTemplate<T>, properties: T): void;
93
+ /** Log a warning-level message. */
94
+ warn(template: string, properties?: Record<string, unknown>): void;
95
+ warn<T extends Record<string, unknown>>(template: TypedTemplate<T>, properties: T): void;
96
+ /** Log an error-level message with an optional exception. */
97
+ error(template: string, properties?: Record<string, unknown>): void;
98
+ error(error: Error, template: string, properties?: Record<string, unknown>): void;
99
+ error<T extends Record<string, unknown>>(template: TypedTemplate<T>, properties: T): void;
100
+ error<T extends Record<string, unknown>>(error: Error, template: TypedTemplate<T>, properties: T): void;
101
+ /** Log a fatal-level message with an optional exception. */
102
+ fatal(template: string, properties?: Record<string, unknown>): void;
103
+ fatal(error: Error, template: string, properties?: Record<string, unknown>): void;
104
+ fatal<T extends Record<string, unknown>>(template: TypedTemplate<T>, properties: T): void;
105
+ fatal<T extends Record<string, unknown>>(error: Error, template: TypedTemplate<T>, properties: T): void;
106
+ /** Flushes all pending events through the pipeline to sinks. */
107
+ flush(): Promise<void>;
108
+ /** Flushes all sinks and releases resources. */
109
+ dispose(): Promise<void>;
110
+ [Symbol.asyncDispose](): Promise<void>;
111
+ }
@@ -0,0 +1,48 @@
1
+ import type { Enricher } from './Enricher.js';
2
+ import type { LogFilter } from './Filter.js';
3
+ import type { LogEvent } from './LogEvent.js';
4
+ import { type LogLevel, type LogLevelName } from './LogLevel.js';
5
+ import type { LogSink } from './Sink.js';
6
+ /**
7
+ * Configuration for the logger pipeline.
8
+ */
9
+ export interface PipelineConfig {
10
+ minimumLevel: LogLevel;
11
+ levelOverrides?: Record<string, LogLevelName>;
12
+ sinks: LogSink[];
13
+ enrichers?: Enricher[];
14
+ filters?: LogFilter[];
15
+ maxQueueSize?: number;
16
+ dropPolicy?: 'dropOldest' | 'dropNewest' | 'block';
17
+ }
18
+ /**
19
+ * Internal pipeline that processes log events asynchronously.
20
+ *
21
+ * Events are pushed into a queue and processed via microtask. The
22
+ * pipeline applies enrichers, filters, and level overrides before
23
+ * fanning out to all configured sinks.
24
+ */
25
+ export declare class LoggerPipeline {
26
+ #private;
27
+ constructor(config: PipelineConfig);
28
+ get minimumLevel(): LogLevel;
29
+ set minimumLevel(level: LogLevel);
30
+ /**
31
+ * Checks if the given level would pass the minimum level check
32
+ * for the given source context.
33
+ */
34
+ isEnabled(level: LogLevel, sourceContext?: string): boolean;
35
+ /**
36
+ * Enqueues a log event for processing.
37
+ * Fire-and-forget — callers never await.
38
+ */
39
+ push(event: LogEvent): void;
40
+ /**
41
+ * Forces all queued events through the pipeline and into sinks.
42
+ */
43
+ flush(): Promise<void>;
44
+ /**
45
+ * Flushes remaining events and disposes all sinks.
46
+ */
47
+ dispose(): Promise<void>;
48
+ }
@@ -0,0 +1,58 @@
1
+ import type { LogEvent } from './LogEvent.js';
2
+ import type { LogLevel } from './LogLevel.js';
3
+ interface TemplateToken {
4
+ type: 'text' | 'property';
5
+ value: string;
6
+ destructure?: boolean;
7
+ }
8
+ /**
9
+ * Parses a Serilog-style message template into tokens.
10
+ *
11
+ * Supports `{Property}` for scalar capture and `{@Property}` for
12
+ * destructuring (full object structure preserved in properties).
13
+ *
14
+ * @param template - message template string with `{Property}` holes
15
+ * @returns array of parsed tokens
16
+ */
17
+ export declare function parseTemplate(template: string): TemplateToken[];
18
+ /**
19
+ * Renders a parsed template into a human-readable string.
20
+ *
21
+ * For non-destructured properties, calls `toString()` if available on
22
+ * the value. For destructured properties (`{@Prop}`), uses JSON.stringify.
23
+ *
24
+ * @param tokens - parsed template tokens
25
+ * @param properties - property values to interpolate
26
+ * @returns the rendered message string
27
+ */
28
+ export declare function renderTemplate(tokens: TemplateToken[], properties: Record<string, unknown>): string;
29
+ /**
30
+ * Captures structured properties from the template, applying destructure
31
+ * semantics: `{@Prop}` keeps the full object, `{Prop}` calls `toString()`
32
+ * on objects that have a custom `toString`.
33
+ *
34
+ * @param tokens - parsed template tokens
35
+ * @param properties - raw property values
36
+ * @returns property bag with appropriate serialization applied
37
+ */
38
+ export declare function captureProperties(tokens: TemplateToken[], properties: Record<string, unknown>): Record<string, unknown>;
39
+ /**
40
+ * Generates a deterministic hex event ID from a message template string.
41
+ * Uses a simple FNV-1a hash for speed — no cryptographic requirements.
42
+ *
43
+ * @param template - the raw message template
44
+ * @returns 8-character hex hash
45
+ */
46
+ export declare function computeEventId(template: string): string;
47
+ /**
48
+ * Creates a complete `LogEvent` from a message template, properties,
49
+ * and metadata.
50
+ *
51
+ * @param level - severity level
52
+ * @param template - message template string with `{Property}` holes
53
+ * @param properties - structured property values
54
+ * @param exception - optional associated error
55
+ * @returns a fully populated `LogEvent`
56
+ */
57
+ export declare function createLogEvent(level: LogLevel, template: string, properties: Record<string, unknown>, exception?: Error): LogEvent;
58
+ export {};
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Internal diagnostic channel for the logging library's own errors.
3
+ *
4
+ * Sinks use this when they fail, instead of throwing and crashing
5
+ * the application. By default writes to `process.stderr`.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * SelfLog.setOutput(fs.createWriteStream('./logs/selflog.txt'));
10
+ * SelfLog.disable();
11
+ * ```
12
+ */
13
+ export declare const SelfLog: {
14
+ _enabled: boolean;
15
+ _output: null | {
16
+ write(s: string): void;
17
+ };
18
+ /**
19
+ * Writes an internal diagnostic message. Includes optional error details.
20
+ *
21
+ * @param message - diagnostic message
22
+ * @param error - optional associated error
23
+ */
24
+ write(message: string, error?: unknown): void;
25
+ /**
26
+ * Sets a custom output stream for self-diagnostics.
27
+ *
28
+ * @param output - writable stream with a `write` method
29
+ */
30
+ setOutput(output: {
31
+ write(s: string): void;
32
+ }): void;
33
+ /** Suppresses all internal diagnostic output. */
34
+ disable(): void;
35
+ /** Re-enables internal diagnostic output. */
36
+ enable(): void;
37
+ };
package/dist/Sink.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { LogEvent } from './LogEvent.js';
2
+ /**
3
+ * A sink that receives batches of log events for output.
4
+ *
5
+ * Sinks must implement `AsyncDisposable` for graceful shutdown.
6
+ * The `flush()` method is optional and forces immediate delivery
7
+ * of any buffered events.
8
+ */
9
+ export interface LogSink extends AsyncDisposable {
10
+ /** Write a batch of events to the output target. */
11
+ emit(events: LogEvent[]): Promise<void>;
12
+ /** Force immediate delivery of buffered events. */
13
+ flush?(): Promise<void>;
14
+ }
@@ -0,0 +1,3 @@
1
+ var r={Trace:0,Debug:1,Information:2,Warning:3,Error:4,Fatal:5},m={trace:r.Trace,debug:r.Debug,information:r.Information,warning:r.Warning,error:r.Error,fatal:r.Fatal},p={[r.Trace]:"trace",[r.Debug]:"debug",[r.Information]:"information",[r.Warning]:"warning",[r.Error]:"error",[r.Fatal]:"fatal"};function L(n){let t=n.toLowerCase(),a=m[t];if(a===void 0)throw new Error(`Invalid log level: "${n}". Valid levels: ${Object.keys(m).join(", ")}`);return a}function S(n){return p[n]??"information"}function k(n){switch(n){case r.Trace:return"TRC";case r.Debug:return"DBG";case r.Information:return"INF";case r.Warning:return"WRN";case r.Error:return"ERR";case r.Fatal:return"FTL";default:return"INF"}}function v(n,t){let a=t?.maxDepth??10,f=t?.maxStringLength??32768,s=new Set;return o(n,0);function o(e,u){if(e==null)return e;switch(typeof e){case"string":return e.length>f?e.slice(0,f)+"...(truncated)":e;case"number":case"boolean":return e;case"bigint":return e.toString();case"symbol":return`[Symbol: ${e.description??""}]`;case"function":return`[Function: ${e.name||"anonymous"}]`;case"object":return h(e,u);default:return String(e)}}function h(e,u){if(typeof Buffer<"u"&&Buffer.isBuffer(e))return`[Buffer(${e.length} bytes)]`;if(e instanceof Error){let l={name:e.name,message:e.message,stack:e.stack};for(let i of Object.getOwnPropertyNames(e))i!=="name"&&i!=="message"&&i!=="stack"&&(l[i]=o(e[i],u+1));return l}if(s.has(e))return"[Circular]";if(u>=a)return Array.isArray(e)?"[Array]":"[Object]";s.add(e);try{if(Array.isArray(e))return e.map(i=>o(i,u+1));if(e instanceof Date)return e.toISOString();if(e instanceof RegExp)return e.toString();if(e instanceof Map){let i={};for(let[d,y]of e)i[String(d)]=o(y,u+1);return i}if(e instanceof Set)return[...e].map(i=>o(i,u+1));let l={};for(let i of Object.keys(e))l[i]=o(e[i],u+1);return l}finally{s.delete(e)}}}var c={_enabled:!0,_output:null,write(n,t){if(!this._enabled)return;let a=new Date().toISOString(),f=t instanceof Error?` ${t.stack??t.message}`:t!==void 0?` ${String(t)}`:"",s=`[${a} SelfLog] ${n}${f}
2
+ `;this._output?this._output.write(s):typeof process<"u"&&process.stderr&&process.stderr.write(s)},setOutput(n){this._output=n},disable(){this._enabled=!1},enable(){this._enabled=!0}};var g=class{#a;#o;#c;#f;#i;#l;#h;#e=[];#t;#n=0;#s=!1;#r;#m=!1;constructor(t){this.#a=t.emit,this.#o=t.batchSize??100,this.#c=t.flushInterval??2e3,this.#f=t.maxQueueSize??5e4,this.#i=t.maxRetries??5,this.#l=t.retryDelay??1e3,this.#h=t.circuitBreakerThreshold??3}async emit(t){if(!this.#m){for(let a of t)this.#e.length>=this.#f&&this.#e.shift(),this.#e.push(a);this.#e.length>=this.#o?await this.flush():this.#g()}}async flush(){for(this.#u();this.#e.length>0;){if(this.#s){c.write("BatchingSink circuit breaker open \u2014 deferring flush");return}let t=this.#e.splice(0,this.#o),a,f=!1;for(let s=0;s<=this.#i;s++)try{await this.#a(t),this.#n=0,f=!0;break}catch(o){if(a=o,s<this.#i){let h=this.#l*2**s;await new Promise(e=>setTimeout(e,h))}}if(!f){this.#n++,c.write(`BatchingSink flush failed after ${this.#i+1} attempts`,a),this.#n>=this.#h&&(this.#s=!0,c.write("BatchingSink circuit breaker opened \u2014 backing off 30s"),this.#r=setTimeout(()=>{this.#s=!1,this.#n=0,c.write("BatchingSink circuit breaker closed"),this.#e.length>0&&this.flush().catch(s=>{c.write("BatchingSink post-circuit flush failed",s)})},3e4),this.#r.unref&&this.#r.unref());return}}}async[Symbol.asyncDispose](){if(this.#m=!0,this.#u(),this.#r&&clearTimeout(this.#r),this.#e.length>0&&!this.#s)try{await this.#a(this.#e.splice(0,this.#e.length))}catch(t){c.write("BatchingSink dispose flush failed",t)}}#g(){this.#u(),this.#t=setTimeout(()=>{this.flush().catch(t=>{c.write("BatchingSink timer flush failed",t)})},this.#c),this.#t.unref&&this.#t.unref()}#u(){this.#t&&(clearTimeout(this.#t),this.#t=void 0)}};export{r as a,L as b,S as c,k as d,v as e,c as f,g};
3
+ //# sourceMappingURL=chunk-EU6TDBKQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/LogLevel.ts","../src/serialization.ts","../src/SelfLog.ts","../src/sinks/BatchingSink.ts"],"sourcesContent":["/**\n * Log severity levels as a const object.\n *\n * Use as values (`LogLevel.Information`) or as a type (`LogLevel`).\n * Levels are ordered numerically — higher values indicate greater severity.\n *\n * @example\n * ```ts\n * if (logger.isEnabled(LogLevel.Debug)) {\n * logger.debug('Expensive computation: {@Result}', { Result: compute() });\n * }\n * ```\n */\nexport const LogLevel = {\n Trace: 0,\n Debug: 1,\n Information: 2,\n Warning: 3,\n Error: 4,\n Fatal: 5\n} as const;\n\nexport type LogLevel = (typeof LogLevel)[keyof typeof LogLevel];\n\n/**\n * Log level name strings for configuration and display.\n */\nexport type LogLevelName =\n | 'trace'\n | 'debug'\n | 'information'\n | 'warning'\n | 'error'\n | 'fatal';\n\nconst levelNameMap: Record<LogLevelName, LogLevel> = {\n trace: LogLevel.Trace,\n debug: LogLevel.Debug,\n information: LogLevel.Information,\n warning: LogLevel.Warning,\n error: LogLevel.Error,\n fatal: LogLevel.Fatal\n};\n\nconst levelStringMap: Record<LogLevel, LogLevelName> = {\n [LogLevel.Trace]: 'trace',\n [LogLevel.Debug]: 'debug',\n [LogLevel.Information]: 'information',\n [LogLevel.Warning]: 'warning',\n [LogLevel.Error]: 'error',\n [LogLevel.Fatal]: 'fatal'\n};\n\n/**\n * Parses a log level name string to a `LogLevel` numeric value.\n *\n * @param name - case-insensitive level name\n * @returns the numeric `LogLevel` value\n */\nexport function parseLogLevel(name: string): LogLevel {\n const normalized = name.toLowerCase() as LogLevelName;\n const level = levelNameMap[normalized];\n if (level === undefined) {\n throw new Error(\n `Invalid log level: \"${name}\". Valid levels: ${Object.keys(levelNameMap).join(', ')}`\n );\n }\n return level;\n}\n\n/**\n * Converts a `LogLevel` numeric value to its string name.\n *\n * @param level - the numeric log level\n * @returns the level name string\n */\nexport function levelToString(level: LogLevel): LogLevelName {\n return levelStringMap[level] ?? 'information';\n}\n\n/**\n * Three-letter abbreviation for display in console output.\n */\nexport function levelToShortString(level: LogLevel): string {\n switch (level) {\n case LogLevel.Trace:\n return 'TRC';\n case LogLevel.Debug:\n return 'DBG';\n case LogLevel.Information:\n return 'INF';\n case LogLevel.Warning:\n return 'WRN';\n case LogLevel.Error:\n return 'ERR';\n case LogLevel.Fatal:\n return 'FTL';\n default:\n return 'INF';\n }\n}\n","/**\n * Options for the safe serializer.\n */\nexport interface SerializationOptions {\n /** Maximum nesting depth for objects/arrays. @default 10 */\n maxDepth?: number;\n /** Maximum length for string values before truncation. @default 32768 */\n maxStringLength?: number;\n}\n\nconst DEFAULT_MAX_DEPTH = 10;\nconst DEFAULT_MAX_STRING_LENGTH = 32_768;\n\n/**\n * Safely serializes a value for structured logging, handling:\n * - Circular references → `\"[Circular]\"`\n * - Depth limits → `\"[Object]\"` or `\"[Array]\"`\n * - BigInt → string representation\n * - Buffer → `\"[Buffer(N bytes)]\"`\n * - Functions → `\"[Function: name]\"`\n * - Symbols → `\"[Symbol: description]\"`\n * - Error objects → `{ message, stack, name, ...ownProperties }`\n * - Long strings → truncated with `\"...(truncated)\"`\n *\n * @param value - the value to serialize\n * @param options - serialization limits\n * @returns a JSON-safe representation of the value\n */\nexport function safeSerialize(\n value: unknown,\n options?: SerializationOptions\n): unknown {\n const maxDepth = options?.maxDepth ?? DEFAULT_MAX_DEPTH;\n const maxStringLength =\n options?.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH;\n const seen = new Set<object>();\n\n return serialize(value, 0);\n\n function serialize(val: unknown, depth: number): unknown {\n if (val === null || val === undefined) {\n return val;\n }\n\n switch (typeof val) {\n case 'string':\n if (val.length > maxStringLength) {\n return val.slice(0, maxStringLength) + '...(truncated)';\n }\n return val;\n\n case 'number':\n case 'boolean':\n return val;\n\n case 'bigint':\n return val.toString();\n\n case 'symbol':\n return `[Symbol: ${val.description ?? ''}]`;\n\n case 'function':\n return `[Function: ${val.name || 'anonymous'}]`;\n\n case 'object':\n return serializeObject(val as object, depth);\n\n default:\n return String(val);\n }\n }\n\n function serializeObject(obj: object, depth: number): unknown {\n // Buffer check\n if (typeof Buffer !== 'undefined' && Buffer.isBuffer(obj)) {\n return `[Buffer(${obj.length} bytes)]`;\n }\n\n // Error objects — extract structured data\n if (obj instanceof Error) {\n const result: Record<string, unknown> = {\n name: obj.name,\n message: obj.message,\n stack: obj.stack\n };\n for (const key of Object.getOwnPropertyNames(obj)) {\n if (key !== 'name' && key !== 'message' && key !== 'stack') {\n result[key] = serialize((obj as any)[key], depth + 1);\n }\n }\n return result;\n }\n\n // Circular reference check\n if (seen.has(obj)) {\n return '[Circular]';\n }\n\n // Depth limit\n if (depth >= maxDepth) {\n return Array.isArray(obj) ? '[Array]' : '[Object]';\n }\n\n seen.add(obj);\n\n try {\n if (Array.isArray(obj)) {\n return obj.map(item => serialize(item, depth + 1));\n }\n\n // Date objects\n if (obj instanceof Date) {\n return obj.toISOString();\n }\n\n // RegExp\n if (obj instanceof RegExp) {\n return obj.toString();\n }\n\n // Map\n if (obj instanceof Map) {\n const result: Record<string, unknown> = {};\n for (const [key, val] of obj) {\n result[String(key)] = serialize(val, depth + 1);\n }\n return result;\n }\n\n // Set\n if (obj instanceof Set) {\n return [...obj].map(item => serialize(item, depth + 1));\n }\n\n // Plain objects\n const result: Record<string, unknown> = {};\n for (const key of Object.keys(obj)) {\n result[key] = serialize((obj as any)[key], depth + 1);\n }\n return result;\n } finally {\n seen.delete(obj);\n }\n }\n}\n","/**\n * Internal diagnostic channel for the logging library's own errors.\n *\n * Sinks use this when they fail, instead of throwing and crashing\n * the application. By default writes to `process.stderr`.\n *\n * @example\n * ```ts\n * SelfLog.setOutput(fs.createWriteStream('./logs/selflog.txt'));\n * SelfLog.disable();\n * ```\n */\nexport const SelfLog = {\n _enabled: true,\n _output: null as null | { write(s: string): void },\n\n /**\n * Writes an internal diagnostic message. Includes optional error details.\n *\n * @param message - diagnostic message\n * @param error - optional associated error\n */\n write(message: string, error?: unknown): void {\n if (!this._enabled) return;\n const timestamp = new Date().toISOString();\n const errorStr =\n error instanceof Error\n ? ` ${error.stack ?? error.message}`\n : error !== undefined\n ? ` ${String(error)}`\n : '';\n const line = `[${timestamp} SelfLog] ${message}${errorStr}\\n`;\n if (this._output) {\n this._output.write(line);\n } else if (typeof process !== 'undefined' && process.stderr) {\n process.stderr.write(line);\n }\n },\n\n /**\n * Sets a custom output stream for self-diagnostics.\n *\n * @param output - writable stream with a `write` method\n */\n setOutput(output: { write(s: string): void }): void {\n this._output = output;\n },\n\n /** Suppresses all internal diagnostic output. */\n disable(): void {\n this._enabled = false;\n },\n\n /** Re-enables internal diagnostic output. */\n enable(): void {\n this._enabled = true;\n }\n};\n","import type { LogEvent } from '../LogEvent.js';\nimport { SelfLog } from '../SelfLog.js';\nimport type { LogSink } from '../Sink.js';\n\n/**\n * Configuration for the batching sink wrapper.\n */\nexport interface BatchingSinkOptions {\n /** Flush after this many events. @default 100 */\n batchSize?: number;\n /** Flush after this many milliseconds of inactivity. @default 2000 */\n flushInterval?: number;\n /** Maximum buffered events before dropping. @default 50000 */\n maxQueueSize?: number;\n /** Maximum retry attempts for failed flushes. @default 5 */\n maxRetries?: number;\n /** Initial retry delay in ms (exponential backoff). @default 1000 */\n retryDelay?: number;\n /** Consecutive failures before circuit breaker opens. @default 3 */\n circuitBreakerThreshold?: number;\n /** The emit function that writes a batch to the target. */\n emit: (batch: LogEvent[]) => Promise<void>;\n}\n\n/**\n * Production-grade batching wrapper for log sinks.\n *\n * Provides buffering, retry with exponential backoff, and circuit\n * breaking. All network/file sinks use this internally.\n *\n * @example\n * ```ts\n * const sink = new BatchingSink({\n * batchSize: 100,\n * flushInterval: 2000,\n * emit: async (batch) => {\n * await fetch('/logs', { method: 'POST', body: JSON.stringify(batch) });\n * },\n * });\n * ```\n */\nexport class BatchingSink implements LogSink {\n readonly #emitFn: (batch: LogEvent[]) => Promise<void>;\n readonly #batchSize: number;\n readonly #flushInterval: number;\n readonly #maxQueueSize: number;\n readonly #maxRetries: number;\n readonly #retryDelay: number;\n readonly #circuitBreakerThreshold: number;\n readonly #buffer: LogEvent[] = [];\n #timer: ReturnType<typeof setTimeout> | undefined;\n #consecutiveFailures = 0;\n #circuitOpen = false;\n #circuitResetTimer: ReturnType<typeof setTimeout> | undefined;\n #disposed = false;\n\n constructor(options: BatchingSinkOptions) {\n this.#emitFn = options.emit;\n this.#batchSize = options.batchSize ?? 100;\n this.#flushInterval = options.flushInterval ?? 2_000;\n this.#maxQueueSize = options.maxQueueSize ?? 50_000;\n this.#maxRetries = options.maxRetries ?? 5;\n this.#retryDelay = options.retryDelay ?? 1_000;\n this.#circuitBreakerThreshold = options.circuitBreakerThreshold ?? 3;\n }\n\n async emit(events: LogEvent[]): Promise<void> {\n if (this.#disposed) return;\n\n for (const event of events) {\n if (this.#buffer.length >= this.#maxQueueSize) {\n this.#buffer.shift(); // drop oldest\n }\n this.#buffer.push(event);\n }\n\n if (this.#buffer.length >= this.#batchSize) {\n await this.flush();\n } else {\n this.#resetTimer();\n }\n }\n\n async flush(): Promise<void> {\n this.#clearTimer();\n\n while (this.#buffer.length > 0) {\n if (this.#circuitOpen) {\n SelfLog.write(\n 'BatchingSink circuit breaker open — deferring flush'\n );\n return;\n }\n\n const batch = this.#buffer.splice(0, this.#batchSize);\n\n let lastError: unknown;\n let emitted = false;\n for (let attempt = 0; attempt <= this.#maxRetries; attempt++) {\n try {\n await this.#emitFn(batch);\n this.#consecutiveFailures = 0;\n emitted = true;\n break;\n } catch (err) {\n lastError = err;\n if (attempt < this.#maxRetries) {\n const delay = this.#retryDelay * 2 ** attempt;\n await new Promise(r => setTimeout(r, delay));\n }\n }\n }\n\n if (!emitted) {\n this.#consecutiveFailures++;\n SelfLog.write(\n `BatchingSink flush failed after ${this.#maxRetries + 1} attempts`,\n lastError\n );\n\n if (\n this.#consecutiveFailures >= this.#circuitBreakerThreshold\n ) {\n this.#circuitOpen = true;\n SelfLog.write(\n `BatchingSink circuit breaker opened — backing off 30s`\n );\n this.#circuitResetTimer = setTimeout(() => {\n this.#circuitOpen = false;\n this.#consecutiveFailures = 0;\n SelfLog.write('BatchingSink circuit breaker closed');\n if (this.#buffer.length > 0) {\n this.flush().catch(err => {\n SelfLog.write(\n 'BatchingSink post-circuit flush failed',\n err\n );\n });\n }\n }, 30_000);\n if (this.#circuitResetTimer.unref) {\n this.#circuitResetTimer.unref();\n }\n }\n return;\n }\n }\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n this.#disposed = true;\n this.#clearTimer();\n if (this.#circuitResetTimer) {\n clearTimeout(this.#circuitResetTimer);\n }\n // Flush remaining events (best-effort)\n if (this.#buffer.length > 0 && !this.#circuitOpen) {\n try {\n await this.#emitFn(this.#buffer.splice(0, this.#buffer.length));\n } catch (err) {\n SelfLog.write('BatchingSink dispose flush failed', err);\n }\n }\n }\n\n #resetTimer(): void {\n this.#clearTimer();\n this.#timer = setTimeout(() => {\n this.flush().catch(err => {\n SelfLog.write('BatchingSink timer flush failed', err);\n });\n }, this.#flushInterval);\n if (this.#timer.unref) {\n this.#timer.unref();\n }\n }\n\n #clearTimer(): void {\n if (this.#timer) {\n clearTimeout(this.#timer);\n this.#timer = undefined;\n }\n }\n}\n"],"mappings":"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","names":["LogLevel","levelNameMap","levelStringMap","parseLogLevel","name","normalized","level","levelToString","levelToShortString","safeSerialize","value","options","maxDepth","maxStringLength","seen","serialize","val","depth","serializeObject","obj","result","key","item","SelfLog","message","error","timestamp","errorStr","line","output","BatchingSink","#emitFn","#batchSize","#flushInterval","#maxQueueSize","#maxRetries","#retryDelay","#circuitBreakerThreshold","#buffer","#timer","#consecutiveFailures","#circuitOpen","#circuitResetTimer","#disposed","options","events","event","#resetTimer","#clearTimer","SelfLog","batch","lastError","emitted","attempt","err","delay","r"]}
@@ -0,0 +1 @@
1
+ export { type ClickHouseColumnMapping, type ClickHouseSinkOptions, type CreateLogsTableOptions, clickHouseSink, createLogsTable } from './sinks/ClickHouseSink.js';
@@ -0,0 +1,16 @@
1
+ import{c as l,e as p,g as c}from"./chunk-EU6TDBKQ.js";var g={timestamp:"timestamp",level:"level",messageTemplate:"message_template",renderedMessage:"rendered_message",properties:"properties",exception:"exception",correlationId:"correlation_id",traceId:"trace_id",spanId:"span_id",sourceContext:"source_context"};async function m(i,t,r){let a=r?.engine??"MergeTree",n=r?.partitionBy??"toYYYYMM(timestamp)",s=r?.orderBy??["timestamp","level"],o=r?.ttl,e=`CREATE TABLE IF NOT EXISTS ${t} (
2
+ timestamp DateTime64(3, 'UTC'),
3
+ level LowCardinality(String),
4
+ message_template String,
5
+ rendered_message String,
6
+ source_context LowCardinality(String) DEFAULT '',
7
+ properties String DEFAULT '{}',
8
+ exception Nullable(String),
9
+ correlation_id Nullable(String),
10
+ trace_id Nullable(String),
11
+ span_id Nullable(String)
12
+ ) ENGINE = ${a}
13
+ PARTITION BY ${n}
14
+ ORDER BY (${s.join(", ")})`;o&&(e+=`
15
+ TTL ${o}`),await i.raw(e)}function d(i){let t={...g,...i.columns},r=i.table,a=i.connection,n=new c({batchSize:i.batchSize??1e3,flushInterval:i.flushInterval??5e3,emit:async s=>{let o=s.map(e=>({[t.timestamp]:e.timestamp,[t.level]:l(e.level),[t.messageTemplate]:e.messageTemplate,[t.renderedMessage]:e.renderedMessage,[t.sourceContext]:e.properties.SourceContext??"",[t.properties]:JSON.stringify(p(e.properties)),[t.exception]:e.exception?.stack??e.exception?.message??null,[t.correlationId]:e.properties.CorrelationId??null,[t.traceId]:e.properties.TraceId??null,[t.spanId]:e.properties.SpanId??null}));await a(r).insert(o)}});return{async emit(s){await n.emit(s)},async flush(){await n.flush()},async[Symbol.asyncDispose](){await n[Symbol.asyncDispose]()}}}export{d as clickHouseSink,m as createLogsTable};
16
+ //# sourceMappingURL=clickhouse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sinks/ClickHouseSink.ts"],"sourcesContent":["import type { LogEvent } from '../LogEvent.js';\nimport { levelToString } from '../LogLevel.js';\nimport type { LogSink } from '../Sink.js';\nimport { safeSerialize } from '../serialization.js';\nimport { BatchingSink } from './BatchingSink.js';\n\n/**\n * Column mapping for the ClickHouse logs table.\n */\nexport interface ClickHouseColumnMapping {\n timestamp?: string;\n level?: string;\n messageTemplate?: string;\n renderedMessage?: string;\n properties?: string;\n exception?: string;\n correlationId?: string;\n traceId?: string;\n spanId?: string;\n sourceContext?: string;\n}\n\n/**\n * ClickHouse sink configuration.\n */\nexport interface ClickHouseSinkOptions {\n /** Knex-ClickHouse connection instance. */\n connection: any;\n /** Table name to insert into. */\n table: string;\n /** Events per batch. @default 1000 */\n batchSize?: number;\n /** Max milliseconds between flushes. @default 5000 */\n flushInterval?: number;\n /** Custom column mapping. */\n columns?: ClickHouseColumnMapping;\n}\n\n/**\n * Options for creating the ClickHouse logs table.\n */\nexport interface CreateLogsTableOptions {\n /** Table engine. @default 'MergeTree' */\n engine?: string;\n /** Partition expression. @default \"toYYYYMM(timestamp)\" */\n partitionBy?: string;\n /** ORDER BY columns. @default ['timestamp', 'level'] */\n orderBy?: string[];\n /** TTL expression for automatic data expiration. */\n ttl?: string;\n}\n\nconst DEFAULT_COLUMNS: Required<ClickHouseColumnMapping> = {\n timestamp: 'timestamp',\n level: 'level',\n messageTemplate: 'message_template',\n renderedMessage: 'rendered_message',\n properties: 'properties',\n exception: 'exception',\n correlationId: 'correlation_id',\n traceId: 'trace_id',\n spanId: 'span_id',\n sourceContext: 'source_context'\n};\n\n/**\n * Creates the ClickHouse logs table with a recommended schema.\n *\n * @param connection - knex-clickhouse connection\n * @param tableName - name for the logs table\n * @param options - DDL options (engine, partitioning, TTL)\n *\n * @example\n * ```ts\n * await createLogsTable(ch, 'application_logs', {\n * ttl: 'timestamp + INTERVAL 90 DAY',\n * });\n * ```\n */\nexport async function createLogsTable(\n connection: any,\n tableName: string,\n options?: CreateLogsTableOptions\n): Promise<void> {\n const engine = options?.engine ?? 'MergeTree';\n const partitionBy = options?.partitionBy ?? 'toYYYYMM(timestamp)';\n const orderBy = options?.orderBy ?? ['timestamp', 'level'];\n const ttl = options?.ttl;\n\n let ddl = `CREATE TABLE IF NOT EXISTS ${tableName} (\n timestamp DateTime64(3, 'UTC'),\n level LowCardinality(String),\n message_template String,\n rendered_message String,\n source_context LowCardinality(String) DEFAULT '',\n properties String DEFAULT '{}',\n exception Nullable(String),\n correlation_id Nullable(String),\n trace_id Nullable(String),\n span_id Nullable(String)\n) ENGINE = ${engine}\nPARTITION BY ${partitionBy}\nORDER BY (${orderBy.join(', ')})`;\n\n if (ttl) {\n ddl += `\\nTTL ${ttl}`;\n }\n\n await connection.raw(ddl);\n}\n\n/**\n * Creates a ClickHouse sink that batch inserts log events.\n *\n * Ships as a separate entrypoint (`@cleverbrush/log/clickhouse`) to\n * avoid forcing `@cleverbrush/knex-clickhouse` as a dependency.\n *\n * @param options - ClickHouse connection and batching configuration\n * @returns a `LogSink` that batch inserts into ClickHouse\n *\n * @example\n * ```ts\n * const sink = clickHouseSink({\n * connection: ch,\n * table: 'application_logs',\n * batchSize: 1000,\n * });\n * ```\n */\nexport function clickHouseSink(options: ClickHouseSinkOptions): LogSink {\n const cols = { ...DEFAULT_COLUMNS, ...options.columns };\n const table = options.table;\n const conn = options.connection;\n\n const batcher = new BatchingSink({\n batchSize: options.batchSize ?? 1_000,\n flushInterval: options.flushInterval ?? 5_000,\n emit: async (batch: LogEvent[]) => {\n const rows = batch.map(event => ({\n [cols.timestamp]: event.timestamp,\n [cols.level]: levelToString(event.level),\n [cols.messageTemplate]: event.messageTemplate,\n [cols.renderedMessage]: event.renderedMessage,\n [cols.sourceContext]:\n (event.properties.SourceContext as string) ?? '',\n [cols.properties]: JSON.stringify(\n safeSerialize(event.properties)\n ),\n [cols.exception]:\n event.exception?.stack ?? event.exception?.message ?? null,\n [cols.correlationId]:\n (event.properties.CorrelationId as string) ?? null,\n [cols.traceId]: (event.properties.TraceId as string) ?? null,\n [cols.spanId]: (event.properties.SpanId as string) ?? null\n }));\n\n await conn(table).insert(rows);\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"],"mappings":"sDAoDA,IAAMA,EAAqD,CACvD,UAAW,YACX,MAAO,QACP,gBAAiB,mBACjB,gBAAiB,mBACjB,WAAY,aACZ,UAAW,YACX,cAAe,iBACf,QAAS,WACT,OAAQ,UACR,cAAe,gBACnB,EAgBA,eAAsBC,EAClBC,EACAC,EACAC,EACa,CACb,IAAMC,EAASD,GAAS,QAAU,YAC5BE,EAAcF,GAAS,aAAe,sBACtCG,EAAUH,GAAS,SAAW,CAAC,YAAa,OAAO,EACnDI,EAAMJ,GAAS,IAEjBK,EAAM,8BAA8BN,CAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aAWxCE,CAAM;AAAA,eACJC,CAAW;AAAA,YACdC,EAAQ,KAAK,IAAI,CAAC,IAEtBC,IACAC,GAAO;AAAA,MAASD,CAAG,IAGvB,MAAMN,EAAW,IAAIO,CAAG,CAC5B,CAoBO,SAASC,EAAeN,EAAyC,CACpE,IAAMO,EAAO,CAAE,GAAGX,EAAiB,GAAGI,EAAQ,OAAQ,EAChDQ,EAAQR,EAAQ,MAChBS,EAAOT,EAAQ,WAEfU,EAAU,IAAIC,EAAa,CAC7B,UAAWX,EAAQ,WAAa,IAChC,cAAeA,EAAQ,eAAiB,IACxC,KAAM,MAAOY,GAAsB,CAC/B,IAAMC,EAAOD,EAAM,IAAIE,IAAU,CAC7B,CAACP,EAAK,SAAS,EAAGO,EAAM,UACxB,CAACP,EAAK,KAAK,EAAGQ,EAAcD,EAAM,KAAK,EACvC,CAACP,EAAK,eAAe,EAAGO,EAAM,gBAC9B,CAACP,EAAK,eAAe,EAAGO,EAAM,gBAC9B,CAACP,EAAK,aAAa,EACdO,EAAM,WAAW,eAA4B,GAClD,CAACP,EAAK,UAAU,EAAG,KAAK,UACpBS,EAAcF,EAAM,UAAU,CAClC,EACA,CAACP,EAAK,SAAS,EACXO,EAAM,WAAW,OAASA,EAAM,WAAW,SAAW,KAC1D,CAACP,EAAK,aAAa,EACdO,EAAM,WAAW,eAA4B,KAClD,CAACP,EAAK,OAAO,EAAIO,EAAM,WAAW,SAAsB,KACxD,CAACP,EAAK,MAAM,EAAIO,EAAM,WAAW,QAAqB,IAC1D,EAAE,EAEF,MAAML,EAAKD,CAAK,EAAE,OAAOK,CAAI,CACjC,CACJ,CAAC,EAED,MAAO,CACH,MAAM,KAAKI,EAAmC,CAC1C,MAAMP,EAAQ,KAAKO,CAAM,CAC7B,EAEA,MAAM,OAAuB,CACzB,MAAMP,EAAQ,MAAM,CACxB,EAEA,MAAO,OAAO,YAAY,GAAmB,CACzC,MAAMA,EAAQ,OAAO,YAAY,EAAE,CACvC,CACJ,CACJ","names":["DEFAULT_COLUMNS","createLogsTable","connection","tableName","options","engine","partitionBy","orderBy","ttl","ddl","clickHouseSink","cols","table","conn","batcher","BatchingSink","batch","rows","event","levelToString","safeSerialize","events"]}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Generates a new correlation ID using UUID v7 (time-sortable).
3
+ *
4
+ * Falls back to UUID v4 if the runtime doesn't support v7.
5
+ *
6
+ * @returns a new UUID string suitable for correlation
7
+ */
8
+ export declare function generateCorrelationId(): string;
9
+ /**
10
+ * Extracts a correlation ID from incoming HTTP request headers.
11
+ *
12
+ * Checks headers in priority order:
13
+ * 1. `X-Correlation-Id`
14
+ * 2. `X-Request-Id`
15
+ * 3. `traceparent` (W3C Trace Context — extracts the trace-id segment)
16
+ *
17
+ * If no header is found, generates a new correlation ID.
18
+ *
19
+ * @param headers - request headers object
20
+ * @returns a correlation ID string
21
+ */
22
+ export declare function extractCorrelationId(headers: Record<string, string | string[] | undefined>): string;