Skip to content

Instantly share code, notes, and snippets.

@WomB0ComB0
Created March 5, 2025 02:09
Show Gist options
  • Save WomB0ComB0/c4b5f58bf73e34c026ab98cf4bf476f0 to your computer and use it in GitHub Desktop.
Save WomB0ComB0/c4b5f58bf73e34c026ab98cf4bf476f0 to your computer and use it in GitHub Desktop.
logger.ts and related files - with AI-generated descriptions
/**
* @fileoverview A comprehensive logging utility for both client and server environments.
* Provides structured logging with support for different log levels, colorization,
* and contextual information.
*/
/**
* Determines if the code is running in a server environment
*/
const isServer = typeof window === "undefined";
/**
* Enum representing different logging levels with their priority values.
* Higher values indicate more verbose logging.
* @enum {number}
*/
export enum LogLevel {
/** No logging */
NONE = 0,
/** Only error messages */
ERROR = 1,
/** Errors and warnings */
WARN = 2,
/** Errors, warnings, and informational messages */
INFO = 3,
/** Errors, warnings, info, and debug messages */
DEBUG = 4,
/** Errors, warnings, info, debug, and trace messages */
TRACE = 5,
/** All possible log messages */
ALL = 6
}
/**
* Available color keys for log formatting
* @typedef {'reset' | 'red' | 'yellow' | 'blue' | 'green' | 'gray' | 'bold' | 'magenta' | 'cyan' | 'white'} ColorKey
*/
export type ColorKey = 'reset' | 'red' | 'yellow' | 'blue' | 'green' | 'gray' | 'bold' | 'magenta' | 'cyan' | 'white';
/**
* Interface for structured data that can be attached to log messages
* @interface
*/
export interface LogData {
/**
* Any key-value pairs to include in the log
*/
[key: string]: any;
}
/**
* Configuration options for the Logger
* @interface
*/
export interface LoggerOptions {
/**
* The minimum level of messages to log
*/
minLevel?: LogLevel;
/**
* Whether to include timestamps in log messages
*/
includeTimestamp?: boolean;
/**
* Whether to colorize log output
*/
colorize?: boolean;
/**
* Whether to write logs to a file (server-side only)
*/
logToFile?: boolean;
/**
* Path to the log file if logToFile is enabled
*/
filePath?: string;
}
/**
* A versatile logging utility that works in both browser and Node.js environments.
* Supports multiple log levels, colorized output, and structured data logging.
*/
export class Logger {
/** The context/category name for this logger instance */
private context: string;
/** Whether this logger is running in a server environment */
private isServerContext: boolean;
/** The minimum log level that will be output */
private minLevel: LogLevel;
/** Whether to include ISO timestamps in log messages */
private includeTimestamp: boolean;
/** Whether to apply ANSI color codes to the output */
private shouldColorize: boolean;
/** Registry of logger instances to implement the singleton pattern */
private static instances: Map<string, Logger> = new Map();
/** ANSI color codes for terminal output */
private colors: Record<ColorKey, string> = {
reset: "\x1b[0m",
red: "\x1b[31m",
green: "\x1b[32m",
yellow: "\x1b[33m",
blue: "\x1b[34m",
magenta: "\x1b[35m",
cyan: "\x1b[36m",
white: "\x1b[37m",
gray: "\x1b[90m",
bold: "\x1b[1m"
};
/** Mapping of log levels to their display colors */
private levelColors: Record<string, ColorKey> = {
error: "red",
warn: "yellow",
info: "blue",
debug: "gray",
trace: "cyan",
action: "magenta",
success: "green"
};
/**
* Create a new Logger instance or return an existing one for the given context
* @param {string} context - The context name for this logger (e.g., component or service name)
* @param {LoggerOptions} [options={}] - Optional logger configuration
*/
constructor(context: string, options: LoggerOptions = {}) {
this.context = context;
this.isServerContext = isServer;
this.minLevel = options.minLevel ?? (process.env.NODE_ENV === "production" ? LogLevel.ERROR : LogLevel.ALL);
this.includeTimestamp = options.includeTimestamp ?? true;
this.shouldColorize = options.colorize ?? this.isServerContext;
}
/**
* Get a logger instance for the given context.
* If a logger with this context already exists, returns the existing instance.
*
* @param {string} context - The context name
* @param {LoggerOptions} [options] - Optional logger configuration
* @returns {Logger} A logger instance for the specified context
*/
public static getLogger(context: string, options?: LoggerOptions): Logger {
if (!Logger.instances.has(context)) {
Logger.instances.set(context, new Logger(context, options));
}
return Logger.instances.get(context)!;
}
/**
* Set global minimum log level for all logger instances
*
* @param {LogLevel} level - The minimum level to log across all loggers
*/
public static setGlobalLogLevel(level: LogLevel): void {
Logger.instances.forEach(logger => {
logger.minLevel = level;
});
}
/**
* Determine if the current environment should log messages at the specified level
*
* @param {LogLevel} level - The log level to check
* @returns {boolean} Whether logging should occur for this level
* @private
*/
private shouldLog(level: LogLevel): boolean {
// Check if level meets minimum threshold
if (level > this.minLevel) return false;
// Always log server-side actions
if (this.isServerContext) return true;
// Only log client-side in development or if explicitly enabled
return process.env.NODE_ENV === "development" || process.env.ENABLE_CLIENT_LOGS === "true";
}
/**
* Format a log message with metadata
*
* @param {string} level - The log level
* @param {string} message - The message to log
* @param {LogData} [data] - Optional data to include
* @returns {Object} Formatted message with prefix and data
* @private
*/
private formatMessage(level: string, message: string, data?: LogData) {
const timestamp = this.includeTimestamp ? new Date().toISOString() : '';
const environment = this.isServerContext ? "[SERVER]" : "[CLIENT]";
const prefix = `${timestamp} ${environment} ${this.context}:`;
return { prefix, message, ...(data && { data }) };
}
/**
* Apply color to text if colorization is enabled
*
* @param {ColorKey} color - The color to apply
* @param {string} text - The text to colorize
* @returns {string} Colorized text (if enabled) or original text
* @private
*/
private colorize(color: ColorKey, text: string): string {
// Only apply colors if enabled
if (!this.shouldColorize) return text;
return `${this.colors[color]}${text}${this.colors.reset}`;
}
/**
* Format a log level indicator with brackets
*
* @param {string} level - The log level string
* @returns {string} Formatted log level indicator
* @private
*/
private formatLogLevel(level: string): string {
return `[${level.toUpperCase()}]`;
}
/**
* Format the final log output combining all components
*
* @param {Object} params - The formatted log data
* @param {string} params.prefix - The log prefix with context information
* @param {string} params.message - The main log message
* @param {LogData} [params.data] - Optional structured data to include
* @returns {string} The complete formatted log message
* @private
*/
private formatOutput({
prefix,
message,
data,
}: {
prefix: string;
message: string;
data?: LogData;
}): string {
const logParts = [prefix, message];
if (data) {
// Handle special cases like Error objects better
if (data.error instanceof Error) {
logParts.push("\nError Details:");
logParts.push(` Name: ${data.error.name}`);
logParts.push(` Message: ${data.error.message}`);
if (data.error.stack) {
logParts.push(` Stack: ${data.error.stack}`);
}
// Remove error from data to avoid duplication
const { error, ...restData } = data;
if (Object.keys(restData).length > 0) {
logParts.push("\nAdditional Data:");
logParts.push(JSON.stringify(restData, null, 2));
}
} else {
logParts.push("\n" + JSON.stringify(data, null, 2));
}
}
return logParts.join(" ");
}
/**
* Log an informational message
*
* @param {string} message - The message to log
* @param {LogData} [data] - Optional data to include
*/
info(message: string, data?: LogData): void {
if (!this.shouldLog(LogLevel.INFO)) return;
const formattedData = this.formatMessage("info", message, data);
console.log(
this.colorize(this.levelColors.info, this.formatLogLevel("info")) +
" " +
this.formatOutput(formattedData)
);
}
/**
* Log an error message
*
* @param {string} message - The error message
* @param {Error|unknown} [error] - Optional Error object or unknown error
* @param {LogData} [data] - Optional additional data
*/
error(message: string, error?: Error | unknown, data?: LogData): void {
if (!this.shouldLog(LogLevel.ERROR)) return;
const errorData =
error instanceof Error
? { name: error.name, message: error.message, stack: error.stack }
: error;
const formattedData = this.formatMessage("error", message, {
...data,
error: errorData,
});
console.error(
this.colorize("bold", this.colorize(this.levelColors.error, this.formatLogLevel("error"))) +
" " +
this.formatOutput(formattedData)
);
}
/**
* Log a warning message
*
* @param {string} message - The warning message
* @param {LogData} [data] - Optional data to include
*/
warn(message: string, data?: LogData): void {
if (!this.shouldLog(LogLevel.WARN)) return;
const formattedData = this.formatMessage("warn", message, data);
console.warn(
this.colorize(this.levelColors.warn, this.formatLogLevel("warn")) +
" " +
this.formatOutput(formattedData)
);
}
/**
* Log a debug message
*
* @param {string} message - The debug message
* @param {LogData} [data] - Optional data to include
*/
debug(message: string, data?: LogData): void {
if (!this.shouldLog(LogLevel.DEBUG)) return;
const formattedData = this.formatMessage("debug", message, data);
console.debug(
this.colorize(this.levelColors.debug, this.formatLogLevel("debug")) +
" " +
this.formatOutput(formattedData)
);
}
/**
* Log a trace message (most verbose level)
*
* @param {string} message - The trace message
* @param {LogData} [data] - Optional data to include
*/
trace(message: string, data?: LogData): void {
if (!this.shouldLog(LogLevel.TRACE)) return;
const formattedData = this.formatMessage("trace", message, data);
console.debug(
this.colorize(this.levelColors.trace, this.formatLogLevel("trace")) +
" " +
this.formatOutput(formattedData)
);
}
/**
* Log an action message (for server actions or important user interactions)
*
* @param {string} message - The action message
* @param {LogData} [data] - Optional data to include
*/
action(message: string, data?: LogData): void {
if (!this.shouldLog(LogLevel.INFO)) return;
const formattedData = this.formatMessage("action", message, data);
console.log(
this.colorize(this.levelColors.action, this.formatLogLevel("action")) +
" " +
this.formatOutput(formattedData)
);
}
/**
* Log a success message
*
* @param {string} message - The success message
* @param {LogData} [data] - Optional data to include
*/
success(message: string, data?: LogData): void {
if (!this.shouldLog(LogLevel.INFO)) return;
const formattedData = this.formatMessage("success", message, data);
console.log(
this.colorize(this.levelColors.success, this.formatLogLevel("success")) +
" " +
this.formatOutput(formattedData)
);
}
/**
* Group related log messages (console.group wrapper)
*
* @param {string} label - The group label
*/
group(label: string): void {
if (!this.shouldLog(LogLevel.INFO)) return;
console.group(this.colorize("bold", label));
}
/**
* End a log group (console.groupEnd wrapper)
*/
groupEnd(): void {
if (!this.shouldLog(LogLevel.INFO)) return;
console.groupEnd();
}
/**
* Log execution time of a function
*
* @template T - The return type of the function being timed
* @param {string} label - Description of the operation being timed
* @param {() => Promise<T> | T} fn - Function to execute and time
* @returns {Promise<T>} The result of the function execution
*/
async time<T>(label: string, fn: () => Promise<T> | T): Promise<T> {
if (!this.shouldLog(LogLevel.DEBUG)) return fn();
const startTime = performance.now();
try {
const result = await fn();
const endTime = performance.now();
this.info(`${label} completed in ${(endTime - startTime).toFixed(2)}ms`);
return result;
} catch (error) {
const endTime = performance.now();
this.error(`${label} failed after ${(endTime - startTime).toFixed(2)}ms`, error);
throw error;
}
}
}
// Usage examples:
/*
// Basic usage
const logger = new Logger("UserService");
logger.info("User logged in", { userId: "123" });
// With options
const detailedLogger = new Logger("AuthService", {
minLevel: LogLevel.DEBUG,
includeTimestamp: true,
colorize: true
});
// Singleton pattern
const logger1 = Logger.getLogger("ApiClient");
const logger2 = Logger.getLogger("ApiClient"); // Returns the same instance
// Set global log level
Logger.setGlobalLogLevel(LogLevel.WARN); // Only show warnings and errors
// Time operations
async function fetchData() {
return await logger.time("API Request", async () => {
const response = await fetch("https://api.example.com/data");
return response.json();
});
}
*/

