Added Mapped Diagnostic Context (MDC) support (#2907)

* Added Mapped Diagnostic Context (MDC) support

* Update include statement

* Optimize string creation

* Fix includes

* Fix padding rules in mdc empty case

* Add comment to describe the use of mdc formatter
This commit is contained in:
Massimiliano Riva 2024-03-18 16:41:46 +01:00 committed by GitHub
parent 23587b0d9a
commit d03eb40c17
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 202 additions and 0 deletions

31
include/spdlog/mdc.h Normal file
View File

@ -0,0 +1,31 @@
#include <spdlog/common.h>
#include <map>
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<std::string, std::string> &get_context() {
static thread_local std::map<std::string, std::string> context;
return context;
}
};
} // namespace spdlog

View File

@ -10,6 +10,7 @@
#include <spdlog/details/fmt_helper.h> #include <spdlog/details/fmt_helper.h>
#include <spdlog/details/log_msg.h> #include <spdlog/details/log_msg.h>
#include <spdlog/details/os.h> #include <spdlog/details/os.h>
#include <spdlog/mdc.h>
#include <spdlog/fmt/fmt.h> #include <spdlog/fmt/fmt.h>
#include <spdlog/formatter.h> #include <spdlog/formatter.h>
@ -867,6 +868,43 @@ private:
memory_buf_t cached_datetime_; memory_buf_t cached_datetime_;
}; };
// Class for formatting Mapped Diagnostic Context (MDC) in log messages.
// Example: [logger-name] [info] [mdc_key_1:mdc_value_1 mdc_key_2:mdc_value_2] some message
template <typename ScopedPadder>
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()) {
ScopedPadder p(0, padinfo_, dest);
return;
} else {
auto last_element = --mdc_map.end();
for (auto it = mdc_map.begin(); it != mdc_map.end(); ++it) {
auto &pair = *it;
const auto &key = pair.first;
const auto &value = pair.second;
size_t content_size = key.size() + value.size() + 1; // 1 for ':'
if (it != last_element) {
content_size++; // 1 for ' '
}
ScopedPadder p(content_size, padinfo_, dest);
fmt_helper::append_string_view(key, dest);
fmt_helper::append_string_view(":", dest);
fmt_helper::append_string_view(value, dest);
if (it != last_element) {
fmt_helper::append_string_view(" ", dest);
}
}
}
}
};
} // namespace details } // namespace details
SPDLOG_INLINE pattern_formatter::pattern_formatter(std::string pattern, SPDLOG_INLINE pattern_formatter::pattern_formatter(std::string pattern,
@ -1159,6 +1197,10 @@ SPDLOG_INLINE void pattern_formatter::handle_flag_(char flag, details::padding_i
padding)); padding));
break; break;
case ('&'):
formatters_.push_back(details::make_unique<details::mdc_formatter<Padder>>(padding));
break;
default: // Unknown flag appears as is default: // Unknown flag appears as is
auto unknown_flag = details::make_unique<details::aggregate_formatter>(); auto unknown_flag = details::make_unique<details::aggregate_formatter>();

View File

@ -26,6 +26,7 @@
#include "spdlog/spdlog.h" #include "spdlog/spdlog.h"
#include "spdlog/async.h" #include "spdlog/async.h"
#include "spdlog/details/fmt_helper.h" #include "spdlog/details/fmt_helper.h"
#include "spdlog/mdc.h"
#include "spdlog/sinks/basic_file_sink.h" #include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/daily_file_sink.h" #include "spdlog/sinks/daily_file_sink.h"
#include "spdlog/sinks/null_sink.h" #include "spdlog/sinks/null_sink.h"

View File

@ -500,3 +500,131 @@ TEST_CASE("override need_localtime", "[pattern_formatter]") {
REQUIRE(to_string_view(formatted) == oss.str()); 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<spdlog::pattern_formatter>();
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<spdlog::pattern_formatter>();
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<spdlog::pattern_formatter>();
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<spdlog::pattern_formatter>();
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<spdlog::pattern_formatter>();
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(); }
}