diff --git a/bench/async_bench.cpp b/bench/async_bench.cpp index e902f9585..183703285 100644 --- a/bench/async_bench.cpp +++ b/bench/async_bench.cpp @@ -8,10 +8,12 @@ // #include #include +#include #include #include #include #include +#include #include "spdlog/sinks/async_sink.h" #include "spdlog/sinks/basic_file_sink.h" @@ -24,20 +26,9 @@ using namespace spdlog::sinks; void bench_mt(int howmany, std::shared_ptr log, int thread_count); -#ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable : 4996) // disable fopen warning under msvc -#endif // _MSC_VER - int count_lines(const char *filename) { - int counter = 0; - auto *infile = fopen(filename, "r"); - int ch = 0; - while (EOF != (ch = getc(infile))) { - if ('\n' == ch) counter++; - } - fclose(infile); - return counter; + std::ifstream ifs(filename); + return std::count(std::istreambuf_iterator(ifs), std::istreambuf_iterator(), '\n'); } void verify_file(const char *filename, int expected_count) { @@ -66,10 +57,10 @@ int main(int argc, char *argv[]) { try { spdlog::set_pattern("[%^%l%$] %v"); - // if (argc == 1) { - // spdlog::info("Usage: {} ", argv[0]); - // return 0; - // } + if (argc > 1 && (std::string(argv[1]) == "-h" || std::string(argv[1]) == "--help")) { + spdlog::info("Usage: {} ", argv[0]); + return 0; + } if (argc > 1) howmany = atoi(argv[1]); if (argc > 2) threads = atoi(argv[2]); @@ -82,6 +73,11 @@ int main(int argc, char *argv[]) { } if (argc > 4) iters = atoi(argv[4]); + // validate all argc values + if (howmany < 1 || threads < 1 || queue_size < 1 || iters < 1) { + spdlog::error("Invalid input values"); + exit(1); + } auto slot_size = sizeof(details::async_log_msg); spdlog::info("-------------------------------------------------"); @@ -98,14 +94,17 @@ int main(int argc, char *argv[]) { spdlog::info("Queue Overflow Policy: block"); spdlog::info("*********************************"); for (int i = 0; i < iters; i++) { - auto async_sink = std::make_shared(queue_size); - auto file_sink = std::make_shared(filename, true); - async_sink->add_sink(std::move(file_sink)); - auto logger = std::make_shared("async_logger", std::move(async_sink)); - bench_mt(howmany, std::move(logger), threads); - verify_file(filename, howmany); + { + auto file_sink = std::make_shared(filename, true); + auto cfg = async_sink::config(); + cfg.queue_size = queue_size; + cfg.sinks.push_back(std::move(file_sink)); + auto async_sink = std::make_shared(cfg); + auto logger = std::make_shared("async_logger", std::move(async_sink)); + bench_mt(howmany, std::move(logger), threads); + } + //verify_file(filename, howmany); // in separate scope to ensure logger is destroyed and all logs were written } - spdlog::info(""); spdlog::info("*********************************"); spdlog::info("Queue Overflow Policy: overrun"); @@ -113,10 +112,12 @@ int main(int argc, char *argv[]) { // do same test but discard the oldest if queue is full instead of blocking filename = "logs/basic_async-overrun.log"; for (int i = 0; i < iters; i++) { - auto async_sink = std::make_shared(queue_size); - async_sink->set_overflow_policy(async_sink_mt::overflow_policy::overrun_oldest); + async_sink::config cfg; + cfg.policy = async_sink::overflow_policy::overrun_oldest; + cfg.queue_size = queue_size; auto file_sink = std::make_shared(filename, true); - async_sink->add_sink(std::move(file_sink)); + cfg.sinks.push_back(std::move(file_sink)); + auto async_sink = std::make_shared(cfg); auto logger = std::make_shared("async_logger", std::move(async_sink)); bench_mt(howmany, std::move(logger), threads); } diff --git a/include/spdlog/sinks/async_sink.h b/include/spdlog/sinks/async_sink.h index 8b408ebde..c1c2a214c 100644 --- a/include/spdlog/sinks/async_sink.h +++ b/include/spdlog/sinks/async_sink.h @@ -3,14 +3,16 @@ #include #include #include +#include #include +#include #include "../details/async_log_msg.h" -#include "dist_sink.h" +#include "sink.h" // async_sink is a sink that sends log messages to a dist_sink in a separate thread using a queue. // The worker thread dequeues the messages and sends them to the dist_sink to perform the actual logging. -// The worker thread is terminated when the async_sink is destroyed. +// Once the sink is destroyed, the worker thread empties the queue and exits. namespace spdlog::details { // forward declaration template @@ -20,61 +22,62 @@ class mpmc_blocking_queue; namespace spdlog { namespace sinks { -template -class async_sink final : public dist_sink { +class async_sink final : public sink { public: - using base_t = dist_sink; - using async_log_msg = details::async_log_msg; - using queue_t = details::mpmc_blocking_queue; - enum { default_queue_size = 8192, max_queue_size = 1024 * 1024 * 10 }; - - // Async overflow policy - block by default. enum class overflow_policy : std::uint8_t { block, // Block until the log message can be enqueued (default). overrun_oldest, // Overrun the oldest message in the queue if full. discard_new // Discard the log message if the queue is full }; - async_sink(size_t queue_size, std::function on_thread_start, std::function on_thread_stop); - ~async_sink() override; + enum { default_queue_size = 8192, max_queue_size = 10 * 1024 * 1024 }; + + struct config { + size_t queue_size = default_queue_size; + overflow_policy policy = overflow_policy::block; + std::vector> sinks; + std::function on_thread_start = nullptr; + std::function on_thread_stop = nullptr; + }; + + explicit async_sink(config async_config); + + // create an async_sink with one backend sink + template + static std::shared_ptr with_sink(SinkArgs &&...sink_args) { + config cfg{}; + cfg.sinks.emplace_back(std::make_shared(std::forward(sink_args)...)); + return std::make_shared(cfg); + } - async_sink(); - explicit async_sink(size_t queue_size); - async_sink(std::function on_thread_start, std::function on_thread_stop); - async_sink(const async_sink &) = delete; - async_sink &operator=(const async_sink &) = delete; - async_sink(async_sink &&) = default; - async_sink &operator=(async_sink &&) = default; + ~async_sink() override; - void set_overflow_policy(overflow_policy policy); - [[nodiscard]] overflow_policy get_overflow_policy() const; + // sink interface implementation + void log(const details::log_msg &msg) override; + void flush() override; + void set_pattern(const std::string &pattern) override; + void set_formatter(std::unique_ptr sink_formatter) override; + // async sink specific methods [[nodiscard]] size_t get_overrun_counter() const; void reset_overrun_counter() const; - [[nodiscard]] size_t get_discard_counter() const; void reset_discard_counter() const; + [[nodiscard]] const config &get_config() const; private: - void sink_it_(const details::log_msg &msg) override; - void flush_() override; - void send_message_(async_log_msg::type msg_type, const details::log_msg &msg); - void backend_loop_(); + using async_log_msg = details::async_log_msg; + using queue_t = details::mpmc_blocking_queue; - std::atomic overflow_policy_ = overflow_policy::block; + void send_message_(async_log_msg::type msg_type, const details::log_msg &msg) const; + void backend_loop_() const; + void backend_log_(const details::log_msg &msg) const; + void backend_flush_() const; + + config config_; std::unique_ptr q_; std::thread worker_thread_; }; -using async_sink_mt = async_sink; -using async_sink_st = async_sink; - } // namespace sinks - -class logger; -template -std::shared_ptr create_async(std::string logger_name, SinkArgs &&...sink_args) { - auto async_sink = std::make_shared(std::forward(sink_args)...); - return std::make_shared(std::move(logger_name), std::move(async_sink)); -} } // namespace spdlog diff --git a/src/sinks/async_sink.cpp b/src/sinks/async_sink.cpp index d47f6983b..49b81b8c2 100644 --- a/src/sinks/async_sink.cpp +++ b/src/sinks/async_sink.cpp @@ -3,99 +3,71 @@ #include "spdlog/sinks/async_sink.h" +#include #include #include -#include -#include "spdlog/details/mpmc_blocking_q.h" #include "spdlog/common.h" +#include "spdlog/details/mpmc_blocking_q.h" #include "spdlog/pattern_formatter.h" +#include "spdlog/spdlog.h" namespace spdlog { namespace sinks { -template -async_sink::async_sink(size_t queue_size, std::function on_thread_start, std::function on_thread_stop) - : base_t() { - if (queue_size == 0 || queue_size > max_queue_size) { +async_sink::async_sink(config async_config) + : config_(std::move(async_config)) { + if (config_.queue_size == 0 || config_.queue_size > max_queue_size) { throw spdlog_ex("async_sink: invalid queue size"); } - // printf("........... Allocating queue: slot: %zu X %zu bytes ====> %lld KB ..............\n", - // queue_size, sizeof(details::async_log_msg), (sizeof(details::async_log_msg) * queue_size)/1024); - q_ = std::make_unique(queue_size); - - worker_thread_ = std::thread([this, on_thread_start, on_thread_stop] { - if (on_thread_start) on_thread_start(); + q_ = std::make_unique(config_.queue_size); + worker_thread_ = std::thread([this] { + if (config_.on_thread_start) config_.on_thread_start(); this->backend_loop_(); - if (on_thread_stop) on_thread_stop(); + if (config_.on_thread_stop) config_.on_thread_stop(); }); } -template -async_sink::~async_sink() { +async_sink::~async_sink() { try { q_->enqueue(async_log_msg(async_log_msg::type::terminate)); worker_thread_.join(); } catch (...) { + printf("Exception in ~async_sink()\n"); } }; -template -async_sink::async_sink() - : async_sink(default_queue_size, nullptr, nullptr) {} +void async_sink::log(const details::log_msg &msg) { send_message_(async_log_msg::type::log, msg); } -template -async_sink::async_sink(size_t queue_size) - : async_sink(queue_size, nullptr, nullptr) {} +void async_sink::flush() { send_message_(async_log_msg::type::flush, details::log_msg()); } -template -async_sink::async_sink(std::function on_thread_start, std::function on_thread_stop) - : async_sink(default_queue_size, on_thread_start, on_thread_stop) {} +void async_sink::set_pattern(const std::string &pattern) { set_formatter(std::make_unique(pattern)); } -template -void async_sink::sink_it_(const details::log_msg &msg) { - send_message_(async_log_msg::type::log, msg); +void async_sink::set_formatter(std::unique_ptr formatter) { + const auto &sinks = config_.sinks; + for (auto it = sinks.begin(); it != sinks.end(); ++it) { + if (std::next(it) == sinks.end()) { + // last element - we can move it. + (*it)->set_formatter(std::move(formatter)); + break; // to prevent clang-tidy warning + } + (*it)->set_formatter(formatter->clone()); + } } +size_t async_sink::get_overrun_counter() const { return q_->overrun_counter(); } -template -void async_sink::set_overflow_policy(overflow_policy policy) { - overflow_policy_ = policy; -} - -template -typename async_sink::overflow_policy async_sink::get_overflow_policy() const { - return overflow_policy_; -} +void async_sink::reset_overrun_counter() const { q_->reset_overrun_counter(); } -template -size_t async_sink::get_overrun_counter() const { - return q_->overrun_counter(); -} +size_t async_sink::get_discard_counter() const { return q_->discard_counter(); } -template -void async_sink::reset_overrun_counter() const { - q_->reset_overrun_counter(); -} +void async_sink::reset_discard_counter() const { q_->reset_discard_counter(); } -template -size_t async_sink::get_discard_counter() const { - return q_->discard_counter(); -} +const async_sink::config &async_sink::get_config() const { return config_; } -template -void async_sink::reset_discard_counter() const { - q_->reset_discard_counter(); -} - -template -void async_sink::flush_() { - send_message_(async_log_msg::type::flush, details::log_msg()); -} - -template -void async_sink::send_message_(async_log_msg::type msg_type, const details::log_msg &msg) { - switch (overflow_policy_) { +// private methods +void async_sink::send_message_(async_log_msg::type msg_type, const details::log_msg &msg) const { + switch (config_.policy) { case overflow_policy::block: q_->enqueue(async_log_msg(msg_type, msg)); break; @@ -111,17 +83,16 @@ void async_sink::send_message_(async_log_msg::type msg_type, const detail } } -template -void async_sink::backend_loop_() { +void async_sink::backend_loop_() const { details::async_log_msg incoming_msg; for (;;) { q_->dequeue(incoming_msg); switch (incoming_msg.message_type()) { case async_log_msg::type::log: - base_t::sink_it_(incoming_msg); + backend_log_(incoming_msg); break; case async_log_msg::type::flush: - base_t::flush_(); + backend_flush_(); break; case async_log_msg::type::terminate: return; @@ -131,10 +102,19 @@ void async_sink::backend_loop_() { } } +void async_sink::backend_log_(const details::log_msg &msg) const { + for (const auto &sink : config_.sinks) { + if (sink->should_log(msg.log_level)) { + sink->log(msg); + } + } +} + +void async_sink::backend_flush_() const { + for (const auto &sink : config_.sinks) { + sink->flush(); + } +} + } // namespace sinks } // namespace spdlog - -// template instantiations -#include "spdlog/details/null_mutex.h" -template class SPDLOG_API spdlog::sinks::async_sink; -template class SPDLOG_API spdlog::sinks::async_sink; diff --git a/tests/test_async.cpp b/tests/test_async.cpp index 1cb226412..db55572b5 100644 --- a/tests/test_async.cpp +++ b/tests/test_async.cpp @@ -7,15 +7,17 @@ #define TEST_FILENAME "test_logs/async_test.log" -using spdlog::sinks::async_sink_mt; +using spdlog::sinks::async_sink; using spdlog::sinks::sink; using spdlog::sinks::test_sink_mt; auto creat_async_logger(size_t queue_size, std::shared_ptr backend_sink) { - auto async_sink = std::make_shared(queue_size); - async_sink->add_sink(std::move(backend_sink)); - auto logger = std::make_shared("async_logger", async_sink); - return std::make_tuple(logger, async_sink); + async_sink::config cfg; + cfg.queue_size = queue_size; + cfg.sinks.push_back(std::move(backend_sink)); + auto s = std::make_shared(cfg); + auto logger = std::make_shared("async_logger", s); + return std::make_tuple(logger, s); } TEST_CASE("basic async test ", "[async]") { @@ -40,41 +42,46 @@ TEST_CASE("basic async test ", "[async]") { TEST_CASE("discard policy ", "[async]") { auto test_sink = std::make_shared(); test_sink->set_delay(std::chrono::milliseconds(1)); - size_t queue_size = 4; + async_sink::config config; + config.queue_size = 4; + config.policy = async_sink::overflow_policy::overrun_oldest; + config.sinks.push_back(test_sink); size_t messages = 1024; - - auto [logger, async_sink] = creat_async_logger(queue_size, test_sink); - async_sink->set_overflow_policy(async_sink_mt::overflow_policy::overrun_oldest); - REQUIRE(async_sink->get_overflow_policy() == async_sink_mt::overflow_policy::overrun_oldest); - REQUIRE(async_sink->get_discard_counter() == 0); - REQUIRE(async_sink->get_overrun_counter() == 0); + auto as = std::make_shared(config); + auto logger = std::make_shared("async_logger", as); + REQUIRE(as->get_discard_counter() == 0); + REQUIRE(as->get_overrun_counter() == 0); for (size_t i = 0; i < messages; i++) { logger->info("Hello message"); } REQUIRE(test_sink->msg_counter() < messages); - REQUIRE(async_sink->get_overrun_counter() > 0); - async_sink->reset_overrun_counter(); - REQUIRE(async_sink->get_overrun_counter() == 0); + REQUIRE(as->get_overrun_counter() > 0); + as->reset_overrun_counter(); + REQUIRE(as->get_overrun_counter() == 0); } TEST_CASE("discard policy discard_new ", "[async]") { auto test_sink = std::make_shared(); test_sink->set_delay(std::chrono::milliseconds(1)); - size_t queue_size = 4; + async_sink::config config; + config.queue_size = 4; + config.policy = async_sink::overflow_policy::discard_new; + config.sinks.push_back(test_sink); size_t messages = 1024; + auto as = std::make_shared(config); + auto logger = std::make_shared("async_logger", as); + - auto [logger, async_sink] = creat_async_logger(queue_size, test_sink); - async_sink->set_overflow_policy(async_sink_mt::overflow_policy::discard_new); - REQUIRE(async_sink->get_overflow_policy() == async_sink_mt::overflow_policy::discard_new); - REQUIRE(async_sink->get_discard_counter() == 0); - REQUIRE(async_sink->get_overrun_counter() == 0); + REQUIRE(as->get_config().policy == async_sink::overflow_policy::discard_new); + REQUIRE(as->get_discard_counter() == 0); + REQUIRE(as->get_overrun_counter() == 0); for (size_t i = 0; i < messages; i++) { logger->info("Hello message"); } REQUIRE(test_sink->msg_counter() < messages); - REQUIRE(async_sink->get_discard_counter() > 0); - async_sink->reset_discard_counter(); - REQUIRE(async_sink->get_discard_counter() == 0); + REQUIRE(as->get_discard_counter() > 0); + as->reset_discard_counter(); + REQUIRE(as->get_discard_counter() == 0); } TEST_CASE("flush", "[async]") { @@ -86,7 +93,6 @@ TEST_CASE("flush", "[async]") { for (size_t i = 0; i < messages; i++) { logger->info("Hello message #{}", i); } - logger->flush(); } // std::this_thread::sleep_for(std::chrono::milliseconds(250)); @@ -95,19 +101,22 @@ TEST_CASE("flush", "[async]") { } TEST_CASE("wait_dtor ", "[async]") { - auto test_sink = std::make_shared(); + auto test_sink = std::make_shared(); test_sink->set_delay(std::chrono::milliseconds(5)); + async_sink::config config; + config.sinks.push_back(test_sink); + config.queue_size = 4; + config.policy = async_sink::overflow_policy::block; size_t messages = 100; { - auto [logger, async_sink] = creat_async_logger(messages, test_sink); - async_sink->set_overflow_policy(async_sink_mt::overflow_policy::block); - + auto as = std::make_shared(config); + auto logger = std::make_shared("async_logger", as); for (size_t i = 0; i < messages; i++) { logger->info("Hello message #{}", i); } logger->flush(); - REQUIRE(async_sink->get_overrun_counter() == 0); - REQUIRE(async_sink->get_discard_counter() == 0); + REQUIRE(as->get_overrun_counter() == 0); + REQUIRE(as->get_discard_counter() == 0); } REQUIRE(test_sink->msg_counter() == messages); @@ -158,15 +167,27 @@ TEST_CASE("to_file", "[async]") { REQUIRE(ends_with(contents, spdlog::fmt_lib::format("Hello message #1023{}", default_eol))); } -TEST_CASE("bad_ctor", "[async]") { REQUIRE_THROWS_AS(std::make_shared(0), spdlog::spdlog_ex); } -TEST_CASE("bad_ctor2", "[async]") { REQUIRE_THROWS_AS(std::make_shared(-1), spdlog::spdlog_ex); } +TEST_CASE("bad_ctor", "[async]") { + async_sink::config cfg; + cfg.queue_size = 0; + REQUIRE_THROWS_AS(std::make_shared(cfg), spdlog::spdlog_ex); +} + +TEST_CASE("bad_ctor2", "[async]") { + async_sink::config cfg; + cfg.queue_size = async_sink::max_queue_size + 1; + REQUIRE_THROWS_AS(std::make_shared(cfg), spdlog::spdlog_ex); +} TEST_CASE("start_stop_clbks", "[async]") { bool start_called = false; bool stop_called = false; { - auto sink = std::make_shared([&] { start_called = true; }, [&] { stop_called = true; }); + async_sink::config cfg; + cfg.on_thread_start = [&] { start_called = true; }; + cfg.on_thread_stop = [&] { stop_called = true; }; + auto sink = std::make_shared(cfg); } REQUIRE(start_called); REQUIRE(stop_called); @@ -176,7 +197,9 @@ TEST_CASE("start_stop_clbks2", "[async]") { bool start_called = false; bool stop_called = false; { - auto sink = std::make_shared([&] { start_called = true; }, nullptr); + async_sink::config cfg; + cfg.on_thread_start = [&] { start_called = true; }; + auto sink = std::make_shared(cfg); } REQUIRE(start_called); REQUIRE_FALSE(stop_called); @@ -186,7 +209,10 @@ TEST_CASE("start_stop_clbks3", "[async]") { bool start_called = false; bool stop_called = false; { - auto sink = std::make_shared(nullptr, [&] { stop_called = true; }); + async_sink::config cfg; + cfg.on_thread_start = nullptr; + cfg.on_thread_stop = [&] { stop_called = true; }; + auto sink = std::make_shared(cfg); } REQUIRE_FALSE(start_called); REQUIRE(stop_called); @@ -196,17 +222,26 @@ TEST_CASE("start_stop_clbks4", "[async]") { bool start_called = false; bool stop_called = false; { - auto sink = std::make_shared(128, [&] { start_called = true; }, [&] { stop_called = true; }); + async_sink::config cfg; + cfg.on_thread_start = [&] { start_called = true; }; + cfg.on_thread_stop = [&] { stop_called = true; }; + cfg.queue_size = 128; + auto sink = std::make_shared(cfg); } REQUIRE(start_called); REQUIRE(stop_called); } +// should not start threads if queue size is invalid TEST_CASE("start_stop_clbks5", "[async]") { bool start_called = false; bool stop_called = false; { - REQUIRE_THROWS(std::make_shared(0, [&] { start_called = true; }, [&] { stop_called = true; })); + async_sink::config cfg; + cfg.on_thread_start = [&] { start_called = true; }; + cfg.on_thread_stop = [&] { stop_called = true; }; + cfg.queue_size = 0; + REQUIRE_THROWS_AS(std::make_shared(cfg), spdlog::spdlog_ex); } REQUIRE_FALSE(start_called); REQUIRE_FALSE(stop_called); @@ -214,17 +249,20 @@ TEST_CASE("start_stop_clbks5", "[async]") { TEST_CASE("multi-sinks", "[async]") { prepare_logdir(); - auto test_sink1 = std::make_shared(); - auto test_sink2 = std::make_shared(); - auto test_sink3 = std::make_shared(); + auto test_sink1 = std::make_shared(); + auto test_sink2 = std::make_shared(); + auto test_sink3 = std::make_shared(); size_t messages = 1024; { - auto [logger, async_sink] = creat_async_logger(messages, test_sink1); - async_sink->add_sink(test_sink2); - async_sink->add_sink(test_sink3); + async_sink::config cfg; + cfg.sinks.push_back(test_sink1); + cfg.sinks.push_back(test_sink2); + cfg.sinks.push_back(test_sink3); + auto as = std::make_shared(cfg); + spdlog::logger l("async_logger", as); for (size_t j = 0; j < messages; j++) { - logger->info("Hello message #{}", j); + l.info("Hello message #{}", j); } } REQUIRE(test_sink1->msg_counter() == messages); diff --git a/tests/test_misc.cpp b/tests/test_misc.cpp index d58a90476..5f2f627b1 100644 --- a/tests/test_misc.cpp +++ b/tests/test_misc.cpp @@ -95,24 +95,25 @@ TEST_CASE("clone-logger", "[clone]") { TEST_CASE("clone async", "[clone]") { using spdlog::sinks::test_sink_mt; - auto async_sink = std::make_shared(32); auto test_sink = std::make_shared(); - async_sink->add_sink(test_sink); - auto logger = std::make_shared("orig", async_sink); - logger->set_pattern("%v"); - auto cloned = logger->clone("clone"); - - REQUIRE(cloned->name() == "clone"); - REQUIRE(logger->sinks() == cloned->sinks()); - REQUIRE(logger->log_level() == cloned->log_level()); - REQUIRE(logger->flush_level() == cloned->flush_level()); - - logger->info("Some message 1"); - cloned->info("Some message 2"); - spdlog::details::os::sleep_for_millis(100); + { + auto cfg = spdlog::sinks::async_sink::config(); + cfg.sinks.push_back(test_sink); + auto async_sink = spdlog::sinks::async_sink::with_sink(); + auto logger = spdlog::create("orig", cfg); + logger->set_pattern("*** %v ***"); + auto cloned = logger->clone("clone"); + REQUIRE(cloned->name() == "clone"); + REQUIRE(logger->sinks() == cloned->sinks()); + REQUIRE(logger->log_level() == cloned->log_level()); + REQUIRE(logger->flush_level() == cloned->flush_level()); + + logger->info("Some message 1"); + cloned->info("Some message 2"); + } REQUIRE(test_sink->lines().size() == 2); - REQUIRE(test_sink->lines()[0] == "Some message 1"); - REQUIRE(test_sink->lines()[1] == "Some message 2"); + REQUIRE(test_sink->lines()[0] == "*** Some message 1 ***"); + REQUIRE(test_sink->lines()[1] == "*** Some message 2 ***"); } TEST_CASE("global logger API", "[global logger]") {