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

Add dist sink support #69

Open
wants to merge 2 commits into
base: master
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,12 @@ The header files should be installed in `build/install/include`.
- `daily_file_sink_mt`
- `null_sink_st`
- `null_sink_mt`
- `dist_sink_st`
- `dist_sink_mt`
- `syslog_sink` (only for Linux, `SPDLOG_ENABLE_SYSLOG` preprocessor definition
must be defined before any `spdlog`/`spdlog_setup` header is included)

Currently `ostream_sink` and `dist_sink` do not fit into the use case and are
not supported.
Currently `ostream_sink` does not fit into the use case and is not supported.

For more information about how the above sinks work in `spdlog`, please refer to
the original `spdlog` sinks wiki page at:
Expand Down
128 changes: 124 additions & 4 deletions include/spdlog_setup/details/conf_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/daily_file_sink.h"
#include "spdlog/sinks/dist_sink.h"
#include "spdlog/sinks/null_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/sinks/sink.h"
Expand All @@ -49,6 +50,7 @@
#include <string>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -132,6 +134,12 @@ enum class sink_type {
/** Represents null_sink_mt */
NullSinkMt,

/** Represents dist_sink_st */
DistSinkSt,

/** Represents dist_sink_mt */
DistSinkMt,

/** Represents syslog_sink_st */
SyslogSinkSt,

Expand Down Expand Up @@ -629,6 +637,8 @@ inline auto sink_type_from_str(const std::string &type) -> sink_type {
{"daily_file_sink_mt", sink_type::DailyFileSinkMt},
{"null_sink_st", sink_type::NullSinkSt},
{"null_sink_mt", sink_type::NullSinkMt},
{"dist_sink_st", sink_type::DistSinkSt},
{"dist_sink_mt", sink_type::DistSinkMt},
#ifdef SPDLOG_ENABLE_SYSLOG
{"syslog_sink_st", sink_type::SyslogSinkSt},
{"syslog_sink_mt", sink_type::SyslogSinkMt},
Expand Down Expand Up @@ -886,8 +896,30 @@ auto setup_syslog_sink(const std::shared_ptr<cpptoml::table> &sink_table)

#endif

template <class DistSink>
auto setup_dist_sink(
const std::shared_ptr<cpptoml::table> &sink_table,
std::vector<std::string> &ref_sinks)
-> std::shared_ptr<spdlog::sinks::sink> {

using names::SINKS;

// std
using std::make_shared;
using std::string;

ref_sinks = array_from_table<string>(
sink_table,
SINKS,
fmt::format("Missing '{}' field of sink names for dist_sink", SINKS));

return make_shared<DistSink>();
}

inline auto sink_from_sink_type(
const sink_type sink_val, const std::shared_ptr<cpptoml::table> &sink_table)
const sink_type sink_val,
const std::shared_ptr<cpptoml::table> &sink_table,
std::vector<std::string> &ref_sinks)
-> std::shared_ptr<spdlog::sinks::sink> {

// fmt
Expand All @@ -898,6 +930,8 @@ inline auto sink_from_sink_type(
using spdlog::sinks::basic_file_sink_st;
using spdlog::sinks::daily_file_sink_mt;
using spdlog::sinks::daily_file_sink_st;
using spdlog::sinks::dist_sink_mt;
using spdlog::sinks::dist_sink_st;
using spdlog::sinks::null_sink_mt;
using spdlog::sinks::null_sink_st;
using spdlog::sinks::rotating_file_sink_mt;
Expand Down Expand Up @@ -977,6 +1011,12 @@ inline auto sink_from_sink_type(
case sink_type::NullSinkMt:
return make_shared<null_sink_mt>();

case sink_type::DistSinkSt:
return setup_dist_sink<dist_sink_st>(sink_table, ref_sinks);

case sink_type::DistSinkMt:
return setup_dist_sink<dist_sink_mt>(sink_table, ref_sinks);

#ifdef SPDLOG_ENABLE_SYSLOG
case sink_type::SyslogSinkSt:
return setup_syslog_sink<syslog_sink_st>(sink_table);
Expand Down Expand Up @@ -1031,7 +1071,9 @@ inline void set_logger_flush_level_if_present(
});
}

inline auto setup_sink(const std::shared_ptr<cpptoml::table> &sink_table)
inline auto setup_sink(
const std::shared_ptr<cpptoml::table> &sink_table,
std::vector<std::string> &ref_sinks)
-> std::shared_ptr<spdlog::sinks::sink> {

using names::TYPE;
Expand All @@ -1047,14 +1089,84 @@ inline auto setup_sink(const std::shared_ptr<cpptoml::table> &sink_table)
sink_table, TYPE, format("Sink missing '{}' field", TYPE));

const auto sink_val = sink_type_from_str(type_val);
auto sink = sink_from_sink_type(sink_val, sink_table);
auto sink = sink_from_sink_type(sink_val, sink_table, ref_sinks);

// set optional parts and return back the same sink
set_sink_level_if_present(sink_table, sink);

return sink;
}

