@beignet/core
Version:
Core framework primitives for Beignet
124 lines (116 loc) • 3.3 kB
text/typescript
/**
* Supported structured logger levels.
*/
export type LogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal";
/**
* App-facing structured logger port.
*
* Application code logs through this interface so production can use Pino,
* Datadog, or another adapter while tests can use no-op or memory loggers.
*/
export interface LoggerPort {
/**
* Log very detailed diagnostic information.
*/
trace(message: string, meta?: Record<string, unknown>): void;
/**
* Log debug-level diagnostic information.
*/
debug(message: string, meta?: Record<string, unknown>): void;
/**
* Log normal application progress.
*/
info(message: string, meta?: Record<string, unknown>): void;
/**
* Log recoverable problems or unusual conditions.
*/
warn(message: string, meta?: Record<string, unknown>): void;
/**
* Log failed operations.
*/
error(message: string, meta?: Record<string, unknown>): void;
/**
* Log unrecoverable failures.
*/
fatal(message: string, meta?: Record<string, unknown>): void;
/**
* Return a logger with additional structured bindings.
*/
child(bindings: Record<string, unknown>): LoggerPort;
}
/**
* Captured entry from `createMemoryLogger(...)`.
*/
export interface MemoryLogEntry {
level: LogLevel;
message: string;
meta?: Record<string, unknown>;
bindings: Record<string, unknown>;
}
/**
* In-memory logger port used by tests and local assertions.
*/
export interface MemoryLoggerPort extends LoggerPort {
/**
* Captured log entries in call order.
*/
entries: MemoryLogEntry[];
}
/**
* Create a logger that discards every log call.
*
* Use this in tests where logging is irrelevant.
*
* @returns A logger port whose methods are no-ops.
*/
export function createNoopLogger(): LoggerPort {
const logger: LoggerPort = {
trace: () => {},
debug: () => {},
info: () => {},
warn: () => {},
error: () => {},
fatal: () => {},
child: () => logger,
};
return logger;
}
/**
* Create a logger that captures entries in memory.
*
* Child loggers inherit existing bindings and append new bindings to each
* captured entry.
*
* @param bindings - Structured bindings attached to every captured entry.
* @param entries - Optional shared entry array.
* @returns A logger port with an inspectable `entries` array.
*/
export function createMemoryLogger(
bindings: Record<string, unknown> = {},
entries: MemoryLogEntry[] = [],
): MemoryLoggerPort {
const capturedBindings = { ...bindings };
const record = (
level: LogLevel,
message: string,
meta?: Record<string, unknown>,
) => {
entries.push({
level,
message,
meta: meta ? { ...meta } : undefined,
bindings: { ...capturedBindings },
});
};
return {
entries,
trace: (message, meta) => record("trace", message, meta),
debug: (message, meta) => record("debug", message, meta),
info: (message, meta) => record("info", message, meta),
warn: (message, meta) => record("warn", message, meta),
error: (message, meta) => record("error", message, meta),
fatal: (message, meta) => record("fatal", message, meta),
child: (childBindings) =>
createMemoryLogger({ ...capturedBindings, ...childBindings }, entries),
};
}