Structured logging infrastructure with multiple logger implementations, level filtering, message transformation, and Observable-based event streaming for comprehensive application monitoring.
Basic types and interfaces for the logging system.
/**
* Available log levels in order of severity
*/
type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
/**
* A log entry containing all information about a logged message
*/
interface LogEntry {
/** Severity level of the log entry */
readonly level: LogLevel;
/** Log message content */
readonly message: string;
/** Timestamp when the entry was created */
readonly timestamp: Date;
/** Additional structured metadata */
readonly metadata: JsonObject;
/** Logger name that created this entry */
readonly logger: string;
/** Full logger path including parent loggers */
readonly path: readonly string[];
}
/**
* Metadata associated with a logger instance
*/
interface LoggerMetadata {
/** Logger name */
name: string;
/** Full path to this logger including parents */
path: readonly string[];
}
/**
* Core logging API interface
*/
interface LoggerApi {
/**
* Create a child logger with the given name
* @param name - Name for the child logger
* @returns New child logger instance
*/
createChild(name: string): Logger;
/**
* Log a debug message
* @param message - Message to log
* @param metadata - Optional additional metadata
*/
debug(message: string, metadata?: JsonObject): void;
/**
* Log an info message
* @param message - Message to log
* @param metadata - Optional additional metadata
*/
info(message: string, metadata?: JsonObject): void;
/**
* Log a warning message
* @param message - Message to log
* @param metadata - Optional additional metadata
*/
warn(message: string, metadata?: JsonObject): void;
/**
* Log an error message
* @param message - Message to log
* @param metadata - Optional additional metadata
*/
error(message: string, metadata?: JsonObject): void;
/**
* Log a fatal error message
* @param message - Message to log
* @param metadata - Optional additional metadata
*/
fatal(message: string, metadata?: JsonObject): void;
}Core logger implementation with Observable event streaming.
/**
* Main logger class that extends Observable to emit log entries
* Provides hierarchical logging with parent-child relationships
*/
class Logger extends Observable<LogEntry> implements LoggerApi {
/** Logger metadata including name and path */
readonly metadata: LoggerMetadata;
/**
* Create a new logger instance
* @param name - Logger name
* @param parent - Optional parent logger for hierarchy
*/
constructor(name: string, parent?: Logger);
/**
* Create a child logger with the given name
* @param name - Name for the child logger
* @returns New child logger instance
*/
createChild(name: string): Logger;
/**
* Log a debug message (lowest severity)
* @param message - Message to log
* @param metadata - Optional additional metadata
*/
debug(message: string, metadata?: JsonObject): void;
/**
* Log an informational message
* @param message - Message to log
* @param metadata - Optional additional metadata
*/
info(message: string, metadata?: JsonObject): void;
/**
* Log a warning message
* @param message - Message to log
* @param metadata - Optional additional metadata
*/
warn(message: string, metadata?: JsonObject): void;
/**
* Log an error message
* @param message - Message to log
* @param metadata - Optional additional metadata
*/
error(message: string, metadata?: JsonObject): void;
/**
* Log a fatal error message (highest severity)
* @param message - Message to log
* @param metadata - Optional additional metadata
*/
fatal(message: string, metadata?: JsonObject): void;
/**
* Subscribe to log entries from this logger and its children
* @param observer - Observer to receive log entries
* @returns Subscription for cleanup
*/
subscribe(observer: Observer<LogEntry>): Subscription;
subscribe(
next?: (value: LogEntry) => void,
error?: (error: any) => void,
complete?: () => void
): Subscription;
}Logger implementations with specific behaviors and transformations.
/**
* Logger that adds indentation to messages based on nesting level
* Useful for showing hierarchical operations in console output
*/
class IndentLogger extends Logger {
/**
* Create an indenting logger
* @param name - Logger name
* @param parent - Optional parent logger
* @param indentBy - String to use for indentation (default: ' ')
*/
constructor(name: string, parent?: Logger, indentBy?: string);
debug(message: string, metadata?: JsonObject): void;
info(message: string, metadata?: JsonObject): void;
warn(message: string, metadata?: JsonObject): void;
error(message: string, metadata?: JsonObject): void;
fatal(message: string, metadata?: JsonObject): void;
}
/**
* No-operation logger that discards all log messages
* Useful for testing or when logging needs to be disabled
*/
class NullLogger extends Logger {
constructor();
debug(message: string, metadata?: JsonObject): void;
info(message: string, metadata?: JsonObject): void;
warn(message: string, metadata?: JsonObject): void;
error(message: string, metadata?: JsonObject): void;
fatal(message: string, metadata?: JsonObject): void;
}
/**
* Logger that transforms log levels before emitting
* Allows remapping of log levels (e.g., debug -> info)
*/
class LevelTransformLogger extends Logger {
/**
* Create a level-transforming logger
* @param name - Logger name
* @param parent - Optional parent logger
* @param levelTransform - Function to transform log levels
*/
constructor(
name: string,
parent?: Logger,
levelTransform?: (level: LogLevel) => LogLevel
);
debug(message: string, metadata?: JsonObject): void;
info(message: string, metadata?: JsonObject): void;
warn(message: string, metadata?: JsonObject): void;
error(message: string, metadata?: JsonObject): void;
fatal(message: string, metadata?: JsonObject): void;
}
/**
* Logger that filters out messages below a specified level
* Only messages at or above the cap level are emitted
*/
class LevelCapLogger extends Logger {
/**
* Create a level-capped logger
* @param name - Logger name
* @param parent - Optional parent logger
* @param levelCap - Minimum level to emit (default: 'info')
*/
constructor(name: string, parent?: Logger, levelCap?: LogLevel);
debug(message: string, metadata?: JsonObject): void;
info(message: string, metadata?: JsonObject): void;
warn(message: string, metadata?: JsonObject): void;
error(message: string, metadata?: JsonObject): void;
fatal(message: string, metadata?: JsonObject): void;
}
/**
* Logger that applies transformations to messages and metadata
* Useful for formatting, filtering, or enriching log data
*/
class TransformLogger extends Logger {
/**
* Create a transforming logger
* @param name - Logger name
* @param stream - Source stream of log entries to transform
* @param transform - Function to transform log entries
*/
constructor(
name: string,
stream: Observable<LogEntry>,
transform?: (entry: LogEntry) => LogEntry
);
}Utility functions for working with log levels and logger operations.
/**
* Check if a log level meets or exceeds a minimum level
* @param level - Level to check
* @param minimum - Minimum required level
* @returns True if level is at or above minimum
*/
function isLevelEnabled(level: LogLevel, minimum: LogLevel): boolean;
/**
* Compare two log levels numerically
* @param a - First level
* @param b - Second level
* @returns Negative if a < b, positive if a > b, 0 if equal
*/
function compareLevels(a: LogLevel, b: LogLevel): number;
/**
* Get numeric value for a log level
* @param level - Log level
* @returns Numeric value (debug=0, info=1, warn=2, error=3, fatal=4)
*/
function levelToNumber(level: LogLevel): number;
/**
* Convert numeric value back to log level
* @param num - Numeric level value
* @returns Corresponding log level
*/
function numberToLevel(num: number): LogLevel;import { logging } from "@angular-devkit/core";
// Create a logger
const logger = new logging.Logger('my-app');
// Log messages at different levels
logger.debug('Debug information', { userId: 123 });
logger.info('Operation completed successfully');
logger.warn('This is a warning message');
logger.error('An error occurred', { errorCode: 'E001' });
logger.fatal('Critical system failure');
// Subscribe to log entries
logger.subscribe(entry => {
console.log(`[${entry.level.toUpperCase()}] ${entry.logger}: ${entry.message}`);
if (Object.keys(entry.metadata).length > 0) {
console.log('Metadata:', entry.metadata);
}
});import { logging } from "@angular-devkit/core";
// Create parent logger
const appLogger = new logging.Logger('app');
// Create child loggers
const dbLogger = appLogger.createChild('database');
const httpLogger = appLogger.createChild('http');
// Subscribe to parent to receive all messages
appLogger.subscribe(entry => {
console.log(`[${entry.path.join('.')}] ${entry.message}`);
});
// Child loggers emit through parent
dbLogger.info('Database connection established');
// Output: [app.database] Database connection established
httpLogger.warn('Request timeout');
// Output: [app.http] Request timeoutimport { logging } from "@angular-devkit/core";
// Indented logger for showing operation hierarchy
const indentLogger = new logging.IndentLogger('build');
const taskLogger = indentLogger.createChild('task');
indentLogger.info('Starting build process');
taskLogger.info('Compiling TypeScript');
// Output:
// Starting build process
// Compiling TypeScript
// Level-capped logger (only warn and above)
const productionLogger = new logging.LevelCapLogger('prod', undefined, 'warn');
productionLogger.debug('Debug info'); // Filtered out
productionLogger.info('Info message'); // Filtered out
productionLogger.warn('Warning message'); // Shown
productionLogger.error('Error message'); // Shown
// Null logger (discards everything)
const testLogger = new logging.NullLogger();
testLogger.info('This message is discarded');import { logging } from "@angular-devkit/core";
// Transform logger that adds timestamps and formats messages
const baseLogger = new logging.Logger('base');
const transformLogger = new logging.TransformLogger(
'formatted',
baseLogger,
(entry) => ({
...entry,
message: `[${entry.timestamp.toISOString()}] ${entry.message}`,
metadata: {
...entry.metadata,
transformed: true
}
})
);
transformLogger.subscribe(entry => {
console.log(entry.message);
// Output: [2023-10-15T10:30:00.000Z] Original message
});
baseLogger.info('Original message');import { logging } from "@angular-devkit/core";
// Check if logging is enabled for a level
const minLevel: logging.LogLevel = 'warn';
const currentLevel: logging.LogLevel = 'info';
if (logging.isLevelEnabled(currentLevel, minLevel)) {
console.log('Logging enabled');
} else {
console.log('Logging filtered out');
}
// Compare levels
const comparison = logging.compareLevels('error', 'warn');
console.log(comparison > 0 ? 'error is higher' : 'warn is higher or equal');
// Convert to/from numbers
const errorNum = logging.levelToNumber('error'); // 3
const levelBack = logging.numberToLevel(errorNum); // 'error'import { logging } from "@angular-devkit/core";
import { filter, map } from 'rxjs/operators';
// Create logger with multiple output streams
const logger = new logging.Logger('multi-output');
// Console output for all levels
logger.subscribe(entry => {
console.log(`${entry.level}: ${entry.message}`);
});
// File output for errors only
logger.pipe(
filter(entry => entry.level === 'error' || entry.level === 'fatal'),
map(entry => `${entry.timestamp.toISOString()} [${entry.level}] ${entry.message}\n`)
).subscribe(line => {
// Write to error log file
fs.appendFileSync('error.log', line);
});
// Metrics collection for warnings and errors
logger.pipe(
filter(entry => entry.level === 'warn' || entry.level === 'error' || entry.level === 'fatal')
).subscribe(entry => {
// Send to monitoring system
metrics.increment(`log.${entry.level}`, 1, { logger: entry.logger });
});