Decaf's Logging
A comprehensive, flexible, and type-safe logging library for TypeScript applications that provides hierarchical context-aware logging, configurable styling, multiple output formats, and method decorators. It offers a lightweight built-in logger and seamless Winston integration, enabling developers to easily add structured logging with different verbosity levels to their applications.
Documentation available here
Description
Decaf's Logging is a powerful TypeScript logging library designed to provide flexible, context-aware logging capabilities for applications of any size. The library is built with a focus on type safety, configurability, and ease of use.
Core Architecture
The library follows a modular architecture with several key components:
-
Logging System:
Logging
: A static class that serves as the central entry point for the logging system. It manages global configuration, provides factory methods for creating loggers, and offers static logging methods.MiniLogger
: A lightweight logger implementation that provides the core logging functionality with support for different log levels, context-aware logging, and customizable formatting.Logger
interface: Defines the standard methods that all logger implementations must provide, ensuring consistency across different logger types.
-
Configuration System:
LoggingConfig
: Defines configuration options for the logging system, including log level, verbosity, styling, timestamp format, and more.DefaultLoggingConfig
: Provides sensible default settings that can be overridden as needed.
-
Log Levels:
LogLevel
enum: Defines standard log levels (error, info, verbose, debug, silly) for categorizing log messages.NumericLogLevels
: Maps log levels to numeric values for comparison and filtering.
-
Styling System:
Theme
interface: Defines a comprehensive theming system for styling log output with colors and formatting.DefaultTheme
: Provides a default color scheme for log output.LoggingMode
enum: Supports different output formats (RAW, JSON) for log messages.
-
Decorator System:
- Method decorators (
@log
,@debug
,@info
,@verbose
,@silly
): Allow for easy integration of logging into class methods with options for benchmarking and verbosity control.
- Method decorators (
-
Winston Integration:
WinstonLogger
: Extends the core logging functionality to leverage the Winston logging library.WinstonFactory
: A factory function for creating Winston-based loggers.
Key Features
-
Hierarchical Context-Aware Logging: The library allows creating loggers for specific classes and methods, maintaining a hierarchy of contexts. This makes it easy to trace log messages back to their source and filter logs by context.
-
Configurable Styling: Extensive support for styling log output with colors and formatting, with a theme system that allows customizing the appearance of different log components.
-
Multiple Output Formats: Support for both human-readable (RAW) and machine-parseable (JSON) output formats.
-
Method Decorators: Easy-to-use decorators for adding logging to class methods, with support for benchmarking execution time.
-
Verbosity Control: Fine-grained control over log verbosity, allowing developers to adjust the detail level of logs without changing code.
-
Type Safety: Comprehensive TypeScript type definitions ensure type safety and enable IDE autocompletion.
-
Winston Integration: Seamless integration with the popular Winston logging library, providing access to its advanced features while maintaining the same interface.
-
Error Handling: Special support for logging errors with stack traces for better debugging.
Usage Patterns
The library supports several usage patterns:
-
Global Logging: Using the static
Logging
class methods for simple, application-wide logging. -
Class-Specific Logging: Creating loggers for specific classes to provide context for log messages.
-
Method-Specific Logging: Creating child loggers for specific methods to further refine the context.
-
Decorator-Based Logging: Using method decorators to automatically log method calls and execution times.
-
Winston-Based Logging: Leveraging Winston's advanced features while maintaining the same interface.
This flexible design makes the library suitable for a wide range of applications, from simple scripts to complex enterprise systems.
How to Use
Basic Usage
Global Logging
The simplest way to use the library is through the static Logging
class:
import { Logging, LogLevel } from 'decaf-logging';
// Configure global logging settings
Logging.setConfig({
level: LogLevel.debug,
style: true,
timestamp: true
});
// Log messages at different levels
Logging.info('Application started');
Logging.debug('Debug information');
Logging.error('An error occurred');
Logging.verbose('Detailed information', 1); // With verbosity level
Class-Specific Logging
For more context-aware logging, create loggers for specific classes:
import { Logging, Logger } from 'decaf-logging';
class UserService {
private logger: Logger;
constructor() {
// Create a logger for this class
this.logger = Logging.for(UserService);
// Or with string name: Logging.for('UserService');
}
getUser(id: string) {
this.logger.info(`Getting user with ID: ${id}`);
// ... implementation
this.logger.debug('User retrieved successfully');
}
updateUser(id: string, data: any) {
try {
this.logger.info(`Updating user with ID: ${id}`);
// ... implementation
this.logger.debug('User updated successfully');
} catch (error) {
this.logger.error(error); // Logs error with stack trace
}
}
}
Method-Specific Logging
Create child loggers for specific methods to further refine the context:
import { Logging, Logger } from 'decaf-logging';
class DataProcessor {
private logger: Logger;
constructor() {
this.logger = Logging.for(DataProcessor);
}
processData(data: any[]) {
// Create a method-specific logger
const methodLogger = this.logger.for('processData');
methodLogger.info(`Processing ${data.length} items`);
// With custom configuration
const verboseLogger = methodLogger.for('details', { verbose: 2 });
verboseLogger.verbose('Starting detailed processing', 1);
// ... implementation
}
}
Using Decorators
Decorators provide an easy way to add logging to class methods:
import { debug, info, log, LogLevel, verbose } from 'decaf-logging';
class PaymentProcessor {
// Basic logging with info level
@info()
processPayment(amount: number, userId: string) {
// Method implementation
return true;
}
// Debug level logging with benchmarking
@debug(true)
validatePayment(paymentData: any) {
// Method implementation
return true;
}
// Verbose logging with custom verbosity
@verbose(2)
recordTransaction(transactionData: any) {
// Method implementation
}
// Custom log level with benchmarking and verbosity
@log(LogLevel.error, true, 1)
handleFailure(error: Error) {
// Method implementation
}
}
Winston Integration
To use Winston for logging:
import { Logging, LogLevel } from 'decaf-logging';
import { WinstonFactory } from 'decaf-logging/winston';
import winston from 'winston';
// Configure Winston transports
const transports = [
new winston.transports.Console(),
new winston.transports.File({ filename: 'app.log' })
];
// Set Winston as the logger factory
Logging.setFactory(WinstonFactory);
// Configure global logging
Logging.setConfig({
level: LogLevel.info,
timestamp: true,
timestampFormat: 'YYYY-MM-DD HH:mm:ss'
});
// Create a Winston logger for a class
const logger = Logging.for('MyService');
logger.info('Service initialized');
Advanced Usage
Custom Styling
Configure custom styling for log output:
import { Logging, LogLevel, Theme } from 'decaf-logging';
// Define a custom theme
const customTheme: Theme = {
class: {
fg: 35, // Magenta
},
id: {
fg: 36, // Cyan
},
stack: {},
timestamp: {
fg: 90, // Gray
},
message: {
error: {
fg: 31, // Red
style: ['bold'],
},
},
method: {},
logLevel: {
error: {
fg: 31, // Red
style: ['bold'],
},
info: {
fg: 32, // Green
},
verbose: {
fg: 34, // Blue
},
debug: {
fg: 33, // Yellow
},
},
};
// Apply the custom theme
Logging.setConfig({
style: true,
theme: customTheme
});
Correlation IDs
Track related log messages with correlation IDs:
import { Logging } from 'decaf-logging';
function processRequest(requestId: string, data: any) {
// Create a logger with correlation ID
const logger = Logging.for('RequestProcessor', {
correlationId: requestId
});
logger.info('Processing request');
// All log messages from this logger will include the correlation ID
processRequestData(data, logger);
logger.info('Request processing completed');
}
function processRequestData(data: any, logger: Logger) {
// Child loggers inherit the correlation ID
const dataLogger = logger.for('DataProcessor');
dataLogger.debug('Processing data');
// ...
}
JSON Output Mode
For machine-readable logs:
import { Logging, LoggingMode } from 'decaf-logging';
// Configure for JSON output
Logging.setConfig({
mode: LoggingMode.JSON,
timestamp: true
});
Logging.info('This will be output in JSON format');
Custom Logger Factory
Create a custom logger implementation:
import { Logger, LoggerFactory, Logging, MiniLogger } from 'decaf-logging';
// Custom logger that adds prefix to all messages
class PrefixedLogger extends MiniLogger {
constructor(context: string, prefix: string, conf?: Partial<LoggingConfig>) {
super(context, conf);
this.prefix = prefix;
}
private prefix: string;
protected override createLog(level: LogLevel, message: StringLike | Error, stack?: string): string {
const msg = typeof message === 'string' ? message : message.message;
const prefixedMsg = `${this.prefix}: ${msg}`;
return super.createLog(level, prefixedMsg, stack);
}
}
// Custom factory function
const PrefixedLoggerFactory: LoggerFactory = (
context: string,
conf?: Partial<LoggingConfig>,
...args: any[]
) => new PrefixedLogger(context, args[0] || '[APP]', conf);
// Set the custom factory
Logging.setFactory(PrefixedLoggerFactory);
// Create a logger with the custom factory
const logger = Logging.for('MyClass', undefined, '[CUSTOM]');
logger.info('Hello world'); // Outputs: "[CUSTOM]: Hello world"
Related
Social
Languages
Getting help
If you have bug reports, questions or suggestions please create a new issue.
Contributing
I am grateful for any contributions made to this project. Please read this to get started.
Supporting
The first and easiest way you can support it is by Contributing. Even just finding a typo in the documentation is important.
Financial support is always welcome and helps keep both me and the project alive and healthy.
So if you can, if this project in any way. either by learning something or simply by helping you save precious time, please consider donating.
License
This project is released under the MIT License.
By developers, for developers...