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/ThreadPrimitives.h" 14 #include "quill/core/TimeUtilities.h" 15 #include "quill/sinks/StreamSink.h" 25 #include <string_view> 30 #if !defined(WIN32_LEAN_AND_MEAN) 31 #define WIN32_LEAN_AND_MEAN 34 #if !defined(NOMINMAX) 49 #if defined(_WIN32) && defined(_MSC_VER) && !defined(__GNUC__) 51 #pragma warning(disable : 4996) 56 enum class FilenameAppendOption : uint8_t
61 StartCustomTimestampFormat
85 FilenameAppendOption value, std::string_view append_filename_format_pattern = std::string_view{})
87 _filename_append_option = value;
89 if (_filename_append_option == FilenameAppendOption::StartCustomTimestampFormat)
91 if (append_filename_format_pattern.empty())
94 QuillError{
"The 'StartCustomTimestampFormat' option was specified, but no format pattern " 96 "Please set a valid strftime format pattern"});
99 _append_filename_format_pattern = append_filename_format_pattern;
101 else if (_filename_append_option == FilenameAppendOption::StartDateTime)
103 _append_filename_format_pattern =
"_%Y%m%d_%H%M%S";
105 else if (_filename_append_option == FilenameAppendOption::StartDate)
107 _append_filename_format_pattern =
"_%Y%m%d";
118 QUILL_ATTRIBUTE_COLD
void set_timezone(Timezone time_zone) { _time_zone = time_zone; }
135 QUILL_ATTRIBUTE_COLD
void set_open_mode(
char open_mode) { _open_mode = open_mode; }
141 QUILL_ATTRIBUTE_COLD
void set_open_mode(std::string_view open_mode) { _open_mode = open_mode; }
156 _write_buffer_size = (value == 0) ? 0 : ((value < 4096) ? 4096 : value);
180 _minimum_fsync_interval = value;
194 _override_pattern_formatter_options = options;
198 QUILL_NODISCARD
bool fsync_enabled() const noexcept {
return _fsync_enabled; }
199 QUILL_NODISCARD Timezone timezone()
const noexcept {
return _time_zone; }
200 QUILL_NODISCARD FilenameAppendOption filename_append_option()
const noexcept
202 return _filename_append_option;
204 QUILL_NODISCARD std::string
const& append_filename_format_pattern()
const noexcept
206 return _append_filename_format_pattern;
208 QUILL_NODISCARD std::string
const& open_mode()
const noexcept {
return _open_mode; }
209 QUILL_NODISCARD
size_t write_buffer_size()
const noexcept {
return _write_buffer_size; }
210 QUILL_NODISCARD std::chrono::milliseconds minimum_fsync_interval()
const noexcept
212 return _minimum_fsync_interval;
214 QUILL_NODISCARD std::optional<PatternFormatterOptions>
const& override_pattern_formatter_options()
const noexcept
216 return _override_pattern_formatter_options;
220 std::string _open_mode{
'a'};
221 std::string _append_filename_format_pattern;
222 size_t _write_buffer_size{64 * 1024};
223 std::chrono::milliseconds _minimum_fsync_interval{0};
224 std::optional<PatternFormatterOptions> _override_pattern_formatter_options;
225 Timezone _time_zone{Timezone::LocalTime};
226 FilenameAppendOption _filename_append_option{FilenameAppendOption::None};
227 bool _fsync_enabled{
false};
246 bool , std::chrono::system_clock::time_point start_time)
247 :
StreamSink(_get_updated_filename_with_appended_datetime(filename, config.filename_append_option(),
248 config.append_filename_format_pattern(),
249 config.timezone(), start_time),
250 nullptr, config.override_pattern_formatter_options(), std::move(file_event_notifier)),
253 if (!_config.fsync_enabled() && (_config.minimum_fsync_interval().count() != 0))
256 QuillError{
"Cannot set a non-zero minimum fsync interval when fsync is disabled."});
260 ~FileSinkBase()
override =
default;
263 QUILL_NODISCARD
static std::string format_datetime_string(uint64_t timestamp_ns, Timezone time_zone,
264 std::string
const& append_format_pattern)
266 auto const time_now =
static_cast<time_t
>(timestamp_ns / 1000000000);
269 if (time_zone == Timezone::GmtTime)
278 static constexpr
size_t buffer_size{128};
279 static constexpr
size_t max_buffer_size{64 * 1024};
280 std::vector<char> buffer(buffer_size);
284 size_t const len = std::strftime(buffer.data(), buffer.size(), append_format_pattern.data(), &now_tm);
287 return std::string{buffer.data(), len};
290 if (buffer.size() >= max_buffer_size)
293 QuillError{
"strftime failed to format filename timestamp. The filename " 294 "timestamp pattern may contain an unsupported format specifier."});
297 buffer.resize(buffer.size() * 2);
301 QUILL_NODISCARD
static std::pair<std::string, std::string> extract_stem_and_extension(fs::path
const& filename)
303 return std::make_pair((filename.parent_path() / filename.stem()).
string(), filename.extension().string());
306 QUILL_NODISCARD
static fs::path append_datetime_to_filename(fs::path
const& filename,
307 std::string
const& append_filename_format_pattern,
309 std::chrono::system_clock::time_point timestamp)
311 auto const [stem, ext] = extract_stem_and_extension(filename);
313 uint64_t
const timestamp_ns =
static_cast<uint64_t
>(
314 std::chrono::duration_cast<std::chrono::nanoseconds>(timestamp.time_since_epoch()).count());
316 return stem + format_datetime_string(timestamp_ns, time_zone, append_filename_format_pattern) + ext;
320 QUILL_NODISCARD
static fs::path _get_updated_filename_with_appended_datetime(
321 fs::path
const& filename, FilenameAppendOption append_to_filename_option,
322 std::string
const& append_filename_format_pattern, Timezone time_zone,
323 std::chrono::system_clock::time_point timestamp)
325 if ((append_to_filename_option == FilenameAppendOption::None) || (filename ==
"/dev/null"))
330 if ((append_to_filename_option == FilenameAppendOption::StartCustomTimestampFormat) ||
331 (append_to_filename_option == FilenameAppendOption::StartDate) ||
332 (append_to_filename_option == FilenameAppendOption::StartDateTime))
334 return append_datetime_to_filename(filename, append_filename_format_pattern, time_zone, timestamp);
337 QUILL_THROW(
QuillError{
"Unexpected FilenameAppendOption value"});
342 std::chrono::steady_clock::time_point _last_fsync_timestamp{};
350 class FileSink :
public FileSinkBase
355 std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now())
356 : FileSinkBase(filename, config, std::move(file_event_notifier), do_fopen, start_time)
358 if (do_fopen && !is_null())
360 open_file(_filename, _config.open_mode());
364 ~
FileSink()
override { _close_file_noexcept(); }
366 QUILL_ATTRIBUTE_HOT
void flush_sink()
override 368 if (!_write_occurred)
373 _flush_native_write_buffer();
374 _write_occurred =
false;
376 if (_config.fsync_enabled())
382 if (!fs::exists(_filename, ec))
385 open_file(_filename, _config.open_mode());
390 uint64_t , std::string_view ,
391 std::string_view , std::string
const& ,
392 std::string_view , LogLevel ,
395 std::vector<std::pair<std::string, std::string>>
const* ,
396 std::string_view , std::string_view log_statement)
override 398 if (QUILL_UNLIKELY(_native_file_handle == INVALID_HANDLE_VALUE))
403 std::string_view statement = log_statement;
404 std::string user_log_statement;
406 if (_file_event_notifier.before_write)
408 user_log_statement = _file_event_notifier.before_write(log_statement);
409 statement = user_log_statement;
412 auto const stmt_size = statement.size();
414 if (_native_write_pos + stmt_size > _native_write_buffer_cap)
416 _flush_native_write_buffer();
419 if (QUILL_LIKELY(stmt_size <= _native_write_buffer_cap))
421 std::memcpy(_native_write_buffer.get() + _native_write_pos, statement.data(), stmt_size);
422 _native_write_pos += stmt_size;
426 _write_to_file(statement.data(), stmt_size);
429 _file_size += stmt_size;
430 _write_occurred =
true;
434 void open_file(fs::path
const& filename, std::string
const& mode)
436 if (_file_event_notifier.before_open)
438 _file_event_notifier.before_open(filename);
441 constexpr
int max_retries = 3;
442 constexpr
int retry_delay_ms = 200;
443 HANDLE native_file_handle = INVALID_HANDLE_VALUE;
444 DWORD last_error = 0;
446 for (
int attempt = 0; attempt < max_retries; ++attempt)
448 DWORD creation_disposition = OPEN_ALWAYS;
449 if (!mode.empty() && mode[0] ==
'w')
451 creation_disposition = CREATE_ALWAYS;
454 native_file_handle = ::CreateFileW(filename.c_str(), GENERIC_WRITE,
455 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
456 nullptr, creation_disposition, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN,
nullptr);
458 if (native_file_handle == INVALID_HANDLE_VALUE)
460 last_error = ::GetLastError();
464 if (!::SetHandleInformation(native_file_handle, HANDLE_FLAG_INHERIT, 0))
466 last_error = ::GetLastError();
467 ::CloseHandle(native_file_handle);
468 native_file_handle = INVALID_HANDLE_VALUE;
472 if (native_file_handle != INVALID_HANDLE_VALUE)
477 if (attempt < max_retries - 1)
483 if (native_file_handle == INVALID_HANDLE_VALUE)
485 QUILL_THROW(
QuillError{std::string{
"CreateFileW failed after "} + std::to_string(max_retries) +
486 " attempts, path: " + filename.string() +
" mode: " + mode +
487 " GetLastError: " + std::to_string(last_error)});
490 _native_write_buffer_cap = _native_write_buffer_capacity();
491 _native_write_buffer = std::make_unique<char[]>(_native_write_buffer_cap);
492 _native_write_pos = 0;
494 if (_file_event_notifier.after_open)
496 QUILL_TRY { _file_event_notifier.after_open(filename, native_file_handle); }
497 #if !defined(QUILL_NO_EXCEPTIONS) 500 ::CloseHandle(native_file_handle);
506 _append_mode = !mode.empty() && mode[0] ==
'a';
507 _native_file_handle = native_file_handle;
512 if (_native_file_handle == INVALID_HANDLE_VALUE)
517 if (_file_event_notifier.before_close)
519 QUILL_TRY { _file_event_notifier.before_close(_filename, _native_file_handle); }
520 #if !defined(QUILL_NO_EXCEPTIONS) 523 HANDLE native_file_handle = _native_file_handle;
524 QUILL_TRY { _flush_native_write_buffer(); }
526 _native_file_handle = INVALID_HANDLE_VALUE;
527 _append_mode =
false;
528 _native_write_pos = 0;
529 ::CloseHandle(native_file_handle);
535 _flush_native_write_buffer();
536 ::CloseHandle(_native_file_handle);
537 _native_file_handle = INVALID_HANDLE_VALUE;
538 _append_mode =
false;
539 _native_write_pos = 0;
541 if (_file_event_notifier.after_close)
543 _file_event_notifier.after_close(_filename);
547 void _close_file_noexcept() noexcept
549 QUILL_TRY { close_file(); }
550 #if !defined(QUILL_NO_EXCEPTIONS) 555 void fsync_file(
bool force_fsync =
false)
557 if (_native_file_handle == INVALID_HANDLE_VALUE)
562 std::chrono::steady_clock::time_point fsync_timestamp{};
566 fsync_timestamp = std::chrono::steady_clock::now();
567 if ((fsync_timestamp - _last_fsync_timestamp) < _config.minimum_fsync_interval())
573 if (!::FlushFileBuffers(_native_file_handle))
575 _write_occurred =
true;
576 QUILL_THROW(
QuillError{std::string{
"FlushFileBuffers failed. GetLastError: "} +
577 std::to_string(::GetLastError())});
582 _last_fsync_timestamp = fsync_timestamp;
586 QUILL_NODISCARD
bool is_open()
const noexcept
588 return _native_file_handle != INVALID_HANDLE_VALUE;
591 void _write_to_file(
char const* data,
size_t size)
595 DWORD bytes_written = 0;
596 constexpr
size_t max_dword =
static_cast<size_t>(~DWORD{0});
597 DWORD
const chunk =
static_cast<DWORD
>(std::min<size_t>(size, max_dword));
598 OVERLAPPED overlapped{};
599 OVERLAPPED* overlapped_ptr{
nullptr};
603 overlapped.Offset = 0xFFFFFFFF;
604 overlapped.OffsetHigh = 0xFFFFFFFF;
605 overlapped_ptr = &overlapped;
608 if (!::WriteFile(_native_file_handle, data, chunk, &bytes_written, overlapped_ptr) ||
609 (bytes_written == 0))
611 QUILL_THROW(
QuillError{std::string{
"WriteFile failed. GetLastError: "} +
612 std::to_string(::GetLastError())});
615 data += bytes_written;
616 size -= bytes_written;
620 void _flush_native_write_buffer()
622 if ((_native_file_handle == INVALID_HANDLE_VALUE) || (_native_write_pos == 0))
627 _write_to_file(_native_write_buffer.get(), _native_write_pos);
628 _native_write_pos = 0;
631 QUILL_NODISCARD
size_t _native_write_buffer_capacity()
const noexcept
633 return _config.write_buffer_size() == 0 ? default_write_buffer_size : _config.write_buffer_size();
637 static constexpr
size_t default_write_buffer_size{64 * 1024};
638 HANDLE _native_file_handle{INVALID_HANDLE_VALUE};
639 std::unique_ptr<char[]> _native_write_buffer;
640 size_t _native_write_pos{0};
641 size_t _native_write_buffer_cap{0};
642 bool _append_mode{
false};
650 std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now())
651 :
StreamSink(_get_updated_filename_with_appended_datetime(filename, config.filename_append_option(),
652 config.append_filename_format_pattern(),
653 config.timezone(), start_time),
654 nullptr, config.override_pattern_formatter_options(), std::move(file_event_notifier)),
657 if (!_config.fsync_enabled() && (_config.minimum_fsync_interval().count() != 0))
660 QuillError{
"Cannot set a non-zero minimum fsync interval when fsync is disabled."});
665 open_file(_filename, _config.open_mode());
669 ~
FileSink()
override { _close_file_noexcept(); }
673 if (!_write_occurred || !_file)
680 if (_config.fsync_enabled())
686 if (!fs::exists(_filename, ec))
689 open_file(_filename, _config.open_mode());
694 struct OpenedFileGuard
704 QUILL_NODISCARD FILE* release() noexcept
706 FILE*
const released_file = file;
708 return released_file;
715 QUILL_NODISCARD
static std::string format_datetime_string(uint64_t timestamp_ns, Timezone time_zone,
716 std::string
const& append_format_pattern)
718 auto const time_now =
static_cast<time_t
>(timestamp_ns / 1000000000);
721 if (time_zone == Timezone::GmtTime)
730 static constexpr
size_t buffer_size{128};
731 static constexpr
size_t max_buffer_size{64 * 1024};
732 std::vector<char> buffer(buffer_size);
736 size_t const len = std::strftime(buffer.data(), buffer.size(), append_format_pattern.data(), &now_tm);
739 return std::string{buffer.data(), len};
742 if (buffer.size() >= max_buffer_size)
745 QuillError{
"strftime failed to format filename timestamp. The filename " 746 "timestamp pattern may contain an unsupported format specifier."});
749 buffer.resize(buffer.size() * 2);
753 QUILL_NODISCARD
static std::pair<std::string, std::string> extract_stem_and_extension(fs::path
const& filename)
755 return std::make_pair((filename.parent_path() / filename.stem()).
string(), filename.extension().string());
758 QUILL_NODISCARD
static fs::path append_datetime_to_filename(fs::path
const& filename,
759 std::string
const& append_filename_format_pattern,
761 std::chrono::system_clock::time_point timestamp)
763 auto const [stem, ext] = extract_stem_and_extension(filename);
765 uint64_t
const timestamp_ns =
static_cast<uint64_t
>(
766 std::chrono::duration_cast<std::chrono::nanoseconds>(timestamp.time_since_epoch()).count());
768 return stem + format_datetime_string(timestamp_ns, time_zone, append_filename_format_pattern) + ext;
771 void open_file(fs::path
const& filename, std::string
const& mode)
773 if (_file_event_notifier.before_open)
775 _file_event_notifier.before_open(filename);
778 constexpr
int max_retries = 3;
779 constexpr
int retry_delay_ms = 200;
780 std::unique_ptr<char[]> write_buffer;
781 OpenedFileGuard opened_file_guard;
783 for (
int attempt = 0; attempt < max_retries; ++attempt)
785 int flags = O_CREAT | O_WRONLY | O_CLOEXEC;
786 flags |= (!mode.empty() && mode[0] ==
'w') ? O_TRUNC : O_APPEND;
792 fd = ::open(filename.string().data(), flags, 0644);
793 }
while (fd == -1 && errno == EINTR);
797 opened_file_guard.file = ::fdopen(fd, mode.data());
798 if (!opened_file_guard.file)
804 if (opened_file_guard.file)
809 if (attempt < max_retries - 1)
815 if (!opened_file_guard.file)
817 QUILL_THROW(
QuillError{std::string{
"fopen failed after "} + std::to_string(max_retries) +
818 " attempts, path: " + filename.string() +
" mode: " + mode +
819 " errno: " + std::to_string(errno) +
" error: " + std::strerror(errno)});
822 if (_config.write_buffer_size() != 0)
824 write_buffer = std::make_unique<char[]>(_config.write_buffer_size());
826 if (setvbuf(opened_file_guard.file, write_buffer.get(), _IOFBF, _config.write_buffer_size()) != 0)
828 QUILL_THROW(
QuillError{std::string{
"setvbuf failed error: "} + std::strerror(errno)});
832 if (_file_event_notifier.after_open)
834 _file_event_notifier.after_open(filename, opened_file_guard.file);
837 _file = opened_file_guard.release();
838 _write_buffer = std::move(write_buffer);
848 if (_file_event_notifier.before_close)
850 QUILL_TRY { _file_event_notifier.before_close(_filename, _file); }
851 #if !defined(QUILL_NO_EXCEPTIONS) 865 if (std::fclose(file) != 0)
867 int const saved_errno = errno;
868 QUILL_THROW(
QuillError{std::string{
"fclose failed errno: "} + std::to_string(saved_errno) +
869 " error: " + std::strerror(saved_errno)});
872 if (_file_event_notifier.after_close)
874 _file_event_notifier.after_close(_filename);
878 void _close_file_noexcept() noexcept
880 QUILL_TRY { close_file(); }
881 #if !defined(QUILL_NO_EXCEPTIONS) 886 void fsync_file(
bool force_fsync =
false)
893 std::chrono::steady_clock::time_point fsync_timestamp{};
897 fsync_timestamp = std::chrono::steady_clock::now();
898 if ((fsync_timestamp - _last_fsync_timestamp) < _config.minimum_fsync_interval())
908 ret = ::fsync(fileno(_file));
909 }
while (ret != 0 && errno == EINTR);
911 if (QUILL_UNLIKELY(ret != 0))
913 int const saved_errno = errno;
914 _write_occurred =
true;
915 QUILL_THROW(
QuillError{std::string{
"fsync failed errno: "} + std::to_string(saved_errno) +
916 " error: " + std::strerror(saved_errno)});
921 _last_fsync_timestamp = fsync_timestamp;
925 QUILL_NODISCARD
bool is_open()
const noexcept {
return _file !=
nullptr; }
928 QUILL_NODISCARD
static fs::path _get_updated_filename_with_appended_datetime(
929 fs::path
const& filename, FilenameAppendOption append_to_filename_option,
930 std::string
const& append_filename_format_pattern, Timezone time_zone,
931 std::chrono::system_clock::time_point timestamp)
933 if ((append_to_filename_option == FilenameAppendOption::None) || (filename ==
"/dev/null"))
938 if ((append_to_filename_option == FilenameAppendOption::StartCustomTimestampFormat) ||
939 (append_to_filename_option == FilenameAppendOption::StartDate) ||
940 (append_to_filename_option == FilenameAppendOption::StartDateTime))
942 return append_datetime_to_filename(filename, append_filename_format_pattern, time_zone, timestamp);
945 QUILL_THROW(
QuillError{
"Unexpected FilenameAppendOption value"});
950 std::chrono::steady_clock::time_point _last_fsync_timestamp{};
951 std::unique_ptr<char[]> _write_buffer;
957 #if defined(_WIN32) && defined(_MSC_VER) && !defined(__GNUC__) QUILL_ATTRIBUTE_HOT void sleep_for_ns(uint64_t ns) noexcept
Mirrors std::this_thread::sleep_for(std::chrono::nanoseconds{ns}).
Definition: ThreadPrimitives.h:43
Keep the Windows-specific FileSink split isolated under _WIN32.
Definition: FileSink.h:645
QUILL_NODISCARD bool fsync_enabled() const noexcept
Getters.
Definition: FileSink.h:198
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:192
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:84
QUILL_ATTRIBUTE_COLD void set_fsync_enabled(bool value)
Sets whether fsync should be performed when flushing.
Definition: FileSink.h:129
tm * localtime_rs(time_t const *timer, tm *buf)
Portable localtime_r or _s per operating system.
Definition: TimeUtilities.h:56
QUILL_ATTRIBUTE_COLD void set_write_buffer_size(size_t value)
Sets the user-defined buffer size for fwrite operations.
Definition: FileSink.h:154
QUILL_ATTRIBUTE_HOT void flush_sink() override
Flushes the sink, synchronizing the associated sink with its controlled output sequence.
Definition: FileSink.h:671
tm * gmtime_rs(time_t const *timer, tm *buf)
Portable gmtime_r or _s per operating system.
Definition: TimeUtilities.h:28
custom exception
Definition: QuillError.h:47
QUILL_ATTRIBUTE_COLD void set_timezone(Timezone time_zone)
Sets the timezone to use for time-based operations e.g.
Definition: FileSink.h:118
The FileSinkConfig class holds the configuration options for the FileSink.
Definition: FileSink.h:67
QUILL_ATTRIBUTE_COLD void set_open_mode(std::string_view open_mode)
Sets the open mode for the file.
Definition: FileSink.h:141
QUILL_ATTRIBUTE_HOT void flush_sink() override
Flushes the stream.
Definition: StreamSink.h:156
Notifies on file events by calling the appropriate callback.
Definition: StreamSink.h:68
QUILL_ATTRIBUTE_COLD void set_minimum_fsync_interval(std::chrono::milliseconds value)
Sets the minimum interval between fsync calls.
Definition: FileSink.h:178
StreamSink class for handling log messages.
Definition: StreamSink.h:80
QUILL_ATTRIBUTE_COLD void set_open_mode(char open_mode)
Sets the open mode for the file.
Definition: FileSink.h:135