spdlog是一个用c++11实现的高性能日志库。
接入方便,功能丰富,代码可读性较高。
Features
- Very fast - performance is the primary goal
- Headers only, just copy and use.
- Feature rich using the excellent fmt library.
- Extremely fast asynchronous mode (optional) - using lockfree queues and other tricks to reach millions of calls/sec.
- Custom formatting.
- Multi/Single threaded loggers.
- Various log targets:
- Rotating log files.
- Daily log files.
- Console logging (colors supported).
- syslog.
- Windows debugger
- Easily extendable with custom log targets (just implement a single function in the sink interface).
- Severity based filtering - threshold levels can be modified in runtime as well as in compile time.
核心设计
spdlog中定义了一系列Sink类,作为实际上把log输出到指定目标的对象,每一个Sink唯一对应一个log的输出目标(如console、文件、db).
每一个logger包含一个sink列表,每当上层调用logger的log方法写日志时,logger就会调用sink列表中的每一个sink的 sink(log_msg)
函数,真正往目标中写日志。
实现Rotating file写日志
当前正在写日志的文件名一直都是log.txt。
当log.txt写不下时,把它重命名为log1.txt,再重新打开一个log.txt文件。
多个文件循环也依次类推。
// Rotate files:
// log.txt -> log.1.txt
// log.1.txt -> log2.txt
// log.2.txt -> log3.txt
// log.3.txt -> delete
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| * Rotating file sink based on size */ template<class Mutex> class rotating_file_sink : public base_sink < Mutex > { public: rotating_file_sink(const filename_t &base_filename, const filename_t &extension, std::size_t max_size, std::size_t max_files ) : { _file_helper.open(calc_filename(_base_filename, 0, _extension)); _current_size = _file_helper.size(); } protected: void _sink_it(const details::log_msg& msg) override { _current_size += msg.formatted.size(); if (_current_size > _max_size) { _rotate(); _current_size = msg.formatted.size(); } _file_helper.write(msg); } private: static filename_t calc_filename(const filename_t& filename, std::size_t index, const filename_t& extension) { return w.str(); } void _rotate() { using details::os::filename_to_str; _file_helper.close(); for (auto i = _max_files; i > 0; --i) { filename_t src = calc_filename(_base_filename, i - 1, _extension); filename_t target = calc_filename(_base_filename, i, _extension); if (details::file_helper::file_exists(target)) { details::os::remove(target); } if (details::file_helper::file_exists(src) && details::os::rename(src, target)) { } } _file_helper.reopen(true); } filename_t _base_filename; filename_t _extension; std::size_t _max_size; std::size_t _max_files; std::size_t _current_size; details::file_helper _file_helper; };
|
实现异步写日志
所有异步写日志的请求都会被push到一个固定大小的队列中。
有一个工作线程会不停的从队列中pop一条日志消息,并最终写到日志中。
调用方:
1 2 3 4 5 6 7 8 9
| void async_example() { size_t q_size = 4096; spdlog::set_async_mode(q_size); auto async_file = spd::daily_logger_st("async_file_logger", "logs/async_log.txt"); for (int i = 0; i < 100; ++i) async_file->info("Async message #{}", i); }
|
具体实现:
开启一个工作线程,在async_log初始时就开始工作了。并且在析构是被关闭
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| std::thread _worker_thread; _worker_thread(&async_log_helper::worker_loop, this) inline void spdlog::details::async_log_helper::worker_loop() { try { if (_worker_warmup_cb) _worker_warmup_cb(); auto last_pop = details::os::now(); auto last_flush = last_pop; while(process_next_msg(last_pop, last_flush)); if (_worker_teardown_cb) _worker_teardown_cb(); } catch (const std::exception &ex) { _err_handler(ex.what()); } catch (...) { _err_handler("Unknown exception"); } } inline spdlog::details::async_log_helper::~async_log_helper() { try { push_msg(async_msg(async_msg_type::terminate)); _worker_thread.join(); } catch (...) {} }
|
从队列中取消息,如果是写日志的消息,则调用sink写日志。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| inline bool spdlog::details::async_log_helper::process_next_msg(log_clock::time_point& last_pop, log_clock::time_point& last_flush) { async_msg incoming_async_msg; if (_q.dequeue(incoming_async_msg)) { last_pop = details::os::now(); switch (incoming_async_msg.msg_type) { case async_msg_type::flush: _flush_requested = true; break; case async_msg_type::terminate: _flush_requested = true; _terminate_requested = true; break; default: log_msg incoming_log_msg; incoming_async_msg.fill_log_msg(incoming_log_msg); _formatter->format(incoming_log_msg); for (auto &s : _sinks) { if(s->should_log( incoming_log_msg.level)) { s->log(incoming_log_msg); } } } return true; } else { auto now = details::os::now(); handle_flush_interval(now, last_flush); sleep_or_yield(now, last_pop); return !_terminate_requested; } }
|
调用写日志,往队列里塞一条写日志消息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| template<typename T> inline void spdlog::logger::log(level::level_enum lvl, const T& msg) { if (!should_log(lvl)) return; try { details::log_msg log_msg(&_name, lvl); log_msg.raw << msg; _sink_it(log_msg); } catch (const std::exception &ex) { _err_handler(ex.what()); } catch (...) { _err_handler("Unknown exception"); } } inline void spdlog::async_logger::_sink_it(details::log_msg& msg) { try { _async_log_helper->log(msg); if (_should_flush_on(msg)) _async_log_helper->flush(false); } catch (const std::exception &ex) { _err_handler(ex.what()); } catch (...) { _err_handler("Unknown exception"); } } inline void spdlog::details::async_log_helper::log(const details::log_msg& msg) { push_msg(async_msg(msg)); } inline void spdlog::details::async_log_helper::push_msg(details::async_log_helper::async_msg&& new_msg) { if (!_q.enqueue(std::move(new_msg)) && _overflow_policy != async_overflow_policy::discard_log_msg) { auto last_op_time = details::os::now(); auto now = last_op_time; do { now = details::os::now(); sleep_or_yield(now, last_op_time); } while (!_q.enqueue(std::move(new_msg))); } }
|
根据时间间隔让线程等待
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| inline void spdlog::details::async_log_helper::sleep_or_yield(const spdlog::log_clock::time_point& now, const spdlog::log_clock::time_point& last_op_time) { using namespace std::this_thread; using std::chrono::milliseconds; using std::chrono::microseconds; auto time_since_op = now - last_op_time; if (time_since_op <= microseconds(50)) return; if (time_since_op <= microseconds(100)) return std::this_thread::yield(); if (time_since_op <= milliseconds(200)) return sleep_for(milliseconds(20)); return sleep_for(milliseconds(200)); }
|