2Brute2Best
πŸ”₯0
P1

Design a Pluggable Logger Framework

Easy High-frequency interview topic Pro OnlySingletonChain of ResponsibilityStrategy std::format std::print
⚑ 30-Second Interview Cheat SheetP1 · 45-Min Design Plan
3 Patterns Combined
1. Scope Boundaries

In a 45-minute LLD round, the interviewer wants to see clean decoupling between log producers, severity filtering, and I/O destinations. Scope the solution to synchronous in-memory routing with DEBUG < INFO < WARN < ERROR severity ordering, a pipeline of handlers, and pluggable sinks. Mention async lock-free ring buffers as a follow-up extension.

2. Core Noun Entities
LogLeveldata-struct
LogSinkinterface
LogHandlerstateful-class
Loggerstateful-class
3. Hotspot β†’ Pattern
Where log messages are written (Console vs File vs Memory) β†’ Strategy (LogSink interface)
Routing different severity levels to different combinations of sinks β†’ Chain of Responsibility (LogHandler)
Global ergonomic access across application modules β†’ Singleton (Meyers Logger::getInstance)
01

Problem statement & clarifying requirements

Design an extensible, enterprise-grade in-process logging framework. The system must support multiple severity levels (DEBUG, INFO, WARN, ERROR), route log messages through a configurable pipeline of level filters, and dispatch messages to pluggable output destinations (such as stdout console, file, or an in-memory ring buffer) without coupling log producers to specific destination implementations.

Questions you should ask the interviewer first
β€œWhat log levels need to be supported and what is their hierarchy?”

Support four standard levels: DEBUG < INFO < WARN < ERROR. A filter set to severity level L will process all messages with severity >= L.

β€œHow are output destinations (console, files, memory buffers) handled?”

Treat destinations as interchangeable strategies (LogSink). Each handler in the filter chain can hold zero or more sinks and broadcast messages to them.

β€œHow should client code interact with the logging framework?”

Provide a globally accessible Logger singleton facade offering ergonomic helper methods (debug(), info(), warn(), error()) while keeping the underlying filter pipeline configurable at startup.

β€œDoes logging need asynchronous background queueing or is synchronous dispatch sufficient for this interview round?”

Focus on the synchronous object-oriented architecture (Chain of Responsibility + Strategy + Singleton). Leave lock-free ring-buffer thread offloading as an explicit architectural extension.

02

First, sketch the design yourself

Spend 5 minutes listing the classes, who owns whom, and which interface hides the part that changes.

  1. 1.

    Never hardcode std::cout or printf inside handler classes. Decouple message filtering from I/O emission using the Strategy pattern (LogSink).

  2. 2.

    Avoid a giant switch (level) statement inside the logger. Use the Chain of Responsibility pattern to decouple level-specific routing and allow dynamic reordering or filtering.

  3. 3.

    Pay close attention to ownership: the Logger facade owns the root LogHandler uniquely via std::unique_ptr, while LogSink instances are shared across handlers using std::shared_ptr.

03

How to think through this problem (5-step walkthrough)

Step 1 Β· Scope the 45-minute target & core boundary

In a 45-minute LLD round, the interviewer wants to see clean decoupling between log producers, severity filtering, and I/O destinations. Scope the solution to synchronous in-memory routing with DEBUG < INFO < WARN < ERROR severity ordering, a pipeline of handlers, and pluggable sinks. Mention async lock-free ring buffers as a follow-up extension.

Step 2 Β· Extract candidate nouns (and reject false classes)

Not every noun in the prompt deserves its own class. Separate dumb data structs from stateful managers, and turn trivial nouns into fields or enums.

LogLevel
Value Struct

Ordered enum class LogLevel { DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3 } so severity comparisons (level >= minLevel_) are a single integer check.

LogSink
Abstract Interface

Output destinations (Console, File, MemoryBuffer, HTTP) vary independently from routing rules β€” classic Strategy interface.

LogHandler
Stateful Class / Manager

Each node in the Chain of Responsibility holds a minimum severity threshold, a list of attached sinks, and a unique_ptr<LogHandler> to the next handler.

Logger
Stateful Class / Manager

Thread-safe Meyers Singleton entry point (Logger::getInstance()) that owns the head of the handler chain.

FormattedString / LogLine
Reject as Class β†’ Field / Method

Trap noun β€” don't create a heavy class hierarchy for formatted text; a helper function or std::format call inside the sink is simpler and faster.

Step 3 Β· Draft the naive design & spot what breaks

A beginner writes a single Logger::log(string level, string msg) method with if (level == "ERROR") { writeToFile(msg); writeToConsole(msg); } else if (level == "INFO") .... Adding a new sink (like Datadog or an in-memory test buffer) or changing which levels go to which files forces editing the core Logger class and breaks the Open-Closed Principle.

Step 4 Β· Map change hotspots to design patterns
Where log messages are written (Console vs File vs Memory)β†’ Strategy (LogSink interface)

Hardcoding std::cout or std::ofstream inside Logger makes unit testing impossible without filesystem side effects.

Routing different severity levels to different combinations of sinks→ Chain of Responsibility (LogHandler)

A rigid switch(level) couples level filtering to specific sinks.

Global ergonomic access across application modules→ Singleton (Meyers Logger::getInstance)

Passing a Logger& into every constructor in the codebase pollutes business signatures.

Step 5 Β· Lock down C++23 ownership, invariants & concurrency
  • βœ“

    Ownership: Logger exclusively owns the head LogHandler via std::unique_ptr<LogHandler>, and each handler owns its next_ link via std::unique_ptr<LogHandler>.

  • βœ“

    Shared sinks: Because both the INFO handler and ERROR handler may write to the same console or file sink, handlers hold std::shared_ptr<LogSink>.

  • βœ“

    Chain invariant: Calling handle(level, msg) always forwards to next_->handle(level, msg) even when the current node also matches, so an ERROR message can be recorded in both the general log and the error-only alert sink.

04

Entities & design patterns used

Brute2Best Pro Content

Unlock Full UML Diagram & C++23 Solution

The complete UML diagram, production-ready C++23 code, follow-up extensions, and design pitfall analysis are part of Brute2Best Pro. All 7 foundation chapters are 100% free.

All 440 visual dry runs & algorithms
Interactive step-by-step playback with pointer & array state transitions
52 comprehensive LLD chapters
Interactive UML diagrams, real-world stories (Food Delivery, Trading, RPG), and practice solutions
40 C++ basics labs & hardware memory models
Vtables, byte layouts, assembly insights, and multi-threading race conditions
Production-ready C++23 source code
Modern idioms, clean contracts, time/space complexity derivations, and interview takeaways

First anchor question per pattern, Part 1 LLD & Module 1 C++ basics are 100% Free forever.

Systems Integration

Related C++ Systems & Algorithmic Foundations

Deep dive into underlying layers