Skip to content

mdc_formatter fails to read MDC values due to static thread_local context being duplicated across TUs #3456

@Cansisti

Description

@Cansisti

Summary

When building spdlog as a compiled library (SPDLOG_COMPILED_LIB) and using the mdc_formatter (%&), MDC values inserted via spdlog::mdc::put(...) are not visible in the formatter. This only happens in optimized builds (-O2, -O3); in debug builds (-O0), everything works as expected.

This is caused by static thread_local usage in mdc::get_context() in the header file mdc.h. Under optimization, the compiler generates separate instances of the thread_local context per translation unit, which causes put(...) and the formatter to operate on different contexts — even within the same thread.


Minimal reproduction

  1. Build spdlog with -O2, -DSPDLOG_BUILD_SHARED=ON and SPDLOG_BUILD_TESTS=ON.
  2. Run the test: mdc formatter test-1 in test_pattern_formatter.cpp.

Expected log output:
[logger-name] [info] [mdc_key_1:mdc_value_1 mdc_key_2:mdc_value_2] some message
Actual output:
[logger-name] [info] [] some message

If you insert this line before the formatter call:

REQUIRE(spdlog::mdc::get("mdc_key_1") == "mdc_value_1");

…then the value is found — confirming that put() worked, but mdc_formatter sees a different thread_local context.

Cause

mdc::get_context() is currently defined as:

static thread_local mdc_map_t context;

in a header-only context. This causes multiple copies of the variable to be instantiated across different TUs when optimization is enabled, due to inlining and aggressive code elimination. In particular:

  • mdc::put() is compiled into the test TU,
  • mdc_formatter::format() is compiled into the spdlog library TU,
  • and they each operate on a different thread_local instance.

This does not occur at -O0 because inlining is disabled, and symbol resolution happens normally.

Suggested fix

Move the definition of get_context() to a .cpp file to guarantee a single instance of the thread_local variable per program (per thread)

This avoids duplication across TUs and ensures that all MDC-aware components share the same storage.

Notes

  • Tested on macOS (Clang 14, Apple Silicon).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions