From 9f71f3639b9073ed732379eb0100e20d82c6a56a Mon Sep 17 00:00:00 2001 From: Massimiliano Riva Date: Thu, 12 Oct 2023 17:42:21 +0200 Subject: [PATCH] Added Mapped Diagnostic Context (MDC) support --- include/spdlog/mdc.h | 30 ++++++ include/spdlog/pattern_formatter-inl.h | 29 ++++++ include/spdlog/pattern_formatter.h | 1 + tests/includes.h | 1 + tests/test_pattern_formatter.cpp | 128 +++++++++++++++++++++++++ 5 files changed, 189 insertions(+) create mode 100644 include/spdlog/mdc.h diff --git a/include/spdlog/mdc.h b/include/spdlog/mdc.h new file mode 100644 index 00000000..abea6f5c --- /dev/null +++ b/include/spdlog/mdc.h @@ -0,0 +1,30 @@ +#include + +namespace spdlog { + +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; + } +}; + +} // namespace spdlog \ No newline at end of file diff --git a/include/spdlog/pattern_formatter-inl.h b/include/spdlog/pattern_formatter-inl.h index a5875a11..b4412965 100644 --- a/include/spdlog/pattern_formatter-inl.h +++ b/include/spdlog/pattern_formatter-inl.h @@ -5,6 +5,7 @@ #ifndef SPDLOG_HEADER_ONLY #include + #include #endif #include @@ -867,6 +868,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 &, const std::tm &, 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 +1184,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..e1646f74 100644 --- a/include/spdlog/pattern_formatter.h +++ b/include/spdlog/pattern_formatter.h @@ -14,6 +14,7 @@ #include #include +#include #include namespace spdlog { diff --git a/tests/includes.h b/tests/includes.h index 8514d642..14e988b9 100644 --- a/tests/includes.h +++ b/tests/includes.h @@ -26,6 +26,7 @@ #include "spdlog/spdlog.h" #include "spdlog/async.h" #include "spdlog/details/fmt_helper.h" +#include "spdlog/mdc.h" #include "spdlog/sinks/basic_file_sink.h" #include "spdlog/sinks/daily_file_sink.h" #include "spdlog/sinks/null_sink.h" diff --git a/tests/test_pattern_formatter.cpp b/tests/test_pattern_formatter.cpp index 350b973b..c0eaa111 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(); + 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{}", + 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(); + 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{}", + 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{}", + 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(); + 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{}", + 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{}", + 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(); + 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{}", + 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(); + 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{}", + spdlog::details::os::default_eol); + REQUIRE(to_string_view(formatted) == expected); + + SECTION("Tear down") { spdlog::mdc::clear(); } +}