# spdlog log_msg Internal Structure: How Log Records Flow Through Sinks

> Explore spdlog's log_msg internal structure and how it efficiently flows through sinks. Learn about zero-copy record delivery for optimal performance.

- Repository: [Gabi Melman/spdlog](https://github.com/gabime/spdlog)
- Tags: internals
- Published: 2026-08-06

---

**`spdlog` constructs an immutable `spdlog::details::log_msg` object for every logging request and passes it by const-reference through the sink pipeline, ensuring zero-copy efficiency and consistent record delivery.**

The `spdlog` library (gabime/spdlog) centralizes log metadata in a single lightweight structure called `log_msg`. Understanding the internal layout of this struct and its journey from logger to output destination is crucial for implementing high-performance custom sinks or debugging formatting issues.

## Anatomy of the log_msg Structure

The `log_msg` definition resides in [`include/spdlog/details/log_msg.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/log_msg.h), with constructors implemented in [`include/spdlog/details/log_msg-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/log_msg-inl.h). This aggregate struct encapsulates everything a sink needs to render or forward a record.

### Core Data Members

Each `log_msg` instance contains the following fields:

- **`logger_name`** (`string_view_t`): Identifies the originating logger.
- **`level`** (`level::level_enum`): Severity classification (trace, debug, info, warning, error, critical).
- **`time`** (`log_clock::time_point`): Timestamp captured at construction via `os::now()`.
- **`thread_id`** (`size_t`): Operating system thread identifier (omitted when `SPDLOG_NO_THREAD_ID` is defined).
- **`source`** (`source_loc`): Source code location containing `__FILE__`, `__LINE__`, and `__func__`.
- **`payload`** (`string_view_t`): The raw or formatted message content.
- **`color_range_start`** / **`color_range_end`** (`size_t`, mutable): Byte offsets indicating which portion of the payload should receive ANSI color codes.

### Immutability and Thread Safety

All members are either fundamental value types or `string_view_t` references to external storage. This design makes the object effectively immutable after construction, allowing the logger to instantiate one `log_msg` and safely distribute const-references to multiple sinks without requiring per-message synchronization.

## The Sink Pipeline: Passing log_msg by Const Reference

The path from logger invocation to final output follows a strict three-layer architecture defined in [`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h) and the sink headers.

### Logger Construction and Formatting

When you invoke a logging method such as `logger::info()`, the logger constructs a `log_msg` and populates its fields:

```cpp
// Conceptual flow inside spdlog::logger::log(...)
details::log_msg msg{logger_name_, lvl, fmt::format_to(...)};
msg.time = os::now();
msg.thread_id = os::thread_id();

```

The logger then executes the registered **pattern formatter** (defined in [`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h)) which may mutate the `payload` string to include timestamps and set the `color_range_start` and `color_range_end` offsets for terminal colorization.

### The base_sink Abstraction

Every thread-safe sink inherits from `base_sink<Mutex>` in [`include/spdlog/sinks/base_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/base_sink.h). The logger iterates over its vector of `std::shared_ptr<sink>` objects and invokes the **non-virtual final** method:

```cpp
virtual void log(const details::log_msg &msg) final override;

```

The implementation in [`include/spdlog/sinks/base_sink-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/base_sink-inl.h) handles locking before dispatch:

```cpp
void base_sink<Mutex>::log(const details::log_msg &msg)
{
    std::lock_guard<Mutex> lock{mutex_};
    sink_it_(msg);  // Pure virtual dispatch to concrete implementation
}

```

By passing `const details::log_msg &`, the pipeline eliminates copy overhead while guaranteeing that every sink receives an identical view of the log record.

### Concrete Sink Implementation

