daily_file_sink.h 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. // Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
  2. // Distributed under the MIT License (http://opensource.org/licenses/MIT)
  3. #pragma once
  4. #include <spdlog/common.h>
  5. #include <spdlog/details/file_helper.h>
  6. #include <spdlog/details/null_mutex.h>
  7. #include <spdlog/fmt/fmt.h>
  8. #include <spdlog/fmt/chrono.h>
  9. #include <spdlog/sinks/base_sink.h>
  10. #include <spdlog/details/os.h>
  11. #include <spdlog/details/circular_q.h>
  12. #include <spdlog/details/synchronous_factory.h>
  13. #include <chrono>
  14. #include <cstdio>
  15. #include <ctime>
  16. #include <mutex>
  17. #include <string>
  18. namespace spdlog {
  19. namespace sinks {
  20. /*
  21. * Generator of daily log file names in format basename.YYYY-MM-DD.ext
  22. */
  23. struct daily_filename_calculator
  24. {
  25. // Create filename for the form basename.YYYY-MM-DD
  26. static filename_t calc_filename(const filename_t &filename, const tm &now_tm)
  27. {
  28. filename_t basename, ext;
  29. std::tie(basename, ext) = details::file_helper::split_by_extension(filename);
  30. return fmt_lib::format(
  31. SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, ext);
  32. }
  33. };
  34. /*
  35. * Generator of daily log file names with strftime format.
  36. * Usages:
  37. * auto sink = std::make_shared<spdlog::sinks::daily_file_format_sink_mt>("myapp-%Y-%m-%d:%H:%M:%S.log", hour, minute);"
  38. * auto logger = spdlog::daily_logger_format_mt("loggername, "myapp-%Y-%m-%d:%X.log", hour, minute)"
  39. *
  40. */
  41. struct daily_filename_format_calculator
  42. {
  43. static filename_t calc_filename(const filename_t &filename, const tm &now_tm)
  44. {
  45. #ifdef SPDLOG_USE_STD_FORMAT
  46. // adapted from fmtlib: https://github.com/fmtlib/fmt/blob/8.0.1/include/fmt/chrono.h#L522-L546
  47. filename_t tm_format;
  48. tm_format.append(filename);
  49. // By appending an extra space we can distinguish an empty result that
  50. // indicates insufficient buffer size from a guaranteed non-empty result
  51. // https://github.com/fmtlib/fmt/issues/2238
  52. tm_format.push_back(' ');
  53. const size_t MIN_SIZE = 10;
  54. filename_t buf;
  55. buf.resize(MIN_SIZE);
  56. for (;;)
  57. {
  58. size_t count = strftime(buf.data(), buf.size(), tm_format.c_str(), &now_tm);
  59. if (count != 0)
  60. {
  61. // Remove the extra space.
  62. buf.resize(count - 1);
  63. break;
  64. }
  65. buf.resize(buf.size() * 2);
  66. }
  67. return buf;
  68. #else
  69. // generate fmt datetime format string, e.g. {:%Y-%m-%d}.
  70. filename_t fmt_filename = fmt::format(SPDLOG_FILENAME_T("{{:{}}}"), filename);
  71. # if defined(_MSC_VER) && defined(SPDLOG_WCHAR_FILENAMES) // for some reason msvc doesn't allow fmt::runtime(..) with wchar here
  72. return fmt::format(fmt_filename, now_tm);
  73. # else
  74. return fmt::format(SPDLOG_FMT_RUNTIME(fmt_filename), now_tm);
  75. # endif
  76. #endif
  77. }
  78. private:
  79. #if defined __GNUC__
  80. # pragma GCC diagnostic push
  81. # pragma GCC diagnostic ignored "-Wformat-nonliteral"
  82. #endif
  83. static size_t strftime(char *str, size_t count, const char *format, const std::tm *time)
  84. {
  85. return std::strftime(str, count, format, time);
  86. }
  87. static size_t strftime(wchar_t *str, size_t count, const wchar_t *format, const std::tm *time)
  88. {
  89. return std::wcsftime(str, count, format, time);
  90. }
  91. #if defined(__GNUC__)
  92. # pragma GCC diagnostic pop
  93. #endif
  94. };
  95. /*
  96. * Rotating file sink based on date.
  97. * If truncate != false , the created file will be truncated.
  98. * If max_files > 0, retain only the last max_files and delete previous.
  99. */
  100. template<typename Mutex, typename FileNameCalc = daily_filename_calculator>
  101. class daily_file_sink final : public base_sink<Mutex>
  102. {
  103. public:
  104. // create daily file sink which rotates on given time
  105. daily_file_sink(filename_t base_filename, int rotation_hour, int rotation_minute, bool truncate = false, uint16_t max_files = 0,
  106. const file_event_handlers &event_handlers = {})
  107. : base_filename_(std::move(base_filename))
  108. , rotation_h_(rotation_hour)
  109. , rotation_m_(rotation_minute)
  110. , file_helper_{event_handlers}
  111. , truncate_(truncate)
  112. , max_files_(max_files)
  113. , filenames_q_()
  114. {
  115. if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59)
  116. {
  117. throw_spdlog_ex("daily_file_sink: Invalid rotation time in ctor");
  118. }
  119. auto now = log_clock::now();
  120. auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(now));
  121. file_helper_.open(filename, truncate_);
  122. rotation_tp_ = next_rotation_tp_();
  123. if (max_files_ > 0)
  124. {
  125. init_filenames_q_();
  126. }
  127. }
  128. filename_t filename()
  129. {
  130. std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
  131. return file_helper_.filename();
  132. }
  133. protected:
  134. void sink_it_(const details::log_msg &msg) override
  135. {
  136. auto time = msg.time;
  137. bool should_rotate = time >= rotation_tp_;
  138. if (should_rotate)
  139. {
  140. auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(time));
  141. file_helper_.open(filename, truncate_);
  142. rotation_tp_ = next_rotation_tp_();
  143. }
  144. memory_buf_t formatted;
  145. base_sink<Mutex>::formatter_->format(msg, formatted);
  146. file_helper_.write(formatted);
  147. // Do the cleaning only at the end because it might throw on failure.
  148. if (should_rotate && max_files_ > 0)
  149. {
  150. delete_old_();
  151. }
  152. }
  153. void flush_() override
  154. {
  155. file_helper_.flush();
  156. }
  157. private:
  158. void init_filenames_q_()
  159. {
  160. using details::os::path_exists;
  161. filenames_q_ = details::circular_q<filename_t>(static_cast<size_t>(max_files_));
  162. std::vector<filename_t> filenames;
  163. auto now = log_clock::now();
  164. while (filenames.size() < max_files_)
  165. {
  166. auto filename = FileNameCalc::calc_filename(base_filename_, now_tm(now));
  167. if (!path_exists(filename))
  168. {
  169. break;
  170. }
  171. filenames.emplace_back(filename);
  172. now -= std::chrono::hours(24);
  173. }
  174. for (auto iter = filenames.rbegin(); iter != filenames.rend(); ++iter)
  175. {
  176. filenames_q_.push_back(std::move(*iter));
  177. }
  178. }
  179. tm now_tm(log_clock::time_point tp)
  180. {
  181. time_t tnow = log_clock::to_time_t(tp);
  182. return spdlog::details::os::localtime(tnow);
  183. }
  184. log_clock::time_point next_rotation_tp_()
  185. {
  186. auto now = log_clock::now();
  187. tm date = now_tm(now);
  188. date.tm_hour = rotation_h_;
  189. date.tm_min = rotation_m_;
  190. date.tm_sec = 0;
  191. auto rotation_time = log_clock::from_time_t(std::mktime(&date));
  192. if (rotation_time > now)
  193. {
  194. return rotation_time;
  195. }
  196. return {rotation_time + std::chrono::hours(24)};
  197. }
  198. // Delete the file N rotations ago.
  199. // Throw spdlog_ex on failure to delete the old file.
  200. void delete_old_()
  201. {
  202. using details::os::filename_to_str;
  203. using details::os::remove_if_exists;
  204. filename_t current_file = file_helper_.filename();
  205. if (filenames_q_.full())
  206. {
  207. auto old_filename = std::move(filenames_q_.front());
  208. filenames_q_.pop_front();
  209. bool ok = remove_if_exists(old_filename) == 0;
  210. if (!ok)
  211. {
  212. filenames_q_.push_back(std::move(current_file));
  213. throw_spdlog_ex("Failed removing daily file " + filename_to_str(old_filename), errno);
  214. }
  215. }
  216. filenames_q_.push_back(std::move(current_file));
  217. }
  218. filename_t base_filename_;
  219. int rotation_h_;
  220. int rotation_m_;
  221. log_clock::time_point rotation_tp_;
  222. details::file_helper file_helper_;
  223. bool truncate_;
  224. uint16_t max_files_;
  225. details::circular_q<filename_t> filenames_q_;
  226. };
  227. using daily_file_sink_mt = daily_file_sink<std::mutex>;
  228. using daily_file_sink_st = daily_file_sink<details::null_mutex>;
  229. using daily_file_format_sink_mt = daily_file_sink<std::mutex, daily_filename_format_calculator>;
  230. using daily_file_format_sink_st = daily_file_sink<details::null_mutex, daily_filename_format_calculator>;
  231. } // namespace sinks
  232. //
  233. // factory functions
  234. //
  235. template<typename Factory = spdlog::synchronous_factory>
  236. inline std::shared_ptr<logger> daily_logger_mt(const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0,
  237. bool truncate = false, uint16_t max_files = 0, const file_event_handlers &event_handlers = {})
  238. {
  239. return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate, max_files, event_handlers);
  240. }
  241. template<typename Factory = spdlog::synchronous_factory>
  242. inline std::shared_ptr<logger> daily_logger_format_mt(const std::string &logger_name, const filename_t &filename, int hour = 0,
  243. int minute = 0, bool truncate = false, uint16_t max_files = 0, const file_event_handlers &event_handlers = {})
  244. {
  245. return Factory::template create<sinks::daily_file_format_sink_mt>(
  246. logger_name, filename, hour, minute, truncate, max_files, event_handlers);
  247. }
  248. template<typename Factory = spdlog::synchronous_factory>
  249. inline std::shared_ptr<logger> daily_logger_st(const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0,
  250. bool truncate = false, uint16_t max_files = 0, const file_event_handlers &event_handlers = {})
  251. {
  252. return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate, max_files, event_handlers);
  253. }
  254. template<typename Factory = spdlog::synchronous_factory>
  255. inline std::shared_ptr<logger> daily_logger_format_st(const std::string &logger_name, const filename_t &filename, int hour = 0,
  256. int minute = 0, bool truncate = false, uint16_t max_files = 0, const file_event_handlers &event_handlers = {})
  257. {
  258. return Factory::template create<sinks::daily_file_format_sink_st>(
  259. logger_name, filename, hour, minute, truncate, max_files, event_handlers);
  260. }
  261. } // namespace spdlog