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 15 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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ find_package(Threads REQUIRED)
set(SPDLOG_HEADERS
"include/spdlog/async.h"
"include/spdlog/async_logger.h"
"include/spdlog/attributes.h"
"include/spdlog/common.h"
"include/spdlog/formatter.h"
"include/spdlog/fwd.h"
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,
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, source_loc loc, string_view_t logger_name, level lvl, string_view_t msg);
log_msg(source_loc loc, string_view_t logger_name, level lvl, string_view_t msg);
log_msg(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 @@ -20,6 +20,7 @@
#include "./common.h"
#include "./details/log_msg.h"
#include "./sinks/sink.h"
#include "attributes.h"

#ifndef SPDLOG_NO_EXCEPTIONS
#define SPDLOG_LOGGER_CATCH(location) \
Expand Down Expand Up @@ -90,20 +91,20 @@ class SPDLOG_API logger {
// log with no format string, just string message
void log(source_loc loc, level lvl, string_view_t msg) {
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) {
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, source_loc loc, level lvl, string_view_t msg) {
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 @@ -229,9 +230,12 @@ class SPDLOG_API logger {
// create new logger with same sinks and configuration.
virtual std::shared_ptr<logger> clone(std::string logger_name);

log_attributes &attrs();

protected:
std::string name_;
std::vector<sink_ptr> sinks_;
log_attributes attributes;
spdlog::atomic_level_t level_{level::info};
spdlog::atomic_level_t flush_level_{level::off};
err_handler custom_err_handler_{nullptr};
Expand All @@ -248,7 +252,7 @@ class SPDLOG_API logger {
#else // use {fmt} lib
memory_buf_t buf;
fmt::vformat_to(std::back_inserter(buf), fmt, 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));
#endif
}
SPDLOG_LOGGER_CATCH(loc)
Expand Down
29 changes: 29 additions & 0 deletions 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(spdlog::log_clock::time_point log_time,
spdlog::source_loc loc,
string_view_t a_logger_name,
spdlog::level lvl,
spdlog::string_view_t msg,
spdlog::log_attributes attributes)
: logger_name(a_logger_name),
log_level(lvl),
time(log_time)
#ifndef SPDLOG_NO_THREAD_ID
,
thread_id(os::thread_id())
#endif
,
source(loc),
payload(msg),
attributes(attributes) {
}

log_msg::log_msg(spdlog::log_clock::time_point log_time,
spdlog::source_loc loc,
string_view_t a_logger_name,
Expand All @@ -28,8 +47,18 @@ log_msg::log_msg(spdlog::log_clock::time_point log_time,
log_msg::log_msg(spdlog::source_loc loc, string_view_t a_logger_name, spdlog::level lvl, spdlog::string_view_t msg)
: log_msg(os::now(), loc, a_logger_name, lvl, msg) {}

log_msg::log_msg(spdlog::source_loc loc,
string_view_t a_logger_name,
spdlog::level lvl,
spdlog::string_view_t msg,
spdlog::log_attributes attributes)
: log_msg(os::now(), loc, a_logger_name, lvl, msg, attributes) {}

log_msg::log_msg(string_view_t a_logger_name, spdlog::level lvl, spdlog::string_view_t msg)
: log_msg(os::now(), source_loc{}, a_logger_name, lvl, msg) {}

log_msg::log_msg(string_view_t a_logger_name, spdlog::level lvl, spdlog::string_view_t msg, spdlog::log_attributes attributes)
: log_msg(os::now(), source_loc{}, a_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 @@ -102,4 +102,7 @@ void logger::err_handler_(const std::string &msg) {
#endif
}
}

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 @@ -780,6 +780,41 @@ class mdc_formatter final : public flag_formatter {
}
};

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 full_formatter final : public flag_formatter {
Expand Down Expand Up @@ -854,6 +889,15 @@ class full_formatter final : public flag_formatter {
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(' ');
}

// add mdc if present
auto &mdc_map = mdc::get_context();
if (!mdc_map.empty()) {
Expand All @@ -862,12 +906,14 @@ class full_formatter final : public flag_formatter {
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{}};
mdc_formatter<null_scoped_padder> mdc_formatter_{padding_info{}};
};
} // namespace details
Expand Down Expand Up @@ -1150,6 +1196,10 @@ void pattern_formatter::handle_flag_(char flag, details::padding_info padding) {
formatters_.push_back(std::make_unique<details::mdc_formatter<Padder>>(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
3 changes: 2 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ set(SPDLOG_UTESTS_SOURCES
test_ringbuffer_sink.cpp
test_source_location.cpp
test_log_level.cpp
test_include_sinks.cpp)
test_include_sinks.cpp
test_attributes.cpp)

if(WIN32)
list(APPEND SPDLOG_UTESTS_SOURCES test_eventlog.cpp)
Expand Down
Loading