Concrete sinks implement the pure virtual `sink_it_(const details::log_msg &msg)` method. For example, the file sink in [`include/spdlog/sinks/basic_file_sink-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/basic_file_sink-inl.h) writes the payload directly:

```cpp
void basic_file_sink<Mutex>::sink_it_(const details::log_msg &msg)
{
    // Write msg.payload to the open file stream
    file_helper_.write(msg.payload);
}

```

Console sinks such as `stdout_color_sink` access the `color_range` members to wrap specific payload segments with ANSI escape codes before writing to `std::cout`.

## Practical Example: Creating and Routing log_msg

This complete example demonstrates the full lifecycle from logger invocation to file output:

```cpp
#include <spdlog/spdlog.h>
#include <spdlog/sinks/basic_file_sink.h>

int main()
{
    // 1. Create a thread-safe file sink
    auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("example.log");
    
    // 2. Build a logger attached to the sink
    spdlog::logger my_logger("my_logger", {file_sink});
    
    // 3. Emit a message
    my_logger.info("Hello {}!", "world");
}

```

**Execution flow:**

1. `my_logger.info()` constructs a `log_msg` with payload `"Hello world!"`.
2. The default pattern formatter populates timestamp metadata in `msg.payload` and calculates `color_range` offsets.
3. `base_sink<std::mutex>::log(msg)` acquires the lock and forwards to `basic_file_sink::sink_it_(msg)`.
4. The file sink writes the final formatted message to `example.log`.

## Essential Source Files

Understanding the `log_msg` lifecycle requires examining these specific files:

- **[`include/spdlog/details/log_msg.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/log_msg.h)** – Structure definition and member layout.
- **[`include/spdlog/details/log_msg-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/log_msg-inl.h)** – Constructors initializing timestamp, thread ID, and source location.
- **[`include/spdlog/sinks/base_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/base_sink.h)** – Abstract template base class declaring the final `log()` method.
- **[`include/spdlog/sinks/base_sink-inl.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/base_sink-inl.h)** – Implementation of thread-safe forwarding to `sink_it_`.
- **[`include/spdlog/pattern_formatter.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/pattern_formatter.h)** – Logic for mutating `payload` and setting color ranges.
- **[`include/spdlog/sinks/basic_file_sink.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/basic_file_sink.h)** – Reference implementation of a concrete sink.
- **[`include/spdlog/logger.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/logger.h)** – Orchestrates `log_msg` construction and multi-sink dispatch.

## Summary

- **`log_msg`** is an immutable aggregate struct defined in [`include/spdlog/details/log_msg.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/details/log_msg.h) that holds all metadata required for logging.
- The struct contains `logger_name`, `level`, `time`, `thread_id`, `source`, `payload`, and mutable `color_range` offsets.
- Loggers instantiate one `log_msg` per logging call using constructors from [`log_msg-inl.h`](https://github.com/gabime/spdlog/blob/main/log_msg-inl.h).
- Messages propagate through the pipeline by **const-reference** via `base_sink::log()`, which locks the mutex and calls the virtual `sink_it_()` method.
- This architecture eliminates message copying and ensures consistent, thread-safe delivery to all registered sinks.

## Frequently Asked Questions

### What makes spdlog's log_msg immutable?

All `log_msg` members are either fundamental types or `string_view_t` references to external storage. Once constructed by the logger, the struct is not modified except for the mutable `color_range` offsets used by pattern formatters. This immutability allows safe concurrent access by multiple sinks via const-reference without synchronization overhead on the message itself.

### Why does base_sink use a template parameter for Mutex?

The `base_sink<Mutex>` template enables compile-time selection between thread-safe (`std::mutex`) and single-threaded (`null_mutex`) sink implementations. The `log()` method locks the specific mutex type before calling the virtual `sink_it_()`, providing zero-cost abstraction when thread safety is unnecessary while guaranteeing synchronization when it is required.

### Can custom sinks modify the log message payload?

No. Concrete sinks receive the `log_msg` via `const details::log_msg &msg` in their `sink_it_()` implementation. While the struct contains mutable `color_range` members intended for formatter use, the `payload` itself is a `string_view_t` and should be treated as read-only to maintain consistency across multiple sinks receiving the same message reference.

### How does the color_range information reach terminal sinks?

The `color_range_start` and `color_range_end` members specify byte offsets within `msg.payload` that should be wrapped with ANSI color codes. During construction, the pattern formatter calculates these offsets based on format specifiers like `%^` and `%$`. Console sinks such as `stdout_color_sink` read these values to apply terminal colors, while file sinks typically ignore them and write the raw payload.