Bird
Raised Fist0

Design a Logging Framework - Log Levels, Appenders, Formatters, Log Rotation

LLDmediumInterview freq: medium
Choose your preparation mode4 modes available
🏗️
Design a Logging Framework - Log Levels, Appenders, Formatters, Log Rotation
A flexible, extensible logging framework supporting multiple log levels, appenders, formatters, and log rotation.
mediumInfrastructure and Utility SystemsFreq: medium
Prerequisites:Singleton PatternStrategy Pattern
⚙️
Functional Requirements
  • Support standard log levels: DEBUG, INFO, WARN, ERROR, FATAL.
  • Allow multiple appenders to be attached to a Logger instance.
  • Format log messages using pluggable Formatter implementations.
  • Rotate log files when they reach a configured size or time interval.
  • Ensure thread-safe logging to prevent message interleaving or loss.
  • Allow runtime configuration changes without restarting the application.
  • Provide a singleton Logger instance per application context.
👤
Use Cases
  • Log messages at different severity levels (DEBUG, INFO, WARN, ERROR).
  • Configure multiple appenders (Console, File, Remote).
  • Apply custom formatting to log messages.
  • Rotate log files based on size or time.
  • Enable or disable logging dynamically.
  • Support concurrent logging from multiple threads.
🧑‍💻
Actors
  • Application Developer
  • System Administrator
  • Logging Framework
❓
Clarifying Questions to Ask
  • How many concurrent threads will typically log simultaneously?
  • Should the framework support asynchronous logging or only synchronous?
  • Is there a pricing or resource constraint for log storage or rotation frequency?
  • What is the expected maximum log file size before rotation?
  • Should appenders be dynamically added or removed at runtime?
  • Are there any specific performance SLAs for logging latency?
  • How should the framework behave if an appender fails (e.g., disk full)?