inline void check_ref_sinks_no_cycles_dfs(
const std::string &name,
const std::unordered_map<std::string, std::vector<std::string>>
&ref_sinks_map,
std::unordered_set<std::string> &visited) {

// fmt
using fmt::format;

auto ref_sinks = ref_sinks_map.find(name);
if (ref_sinks == ref_sinks_map.end()) {
throw setup_error(format("Reference to unknown sink '{}'", name));
}

for (const auto &ref_sink : ref_sinks->second) {
auto result = visited.insert(ref_sink);
if (result.second) {
check_ref_sinks_no_cycles_dfs(ref_sink, ref_sinks_map, visited);
} else {
throw setup_error(
format("Cyclic reference with sink '{}'", ref_sink));
}
}
}

inline void setup_ref_sinks(
std::unordered_map<std::string, std::shared_ptr<spdlog::sinks::sink>>
&sinks_map,
const std::unordered_map<std::string, std::vector<std::string>>
&ref_sinks_map) {

// spdlog
using spdlog::sink_ptr;
using spdlog::sinks::dist_sink_mt;
using spdlog::sinks::dist_sink_st;

// std
using std::move;
using std::string;
using std::unordered_set;
using std::vector;

for (const auto &kv : ref_sinks_map) {
unordered_set<string> visited{kv.first};
check_ref_sinks_no_cycles_dfs(kv.first, ref_sinks_map, visited);

auto sink = sinks_map[kv.first];
auto *dist_st = dynamic_cast<dist_sink_st *>(sink.get());
auto *dist_mt = dynamic_cast<dist_sink_mt *>(sink.get());

if (!dist_st && !dist_mt) {
if (kv.second.empty())
continue;

throw setup_error(
"Internal error: only dist sinks may connect to other sinks");
}

vector<sink_ptr> sinks;
for (const auto &ref_sink : kv.second) {
sinks.emplace_back(sinks_map[ref_sink]);
}
if (dist_st) {
dist_st->set_sinks(move(sinks));
} else {
dist_mt->set_sinks(move(sinks));
}
}
}

inline auto setup_sinks(const std::shared_ptr<cpptoml::table> &config)
-> std::unordered_map<std::string, std::shared_ptr<spdlog::sinks::sink>> {

Expand All @@ -1068,7 +1180,9 @@ inline auto setup_sinks(const std::shared_ptr<cpptoml::table> &config)
using std::move;
using std::shared_ptr;
using std::string;
using std::unique_ptr;
using std::unordered_map;
using std::vector;

const auto sinks = config->get_table_array(SINK_TABLE);

Expand All @@ -1077,22 +1191,28 @@ inline auto setup_sinks(const std::shared_ptr<cpptoml::table> &config)
}

unordered_map<string, shared_ptr<spdlog::sinks::sink>> sinks_map;
unordered_map<string, vector<string>> ref_sinks_map;

for (const auto &sink_table : *sinks) {
auto name = value_from_table<string>(
sink_table,
NAME,
format("One of the sinks does not have a '{}' field", NAME));

auto &ref_sinks = ref_sinks_map[name];
auto sink = add_msg_on_err(
[&sink_table] { return setup_sink(sink_table); },
[&sink_table, &ref_sinks] {
return setup_sink(sink_table, ref_sinks);
},
[&name](const string &err_msg) {
return format("Sink '{}' error:\n > {}", name, err_msg);
});

sinks_map.emplace(move(name), move(sink));
}

setup_ref_sinks(sinks_map, ref_sinks_map);

return sinks_map;
}

Expand Down
8 changes: 6 additions & 2 deletions src/unit_test/loggers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ TEST_CASE(
}

