Handle exceptions in async sink

pull/3309/head
gabime 8 months ago
parent 0d780b0b58
commit 833bb360a3

@ -209,8 +209,7 @@ private:
void flush_(); void flush_();
[[nodiscard]] bool should_flush_(const details::log_msg &msg) const; [[nodiscard]] bool should_flush_(const details::log_msg &msg) const;
// handle errors during logging. // default handler prints the error to stderr
// default handler prints the error to stderr at max rate of 1 message/sec.
void err_handler_(const std::string &msg); void err_handler_(const std::string &msg);
}; };

@ -70,9 +70,10 @@ private:
using queue_t = details::mpmc_blocking_queue<async_log_msg>; using queue_t = details::mpmc_blocking_queue<async_log_msg>;
void send_message_(async_log_msg::type msg_type, const details::log_msg &msg) const; void send_message_(async_log_msg::type msg_type, const details::log_msg &msg) const;
void backend_loop_() const; void backend_loop_();
void backend_log_(const details::log_msg &msg) const; void backend_log_(const details::log_msg &msg) ;
void backend_flush_() const; void backend_flush_();
void err_handler_(const std::string &msg);
config config_; config config_;
std::unique_ptr<queue_t> q_; std::unique_ptr<queue_t> q_;

@ -82,7 +82,7 @@ void async_sink::send_message_(async_log_msg::type msg_type, const details::log_
} }
} }
void async_sink::backend_loop_() const { void async_sink::backend_loop_() {
details::async_log_msg incoming_msg; details::async_log_msg incoming_msg;
for (;;) { for (;;) {
q_->dequeue(incoming_msg); q_->dequeue(incoming_msg);
@ -101,18 +101,40 @@ void async_sink::backend_loop_() const {
} }
} }
void async_sink::backend_log_(const details::log_msg &msg) const { void async_sink::backend_log_(const details::log_msg &msg) {
for (const auto &sink : config_.sinks) { for (const auto &sink : config_.sinks) {
if (sink->should_log(msg.log_level)) { if (sink->should_log(msg.log_level)) {
try {
sink->log(msg); sink->log(msg);
} catch (const std::exception &ex) {
err_handler_(std::string("async log failed: ") + ex.what());
}
} }
} }
} }
void async_sink::backend_flush_() const { void async_sink::backend_flush_() {
for (const auto &sink : config_.sinks) { for (const auto &sink : config_.sinks) {
try {
sink->flush(); sink->flush();
} catch (const std::exception &ex) {
err_handler_(std::string("async flush failed: ") + ex.what());
} catch (...) {
err_handler_("Async flush failed with unknown exception");
}
}
} }
void async_sink::err_handler_(const std::string &message) {
using std::chrono::system_clock;
const auto now = system_clock::now();
const auto tm_time = details::os::localtime(system_clock::to_time_t(now));
char date_buf[64];
std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time);
#if defined(USING_R) && defined(R_R_H) // if in R environment
REprintf("[*** LOG ERROR ***] [%s] [%s] %s\n", date_buf, name().c_str(), message.c_str());
#else
std::fprintf(stderr, "[*** LOG ERROR ***] [%s] %s\n", date_buf, message.c_str());
#endif
} }
} // namespace sinks } // namespace sinks

@ -286,3 +286,12 @@ TEST_CASE("level-off", "[async]") {
REQUIRE(test_sink->msg_counter() == 0); REQUIRE(test_sink->msg_counter() == 0);
REQUIRE(test_sink->flush_counter() == 0); REQUIRE(test_sink->flush_counter() == 0);
} }
TEST_CASE("backend_ex", "[async]") {
const auto test_sink = std::make_shared<test_sink_mt>();
test_sink->set_exception(std::runtime_error("test backend exception"));
constexpr size_t queue_size = 16;
auto [logger, async_sink] = creat_async_logger(queue_size, test_sink);
REQUIRE_NOTHROW(logger->info("Hello message"));
REQUIRE_NOTHROW(logger->flush());
}

@ -8,6 +8,7 @@
#include <chrono> #include <chrono>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <exception>
#include "spdlog/details/null_mutex.h" #include "spdlog/details/null_mutex.h"
#include "spdlog/details/os.h" #include "spdlog/details/os.h"
@ -36,6 +37,14 @@ public:
delay_ = delay; delay_ = delay;
} }
void set_exception(const std::runtime_error& ex) {
exception_ptr_ = std::make_exception_ptr(ex);
}
void clear_exception() {
exception_ptr_ = nullptr;
}
// return last output without the eol // return last output without the eol
std::vector<std::string> lines() { std::vector<std::string> lines() {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
@ -44,6 +53,9 @@ public:
protected: protected:
void sink_it_(const details::log_msg &msg) override { void sink_it_(const details::log_msg &msg) override {
if (exception_ptr_) {
std::rethrow_exception(exception_ptr_);
}
memory_buf_t formatted; memory_buf_t formatted;
base_sink<Mutex>::formatter_->format(msg, formatted); base_sink<Mutex>::formatter_->format(msg, formatted);
// save the line without the eol // save the line without the eol
@ -55,12 +67,18 @@ protected:
std::this_thread::sleep_for(delay_); std::this_thread::sleep_for(delay_);
} }
void flush_() override { flush_counter_++; } void flush_() override {
if (exception_ptr_) {
std::rethrow_exception(exception_ptr_);
}
flush_counter_++;
}
size_t msg_counter_{0}; size_t msg_counter_{0};
size_t flush_counter_{0}; size_t flush_counter_{0};
std::chrono::milliseconds delay_{std::chrono::milliseconds::zero()}; std::chrono::milliseconds delay_{std::chrono::milliseconds::zero()};
std::vector<std::string> lines_; std::vector<std::string> lines_;
std::exception_ptr exception_ptr_; // will be thrown on next log or flush if not null
}; };
using test_sink_mt = test_sink<std::mutex>; using test_sink_mt = test_sink<std::mutex>;

Loading…
Cancel
Save