Enhanced logging functionality with improved context handling and documentation.

-  **Feature**: Added support for hierarchical logger context
- 🛠️ **Refactor**: Updated logging methods to use LoggerContext
- 📚 **Docs**: Improved documentation for logger methods and context
This commit is contained in:
Lorenz Hilpert
2025-04-16 15:35:52 +02:00
parent c9b5af7282
commit 39d101d456
4 changed files with 121 additions and 66 deletions

View File

@@ -22,22 +22,48 @@ export function logger(): LoggerApi {
// Return an object with methods that forward to the logging service
// with the provided context
return {
trace: (message: string, additionalContext?: unknown): void => {
/**
* Logs trace information with optional additional context.
* @param message The log message.
* @param additionalContext Optional context data specific to this log message.
*/
trace: (message: string, additionalContext?: LoggerContext): void => {
loggingService.trace(message, mergeContexts(context, additionalContext));
},
debug: (message: string, additionalContext?: unknown): void => {
/**
* Logs debug information with optional additional context.
* @param message The log message.
* @param additionalContext Optional context data specific to this log message.
*/
debug: (message: string, additionalContext?: LoggerContext): void => {
loggingService.debug(message, mergeContexts(context, additionalContext));
},
info: (message: string, additionalContext?: unknown): void => {
/**
* Logs informational messages with optional additional context.
* @param message The log message.
* @param additionalContext Optional context data specific to this log message.
*/
info: (message: string, additionalContext?: LoggerContext): void => {
loggingService.info(message, mergeContexts(context, additionalContext));
},
warn: (message: string, additionalContext?: unknown): void => {
/**
* Logs warning messages with optional additional context.
* @param message The log message.
* @param additionalContext Optional context data specific to this log message.
*/
warn: (message: string, additionalContext?: LoggerContext): void => {
loggingService.warn(message, mergeContexts(context, additionalContext));
},
/**
* Logs error messages with optional error object and additional context.
* @param message The log message.
* @param error Optional error object.
* @param additionalContext Optional context data specific to this log message.
*/
error: (
message: string,
error?: Error,
additionalContext?: unknown,
additionalContext?: LoggerContext,
): void => {
loggingService.error(
message,
@@ -49,35 +75,24 @@ export function logger(): LoggerApi {
}
/**
* Merges component-level context with message-specific context.
* @param baseContext The component-level context.
* @param additionalContext The message-specific context.
* @returns The merged context.
* Merges component-level context (potentially multiple contexts) with message-specific context.
* @param baseContext The array of component-level contexts provided via `provideLoggerContext`.
* @param additionalContext The message-specific context provided in the log call.
* @returns The merged context object containing properties from all provided contexts.
*/
function mergeContexts(
baseContext?: LoggerContext | null,
additionalContext?: unknown,
): unknown {
if (!baseContext) {
return additionalContext;
baseContext: LoggerContext[] | null,
additionalContext?: LoggerContext,
): LoggerContext {
const contextArray = Array.isArray(baseContext) ? baseContext : [];
if (typeof additionalContext === 'object') {
contextArray.push(additionalContext);
}
if (!additionalContext) {
return baseContext;
if (!contextArray.length) {
return {};
}
// If both contexts are objects, merge them
if (
typeof additionalContext === 'object' &&
additionalContext !== null &&
!Array.isArray(additionalContext)
) {
return { ...baseContext, ...additionalContext };
}
// If not both objects, return them separately
return {
baseContext,
additionalContext,
};
return contextArray.reduce((acc, context) => ({ ...acc, ...context }), {});
}

View File

@@ -12,7 +12,11 @@ import { LoggerContext, LoggingConfig, Sink, SinkFn } from './logging.types';
// Injection tokens for the Logger API
export const LOGGER_CONFIG = new InjectionToken<LoggingConfig>('LOGGER_CONFIG');
export const LOGGER_CONTEXT = new InjectionToken<LoggerContext>(
/**
* Injection token for providing hierarchical logger context.
* Allows multiple contexts to be provided and merged.
*/
export const LOGGER_CONTEXT = new InjectionToken<LoggerContext[]>(
'LOGGER_CONTEXT',
);
@@ -113,14 +117,16 @@ export function provideLogging(
}
/**
* Provides a context object for logging within a specific component or module
* @param context The context object to provide
* Provides a context object for logging within a specific component or module scope.
* Multiple contexts can be provided at different levels and will be merged.
* @param context The context object to provide for this scope.
*/
export function provideLoggerContext(context: LoggerContext): Provider[] {
return [
{
provide: LOGGER_CONTEXT,
useValue: context,
multi: true,
},
];
}

View File

@@ -1,6 +1,12 @@
import { Injectable, inject } from '@angular/core';
import { LogLevel } from './log-level.enum';
import { LoggerApi, LoggingConfig, Sink, SinkFn } from './logging.types';
import {
LoggerApi,
LoggerContext,
LoggingConfig,
Sink,
SinkFn,
} from './logging.types';
/**
* The main service for logging functionality.
@@ -10,9 +16,14 @@ import { LoggerApi, LoggingConfig, Sink, SinkFn } from './logging.types';
export class LoggingService implements LoggerApi {
private level: LogLevel = LogLevel.Info; // Default level
private sinks: Array<
(level: LogLevel, message: string, context?: unknown, error?: Error) => void
(
level: LogLevel,
message: string,
context?: LoggerContext,
error?: Error,
) => void
> = [];
private globalContext?: Record<string, unknown>;
private globalContext?: LoggerContext;
// Cache log level indexes for performance
private readonly LOG_LEVEL_ORDER: Record<LogLevel, number> = {
@@ -26,7 +37,7 @@ export class LoggingService implements LoggerApi {
/**
* Configures the logging service with the provided options.
* @param config The logging configuration options.
* @param config The logging configuration options, including level, sinks, and global context.
*/
configure(config: LoggingConfig): void {
this.level = config.level;
@@ -55,36 +66,36 @@ export class LoggingService implements LoggerApi {
/**
* Logs trace information.
* @param message The log message.
* @param context Optional context data.
* @param context Optional context data specific to this log message.
*/
trace(message: string, context?: unknown): void {
trace(message: string, context?: LoggerContext): void {
this.log(LogLevel.Trace, message, context);
}
/**
* Logs debug information.
* @param message The log message.
* @param context Optional context data.
* @param context Optional context data specific to this log message.
*/
debug(message: string, context?: unknown): void {
debug(message: string, context?: LoggerContext): void {
this.log(LogLevel.Debug, message, context);
}
/**
* Logs informational messages.
* @param message The log message.
* @param context Optional context data.
* @param context Optional context data specific to this log message.
*/
info(message: string, context?: unknown): void {
info(message: string, context?: LoggerContext): void {
this.log(LogLevel.Info, message, context);
}
/**
* Logs warning messages.
* @param message The log message.
* @param context Optional context data.
* @param context Optional context data specific to this log message.
*/
warn(message: string, context?: unknown): void {
warn(message: string, context?: LoggerContext): void {
this.log(LogLevel.Warn, message, context);
}
@@ -92,23 +103,23 @@ export class LoggingService implements LoggerApi {
* Logs error messages.
* @param message The log message.
* @param error Optional error object.
* @param context Optional context data.
* @param context Optional context data specific to this log message.
*/
error(message: string, error?: Error, context?: unknown): void {
error(message: string, error?: Error, context?: LoggerContext): void {
this.log(LogLevel.Error, message, context, error);
}
/**
* Internal method to handle logging with level filtering.
* Internal method to handle logging with level filtering and context merging.
* @param level The log level.
* @param message The log message.
* @param context Optional context data.
* @param context Optional context data specific to this log message.
* @param error Optional error object.
*/
private log(
level: LogLevel,
message: string,
context?: unknown,
context?: LoggerContext,
error?: Error,
): void {
// Short-circuit if logging is disabled or level is too low (performance optimization)
@@ -152,13 +163,13 @@ export class LoggingService implements LoggerApi {
}
/**
* Merges the global context with the provided context.
* @param context The context provided in the log call.
* @returns The merged context object.
* Merges the global context (if configured) with the context provided in the log call.
* @param context The context provided in the specific log call.
* @returns The merged context object. If both global and specific contexts exist, they are merged with specific context properties overwriting global ones.
*/
private mergeContext(context?: unknown): unknown {
private mergeContext(context?: LoggerContext): LoggerContext {
if (!this.globalContext) {
return context;
return context || {};
}
if (!context) {

View File

@@ -5,16 +5,30 @@ import { Type } from '@angular/core';
* Represents a destination where log messages are sent.
*/
export interface Sink {
log(level: LogLevel, message: string, context?: unknown, error?: Error): void;
/**
* Method called by the LoggingService to send a log entry to this sink.
* @param level The log level of the message.
* @param message The log message string.
* @param context Optional context data associated with the log message.
* @param error Optional error object associated with the log message.
*/
log(
level: LogLevel,
message: string,
context?: LoggerContext,
error?: Error,
): void;
}
/**
* Function implementation of a Sink.
* A factory function that returns a logging function conforming to the Sink interface.
* Allows for dependency injection or setup logic within the factory.
* @returns A function that handles logging a message.
*/
export type SinkFn = () => (
level: LogLevel,
message: string,
context?: unknown,
context?: LoggerContext,
error?: Error,
) => void;
@@ -22,24 +36,33 @@ export type SinkFn = () => (
* Configuration options for the logging service.
*/
export interface LoggingConfig {
/** The minimum log level to process. */
level: LogLevel;
/** An array of sinks (instances, classes, or factory functions) to send logs to. */
sinks: (Sink | SinkFn | Type<Sink>)[];
context?: Record<string, unknown>;
/** Optional global context to be included with every log message. */
context?: LoggerContext;
}
/**
* Represents the logger API for logging operations.
* Represents the public API for logging operations provided by the logger factory.
*/
export interface LoggerApi {
trace(message: string, context?: unknown): void;
debug(message: string, context?: unknown): void;
info(message: string, context?: unknown): void;
warn(message: string, context?: unknown): void;
error(message: string, error?: Error, context?: unknown): void;
/** Logs a trace message with optional context. */
trace(message: string, context?: LoggerContext): void;
/** Logs a debug message with optional context. */
debug(message: string, context?: LoggerContext): void;
/** Logs an info message with optional context. */
info(message: string, context?: LoggerContext): void;
/** Logs a warning message with optional context. */
warn(message: string, context?: LoggerContext): void;
/** Logs an error message with an optional error object and context. */
error(message: string, error?: Error, context?: LoggerContext): void;
}
/**
* Logger context for context-aware logging.
* Represents context data associated with a log message.
* It's an object where keys are strings and values can be of any type.
*/
export interface LoggerContext {
[key: string]: unknown;