9 #include "quill/core/Attributes.h" 10 #include "quill/core/Common.h" 11 #include "quill/core/Filesystem.h" 12 #include "quill/core/LogLevel.h" 13 #include "quill/core/QuillError.h" 14 #include "quill/core/TimeUtilities.h" 15 #include "quill/sinks/FileSink.h" 16 #include "quill/sinks/StreamSink.h" 25 #include <string_view> 26 #include <system_error> 76 QUILL_THROW(
QuillError{
"rotation_max_file_size must be greater than or equal to 512 bytes"});
79 _rotation_max_file_size = value;
89 if (frequency ==
'M' || frequency ==
'm')
91 _rotation_frequency = RotationFrequency::Minutely;
93 else if (frequency ==
'H' || frequency ==
'h')
95 _rotation_frequency = RotationFrequency::Hourly;
100 "Invalid frequency. Valid values are 'M' or 'm' for minutes or 'H' or 'h' for hours"});
105 QUILL_THROW(
QuillError{
"interval must be set to a value greater than 0"});
108 _rotation_interval = interval;
109 _daily_rotation_time = _disabled_daily_rotation_time();
118 _rotation_frequency = RotationFrequency::Daily;
119 _rotation_interval = 0;
120 _daily_rotation_time = _parse_daily_rotation_time(daily_rotation_time_str);
137 _overwrite_rolled_files = value;
156 _rotation_naming_scheme = value;
168 _rotation_on_creation = value;
173 QUILL_NODISCARD uint32_t max_backup_files()
const noexcept {
return _max_backup_files; }
174 QUILL_NODISCARD
bool overwrite_rolled_files()
const noexcept {
return _overwrite_rolled_files; }
175 QUILL_NODISCARD
bool remove_old_files()
const noexcept {
return _remove_old_files; }
178 return _rotation_frequency;
180 QUILL_NODISCARD uint32_t rotation_interval()
const noexcept {
return _rotation_interval; }
181 QUILL_NODISCARD std::pair<std::chrono::hours, std::chrono::minutes> daily_rotation_time()
const noexcept
183 return _daily_rotation_time;
187 return _rotation_naming_scheme;
189 QUILL_NODISCARD
bool rotation_on_creation()
const noexcept {
return _rotation_on_creation; }
193 static std::pair<std::chrono::hours, std::chrono::minutes> _disabled_daily_rotation_time() noexcept
195 return std::make_pair(std::chrono::hours{std::numeric_limits<std::chrono::hours::rep>::max()},
196 std::chrono::minutes{std::numeric_limits<std::chrono::hours::rep>::max()});
200 static std::pair<std::chrono::hours, std::chrono::minutes> _parse_daily_rotation_time(std::string
const& daily_rotation_time_str)
202 std::vector<std::string> tokens;
204 size_t start = 0, end = 0;
206 while ((end = daily_rotation_time_str.find(
':', start)) != std::string::npos)
208 token = daily_rotation_time_str.substr(start, end - start);
209 tokens.push_back(token);
214 token = daily_rotation_time_str.substr(start);
215 tokens.push_back(token);
217 if (tokens.size() != 2)
220 QuillError{
"Invalid daily_rotation_time_str value format. The format should be `HH:MM`."});
223 for (
auto const& parsed_token : tokens)
225 if (parsed_token.size() != 2)
228 "Invalid daily_rotation_time_str value format. Each component of the time (HH and MM) " 229 "should be two digits."});
233 auto const daily_rotation_time_str_tp = std::make_pair(
234 std::chrono::hours{std::stoi(tokens[0])}, std::chrono::minutes{std::stoi(tokens[1])});
236 if ((daily_rotation_time_str_tp.first > std::chrono::hours{23}) ||
237 (daily_rotation_time_str_tp.second > std::chrono::minutes{59}))
240 QuillError(
"Invalid rotation values. The hour value should be between 00 and 23, and the " 241 "minute value should be between 00 and 59."));
244 return daily_rotation_time_str_tp;
248 std::pair<std::chrono::hours, std::chrono::minutes> _daily_rotation_time;
249 size_t _rotation_max_file_size{0};
250 uint32_t _max_backup_files{std::numeric_limits<uint32_t>::max()};
251 uint32_t _rotation_interval{0};
254 bool _overwrite_rolled_files{
true};
255 bool _remove_old_files{
true};
256 bool _rotation_on_creation{
false};
262 template <
typename TBase>
266 using base_type = TBase;
280 std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now())
281 : base_type(filename, static_cast<FileSinkConfig const&>(config),
282 std::move(file_event_notifier),
false, start_time),
285 uint64_t
const today_timestamp_ns =
static_cast<uint64_t
>(
286 std::chrono::duration_cast<std::chrono::nanoseconds>(start_time.time_since_epoch()).count());
288 _clean_and_recover_files(this->_filename, _config.open_mode(), today_timestamp_ns);
290 if (_config.rotation_frequency() != RotatingFileSinkConfig::RotationFrequency::Disabled)
293 _next_rotation_time = _calculate_initial_rotation_tp(
294 static_cast<uint64_t>(
295 std::chrono::duration_cast<std::chrono::nanoseconds>(start_time.time_since_epoch()).count()),
300 this->open_file(this->_filename, _config.open_mode());
301 _open_file_timestamp =
static_cast<uint64_t
>(
302 std::chrono::duration_cast<std::chrono::nanoseconds>(start_time.time_since_epoch()).count());
304 _created_files.emplace_front(this->_filename, 0, std::string{});
307 if (_config.rotation_on_creation() && !this->is_null() && _get_file_size(this->_filename) > 0)
309 _rotate_files(today_timestamp_ns);
312 if (!this->is_null())
314 this->_file_size = _get_file_size(this->_filename);
336 std::string_view thread_id, std::string_view thread_name,
337 std::string
const& process_id, std::string_view logger_name,
338 LogLevel log_level, std::string_view log_level_description,
339 std::string_view log_level_short_code,
340 std::vector<std::pair<std::string, std::string>>
const* named_args,
341 std::string_view log_message, std::string_view log_statement)
override 345 base_type::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id,
346 logger_name, log_level, log_level_description, log_level_short_code,
347 named_args, log_message, log_statement);
351 bool time_rotation =
false;
353 if (_config.rotation_frequency() != RotatingFileSinkConfig::RotationFrequency::Disabled)
356 time_rotation = _time_rotation(log_timestamp);
359 if (!time_rotation && _config.rotation_max_file_size() != 0)
362 _size_rotation(log_statement.size(), log_timestamp);
366 base_type::write_log(log_metadata, log_timestamp, thread_id, thread_name, process_id,
367 logger_name, log_level, log_level_description, log_level_short_code,
368 named_args, log_message, log_statement);
373 QUILL_NODISCARD
bool _time_rotation(uint64_t record_timestamp_ns)
375 if (record_timestamp_ns >= _next_rotation_time)
377 _rotate_files(record_timestamp_ns);
378 _next_rotation_time = _calculate_rotation_tp(record_timestamp_ns, _config);
386 void _size_rotation(
size_t log_msg_size, uint64_t record_timestamp_ns)
389 if ((this->_file_size + log_msg_size) > _config.rotation_max_file_size())
391 _rotate_files(record_timestamp_ns);
396 void _rotate_files(uint64_t record_timestamp_ns)
398 if ((_created_files.size() > _config.max_backup_files()) && !_config.overwrite_rolled_files())
407 this->open_file(this->_filename, _reopen_mode_after_failed_rotation(_config.open_mode()));
408 _open_file_timestamp = record_timestamp_ns;
409 this->_file_size = _get_file_size(this->_filename);
414 base_type::flush_sink();
415 base_type::fsync_file(
true);
417 if (_get_file_size(this->_filename) <= 0)
426 std::string datetime_suffix;
427 if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Date)
430 this->format_datetime_string(_open_file_timestamp, _config.timezone(),
"%Y%m%d");
432 else if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::DateAndTime)
435 this->format_datetime_string(_open_file_timestamp, _config.timezone(),
"%Y%m%d_%H%M%S");
439 for (
auto it = _created_files.rbegin(); it != _created_files.rend(); ++it)
443 fs::path existing_file;
444 fs::path renamed_file;
446 existing_file = _get_filename(it->base_filename, it->index, it->date_time);
449 uint32_t index_to_use = it->index;
451 if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Index ||
452 it->date_time == datetime_suffix)
457 renamed_file = _get_filename(it->base_filename, index_to_use, datetime_suffix);
459 it->index = index_to_use;
460 it->date_time = datetime_suffix;
462 _rename_file(existing_file, renamed_file);
464 else if (it->date_time.empty())
467 index_to_use = it->index;
469 renamed_file = _get_filename(it->base_filename, index_to_use, datetime_suffix);
471 it->index = index_to_use;
472 it->date_time = datetime_suffix;
474 _rename_file(existing_file, renamed_file);
479 if (_created_files.size() > _config.max_backup_files())
482 fs::path
const removed_file = _get_filename(
483 _created_files.back().base_filename, _created_files.back().index, _created_files.back().date_time);
484 _remove_file(removed_file);
485 _created_files.pop_back();
489 _created_files.emplace_front(this->_filename, 0, std::string{});
492 this->open_file(this->_filename, _config.open_mode());
493 _open_file_timestamp = record_timestamp_ns;
494 this->_file_size = 0;
498 void _clean_and_recover_files(fs::path
const& filename, std::string
const& open_mode, uint64_t today_timestamp_ns)
500 if ((_config.rotation_naming_scheme() != RotatingFileSinkConfig::RotationNamingScheme::Index) &&
501 (_config.rotation_naming_scheme() != RotatingFileSinkConfig::RotationNamingScheme::Date))
509 if (_config.remove_old_files() && (open_mode.find(
'w') != std::string::npos))
511 for (
auto const& entry : fs::directory_iterator(fs::current_path() / filename.parent_path()))
513 if (entry.path().extension().string() != filename.extension().string())
520 if (entry.path().filename().string().find(filename.stem().string() +
".") != 0)
526 if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Index)
530 else if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Date)
534 if (
size_t const pos = entry.path().stem().string().find_last_of(
'.'); pos != std::string::npos)
537 std::string
const today_date =
538 this->format_datetime_string(today_timestamp_ns, _config.timezone(),
"%Y%m%d");
540 if (std::string
const index_or_date =
541 entry.path().stem().string().substr(pos + 1, entry.path().stem().string().length());
542 (index_or_date.length() >= 8) && (index_or_date == today_date))
545 if (index_or_date == today_date)
554 std::string
const filename_with_date = entry.path().filename().string().substr(0, pos);
556 if (
size_t const second_last = filename_with_date.find_last_of(
'.'); second_last != std::string::npos)
558 if (std::string
const date_part =
559 filename_with_date.substr(second_last + 1, filename_with_date.length());
560 date_part == today_date)
570 else if (open_mode.find(
'a') != std::string::npos)
573 for (
auto const& entry : fs::directory_iterator(fs::current_path() / filename.parent_path()))
576 if (entry.path().extension().string() != filename.extension().string())
583 if (entry.path().filename().string().find(filename.stem().string() +
".") != 0)
589 std::string
const extension = entry.path().extension().string();
592 if (
size_t const pos = entry.path().stem().string().find_last_of(
'.'); pos != std::string::npos)
594 if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Index)
596 std::string
const index =
597 entry.path().stem().string().substr(pos + 1, entry.path().stem().string().length());
599 std::string
const current_filename = entry.path().filename().string().substr(0, pos) + extension;
600 fs::path current_file = entry.path().parent_path();
601 current_file.append(current_filename);
606 _created_files.emplace_front(current_file, static_cast<uint32_t>(std::stoul(index)),
609 QUILL_CATCH_ALL() {
continue; }
611 else if (_config.rotation_naming_scheme() == RotatingFileSinkConfig::RotationNamingScheme::Date)
614 std::string
const today_date =
615 this->format_datetime_string(today_timestamp_ns, _config.timezone(),
"%Y%m%d");
617 if (std::string
const index_or_date =
618 entry.path().stem().string().substr(pos + 1, entry.path().stem().string().length());
619 (index_or_date.length() >= 8) && (index_or_date == today_date))
622 std::string
const current_filename = entry.path().filename().string().substr(0, pos) + extension;
623 fs::path current_file = entry.path().parent_path();
624 current_file.append(current_filename);
626 _created_files.emplace_front(current_file, 0, index_or_date);
632 std::string
const filename_with_date = entry.path().filename().string().substr(0, pos);
634 if (
size_t const second_last = filename_with_date.find_last_of(
'.'); second_last != std::string::npos)
636 if (std::string
const date_part =
637 filename_with_date.substr(second_last + 1, filename_with_date.length());
638 date_part == today_date)
640 std::string
const current_filename = filename_with_date.substr(0, second_last) + extension;
641 fs::path current_file = entry.path().parent_path();
642 current_file.append(current_filename);
647 _created_files.emplace_front(
648 current_file, static_cast<uint32_t>(std::stoul(index_or_date)), date_part);
650 QUILL_CATCH_ALL() {
continue; }
659 std::sort(_created_files.begin(), _created_files.end(),
665 QUILL_NODISCARD
static std::string _reopen_mode_after_failed_rotation(
666 std::string
const& open_mode)
668 if (!open_mode.empty() && (open_mode.front() ==
'w'))
670 std::string reopen_mode = open_mode;
671 reopen_mode.front() =
'a';
679 QUILL_NODISCARD
static size_t _get_file_size(fs::path
const& filename)
681 return static_cast<size_t>(fs::file_size(filename));
685 static void _remove_file(fs::path
const& filename) noexcept
689 fs::file_status
const status = fs::status(filename, ec);
691 if (ec || status.type() != fs::file_type::regular)
697 fs::remove(filename, ec);
701 bool static _rename_file(fs::path
const& previous_file, fs::path
const& new_file) noexcept
704 fs::rename(previous_file, new_file, ec);
710 std::this_thread::sleep_for(std::chrono::milliseconds{250});
713 fs::rename(previous_file, new_file, ec);
725 QUILL_NODISCARD
static fs::path _append_index_to_filename(fs::path
const& filename, uint32_t index) noexcept
733 auto const [stem, ext] = base_type::extract_stem_and_extension(filename);
734 return fs::path{stem +
"." + std::to_string(index) + ext};
738 QUILL_NODISCARD
static fs::path _append_string_to_filename(fs::path
const& filename, std::string
const& text) noexcept
746 auto const [stem, ext] = base_type::extract_stem_and_extension(filename);
747 return fs::path{stem +
"." + text + ext};
751 static uint64_t _calculate_initial_rotation_tp(uint64_t start_time_ns,
RotatingFileSinkConfig const& config)
754 #if (defined(__i386)) 755 time_t
const time_now =
static_cast<time_t
>(start_time_ns / 1000000000);
757 time_t
const time_now =
static_cast<time_t
>(start_time_ns) / 1000000000;
762 if (config.timezone() == Timezone::GmtTime)
772 if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Minutely)
777 else if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Hourly)
783 else if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Daily)
785 date.tm_hour =
static_cast<decltype(date.tm_hour)
>(config.daily_rotation_time().first.count());
786 date.tm_min =
static_cast<decltype(date.tm_min)
>(config.daily_rotation_time().second.count());
791 QUILL_THROW(
QuillError{
"Invalid rotation frequency"});
795 time_t
const rotation_time =
796 (config.timezone() == Timezone::GmtTime) ?
detail::timegm(&date) : std::mktime(&date);
798 uint64_t
const rotation_time_seconds = (rotation_time > time_now)
799 ? static_cast<uint64_t>(rotation_time)
800 :
static_cast<uint64_t
>(rotation_time + std::chrono::seconds{std::chrono::hours{24}}.count());
802 return static_cast<uint64_t
>(
803 std::chrono::nanoseconds{std::chrono::seconds{rotation_time_seconds}}.count());
807 static uint64_t _calculate_rotation_tp(uint64_t rotation_timestamp_ns,
RotatingFileSinkConfig const& config)
809 if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Minutely)
811 return rotation_timestamp_ns +
812 static_cast<uint64_t
>(
813 std::chrono::nanoseconds{std::chrono::minutes{config.rotation_interval()}}.count());
816 if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Hourly)
818 return rotation_timestamp_ns +
819 static_cast<uint64_t
>(
820 std::chrono::nanoseconds{std::chrono::hours{config.rotation_interval()}}.count());
823 if (config.rotation_frequency() == RotatingFileSinkConfig::RotationFrequency::Daily)
825 return rotation_timestamp_ns + std::chrono::nanoseconds{std::chrono::hours{24}}.count();
828 QUILL_THROW(
QuillError{
"Invalid rotation frequency"});
832 static fs::path _get_filename(fs::path filename, uint32_t index, std::string
const& date_time)
834 if (!date_time.empty())
836 filename = _append_string_to_filename(filename, date_time);
841 filename = _append_index_to_filename(filename, index);
850 FileInfo(fs::path base_filename, uint32_t index, std::string date_time)
851 : base_filename{std::move(base_filename)}, date_time{std::move(date_time)}, index{index}
855 fs::path base_filename;
856 std::string date_time;
863 uint64_t _open_file_timestamp{0};
QUILL_ATTRIBUTE_COLD void set_max_backup_files(uint32_t value)
Sets the maximum number of log files to keep.
Definition: RotatingSink.h:127
Definition: RotatingSink.h:848
QUILL_ATTRIBUTE_COLD void set_rotation_max_file_size(size_t value)
Sets the maximum file size for rotation.
Definition: RotatingSink.h:72
QUILL_ATTRIBUTE_COLD void set_rotation_naming_scheme(RotationNamingScheme value)
Sets the naming scheme for the rotated files.
Definition: RotatingSink.h:154
std::deque< FileInfo > _created_files
We store in a queue the filenames we created, first: index, second: date/datetime, third: base_filename.
Definition: RotatingSink.h:861
QUILL_NODISCARD size_t rotation_max_file_size() const noexcept
Getter methods.
Definition: RotatingSink.h:172
QUILL_ATTRIBUTE_HOT void write_log(MacroMetadata const *log_metadata, uint64_t log_timestamp, std::string_view thread_id, std::string_view thread_name, std::string const &process_id, std::string_view logger_name, LogLevel log_level, std::string_view log_level_description, std::string_view log_level_short_code, std::vector< std::pair< std::string, std::string >> const *named_args, std::string_view log_message, std::string_view log_statement) override
Writes a formatted log message to the stream.
Definition: RotatingSink.h:335
tm * localtime_rs(time_t const *timer, tm *buf)
Portable localtime_r or _s per operating system.
Definition: TimeUtilities.h:59
RotationFrequency
The frequency of log file rotation.
Definition: RotatingSink.h:45
RotatingFileSinkConfig()
Constructs a new RotatingFileSinkConfig object.
Definition: RotatingSink.h:66
uint64_t _next_rotation_time
The next rotation time point.
Definition: RotatingSink.h:862
RotationNamingScheme
The naming scheme for rotated log files.
Definition: RotatingSink.h:56
tm * gmtime_rs(time_t const *timer, tm *buf)
Portable gmtime_r or _s per operating system.
Definition: TimeUtilities.h:31
time_t timegm(tm *tm)
inverses of gmtime
Definition: TimeUtilities.h:85
QUILL_ATTRIBUTE_COLD void set_rotation_time_daily(std::string const &daily_rotation_time_str)
Sets the time of day for daily log file rotation.
Definition: RotatingSink.h:116
custom exception
Definition: QuillError.h:45
QUILL_ATTRIBUTE_COLD void set_rotation_on_creation(bool value)
Sets whether to force rotation on file sink creation/startup.
Definition: RotatingSink.h:166
The FileSinkConfig class holds the configuration options for the FileSink.
Definition: FileSink.h:64
QUILL_ATTRIBUTE_COLD void set_remove_old_files(bool value)
Sets whether previous rotated log files should be removed on process start up.
Definition: RotatingSink.h:147
Notifies on file events by calling the appropriate callback, the callback is executed on the backend ...
Definition: StreamSink.h:55
The RotatingSink class.
Definition: RotatingSink.h:263
QUILL_ATTRIBUTE_COLD void set_overwrite_rolled_files(bool value)
Sets whether the oldest rolled logs should be overwritten when the maximum backup count is reached...
Definition: RotatingSink.h:135
RotatingSink(fs::path const &filename, RotatingFileSinkConfig const &config, FileEventNotifier file_event_notifier=FileEventNotifier{}, std::chrono::system_clock::time_point start_time=std::chrono::system_clock::now())
Constructor.
Definition: RotatingSink.h:278
QUILL_ATTRIBUTE_COLD void set_rotation_frequency_and_interval(char frequency, uint32_t interval)
Sets the frequency and interval of file rotation.
Definition: RotatingSink.h:87
The configuration options for the RotatingSink.
Definition: RotatingSink.h:39