Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add max_bytes to TimeRotatingFileHandler #324

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions quill/include/quill/Quill.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,20 +176,22 @@ QUILL_NODISCARD QUILL_ATTRIBUTE_COLD std::shared_ptr<Handler> file_handler(
* @param append_to_filename additional info to append to the name of the file.
* FilenameAppend::None, FilenameAppend::Date, FilenameAppend::DateTime
* @param when 'M' for minutes, 'H' for hours or 'daily'
* @param interval The interval used for rotation.
* @param interval The interval used for rotation, Used only when 'M' or 'H' is specified
* @param backup_count maximum backup files to keep
* @param timezone if true times in UTC will be used; otherwise local time is used
* @param at_time specifies the time of day when rollover occurs if 'daily' is passed
* @param file_event_notifier a FileEventNotifier to get callbacks to file events such as before_open, after_open etc
* @param do_fsync calls fsync in addition to fflush when flushing the file
* @param max_bytes The max_bytes of the file, when the size is exceeded the file will rollover
* @return a pointer to a time rotating file handler
*/
QUILL_NODISCARD QUILL_ATTRIBUTE_COLD std::shared_ptr<Handler> time_rotating_file_handler(
fs::path const& base_filename, std::string const& mode = std::string{"a"},
FilenameAppend append_to_filename = FilenameAppend::None, std::string const& when = std::string{"H"},
uint32_t interval = 1, uint32_t backup_count = std::numeric_limits<std::uint32_t>::max(),
Timezone timezone = Timezone::LocalTime, std::string const& at_time = std::string{"00:00"},
FileEventNotifier file_event_notifier = FileEventNotifier{}, bool do_fsync = false);
FileEventNotifier file_event_notifier = FileEventNotifier{}, bool do_fsync = false,
size_t max_bytes = std::numeric_limits<size_t>::max());

/**
* Creates a new instance of the RotatingFileHandler class or looks up an existing instance.
Expand Down
13 changes: 10 additions & 3 deletions quill/include/quill/handlers/TimeRotatingFileHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,17 @@ class TimeRotatingFileHandler final : public FileHandler
* @param mode mode to open_file file
* @param append_to_filename appends extra info to the file
* @param when 'M', 'H' or 'daily'
* @param interval Used when 'M' is 'H' is specified
* @param interval Used when 'M' or 'H' is specified
* @param max_bytes Max file size in bytes
* @param backup_count Maximum files to keep
* @param timezone if true gmt time then UTC times are used instead
* @param at_time used when 'daily' is specified
* @param file_event_notifier notifies on file events
* @param do_fsync also fsync when flushing
*/
TimeRotatingFileHandler(fs::path const& base_filename, std::string const& mode,
FilenameAppend append_to_filename, std::string when,
uint32_t interval, uint32_t backup_count, Timezone timezone,
FilenameAppend append_to_filename, std::string when, uint32_t interval,
size_t max_bytes, uint32_t backup_count, Timezone timezone,
std::string const& at_time, FileEventNotifier file_event_notifier, bool do_fsync);

~TimeRotatingFileHandler() override = default;
Expand All @@ -56,11 +57,17 @@ class TimeRotatingFileHandler final : public FileHandler
static QUILL_ATTRIBUTE_COLD std::chrono::system_clock::time_point _calculate_rotation_tp(
std::chrono::system_clock::time_point time_now, std::string const& when, uint32_t interval) noexcept;

bool _time_rotation(uint64_t log_msg_ts);
void _size_rotation(size_t log_msg_size);
void _rotate_files(bool zero_minutes);

private:
std::deque<std::pair<uint32_t, fs::path>> _created_files; /**< We store in a queue the filenames we created, in order to remove_file them if we exceed _backup_count limit */
std::string _when; /**< 'M', 'H' or 'daily' */
std::chrono::system_clock::time_point _file_creation_time; /**< The time we create the file we are writing */
std::chrono::system_clock::time_point _next_rotation_time; /**< The next rotation time point */
size_t _max_bytes; /**< Max file size in bytes */
size_t _current_size{0}; /**< Current file size in bytes */
uint32_t _interval; /**< Interval when 'M' or 'H' is used */
uint32_t _backup_count; /**< Maximum files to keep after rotation */
Timezone _using_timezone; /**< The timezone used */
Expand Down
9 changes: 5 additions & 4 deletions quill/src/Quill.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,13 @@ std::shared_ptr<Handler> time_rotating_file_handler(
FilenameAppend append_to_filename /* = FilenameAppend::None */,
std::string const& when /* = std::string{"H"} */, uint32_t interval /* = 1 */,
uint32_t backup_count /* = std::numeric_limits<std::uint32_t>::max() */,
Timezone timezone /* = Timezone::LocalTime */, std::string const& at_time /* = std::string{} */,
FileEventNotifier file_event_notifier /* = FileEventNotifier{} */, bool do_fsync /* = false */)
Timezone timezone /* = Timezone::LocalTime */, std::string const& at_time /* = std::string{"00:00"} */,
FileEventNotifier file_event_notifier /* = FileEventNotifier{} */, bool do_fsync /* = false */,
size_t max_bytes /* = std::numeric_limits<size_t>::max() */)
{
return create_handler<TimeRotatingFileHandler>(base_filename.string(), mode, append_to_filename,
when, interval, backup_count, timezone, at_time,
std::move(file_event_notifier), do_fsync);
when, interval, max_bytes, backup_count, timezone,
at_time, std::move(file_event_notifier), do_fsync);
}