Out of Scope
✗ Distributed log aggregation and analysis.
✗ Network protocols for remote logging beyond basic appenders.
✗ Advanced filtering rules beyond log levels.
✗ GUI or visualization of logs.
🔢
Enumerations
enum LogLevel
DEBUG | INFO | WARN | ERROR | FATAL
Defines discrete log severity levels to prevent typo bugs and enable exhaustive switch statements for filtering and routing logs.
enum RotationPolicy
SIZE_BASED | TIME_BASED
Specifies log rotation triggers, enabling flexible rotation strategies and preventing misuse of magic constants.
📦
Logger
3 fields
private volatile static Logger instanceprivate List<Appender> appendersprivate LogLevel currentLogLevel
Singleton entry point for logging; manages log level filtering and delegates formatted messages to appenders.
Why thisSingleton pattern ensures a single Logger instance per application context, avoiding multiple conflicting loggers. Double-checked locking with volatile is a proven Java idiom for thread-safe lazy initialization.
Why notUsing static utility methods without instance would limit extensibility and configuration. Dependency injection frameworks add complexity unnecessary for this core utility.
public static Logger getInstance()Returns the singleton Logger instance using double-checked locking with volatile to prevent JVM instruction reordering.
public synchronized void log(LogLevel level, String message)Logs a message if level is enabled; formats and dispatches to all appenders atomically to prevent interleaving.
public synchronized void addAppender(Appender appender)Adds an appender to the Logger's list in a thread-safe manner.
public synchronized void setLogLevel(LogLevel level)Sets the minimum log level to filter messages.
📦
Appender
1 fields
protected Formatter formatter
Abstract base for output destinations; formats messages and writes them to target sinks.
Why thisStrategy pattern allows flexible formatting and output destinations without changing Logger code. Abstract base class enables code reuse and polymorphism.
Why notHardcoding output destinations or formats would reduce extensibility and violate open-closed principle.
public abstract synchronized void append(String formattedMessage)Writes the formatted message to the output destination; synchronized to ensure thread-safe writes.
public void setFormatter(Formatter formatter)Sets the formatter strategy for this appender.
📦
ConsoleAppender
1 fields
none
Concrete Appender that writes formatted logs to the system console.
Why thisConsole output is a common logging target for development and debugging.
Why notUsing external logging libraries or frameworks would add dependencies and complexity.
public synchronized void append(String formattedMessage)Prints the formatted message to System.out in a thread-safe manner.
📦
FileAppender
5 fields
private File logFileprivate RotationPolicy rotationPolicyprivate long maxFileSizeprivate volatile long currentFileSizeprivate final Object rotationLock
Writes logs to a file and manages log rotation based on policy and size.
Why thisFileAppender supports persistent log storage with rotation to prevent disk overuse. Synchronization ensures thread safety.
Why notUsing external log rotation tools would complicate deployment and reduce control.
public synchronized void append(String formattedMessage)Writes message to file, updates currentFileSize atomically, and triggers rotation if needed.
private synchronized void rotateLogFile()Performs log rotation by closing current file and creating a new one; synchronized to prevent concurrent rotations.
📦
Formatter
0 fields
Strategy interface for formatting log messages.
Why thisStrategy pattern allows flexible message formatting without changing appenders or logger.
Why notHardcoded formats reduce flexibility and reuse.
public abstract String format(LogLevel level, String message, long timestamp)Formats the log message with level and timestamp.
📦
SimpleFormatter
0 fields
Formats log messages in a simple human-readable format.
Why thisSimpleFormatter provides a default easy-to-read format for logs.
Why notComplex pattern-based formatting is optional and handled by PatternFormatter.
public String format(LogLevel level, String message, long timestamp)Returns a string with timestamp, level, and message concatenated.
📦
PatternFormatter
1 fields
private String pattern
Formats log messages based on a user-defined pattern string.
Why thisPatternFormatter enables customizable log formats per user requirements.
Why notHardcoded formats limit flexibility.
public String format(LogLevel level, String message, long timestamp)Parses the pattern and replaces placeholders with actual values.
🔗
Relationships
Logger◇──►Appender[1..*]
aggregationLogger manages appenders but appenders can exist independently
Appender◇──►Formatter[1]
aggregationAppender holds a reference to Formatter; Formatter can be shared or replaced
ConsoleAppender──►Appender[1]
inheritanceConsoleAppender is a subtype of Appender
FileAppender──►Appender[1]
inheritanceFileAppender is a subtype of Appender
🗄️
LogEntries
PK: logDate
logDate (Date)timestamp (Long)logLevel (String)message (String)appenderId (String)
Stores log entries partitioned by date for efficient querying and clustering by timestamp.
🗄️
AppenderConfig
PK: appenderId
appenderId (String)configVersion (Integer)formatterPattern (String)rotationPolicy (String)maxFileSize (Long)
Stores configuration for each appender with versioning for runtime updates.
🔌
POST /log
Request
{
  "logLevel": "String",
  "message": "String",
  "timestamp": "Long"
}
Response
{
  "status": "String",
  "message": "String"
}
Endpoint to submit a log message synchronously.
🔌
POST /config/appender
Request
{
  "appenderId": "String",
  "formatterPattern": "String",
  "rotationPolicy": "String",
  "maxFileSize": "Long"
}
Response
{
  "status": "String",
  "message": "String"
}
Endpoint to update appender configuration at runtime.
🔌
GET /logs
Request
{
  "startDate": "String",
  "endDate": "String",
  "logLevel": "String"
}
Response
{
  "logs": "List<LogEntry>"
}
Endpoint to query logs filtered by date and level.

Practice

(1/5)
1. In designing the Wallet System, should the Account class be an abstract class or an interface, considering it holds state like balance and transaction history? Why?
easy
A. Interface, because it enforces method signatures without implementation, suitable for stateful classes.
B. Abstract class, because it prevents multiple inheritance issues common with interfaces.
C. Interface, because Java interfaces can now hold state with default methods.
D. Abstract class, because it allows sharing state and common behavior like balance management across subclasses.

Solution

  1. Step 1: Analyze state and behavior requirements

    The Account class holds mutable state (balance, transaction history) and common behavior (debit, credit), which requires shared implementation and fields.
  2. Step 2: Abstract class vs interface for stateful behavior

    Interfaces cannot hold instance state (fields), only constants and default methods without backing fields. Abstract classes allow fields and method implementations, making them suitable for shared state and behavior.
  3. Final Answer:

    Option D -> Option D
  4. Quick Check:

    Stateful class with shared behavior -> Abstract class [check]
