9 #include "quill/core/Attributes.h" 10 #include "quill/core/Common.h" 11 #include "quill/core/Filesystem.h" 12 #include "quill/core/QuillError.h" 13 #include "quill/core/TimeUtilities.h" 14 #include "quill/sinks/StreamSink.h" 24 #include <string_view> 29 #if !defined(WIN32_LEAN_AND_MEAN) 30 #define WIN32_LEAN_AND_MEAN 33 #if !defined(NOMINMAX) 48 #if defined(_WIN32) && defined(_MSC_VER) && !defined(__GNUC__) 50 #pragma warning(disable : 4996) 53 enum class FilenameAppendOption : uint8_t
58 StartCustomTimestampFormat
82 FilenameAppendOption value, std::string_view append_filename_format_pattern = std::string_view{})
84 _filename_append_option = value;
86 if (_filename_append_option == FilenameAppendOption::StartCustomTimestampFormat)
88 if (append_filename_format_pattern.empty())
91 "The 'CustomDateTimeFormat' option was specified, but no format pattern was provided. " 92 "Please set a valid strftime format pattern"});
95 _append_filename_format_pattern = append_filename_format_pattern;
97 else if (_filename_append_option == FilenameAppendOption::StartDateTime)
99 _append_filename_format_pattern =
"_%Y%m%d_%H%M%S";
101 else if (_filename_append_option == FilenameAppendOption::StartDate)
103 _append_filename_format_pattern =
"_%Y%m%d";
114 QUILL_ATTRIBUTE_COLD
void set_timezone(Timezone time_zone) { _time_zone = time_zone; }
127 QUILL_ATTRIBUTE_COLD
void set_open_mode(
char open_mode) { _open_mode = open_mode; }
133 QUILL_ATTRIBUTE_COLD
void set_open_mode(std::string_view open_mode) { _open_mode = open_mode; }
148 _write_buffer_size = (value == 0) ? 0 : ((value < 4096) ? 4096 : value);
172 _minimum_fsync_interval = value;
186 _override_pattern_formatter_options = options;
190 QUILL_NODISCARD
bool fsync_enabled() const noexcept {
return _fsync_enabled; }
191 QUILL_NODISCARD Timezone timezone()
const noexcept {
return _time_zone; }
192 QUILL_NODISCARD FilenameAppendOption filename_append_option()
const noexcept
194 return _filename_append_option;
196 QUILL_NODISCARD std::string
const& append_filename_format_pattern()
const noexcept
198 return _append_filename_format_pattern;
200 QUILL_NODISCARD std::string
const& open_mode()
const noexcept {
return _open_mode; }
201 QUILL_NODISCARD
size_t write_buffer_size()
const noexcept {
return _write_buffer_size; }
202 QUILL_NODISCARD std::chrono::milliseconds minimum_fsync_interval()
const noexcept
204 return _minimum_fsync_interval;
206 QUILL_NODISCARD std::optional<PatternFormatterOptions>
const& override_pattern_formatter_options()
const noexcept
208 return _override_pattern_formatter_options;
212 std::string _open_mode{
'a'};
213 std::string _append_filename_format_pattern;
214 size_t _write_buffer_size{64 * 1024};
215 std::chrono::milliseconds _minimum_fsync_interval{0};
216 std::optional<PatternFormatterOptions> _override_pattern_formatter_options;
217 Timezone _time_zone{Timezone::LocalTime};
218 FilenameAppendOption _filename_append_option{FilenameAppendOption::None};
219 bool _fsync_enabled{
false};
240 std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now())
241 :
StreamSink(_get_updated_filename_with_appended_datetime(filename, config.filename_append_option(),
242 config.append_filename_format_pattern(),
243 config.timezone(), start_time),
244 nullptr, config.override_pattern_formatter_options(), std::move(file_event_notifier)),
247 if (!_config.fsync_enabled() && (_config.minimum_fsync_interval().count() != 0))
250 QuillError{
"Cannot set a non-zero minimum fsync interval when fsync is disabled."});
255 open_file(_filename, _config.open_mode());
259 ~
FileSink()
override { close_file(); }
266 if (!_write_occurred || !_file)
274 if (_config.fsync_enabled())
279 if (!fs::exists(_filename))
286 open_file(_filename,
"w");
299 std::string
const& append_format_pattern)
302 auto const time_now =
static_cast<time_t
>(timestamp_ns / 1000000000);
305 if (time_zone == Timezone::GmtTime)
315 static constexpr
size_t buffer_size{128};
316 char buffer[buffer_size];
317 std::strftime(buffer, buffer_size, append_format_pattern.data(), &now_tm);
319 return std::string{buffer};
330 return std::make_pair((filename.parent_path() / filename.stem()).
string(), filename.extension().string());
342 std::string
const& append_filename_format_pattern,
344 std::chrono::system_clock::time_point timestamp) noexcept
347 auto const [stem, ext] = extract_stem_and_extension(filename);
350 uint64_t
const timestamp_ns =
static_cast<uint64_t
>(
351 std::chrono::duration_cast<std::chrono::nanoseconds>(timestamp.time_since_epoch()).count());
354 return stem + format_datetime_string(timestamp_ns, time_zone, append_filename_format_pattern) + ext;
362 void open_file(fs::path
const& filename, std::string
const& mode)
364 if (_file_event_notifier.before_open)
366 _file_event_notifier.before_open(filename);
370 constexpr
int max_retries = 3;
371 constexpr
int retry_delay_ms = 200;
373 for (
int attempt = 0; attempt < max_retries; ++attempt)
378 _file = ::_fsopen(filename.string().data(), mode.data(), _SH_DENYNO);
383 auto file_handle =
reinterpret_cast<HANDLE
>(::_get_osfhandle(::_fileno(_file)));
384 if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0))
392 int flags = O_CREAT | O_WRONLY | O_CLOEXEC;
393 flags |= (mode ==
"w") ? O_TRUNC : O_APPEND;
395 int fd = ::open(filename.string().data(), flags, 0644);
398 _file = ::fdopen(fd, mode.data());
412 if (attempt < max_retries - 1)
414 std::this_thread::sleep_for(std::chrono::milliseconds{retry_delay_ms});
420 QUILL_THROW(
QuillError{std::string{
"fopen failed after "} + std::to_string(max_retries) +
421 " attempts, path: " + filename.string() +
" mode: " + mode +
422 " errno: " + std::to_string(errno) +
" error: " + std::strerror(errno)});
425 if (_config.write_buffer_size() != 0)
427 _write_buffer = std::make_unique<char[]>(_config.write_buffer_size());
429 if (setvbuf(_file, _write_buffer.get(), _IOFBF, _config.write_buffer_size()) != 0)
431 QUILL_THROW(
QuillError{std::string{
"setvbuf failed error: "} + std::strerror(errno)});
435 if (_file_event_notifier.after_open)
437 _file_event_notifier.after_open(filename, _file);
451 if (_file_event_notifier.before_close)
453 _file_event_notifier.before_close(_filename, _file);
459 if (_file_event_notifier.after_close)
461 _file_event_notifier.after_close(_filename);
472 auto const now = std::chrono::steady_clock::now();
473 if ((now - _last_fsync_timestamp) < _config.minimum_fsync_interval())
477 _last_fsync_timestamp = now;
481 FlushFileBuffers(reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(_file))));
483 ::fsync(fileno(_file));
497 QUILL_NODISCARD
static fs::path _get_updated_filename_with_appended_datetime(
498 fs::path
const& filename, FilenameAppendOption append_to_filename_option,
499 std::string
const& append_filename_format_pattern, Timezone time_zone,
500 std::chrono::system_clock::time_point timestamp)
502 if ((append_to_filename_option == FilenameAppendOption::None) || (filename ==
"/dev/null"))
507 if ((append_to_filename_option == FilenameAppendOption::StartCustomTimestampFormat) ||
508 (append_to_filename_option == FilenameAppendOption::StartDate) ||
509 (append_to_filename_option == FilenameAppendOption::StartDateTime))
511 return append_datetime_to_filename(filename, append_filename_format_pattern, time_zone, timestamp);
519 std::chrono::steady_clock::time_point _last_fsync_timestamp{};
520 std::unique_ptr<char[]> _write_buffer;
523 #if defined(_WIN32) && defined(_MSC_VER) && !defined(__GNUC__) static QUILL_NODISCARD std::string format_datetime_string(uint64_t timestamp_ns, Timezone time_zone, std::string const &append_format_pattern)
Format a datetime string.
Definition: FileSink.h:298
FileSink Writes the log messages to a file.
Definition: FileSink.h:226
QUILL_NODISCARD bool fsync_enabled() const noexcept
Getters.
Definition: FileSink.h:190
QUILL_ATTRIBUTE_COLD void set_override_pattern_formatter_options(std::optional< PatternFormatterOptions > const &options)
Sets custom pattern formatter options for this sink.
Definition: FileSink.h:184
QUILL_ATTRIBUTE_COLD void set_filename_append_option(FilenameAppendOption value, std::string_view append_filename_format_pattern=std::string_view{})
Sets the append type for the file name.
Definition: FileSink.h:81
QUILL_ATTRIBUTE_COLD void set_fsync_enabled(bool value)
Sets whether fsync should be performed when flushing.
Definition: FileSink.h:121
tm * localtime_rs(time_t const *timer, tm *buf)
Portable localtime_r or _s per operating system.
Definition: TimeUtilities.h:59
void fsync_file(bool force_fsync=false) noexcept
Fsync the file descriptor.
Definition: FileSink.h:468
static QUILL_NODISCARD std::pair< std::string, std::string > extract_stem_and_extension(fs::path const &filename) noexcept
Extract stem and extension from a filename.
Definition: FileSink.h:327
QUILL_ATTRIBUTE_COLD void set_write_buffer_size(size_t value)
Sets the user-defined buffer size for fwrite operations.
Definition: FileSink.h:146
void open_file(fs::path const &filename, std::string const &mode)
Open a file.
Definition: FileSink.h:362
QUILL_ATTRIBUTE_HOT void flush_sink() override
Flushes the stream and optionally fsyncs it.
Definition: FileSink.h:264
tm * gmtime_rs(time_t const *timer, tm *buf)
Portable gmtime_r or _s per operating system.
Definition: TimeUtilities.h:31
void close_file()
Close the file.
Definition: FileSink.h:444
FileSink(fs::path const &filename, FileSinkConfig const &config=FileSinkConfig{}, FileEventNotifier file_event_notifier=FileEventNotifier{}, bool do_fopen=true, std::chrono::system_clock::time_point start_time=std::chrono::system_clock::now())
Construct a FileSink object.
Definition: FileSink.h:238
static QUILL_NODISCARD fs::path append_datetime_to_filename(fs::path const &filename, std::string const &append_filename_format_pattern, Timezone time_zone, std::chrono::system_clock::time_point timestamp) noexcept
Append date and/or time to a filename.
Definition: FileSink.h:341
custom exception
Definition: QuillError.h:45
QUILL_ATTRIBUTE_COLD void set_timezone(Timezone time_zone)
Sets the timezone to use for time-based operations e.g.
Definition: FileSink.h:114
The FileSinkConfig class holds the configuration options for the FileSink.
Definition: FileSink.h:64
QUILL_ATTRIBUTE_COLD void set_open_mode(std::string_view open_mode)
Sets the open mode for the file.
Definition: FileSink.h:133
QUILL_ATTRIBUTE_HOT void flush_sink() override
Flushes the stream.
Definition: StreamSink.h:185
Notifies on file events by calling the appropriate callback, the callback is executed on the backend ...
Definition: StreamSink.h:55
QUILL_ATTRIBUTE_COLD void set_minimum_fsync_interval(std::chrono::milliseconds value)
Sets the minimum interval between fsync calls.
Definition: FileSink.h:170
StreamSink class for handling log messages.
Definition: StreamSink.h:67
QUILL_ATTRIBUTE_COLD void set_open_mode(char open_mode)
Sets the open mode for the file.
Definition: FileSink.h:127