DEV Community

Dev Cookies
Dev Cookies

Posted on

Logger System LLD (Java)

Problem Statement

Design a Logger Framework that supports:

  • Multiple Log Levels

    • DEBUG
    • INFO
    • WARN
    • ERROR
    • FATAL
  • Multiple Output Destinations

    • Console
    • File
    • Database
    • Kafka (future)
    • ElasticSearch (future)
  • Configurable Minimum Log Level

  • Thread Safe

  • Easy to extend


Functional Requirements

  • Log message
  • Timestamp
  • Log Level
  • Destination
  • Format message

Non Functional Requirements

  • Extensible
  • Low Coupling
  • High Cohesion
  • Open Closed Principle
  • Thread Safe
  • Singleton Logger

Interview Expectation

Application
│
▼
Logger (Singleton)
│
▼
LogManager
│
▼
Formatter
│
▼
Appender
├── ConsoleAppender
├── FileAppender
├── DatabaseAppender
Enter fullscreen modeExit fullscreen mode

Design Patterns Used

PatternWhy
SingletonOne Logger instance
StrategyDifferent Appenders
FactoryCreate appenders
BuilderBuild LogMessage
Chain of Responsibility (Optional)Multiple appenders
Dependency InjectionLoose coupling

Step 1: Log Level

publicenumLogLevel{DEBUG(1),INFO(2),WARN(3),ERROR(4),FATAL(5);privatefinalintpriority;LogLevel(intpriority){this.priority=priority;}publicintgetPriority(){returnpriority;}}
Enter fullscreen modeExit fullscreen mode

Step 2: Log Message

importjava.time.LocalDateTime;publicclassLogMessage{privatefinalLogLevellevel;privatefinalStringmessage;privatefinalLocalDateTimetimestamp;publicLogMessage(LogLevellevel,Stringmessage){this.level=level;this.message=message;this.timestamp=LocalDateTime.now();}publicLogLevelgetLevel(){returnlevel;}publicStringgetMessage(){returnmessage;}publicLocalDateTimegetTimestamp(){returntimestamp;}}
Enter fullscreen modeExit fullscreen mode

Step 3: Formatter

publicinterfaceLogFormatter{Stringformat(LogMessagemessage);}
Enter fullscreen modeExit fullscreen mode

Default Formatter

publicclassDefaultFormatterimplementsLogFormatter{@OverridepublicStringformat(LogMessagemessage){returnString.format("[%s] [%s] %s",message.getTimestamp(),message.getLevel(),message.getMessage());}}
Enter fullscreen modeExit fullscreen mode

Step 4: Appender

publicinterfaceAppender{voidappend(LogMessagemessage);}
Enter fullscreen modeExit fullscreen mode

Console Appender

publicclassConsoleAppenderimplementsAppender{privatefinalLogFormatterformatter;publicConsoleAppender(LogFormatterformatter){this.formatter=formatter;}@Overridepublicvoidappend(LogMessagemessage){System.out.println(formatter.format(message));}}
Enter fullscreen modeExit fullscreen mode

File Appender

importjava.io.FileWriter;importjava.io.IOException;publicclassFileAppenderimplementsAppender{privatefinalLogFormatterformatter;publicFileAppender(LogFormatterformatter){this.formatter=formatter;}@Overridepublicvoidappend(LogMessagemessage){try(FileWriterwriter=newFileWriter("app.log",true)){writer.write(formatter.format(message));writer.write("\n");}catch(IOExceptione){e.printStackTrace();}}}
Enter fullscreen modeExit fullscreen mode

Step 5: Logger

importjava.util.List;publicclassLogger{privatestaticvolatileLoggerinstance;privatefinalList<Appender>appenders;privateLogLevelminimumLevel;privateLogger(List<Appender>appenders,LogLevelminimumLevel){this.appenders=appenders;this.minimumLevel=minimumLevel;}publicstaticLoggergetInstance(List<Appender>appenders,LogLevellevel){if(instance==null){synchronized(Logger.class){if(instance==null){instance=newLogger(appenders,level);}}}returninstance;}publicvoidlog(LogLevellevel,Stringmessage){if(level.getPriority()<minimumLevel.getPriority())return;LogMessagelogMessage=newLogMessage(level,message);for(Appenderappender:appenders)appender.append(logMessage);}}
Enter fullscreen modeExit fullscreen mode

