diff --git a/include/spdlog/details/spdlog_impl.h b/include/spdlog/details/spdlog_impl.h index 4c363834..7d9195bc 100644 --- a/include/spdlog/details/spdlog_impl.h +++ b/include/spdlog/details/spdlog_impl.h @@ -83,6 +83,19 @@ inline std::shared_ptr spdlog::daily_logger_st( return create(logger_name, filename, hour, minute); } +// Create a file logger that creates new files with a specified increment +inline std::shared_ptr spdlog::step_logger_mt( + const std::string &logger_name, const filename_t &filename_fmt, unsigned seconds, size_t max_file_size) +{ + return create(logger_name, filename_fmt, seconds, max_file_size); +} + +inline std::shared_ptr spdlog::step_logger_st( + const std::string &logger_name, const filename_t &filename_fmt, unsigned seconds, size_t max_file_size) +{ + return create(logger_name, filename_fmt, seconds, max_file_size); +} + // // stdout/stderr loggers // diff --git a/include/spdlog/sinks/file_sinks.h b/include/spdlog/sinks/file_sinks.h index 109c493a..407c5b83 100644 --- a/include/spdlog/sinks/file_sinks.h +++ b/include/spdlog/sinks/file_sinks.h @@ -251,5 +251,97 @@ private: using daily_file_sink_mt = daily_file_sink; using daily_file_sink_st = daily_file_sink; +/* + * Default generator of step log file names. + */ +struct default_step_file_name_calculator +{ + // Create filename for the form filename_YYYY-MM-DD_hh-mm-ss.ext + static std::tuple calc_filename(const filename_t &filename) + { + std::tm tm = spdlog::details::os::localtime(); + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; + w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}_{:02d}-{:02d}-{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + return std::make_tuple(w.str(), ext); + } +}; + +/* + * Rotating file sink based on size and a specified time step + */ +template +class step_file_sink SPDLOG_FINAL : public base_sink +{ +public: + step_file_sink(filename_t base_filename, unsigned step_seconds, std::size_t max_size) + : _base_filename(std::move(base_filename)) + , _step_seconds(step_seconds) + , _max_size(max_size) + { + _tp = _next_tp(); + std::tie(_current_filename, _ext) = FileNameCalc::calc_filename(_base_filename); + _file_helper.open(_current_filename); + _current_size = _file_helper.size(); // expensive. called only once + } + +protected: + void _sink_it(const details::log_msg &msg) override + { + _current_size += msg.formatted.size(); + if (std::chrono::system_clock::now() >= _tp || _current_size > _max_size) + { + close_current_file(); + + std::tie(_current_filename, std::ignore) = FileNameCalc::calc_filename(_base_filename); + _file_helper.open(_current_filename); + _tp = _next_tp(); + _current_size = msg.formatted.size(); + } + _file_helper.write(msg); + } + + void _flush() override + { + _file_helper.flush(); + close_current_file(); + } + +private: + std::chrono::system_clock::time_point _next_tp() + { + return std::chrono::system_clock::now() + _step_seconds; + } + + void close_current_file() + { + using details::os::filename_to_str; + + filename_t src =_current_filename; + filename_t target = _current_filename + _ext; + + if (details::file_helper::file_exists(src) && details::os::rename(src, target) != 0) + { + throw spdlog_ex("step_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); + } + } + + filename_t _base_filename; + std::chrono::seconds _step_seconds; + std::size_t _max_size; + + std::chrono::system_clock::time_point _tp; + filename_t _current_filename; + filename_t _ext; + std::size_t _current_size; + + details::file_helper _file_helper; +}; + +using step_file_sink_mt = step_file_sink; +using step_file_sink_st = step_file_sink; + } // namespace sinks } // namespace spdlog diff --git a/include/spdlog/spdlog.h b/include/spdlog/spdlog.h index 21f5951b..f65fe57e 100644 --- a/include/spdlog/spdlog.h +++ b/include/spdlog/spdlog.h @@ -91,6 +91,12 @@ std::shared_ptr rotating_logger_st( std::shared_ptr daily_logger_mt(const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0); std::shared_ptr daily_logger_st(const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0); +// +// Create a file logger which creates new files with a specified time step and fixed file size: +// +std::shared_ptr step_logger_mt(const std::string &logger_name, const filename_t &filename, unsigned seconds = 60, size_t max_file_size = std::numeric_limits::max()); +std::shared_ptr step_logger_st(const std::string &logger_name, const filename_t &filename, unsigned seconds = 60, size_t max_file_size = std::numeric_limits::max()); + // // Create and register stdout/stderr loggers // diff --git a/tests/file_log.cpp b/tests/file_log.cpp index e20071a3..afcac255 100644 --- a/tests/file_log.cpp +++ b/tests/file_log.cpp @@ -179,6 +179,71 @@ TEST_CASE("daily_logger with custom calculator", "[daily_logger_custom]]") REQUIRE(count_lines(filename) == 10); } +TEST_CASE("step_logger", "[step_logger]]") +{ + prepare_logdir(); + // calculate filename (time based) + std::string basename = "logs/step_log"; + std::tm tm = spdlog::details::os::localtime(); + fmt::MemoryWriter w; + w.write("{}_{:04d}-{:02d}-{:02d}_{:02d}-{:02d}-{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + + auto logger = spdlog::step_logger_mt("logger", basename, 60); + logger->flush_on(spdlog::level::info); + for (int i = 0; i < 10; ++i) + { +#if !defined(SPDLOG_FMT_PRINTF) + logger->info("Test message {}", i); +#else + logger->info("Test message %d", i); +#endif + } + + auto filename = w.str(); + REQUIRE(count_lines(filename) == 10); +} + +struct custom_step_file_name_calculator +{ + static std::tuple calc_filename(const spdlog::filename_t &filename) + { + std::tm tm = spdlog::details::os::localtime(); + spdlog::filename_t basename, ext; + std::tie(basename, ext) = spdlog::details::file_helper::split_by_extenstion(filename); + std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; + w.write(SPDLOG_FILENAME_T("{}.{:04d}:{:02d}:{:02d}.{:02d}:{:02d}:{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + return std::make_tuple(w.str(), ext); + } +}; + +TEST_CASE("step_logger with custom calculator", "[step_logger_custom]]") +{ + using sink_type = spdlog::sinks::step_file_sink; + + prepare_logdir(); + + std::string basename = "logs/step_log_custom"; + std::tm tm = spdlog::details::os::localtime(); + fmt::MemoryWriter w; + w.write("{}.{:04d}:{:02d}:{:02d}.{:02d}:{:02d}:{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + + auto logger = spdlog::create("logger", basename, 60, std::numeric_limits::max()); + for (int i = 0; i < 10; ++i) + { +#if !defined(SPDLOG_FMT_PRINTF) + logger->info("Test message {}", i); +#else + logger->info("Test message %d", i); +#endif + } + + logger->flush(); + auto filename = w.str(); + REQUIRE(count_lines(filename) == 10); +} + /* * File name calculations */