1// 2// Copyright(c) 2015 Gabi Melman. 3// Distributed under the MIT License (http://opensource.org/licenses/MIT) 4// 5 6#pragma once 7#include "spdlog/details/file_helper.h" 8#include "spdlog/details/null_mutex.h" 9#include "spdlog/fmt/fmt.h" 10#include "spdlog/sinks/base_sink.h" 11#include "spdlog/spdlog.h" 12 13#include <cerrno> 14#include <chrono> 15#include <ctime> 16#include <mutex> 17#include <string> 18#include <tuple> 19 20namespace spdlog { 21namespace sinks { 22 23// 24// Rotating file sink based on size 25// 26template<typename Mutex> 27class rotating_file_sink SPDLOG_FINAL : public base_sink<Mutex> 28{ 29public: 30 rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files) 31 : base_filename_(std::move(base_filename)) 32 , max_size_(max_size) 33 , max_files_(max_files) 34 { 35 file_helper_.open(calc_filename(base_filename_, 0)); 36 current_size_ = file_helper_.size(); // expensive. called only once 37 } 38 39 // calc filename according to index and file extension if exists. 40 // e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". 41 static filename_t calc_filename(const filename_t &filename, std::size_t index) 42 { 43 typename std::conditional<std::is_same<filename_t::value_type, char>::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w; 44 if (index != 0u) 45 { 46 filename_t basename, ext; 47 std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); 48 fmt::format_to(w, SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); 49 } 50 else 51 { 52 fmt::format_to(w, SPDLOG_FILENAME_T("{}"), filename); 53 } 54 return fmt::to_string(w); 55 } 56 57protected: 58 void sink_it_(const details::log_msg &msg) override 59 { 60 fmt::memory_buffer formatted; 61 sink::formatter_->format(msg, formatted); 62 current_size_ += formatted.size(); 63 if (current_size_ > max_size_) 64 { 65 rotate_(); 66 current_size_ = formatted.size(); 67 } 68 file_helper_.write(formatted); 69 } 70 71 void flush_() override 72 { 73 file_helper_.flush(); 74 } 75 76private: 77 // Rotate files: 78 // log.txt -> log.1.txt 79 // log.1.txt -> log.2.txt 80 // log.2.txt -> log.3.txt 81 // log.3.txt -> delete 82 void rotate_() 83 { 84 using details::os::filename_to_str; 85 file_helper_.close(); 86 for (auto i = max_files_; i > 0; --i) 87 { 88 filename_t src = calc_filename(base_filename_, i - 1); 89 filename_t target = calc_filename(base_filename_, i); 90 91 if (details::file_helper::file_exists(target)) 92 { 93 if (details::os::remove(target) != 0) 94 { 95 throw spdlog_ex("rotating_file_sink: failed removing " + filename_to_str(target), errno); 96 } 97 } 98 if (details::file_helper::file_exists(src) && details::os::rename(src, target) != 0) 99 { 100 // if failed try again after small delay. 101 // this is a workaround to a windows issue, where very high rotation 102 // rates sometimes fail (because of antivirus?). 103 details::os::sleep_for_millis(20); 104 details::os::remove(target); 105 if (details::os::rename(src, target) != 0) 106 { 107 throw spdlog_ex( 108 "rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); 109 } 110 } 111 } 112 file_helper_.reopen(true); 113 } 114 115 filename_t base_filename_; 116 std::size_t max_size_; 117 std::size_t max_files_; 118 std::size_t current_size_; 119 details::file_helper file_helper_; 120}; 121 122using rotating_file_sink_mt = rotating_file_sink<std::mutex>; 123using rotating_file_sink_st = rotating_file_sink<details::null_mutex>; 124 125} // namespace sinks 126 127// 128// factory functions 129// 130 131template<typename Factory = default_factory> 132inline std::shared_ptr<logger> rotating_logger_mt( 133 const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files) 134{ 135 return Factory::template create<sinks::rotating_file_sink_mt>(logger_name, filename, max_file_size, max_files); 136} 137 138template<typename Factory = default_factory> 139inline std::shared_ptr<logger> rotating_logger_st( 140 const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files) 141{ 142 return Factory::template create<sinks::rotating_file_sink_st>(logger_name, filename, max_file_size, max_files); 143} 144} // namespace spdlog 145