CppLogging  1.0.4.0
C++ Logging Library
rolling_file_appender.cpp
Go to the documentation of this file.
1 
10 
11 #include "errors/fatal.h"
12 #include "string/format.h"
13 #include "threads/thread.h"
14 #include "threads/wait_queue.h"
15 #include "time/timezone.h"
16 #include "utility/countof.h"
17 #include "utility/resource.h"
18 #include "utility/validate_aligned_storage.h"
19 
20 #include "minizip/zip.h"
21 #if defined(_WIN32) || defined(_WIN64)
22 #include "minizip/iowin32.h"
23 #endif
24 
25 #include <atomic>
26 #include <cassert>
27 
28 namespace CppLogging {
29 
31 
32 class RollingFileAppender::Impl
33 {
34 public:
35  static const std::string ARCHIVE_EXTENSION;
36 
37  Impl(RollingFileAppender& appender, const CppCommon::Path& path, bool archive, bool truncate, bool auto_flush, bool auto_start)
38  : _appender(appender), _path(path), _archive(archive), _truncate(truncate), _auto_flush(auto_flush)
39  {
40  // Start the rolling file appender
41  if (auto_start)
42  Start();
43  }
44 
45  virtual ~Impl()
46  {
47  // Stop the rolling file appender
48  if (IsStarted())
49  Stop();
50  }
51 
52  virtual bool IsStarted() const noexcept { return _started; }
53 
54  virtual bool Start()
55  {
56  if (IsStarted())
57  return false;
58 
59  if (_archive)
60  ArchivationStart();
61 
62  _started = true;
63  return true;
64  }
65 
66  virtual bool Stop()
67  {
68  if (!IsStarted())
69  return false;
70 
71  CloseFile();
72 
73  if (_archive)
74  ArchivationStop();
75 
76  _started = false;
77  return true;
78  }
79 
80  virtual void AppendRecord(Record& record) = 0;
81  virtual void Flush() = 0;
82 
83 protected:
84  RollingFileAppender& _appender;
85  CppCommon::Path _path;
86  bool _archive;
87  bool _truncate;
88  bool _auto_flush;
89 
90  std::atomic<bool> _started{false};
91  CppCommon::Timestamp _retry{0};
92  CppCommon::File _file;
93  size_t _written{0};
94 
95  bool CloseFile()
96  {
97  try
98  {
99  // Check if the file is already opened for writing
100  if (_file.IsFileWriteOpened())
101  {
102  // Flush & close the file
103  _file.Flush();
104  _file.Close();
105 
106  // Archive the file
107  if (_archive)
108  ArchiveQueue(_file);
109  }
110  return true;
111  }
112  catch (const CppCommon::FileSystemException&) { return false; }
113  }
114 
115  std::thread _archive_thread;
116  CppCommon::WaitQueue<CppCommon::Path> _archive_queue;
117 
118  virtual void ArchiveQueue(const CppCommon::Path& path)
119  {
120  _archive_queue.Enqueue(path);
121  }
122 
123  virtual void ArchiveFile(const CppCommon::Path& path, const CppCommon::Path& filename)
124  {
125  CppCommon::File file(path);
126 
127  // Create a new zip archive
128  zipFile zf;
129 #if defined(_WIN32) || defined(_WIN64)
130  zlib_filefunc64_def ffunc;
131  fill_win32_filefunc64W(&ffunc);
132  zf = zipOpen2_64((file + ".zip").wstring().c_str(), APPEND_STATUS_CREATE, nullptr, &ffunc);
133 #else
134  zf = zipOpen64((file + ".zip").string().c_str(), APPEND_STATUS_CREATE);
135 #endif
136  if (zf == nullptr)
137  throwex CppCommon::FileSystemException("Cannot create a new zip archive!").Attach(file);
138 
139  // Smart resource cleaner pattern
140  auto zip = CppCommon::resource(zf, [](zipFile handle) { zipClose(handle, nullptr); });
141 
142  // Open a new file in zip archive
143  int result = zipOpenNewFileInZip64(zf, filename.empty() ? file.filename().string().c_str() : filename.string().c_str(), nullptr, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION, 1);
144  if (result != ZIP_OK)
145  throwex CppCommon::FileSystemException("Cannot open a new file in zip archive!").Attach(file);
146 
147  // Smart resource cleaner pattern
148  auto zip_file = CppCommon::resource(zf, [](zipFile handle) { zipCloseFileInZip(handle); });
149 
150  CppCommon::File source(file);
151  uint8_t buffer[16384];
152  size_t size;
153 
154  // Open the source file for reading
155  source.Open(true, false);
156 
157  // Write data into the zip file
158  do
159  {
160  size = source.Read(buffer, CppCommon::countof(buffer));
161  if (size > 0)
162  {
163  result = zipWriteInFileInZip(zf, buffer, (unsigned)size);
164  if (result != ZIP_OK)
165  throwex CppCommon::FileSystemException("Cannot write into the zip file!").Attach(file);
166  }
167  } while (size > 0);
168 
169  // Close the source file
170  source.Close();
171 
172  // Close the file in zip archive
173  result = zipCloseFileInZip(zf);
174  if (result != ZIP_OK)
175  throwex CppCommon::FileSystemException("Cannot close a file in zip archive!").Attach(file);
176  zip_file.release();
177 
178  // Close zip archive
179  result = zipClose(zf, nullptr);
180  if (result != ZIP_OK)
181  throwex CppCommon::FileSystemException("Cannot close a zip archive!").Attach(file);
182  zip.release();
183 
184  // Remove the source file
185  CppCommon::File::Remove(source);
186  }
187 
188  void ArchivationStart()
189  {
190  // Start archivation thread
191  _archive_thread = CppCommon::Thread::Start([this]() { ArchivationThread(); });
192  }
193 
194  void ArchivationStop()
195  {
196  // Stop archivation thread
197  _archive_queue.Close();
198  _archive_thread.join();
199  }
200 
201  void ArchivationThread()
202  {
203  // Call initialize archivation thread handler
204  _appender.onArchiveThreadInitialize();
205 
206  try
207  {
208  CppCommon::Path path;
209  while (_archive_queue.Dequeue(path))
210  ArchiveFile(path, "");
211  }
212  catch (const std::exception& ex)
213  {
214  fatality(ex);
215  }
216  catch (...)
217  {
218  fatality("Archivation thread terminated!");
219  }
220 
221  // Call cleanup archivation thread handler
222  _appender.onArchiveThreadCleanup();
223  }
224 };
225 
226 const std::string RollingFileAppender::Impl::ARCHIVE_EXTENSION = "zip";
227 
228 class TimePolicyImpl : public RollingFileAppender::Impl
229 {
230  enum class PlaceholderType
231  {
232  String,
233  UtcDateTime,
234  UtcDate,
235  UtcTime,
236  UtcYear,
237  UtcMonth,
238  UtcDay,
239  UtcHour,
240  UtcMinute,
241  UtcSecond,
242  UtcTimezone,
243  LocalDateTime,
244  LocalDate,
245  LocalTime,
246  LocalYear,
247  LocalMonth,
248  LocalDay,
249  LocalHour,
250  LocalMinute,
251  LocalSecond,
252  LocalTimezone
253  };
254 
255  struct Placeholder
256  {
257  PlaceholderType type;
258  std::string value;
259 
260  explicit Placeholder(PlaceholderType t) : type(t) {}
261  Placeholder(PlaceholderType t, const std::string& v) : type(t), value(v) {}
262  };
263 
264 public:
265  TimePolicyImpl(RollingFileAppender& appender, const CppCommon::Path& path, TimeRollingPolicy policy, const std::string& pattern, bool archive, bool truncate, bool auto_flush, bool auto_start)
266  : RollingFileAppender::Impl(appender, path, archive, truncate, auto_flush, auto_start),
267  _policy(policy), _pattern(pattern)
268  {
269  std::string placeholder;
270  std::string subpattern;
271 
272  // Tokenize layout pattern
273  bool read_placeholder = false;
274  for (char ch : pattern)
275  {
276  // Start reading placeholder or pattern
277  if (ch == '{')
278  {
279  if (read_placeholder)
280  AppendPattern(placeholder);
281  else
282  AppendPattern(subpattern);
283  placeholder.clear();
284  subpattern.clear();
285  read_placeholder = true;
286  }
287  // Stop reading placeholder or pattern
288  else if (ch == '}')
289  {
290  if (read_placeholder)
291  {
292  AppendPlaceholder(placeholder);
293  read_placeholder = false;
294  }
295  else
296  subpattern += ch;
297  }
298  // Continue reading placeholder or pattern
299  else
300  {
301  if (read_placeholder)
302  placeholder += ch;
303  else
304  subpattern += ch;
305  }
306  }
307 
308  // Addend last value of placeholder or pattern
309  if (read_placeholder)
310  AppendPattern(placeholder);
311  else
312  AppendPattern(subpattern);
313 
314  // Calculate rolling delay
315  switch (policy)
316  {
318  _rolldelay = 1000000000ull;
319  break;
321  _rolldelay = 60 * 1000000000ull;
322  break;
324  _rolldelay = 60 * 60 * 1000000000ull;
325  break;
326  default:
327  _rolldelay = 24 * 60 * 60 * 1000000000ull;
328  break;
329  }
330  }
331 
332  virtual ~TimePolicyImpl()
333  {
334  // Stop the rolling file appender
335  if (IsStarted())
336  Stop();
337  }
338 
339  TimeRollingPolicy policy() const
340  {
341  return _policy;
342  }
343 
344  void AppendRecord(Record& record) override
345  {
346  // Skip logging records without layout
347  if (record.raw.empty())
348  return;
349 
350  size_t size = record.raw.size() - 1;
351 
352  if (PrepareFile(record.timestamp))
353  {
354  // Try to write logging record content into the opened file
355  try
356  {
357  _file.Write(record.raw.data(), size);
358  _written += size;
359 
360  // Perform auto-flush if enabled
361  if (_auto_flush)
362  _file.Flush();
363  }
364  catch (const CppCommon::FileSystemException&)
365  {
366  // Try to close the opened file in case of any IO error
367  try
368  {
369  _file.Close();
370  }
371  catch (const CppCommon::FileSystemException&) {}
372  }
373  }
374  }
375 
376  void Flush() override
377  {
378  if (FlushFile(CppCommon::Timestamp::utc()))
379  {
380  // Try to flush the opened file
381  try
382  {
383  _file.Flush();
384  }
385  catch (const CppCommon::FileSystemException&)
386  {
387  // Try to close the opened file in case of any IO error
388  try
389  {
390  _file.Close();
391  }
392  catch (const CppCommon::FileSystemException&) {}
393  }
394  }
395  }
396 
397 private:
398  TimeRollingPolicy _policy;
399  std::string _pattern;
400  std::vector<Placeholder> _placeholders;
401  CppCommon::Timestamp _rollstamp{0};
402  CppCommon::Timespan _rolldelay{0};
403  bool _first{true};
404 
405  bool FlushFile(uint64_t timestamp)
406  {
407  try
408  {
409  // 1. Check if the file is already opened for writing
410  if (_file.IsFileWriteOpened())
411  {
412  // 1.1. Check the rolling timestamp
413  if (timestamp < (_rollstamp + _rolldelay))
414  return true;
415 
416  // 1.2. Flush & close the file
417  _file.Flush();
418  _file.Close();
419 
420  // 1.3. Archive the file
421  if (_archive)
422  ArchiveQueue(_file);
423  }
424  }
425  catch (const CppCommon::FileSystemException&)
426  {
427  // In case of any IO error reset the retry timestamp and return false!
428  _retry = CppCommon::Timestamp::utc();
429  }
430 
431  return false;
432  }
433 
434  bool PrepareFile(uint64_t timestamp)
435  {
436  try
437  {
438  // 1. Flush the rolling file
439  if (FlushFile(timestamp))
440  return true;
441 
442  // 2. Check retry timestamp if 100ms elapsed after the last attempt
443  if ((CppCommon::Timestamp::utc() - _retry).milliseconds() < 100)
444  return false;
445 
446  uint64_t rollstamp = timestamp;
447 
448  // 3. Truncate rolling timestamp according to the time rolling policy
449  switch (_policy)
450  {
452  rollstamp = (rollstamp / 1000000000ull) * 1000000000ull;
453  break;
455  rollstamp = (rollstamp / (60 * 1000000000ull)) * (60 * 1000000000ull);
456  break;
458  rollstamp = (rollstamp / (60 * 60 * 1000000000ull)) * (60 * 60 * 1000000000ull);
459  break;
460  default:
461  rollstamp = (rollstamp / (24 * 60 * 60 * 1000000000ull)) * (24 * 60 * 60 * 1000000000ull);
462  break;
463  }
464 
465  // 4. Reset the flag for the first rolling file
466  if (_first)
467  _first = false;
468  else
469  timestamp = rollstamp;
470 
471  // 5. If the file is opened for reading close it
472  if (_file.IsFileReadOpened())
473  _file.Close();
474 
475  // 6. Prepare the actual rolling file path
476  _file = PrepareFilePath(CppCommon::Timestamp(timestamp));
477 
478  // 7. Create the parent directory tree
479  CppCommon::Directory::CreateTree(_file.parent());
480 
481  // 8. Open or create the rolling file
482  _file.OpenOrCreate(false, true, _truncate);
483 
484  // 9. Reset the written bytes counter
485  _written = 0;
486 
487  // 10. Reset the retry timestamp
488  _retry = 0;
489 
490  // 11. Reset the rolling timestamp
491  _rollstamp = rollstamp;
492 
493  return true;
494  }
495  catch (const CppCommon::FileSystemException&)
496  {
497  // In case of any IO error reset the retry timestamp and return false!
498  _retry = CppCommon::Timestamp::utc();
499  return false;
500  }
501  }
502 
503  CppCommon::Path PrepareFilePath(const CppCommon::Timestamp& timestamp)
504  {
505  thread_local bool cache_initizlied = false;
506  thread_local bool cache_time_required = false;
507  thread_local bool cache_utc_required = false;
508  thread_local bool cache_local_required = false;
509  thread_local bool cache_timezone_required = false;
510  thread_local uint64_t cache_seconds = 0;
511  thread_local char cache_utc_datetime_str[] = "1970-01-01T010101Z";
512  thread_local char cache_utc_date_str[] = "1970-01-01";
513  thread_local char cache_utc_time_str[] = "010101Z";
514  thread_local char cache_utc_year_str[] = "1970";
515  thread_local char cache_utc_month_str[] = "01";
516  thread_local char cache_utc_day_str[] = "01";
517  thread_local char cache_utc_hour_str[] = "00";
518  thread_local char cache_utc_minute_str[] = "00";
519  thread_local char cache_utc_second_str[] = "00";
520  thread_local char cache_utc_timezone_str[] = "Z";
521  thread_local char cache_local_datetime_str[] = "1970-01-01T010101+0000";
522  thread_local char cache_local_date_str[] = "1970-01-01";
523  thread_local char cache_local_time_str[] = "010101+0000";
524  thread_local char cache_local_year_str[] = "1970";
525  thread_local char cache_local_month_str[] = "01";
526  thread_local char cache_local_day_str[] = "01";
527  thread_local char cache_local_hour_str[] = "00";
528  thread_local char cache_local_minute_str[] = "00";
529  thread_local char cache_local_second_str[] = "00";
530  thread_local char cache_local_timezone_str[] = "+0000";
531  bool cache_update_datetime = false;
532 
533  // Update time cache
534  if (cache_time_required || !cache_initizlied)
535  {
536  uint64_t seconds = timestamp.seconds();
537 
538  if (seconds != cache_seconds)
539  {
540  cache_seconds = seconds;
541 
542  // Update timezone cache values
543  if (cache_timezone_required || !cache_initizlied)
544  {
545  CppCommon::Timezone local;
546  ConvertTimezone(cache_local_timezone_str, local.total().minutes(), 5);
547  cache_update_datetime = true;
548  }
549 
550  // Update UTC time cache values
551  if (cache_utc_required || !cache_initizlied)
552  {
553  CppCommon::UtcTime utc(timestamp);
554  ConvertNumber(cache_utc_year_str, utc.year(), 4);
555  ConvertNumber(cache_utc_month_str, utc.month(), 2);
556  ConvertNumber(cache_utc_day_str, utc.day(), 2);
557  ConvertNumber(cache_utc_hour_str, utc.hour(), 2);
558  ConvertNumber(cache_utc_minute_str, utc.minute(), 2);
559  ConvertNumber(cache_utc_second_str, utc.second(), 2);
560  cache_update_datetime = true;
561  }
562 
563  // Update local time cache values
564  if (cache_local_required || !cache_initizlied)
565  {
566  CppCommon::LocalTime local(timestamp);
567  ConvertNumber(cache_local_year_str, local.year(), 4);
568  ConvertNumber(cache_local_month_str, local.month(), 2);
569  ConvertNumber(cache_local_day_str, local.day(), 2);
570  ConvertNumber(cache_local_hour_str, local.hour(), 2);
571  ConvertNumber(cache_local_minute_str, local.minute(), 2);
572  ConvertNumber(cache_local_second_str, local.second(), 2);
573  cache_update_datetime = true;
574  }
575  }
576  }
577 
578  // Update date & time cache
579  if (cache_update_datetime)
580  {
581  char* buffer = cache_utc_date_str;
582  std::memcpy(buffer, cache_utc_year_str, CppCommon::countof(cache_utc_year_str) - 1);
583  buffer += CppCommon::countof(cache_utc_year_str) - 1;
584  *buffer++ = '-';
585  std::memcpy(buffer, cache_utc_month_str, CppCommon::countof(cache_utc_month_str) - 1);
586  buffer += CppCommon::countof(cache_utc_month_str) - 1;
587  *buffer++ = '-';
588  std::memcpy(buffer, cache_utc_day_str, CppCommon::countof(cache_utc_day_str) - 1);
589  buffer += CppCommon::countof(cache_utc_day_str) - 1;
590 
591  buffer = cache_utc_time_str;
592  std::memcpy(buffer, cache_utc_hour_str, CppCommon::countof(cache_utc_hour_str) - 1);
593  buffer += CppCommon::countof(cache_utc_hour_str) - 1;
594  std::memcpy(buffer, cache_utc_minute_str, CppCommon::countof(cache_utc_minute_str) - 1);
595  buffer += CppCommon::countof(cache_utc_minute_str) - 1;
596  std::memcpy(buffer, cache_utc_second_str, CppCommon::countof(cache_utc_second_str) - 1);
597  buffer += CppCommon::countof(cache_utc_second_str) - 1;
598  std::memcpy(buffer, cache_utc_timezone_str, CppCommon::countof(cache_utc_timezone_str) - 1);
599  buffer += CppCommon::countof(cache_utc_timezone_str) - 1;
600 
601  buffer = cache_utc_datetime_str;
602  std::memcpy(buffer, cache_utc_date_str, CppCommon::countof(cache_utc_date_str) - 1);
603  buffer += CppCommon::countof(cache_utc_date_str) - 1;
604  *buffer++ = 'T';
605  std::memcpy(buffer, cache_utc_time_str, CppCommon::countof(cache_utc_time_str) - 1);
606  buffer += CppCommon::countof(cache_utc_time_str) - 1;
607 
608  buffer = cache_local_date_str;
609  std::memcpy(buffer, cache_local_year_str, CppCommon::countof(cache_local_year_str) - 1);
610  buffer += CppCommon::countof(cache_local_year_str) - 1;
611  *buffer++ = '-';
612  std::memcpy(buffer, cache_local_month_str, CppCommon::countof(cache_local_month_str) - 1);
613  buffer += CppCommon::countof(cache_local_month_str) - 1;
614  *buffer++ = '-';
615  std::memcpy(buffer, cache_local_day_str, CppCommon::countof(cache_local_day_str) - 1);
616  buffer += CppCommon::countof(cache_local_day_str) - 1;
617 
618  buffer = cache_local_time_str;
619  std::memcpy(buffer, cache_local_hour_str, CppCommon::countof(cache_local_hour_str) - 1);
620  buffer += CppCommon::countof(cache_local_hour_str) - 1;
621  std::memcpy(buffer, cache_local_minute_str, CppCommon::countof(cache_local_minute_str) - 1);
622  buffer += CppCommon::countof(cache_local_minute_str) - 1;
623  std::memcpy(buffer, cache_local_second_str, CppCommon::countof(cache_local_second_str) - 1);
624  buffer += CppCommon::countof(cache_local_second_str) - 1;
625  std::memcpy(buffer, cache_local_timezone_str, CppCommon::countof(cache_local_timezone_str) - 1);
626  buffer += CppCommon::countof(cache_local_timezone_str) - 1;
627 
628  buffer = cache_local_datetime_str;
629  std::memcpy(buffer, cache_local_date_str, CppCommon::countof(cache_local_date_str) - 1);
630  buffer += CppCommon::countof(cache_local_date_str) - 1;
631  *buffer++ = 'T';
632  std::memcpy(buffer, cache_local_time_str, CppCommon::countof(cache_local_time_str) - 1);
633  buffer += CppCommon::countof(cache_local_time_str) - 1;
634 
635  cache_update_datetime = false;
636  }
637 
638  cache_initizlied = true;
639 
640  std::string filename;
641 
642  // Iterate through all placeholders
643  for (const auto& placeholder : _placeholders)
644  {
645  switch (placeholder.type)
646  {
647  case PlaceholderType::String:
648  {
649  // Output pattern string
650  filename.insert(filename.end(), placeholder.value.begin(), placeholder.value.end());
651  break;
652  }
653  case PlaceholderType::UtcDateTime:
654  {
655  // Output UTC date & time string
656  filename.insert(filename.end(), std::begin(cache_utc_datetime_str), std::end(cache_utc_datetime_str) - 1);
657  // Set the corresponding cache required flag
658  cache_time_required = true;
659  cache_utc_required = true;
660  break;
661  }
662  case PlaceholderType::UtcDate:
663  {
664  // Output UTC date string
665  filename.insert(filename.end(), std::begin(cache_utc_date_str), std::end(cache_utc_date_str) - 1);
666  // Set the corresponding cache required flag
667  cache_time_required = true;
668  cache_utc_required = true;
669  break;
670  }
671  case PlaceholderType::UtcTime:
672  {
673  // Output UTC time string
674  filename.insert(filename.end(), std::begin(cache_utc_time_str), std::end(cache_utc_time_str) - 1);
675  // Set the corresponding cache required flag
676  cache_time_required = true;
677  cache_utc_required = true;
678  break;
679  }
680  case PlaceholderType::UtcYear:
681  {
682  // Output UTC year string
683  filename.insert(filename.end(), std::begin(cache_utc_year_str), std::end(cache_utc_year_str) - 1);
684  // Set the corresponding cache required flag
685  cache_time_required = true;
686  cache_utc_required = true;
687  break;
688  }
689  case PlaceholderType::UtcMonth:
690  {
691  // Output UTC month string
692  filename.insert(filename.end(), std::begin(cache_utc_month_str), std::end(cache_utc_month_str) - 1);
693  // Set the corresponding cache required flag
694  cache_time_required = true;
695  cache_utc_required = true;
696  break;
697  }
698  case PlaceholderType::UtcDay:
699  {
700  // Output UTC day string
701  filename.insert(filename.end(), std::begin(cache_utc_day_str), std::end(cache_utc_day_str) - 1);
702  // Set the corresponding cache required flag
703  cache_time_required = true;
704  cache_utc_required = true;
705  break;
706  }
707  case PlaceholderType::UtcHour:
708  {
709  // Output UTC hour string
710  filename.insert(filename.end(), std::begin(cache_utc_hour_str), std::end(cache_utc_hour_str) - 1);
711  // Set the corresponding cache required flag
712  cache_time_required = true;
713  cache_utc_required = true;
714  break;
715  }
716  case PlaceholderType::UtcMinute:
717  {
718  // Output UTC minute string
719  filename.insert(filename.end(), std::begin(cache_utc_minute_str), std::end(cache_utc_minute_str) - 1);
720  // Set the corresponding cache required flag
721  cache_time_required = true;
722  cache_utc_required = true;
723  break;
724  }
725  case PlaceholderType::UtcSecond:
726  {
727  // Output UTC second string
728  filename.insert(filename.end(), std::begin(cache_utc_second_str), std::end(cache_utc_second_str) - 1);
729  // Set the corresponding cache required flag
730  cache_time_required = true;
731  cache_utc_required = true;
732  break;
733  }
734  case PlaceholderType::UtcTimezone:
735  {
736  // Output UTC timezone string
737  filename.insert(filename.end(), std::begin(cache_utc_timezone_str), std::end(cache_utc_timezone_str) - 1);
738  break;
739  }
740  case PlaceholderType::LocalDateTime:
741  {
742  // Output local date & time string
743  filename.insert(filename.end(), std::begin(cache_local_datetime_str), std::end(cache_local_datetime_str) - 1);
744  // Set the corresponding cache required flag
745  cache_time_required = true;
746  cache_local_required = true;
747  cache_timezone_required = true;
748  break;
749  }
750  case PlaceholderType::LocalDate:
751  {
752  // Output local date string
753  filename.insert(filename.end(), std::begin(cache_local_date_str), std::end(cache_local_date_str) - 1);
754  // Set the corresponding cache required flag
755  cache_time_required = true;
756  cache_local_required = true;
757  break;
758  }
759  case PlaceholderType::LocalTime:
760  {
761  // Output local time string
762  filename.insert(filename.end(), std::begin(cache_local_time_str), std::end(cache_local_time_str) - 1);
763  // Set the corresponding cache required flag
764  cache_time_required = true;
765  cache_local_required = true;
766  cache_timezone_required = true;
767  break;
768  }
769  case PlaceholderType::LocalYear:
770  {
771  // Output local year string
772  filename.insert(filename.end(), std::begin(cache_local_year_str), std::end(cache_local_year_str) - 1);
773  // Set the corresponding cache required flag
774  cache_time_required = true;
775  cache_local_required = true;
776  break;
777  }
778  case PlaceholderType::LocalMonth:
779  {
780  // Output local month string
781  filename.insert(filename.end(), std::begin(cache_local_month_str), std::end(cache_local_month_str) - 1);
782  // Set the corresponding cache required flag
783  cache_time_required = true;
784  cache_local_required = true;
785  break;
786  }
787  case PlaceholderType::LocalDay:
788  {
789  // Output local day string
790  filename.insert(filename.end(), std::begin(cache_local_day_str), std::end(cache_local_day_str) - 1);
791  // Set the corresponding cache required flag
792  cache_time_required = true;
793  cache_local_required = true;
794  break;
795  }
796  case PlaceholderType::LocalHour:
797  {
798  // Output local hour string
799  filename.insert(filename.end(), std::begin(cache_local_hour_str), std::end(cache_local_hour_str) - 1);
800  // Set the corresponding cache required flag
801  cache_time_required = true;
802  cache_local_required = true;
803  break;
804  }
805  case PlaceholderType::LocalMinute:
806  {
807  // Output local minute string
808  filename.insert(filename.end(), std::begin(cache_local_minute_str), std::end(cache_local_minute_str) - 1);
809  // Set the corresponding cache required flag
810  cache_time_required = true;
811  cache_local_required = true;
812  break;
813  }
814  case PlaceholderType::LocalSecond:
815  {
816  // Output local second string
817  filename.insert(filename.end(), std::begin(cache_local_second_str), std::end(cache_local_second_str) - 1);
818  // Set the corresponding cache required flag
819  cache_time_required = true;
820  cache_local_required = true;
821  break;
822  }
823  case PlaceholderType::LocalTimezone:
824  {
825  // Output local timezone string
826  filename.insert(filename.end(), std::begin(cache_local_timezone_str), std::end(cache_local_timezone_str) - 1);
827  // Set the corresponding cache required flag
828  cache_time_required = true;
829  cache_timezone_required = true;
830  break;
831  }
832  }
833  }
834 
835  return CppCommon::Path(_path / filename);
836  }
837 
838  void AppendPattern(const std::string& pattern)
839  {
840  // Skip empty pattern
841  if (pattern.empty())
842  return;
843 
844  // Insert or append pattern into placeholders collection
845  if (_placeholders.empty() || (_placeholders[_placeholders.size() - 1].type != PlaceholderType::String))
846  _placeholders.emplace_back(PlaceholderType::String, pattern);
847  else
848  _placeholders[_placeholders.size() - 1].value += pattern;
849  }
850 
851  void AppendPlaceholder(const std::string& placeholder)
852  {
853  // Skip empty placeholder
854  if (placeholder.empty())
855  return;
856 
857  if (placeholder == "UtcDateTime")
858  _placeholders.emplace_back(PlaceholderType::UtcDateTime);
859  else if (placeholder == "UtcDate")
860  _placeholders.emplace_back(PlaceholderType::UtcDate);
861  else if (placeholder == "UtcTime")
862  _placeholders.emplace_back(PlaceholderType::UtcTime);
863  else if (placeholder == "UtcYear")
864  _placeholders.emplace_back(PlaceholderType::UtcYear);
865  else if (placeholder == "UtcMonth")
866  _placeholders.emplace_back(PlaceholderType::UtcMonth);
867  else if (placeholder == "UtcDay")
868  _placeholders.emplace_back(PlaceholderType::UtcDay);
869  else if (placeholder == "UtcHour")
870  _placeholders.emplace_back(PlaceholderType::UtcHour);
871  else if (placeholder == "UtcMinute")
872  _placeholders.emplace_back(PlaceholderType::UtcMinute);
873  else if (placeholder == "UtcSecond")
874  _placeholders.emplace_back(PlaceholderType::UtcSecond);
875  else if (placeholder == "UtcTimezone")
876  _placeholders.emplace_back(PlaceholderType::UtcTimezone);
877  else if (placeholder == "LocalDateTime")
878  _placeholders.emplace_back(PlaceholderType::LocalDateTime);
879  else if (placeholder == "LocalDate")
880  _placeholders.emplace_back(PlaceholderType::LocalDate);
881  else if (placeholder == "LocalTime")
882  _placeholders.emplace_back(PlaceholderType::LocalTime);
883  else if (placeholder == "LocalYear")
884  _placeholders.emplace_back(PlaceholderType::LocalYear);
885  else if (placeholder == "LocalMonth")
886  _placeholders.emplace_back(PlaceholderType::LocalMonth);
887  else if (placeholder == "LocalDay")
888  _placeholders.emplace_back(PlaceholderType::LocalDay);
889  else if (placeholder == "LocalHour")
890  _placeholders.emplace_back(PlaceholderType::LocalHour);
891  else if (placeholder == "LocalMinute")
892  _placeholders.emplace_back(PlaceholderType::LocalMinute);
893  else if (placeholder == "LocalSecond")
894  _placeholders.emplace_back(PlaceholderType::LocalSecond);
895  else if (placeholder == "LocalTimezone")
896  _placeholders.emplace_back(PlaceholderType::LocalTimezone);
897  else
898  AppendPattern("{" + placeholder + "}");
899  }
900 
901  static void ConvertNumber(char* output, int number, size_t size)
902  {
903  // Prepare the output string
904  std::memset(output, '0', size);
905 
906  // Calculate the output index
907  size_t index = size - 1;
908 
909  // Output digits
910  while ((number >= 10) && (index != 0))
911  {
912  int a = number / 10;
913  int b = number % 10;
914  output[index--] = '0' + (char)b;
915  number = a;
916  }
917 
918  // Output the last digit
919  output[index] = '0' + (char)number;
920  }
921 
922  static void ConvertTimezone(char* output, int64_t offset, size_t size)
923  {
924  // Prepare the output string
925  std::memset(output, '0', size);
926 
927  // Calculate the output index
928  size_t index = size - 1;
929 
930  // Output offset minutes
931  int64_t minutes = offset % 60;
932  if (minutes < 9)
933  {
934  output[index--] = '0' + (char)minutes;
935  --index;
936  }
937  else
938  {
939  output[index--] = '0' + (char)(minutes % 10);
940  minutes /= 10;
941  output[index--] = '0' + (char)minutes;
942  }
943 
944  // Output offset hours
945  int64_t hours = offset / 60;
946  if (hours < 9)
947  {
948  output[index] = '0' + (char)hours;
949  }
950  else
951  {
952  output[index--] = '0' + (char)(hours % 10);
953  hours /= 10;
954  output[index] = '0' + (char)hours;
955  }
956 
957  // Output minus prefix
958  output[0] = (offset < 0) ? '-' : '+';
959  }
960 };
961 
962 class SizePolicyImpl : public RollingFileAppender::Impl
963 {
964 public:
965  SizePolicyImpl(RollingFileAppender& appender, const CppCommon::Path& path, const std::string& filename, const std::string& extension, size_t size, size_t backups, bool archive, bool truncate, bool auto_flush, bool auto_start)
966  : RollingFileAppender::Impl(appender, path, archive, truncate, auto_flush, auto_start),
967  _filename(filename), _extension(extension), _size(size), _backups(backups)
968  {
969  assert((size > 0) && "Size limit should be greater than zero!");
970  if (size <= 0)
971  throwex CppCommon::ArgumentException("Size limit should be greater than zero!");
972 
973  assert((backups > 0) && "Backups count should be greater than zero!");
974  if (backups <= 0)
975  throwex CppCommon::ArgumentException("Backups count should be greater than zero!");
976  }
977 
978  virtual ~SizePolicyImpl()
979  {
980  // Stop the rolling file appender
981  if (IsStarted())
982  Stop();
983  }
984 
985  void AppendRecord(Record& record) override
986  {
987  // Skip logging records without layout
988  if (record.raw.empty())
989  return;
990 
991  size_t size = record.raw.size() - 1;
992 
993  if (PrepareFile(size))
994  {
995  // Try to write logging record content into the opened file
996  try
997  {
998  _file.Write(record.raw.data(), size);
999  _written += size;
1000 
1001  // Perform auto-flush if enabled
1002  if (_auto_flush)
1003  _file.Flush();
1004  }
1005  catch (const CppCommon::FileSystemException&)
1006  {
1007  // Try to close the opened file in case of any IO error
1008  try
1009  {
1010  _file.Close();
1011  }
1012  catch (const CppCommon::FileSystemException&) {}
1013  }
1014  }
1015  }
1016 
1017  void Flush() override
1018  {
1019  if (FlushFile(0))
1020  {
1021  // Try to flush the opened file
1022  try
1023  {
1024  _file.Flush();
1025  }
1026  catch (const CppCommon::FileSystemException&)
1027  {
1028  // Try to close the opened file in case of any IO error
1029  try
1030  {
1031  _file.Close();
1032  }
1033  catch (const CppCommon::FileSystemException&) {}
1034  }
1035  }
1036  }
1037 
1038 private:
1039  std::string _filename;
1040  std::string _extension;
1041  size_t _size;
1042  size_t _backups;
1043 
1044  bool FlushFile(size_t size)
1045  {
1046  try
1047  {
1048  // 1. Check if the file is already opened for writing
1049  if (_file.IsFileWriteOpened())
1050  {
1051  // 1.1. Check file size limit
1052  if ((_written + size) <= _size)
1053  return true;
1054 
1055  // 1.2. Flush & close the file
1056  _file.Flush();
1057  _file.Close();
1058 
1059  // 1.3. Archive or roll the current backup
1060  if (_archive)
1061  ArchiveQueue(_file);
1062  else
1063  RollBackup(_file);
1064  }
1065  }
1066  catch (const CppCommon::FileSystemException&)
1067  {
1068  // In case of any IO error reset the retry timestamp and return false!
1069  _retry = CppCommon::Timestamp::utc();
1070  }
1071 
1072  return false;
1073  }
1074 
1075  bool PrepareFile(size_t size)
1076  {
1077  try
1078  {
1079  // 1. Flush the rolling file
1080  if (FlushFile(size))
1081  return true;
1082 
1083  // 2. Check retry timestamp if 100ms elapsed after the last attempt
1084  if ((CppCommon::Timestamp::utc() - _retry).milliseconds() < 100)
1085  return false;
1086 
1087  // 3. If the file is opened for reading close it
1088  if (_file.IsFileReadOpened())
1089  _file.Close();
1090 
1091  // 4. Prepare the actual rolling file path
1092  _file = PrepareFilePath();
1093 
1094  // 5. Create the parent directory tree
1095  CppCommon::Directory::CreateTree(_file.parent());
1096 
1097  // 6. Open or create the rolling file
1098  _file.OpenOrCreate(false, true, _truncate);
1099 
1100  // 7. Reset the written bytes counter
1101  _written = 0;
1102 
1103  // 8. Reset the retry timestamp
1104  _retry = 0;
1105 
1106  return true;
1107  }
1108  catch (const CppCommon::FileSystemException&)
1109  {
1110  // In case of any IO error reset the retry timestamp and return false!
1111  _retry = CppCommon::Timestamp::utc();
1112  return false;
1113  }
1114  }
1115 
1116  void ArchiveQueue(const CppCommon::Path& path) override
1117  {
1118  // Create unique file name
1119  CppCommon::File unique = CppCommon::File(path).ReplaceFilename(CppCommon::File::unique());
1120  CppCommon::File::Rename(path, unique);
1121 
1122  _archive_queue.Enqueue(unique);
1123  }
1124 
1125  void ArchiveFile(const CppCommon::Path& path, const CppCommon::Path& filename) override
1126  {
1127  // Roll backup
1128  CppCommon::File backup = RollBackup(path);
1129 
1130  // Archive backup
1131  Impl::ArchiveFile(backup, PrepareFilePath());
1132  }
1133 
1134  CppCommon::File RollBackup(const CppCommon::Path& path)
1135  {
1136  // Delete the last backup and archive if exists
1137  CppCommon::File backup = PrepareFilePath(_backups);
1138  if (backup.IsFileExists())
1139  CppCommon::File::Remove(backup);
1140  backup += "." + ARCHIVE_EXTENSION;
1141  if (backup.IsFileExists())
1142  CppCommon::File::Remove(backup);
1143 
1144  // Roll backup files
1145  for (size_t i = _backups - 1; i > 0; --i)
1146  {
1147  CppCommon::File src = PrepareFilePath(i);
1148  CppCommon::File dst = PrepareFilePath(i + 1);
1149  if (src.IsFileExists())
1150  CppCommon::File::Rename(src, dst);
1151  src += "." + ARCHIVE_EXTENSION;
1152  dst += "." + ARCHIVE_EXTENSION;
1153  if (src.IsFileExists())
1154  CppCommon::File::Rename(src, dst);
1155  }
1156 
1157  // Backup the current file
1158  backup = PrepareFilePath(1);
1159  CppCommon::File::Rename(path, backup);
1160  return backup;
1161  }
1162 
1163  CppCommon::Path PrepareFilePath()
1164  {
1165  return CppCommon::Path(_path / CppCommon::format("{}.{}", _filename, _extension));
1166  }
1167 
1168  CppCommon::Path PrepareFilePath(size_t backup)
1169  {
1170  return CppCommon::Path(_path / CppCommon::format("{}.{}.{}", _filename, backup, _extension));
1171  }
1172 };
1173 
1175 
1176 RollingFileAppender::RollingFileAppender(const CppCommon::Path& path, TimeRollingPolicy policy, const std::string& pattern, bool archive, bool truncate, bool auto_flush, bool auto_start)
1177 {
1178  // Check implementation storage parameters
1179  [[maybe_unused]] CppCommon::ValidateAlignedStorage<sizeof(Impl), alignof(Impl), StorageSize, StorageAlign> _;
1180  static_assert((StorageSize >= sizeof(Impl)), "RollingFileAppender::StorageSize must be increased!");
1181  static_assert(((StorageAlign % alignof(Impl)) == 0), "RollingFileAppender::StorageAlign must be adjusted!");
1182 
1183  // Create the implementation instance
1184  new(&_storage)TimePolicyImpl(*this, path, policy, pattern, archive, truncate, auto_flush, auto_start);
1185 }
1186 
1187 RollingFileAppender::RollingFileAppender(const CppCommon::Path& path, const std::string& filename, const std::string& extension, size_t size, size_t backups, bool archive, bool truncate, bool auto_flush, bool auto_start)
1188 {
1189  // Check implementation storage parameters
1190  [[maybe_unused]] CppCommon::ValidateAlignedStorage<sizeof(Impl), alignof(Impl), StorageSize, StorageAlign> _;
1191  static_assert((StorageSize >= sizeof(Impl)), "RollingFileAppender::StorageSize must be increased!");
1192  static_assert(((StorageAlign % alignof(Impl)) == 0), "RollingFileAppender::StorageAlign must be adjusted!");
1193 
1194  // Create the implementation instance
1195  new(&_storage)SizePolicyImpl(*this, path, filename, extension, size, backups, archive, truncate, auto_flush, auto_start);
1196 }
1197 
1199 {
1200  // Delete the implementation instance
1201  reinterpret_cast<Impl*>(&_storage)->~Impl();
1202 }
1203 
1204 bool RollingFileAppender::IsStarted() const noexcept { return impl().IsStarted(); }
1205 bool RollingFileAppender::Start() { return impl().Start(); }
1206 bool RollingFileAppender::Stop() { return impl().Stop(); }
1207 void RollingFileAppender::AppendRecord(Record& record) { impl().AppendRecord(record); }
1208 void RollingFileAppender::Flush() { impl().Flush(); }
1209 
1210 } // namespace CppLogging
Logging record.
Definition: record.h:37
void AppendRecord(Record &record) override
Append the given logging record.
bool IsStarted() const noexcept override
Is the logging element started?
bool Start() override
Start the logging element.
bool Stop() override
Stop the logging element.
void Flush() override
Flush the logging appender.
RollingFileAppender(const CppCommon::Path &path, TimeRollingPolicy policy=TimeRollingPolicy::DAY, const std::string &pattern="{UtcDateTime}.log", bool archive=false, bool truncate=false, bool auto_flush=false, bool auto_start=true)
Initialize the rolling file appender with a time-based policy.
C++ Logging project definitions.
Definition: appender.h:15
TimeRollingPolicy
Time rolling policy.
@ SECOND
Second rolling policy.
@ MINUTE
Minute rolling policy.
@ HOUR
Hour rolling policy.
Rolling file appender definition.