Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
LennardGeissler committed Jan 24, 2025
2 parents 513f5ed + e15ee04 commit 9edf85f
Show file tree
Hide file tree
Showing 20 changed files with 296 additions and 16 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Lennard Geißler

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const handler = new ConsoleHandler();
handler.setFormatter(new TextFormatter('[{timestamp}] [{level}] {message}'));
logger.addHandler(handler);
// Start logging!
logger.info('Application started', { version: '1.0.0' });
logger.info('Application started', { version: '1.0.1' });
logger.debug('Debug message');
logger.warn('Warning message', { details: 'Something went wrong' });
logger.error('Error occurred', { error: new Error('Failed to process') });
Expand Down Expand Up @@ -156,4 +156,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file

## 👨‍💻 Authors

- Lennard Geissler
- Lennard Geissler (@LennardGeissler)
13 changes: 8 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lennardgeissler/logger",
"version": "1.0.0",
"version": "1.0.1",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
Expand Down
15 changes: 13 additions & 2 deletions src/components/LoggerProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { ReactNode } from 'react';
import React, { JSX, ReactNode } from 'react';
import { Logger } from '../core/Logger';
import { LoggerContext } from '../context/LoggerContext';
import { ConsoleHandler } from '../handlers/ConsoleHandler';
Expand All @@ -9,11 +9,22 @@ interface LoggerProviderProps {
handlers?: Array<any>;
}

