diff --git a/include/spdlog/details/file_helper-inl.h b/include/spdlog/details/file_helper-inl.h index 37d1d46f..b12b4bca 100644 --- a/include/spdlog/details/file_helper-inl.h +++ b/include/spdlog/details/file_helper-inl.h @@ -106,6 +106,15 @@ SPDLOG_INLINE void file_helper::write(const memory_buf_t &buf) { } } +SPDLOG_INLINE void file_helper::write(string_view_t buf) { + if (fd_ == nullptr) return; + size_t msg_size = buf.size(); + auto data = buf.data(); + if (std::fwrite(data, 1, msg_size, fd_) != msg_size) { + throw_spdlog_ex("Failed writing to file " + os::filename_to_str(filename_), errno); + } +} + SPDLOG_INLINE size_t file_helper::size() const { if (fd_ == nullptr) { throw_spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(filename_)); diff --git a/include/spdlog/details/file_helper.h b/include/spdlog/details/file_helper.h index f0e5d180..a39eaab9 100644 --- a/include/spdlog/details/file_helper.h +++ b/include/spdlog/details/file_helper.h @@ -28,6 +28,7 @@ public: void sync(); void close(); void write(const memory_buf_t &buf); + void write(string_view_t buf); size_t size() const; const filename_t &filename() const; diff --git a/include/spdlog/details/rotating_file-inl.h b/include/spdlog/details/rotating_file-inl.h new file mode 100644 index 00000000..f4814208 --- /dev/null +++ b/include/spdlog/details/rotating_file-inl.h @@ -0,0 +1,123 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#ifndef SPDLOG_HEADER_ONLY + #include +#endif + +#include + +#include + +namespace spdlog { +namespace details { + +SPDLOG_INLINE rotating_file::rotating_file(filename_t base_filename, + std::size_t max_size, + std::size_t max_files, + bool rotate_on_open, + const file_event_handlers &event_handlers) + : base_filename_(std::move(base_filename)), + max_size_(max_size), + max_files_(max_files), + file_helper_{event_handlers} { + if (max_size == 0) { + throw_spdlog_ex("rotating sink constructor: max_size arg cannot be zero"); + } + if (max_files > 200000) { + throw_spdlog_ex("rotating sink constructor: max_files arg cannot exceed 200000"); + } + file_helper_.open(calc_filename(base_filename_, 0)); + current_size_ = file_helper_.size(); // expensive. called only once + if (rotate_on_open && current_size_ > 0) { + rotate_(); + current_size_ = 0; + } +} + +SPDLOG_INLINE void rotating_file::write(const memory_buf_t &buf) { + write(string_view_t{buf.data(), buf.size()}); +} + +SPDLOG_INLINE void rotating_file::write(string_view_t buf) { + auto new_size = current_size_ + buf.size(); + + // rotate if the new estimated file size exceeds max size. + // rotate only if the real size > 0 to better deal with full disk (see issue #2261). + // we only check the real size when new_size > max_size_ because it is relatively expensive. + if (new_size > max_size_) { + file_helper_.flush(); + if (file_helper_.size() > 0) { + rotate_(); + new_size = buf.size(); + } + } + file_helper_.write(buf); + current_size_ = new_size; +} + +// Rotate files: +// log.txt -> log.1.txt +// log.1.txt -> log.2.txt +// log.2.txt -> log.3.txt +// log.3.txt -> delete +SPDLOG_INLINE void rotating_file::rotate_() { + using details::os::filename_to_str; + using details::os::path_exists; + + file_helper_.close(); + for (auto i = max_files_; i > 0; --i) { + filename_t src = calc_filename(base_filename_, i - 1); + if (!path_exists(src)) { + continue; + } + filename_t target = calc_filename(base_filename_, i); + + if (!rename_file_(src, target)) { + // if failed try again after a small delay. + // this is a workaround to a windows issue, where very high rotation + // rates can cause the rename to fail with permission denied (because of antivirus?). + details::os::sleep_for_millis(100); + if (!rename_file_(src, target)) { + file_helper_.reopen( + true); // truncate the log file anyway to prevent it to grow beyond its limit! + current_size_ = 0; + throw_spdlog_ex("rotating_file_sink: failed renaming " + filename_to_str(src) + + " to " + filename_to_str(target), + errno); + } + } + } + file_helper_.reopen(true); +} + +SPDLOG_INLINE void rotating_file::flush() { file_helper_.flush(); } + +// calc filename according to index and file extension if exists. +// e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". +SPDLOG_INLINE filename_t rotating_file::calc_filename(const filename_t &filename, + std::size_t index) { + if (index == 0u) { + return filename; + } + + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extension(filename); + return fmt_lib::format(SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); +} + +SPDLOG_INLINE filename_t rotating_file::filename() const { return file_helper_.filename(); } + +// delete the target if exists, and rename the src file to target +// return true on success, false otherwise. +SPDLOG_INLINE bool rotating_file::rename_file_(const filename_t &src_filename, + const filename_t &target_filename) { + // try to delete the target file in case it already exists. + (void)details::os::remove(target_filename); + return details::os::rename(src_filename, target_filename) == 0; +} + +} // namespace details +} // namespace spdlog diff --git a/include/spdlog/details/rotating_file.h b/include/spdlog/details/rotating_file.h new file mode 100644 index 00000000..b7708f5a --- /dev/null +++ b/include/spdlog/details/rotating_file.h @@ -0,0 +1,58 @@ +// Copyright(c) 2015-present, Gabi Melman & spdlog contributors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) + +#pragma once + +#include + +#include + +#include + +namespace spdlog { +namespace details { + +class SPDLOG_API rotating_file { +public: + explicit rotating_file(filename_t base_filename, + std::size_t max_size, + std::size_t max_files, + bool rotate_on_open, + const file_event_handlers &event_handlers); + rotating_file(const rotating_file &) = delete; + rotating_file &operator=(const rotating_file &) = delete; + + static filename_t calc_filename(const filename_t &filename, std::size_t index); + + void write(const memory_buf_t &buf); + void write(string_view_t buf); + + void flush(); + + filename_t filename() const; + +private: + // Rotate files: + // log.txt -> log.1.txt + // log.1.txt -> log.2.txt + // log.2.txt -> log.3.txt + // log.3.txt -> delete + void rotate_(); + + // delete the target if exists, and rename the src file to target + // return true on success, false otherwise. + bool rename_file_(const filename_t &src_filename, const filename_t &target_filename); + + filename_t base_filename_; + std::size_t max_size_; + std::size_t max_files_; + std::size_t current_size_; + file_helper file_helper_; +}; + +} // namespace details +} // namespace spdlog + +#ifdef SPDLOG_HEADER_ONLY + #include "rotating_file-inl.h" +#endif diff --git a/include/spdlog/sinks/rotating_file_sink-inl.h b/include/spdlog/sinks/rotating_file_sink-inl.h index 6f10256f..80af3bf5 100644 --- a/include/spdlog/sinks/rotating_file_sink-inl.h +++ b/include/spdlog/sinks/rotating_file_sink-inl.h @@ -9,16 +9,11 @@ #include -#include +#include #include #include -#include -#include -#include #include -#include -#include namespace spdlog { namespace sinks { @@ -30,114 +25,31 @@ SPDLOG_INLINE rotating_file_sink::rotating_file_sink( std::size_t max_files, bool rotate_on_open, const file_event_handlers &event_handlers) - : base_filename_(std::move(base_filename)), - max_size_(max_size), - max_files_(max_files), - file_helper_{event_handlers} { - if (max_size == 0) { - throw_spdlog_ex("rotating sink constructor: max_size arg cannot be zero"); - } - - if (max_files > 200000) { - throw_spdlog_ex("rotating sink constructor: max_files arg cannot exceed 200000"); - } - file_helper_.open(calc_filename(base_filename_, 0)); - current_size_ = file_helper_.size(); // expensive. called only once - if (rotate_on_open && current_size_ > 0) { - rotate_(); - current_size_ = 0; - } -} + : file_{base_filename, max_size, max_files, rotate_on_open, event_handlers} {} // calc filename according to index and file extension if exists. // e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". template SPDLOG_INLINE filename_t rotating_file_sink::calc_filename(const filename_t &filename, std::size_t index) { - if (index == 0u) { - return filename; - } - - filename_t basename, ext; - std::tie(basename, ext) = details::file_helper::split_by_extension(filename); - return fmt_lib::format(SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); + return details::rotating_file::calc_filename(filename, index); } template SPDLOG_INLINE filename_t rotating_file_sink::filename() { - std::lock_guard lock(base_sink::mutex_); - return file_helper_.filename(); + return file_.filename(); } template SPDLOG_INLINE void rotating_file_sink::sink_it_(const details::log_msg &msg) { memory_buf_t formatted; base_sink::formatter_->format(msg, formatted); - auto new_size = current_size_ + formatted.size(); - - // rotate if the new estimated file size exceeds max size. - // rotate only if the real size > 0 to better deal with full disk (see issue #2261). - // we only check the real size when new_size > max_size_ because it is relatively expensive. - if (new_size > max_size_) { - file_helper_.flush(); - if (file_helper_.size() > 0) { - rotate_(); - new_size = formatted.size(); - } - } - file_helper_.write(formatted); - current_size_ = new_size; + file_.write(formatted); } template SPDLOG_INLINE void rotating_file_sink::flush_() { - file_helper_.flush(); -} - -// Rotate files: -// log.txt -> log.1.txt -// log.1.txt -> log.2.txt -// log.2.txt -> log.3.txt -// log.3.txt -> delete -template -SPDLOG_INLINE void rotating_file_sink::rotate_() { - using details::os::filename_to_str; - using details::os::path_exists; - - file_helper_.close(); - for (auto i = max_files_; i > 0; --i) { - filename_t src = calc_filename(base_filename_, i - 1); - if (!path_exists(src)) { - continue; - } - filename_t target = calc_filename(base_filename_, i); - - if (!rename_file_(src, target)) { - // if failed try again after a small delay. - // this is a workaround to a windows issue, where very high rotation - // rates can cause the rename to fail with permission denied (because of antivirus?). - details::os::sleep_for_millis(100); - if (!rename_file_(src, target)) { - file_helper_.reopen( - true); // truncate the log file anyway to prevent it to grow beyond its limit! - current_size_ = 0; - throw_spdlog_ex("rotating_file_sink: failed renaming " + filename_to_str(src) + - " to " + filename_to_str(target), - errno); - } - } - } - file_helper_.reopen(true); -} - -// delete the target if exists, and rename the src file to target -// return true on success, false otherwise. -template -SPDLOG_INLINE bool rotating_file_sink::rename_file_(const filename_t &src_filename, - const filename_t &target_filename) { - // try to delete the target file in case it already exists. - (void)details::os::remove(target_filename); - return details::os::rename(src_filename, target_filename) == 0; + file_.flush(); } } // namespace sinks diff --git a/include/spdlog/sinks/rotating_file_sink.h b/include/spdlog/sinks/rotating_file_sink.h index cd43d349..9a8f4f60 100644 --- a/include/spdlog/sinks/rotating_file_sink.h +++ b/include/spdlog/sinks/rotating_file_sink.h @@ -3,7 +3,7 @@ #pragma once -#include +#include #include #include #include @@ -34,22 +34,7 @@ protected: void flush_() override; private: - // Rotate files: - // log.txt -> log.1.txt - // log.1.txt -> log.2.txt - // log.2.txt -> log.3.txt - // log.3.txt -> delete - void rotate_(); - - // delete the target if exists, and rename the src file to target - // return true on success, false otherwise. - bool rename_file_(const filename_t &src_filename, const filename_t &target_filename); - - filename_t base_filename_; - std::size_t max_size_; - std::size_t max_files_; - std::size_t current_size_; - details::file_helper file_helper_; + details::rotating_file file_; }; using rotating_file_sink_mt = rotating_file_sink; diff --git a/src/spdlog.cpp b/src/spdlog.cpp index 9f8390bc..27cc592b 100644 --- a/src/spdlog.cpp +++ b/src/spdlog.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include