Back to LLD explorer
Behavioral• Patterns: Chain of Responsibility, Singleton, Strategy

Logger

Easy

Problem Summary

Design an asynchronous logging framework with tiered severity levels (INFO, DEBUG, ERROR), a chain of responsibility handler pipeline, and pluggable sinks (Console, File, Kafka).

Functional Scope

  • Support hierarchical severity levels: DEBUG < INFO < WARN < ERROR.
  • Chain of Responsibility filters and forwards messages based on threshold levels.
  • Pluggable Appender sinks: Console, Rolling File, Kafka/Network.
  • Non-blocking asynchronous write model using in-memory ring buffer or queue.
  • Singleton LogManager for thread-safe logger retrieval.

Entity-Relationship (ER) Schema

LogManager [1] --> [*] Logger
Logger [1] --> [1] LogHandler
LogHandler [1] --> [0..1] LogHandler (Next in Chain)
Logger [1] --> [*] LogAppender
LogAppender <|.. ConsoleAppender
LogAppender <|.. FileAppender

Design Approach

Employ Chain of Responsibility to decouple the sender of a log from its processing handlers, and Bridge/Strategy for output appenders (Console, File, Kafka) to isolate format layout from write destination.

Core Classes & Models

LogManager (Singleton registry of named loggers)Logger (Client-facing logging interface: info, debug, error)LogHandler (Abstract Chain of Responsibility link)DebugLogHandler, InfoLogHandler, ErrorLogHandler (Concrete handlers)LogAppender (Destination sink interface)ConsoleAppender, FileAppender (Concrete sinks)AsyncLogWorker (Decouples disk/network I/O from caller threads)
Code Blueprint
122 lines
/**
 * Low-Level Design: Logger Framework
 * Design Patterns Applied:
 *  - Chain of Responsibility Pattern: Sequentially delegating log records by severity level
 *  - Singleton Pattern: Global thread-safe logger context
 *  - Strategy Pattern: Pluggable appenders (Console, File, Kafka)
 */

import java.util.*;

enum LogLevel { INFO, DEBUG, WARN, ERROR, FATAL }

// --- [DESIGN PATTERN: Strategy] Pluggable log destination appender ---
interface LogAppender {
    void append(String message);
}

class ConsoleAppender implements LogAppender {
    @Override
    public void append(String message) {
        System.out.println("[CONSOLE] " + message);
    }
}

class FileAppender implements LogAppender {
    @Override
    public void append(String message) {
        System.out.println("[FILE /var/log/app.log] " + message);
    }
}

// --- [DESIGN PATTERN: Chain of Responsibility] Handler chain based on severity ---
abstract class LogHandler {
    protected final LogLevel level;
    protected LogHandler nextHandler;
    protected final List<LogAppender> appenders = new ArrayList<>();

    public LogHandler(LogLevel level) {
        this.level = level;
    }

    public LogHandler setNext(LogHandler next) {
        this.nextHandler = next;
        return next;
    }

    public void addAppender(LogAppender appender) {
        this.appenders.add(appender);
    }

    public void log(LogLevel level, String message) {
        if (this.level.ordinal() <= level.ordinal()) {
            write(level, message);
        }
        if (nextHandler != null) {
            nextHandler.log(level, message);
        }
    }

    protected void write(LogLevel level, String message) {
        String formatted = String.format("[%s] [%s] %s", new Date(), level, message);
        for (LogAppender appender : appenders) {
            appender.append(formatted);
        }
    }
}

class InfoHandler extends LogHandler {
    public InfoHandler() { super(LogLevel.INFO); }
}

class WarnHandler extends LogHandler {
    public WarnHandler() { super(LogLevel.WARN); }
}

class ErrorHandler extends LogHandler {
    public ErrorHandler() { super(LogLevel.ERROR); }
}

// --- [DESIGN PATTERN: Singleton] Global Logger entry point ---
class Logger {
    private static volatile Logger instance;
    private final LogHandler chain;

    private Logger() {
        InfoHandler info = new InfoHandler();
        WarnHandler warn = new WarnHandler();
        ErrorHandler error = new ErrorHandler();

        info.addAppender(new ConsoleAppender());
        warn.addAppender(new ConsoleAppender());
        error.addAppender(new ConsoleAppender());
        error.addAppender(new FileAppender());

        info.setNext(warn);
        warn.setNext(error);

        this.chain = info;
    }

    public static Logger getInstance() {
        if (instance == null) {
            synchronized (Logger.class) {
                if (instance == null) instance = new Logger();
            }
        }
        return instance;
    }

    public void info(String msg) { chain.log(LogLevel.INFO, msg); }
    public void warn(String msg) { chain.log(LogLevel.WARN, msg); }
    public void error(String msg) { chain.log(LogLevel.ERROR, msg); }
}

public class Main {
    public static void main(String[] args) {
        Logger logger = Logger.getInstance();
        logger.info("Application context initialized successfully.");
        logger.warn("Memory consumption threshold reaching 80%.");
        logger.error("Database connection lost: timeout after 5000ms.");
    }
}