/**
* A React component that provides a logging context to its child components.
* It initializes a logger instance and configures it with specified handlers.
*
* @param {LoggerProviderProps} props - The props for the LoggerProvider component.
* @param {ReactNode} props.children - The child components that will have access to the logger context.
* @param {Logger} [props.logger] - An optional custom logger instance. If not provided, the default singleton Logger is used.
* @param {Array<any>} [props.handlers] - An optional array of handlers to configure the logger. Defaults to a single ConsoleHandler.
*
* @returns {JSX.Element} A context provider wrapping the child components.
*/
export function LoggerProvider({
children,
logger = Logger.getInstance(),
handlers = [new ConsoleHandler()]
}: LoggerProviderProps) {
}: LoggerProviderProps): JSX.Element {
// Initialize logger with handlers if provided
React.useEffect(() => {
logger.clearHandlers();
Expand Down
9 changes: 8 additions & 1 deletion src/context/LoggerContext.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { createContext } from 'react';
import { Logger } from '../core/Logger';

export const LoggerContext = createContext<Logger | null>(null);
/**
* A React context for providing a Logger instance to components within the application.
* It allows child components to access and use a shared Logger instance for logging purposes.
*
* @type {React.Context<Logger | null>}
* @defaultValue null - The default value is `null`, indicating that no Logger instance is provided by default.
*/
export const LoggerContext: React.Context<Logger | null> = createContext<Logger | null>(null);
103 changes: 100 additions & 3 deletions src/core/Logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,97 @@ import { LogMessage } from "../types/LogMessage";
import { getTimestamp } from "../utils/getTimestamp";
import { BaseHandler } from "../handlers/BaseHandler";

/**
* A singleton class for managing application logging.
* It supports multiple log levels, custom handlers, and centralized logging configuration.
*/
export class Logger {
private static instance: Logger;
private logLevel: LogLevel = "info";
private handlers: BaseHandler[] = [];

private constructor() {}

/**
* Private constructor to prevent direct instantiation.
* Use `Logger.getInstance()` to obtain the singleton instance.
* @private
*/
private constructor() { }

/**
* Retrieves the singleton instance of the Logger.
*
* @returns {Logger} The singleton Logger instance.
*/
static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}

/**
* Adds a handler to the Logger.
*
* @param {BaseHandler} handler - The handler to add.
* @returns {void}
*/
addHandler(handler: BaseHandler): void {
this.handlers.push(handler);
}

/**
* Removes a handler from the Logger.
*
* @param {BaseHandler} handler - The handler to remove.
* @returns {void}
*/
removeHandler(handler: BaseHandler): void {
const index = this.handlers.indexOf(handler);
if (index > -1) {
this.handlers.splice(index, 1);
}
}

/**
* Clears all handlers from the Logger.
*
* @returns {void}
*/
clearHandlers(): void {
this.handlers = [];
}

/**
* Sets the log level of the Logger. Messages below this level are ignored.
*
* @param {LogLevel} level - The log level to set.
* @returns {void}
*/
setLogLevel(level: LogLevel): void {
this.logLevel = level;
}

/**
* Determines whether a message at the specified log level should be logged.
*
* @private
* @param {LogLevel} level - The log level of the message.
* @returns {boolean} `true` if the message should be logged; otherwise, `false`.
*/
private shouldLog(level: LogLevel): boolean {
const levels: LogLevel[] = ["debug", "info", "warn", "error", "fatal"];
return levels.indexOf(level) >= levels.indexOf(this.logLevel);
}

/**
* Creates a structured log message object.
*
* @private
* @param {LogLevel} level - The log level of the message.
* @param {string} message - The message to log.
* @param {Record<string, any> | null} [context] - Additional context for the log message.
* @returns {LogMessage} A structured log message object.
*/
private createLogMessage(level: LogLevel, message: string, context?: Record<string, any> | null): LogMessage {
return {
timestamp: getTimestamp(),
Expand All @@ -50,11 +103,20 @@ export class Logger {
};
}

/**
* Logs a message at the specified level, dispatching it to the registered handlers.
*
* @private
* @param {LogLevel} level - The log level of the message.
* @param {string} message - The message to log.
* @param {Record<string, any> | null} [context] - Additional context for the log message.
* @returns {void}
*/
private log(level: LogLevel, message: string, context?: Record<string, any> | null): void {
if (!this.shouldLog(level)) return;

const logMessage = this.createLogMessage(level, message, context);

// If no handlers are registered, use console as fallback
if (this.handlers.length === 0) {
console.warn('No handlers registered for logger, using console as fallback');
Expand All @@ -73,22 +135,57 @@ export class Logger {
});
}

/**
* Logs a debug message.
*
* @param {string} message - The message to log.
* @param {Record<string, any> | null} [context] - Additional context for the log message.
* @returns {void}
*/
debug(message: string, context?: Record<string, any> | null): void {
this.log("debug", message, context);
}

/**
* Logs an informational message.
*
* @param {string} message - The message to log.
* @param {Record<string, any> | null} [context] - Additional context for the log message.
* @returns {void}
*/
info(message: string, context?: Record<string, any> | null): void {
this.log("info", message, context);
}

/**
* Logs a warning message.
*
* @param {string} message - The message to log.
* @param {Record<string, any> | null} [context] - Additional context for the log message.
* @returns {void}
*/
warn(message: string, context?: Record<string, any> | null): void {
this.log("warn", message, context);
}

/**
* Logs an error message.
*
* @param {string} message - The message to log.
* @param {Record<string, any> | null} [context] - Additional context for the log message.
* @returns {void}
*/
error(message: string, context?: Record<string, any> | null): void {
this.log("error", message, context);
}

/**
* Logs a fatal error message.
*
* @param {string} message - The message to log.
* @param {Record<string, any> | null} [context] - Additional context for the log message.
* @returns {void}
*/
fatal(message: string, context?: Record<string, any> | null): void {
this.log("fatal", message, context);
}
Expand Down
13 changes: 13 additions & 0 deletions src/formatters/BaseFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
import { LogMessage } from "../types/LogMessage";

/**
* BaseFormatter is an abstract class that defines the structure for log message formatters.
* Subclasses should implement the `format` method to specify how log messages are formatted.
*
* @abstract
*/
export abstract class BaseFormatter {
/**
* Formats a log message into a string.
*
* @abstract
* @param {LogMessage} log - The log message to format.
* @returns {string} The formatted log message as a string.
*/
abstract format(log: LogMessage): string;
}
10 changes: 10 additions & 0 deletions src/formatters/JsonFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import { BaseFormatter } from "./BaseFormatter";
import { LogMessage } from "../types/LogMessage";

/**
* A concrete implementation of the BaseFormatter class.
* It formats log messages as JSON strings for structured logging.
*/
export class JsonFormatter extends BaseFormatter {
/**
* Formats a log message into a JSON string.
*
* @param {LogMessage} log - The log message to format.
* @returns {string} The formatted log message as a JSON string.
*/
format(log: LogMessage): string {
return JSON.stringify({
timestamp: log.timestamp,
Expand Down
16 changes: 16 additions & 0 deletions src/formatters/TextFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
import { BaseFormatter } from "./BaseFormatter";
import { LogMessage } from "../types/LogMessage";

/**
* A concrete implementation of the BaseFormatter class.
* It formats log messages into a customizable plain text format.
*/
export class TextFormatter extends BaseFormatter {
private template: string;

/**
* Constructs a TextFormatter with a customizable template.
*
* @param {string} [template] - The template string for formatting log messages.
* @default '[{timestamp}] [{level}] {message} {context}' - The default template.
*/
constructor(template: string = '[{timestamp}] [{level}] {message} {context}') {
super();
this.template = template;
}

/**
* Formats a log message into a string using the configured template.
*
* @param {LogMessage} log - The log message to format.
* @returns {string} The formatted log message as a string.
*/
format(log: LogMessage): string {
return this.template
.replace('{timestamp}', log.timestamp)
Expand Down
Loading

0 comments on commit 9edf85f

Please # to comment.