/***/
Expand Down
2 changes: 1 addition & 1 deletion quill/src/handlers/RotatingFileHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ void RotatingFileHandler::_rotate()
if (!_overwrite_oldest_files)
{
// always increment current index as we will break above. This is needed to avoid
// _backup_count - 1 contidion below when _backup_count == 1 and !_overwrite_oldest_files
// _backup_count - 1 condition below when _backup_count == 1 and !_overwrite_oldest_files
++_current_index;
}
else
Expand Down
230 changes: 167 additions & 63 deletions quill/src/handlers/TimeRotatingFileHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ namespace quill
/***/
TimeRotatingFileHandler::TimeRotatingFileHandler(fs::path const& base_filename, std::string const& mode,
FilenameAppend append_to_filename, std::string when,
uint32_t interval, uint32_t backup_count,
uint32_t interval, size_t max_bytes, uint32_t backup_count,
Timezone timezone, std::string const& at_time,
FileEventNotifier file_event_notifier, bool do_fsync)
: FileHandler(base_filename, append_to_filename, std::move(file_event_notifier), do_fsync),
_when(std::move(when)),
_max_bytes(max_bytes),
_interval(interval),
_backup_count(backup_count),
_using_timezone(timezone),
Expand Down Expand Up @@ -81,79 +82,31 @@ TimeRotatingFileHandler::TimeRotatingFileHandler(fs::path const& base_filename,
{
_created_files.emplace_front(0, _filename);
}

if (!is_null())
{
_current_size = detail::file_size(_filename);
}
}

/***/
void TimeRotatingFileHandler::write(fmt_buffer_t const& formatted_log_message, quill::TransitEvent const& log_event)
{
bool const should_rotate =
(std::chrono::nanoseconds{log_event.header.timestamp} >= _next_rotation_time.time_since_epoch()) &&
!is_null();

if (QUILL_UNLIKELY(should_rotate))
if (is_null())
{
close_file();

if (_append_to_filename == FilenameAppend::None)
{
bool const zero_out_minutes = (_when == "H") || (_when == "daily");

// when the log files don't include the date and time information as part of their name
// we add that as part of the rotation
fs::path const previous_file = _filename;
bool const append_time_to_filename = true;
fs::path const new_file = detail::append_date_to_filename(
_filename, _file_creation_time, append_time_to_filename, _using_timezone, zero_out_minutes, true);

detail::rename_file(previous_file, new_file);

// Also store the file name in a queue to remove_file it later if we exceed backup count
_created_files.emplace_front(0, new_file);
}
else if ((_append_to_filename == FilenameAppend::Date) || (_append_to_filename == FilenameAppend::DateTime))
{
// else we only add an index to the file name
for (auto it = _created_files.rbegin(); it != _created_files.rend(); ++it)
{
// it->first: index, it->second: filename
fs::path previous_file;

if (it->first == 0)
{
// when the index is 0 we want to rename the latest file
previous_file = _filename;
}
else
{
previous_file = detail::append_index_to_filename(_filename, it->first);
}

fs::path const new_file = detail::append_index_to_filename(_filename, it->first + 1);
it->first = it->first + 1;
quill::detail::rename_file(previous_file, new_file);
}

_created_files.emplace_front(0, _filename);
}

// If we have too many files in the queue remove_file the oldest one
if (_created_files.size() > _backup_count)
{
// remove_file that file from the system and also pop it from the queue
detail::remove_file(_created_files.back().second);
_created_files.pop_back();
}

// Calculate next rotation time and start writing the new log
_file_creation_time = std::chrono::system_clock::now();
_next_rotation_time = _calculate_rotation_tp(_file_creation_time, _when, _interval);
StreamHandler::write(formatted_log_message, log_event);
return;
}

// Open file for logging
open_file(_filename, "w");
if (!_time_rotation(log_event.header.timestamp))
{
// check if we need to rotate by size
_size_rotation(formatted_log_message.size());
}

// write to file
StreamHandler::write(formatted_log_message, log_event);
_current_size += formatted_log_message.size();
}