Client

importjava.util.List;publicclassMain{publicstaticvoidmain(String[]args){LogFormatterformatter=newDefaultFormatter();Appenderconsole=newConsoleAppender(formatter);Appenderfile=newFileAppender(formatter);Loggerlogger=Logger.getInstance(List.of(console,file),LogLevel.INFO);logger.log(LogLevel.INFO,"Application Started");logger.log(LogLevel.DEBUG,"Debug Message");logger.log(LogLevel.ERROR,"Database Down");}}
Enter fullscreen modeExit fullscreen mode

Output

[2026-07-09T10:30] [INFO] Application Started
[2026-07-09T10:31] [ERROR] Database Down
Enter fullscreen modeExit fullscreen mode

(DEBUG ignored)


UML

 +------------------+
| Logger |
+------------------+
| minimumLevel |
| appenders |
+------------------+
|
-------------------------------
| |
▼ ▼
+----------------+ +----------------+
| Appender | | LogFormatter |
+----------------+ +----------------+
| append() | | format() |
+----------------+ +----------------+
▲
│
-----------------------------
| | |
▼ ▼ ▼
Console File Database
Appender Appender Appender
▲
|
+--------------+
| LogMessage |
+--------------+
| level |
| timestamp |
| message |
+--------------+
Enter fullscreen modeExit fullscreen mode

SOLID Principles

PrincipleApplication
SRPLogMessage, Appender, Formatter, Logger each have a single responsibility.
OCPAdd new appenders or formatters without modifying existing classes.
LSPAny Appender implementation can replace another.
ISPSmall focused interfaces (Appender, LogFormatter).
DIPLogger depends on abstractions rather than concrete implementations.

Common Interview Follow-up Questions

  1. How would you support asynchronous logging?
  • Introduce a BlockingQueue<LogMessage> and a dedicated worker thread or thread pool that consumes log events and dispatches them to appenders.
  1. How would you prevent logging from blocking application threads?
  • Use non-blocking queues, batching, and asynchronous appenders with configurable backpressure policies.
  1. How would you support log rotation?
  • Create a RollingFileAppender that rotates files based on size or time (daily/hourly) and manages retention.
  1. How would you support multiple log formats?
  • Implement additional LogFormatter strategies such as JsonFormatter, XmlFormatter, or custom templates.
  1. How would you enable configuration from a properties or YAML file?
  • Build a LoggerConfiguration component and an AppenderFactory to instantiate appenders based on external configuration.
  1. How would you make the logger thread-safe?
  • Keep shared state immutable where possible, use thread-safe collections if appenders are modified at runtime, and ensure appenders handle concurrent writes correctly.
  1. How would you add contextual information (e.g., request ID, user ID)?
  • Introduce a Mapped Diagnostic Context (MDC) using ThreadLocal to automatically enrich each LogMessage.
  1. How would you support filtering?
  • Add a LogFilter abstraction (Strategy pattern) to filter messages by package, marker, or custom predicates before dispatching.

Possible Extensions

  • AsyncLogger
  • RollingFileAppender
  • DatabaseAppender
  • KafkaAppender
  • ElasticSearchAppender
  • JsonFormatter
  • XMLFormatter
  • LoggerFactory
  • LoggerConfiguration
  • LogFilter
  • MDC (Mapped Diagnostic Context)
  • Rate limiting and sampling
  • Dynamic log level updates
  • Metrics for dropped/processed log events

This design demonstrates strong OOP fundamentals, SOLID principles, and extensibility while remaining concise enough to explain in a typical 30–45 minute LLD interview.

Top comments (0)