Low-Level Design of a Logging Library Shared by Many Threads

Read the full interview experience this question came from →

Quick Overview

Low-level design question about an in-process logging library that many threads call at the same time. It tests class modeling for loggers, records, formatters and output destinations, plus thread safety, the hand-off to a background writer, ordering, overflow behavior, failure isolation and clean shutdown.

Low-Level Design of a Logging Library Shared by Many Threads

Company: Microsoft

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Other

Design a logging framework for a service in which many threads write log messages at the same time. Model the classes and how they relate to each other, then implement the methods that carry the most risk: the call an application thread makes to log a message, and the machinery that moves records to their destinations. You do not need to implement every method; the focus is a sound class structure and correct concurrent behavior on the key paths. For practice, assume an in-process library with severity levels, named loggers whose minimum level can be configured, more than one output destination (for example the console and a file), and pluggable formatting. Application threads should not have to wait on disk writes. ### Constraints and Clarifications - Any number of application threads may log at the same moment. - Records produced by one thread must reach each destination in the order that thread produced them. - A failing destination (full disk, closed stream) must not crash or deadlock the application. - Use any language; methods that are not on the critical path can be sketched. ### Clarifying Questions - Is it acceptable to lose buffered records if the process crashes, or must some levels be durable before the logging call returns? - When threads produce records faster than they can be written, should callers wait, or should records be dropped, and which ones first? - Must levels and destinations be changeable at runtime without a restart? - Is this a single process, or do logs from many processes need to be combined? (Assume a single process.) ### Part 1 — Model the classes Identify the core classes and interfaces, the responsibility, key fields and methods of each, and the relationships between them (who creates, owns or references whom). Show how a call such as `logger.info("order %s shipped", order_id)` ends up in both the console and a file. ```hint Separate what varies List the decisions a single logging call involves, such as whether to emit, how to render and where to write, and ask which of them should be swappable independently. ``` #### What This Part Should Cover - A clear split between creating records, filtering by level, formatting and writing - Interfaces that let new destinations and formats be added without touching existing classes - How code obtains a logger and how configuration reaches it without duplicate instances - A cheap level check that happens before any expensive work ### Part 2 — Make it safe and fast under concurrency Implement the logging call path and the hand-off from application threads to the destinations so that it is thread-safe, keeps callers off the I/O path and shuts down cleanly. Explain which objects are shared, what protects each one, and what happens when the buffer fills up or a destination throws. ```hint Count the writers Decide how many threads should ever touch a given file handle, and what that choice implies for locking inside each destination. ``` #### What This Part Should Cover - The shared state and the synchronization that protects it - Ordering guarantees across the hand-off from callers to the writing side - Overflow behavior and isolation of a failing destination - Flush and shutdown semantics that do not lose records already accepted ### What a Strong Answer Covers - A cohesive class model with single-purpose classes and clean extension points - Thread safety reasoned object by object, with minimal locking on the caller's path - Callers decoupled from I/O through a bounded hand-off, with an explicit overflow policy - Ordering, durability and shutdown behavior stated precisely - Failure isolation, including errors raised inside the framework itself - Working code for the critical methods ### Follow-up Questions - How would you attach request-scoped context, such as a request ID, that follows work across threads? - How would you guarantee that an error record survives a crash that happens immediately after it is logged? - How would you let operators change a logger's level at runtime without a restart? - At very high volume the single shared buffer becomes a contention point. What would you change?

Overview: Low-level design question about an in-process logging library that many threads call at the same time. It tests class modeling for loggers, records, formatters and output destinations, plus thread safety, the hand-off to a background writer, ordering, overflow behavior, failure isolation and clean shutdown.

Read the full Microsoft Software Engineer interview experience this question came from

|Home/Software Engineering Fundamentals/Microsoft
Microsoft logo
Microsoft
Apr 2, 2026
mediumSoftware EngineerOtherSoftware Engineering Fundamentals
0
0

Design a logging framework for a service in which many threads write log messages at the same time. Model the classes and how they relate to each other, then implement the methods that carry the most risk: the call an application thread makes to log a message, and the machinery that moves records to their destinations. You do not need to implement every method; the focus is a sound class structure and correct concurrent behavior on the key paths.

For practice, assume an in-process library with severity levels, named loggers whose minimum level can be configured, more than one output destination (for example the console and a file), and pluggable formatting. Application threads should not have to wait on disk writes.

Constraints and Clarifications

  • Any number of application threads may log at the same moment.
  • Records produced by one thread must reach each destination in the order that thread produced them.
  • A failing destination (full disk, closed stream) must not crash or deadlock the application.
  • Use any language; methods that are not on the critical path can be sketched.

Clarifying Questions Guidance

  • Is it acceptable to lose buffered records if the process crashes, or must some levels be durable before the logging call returns?
  • When threads produce records faster than they can be written, should callers wait, or should records be dropped, and which ones first?
  • Must levels and destinations be changeable at runtime without a restart?
  • Is this a single process, or do logs from many processes need to be combined? (Assume a single process.)

Part 1 — Model the classes

Identify the core classes and interfaces, the responsibility, key fields and methods of each, and the relationships between them (who creates, owns or references whom). Show how a call such as logger.info("order %s shipped", order_id) ends up in both the console and a file.

What This Part Should Cover Guidance

  • A clear split between creating records, filtering by level, formatting and writing
  • Interfaces that let new destinations and formats be added without touching existing classes
  • How code obtains a logger and how configuration reaches it without duplicate instances
  • A cheap level check that happens before any expensive work

Part 2 — Make it safe and fast under concurrency

Implement the logging call path and the hand-off from application threads to the destinations so that it is thread-safe, keeps callers off the I/O path and shuts down cleanly. Explain which objects are shared, what protects each one, and what happens when the buffer fills up or a destination throws.

What This Part Should Cover Guidance

  • The shared state and the synchronization that protects it
  • Ordering guarantees across the hand-off from callers to the writing side
  • Overflow behavior and isolation of a failing destination
  • Flush and shutdown semantics that do not lose records already accepted

What a Strong Answer Covers Guidance

  • A cohesive class model with single-purpose classes and clean extension points
  • Thread safety reasoned object by object, with minimal locking on the caller's path
  • Callers decoupled from I/O through a bounded hand-off, with an explicit overflow policy
  • Ordering, durability and shutdown behavior stated precisely
  • Failure isolation, including errors raised inside the framework itself
  • Working code for the critical methods

Follow-up Questions Guidance

  • How would you attach request-scoped context, such as a request ID, that follows work across threads?
  • How would you guarantee that an error record survives a crash that happens immediately after it is logged?
  • How would you let operators change a logger's level at runtime without a restart?
  • At very high volume the single shared buffer becomes a contention point. What would you change?