Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V2.x - Attribute support #3128

Open
wants to merge 16 commits into
base: v2.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ set(SPDLOG_HEADERS
"include/spdlog/sinks/systemd_sink.h"
"include/spdlog/sinks/tcp_sink.h"
"include/spdlog/sinks/udp_sink.h"
"include/spdlog/sinks/async_sink.h")
"include/spdlog/sinks/async_sink.h"
"include/spdlog/attributes.h")
set(SPDLOG_SRCS
"src/common.cpp"
"src/logger.cpp"
Expand Down
102 changes: 102 additions & 0 deletions include/spdlog/attributes.h
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions include/spdlog/details/log_msg.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,23 @@

#include <string>

#include "../attributes.h"
#include "../common.h"

namespace spdlog {
namespace details {
struct SPDLOG_API log_msg {
log_msg() = default;
log_msg(log_clock::time_point log_time,
const source_loc &loc,
string_view_t logger_name,
level lvl,
string_view_t msg,
log_attributes attributes);
log_msg(log_clock::time_point log_time, const source_loc &loc, string_view_t logger_name, level lvl, string_view_t msg);
log_msg(const source_loc &loc, string_view_t logger_name, level lvl, string_view_t msg);
log_msg(const source_loc &loc, string_view_t logger_name, level lvl, string_view_t msg, log_attributes attributes);
log_msg(string_view_t logger_name, level lvl, string_view_t msg, log_attributes attributes);
log_msg(string_view_t logger_name, level lvl, string_view_t msg);
log_msg(const log_msg &other) = default;
log_msg &operator=(const log_msg &other) = default;
Expand All @@ -28,6 +37,7 @@ struct SPDLOG_API log_msg {

source_loc source;
string_view_t payload;
log_attributes attributes;
};
} // namespace details
} // namespace spdlog
12 changes: 8 additions & 4 deletions include/spdlog/logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "details/err_helper.h"
#include "details/log_msg.h"
#include "sinks/sink.h"
#include "attributes.h"

namespace spdlog {

Expand Down Expand Up @@ -63,20 +64,20 @@ class SPDLOG_API logger {
// log with no format string, just string message
void log(const source_loc &loc, level lvl, string_view_t msg) noexcept {
if (should_log(lvl)) {
sink_it_(details::log_msg(loc, name_, lvl, msg));
sink_it_(details::log_msg(loc, name_, lvl, msg, attributes));
}
}

void log(level lvl, string_view_t msg) noexcept {
if (should_log(lvl)) {
sink_it_(details::log_msg(source_loc{}, name_, lvl, msg));
sink_it_(details::log_msg(source_loc{}, name_, lvl, msg, attributes));
}
}

// support for custom time
void log(log_clock::time_point log_time, const source_loc &loc, level lvl, string_view_t msg) noexcept {
if (should_log(lvl)) {
sink_it_(details::log_msg(log_time, loc, name_, lvl, msg));
sink_it_(details::log_msg(log_time, loc, name_, lvl, msg, attributes));
}
}

Expand Down Expand Up @@ -160,9 +161,12 @@ class SPDLOG_API logger {
// create new logger with same sinks and configuration.
std::shared_ptr<logger> clone(std::string logger_name);

log_attributes &attrs();

private:
std::string name_;
std::vector<sink_ptr> sinks_;
log_attributes attributes;
atomic_level_t level_{level::info};
atomic_level_t flush_level_{level::off};
details::err_helper err_helper_;
Expand All @@ -178,7 +182,7 @@ class SPDLOG_API logger {
try {
memory_buf_t buf;
fmt::vformat_to(std::back_inserter(buf), format_string, fmt::make_format_args(args...));
sink_it_(details::log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())));
sink_it_(details::log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()), attributes));
} catch (const std::exception &ex) {
err_helper_.handle_ex(name_, loc, ex);
} catch (...) {
Expand Down
27 changes: 26 additions & 1 deletion src/details/log_msg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,25 @@
namespace spdlog {
namespace details {

log_msg::log_msg(const log_clock::time_point log_time,
const source_loc &loc,
const string_view_t logger_name,
const level lvl,
const string_view_t msg,
const log_attributes attributes)
: logger_name(logger_name),
log_level(lvl),
time(log_time),
#ifdef SPDLOG_NO_THREAD_ID
thread_id(0),
#else
thread_id(os::thread_id()),
#endif
source(loc),
payload(msg),
attributes(attributes) {
}

log_msg::log_msg(const log_clock::time_point log_time,
const source_loc &loc,
const string_view_t logger_name,
Expand All @@ -26,10 +45,16 @@ log_msg::log_msg(const log_clock::time_point log_time,
}

log_msg::log_msg(const source_loc &loc, const string_view_t logger_name, const level lvl, const string_view_t msg)
: log_msg(os::now(), loc, logger_name, lvl, msg) {}
: log_msg(os::now(), loc, logger_name, lvl, msg, {}) {}

log_msg::log_msg(const source_loc &loc, string_view_t logger_name, level lvl, string_view_t msg, log_attributes attributes)
: log_msg(os::now(), loc, logger_name, lvl, msg, attributes) {}

log_msg::log_msg(const string_view_t logger_name, const level lvl, const string_view_t msg)
: log_msg(os::now(), source_loc{}, logger_name, lvl, msg) {}

log_msg::log_msg(const string_view_t logger_name, const level lvl, const string_view_t msg, const log_attributes attributes)
: log_msg(os::now(), source_loc{}, logger_name, lvl, msg, attributes) {}

} // namespace details
} // namespace spdlog
3 changes: 3 additions & 0 deletions src/logger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,7 @@ void logger::flush_() noexcept {
}
}
}

log_attributes &logger::attrs() { return attributes; }

} // namespace spdlog
50 changes: 50 additions & 0 deletions src/pattern_formatter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,41 @@ class elapsed_formatter final : public flag_formatter {
log_clock::time_point last_message_time_;
};

template <typename ScopedPadder>
class attribute_formatter final : public flag_formatter {
public:
explicit attribute_formatter(padding_info padinfo)
: flag_formatter(padinfo) {}

void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override {
if (msg.attributes.empty()) {
ScopedPadder p(0, padinfo_, dest);
return;
} else {
const auto &attributes = msg.attributes.get_map();
format_attributes(attributes, dest);
}
}

void format_attributes(const spdlog::log_attributes::attr_map_t &attributes, memory_buf_t &dest) {
for (auto it = attributes.begin(); it != attributes.end(); ++it) {
bool last_item = std::next(it) == attributes.end();
auto &attribute = *it;
const auto &key = attribute.first;
const auto &value = attribute.second;
size_t content_size = key.size() + value.size() + 1; // 1 for ':'

if (!last_item) 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 (!last_item) fmt_helper::append_string_view(" ", dest);
}
}
};

// Full info formatter
// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] [%s:%#] %v
class default_format_formatter final : public flag_formatter {
Expand Down Expand Up @@ -821,12 +856,23 @@ class default_format_formatter final : public flag_formatter {
dest.push_back(']');
dest.push_back(' ');
}

// add attributes if present
if (!msg.attributes.empty()) {
dest.push_back('[');
const auto &attributes = msg.attributes.get_map();
attribute_formatter_.format_attributes(attributes, dest);
dest.push_back(']');
dest.push_back(' ');
}

fmt_helper::append_string_view(msg.payload, dest);
}

private:
std::chrono::seconds cache_timestamp_{0};
memory_buf_t cached_datetime_;
attribute_formatter<null_scoped_padder> attribute_formatter_{padding_info{}};
};
} // namespace details

Expand Down Expand Up @@ -1104,6 +1150,10 @@ void pattern_formatter::handle_flag_(char flag, details::padding_info padding) {
formatters_.push_back(std::make_unique<details::elapsed_formatter<Padder, std::chrono::seconds>>(padding));
break;

case ('*'):
formatters_.push_back(std::make_unique<details::attribute_formatter<Padder>>(padding));
break;

default: // Unknown flag appears as is
auto unknown_flag = std::make_unique<details::aggregate_formatter>();

Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ set(SPDLOG_UTESTS_SOURCES
test_no_source_location.cpp
test_log_level.cpp
test_include_sinks.cpp
test_attributes.cpp
test_bin_to_hex.cpp
test_errors.cpp)

Expand Down
Loading