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, std::size_t max_size, std::size_t max_files, bool rotate_on_open, const file_event_handlers &event_handlers) |
29 | : base_filename_(std::move(base_filename)) |
30 | , max_size_(max_size) |
31 | , max_files_(max_files) |
32 | , file_helper_{event_handlers} |
33 | { |
34 | if (max_size == 0) |
35 | { |
36 | throw_spdlog_ex("rotating sink constructor: max_size arg cannot be zero" ); |
37 | } |
38 | |
39 | if (max_files > 200000) |
40 | { |
41 | throw_spdlog_ex("rotating sink constructor: max_files arg cannot exceed 200000" ); |
42 | } |
43 | file_helper_.open(calc_filename(base_filename_, 0)); |
44 | current_size_ = file_helper_.size(); // expensive. called only once |
45 | if (rotate_on_open && current_size_ > 0) |
46 | { |
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, std::size_t index) |
56 | { |
57 | if (index == 0u) |
58 | { |
59 | return filename; |
60 | } |
61 | |
62 | filename_t basename, ext; |
63 | std::tie(basename, ext) = details::file_helper::split_by_extension(filename); |
64 | return fmt_lib::format(SPDLOG_FILENAME_T("{}.{}{}" ), basename, index, ext); |
65 | } |
66 | |
67 | template<typename Mutex> |
68 | SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::filename() |
69 | { |
70 | std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); |
71 | return file_helper_.filename(); |
72 | } |
73 | |
74 | template<typename Mutex> |
75 | SPDLOG_INLINE void rotating_file_sink<Mutex>::sink_it_(const details::log_msg &msg) |
76 | { |
77 | memory_buf_t formatted; |
78 | base_sink<Mutex>::formatter_->format(msg, formatted); |
79 | auto new_size = current_size_ + formatted.size(); |
80 | |
81 | // rotate if the new estimated file size exceeds max size. |
82 | // rotate only if the real size > 0 to better deal with full disk (see issue #2261). |
83 | // we only check the real size when new_size > max_size_ because it is relatively expensive. |
84 | if (new_size > max_size_) |
85 | { |
86 | file_helper_.flush(); |
87 | if (file_helper_.size() > 0) |
88 | { |
89 | rotate_(); |
90 | new_size = formatted.size(); |
91 | } |
92 | } |
93 | file_helper_.write(formatted); |
94 | current_size_ = new_size; |
95 | } |
96 | |
97 | template<typename Mutex> |
98 | SPDLOG_INLINE void rotating_file_sink<Mutex>::flush_() |
99 | { |
100 | file_helper_.flush(); |
101 | } |
102 | |
103 | // Rotate files: |
104 | // log.txt -> log.1.txt |
105 | // log.1.txt -> log.2.txt |
106 | // log.2.txt -> log.3.txt |
107 | // log.3.txt -> delete |
108 | template<typename Mutex> |
109 | SPDLOG_INLINE void rotating_file_sink<Mutex>::rotate_() |
110 | { |
111 | using details::os::filename_to_str; |
112 | using details::os::path_exists; |
113 | |
114 | file_helper_.close(); |
115 | for (auto i = max_files_; i > 0; --i) |
116 | { |
117 | filename_t src = calc_filename(base_filename_, i - 1); |
118 | if (!path_exists(src)) |
119 | { |
120 | continue; |
121 | } |
122 | filename_t target = calc_filename(base_filename_, i); |
123 | |
124 | if (!rename_file_(src, target)) |
125 | { |
126 | // if failed try again after a small delay. |
127 | // this is a workaround to a windows issue, where very high rotation |
128 | // rates can cause the rename to fail with permission denied (because of antivirus?). |
129 | details::os::sleep_for_millis(100); |
130 | if (!rename_file_(src, target)) |
131 | { |
132 | file_helper_.reopen(true); // truncate the log file anyway to prevent it to grow beyond its limit! |
133 | current_size_ = 0; |
134 | throw_spdlog_ex("rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); |
135 | } |
136 | } |
137 | } |
138 | file_helper_.reopen(true); |
139 | } |
140 | |
141 | // delete the target if exists, and rename the src file to target |
142 | // return true on success, false otherwise. |
143 | template<typename Mutex> |
144 | SPDLOG_INLINE bool rotating_file_sink<Mutex>::rename_file_(const filename_t &src_filename, const filename_t &target_filename) |
145 | { |
146 | // try to delete the target file in case it already exists. |
147 | (void)details::os::remove(target_filename); |
148 | return details::os::rename(src_filename, target_filename) == 0; |
149 | } |
150 | |
151 | } // namespace sinks |
152 | } // namespace spdlog |
153 | |