From e695757dbd98398e0c41a8fdbbb4eca513bbf3c4 Mon Sep 17 00:00:00 2001 From: Nikola Gamzakov Date: Mon, 14 Jan 2019 16:21:16 +0200 Subject: [PATCH] Added rotating_file_sink_extended, as normal rotatating_file_sink with addition to have timestamp in the filename, and to rotate daily --- example/example.cpp | 41 +++ .../sinks/rotating_file_sink_extended.h | 258 ++++++++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 include/spdlog/sinks/rotating_file_sink_extended.h diff --git a/example/example.cpp b/example/example.cpp index 345f8bd1..59dfafbe 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -21,6 +21,7 @@ void user_defined_example(); void err_handler_example(); void syslog_example(); void clone_example(); +void rotating_example_extended(); #include "spdlog/spdlog.h" @@ -57,6 +58,7 @@ int main(int, char *[]) user_defined_example(); err_handler_example(); trace_example(); + rotating_example_extended(); // Flush all *registered* loggers using a worker thread every 3 seconds. // note: registered loggers *must* be thread safe for this to work correctly! @@ -187,6 +189,45 @@ void multi_sink_example() logger.info("this message should not appear in the console, only in the file"); } +#include "spdlog/sinks/rotating_file_sink_extended.h" +void rotating_example_extended() +{ + // get current time + auto now = spdlog::log_clock::now(); + auto tnow = spdlog::log_clock::to_time_t(now); + auto lnow = spdlog::details::os::localtime(tnow); + + int rotation_minute = (lnow.tm_min + 1) % 60; + int rotation_hour = lnow.tm_min < 1 ? lnow.tm_hour++ % 24 : lnow.tm_hour; + int wait_time = 60 - lnow.tm_sec + 1; + + // create logger with size 128 bytes + // will rotate files when size is reached + // will create new file in 2 minutes regardless of size + + // max files will apply to files with the same name + // if not date is needed in filename pass is as empty string (or wstring) + // auto rotating_logger = spdlog::rotating_logger_extended_st( + // "rotating_daily_logger", "logs/rotating_daily.txt", "", 128, 5, rotation_hour, rotation_minute); + auto rotating_logger = spdlog::rotating_logger_extended_st( + "rotating_daily_logger", "logs/rotating_daily.txt", "%Y-%m-%d_%H-%M", 128, 5, rotation_hour, rotation_minute); + + // set only message to be printed for more precision when writing + rotating_logger->set_pattern("%v"); + rotating_logger->set_level(spdlog::level::debug); + + rotating_logger->log(spdlog::level::debug, "Message 1: 60 bytes: junk data to fill up to 60 bytes......."); + rotating_logger->log(spdlog::level::debug, "Message 2: 60 bytes: junk data to fill up to 60 bytes......."); + // overflow, next message will go in new file + rotating_logger->log(spdlog::level::debug, "Message 3: 60 bytes: junk data to fill up to 60 bytes......."); + + // wait to pass the roration minute + spdlog::details::os::sleep_for_millis(1000 * wait_time); + + // daily rotation, next message will go in new file regardless old file has 60 bytes in it and will not overflow + rotating_logger->log(spdlog::level::debug, "Message 4: 60 bytes: junk data to fill up to 60 bytes......."); +} + // User defined types logging by implementing operator<< #include "spdlog/fmt/ostr.h" // must be included struct my_type diff --git a/include/spdlog/sinks/rotating_file_sink_extended.h b/include/spdlog/sinks/rotating_file_sink_extended.h new file mode 100644 index 00000000..25991c8f --- /dev/null +++ b/include/spdlog/sinks/rotating_file_sink_extended.h @@ -0,0 +1,258 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#ifndef SPDLOG_H +#error "spdlog.h must be included before this file." +#endif + +#include "spdlog/details/file_helper.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/fmt/fmt.h" +#include "spdlog/sinks/base_sink.h" + +#include +#include +#include +#include +#include + +namespace spdlog { +namespace sinks { + +template +class rotating_file_sink_extended final : public sinks::base_sink +{ +public: + rotating_file_sink_extended( + const filename_t &base_filename, const filename_t &datetime_ext, std::size_t max_size, std::size_t max_files) + : base_filename_(std::move(base_filename)) + , base_datetime_ext_(std::move(datetime_ext)) + , max_size_(max_size) + , max_files_(max_files) + , daily_rotate_(false) + { + file_helper_.open(calc_filename(0)); + current_size_ = file_helper_.size(); // expensive. called only once + } + + rotating_file_sink_extended(const filename_t &base_filename, const filename_t &datetime_ext, std::size_t max_size, + std::size_t max_files, int rotation_hour, int rotation_minute) + : base_filename_(std::move(base_filename)) + , base_datetime_ext_(std::move(datetime_ext)) + , max_size_(max_size) + , max_files_(max_files) + , rotation_h_(rotation_hour) + , rotation_m_(rotation_minute) + , daily_rotate_(true) + { + if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59) + { + throw spdlog_ex("daily_file_sink: Invalid rotation time in ctor"); + } + + file_helper_.open(calc_filename(0)); + current_size_ = file_helper_.size(); // expensive. called only once + rotation_tp_ = next_rotation_tp_(); + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + current_size_ += formatted.size(); + + if (daily_rotate_ && msg.time >= rotation_tp_) + { + file_helper_.open(calc_filename(0)); + rotation_tp_ = next_rotation_tp_(); + current_size_ = formatted.size(); + } + + if (current_size_ > max_size_) + { + rotate_(); + current_size_ = formatted.size(); + } + + file_helper_.write(formatted); + } + + void flush_() override + { + file_helper_.flush(); + } + +private: + filename_t calc_filename(std::size_t index) + { + typename std::conditional::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w; + typename std::conditional::value, fmt::memory_buffer, fmt::wmemory_buffer>::type wt; + + filename_t filename = base_filename_; + if (base_datetime_ext_.length() > 0) + { + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + fmt::format_to(wt, SPDLOG_FILENAME_T("{}_{}{}"), basename, formated_time(), ext); + filename = fmt::to_string(wt); + } + + if (index != 0u) + { + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + fmt::format_to(w, SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); + } + else + { + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + fmt::format_to(w, SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); + } + + return fmt::to_string(w); + } + + void rotate_() + { + rotate_files_(); + file_helper_.close(); + file_helper_.open(calc_filename(0)); + } + + void rotate_files_() + { + using details::os::filename_to_str; + file_helper_.close(); + for (auto i = max_files_; i > 0; --i) + { + filename_t src = calc_filename(i - 1); + if (!details::file_helper::file_exists(src)) + { + continue; + } + filename_t target = calc_filename(i); + + if (!rename_file(src, target)) + { + // if failed try again after a small delay. + // this is a workaround to a windows issue, where very high rotation + // rates can cause the rename to fail with permission denied (because of antivirus?). + details::os::sleep_for_millis(100); + if (!rename_file(src, target)) + { + file_helper_.reopen(true); // truncate the log file anyway to prevent it to grow beyond its limit! + current_size_ = 0; + throw spdlog_ex( + "rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); + } + } + } + } + + filename_t formated_time() + { + static filename_t empty_time; + + if (base_datetime_ext_.length() > 0) + { + auto now = log_clock::now(); + tm date = now_tm(now); + +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) + wchar_t buffer[63]; + wcsftime(buffer, sizeof(buffer), base_datetime_ext_.c_str(), &date); + return filename_t(buffer); +#else + char buffer[63]; + strftime(buffer, sizeof(buffer), base_datetime_ext_.c_str(), &date); + return filename_t(buffer); +#endif + } + + return empty_time; + } + + tm now_tm(log_clock::time_point tp) + { + time_t tnow = log_clock::to_time_t(tp); + return details::os::localtime(tnow); + } + + log_clock::time_point next_rotation_tp_() + { + auto now = log_clock::now(); + tm date = now_tm(now); + date.tm_hour = rotation_h_; + date.tm_min = rotation_m_; + date.tm_sec = 0; + auto rotation_time = log_clock::from_time_t(std::mktime(&date)); + if (rotation_time > now) + { + return rotation_time; + } + + return {rotation_time + std::chrono::hours(24)}; + } + + // delete the target if exists, and rename the src file to target + // return true on success, false otherwise. + bool rename_file(const filename_t &src_filename, const filename_t &target_filename) + { + // try to delete the target file in case it already exists. + (void)details::os::remove(target_filename); + return details::os::rename(src_filename, target_filename) == 0; + } + + filename_t base_filename_; + filename_t base_datetime_ext_; + std::size_t max_size_; + std::size_t max_files_; + std::size_t current_size_; + details::file_helper file_helper_; + + bool daily_rotate_; + int rotation_h_; + int rotation_m_; + log_clock::time_point rotation_tp_; +}; + +using rotating_file_sink_extended_mt = rotating_file_sink_extended; +using rotating_file_sink_extended_st = rotating_file_sink_extended; + +} // namespace sinks +template +inline std::shared_ptr rotating_logger_extended_mt( + const std::string &logger_name, const filename_t &filename, const filename_t &datetime_ext, size_t max_size, size_t max_files) +{ + return Factory::template create(logger_name, filename, datetime_ext, max_size, max_files); +} + +template +inline std::shared_ptr rotating_logger_extended_st( + const std::string &logger_name, const filename_t &filename, const filename_t &datetime_ext, size_t max_size, size_t max_files) +{ + return Factory::template create(logger_name, filename, datetime_ext, max_size, max_files); +} + +template +inline std::shared_ptr rotating_logger_extended_mt(const std::string &logger_name, const filename_t &filename, + const filename_t &datetime_ext, size_t max_size, size_t max_files, int rotation_hour, int rotation_minute) +{ + return Factory::template create( + logger_name, filename, datetime_ext, max_size, max_files, rotation_hour, rotation_minute); +} + +template +inline std::shared_ptr rotating_logger_extended_st(const std::string &logger_name, const filename_t &filename, + const filename_t &datetime_ext, size_t max_size, size_t max_files, int rotation_hour, int rotation_minute) +{ + return Factory::template create( + logger_name, filename, datetime_ext, max_size, max_files, rotation_hour, rotation_minute); +} +} // namespace spdlog \ No newline at end of file