1 //
2 // Copyright(c) 2015 Gabi Melman.
3 // Distributed under the MIT License (http://opensource.org/licenses/MIT)
4 //
5 
6 #pragma once
7 #include "spdlog/details/file_helper.h"
8 #include "spdlog/details/null_mutex.h"
9 #include "spdlog/fmt/fmt.h"
10 #include "spdlog/sinks/base_sink.h"
11 #include "spdlog/spdlog.h"
12 
13 #include <cerrno>
14 #include <chrono>
15 #include <ctime>
16 #include <mutex>
17 #include <string>
18 #include <tuple>
19 
20 namespace spdlog {
21 namespace sinks {
22 
23 //
24 // Rotating file sink based on size
25 //
26 template<typename Mutex>
27 class rotating_file_sink SPDLOG_FINAL : public base_sink<Mutex>
28 {
29 public:
rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files)30     rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files)
31         : base_filename_(std::move(base_filename))
32         , max_size_(max_size)
33         , max_files_(max_files)
34     {
35         file_helper_.open(calc_filename(base_filename_, 0));
36         current_size_ = file_helper_.size(); // expensive. called only once
37     }
38 
39     // calc filename according to index and file extension if exists.
40     // e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt".
calc_filename(const filename_t &filename, std::size_t index)41     static filename_t calc_filename(const filename_t &filename, std::size_t index)
42     {
43         typename std::conditional<std::is_same<filename_t::value_type, char>::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w;
44         if (index != 0u)
45         {
46             filename_t basename, ext;
47             std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename);
48             fmt::format_to(w, SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext);
49         }
50         else
51         {
52             fmt::format_to(w, SPDLOG_FILENAME_T("{}"), filename);
53         }
54         return fmt::to_string(w);
55     }
56 
57 protected:
58     void sink_it_(const details::log_msg &msg) override
59     {
60         fmt::memory_buffer formatted;
61         sink::formatter_->format(msg, formatted);
62         current_size_ += formatted.size();
63         if (current_size_ > max_size_)
64         {
65             rotate_();
66             current_size_ = formatted.size();
67         }
68         file_helper_.write(formatted);
69     }
70 
71     void flush_() override
72     {
73         file_helper_.flush();
74     }
75 
76 private:
77     // Rotate files:
78     // log.txt -> log.1.txt
79     // log.1.txt -> log.2.txt
80     // log.2.txt -> log.3.txt
81     // log.3.txt -> delete
rotate_()82     void rotate_()
83     {
84         using details::os::filename_to_str;
85         file_helper_.close();
86         for (auto i = max_files_; i > 0; --i)
87         {
88             filename_t src = calc_filename(base_filename_, i - 1);
89             filename_t target = calc_filename(base_filename_, i);
90 
91             if (details::file_helper::file_exists(target))
92             {
93                 if (details::os::remove(target) != 0)
94                 {
95                     throw spdlog_ex("rotating_file_sink: failed removing " + filename_to_str(target), errno);
96                 }
97             }
98             if (details::file_helper::file_exists(src) && details::os::rename(src, target) != 0)
99             {
100                 // if failed try again after small delay.
101                 // this is a workaround to a windows issue, where very high rotation
102                 // rates sometimes fail (because of antivirus?).
103                 details::os::sleep_for_millis(20);
104                 details::os::remove(target);
105                 if (details::os::rename(src, target) != 0)
106                 {
107                     throw spdlog_ex(
108                         "rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno);
109                 }
110             }
111         }
112         file_helper_.reopen(true);
113     }
114 
115     filename_t base_filename_;
116     std::size_t max_size_;
117     std::size_t max_files_;
118     std::size_t current_size_;
119     details::file_helper file_helper_;
120 };
121 
122 using rotating_file_sink_mt = rotating_file_sink<std::mutex>;
123 using rotating_file_sink_st = rotating_file_sink<details::null_mutex>;
124 
125 } // namespace sinks
126 
127 //
128 // factory functions
129 //
130 
131 template<typename Factory = default_factory>
rotating_logger_mt( const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files)132 inline std::shared_ptr<logger> rotating_logger_mt(
133     const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files)
134 {
135     return Factory::template create<sinks::rotating_file_sink_mt>(logger_name, filename, max_file_size, max_files);
136 }
137 
138 template<typename Factory = default_factory>
rotating_logger_st( const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files)139 inline std::shared_ptr<logger> rotating_logger_st(
140     const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files)
141 {
142     return Factory::template create<sinks::rotating_file_sink_st>(logger_name, filename, max_file_size, max_files);
143 }
144 } // namespace spdlog
145