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.");
}
}