diff --git a/src/logger/rotating_file_sink-inl.h b/src/logger/rotating_file_sink-inl.h index 6f10256f..82f4923f 100644 --- a/src/logger/rotating_file_sink-inl.h +++ b/src/logger/rotating_file_sink-inl.h @@ -4,7 +4,7 @@ #pragma once #ifndef SPDLOG_HEADER_ONLY - #include + #include #endif #include @@ -20,6 +20,11 @@ #include #include +#include +#include +#include +#include + namespace spdlog { namespace sinks { @@ -29,11 +34,13 @@ SPDLOG_INLINE rotating_file_sink::rotating_file_sink( std::size_t max_size, std::size_t max_files, bool rotate_on_open, + std::size_t compress_begin, const file_event_handlers &event_handlers) : base_filename_(std::move(base_filename)), max_size_(max_size), max_files_(max_files), - file_helper_{event_handlers} { + file_helper_{event_handlers}, + compress_begin_(compress_begin) { if (max_size == 0) { throw_spdlog_ex("rotating sink constructor: max_size arg cannot be zero"); } @@ -60,6 +67,8 @@ SPDLOG_INLINE filename_t rotating_file_sink::calc_filename(const filename filename_t basename, ext; std::tie(basename, ext) = details::file_helper::split_by_extension(filename); + if (compress_begin_ > 0 && index >= compress_begin_) + ext += ".gz"; return fmt_lib::format(SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); } @@ -110,14 +119,17 @@ SPDLOG_INLINE void rotating_file_sink::rotate_() { if (!path_exists(src)) { continue; } + filename_t target = calc_filename(base_filename_, i); - if (!rename_file_(src, target)) { + bool compress = i == compress_begin_; + + if (!rename_file_(src, target, compress)) { // 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)) { + if (!rename_file_(src, target, compress)) { file_helper_.reopen( true); // truncate the log file anyway to prevent it to grow beyond its limit! current_size_ = 0; @@ -134,11 +146,45 @@ SPDLOG_INLINE void rotating_file_sink::rotate_() { // return true on success, false otherwise. template SPDLOG_INLINE bool rotating_file_sink::rename_file_(const filename_t &src_filename, - const filename_t &target_filename) { + const filename_t &target_filename, bool compress) { // try to delete the target file in case it already exists. (void)details::os::remove(target_filename); + if (compress) + compress_file_(src_filename, target_filename); + return details::os::rename(src_filename, target_filename) == 0; } +template +SPDLOG_INLINE bool rotating_file_sink::compress_file_(const filename_t &src_filename, + const filename_t &target_filename) { + auto ifd = open(src_filename.c_str(), O_RDONLY); + if (!ifd) + return false; + struct stat istat; + fstat(ifd, &istat); + auto ptr = mmap(nullptr, istat.st_size, PROT_READ, MAP_PRIVATE, ifd, 0); + if (ptr == MAP_FAILED) { + close(ifd); + return false; + } + bool failed = false; + auto fp = gzopen(target_filename.c_str(), "wb"); + if (fp) { + failed = gzwrite(fp, ptr, istat.st_size) == 0; + } else + failed = true; + munmap(ptr, istat.st_size); + close(ifd); + if (fp && gzclose(fp)) + failed = true; + + if (failed) { + (void)details::os::remove(target_filename); + return false; + } + return true; +} + } // namespace sinks } // namespace spdlog diff --git a/src/logger/rotating_file_sink.h b/src/logger/rotating_file_sink.h index cd43d349..776549d0 100644 --- a/src/logger/rotating_file_sink.h +++ b/src/logger/rotating_file_sink.h @@ -25,8 +25,9 @@ class rotating_file_sink final : public base_sink { std::size_t max_size, std::size_t max_files, bool rotate_on_open = false, + size_t compress_begin = 0, const file_event_handlers &event_handlers = {}); - static filename_t calc_filename(const filename_t &filename, std::size_t index); + filename_t calc_filename(const filename_t &filename, std::size_t index); filename_t filename(); protected: @@ -41,15 +42,20 @@ class rotating_file_sink final : public base_sink { // log.3.txt -> delete void rotate_(); - // delete the target if exists, and rename the src file to target + // delete the target if exists, and rename (with possible compression) the src file to target // return true on success, false otherwise. - bool rename_file_(const filename_t &src_filename, const filename_t &target_filename); + bool rename_file_(const filename_t &src_filename, const filename_t &target_filename, bool compress); + + // Compress file + // return true on success, false otherwise. + bool compress_file_(const filename_t &src_filename, const filename_t &target_filename); filename_t base_filename_; std::size_t max_size_; std::size_t max_files_; std::size_t current_size_; details::file_helper file_helper_; + std::size_t compress_begin_ = 0; }; using rotating_file_sink_mt = rotating_file_sink; @@ -85,5 +91,5 @@ inline std::shared_ptr rotating_logger_st(const std::string &logger_name } // namespace spdlog #ifdef SPDLOG_HEADER_ONLY - #include "rotating_file_sink-inl.h" + #include "logger/rotating_file_sink-inl.h" #endif diff --git a/src/logger/spdlog.cc b/src/logger/spdlog.cc index 96f9e538..e08f4b46 100644 --- a/src/logger/spdlog.cc +++ b/src/logger/spdlog.cc @@ -32,11 +32,13 @@ #include #include #include -#include #ifndef _WIN32 #include #endif +#include +#include + namespace { spdlog::level::level_enum tll2spdlog(tll_logger_level_t l) { @@ -286,14 +288,10 @@ struct spdlog_impl_t : public tll_logger_impl_t auto max_size = reader.getT("max-size", 64 * 1024 * 1024); auto max_files = reader.getT("max-files", 5u); auto rotate_on_open = reader.getT("rotate-on-open", false); + auto compress_begin = reader.getT("compress-begin", 0u); if (!reader) return log.fail(EINVAL, "Invalid parameters for sink {}: {}", *type, reader.error()); -#if SPDLOG_VERSION < 10500 - sink.sink.reset(new spdlog::sinks::rotating_file_sink_mt(filename, max_size, max_files)); - (void) rotate_on_open; -#else - sink.sink.reset(new spdlog::sinks::rotating_file_sink_mt(filename, max_size, max_files, rotate_on_open)); -#endif + sink.sink.reset(new spdlog::sinks::rotating_file_sink_mt(filename, max_size, max_files, rotate_on_open, compress_begin)); #ifndef _WIN32 } else if (*type == "syslog") { auto ident = reader.getT("ident", "");