mirror of https://github.com/gabime/spdlog.git
Merge 0f9f7540d3
into ce0424bb37
commit
07d93daba3
@ -0,0 +1,102 @@
|
||||
#pragma once
|
||||
|
||||
#include <spdlog/common.h>
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
|
||||
namespace spdlog {
|
||||
|
||||
class SPDLOG_API log_attributes {
|
||||
public:
|
||||
using attr_map_t = std::map<std::string, std::string>;
|
||||
using key_t = attr_map_t::key_type;
|
||||
using value_t = attr_map_t::mapped_type;
|
||||
using const_iter = attr_map_t::const_iterator;
|
||||
|
||||
class SPDLOG_API log_attr_context {
|
||||
public:
|
||||
log_attr_context(log_attributes& parent, attr_map_t const& attrs)
|
||||
: parent_(parent),
|
||||
tmp_attrs_{attrs} {
|
||||
parent_.put(attrs);
|
||||
}
|
||||
explicit log_attr_context(log_attributes& parent)
|
||||
: log_attr_context(parent, {}) {}
|
||||
|
||||
~log_attr_context() {
|
||||
for (auto&& key_value : tmp_attrs_) parent_.remove(key_value.first);
|
||||
}
|
||||
|
||||
private:
|
||||
log_attributes& parent_;
|
||||
attr_map_t tmp_attrs_;
|
||||
};
|
||||
|
||||
log_attributes() = default;
|
||||
log_attributes(const log_attributes& other) { put(other.get_map()); }
|
||||
log_attributes& operator=(const log_attributes& other) {
|
||||
if (this != &other) {
|
||||
clear();
|
||||
put(other.get_map());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void put(attr_map_t const& attributes) {
|
||||
auto lck = lock();
|
||||
for (auto const& attribute : attributes) attrs.insert_or_assign(attribute.first, attribute.second);
|
||||
}
|
||||
void put(const key_t& key, const value_t& value) { put({{key, value}}); }
|
||||
|
||||
void remove(const key_t& key) {
|
||||
auto lck = lock();
|
||||
auto value_it = attrs.find(key);
|
||||
if (value_it != attrs.end()) {
|
||||
attrs.erase(key);
|
||||
}
|
||||
}
|
||||
|
||||
void clear() {
|
||||
auto lck = lock();
|
||||
attrs.clear();
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
auto lck = lock();
|
||||
return attrs.empty();
|
||||
}
|
||||
|
||||
// return {true, iter} if found, {false, end} otherwise
|
||||
std::pair<bool, const_iter> const get(key_t const& key) const {
|
||||
auto lck = lock();
|
||||
auto value_it = attrs.find(key);
|
||||
|
||||
return std::make_pair(value_it != attrs.end(), value_it);
|
||||
}
|
||||
|
||||
// provide a local copy of the attributes to be free of race issues
|
||||
// alternative is to lock the attr_map while the formatter iterates over it
|
||||
attr_map_t get_map() const {
|
||||
auto lck = lock();
|
||||
return attrs;
|
||||
}
|
||||
|
||||
// RAII-wrapper that inserts a couple of attributes and removes them upon destruction
|
||||
log_attr_context scoped_ctx(attr_map_t const& attributes) { return log_attr_context(*this, attributes); }
|
||||
log_attr_context scoped_ctx(key_t key, value_t value) { return log_attr_context(*this, {{key, value}}); }
|
||||
|
||||
bool empty() {
|
||||
auto lck = lock();
|
||||
return attrs.empty();
|
||||
}
|
||||
|
||||
private:
|
||||
std::lock_guard<std::mutex> lock() const { return std::lock_guard(attrs_mtx); }
|
||||
|
||||
mutable std::mutex attrs_mtx;
|
||||
attr_map_t attrs;
|
||||
};
|
||||
|
||||
} // namespace spdlog
|
@ -0,0 +1,202 @@
|
||||
#include <future>
|
||||
|
||||
#include "includes.h"
|
||||
#include "spdlog/sinks/stdout_sinks.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "test_sink.h"
|
||||
|
||||
using namespace spdlog;
|
||||
|
||||
std::string format_attrs(std::string_view const log_name, log_attributes::attr_map_t const& attrs = {}) {
|
||||
std::string fmt_attrs;
|
||||
for (auto&& attr : attrs) fmt_attrs += fmt_lib::format("{}:{} ", attr.first, attr.second);
|
||||
if (!fmt_attrs.empty()) fmt_attrs.pop_back();
|
||||
|
||||
return fmt_lib::format("[{}] [{}]", log_name, fmt_attrs);
|
||||
}
|
||||
|
||||
std::pair<logger, std::shared_ptr<sinks::test_sink_st>> make_logger() {
|
||||
auto test_sink = std::make_shared<sinks::test_sink_st>();
|
||||
auto log = logger("logger", test_sink);
|
||||
log.set_pattern("[%n] [%*]");
|
||||
return std::make_pair(log, test_sink);
|
||||
}
|
||||
|
||||
TEST_CASE("attribute test - multiple (single thread)") {
|
||||
auto test_sink = std::make_shared<sinks::test_sink_st>();
|
||||
logger log_a("log_a", test_sink);
|
||||
logger log_b("log_b", test_sink);
|
||||
log_a.set_pattern("[%n] [%*]");
|
||||
log_b.set_pattern("[%n] [%*]");
|
||||
|
||||
log_a.attrs().put("my_key", "my_value");
|
||||
|
||||
log_a.info("Hello");
|
||||
log_b.info("Hello");
|
||||
|
||||
auto expected_log_a = format_attrs("log_a", {{"my_key", "my_value"}});
|
||||
auto expected_log_b = format_attrs("log_b");
|
||||
|
||||
auto [found, value] = log_a.attrs().get("my_key");
|
||||
REQUIRE(found);
|
||||
REQUIRE(value->first == "my_key");
|
||||
REQUIRE(value->second == "my_value");
|
||||
|
||||
auto [not_found, _] = log_a.attrs().get("my_non_existent_key");
|
||||
REQUIRE(!not_found);
|
||||
|
||||
auto lines = test_sink->lines();
|
||||
REQUIRE(lines.size() == 2);
|
||||
REQUIRE(lines[0] == expected_log_a);
|
||||
REQUIRE(lines[1] == expected_log_b);
|
||||
}
|
||||
|
||||
TEST_CASE("attribute test - remove") {
|
||||
std::vector<log_attributes::attr_map_t::value_type> attrs{
|
||||
{"my_key", "my_value"},
|
||||
{"my_key2", "my_value2"},
|
||||
};
|
||||
|
||||
auto [log, test_sink] = make_logger();
|
||||
|
||||
log.attrs().put(attrs[0].first, attrs[0].second);
|
||||
log.attrs().put(attrs[1].first, attrs[1].second);
|
||||
|
||||
log.info("");
|
||||
auto expected_log = format_attrs("logger", {attrs.begin(), attrs.end()});
|
||||
|
||||
REQUIRE(!test_sink->lines().empty());
|
||||
REQUIRE(test_sink->lines().back() == expected_log);
|
||||
|
||||
log.attrs().remove(attrs.front().first);
|
||||
log.info("");
|
||||
expected_log = format_attrs("logger", {attrs.begin() + 1, attrs.end()});
|
||||
REQUIRE(test_sink->lines().back() == expected_log);
|
||||
|
||||
log.attrs().remove(attrs.back().first);
|
||||
log.info("");
|
||||
expected_log = format_attrs("logger");
|
||||
REQUIRE(test_sink->lines().back() == expected_log);
|
||||
}
|
||||
|
||||
TEST_CASE("attribute test - from range") {
|
||||
std::vector<log_attributes::attr_map_t::value_type> attrs{
|
||||
{"my_key", "my_value"},
|
||||
{"my_key2", "my_value2"},
|
||||
};
|
||||
|
||||
auto [log, test_sink] = make_logger();
|
||||
|
||||
log.attrs().put({attrs.begin(), attrs.end()});
|
||||
log.info("");
|
||||
auto expected_log = format_attrs("logger", {attrs.begin(), attrs.end()});
|
||||
|
||||
REQUIRE(!test_sink->lines().empty());
|
||||
REQUIRE(test_sink->lines().back() == expected_log);
|
||||
}
|
||||
|
||||
TEST_CASE("attribute test - scoped") {
|
||||
std::vector<log_attributes::attr_map_t::value_type> attrs{
|
||||
{"my_key", "my_value"},
|
||||
{"my_key2", "my_value2"},
|
||||
};
|
||||
|
||||
auto [log, test_sink] = make_logger();
|
||||
|
||||
auto key_value = std::make_pair("additional_key", "additional_value");
|
||||
{
|
||||
// add the attributes in scope
|
||||
auto ctx = log.attrs().scoped_ctx({attrs.begin(), attrs.end()});
|
||||
log.info("");
|
||||
auto expected_log = format_attrs("logger", {attrs.begin(), attrs.end()});
|
||||
|
||||
REQUIRE(!test_sink->lines().empty());
|
||||
REQUIRE(test_sink->lines().back() == expected_log);
|
||||
|
||||
// remove one of the scoped attributes
|
||||
log.attrs().remove(attrs.front().first);
|
||||
log.info("");
|
||||
expected_log = format_attrs("logger", {attrs.begin() + 1, attrs.end()});
|
||||
REQUIRE(test_sink->lines().back() == expected_log);
|
||||
|
||||
// add another non-scoped attribute
|
||||
log.attrs().put(key_value.first, key_value.second);
|
||||
log.info("");
|
||||
expected_log = format_attrs("logger", {attrs[1], key_value});
|
||||
REQUIRE(test_sink->lines().back() == expected_log);
|
||||
}
|
||||
|
||||
log.info("");
|
||||
auto expected_log = format_attrs("logger", {key_value});
|
||||
}
|
||||
|
||||
TEST_CASE("attribute test - nested scoped") {
|
||||
std::vector<log_attributes::attr_map_t::value_type> attrs{
|
||||
{"my_key", "my_value"},
|
||||
{"my_key2", "my_value2"},
|
||||
};
|
||||
std::vector<log_attributes::attr_map_t::value_type> nested_attrs{
|
||||
{"nested_key", "nested_value"},
|
||||
{"nested_key2", "nested_value2"},
|
||||
};
|
||||
|
||||
auto [log, test_sink] = make_logger();
|
||||
|
||||
{
|
||||
// add the attributes in scope
|
||||
auto ctx = log.attrs().scoped_ctx({attrs.begin(), attrs.end()});
|
||||
log.info("");
|
||||
auto expected_log = format_attrs("logger", {attrs.begin(), attrs.end()});
|
||||
|
||||
REQUIRE(!test_sink->lines().empty());
|
||||
REQUIRE(test_sink->lines().back() == expected_log);
|
||||
|
||||
// add another scoped context
|
||||
{
|
||||
auto ctx_nested = log.attrs().scoped_ctx({nested_attrs.begin(), nested_attrs.end()});
|
||||
log.info("");
|
||||
log_attributes::attr_map_t all_attrs{attrs.begin(), attrs.end()};
|
||||
all_attrs.insert(nested_attrs.begin(), nested_attrs.end());
|
||||
expected_log = format_attrs("logger", {all_attrs.begin(), all_attrs.end()});
|
||||
REQUIRE(test_sink->lines().back() == expected_log);
|
||||
}
|
||||
|
||||
// delete nested scope
|
||||
log.info("");
|
||||
expected_log = format_attrs("logger", {attrs.begin(), attrs.end()});
|
||||
REQUIRE(test_sink->lines().back() == expected_log);
|
||||
}
|
||||
|
||||
log.info("");
|
||||
auto expected_log = format_attrs("logger");
|
||||
}
|
||||
|
||||
TEST_CASE("attribute test - multi threaded") {
|
||||
const unsigned int n_tasks = std::thread::hardware_concurrency();
|
||||
constexpr auto n_values = 30;
|
||||
auto mt_sink = std::make_shared<spdlog::sinks::test_sink_mt>();
|
||||
auto logger = spdlog::logger("logger", mt_sink);
|
||||
logger.set_pattern("[%n] [%*]");
|
||||
|
||||
// put attributes with multiple threads simultaneously
|
||||
std::vector<std::future<void>> tasks;
|
||||
for (unsigned int i = 0; i < n_tasks; ++i) {
|
||||
auto task = std::async([&logger, i, n_values] {
|
||||
for (auto j = 0; j < n_values; ++j)
|
||||
logger.attrs().put(fmt_lib::format("log_{}_key_{}", i, j), fmt_lib::format("log_{}_value_{}", i, j));
|
||||
});
|
||||
tasks.emplace_back(std::move(task));
|
||||
}
|
||||
|
||||
for (auto&& task : tasks) task.wait();
|
||||
|
||||
logger.info("");
|
||||
REQUIRE(!mt_sink->lines().empty());
|
||||
auto log_line = mt_sink->lines().back();
|
||||
for (unsigned int i = 0; i < n_tasks; ++i) {
|
||||
for (auto j = 0; j < n_values; ++j) {
|
||||
auto search_term = fmt_lib::format("log_{0}_key_{1}:log_{0}_value_{1}", i, j);
|
||||
REQUIRE(log_line.find(search_term) != std::string::npos);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue