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