Hint: State + behavior shared -> abstract class, not interface [check]
Common Mistakes:
2. In the Elevator System, the assignElevatorToFloorRequest() method checks if an elevator is available before assigning it. Without synchronizing this method, what exact sequence of thread interleaving can cause two threads to assign the same elevator simultaneously?
medium
A. Thread 1 assigns elevator, then Thread 2 assigns a different elevator without conflict.
B. Thread 1 and Thread 2 both call assignElevatorToFloorRequest() but one waits until the other finishes.
C. Thread 1 checks elevator availability -> true, context switch; Thread 2 checks availability -> true; both assign the same elevator concurrently.
D. Thread 1 and Thread 2 both fail to find an available elevator and return null.

Solution

  1. Step 1: Identify check-then-act race condition

    Without synchronization, Thread 1 reads elevator as available, then context switches before assignment.
  2. Step 2: Thread 2 also reads elevator as available and proceeds to assign it, causing double-booking.

    Both threads assign the same elevator because the availability check and assignment are not atomic.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Unsynchronized check-then-act causes double assignment [check]
Hint: Check-then-act without sync allows double assignment race [check]
Common Mistakes:
3. In a multi-threaded Java Observer implementation, if notifyObservers() is not synchronized, which exact sequence of events can cause observers to receive inconsistent or duplicate updates?
medium
A. Thread 1 checks observer list size -> 3, context switch; Thread 2 removes an observer -> size 2; Thread 1 continues notifying 3 observers, causing duplicate notifications.
B. Thread 1 calls notifyObservers(), Thread 2 waits until Thread 1 finishes, then calls notifyObservers().
C. Thread 1 and Thread 2 both add observers simultaneously, causing a ConcurrentModificationException during notification.
D. Thread 1 updates subject state, Thread 2 reads old state before notification, causing stale data delivery.

Solution

  1. Step 1: Identify race condition in notifyObservers()

    Without synchronization, Thread 1 reads observer list size as 3, then context switches.
  2. Step 2: Thread 2 modifies observer list by removing one observer, reducing size to 2.

    Thread 1 resumes and tries to notify 3 observers, causing out-of-date or duplicate notifications.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Unsynchronized notifyObservers() -> stale observer list read -> inconsistent notifications [check]
Hint: Unsynchronized notify reads stale observer list -> duplicate notifications [check]
Common Mistakes:
4. In a vending machine using the State pattern, if the method selectItem() is not synchronized, which exact thread interleaving causes dispensing the same item twice?
medium
A. Thread 1 checks item availability -> true, context switch, Thread 2 checks availability -> true, both dispense item
B. Thread 1 and Thread 2 both wait on a lock, causing deadlock
C. Thread 1 updates inventory, Thread 2 reads old inventory before update
D. Thread 1 calls selectItem(), Thread 2 calls cancel(), both proceed concurrently

Solution

  1. Step 1: Identify the check-then-act race

    Without synchronization, both threads can read item availability as true before either decrements inventory.
  2. Step 2: Trace the interleaving

    Thread 1 checks availability -> true, context switch; Thread 2 checks availability -> true; both proceed to dispense, causing double dispense.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Unsynchronized check-then-act -> double dispense [check]
Hint: Check-then-act without sync -> double dispense race [check]
Common Mistakes:
5. Adding a WeekendFeeStrategy to the parking lot pricing requires which minimum change to comply with SOLID principles?
medium
A. Modify the existing FeeCalculator class to add weekend logic with an if-else block.
B. Add a boolean isWeekend flag to Vehicle and change pricing logic accordingly.
C. Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without modifying existing classes.
D. Combine all fee strategies into one large class with multiple methods for each pricing rule.

Solution

  1. Step 1: Identify the SOLID principle involved

    The Open-Closed Principle (OCP) requires that classes be open for extension but closed for modification.
  2. Step 2: Evaluate options against OCP

    Create a new WeekendFeeStrategy class implementing FeeStrategy and inject it without modifying existing classes. respects OCP by adding a new strategy class without modifying existing code. Add a boolean isWeekend flag to Vehicle and change pricing logic accordingly. violates OCP by modifying Vehicle state, which is unrelated to pricing and causes tight coupling.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    New strategy class -> OCP-compliant extension [check]
Hint: OCP: add new class, don't modify existing ones [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard