@wgtechlabs/log-engine
Version:
A lightweight, security-first logging utility with automatic data redaction for Node.js applications - the first logging library with built-in PII protection.
398 lines • 17.2 kB
JavaScript
/**
* Core Logger class that handles log message output with configurable levels
* Supports DEBUG, INFO, WARN, ERROR, and LOG levels with intelligent filtering
* LOG level always outputs regardless of configuration
* Uses colorized console output with timestamps for better readability
* Includes automatic data redaction for sensitive information
*/
import { LogLevel, LogMode } from '../types/index.js';
import { LogFormatter } from '../formatter/index.js';
import { DataRedactor, RedactionController, defaultRedactionConfig } from '../redaction/index.js';
import { LoggerConfigManager } from './config.js';
import { LogFilter } from './filtering.js';
import { createBuiltInHandler } from './advanced-outputs.js';
/**
* Logger class responsible for managing log output and configuration
* Provides mode-based filtering and formatted console output
*/
export class Logger {
/**
* Logger constructor - sets up environment-based auto-configuration
*/
constructor() {
this.cachedConfig = null;
this.configManager = new LoggerConfigManager();
}
/**
* Get cached configuration or refresh cache if needed
* This avoids repeated getConfig() calls for better performance
*/
getCachedConfig() {
if (this.cachedConfig === null) {
this.cachedConfig = this.configManager.getConfig();
}
return this.cachedConfig;
}
/**
* Invalidate the configuration cache when configuration changes
*/
invalidateConfigCache() {
this.cachedConfig = null;
}
/**
* Helper method to format log messages with cached configuration
* Reduces code duplication across all log methods
* @param level - The log level to format for
* @param message - The message content to format
* @param data - Optional data object to include in the log output
* @returns Formatted string with appropriate configuration applied
*/
formatMessage(level, message, data) {
const cachedConfig = this.getCachedConfig();
return LogFormatter.format(level, message, data, cachedConfig.format);
}
/**
* Built-in output handlers for common use cases
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
getBuiltInHandler(type, config) {
switch (type) {
case 'console':
return (level, message, data) => {
// Use appropriate console method based on level
if (level === 'error') {
if (data !== undefined) {
console.error(message, data);
}
else {
console.error(message);
}
}
else if (level === 'warn') {
if (data !== undefined) {
console.warn(message, data);
}
else {
console.warn(message);
}
}
else {
if (data !== undefined) {
console.log(message, data);
}
else {
console.log(message);
}
}
};
case 'silent':
return () => {
// Do nothing - silent output
};
case 'file':
case 'http':
// Use advanced output handlers for file and http
return createBuiltInHandler(type, config);
default:
return null;
}
}
/**
* Process a single output target with error handling
* @param output - Single output target to process
* @param level - Log level
* @param rawMessage - Original unformatted message
* @param formattedMessage - Formatted message for console-based outputs
* @param data - Optional data
* @param isEnhanced - Whether this is an enhanced output (supports configured handler objects)
*/
processSingleOutput(output, level, rawMessage, formattedMessage, data, isEnhanced = false) {
const config = this.configManager.getConfig();
try {
if (typeof output === 'string') {
// Built-in handler - get config if available
const outputConfig = config.advancedOutputConfig?.[output];
const handler = this.getBuiltInHandler(output, outputConfig);
if (handler) {
// Advanced handlers (file, http) get raw message, console gets formatted
const messageToUse = (output === 'file' || output === 'http') ? rawMessage : formattedMessage;
handler(level, messageToUse, data);
}
else {
console.error('[LogEngine] Unknown built-in output handler:', JSON.stringify(output));
}
}
else if (typeof output === 'function') {
// Custom function handler gets formatted message for backward compatibility
output(level, formattedMessage, data);
}
else if (isEnhanced && typeof output === 'object' && output.type && output.config) {
// Configured handler object (only available for enhanced outputs)
const handler = this.getBuiltInHandler(output.type, output.config);
if (handler) {
// Advanced configured handlers get raw message
handler(level, rawMessage, data);
}
else {
console.error('[LogEngine] Unknown enhanced output handler type:', JSON.stringify(output));
}
}
else {
const outputType = isEnhanced ? 'enhanced output target' : 'output target';
console.error(`[LogEngine] Invalid ${outputType}:`, output);
}
}
catch (error) {
// Continue processing other outputs even if one fails
const handlerType = isEnhanced ? 'Enhanced output' : 'Output';
console.error(`[LogEngine] ${handlerType} handler failed: ${error}`);
}
}
/**
* Process multiple output targets
* @param outputs - Array of output targets to process
* @param level - Log level
* @param rawMessage - Original unformatted message
* @param formattedMessage - Formatted message for console-based outputs
* @param data - Optional data
*/
processOutputs(outputs, level, rawMessage, formattedMessage, data) {
for (const output of outputs) {
this.processSingleOutput(output, level, rawMessage, formattedMessage, data, false);
}
}
/**
* Process enhanced output targets
* @param enhancedOutputs - Array of enhanced output targets to process
* @param level - Log level
* @param rawMessage - Original unformatted message
* @param formattedMessage - Formatted message for console-based outputs
* @param data - Optional data
*/
processEnhancedOutputs(enhancedOutputs, level, rawMessage, formattedMessage, data) {
for (const output of enhancedOutputs) {
this.processSingleOutput(output, level, rawMessage, formattedMessage, data, true);
}
}
/**
* Updates logger configuration with new settings
* Also updates redaction configuration based on environment
* @param config - Partial configuration object to apply
*/
configure(config) {
this.configManager.updateConfig(config);
// Invalidate configuration cache since config has changed
this.invalidateConfigCache();
// Update redaction configuration based on current environment
DataRedactor.updateConfig({
...defaultRedactionConfig,
...RedactionController.getEnvironmentConfig()
});
}
/**
* Get current logger configuration
* @returns Current logger configuration
*/
getConfig() {
return this.configManager.getConfig();
}
/**
* Determines if a message should be logged based on current log mode
* @param level - The log level of the message to check
* @returns true if message should be logged, false otherwise
*/
shouldLog(level) {
const currentConfig = this.configManager.getConfig();
const currentMode = currentConfig.mode !== undefined ? currentConfig.mode : LogMode.INFO;
return LogFilter.shouldLog(level, currentMode);
}
/**
* Writes log output using configured output handler or default console methods
* Supports single output handler, multiple outputs, and enhanced outputs
* Priority: outputs > enhancedOutputs > outputHandler > default console
* @param level - The log level as a string
* @param rawMessage - The original unformatted message
* @param formattedMessage - The pre-formatted message to output
* @param data - Optional data object that was logged
* @param isError - Whether this is an error level message (for console.error)
* @param isWarn - Whether this is a warning level message (for console.warn)
*/
writeToOutput(level, rawMessage, formattedMessage, data, isError = false, isWarn = false) {
const config = this.configManager.getConfig();
// Multiple outputs support (highest priority - newer API)
if (config.outputs !== undefined) {
if (config.outputs.length > 0) {
// Process outputs array when it has actual outputs
this.processOutputs(config.outputs, level, rawMessage, formattedMessage, data);
}
// If outputs is explicitly set to empty array, disable all logging
return;
}
// Enhanced outputs with advanced configuration (second priority)
if (config.enhancedOutputs !== undefined && config.enhancedOutputs.length > 0) {
this.processEnhancedOutputs(config.enhancedOutputs, level, rawMessage, formattedMessage, data);
return;
}
// Single output handler (third priority - legacy compatibility)
if (config.outputHandler) {
try {
config.outputHandler(level, formattedMessage, data);
}
catch (error) {
// Fallback to console if custom handler fails
console.error(`[LogEngine] Output handler failed: ${error}. Falling back to console.`);
if (isError) {
console.error(formattedMessage);
}
else if (isWarn) {
console.warn(formattedMessage);
}
else {
console.log(formattedMessage);
}
}
return;
}
// Default: Console output (unless suppressed)
if (!config.suppressConsoleOutput) {
if (isError) {
console.error(formattedMessage);
}
else if (isWarn) {
console.warn(formattedMessage);
}
else {
console.log(formattedMessage);
}
}
// If suppressConsoleOutput is true and no outputHandler/outputs, do nothing (silent)
}
/**
* Log a debug message with DEBUG level formatting
* Uses console.log for output with purple/magenta coloring
* Automatically redacts sensitive data when provided
* @param message - The debug message to log
* @param data - Optional data object to log (will be redacted)
*/
debug(message, data) {
if (this.shouldLog(LogLevel.DEBUG)) {
const processedData = DataRedactor.redactData(data);
const formatted = this.formatMessage(LogLevel.DEBUG, message, processedData);
this.writeToOutput('debug', message, formatted, processedData);
}
}
/**
* Log an informational message with INFO level formatting
* Uses console.log for output with blue coloring
* Automatically redacts sensitive data when provided
* @param message - The info message to log
* @param data - Optional data object to log (will be redacted)
*/
info(message, data) {
if (this.shouldLog(LogLevel.INFO)) {
const processedData = DataRedactor.redactData(data);
const formatted = this.formatMessage(LogLevel.INFO, message, processedData);
this.writeToOutput('info', message, formatted, processedData);
}
}
/**
* Log a warning message with WARN level formatting
* Uses console.warn for output with yellow coloring
* Automatically redacts sensitive data when provided
* @param message - The warning message to log
* @param data - Optional data object to log (will be redacted)
*/
warn(message, data) {
if (this.shouldLog(LogLevel.WARN)) {
const processedData = DataRedactor.redactData(data);
const formatted = this.formatMessage(LogLevel.WARN, message, processedData);
this.writeToOutput('warn', message, formatted, processedData, false, true);
}
}
/**
* Log an error message with ERROR level formatting
* Uses console.error for output with red coloring
* Automatically redacts sensitive data when provided
* @param message - The error message to log
* @param data - Optional data object to log (will be redacted)
*/
error(message, data) {
if (this.shouldLog(LogLevel.ERROR)) {
const processedData = DataRedactor.redactData(data);
const formatted = this.formatMessage(LogLevel.ERROR, message, processedData);
this.writeToOutput('error', message, formatted, processedData, true, false);
}
}
/**
* Log a message with LOG level formatting (always outputs unless mode is OFF)
* Uses console.log for output with green coloring
* LOG level bypasses normal filtering and always outputs (except when OFF is set)
* Automatically redacts sensitive data when provided
* @param message - The log message to output
* @param data - Optional data object to log (will be redacted)
*/
log(message, data) {
if (this.shouldLog(LogLevel.LOG)) {
const processedData = DataRedactor.redactData(data);
const formatted = this.formatMessage(LogLevel.LOG, message, processedData);
this.writeToOutput('log', message, formatted, processedData);
}
}
// Raw logging methods (bypass redaction for debugging)
/**
* Log a debug message without data redaction
* @param message - The debug message to log
* @param data - Optional data object to log (no redaction applied)
*/
debugRaw(message, data) {
if (this.shouldLog(LogLevel.DEBUG)) {
const formatted = this.formatMessage(LogLevel.DEBUG, message, data);
this.writeToOutput('debug', message, formatted, data);
}
}
/**
* Log an info message without data redaction
* @param message - The info message to log
* @param data - Optional data object to log (no redaction applied)
*/
infoRaw(message, data) {
if (this.shouldLog(LogLevel.INFO)) {
const formatted = this.formatMessage(LogLevel.INFO, message, data);
this.writeToOutput('info', message, formatted, data);
}
}
/**
* Log a warning message without data redaction
* @param message - The warning message to log
* @param data - Optional data object to log (no redaction applied)
*/
warnRaw(message, data) {
if (this.shouldLog(LogLevel.WARN)) {
const formatted = this.formatMessage(LogLevel.WARN, message, data);
this.writeToOutput('warn', message, formatted, data, false, true);
}
}
/**
* Log an error message without data redaction
* @param message - The error message to log
* @param data - Optional data object to log (no redaction applied)
*/
errorRaw(message, data) {
if (this.shouldLog(LogLevel.ERROR)) {
const formatted = this.formatMessage(LogLevel.ERROR, message, data);
this.writeToOutput('error', message, formatted, data, true, false);
}
}
/**
* Log a message without data redaction (always outputs unless mode is OFF)
* @param message - The log message to output
* @param data - Optional data object to log (no redaction applied)
*/
logRaw(message, data) {
if (this.shouldLog(LogLevel.LOG)) {
const formatted = this.formatMessage(LogLevel.LOG, message, data);
this.writeToOutput('log', message, formatted, data);
}
}
}
//# sourceMappingURL=core.js.map