/***/
Expand Down Expand Up @@ -220,4 +173,155 @@ std::chrono::system_clock::time_point TimeRotatingFileHandler::_calculate_rotati

return std::chrono::system_clock::time_point{};
}

/***/
void TimeRotatingFileHandler::_size_rotation(size_t log_msg_size)
{
// Calculate the new size of the file
size_t new_size = _current_size + log_msg_size;

bool const should_rotate = (new_size > _max_bytes);

if (QUILL_UNLIKELY(should_rotate))
{
_rotate_files(false);
}
}

/***/
bool TimeRotatingFileHandler::_time_rotation(uint64_t log_msg_ts)
{
bool const should_rotate =
(std::chrono::nanoseconds{log_msg_ts} >= _next_rotation_time.time_since_epoch());

if (QUILL_UNLIKELY(should_rotate))
{
_rotate_files(true);
return true;
}

return false;
}

/***/
void TimeRotatingFileHandler::_rotate_files(bool zero_minutes)
{
FileHandler::flush();

if (detail::file_size(_filename) <= 0)
{
// Also check the file size is > 0 to better deal with full disk
return;
}

close_file();

if (_append_to_filename == FilenameAppend::None)
{
bool const zero_out_minutes = zero_minutes && ((_when == "H") || (_when == "daily"));

// when the log files don't include the date and time information as part of their name
// we add that as part of the rotation
fs::path const previous_file = _filename;
bool const append_time_to_filename = true;
fs::path const new_file = detail::append_date_to_filename(
_filename, _file_creation_time, append_time_to_filename, _using_timezone, zero_out_minutes, true);

// check the new file doesn't exist
for (auto it = _created_files.rbegin(); it != _created_files.rend(); ++it)
{
// it->first: index, it->second: filename

if (it->second == new_file)
{
// we already have this file
fs::path existing_file;
fs::path renamed_file;

if (it->first == 0)
{
// when the index is 0 we want to rename the latest file
existing_file = it->second;
}
else
{
existing_file = detail::append_index_to_filename(it->second, it->first);
}

renamed_file = detail::append_index_to_filename(it->second, it->first + 1);
it->first += 1;

quill::detail::rename_file(existing_file, renamed_file);
}
}

detail::rename_file(previous_file, new_file);

// Also store the file name in a queue to remove_file it later if we exceed backup count
_created_files.emplace_front(0, new_file);

// If we have too many files in the queue remove_file the oldest one
if (_created_files.size() > _backup_count)
{
// remove_file that file from the system and also pop it from the queue
fs::path erase_file;

if (_created_files.back().first == 0)
{
// when the index is 0 we want to rename the latest file
erase_file = _created_files.back().second;
}
else
{
erase_file =
detail::append_index_to_filename(_created_files.back().second, _created_files.back().first);
}

detail::remove_file(erase_file);
_created_files.pop_back();
}
}
else if ((_append_to_filename == FilenameAppend::Date) || (_append_to_filename == FilenameAppend::DateTime))
{
// else we only add an index to the file name
for (auto it = _created_files.rbegin(); it != _created_files.rend(); ++it)
{
// it->first: index, it->second: filename
fs::path previous_file;

if (it->first == 0)
{
// when the index is 0 we want to rename the latest file
previous_file = _filename;
}
else
{
previous_file = detail::append_index_to_filename(_filename, it->first);
}

fs::path const new_file = detail::append_index_to_filename(_filename, it->first + 1);
it->first = it->first + 1;
quill::detail::rename_file(previous_file, new_file);
}

_created_files.emplace_front(0, _filename);

// If we have too many files in the queue remove_file the oldest one
if (_created_files.size() > _backup_count)
{
// remove_file that file from the system and also pop it from the queue
detail::remove_file(_created_files.back().second);
_created_files.pop_back();
}
}

// Calculate next rotation time and start writing the new log
_file_creation_time = std::chrono::system_clock::now();
_next_rotation_time = _calculate_rotation_tp(_file_creation_time, _when, _interval);

// Open file for logging
open_file(_filename, "w");
_current_size = 0;
}

} // namespace quill
Loading