bitz-server  2.0.1
step_file_sink.h
1 #pragma once
2 
3 #include "../../details/file_helper.h"
4 #include "../../details/null_mutex.h"
5 #include "../../fmt/fmt.h"
6 #include "../../sinks/base_sink.h"
7 
8 #include <algorithm>
9 #include <cerrno>
10 #include <chrono>
11 #include <cstdio>
12 #include <ctime>
13 #include <mutex>
14 #include <string>
15 
16 // Example for spdlog.h
17 //
18 // Create a file logger which creates new files with a specified time step and fixed file size:
19 //
20 // std::shared_ptr<logger> step_logger_mt(const std::string &logger_name, const filename_t &filename, unsigned seconds = 60, const
21 // filename_t &tmp_ext = ".tmp", unsigned max_file_size = std::numeric_limits<unsigned>::max(), bool delete_empty_files = true, const
22 // filename_t &file_header = ""); std::shared_ptr<logger> step_logger_st(const std::string &logger_name, const filename_t &filename,
23 // unsigned seconds = 60, const filename_t &tmp_ext = ".tmp", unsigned max_file_size = std::numeric_limits<unsigned>::max());
24 
25 // Example for spdlog_impl.h
26 // Create a file logger that creates new files with a specified increment
27 // inline std::shared_ptr<spdlog::logger> spdlog::step_logger_mt(
28 // const std::string &logger_name, const filename_t &filename_fmt, unsigned seconds, const filename_t &tmp_ext, unsigned max_file_size,
29 // bool delete_empty_files, const filename_t &file_header)
30 // {
31 // return create<spdlog::sinks::step_file_sink_mt>(logger_name, filename_fmt, seconds, tmp_ext, max_file_size, delete_empty_files,
32 // file_header);
33 // }
34 
35 // inline std::shared_ptr<spdlog::logger> spdlog::step_logger_st(
36 // const std::string &logger_name, const filename_t &filename_fmt, unsigned seconds, const filename_t &tmp_ext, unsigned max_file_size,
37 // bool delete_empty_files, const filename_t &file_header)
38 // {
39 // return create<spdlog::sinks::step_file_sink_st>(logger_name, filename_fmt, seconds, tmp_ext, max_file_size, delete_empty_files,
40 // file_header);
41 // }
42 
43 namespace spdlog {
44 namespace sinks {
45 
46 /*
47  * Default generator of step log file names.
48  */
50 {
51  // Create filename for the form filename_YYYY-MM-DD_hh-mm-ss.ext
52  static std::tuple<filename_t, filename_t> calc_filename(const filename_t &filename, const filename_t &tmp_ext)
53  {
54  std::tm tm = spdlog::details::os::localtime();
55  filename_t basename, ext;
56  std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename);
57  std::conditional<std::is_same<filename_t::value_type, char>::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w;
58  w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}_{:02d}-{:02d}-{:02d}{}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
59  tm.tm_hour, tm.tm_min, tm.tm_sec, tmp_ext);
60  return std::make_tuple(w.str(), ext);
61  }
62 };
63 
64 /*
65  * Rotating file sink based on size and a specified time step
66  */
67 template<class Mutex, class FileNameCalc = default_step_file_name_calculator>
68 class step_file_sink SPDLOG_FINAL : public base_sink<Mutex>
69 {
70 public:
71  step_file_sink(filename_t base_filename, unsigned step_seconds, filename_t tmp_ext, unsigned max_size, bool delete_empty_files,
72  filename_t file_header)
73  : _base_filename(std::move(base_filename))
74  , _tmp_ext(std::move(tmp_ext))
75  , _step_seconds(step_seconds)
76  , _max_size(max_size)
77  , _delete_empty_files(delete_empty_files)
78  {
79  if (step_seconds == 0)
80  {
81  throw spdlog_ex("step_file_sink: Invalid time step in ctor");
82  }
83 
84  if (!file_header.empty())
85  {
86  pattern_formatter formatter_for_file_header("%v");
87  _file_header.raw << file_header;
88  formatter_for_file_header.format(_file_header);
89  }
90 
91  if (max_size <= _file_header.formatted.size())
92  {
93  throw spdlog_ex("step_file_sink: Invalid max log size in ctor");
94  }
95 
96  _tp = _next_tp();
97  std::tie(_current_filename, _ext) = FileNameCalc::calc_filename(_base_filename, _tmp_ext);
98 
99  if (_tmp_ext == _ext)
100  {
101  throw spdlog_ex("step_file_sink: The temporary extension matches the specified in ctor");
102  }
103 
104  _file_helper.open(_current_filename);
105  _current_size = _file_helper.size(); // expensive. called only once
106 
107  if (!_current_size)
108  {
109  _current_size += _file_header.formatted.size();
110  if (_current_size)
111  _file_helper.write(_file_header);
112  }
113  }
114 
115  ~step_file_sink()
116  {
117  try
118  {
119  close_current_file();
120  }
121  catch (...)
122  {
123  }
124  }
125 
126 protected:
127  void _sink_it(const details::log_msg &msg) override
128  {
129  auto msg_size = msg.formatted.size();
130 
131  if (std::chrono::system_clock::now() >= _tp || _current_size + msg_size > _max_size)
132  {
133  filename_t new_filename;
134  std::tie(new_filename, std::ignore) = FileNameCalc::calc_filename(_base_filename, _tmp_ext);
135 
136  bool change_occured = !details::file_helper::file_exists(new_filename);
137  if (change_occured)
138  {
139  close_current_file();
140 
141  _current_filename = std::move(new_filename);
142 
143  _file_helper.open(_current_filename);
144  }
145 
146  _tp = _next_tp();
147 
148  if (change_occured)
149  {
150  _current_size = _file_header.formatted.size();
151  if (_current_size)
152  _file_helper.write(_file_header);
153  }
154  }
155 
156  _current_size += msg_size;
157  _file_helper.write(msg);
158  }
159 
160  void _flush() override
161  {
162  _file_helper.flush();
163  }
164 
165 private:
166  std::chrono::system_clock::time_point _next_tp()
167  {
168  return std::chrono::system_clock::now() + _step_seconds;
169  }
170 
171  void close_current_file()
172  {
173  using details::os::filename_to_str;
174 
175  // Delete empty files, if required
176  if (_delete_empty_files && _current_size <= _file_header.formatted.size())
177  {
178  if (details::os::remove(_current_filename) != 0)
179  {
180  throw spdlog_ex("step_file_sink: not remove " + filename_to_str(_current_filename), errno);
181  }
182 
183  return;
184  }
185 
186  filename_t target;
187  std::tie(target, std::ignore) = details::file_helper::split_by_extenstion(_current_filename);
188  target += _ext;
189 
190  if (details::file_helper::file_exists(_current_filename) && details::os::rename(_current_filename, target) != 0)
191  {
192  throw spdlog_ex(
193  "step_file_sink: failed renaming " + filename_to_str(_current_filename) + " to " + filename_to_str(target), errno);
194  }
195  }
196 
197  const filename_t _base_filename;
198  const filename_t _tmp_ext;
199  const std::chrono::seconds _step_seconds;
200  const unsigned _max_size;
201  const bool _delete_empty_files;
202 
203  std::chrono::system_clock::time_point _tp;
204  filename_t _current_filename;
205  filename_t _ext;
206  unsigned _current_size;
207 
208  details::file_helper _file_helper;
209  details::log_msg _file_header;
210 };
211 
212 using step_file_sink_mt = step_file_sink<std::mutex>;
213 using step_file_sink_st = step_file_sink<details::null_mutex>;
214 
215 } // namespace sinks
216 } // namespace spdlog
Definition: lib/spdlog/common.h:146
Definition: file_helper.h:25
Definition: async_logger.h:26
std::size_t size() const
Definition: format.h:3271
void write(BasicCStringRef< Char > format, ArgList args)
Definition: format.h:3332
Definition: log_msg.h:16
Definition: step_file_sink.h:68
Definition: format.h:3924
Definition: base_sink.h:23