logger.ts Description

File Type: ts

Generated Description:

logger.ts Analysis

This TypeScript file (logger.ts) implements a flexible and robust logging utility designed for use in both client-side (browser) and server-side (Node.js) JavaScript environments. It provides structured logging with configurable levels, colorization, and the ability to include contextual data.

Summary

The file defines a Logger class that facilitates logging messages with different severity levels (e.g., error, warning, info, debug). It supports customization through options, including setting a minimum log level, timestamp inclusion, colorized output, and file logging (server-side). A singleton pattern ensures that only one instance of the logger exists for each context (e.g., a module or component).

Key Components and Functions

  • LogLevel enum: Defines the available log levels (NONE, ERROR, WARN, INFO, DEBUG, TRACE, ALL) with associated numerical priorities.

  • ColorKey type: Defines an alias for available color keys used for terminal output colorization.

  • LogData interface: Defines the structure for additional key-value data that can be attached to log messages.

  • LoggerOptions interface: Defines configuration options for the Logger class, including minLevel, includeTimestamp, colorize, logToFile, and filePath.

  • Logger class: The core logging utility. Key methods include:

    • constructor(context: string, options: LoggerOptions = {}): Creates a new logger instance for a specified context. It uses default options if none are provided, falling back to LogLevel.ERROR in production and LogLevel.ALL in other environments.
    • getLogger(context: string, options?: LoggerOptions): Logger: Implements the singleton pattern. Returns an existing logger instance for a given context or creates a new one if it doesn't exist.
    • setGlobalLogLevel(level: LogLevel): void: Allows changing the minimum log level for all existing logger instances.
    • (Implicit logging methods): The code snippet is incomplete, but the class would presumably include methods like error(), warn(), info(), debug(), and trace() to log messages at different levels. These methods would likely format the message, including context, timestamp (if enabled), and color codes (if enabled), before outputting the message to the console or a log file.
  • isServer constant: A simple check to determine whether the code is running in a server environment (Node.js).

  • colors and levelColors properties: Contain mappings of color keys to ANSI escape codes and log levels to their associated colors, respectively, for terminal colorization.

