This commit is contained in:
IanKulin
2025-09-28 09:37:20 +08:00
parent ec78edbbc6
commit 8af3d823c5

View File

@@ -1,3 +1,29 @@
/**
* @fileoverview A comprehensive Deno-based logging library with configurable levels, formatting, and caller detection.
*
* This module provides a Logger class that supports multiple log levels, JSON and simple text formatting,
* automatic TTY detection for colored output, and optional caller information inclusion. It includes
* built-in util.format-style string formatting with %s, %d, %j, and other specifiers.
*
* @module logger
* @example
* ```ts
* import Logger from "@iankulin/logger";
*
* // Create a basic logger
* const logger = new Logger({ level: "info", format: "json" });
*
* // Log messages with different levels
* logger.info("User %s logged in", "alice");
* logger.warn("High memory usage: %d%%", 89);
* logger.error("Database connection failed: %j", { host: "localhost", port: 5432 });
*
* // Change log level dynamically
* logger.level("debug");
* logger.debug("This will now be shown");
* ```
*/
// Native implementation of util.format functionality // Native implementation of util.format functionality
function format(f: unknown, ...args: unknown[]): string { function format(f: unknown, ...args: unknown[]): string {
if (typeof f !== "string") { if (typeof f !== "string") {
@@ -109,8 +135,21 @@ function format(f: unknown, ...args: unknown[]): string {
return str; return str;
} }
/**
* Available log levels in order of priority.
*
* - `silent`: No logging output
* - `error`: Only error messages
* - `warn`: Error and warning messages
* - `info`: Error, warning, and info messages (default)
* - `debug`: All messages including debug output
*/
export type LogLevel = "silent" | "error" | "warn" | "info" | "debug"; export type LogLevel = "silent" | "error" | "warn" | "info" | "debug";
/**
* Numeric mapping for log levels used internally for level comparison.
* Lower numbers have higher priority (are shown more often).
*/
export interface LogLevels { export interface LogLevels {
[level: string]: number; [level: string]: number;
silent: -1; silent: -1;
@@ -120,6 +159,10 @@ export interface LogLevels {
debug: 3; debug: 3;
} }
/**
* ANSI color codes for different log levels.
* Only used when output is to a terminal (TTY), not when redirected to files.
*/
export interface Colours { export interface Colours {
[level: string]: string; [level: string]: string;
error: string; error: string;
@@ -129,29 +172,137 @@ export interface Colours {
reset: string; reset: string;
} }
/**
* Structure of a log entry passed to formatters.
*
* This interface defines the shape of log objects that are passed to custom formatters.
* All standard properties are always present, with optional caller information included
* based on the {@link LoggerOptions.callerLevel} setting.
*
* @example
* ```ts
* const entry: LogEntry = {
* level: "info",
* levelNumber: 2,
* time: "2024-01-15 10:30",
* pid: 1234,
* hostname: "localhost",
* msg: "User alice logged in",
* callerFile: "/path/to/app.ts",
* callerLine: 42
* };
* ```
*/
export interface LogEntry { export interface LogEntry {
/** The log level as a string (e.g., "info", "error") */
level: string; level: string;
/** The numeric value of the log level for comparison */
levelNumber: number; levelNumber: number;
/** Formatted timestamp string */
time: string; time: string;
/** Process ID of the current Deno process */
pid: number; pid: number;
/** Hostname of the current machine */
hostname: string; hostname: string;
/** The final formatted log message */
msg: string; msg: string;
/** Source file path (included based on callerLevel setting) */
callerFile?: string; callerFile?: string;
/** Line number in source file (included based on callerLevel setting) */
callerLine?: number; callerLine?: number;
/** Additional custom properties can be added by formatters */
[key: string]: unknown; [key: string]: unknown;
} }
/**
* Custom formatter function type for log entries.
*
* @param logEntry - The log entry object to format
* @returns A formatted string representation of the log entry
*
* @example
* ```ts
* const customFormatter: Formatter = (entry) => {
* return `${entry.time} [${entry.level.toUpperCase()}] ${entry.msg}`;
* };
* ```
*/
export type Formatter = (logEntry: LogEntry) => string; export type Formatter = (logEntry: LogEntry) => string;
/**
* Configuration options for the Logger constructor.
*
* @example
* ```ts
* // Basic configuration
* const logger = new Logger({
* level: "debug",
* format: "simple"
* });
*
* // Advanced configuration with caller detection
* const logger = new Logger({
* level: "info",
* format: "json",
* time: "long",
* callerLevel: "warn",
* colours: {
* error: "\x1b[91m",
* info: "\x1b[94m"
* }
* });
* ```
*/
export interface LoggerOptions { export interface LoggerOptions {
/** Minimum log level to output. Defaults to "info" */
level?: LogLevel; level?: LogLevel;
/** Custom level mappings. Partial override of default levels */
levels?: Partial<LogLevels>; levels?: Partial<LogLevels>;
/** Output format: "json" for structured logs, "simple" for human-readable. Defaults to "json" */
format?: "json" | "simple"; format?: "json" | "simple";
/** Timestamp format: "long" for ISO string, "short" for abbreviated. Defaults to "short" */
time?: "long" | "short"; time?: "long" | "short";
/** Minimum level to include caller info (file/line). Defaults to "warn" */
callerLevel?: LogLevel; callerLevel?: LogLevel;
/** Custom ANSI color codes for log levels */
colours?: Partial<Colours>; colours?: Partial<Colours>;
} }
/**
* A comprehensive logging class with configurable levels, formatting, and caller detection.
*
* Features:
* - Multiple log levels (silent, error, warn, info, debug)
* - JSON and simple text formatting
* - util.format-style string interpolation (%s, %d, %j, etc.)
* - Automatic TTY detection for colored vs plain output
* - Optional caller information (file/line) inclusion
* - Dynamic level changing
* - Extensible color and level configuration
*
* @example
* ```ts
* // Basic usage
* const logger = new Logger();
* logger.info("Hello, world!");
* logger.error("Something went wrong");
*
* // With format strings
* logger.info("User %s has %d notifications", "alice", 5);
* logger.warn("Config: %j", { timeout: 5000 });
*
* // Dynamic level changes
* logger.level("debug");
* logger.debug("Debug info now visible");
*
* // Custom configuration
* const customLogger = new Logger({
* level: "warn",
* format: "simple",
* callerLevel: "error"
* });
* ```
*/
class Logger { class Logger {
options: { options: {
level: LogLevel; level: LogLevel;
@@ -166,6 +317,31 @@ class Logger {
callerErrorCount: number; callerErrorCount: number;
maxCallerErrors: number; maxCallerErrors: number;
/**
* Creates a new Logger instance with the specified configuration.
*
* @param options - Configuration options for the logger
*
* @example
* ```ts
* // Default logger (info level, JSON format)
* const logger = new Logger();
*
* // Debug logger with simple formatting
* const debugLogger = new Logger({
* level: "debug",
* format: "simple"
* });
*
* // Production logger with caller info on errors only
* const prodLogger = new Logger({
* level: "warn",
* format: "json",
* time: "long",
* callerLevel: "error"
* });
* ```
*/
constructor(options: LoggerOptions = {}) { constructor(options: LoggerOptions = {}) {
this.validateOptions(options); this.validateOptions(options);
@@ -472,22 +648,93 @@ class Logger {
} }
} }
/**
* Logs an error message. Always shown unless level is set to "silent".
*
* @param message - The message to log (supports format strings)
* @param args - Additional arguments for format string interpolation
*
* @example
* ```ts
* logger.error("Database connection failed");
* logger.error("User %s authentication failed", "john");
* logger.error("Error details: %j", { code: 500, message: "Internal error" });
* ```
*/
error(message: unknown, ...args: unknown[]): void { error(message: unknown, ...args: unknown[]): void {
this.log("error", message, ...args); this.log("error", message, ...args);
} }
/**
* Logs a warning message. Shown when level is "warn", "info", or "debug".
*
* @param message - The message to log (supports format strings)
* @param args - Additional arguments for format string interpolation
*
* @example
* ```ts
* logger.warn("High memory usage detected");
* logger.warn("Cache miss for key: %s", "user:123");
* logger.warn("Performance warning: %d ms", 2500);
* ```
*/
warn(message: unknown, ...args: unknown[]): void { warn(message: unknown, ...args: unknown[]): void {
this.log("warn", message, ...args); this.log("warn", message, ...args);
} }
/**
* Logs an informational message. Shown when level is "info" or "debug" (default behavior).
*
* @param message - The message to log (supports format strings)
* @param args - Additional arguments for format string interpolation
*
* @example
* ```ts
* logger.info("Application started successfully");
* logger.info("User %s logged in", "alice");
* logger.info("Request processed in %d ms", 150);
* ```
*/
info(message: unknown, ...args: unknown[]): void { info(message: unknown, ...args: unknown[]): void {
this.log("info", message, ...args); this.log("info", message, ...args);
} }
/**
* Logs a debug message. Only shown when level is set to "debug".
*
* @param message - The message to log (supports format strings)
* @param args - Additional arguments for format string interpolation
*
* @example
* ```ts
* logger.debug("Processing user data");
* logger.debug("Cache hit for key: %s", "user:123");
* logger.debug("Function arguments: %j", { id: 123, name: "test" });
* ```
*/
debug(message: unknown, ...args: unknown[]): void { debug(message: unknown, ...args: unknown[]): void {
this.log("debug", message, ...args); this.log("debug", message, ...args);
} }
/**
* Gets the current log level or sets a new one.
*
* @param newLevel - Optional new log level to set
* @returns The current (or newly set) log level
*
* @example
* ```ts
* // Get current level
* const currentLevel = logger.level(); // "info"
*
* // Set new level
* logger.level("debug");
* logger.debug("This will now be shown");
*
* // Chaining usage
* const level = logger.level("warn"); // Sets to "warn" and returns "warn"
* ```
*/
level(): LogLevel; level(): LogLevel;
level(newLevel: LogLevel): LogLevel; level(newLevel: LogLevel): LogLevel;
level(newLevel?: LogLevel): LogLevel { level(newLevel?: LogLevel): LogLevel {
@@ -502,6 +749,21 @@ class Logger {
return this.options.level; return this.options.level;
} }
/**
* Alternative method to set/get the log level. Identical to {@link level}.
*
* @param newLevel - Optional new log level to set
* @returns The current (or newly set) log level
*
* @example
* ```ts
* // Get current level
* const currentLevel = logger.setLevel(); // "info"
*
* // Set new level
* logger.setLevel("error");
* ```
*/
setLevel(): LogLevel; setLevel(): LogLevel;
setLevel(newLevel: LogLevel): LogLevel; setLevel(newLevel: LogLevel): LogLevel;
setLevel(newLevel?: LogLevel): LogLevel { setLevel(newLevel?: LogLevel): LogLevel {