| 1 | // Copyright(c) 2015-present, Gabi Melman & spdlog contributors. |
| 2 | // Distributed under the MIT License (http://opensource.org/licenses/MIT) |
| 3 | |
| 4 | #pragma once |
| 5 | |
| 6 | #ifndef SPDLOG_HEADER_ONLY |
| 7 | #include <spdlog/sinks/rotating_file_sink.h> |
| 8 | #endif |
| 9 | |
| 10 | #include <spdlog/common.h> |
| 11 | |
| 12 | #include <spdlog/details/file_helper.h> |
| 13 | #include <spdlog/details/null_mutex.h> |
| 14 | #include <spdlog/fmt/fmt.h> |
| 15 | |
| 16 | #include <cerrno> |
| 17 | #include <chrono> |
| 18 | #include <ctime> |
| 19 | #include <mutex> |
| 20 | #include <string> |
| 21 | #include <tuple> |
| 22 | |
| 23 | namespace spdlog { |
| 24 | namespace sinks { |
| 25 | |
| 26 | template <typename Mutex> |
| 27 | SPDLOG_INLINE rotating_file_sink<Mutex>::rotating_file_sink( |
| 28 | filename_t base_filename, |
| 29 | std::size_t max_size, |
| 30 | std::size_t max_files, |
| 31 | bool rotate_on_open, |
| 32 | const file_event_handlers &event_handlers) |
| 33 | : base_filename_(std::move(base_filename)), |
| 34 | max_size_(max_size), |
| 35 | max_files_(max_files), |
| 36 | file_helper_{event_handlers} { |
| 37 | if (max_size == 0) { |
| 38 | throw_spdlog_ex(msg: "rotating sink constructor: max_size arg cannot be zero" ); |
| 39 | } |
| 40 | |
| 41 | if (max_files > 200000) { |
| 42 | throw_spdlog_ex(msg: "rotating sink constructor: max_files arg cannot exceed 200000" ); |
| 43 | } |
| 44 | file_helper_.open(fname: calc_filename(filename: base_filename_, index: 0)); |
| 45 | current_size_ = file_helper_.size(); // expensive. called only once |
| 46 | if (rotate_on_open && current_size_ > 0) { |
| 47 | rotate_(); |
| 48 | current_size_ = 0; |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | // calc filename according to index and file extension if exists. |
| 53 | // e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". |
| 54 | template <typename Mutex> |
| 55 | SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::calc_filename(const filename_t &filename, |
| 56 | std::size_t index) { |
| 57 | if (index == 0u) { |
| 58 | return filename; |
| 59 | } |
| 60 | |
| 61 | filename_t basename, ext; |
| 62 | std::tie(args&: basename, args&: ext) = details::file_helper::split_by_extension(fname: filename); |
| 63 | return fmt_lib::format(SPDLOG_FILENAME_T("{}.{}{}" ), args&: basename, args&: index, args&: ext); |
| 64 | } |
| 65 | |
| 66 | template <typename Mutex> |
| 67 | SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::filename() { |
| 68 | std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); |
| 69 | return file_helper_.filename(); |
| 70 | } |
| 71 | |
| 72 | template <typename Mutex> |
| 73 | SPDLOG_INLINE void rotating_file_sink<Mutex>::sink_it_(const details::log_msg &msg) { |
| 74 | memory_buf_t formatted; |
| 75 | base_sink<Mutex>::formatter_->format(msg, formatted); |
| 76 | auto new_size = current_size_ + formatted.size(); |
| 77 | |
| 78 | // rotate if the new estimated file size exceeds max size. |
| 79 | // rotate only if the real size > 0 to better deal with full disk (see issue #2261). |
| 80 | // we only check the real size when new_size > max_size_ because it is relatively expensive. |
| 81 | if (new_size > max_size_) { |
| 82 | file_helper_.flush(); |
| 83 | if (file_helper_.size() > 0) { |
| 84 | rotate_(); |
| 85 | new_size = formatted.size(); |
| 86 | } |
| 87 | } |
| 88 | file_helper_.write(buf: formatted); |
| 89 | current_size_ = new_size; |
| 90 | } |
| 91 | |
| 92 | template <typename Mutex> |
| 93 | SPDLOG_INLINE void rotating_file_sink<Mutex>::flush_() { |
| 94 | file_helper_.flush(); |
| 95 | } |
| 96 | |
| 97 | // Rotate files: |
| 98 | // log.txt -> log.1.txt |
| 99 | // log.1.txt -> log.2.txt |
| 100 | // log.2.txt -> log.3.txt |
| 101 | // log.3.txt -> delete |
| 102 | template <typename Mutex> |
| 103 | SPDLOG_INLINE void rotating_file_sink<Mutex>::rotate_() { |
| 104 | using details::os::filename_to_str; |
| 105 | using details::os::path_exists; |
| 106 | |
| 107 | file_helper_.close(); |
| 108 | for (auto i = max_files_; i > 0; --i) { |
| 109 | filename_t src = calc_filename(filename: base_filename_, index: i - 1); |
| 110 | if (!path_exists(src)) { |
| 111 | continue; |
| 112 | } |
| 113 | filename_t target = calc_filename(filename: base_filename_, index: i); |
| 114 | |
| 115 | if (!rename_file_(src_filename: src, target_filename: target)) { |
| 116 | // if failed try again after a small delay. |
| 117 | // this is a workaround to a windows issue, where very high rotation |
| 118 | // rates can cause the rename to fail with permission denied (because of antivirus?). |
| 119 | details::os::sleep_for_millis(milliseconds: 100); |
| 120 | if (!rename_file_(src_filename: src, target_filename: target)) { |
| 121 | file_helper_.reopen( |
| 122 | truncate: true); // truncate the log file anyway to prevent it to grow beyond its limit! |
| 123 | current_size_ = 0; |
| 124 | throw_spdlog_ex("rotating_file_sink: failed renaming " + filename_to_str(src) + |
| 125 | " to " + filename_to_str(target), |
| 126 | errno); |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | file_helper_.reopen(truncate: true); |
| 131 | } |
| 132 | |
| 133 | // delete the target if exists, and rename the src file to target |
| 134 | // return true on success, false otherwise. |
| 135 | template <typename Mutex> |
| 136 | SPDLOG_INLINE bool rotating_file_sink<Mutex>::rename_file_(const filename_t &src_filename, |
| 137 | const filename_t &target_filename) { |
| 138 | // try to delete the target file in case it already exists. |
| 139 | (void)details::os::remove(filename: target_filename); |
| 140 | return details::os::rename(filename1: src_filename, filename2: target_filename) == 0; |
| 141 | } |
| 142 | |
| 143 | } // namespace sinks |
| 144 | } // namespace spdlog |
| 145 | |