1 // 2 // Copyright(c) 2015 Gabi Melman. 3 // Distributed under the MIT License (http://opensource.org/licenses/MIT) 4 // 5 6 #pragma once 7 8 #include <spdlog/sinks/base_sink.h> 9 #include <spdlog/details/null_mutex.h> 10 #include <spdlog/details/file_helper.h> 11 #include <spdlog/fmt/fmt.h> 12 13 #include <algorithm> 14 #include <chrono> 15 #include <cstdio> 16 #include <ctime> 17 #include <mutex> 18 #include <string> 19 #include <cerrno> 20 21 namespace spdlog 22 { 23 namespace sinks 24 { 25 /* 26 * Trivial file sink with single file as target 27 */ 28 template<class Mutex> 29 class simple_file_sink : public base_sink < Mutex > 30 { 31 public: _force_flush(false)32 explicit simple_file_sink(const filename_t &filename, bool truncate = false):_force_flush(false) 33 { 34 _file_helper.open(filename, truncate); 35 } flush()36 void flush() override 37 { 38 _file_helper.flush(); 39 } set_force_flush(bool force_flush)40 void set_force_flush(bool force_flush) 41 { 42 _force_flush = force_flush; 43 } 44 45 protected: _sink_it(const details::log_msg & msg)46 void _sink_it(const details::log_msg& msg) override 47 { 48 _file_helper.write(msg); 49 if(_force_flush) 50 _file_helper.flush(); 51 } 52 private: 53 details::file_helper _file_helper; 54 bool _force_flush; 55 }; 56 57 typedef simple_file_sink<std::mutex> simple_file_sink_mt; 58 typedef simple_file_sink<details::null_mutex> simple_file_sink_st; 59 60 /* 61 * Rotating file sink based on size 62 */ 63 template<class Mutex> 64 class rotating_file_sink : public base_sink < Mutex > 65 { 66 public: rotating_file_sink(const filename_t & base_filename,const filename_t & extension,std::size_t max_size,std::size_t max_files)67 rotating_file_sink(const filename_t &base_filename, const filename_t &extension, 68 std::size_t max_size, std::size_t max_files ) : 69 _base_filename(base_filename), 70 _extension(extension), 71 _max_size(max_size), 72 _max_files(max_files), 73 _current_size(0), 74 _file_helper() 75 { 76 _file_helper.open(calc_filename(_base_filename, 0, _extension)); 77 _current_size = _file_helper.size(); //expensive. called only once 78 } 79 flush()80 void flush() override 81 { 82 _file_helper.flush(); 83 } 84 85 protected: _sink_it(const details::log_msg & msg)86 void _sink_it(const details::log_msg& msg) override 87 { 88 _current_size += msg.formatted.size(); 89 if (_current_size > _max_size) 90 { 91 _rotate(); 92 _current_size = msg.formatted.size(); 93 } 94 _file_helper.write(msg); 95 } 96 97 private: calc_filename(const filename_t & filename,std::size_t index,const filename_t & extension)98 static filename_t calc_filename(const filename_t& filename, std::size_t index, const filename_t& extension) 99 { 100 std::conditional<std::is_same<filename_t::value_type, char>::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; 101 if (index) 102 w.write(SPDLOG_FILENAME_T("{}.{}.{}"), filename, index, extension); 103 else 104 w.write(SPDLOG_FILENAME_T("{}.{}"), filename, extension); 105 return w.str(); 106 } 107 108 // Rotate files: 109 // log.txt -> log.1.txt 110 // log.1.txt -> log2.txt 111 // log.2.txt -> log3.txt 112 // log.3.txt -> delete 113 _rotate()114 void _rotate() 115 { 116 using details::os::filename_to_str; 117 _file_helper.close(); 118 for (auto i = _max_files; i > 0; --i) 119 { 120 filename_t src = calc_filename(_base_filename, i - 1, _extension); 121 filename_t target = calc_filename(_base_filename, i, _extension); 122 123 if (details::file_helper::file_exists(target)) 124 { 125 if (details::os::remove(target) != 0) 126 { 127 throw spdlog_ex("rotating_file_sink: failed removing " + filename_to_str(target), errno); 128 } 129 } 130 if (details::file_helper::file_exists(src) && details::os::rename(src, target)) 131 { 132 throw spdlog_ex("rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); 133 } 134 } 135 _file_helper.reopen(true); 136 } 137 filename_t _base_filename; 138 filename_t _extension; 139 std::size_t _max_size; 140 std::size_t _max_files; 141 std::size_t _current_size; 142 details::file_helper _file_helper; 143 }; 144 145 typedef rotating_file_sink<std::mutex> rotating_file_sink_mt; 146 typedef rotating_file_sink<details::null_mutex>rotating_file_sink_st; 147 148 /* 149 * Default generator of daily log file names. 150 */ 151 struct default_daily_file_name_calculator 152 { 153 // Create filename for the form basename.YYYY-MM-DD_hh-mm.extension calc_filenamedefault_daily_file_name_calculator154 static filename_t calc_filename(const filename_t& basename, const filename_t& extension) 155 { 156 std::tm tm = spdlog::details::os::localtime(); 157 std::conditional<std::is_same<filename_t::value_type, char>::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; 158 w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}_{:02d}-{:02d}.{}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, extension); 159 return w.str(); 160 } 161 }; 162 163 /* 164 * Generator of daily log file names in format basename.YYYY-MM-DD.extension 165 */ 166 struct dateonly_daily_file_name_calculator 167 { 168 // Create filename for the form basename.YYYY-MM-DD.extension calc_filenamedateonly_daily_file_name_calculator169 static filename_t calc_filename(const filename_t& basename, const filename_t& extension) 170 { 171 std::tm tm = spdlog::details::os::localtime(); 172 std::conditional<std::is_same<filename_t::value_type, char>::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; 173 w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}.{}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, extension); 174 return w.str(); 175 } 176 }; 177 178 /* 179 * Rotating file sink based on date. rotates at midnight 180 */ 181 template<class Mutex, class FileNameCalc = default_daily_file_name_calculator> 182 class daily_file_sink :public base_sink < Mutex > 183 { 184 public: 185 //create daily file sink which rotates on given time daily_file_sink(const filename_t & base_filename,const filename_t & extension,int rotation_hour,int rotation_minute)186 daily_file_sink( 187 const filename_t& base_filename, 188 const filename_t& extension, 189 int rotation_hour, 190 int rotation_minute) : _base_filename(base_filename), 191 _extension(extension), 192 _rotation_h(rotation_hour), 193 _rotation_m(rotation_minute) 194 { 195 if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59) 196 throw spdlog_ex("daily_file_sink: Invalid rotation time in ctor"); 197 _rotation_tp = _next_rotation_tp(); 198 _file_helper.open(FileNameCalc::calc_filename(_base_filename, _extension)); 199 } 200 flush()201 void flush() override 202 { 203 _file_helper.flush(); 204 } 205 206 protected: _sink_it(const details::log_msg & msg)207 void _sink_it(const details::log_msg& msg) override 208 { 209 if (std::chrono::system_clock::now() >= _rotation_tp) 210 { 211 _file_helper.open(FileNameCalc::calc_filename(_base_filename, _extension)); 212 _rotation_tp = _next_rotation_tp(); 213 } 214 _file_helper.write(msg); 215 } 216 217 private: _next_rotation_tp()218 std::chrono::system_clock::time_point _next_rotation_tp() 219 { 220 auto now = std::chrono::system_clock::now(); 221 time_t tnow = std::chrono::system_clock::to_time_t(now); 222 tm date = spdlog::details::os::localtime(tnow); 223 date.tm_hour = _rotation_h; 224 date.tm_min = _rotation_m; 225 date.tm_sec = 0; 226 auto rotation_time = std::chrono::system_clock::from_time_t(std::mktime(&date)); 227 if (rotation_time > now) 228 return rotation_time; 229 else 230 return std::chrono::system_clock::time_point(rotation_time + std::chrono::hours(24)); 231 } 232 233 filename_t _base_filename; 234 filename_t _extension; 235 int _rotation_h; 236 int _rotation_m; 237 std::chrono::system_clock::time_point _rotation_tp; 238 details::file_helper _file_helper; 239 }; 240 241 typedef daily_file_sink<std::mutex> daily_file_sink_mt; 242 typedef daily_file_sink<details::null_mutex> daily_file_sink_st; 243 } 244 } 245