CppLogging 1.0.5.0
C++ Logging Library
Loading...
Searching...
No Matches
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
28namespace CppLogging {
29
31
32class RollingFileAppender::Impl
33{
34public:
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
83protected:
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
226const std::string RollingFileAppender::Impl::ARCHIVE_EXTENSION = "zip";
227
228class 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
264public:
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 {
317 case TimeRollingPolicy::SECOND:
318 _rolldelay = 1000000000ull;
319 break;
320 case TimeRollingPolicy::MINUTE:
321 _rolldelay = 60 * 1000000000ull;
322 break;
323 case TimeRollingPolicy::HOUR:
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
397private:
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 {
451 case TimeRollingPolicy::SECOND:
452 rollstamp = (rollstamp / 1000000000ull) * 1000000000ull;
453 break;
454 case TimeRollingPolicy::MINUTE:
455 rollstamp = (rollstamp / (60 * 1000000000ull)) * (60 * 1000000000ull);
456 break;
457 case TimeRollingPolicy::HOUR:
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
962class SizePolicyImpl : public RollingFileAppender::Impl
963{
964public:
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
1038private:
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
1176RollingFileAppender::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
1187RollingFileAppender::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
1204bool RollingFileAppender::IsStarted() const noexcept { return impl().IsStarted(); }
1205bool RollingFileAppender::Start() { return impl().Start(); }
1206bool RollingFileAppender::Stop() { return impl().Stop(); }
1207void RollingFileAppender::AppendRecord(Record& record) { impl().AppendRecord(record); }
1208void 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.
Rolling file appender definition.