TEST_CASE("Parse logger with sink", "[parse_logger_with_sink]") {
auto sink = spdlog_setup::details::setup_sink(generate_stdout_sink_st());
std::vector<std::string> ref_sinks;
auto sink =
spdlog_setup::details::setup_sink(generate_stdout_sink_st(), ref_sinks);
const auto sinks_map =
std::unordered_map<std::string, std::shared_ptr<spdlog::sinks::sink>>{
{TEST_SINK_NAME, std::move(sink)}};
Expand All @@ -145,7 +147,9 @@ TEST_CASE("Parse logger with sink", "[parse_logger_with_sink]") {

TEST_CASE(
"Parse logger with missing sink", "[parse_logger_with_missing_sink]") {
auto sink = spdlog_setup::details::setup_sink(generate_stdout_sink_st());
std::vector<std::string> ref_sinks;
auto sink =
spdlog_setup::details::setup_sink(generate_stdout_sink_st(), ref_sinks);
const auto sinks_map =
std::unordered_map<std::string, std::shared_ptr<spdlog::sinks::sink>>{
{"xxx", std::move(sink)}};
Expand Down
48 changes: 44 additions & 4 deletions src/unit_test/sinks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,65 @@
#include <typeinfo>

TEST_CASE("Parse stdout sink st", "[parse_generate_stdout_sink_st]") {
std::vector<std::string> ref_sinks;
const auto sink =
spdlog_setup::details::setup_sink(generate_stdout_sink_st());
spdlog_setup::details::setup_sink(generate_stdout_sink_st(), ref_sinks);
REQUIRE(typeid(*sink) == typeid(const spdlog::sinks::stdout_sink_st &));
REQUIRE(ref_sinks.empty());
}

TEST_CASE("Parse stderr sink st", "[parse_generate_stderr_sink_st]") {
std::vector<std::string> ref_sinks;
const auto sink =
spdlog_setup::details::setup_sink(generate_stderr_sink_st());
spdlog_setup::details::setup_sink(generate_stderr_sink_st(), ref_sinks);
REQUIRE(typeid(*sink) == typeid(const spdlog::sinks::stderr_sink_st &));
REQUIRE(ref_sinks.empty());
}

TEST_CASE("Parse stdout sink mt", "[parse_generate_stdout_sink_mt]") {
std::vector<std::string> ref_sinks;
const auto sink =
spdlog_setup::details::setup_sink(generate_stdout_sink_mt());
spdlog_setup::details::setup_sink(generate_stdout_sink_mt(), ref_sinks);
REQUIRE(typeid(*sink) == typeid(const spdlog::sinks::stdout_sink_mt &));
REQUIRE(ref_sinks.empty());
}

TEST_CASE("Parse stderr sink mt", "[parse_generate_stderr_sink_mt]") {
std::vector<std::string> ref_sinks;
const auto sink =
spdlog_setup::details::setup_sink(generate_stderr_sink_mt());
spdlog_setup::details::setup_sink(generate_stderr_sink_mt(), ref_sinks);
REQUIRE(typeid(*sink) == typeid(const spdlog::sinks::stderr_sink_mt &));
REQUIRE(ref_sinks.empty());
}

TEST_CASE("Parse dist sink st", "[parse_generate_dist_sink_st]") {
std::vector<std::string> ref_sinks;
const auto sink =
spdlog_setup::details::setup_sink(generate_dist_sink_st(), ref_sinks);
REQUIRE(typeid(*sink) == typeid(const spdlog::sinks::dist_sink_st &));
REQUIRE_THAT(ref_sinks, Catch::Equals<std::string>({"sink1", "sink2"}));
}

TEST_CASE("Parse dist sink mt", "[parse_generate_dist_sink_mt]") {
std::vector<std::string> ref_sinks;
const auto sink =
spdlog_setup::details::setup_sink(generate_dist_sink_mt(), ref_sinks);
REQUIRE(typeid(*sink) == typeid(const spdlog::sinks::dist_sink_mt &));
REQUIRE_THAT(ref_sinks, Catch::Equals<std::string>({"sink1", "sink2"}));
}

TEST_CASE("Parse dist sink circular", "[parse_generate_dist_sink_circular]") {
REQUIRE_THROWS_AS(
spdlog_setup::details::setup_sinks(generate_dist_sink_circular()),
spdlog_setup::setup_error);
}

TEST_CASE("Parse dist ref sinks", "[parse_generate_dist_sink_refs]") {
auto sinks_map =
spdlog_setup::details::setup_sinks(generate_dist_sink_with_refs());
auto sink = sinks_map["dist_sink"];
REQUIRE(typeid(*sink) == typeid(const spdlog::sinks::dist_sink_mt &));
// Can't check the dist sink's wiring because there's no getter for its
// subordinate sinks, but setup_sinks returning normally rather than
// exceptionally is enough
}
Loading