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
Design Patterns Used
| Pattern | Why |
|---|---|
| Singleton | One Logger instance |
| Strategy | Different Appenders |
| Factory | Create appenders |
| Builder | Build LogMessage |
| Chain of Responsibility (Optional) | Multiple appenders |
| Dependency Injection | Loose coupling |
Step 1: Log Level
publicenumLogLevel{DEBUG(1),INFO(2),WARN(3),ERROR(4),FATAL(5);privatefinalintpriority;LogLevel(intpriority){this.priority=priority;}publicintgetPriority(){returnpriority;}}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;}}Step 3: Formatter
publicinterfaceLogFormatter{Stringformat(LogMessagemessage);}Default Formatter
publicclassDefaultFormatterimplementsLogFormatter{@OverridepublicStringformat(LogMessagemessage){returnString.format("[%s] [%s] %s",message.getTimestamp(),message.getLevel(),message.getMessage());}}Step 4: Appender
publicinterfaceAppender{voidappend(LogMessagemessage);}Console Appender
publicclassConsoleAppenderimplementsAppender{privatefinalLogFormatterformatter;publicConsoleAppender(LogFormatterformatter){this.formatter=formatter;}@Overridepublicvoidappend(LogMessagemessage){System.out.println(formatter.format(message));}}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();}}}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);}}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");}}Output
[2026-07-09T10:30] [INFO] Application Started
[2026-07-09T10:31] [ERROR] Database Down
(DEBUG ignored)
UML
+------------------+
| Logger |
+------------------+
| minimumLevel |
| appenders |
+------------------+
|
-------------------------------
| |
▼ ▼
+----------------+ +----------------+
| Appender | | LogFormatter |
+----------------+ +----------------+
| append() | | format() |
+----------------+ +----------------+
▲
│
-----------------------------
| | |
▼ ▼ ▼
Console File Database
Appender Appender Appender
▲
|
+--------------+
| LogMessage |
+--------------+
| level |
| timestamp |
| message |
+--------------+
SOLID Principles
| Principle | Application |
|---|---|
| SRP | LogMessage, Appender, Formatter, Logger each have a single responsibility. |
| OCP | Add new appenders or formatters without modifying existing classes. |
| LSP | Any Appender implementation can replace another. |
| ISP | Small focused interfaces (Appender, LogFormatter). |
| DIP | Logger depends on abstractions rather than concrete implementations. |
Common Interview Follow-up Questions
- 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.
- How would you prevent logging from blocking application threads?
- Use non-blocking queues, batching, and asynchronous appenders with configurable backpressure policies.
- How would you support log rotation?
- Create a
RollingFileAppenderthat rotates files based on size or time (daily/hourly) and manages retention.
- How would you support multiple log formats?
- Implement additional
LogFormatterstrategies such asJsonFormatter,XmlFormatter, or custom templates.
- How would you enable configuration from a properties or YAML file?
- Build a
LoggerConfigurationcomponent and anAppenderFactoryto instantiate appenders based on external configuration.
- 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.
- How would you add contextual information (e.g., request ID, user ID)?
- Introduce a
Mapped Diagnostic Context (MDC)usingThreadLocalto automatically enrich eachLogMessage.
- How would you support filtering?
- Add a
LogFilterabstraction (Strategy pattern) to filter messages by package, marker, or custom predicates before dispatching.
Possible Extensions
AsyncLoggerRollingFileAppenderDatabaseAppenderKafkaAppenderElasticSearchAppenderJsonFormatterXMLFormatterLoggerFactoryLoggerConfigurationLogFilterMDC(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)