diff --git a/include/spdlog/pattern_formatter-inl.h b/include/spdlog/pattern_formatter-inl.h index a5875a11..ed5de5d8 100644 --- a/include/spdlog/pattern_formatter-inl.h +++ b/include/spdlog/pattern_formatter-inl.h @@ -867,6 +867,30 @@ private: memory_buf_t cached_datetime_; }; +template +class mdc_formatter : public flag_formatter { +public: + explicit mdc_formatter(padding_info padinfo) + : flag_formatter(padinfo) {} + + void format(const details::log_msg &msg, const std::tm &tm_time, memory_buf_t &dest) override { + auto mdc_map = MDC::get_context(); + if (!mdc_map.empty()) { + auto last_element = *(--mdc_map.end()); + for (const auto &pair : mdc_map) { + std::string mdc_content_; + if (pair != last_element) { + mdc_content_ = pair.first + ':' + pair.second + ' '; + } else { + mdc_content_ = pair.first + ':' + pair.second; + } + ScopedPadder p(mdc_content_.size(), padinfo_, dest); + fmt_helper::append_string_view(mdc_content_, dest); + } + } + } +}; + } // namespace details SPDLOG_INLINE pattern_formatter::pattern_formatter(std::string pattern, @@ -1159,6 +1183,10 @@ SPDLOG_INLINE void pattern_formatter::handle_flag_(char flag, details::padding_i padding)); break; + case ('&'): + formatters_.push_back(details::make_unique>(padding)); + break; + default: // Unknown flag appears as is auto unknown_flag = details::make_unique(); diff --git a/include/spdlog/pattern_formatter.h b/include/spdlog/pattern_formatter.h index ececd673..91697cc7 100644 --- a/include/spdlog/pattern_formatter.h +++ b/include/spdlog/pattern_formatter.h @@ -14,6 +14,7 @@ #include #include +#include #include namespace spdlog { @@ -62,6 +63,31 @@ public: } }; +class SPDLOG_API MDC { +public: + static void put(const std::string &key, const std::string &value) { + get_context()[key] = value; + } + + static std::string get(const std::string &key) { + auto &context = get_context(); + auto it = context.find(key); + if (it != context.end()) { + return it->second; + } + return ""; + } + + static void remove(const std::string &key) { get_context().erase(key); } + + static void clear() { get_context().clear(); } + + static std::map &get_context() { + static thread_local std::map context; + return context; + } +}; + class SPDLOG_API pattern_formatter final : public formatter { public: using custom_flags = std::unordered_map>; diff --git a/tests/test_pattern_formatter.cpp b/tests/test_pattern_formatter.cpp index 350b973b..d5ab7fde 100644 --- a/tests/test_pattern_formatter.cpp +++ b/tests/test_pattern_formatter.cpp @@ -500,3 +500,131 @@ TEST_CASE("override need_localtime", "[pattern_formatter]") { REQUIRE(to_string_view(formatted) == oss.str()); } } + +TEST_CASE("MDC formatter test-1", "[pattern_formatter]") { + spdlog::MDC::put("mdc_key_1", "mdc_value_1"); + spdlog::MDC::put("mdc_key_2", "mdc_value_2"); + + auto formatter = std::make_shared("\n"); + formatter->set_pattern("[%n] [%l] [%&] %v"); + + memory_buf_t formatted; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + + auto expected = spdlog::fmt_lib::format( + "[logger-name] [info] [mdc_key_1:mdc_value_1 mdc_key_2:mdc_value_2] some message\n", + spdlog::details::os::default_eol); + REQUIRE(to_string_view(formatted) == expected); + + SECTION("Tear down") { spdlog::MDC::clear(); } +} + +TEST_CASE("MDC formatter value update", "[pattern_formatter]") { + spdlog::MDC::put("mdc_key_1", "mdc_value_1"); + spdlog::MDC::put("mdc_key_2", "mdc_value_2"); + + auto formatter = std::make_shared("\n"); + formatter->set_pattern("[%n] [%l] [%&] %v"); + + memory_buf_t formatted_1; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted_1); + + auto expected = spdlog::fmt_lib::format( + "[logger-name] [info] [mdc_key_1:mdc_value_1 mdc_key_2:mdc_value_2] some message\n", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted_1) == expected); + + spdlog::MDC::put("mdc_key_1", "new_mdc_value_1"); + memory_buf_t formatted_2; + formatter->format(msg, formatted_2); + expected = spdlog::fmt_lib::format( + "[logger-name] [info] [mdc_key_1:new_mdc_value_1 mdc_key_2:mdc_value_2] some message\n", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted_2) == expected); + + SECTION("Tear down") { spdlog::MDC::clear(); } +} + +TEST_CASE("MDC different threads", "[pattern_formatter]") { + auto formatter = std::make_shared("\n"); + formatter->set_pattern("[%n] [%l] [%&] %v"); + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + + memory_buf_t formatted_2; + + auto lambda_1 = [formatter, msg]() { + spdlog::MDC::put("mdc_key", "thread_1_id"); + memory_buf_t formatted; + formatter->format(msg, formatted); + + auto expected = + spdlog::fmt_lib::format("[logger-name] [info] [mdc_key:thread_1_id] some message\n", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted) == expected); + }; + + auto lambda_2 = [formatter, msg]() { + spdlog::MDC::put("mdc_key", "thread_2_id"); + memory_buf_t formatted; + formatter->format(msg, formatted); + + auto expected = + spdlog::fmt_lib::format("[logger-name] [info] [mdc_key:thread_2_id] some message\n", + spdlog::details::os::default_eol); + + REQUIRE(to_string_view(formatted) == expected); + }; + + std::thread thread_1(lambda_1); + std::thread thread_2(lambda_2); + + thread_1.join(); + thread_2.join(); + + SECTION("Tear down") { spdlog::MDC::clear(); } +} + +TEST_CASE("MDC remove key", "[pattern_formatter]") { + spdlog::MDC::put("mdc_key_1", "mdc_value_1"); + spdlog::MDC::put("mdc_key_2", "mdc_value_2"); + spdlog::MDC::remove("mdc_key_1"); + + auto formatter = std::make_shared("\n"); + formatter->set_pattern("[%n] [%l] [%&] %v"); + + memory_buf_t formatted; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + + auto expected = + spdlog::fmt_lib::format("[logger-name] [info] [mdc_key_2:mdc_value_2] some message\n", + spdlog::details::os::default_eol); + REQUIRE(to_string_view(formatted) == expected); + + SECTION("Tear down") { spdlog::MDC::clear(); } +} + +TEST_CASE("MDC empty", "[pattern_formatter]") { + auto formatter = std::make_shared("\n"); + formatter->set_pattern("[%n] [%l] [%&] %v"); + + memory_buf_t formatted; + spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, + "some message"); + formatter->format(msg, formatted); + + auto expected = spdlog::fmt_lib::format("[logger-name] [info] [] some message\n", + spdlog::details::os::default_eol); + REQUIRE(to_string_view(formatted) == expected); + + SECTION("Tear down") { spdlog::MDC::clear(); } +}