Notable Patterns and Techniques

  • Singleton Pattern: The getLogger static method ensures only one Logger instance exists per context, preventing potential issues with multiple loggers writing to the same output and simplifying management.
  • Configuration Options: The LoggerOptions interface allows flexible customization of the logger's behavior.
  • Environmental Awareness: The logger automatically adjusts its default minimum log level based on the NODE_ENV environment variable (production vs. development).
  • Colorized Output: Uses ANSI escape codes to provide colorized output in compatible terminals, improving readability.
  • Structured Logging: Allows including additional contextual data (LogData) within log messages, making debugging easier.

Potential Use Cases

  • Application Debugging: Provides detailed logging for tracking application flow and identifying errors. Different log levels allow filtering messages based on severity.
  • Monitoring and Auditing: Logging critical events and user actions can aid in monitoring system health and detecting security breaches.
  • Microservices Communication: Structured logging enhances communication between microservices by providing rich context.
  • Library Development: The Logger can be included in libraries to provide logging functionality to applications using the library.
  • Server-Side Applications: The logToFile option enables persistent logging of server activity.

The incomplete code snippet prevents a fully comprehensive analysis of the logging methods themselves, but the structure strongly suggests a well-designed and versatile logging solution.

Description generated on 3/4/2025, 9:09:20 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment