From bfd99670f07309d0a7866e831cccd1366342bd7a Mon Sep 17 00:00:00 2001 From: iamantony Date: Tue, 15 Aug 2017 20:02:36 +0300 Subject: [PATCH] Add rotating_file_sink_mp for multi-process environment --- include/spdlog/details/file_helper.h | 142 ++++++++++++++---- include/spdlog/details/os.h | 206 +++++++++++++++++++++++---- include/spdlog/sinks/file_sinks.h | 159 +++++++++++++++++++++ tests/file_helper.cpp | 66 +++++++-- tests/file_log.cpp | 38 ++++- tests/includes.h | 1 + 6 files changed, 550 insertions(+), 62 deletions(-) diff --git a/include/spdlog/details/file_helper.h b/include/spdlog/details/file_helper.h index d0d730e2..021c5ffd 100644 --- a/include/spdlog/details/file_helper.h +++ b/include/spdlog/details/file_helper.h @@ -17,19 +17,49 @@ #include #include #include +#include namespace spdlog { namespace details { -class file_helper +class base_file_helper { - public: const int open_tries = 5; const int open_interval = 10; + explicit base_file_helper() {} + virtual ~base_file_helper() {} + + base_file_helper(const base_file_helper&) = delete; + base_file_helper& operator=(const base_file_helper&) = delete; + + virtual void open(const filename_t& fname, bool truncate) = 0; + virtual void reopen(bool truncate) = 0; + virtual void write(const log_msg& msg) = 0; + virtual void flush() = 0; + virtual void close() = 0; + virtual size_t size() = 0; + + const filename_t& filename() const + { + return _filename; + } + + static bool file_exists(const filename_t& name) + { + return os::file_exists(name); + } + +protected: + filename_t _filename; +}; + +class file_helper : public base_file_helper +{ +public: explicit file_helper() : _fd(nullptr) {} @@ -37,15 +67,13 @@ public: file_helper(const file_helper&) = delete; file_helper& operator=(const file_helper&) = delete; - ~file_helper() + virtual ~file_helper() override { close(); } - - void open(const filename_t& fname, bool truncate = false) + virtual void open(const filename_t& fname, bool truncate = false) override { - close(); auto *mode = truncate ? SPDLOG_FILENAME_T("wb") : SPDLOG_FILENAME_T("ab"); _filename = fname; @@ -60,20 +88,28 @@ public: throw spdlog_ex("Failed opening file " + os::filename_to_str(_filename) + " for writing", errno); } - void reopen(bool truncate) + virtual void reopen(bool truncate) override { if (_filename.empty()) throw spdlog_ex("Failed re opening file - was not opened before"); + open(_filename, truncate); + } + virtual void write(const log_msg& msg) override + { + size_t msg_size = msg.formatted.size(); + auto data = msg.formatted.data(); + if (std::fwrite(data, 1, msg_size, _fd) != msg_size) + throw spdlog_ex("Failed writing to file " + os::filename_to_str(_filename), errno); } - void flush() + virtual void flush() override { std::fflush(_fd); } - void close() + virtual void close() override { if (_fd) { @@ -82,36 +118,94 @@ public: } } - void write(const log_msg& msg) + virtual size_t size() override { + if (!_fd) + throw spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(_filename)); - size_t msg_size = msg.formatted.size(); - auto data = msg.formatted.data(); - if (std::fwrite(data, 1, msg_size, _fd) != msg_size) - throw spdlog_ex("Failed writing to file " + os::filename_to_str(_filename), errno); + return os::filesize(_fd); } - size_t size() +private: + FILE* _fd; +}; + +// file_helper_mp - helper class for multi-process version of file sinks +class file_helper_mp : public base_file_helper +{ +public: + explicit file_helper_mp() : + _fd(-1) + {} + + file_helper_mp(const file_helper_mp&) = delete; + file_helper_mp& operator=(const file_helper_mp&) = delete; + + virtual ~file_helper_mp() override { - if (!_fd) - throw spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(_filename)); - return os::filesize(_fd); + close(); } - const filename_t& filename() const + virtual void open(const spdlog::filename_t& fname, bool truncate = false) override { - return _filename; + close(); + + _filename = fname; + int mode = SPDLOG_O_RDWR | SPDLOG_O_APPEND | SPDLOG_O_CREATE | SPDLOG_O_BINARY; + if (truncate) + mode |= SPDLOG_O_TRUNCATE; + + for (int tries = 0; tries < open_tries; ++tries) + { + if (!os::fopen_s(&_fd, fname, mode)) + return; + + std::this_thread::sleep_for(std::chrono::milliseconds(open_interval)); + } + + throw spdlog_ex("Failed opening file " + os::filename_to_str(_filename) + " for writing", errno); } - static bool file_exists(const filename_t& name) + virtual void reopen(bool truncate) override + { + if (_filename.empty()) + { + throw spdlog_ex("Failed re opening file - was not opened before"); + } + + open(_filename, truncate); + } + + virtual void write(const spdlog::details::log_msg& msg) override { + os::write_s(_fd, msg.formatted.data(), msg.formatted.size()); + } - return os::file_exists(name); + virtual void flush() override + { + // We do not flush because writes supposed to be atomic - all + // data is already in file on disk + } + + virtual void close() override + { + if (_fd >= 0) + { + os::close_s(_fd); + _fd = -1; + } + } + + virtual size_t size() override + { + if (_fd < 0) + throw spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(_filename)); + + return os::filesize(_fd); } private: - FILE* _fd; - filename_t _filename; + int _fd; }; } } diff --git a/include/spdlog/details/os.h b/include/spdlog/details/os.h index 735f6014..2b1a3900 100644 --- a/include/spdlog/details/os.h +++ b/include/spdlog/details/os.h @@ -17,6 +17,7 @@ #include #include #include +#include #ifdef _WIN32 @@ -38,17 +39,53 @@ #else // unix #include -#include +#include #ifdef __linux__ #include //Use gettid() syscall under linux to get thread id +#include // for PIPE_BUF #elif __FreeBSD__ #include //Use thr_self() syscall under FreeBSD to get thread id +#include +#endif + +// Max number of iovec structures +#ifndef IOV_MAX +#define IOV_MAX UIO_MAXIOV #endif #endif //unix +// File open modes +#if defined(_WIN32) && !defined(__MINGW32__) + +#define SPDLOG_O_APPEND _O_APPEND +#define SPDLOG_O_CREATE _O_CREAT +#define SPDLOG_O_BINARY _O_BINARY +#define SPDLOG_O_WRONLY _O_WRONLY +#define SPDLOG_O_RDONLY _O_RDONLY +#define SPDLOG_O_RDWR _O_RDWR +#define SPDLOG_O_TRUNCATE _O_TRUNC + +#else // unix and MinGW + +#define SPDLOG_O_APPEND O_APPEND +#define SPDLOG_O_CREATE O_CREAT + +#ifdef O_BINARY +#define SPDLOG_O_BINARY O_BINARY +#else +#define SPDLOG_O_BINARY 0 +#endif + +#define SPDLOG_O_WRONLY O_WRONLY +#define SPDLOG_O_RDONLY O_RDONLY +#define SPDLOG_O_RDWR O_RDWR +#define SPDLOG_O_TRUNCATE O_TRUNC + +#endif + #ifndef __has_feature // Clang - feature checking macros. #define __has_feature(x) 0 // Compatibility with non-clang compilers. #endif @@ -77,6 +114,7 @@ inline spdlog::log_clock::time_point now() #endif } + inline std::tm localtime(const std::time_t &time_tt) { @@ -96,7 +134,6 @@ inline std::tm localtime() return localtime(now_t); } - inline std::tm gmtime(const std::time_t &time_tt) { @@ -115,6 +152,7 @@ inline std::tm gmtime() std::time_t now_t = time(nullptr); return gmtime(now_t); } + inline bool operator==(const std::tm& tm1, const std::tm& tm2) { return (tm1.tm_sec == tm2.tm_sec && @@ -143,31 +181,43 @@ inline bool operator!=(const std::tm& tm1, const std::tm& tm2) SPDLOG_CONSTEXPR static const char* eol = SPDLOG_EOL; SPDLOG_CONSTEXPR static int eol_size = sizeof(SPDLOG_EOL) - 1; -inline void prevent_child_fd(FILE *f) +inline int fileno_s(FILE* file) +{ +#ifdef _WIN32 + return _fileno(file); +#else + return fileno(file); +#endif +} + +inline void prevent_child_fd(int fd) { #ifdef _WIN32 - auto file_handle = (HANDLE)_get_osfhandle(_fileno(f)); + auto file_handle = (HANDLE)_get_osfhandle(fd); if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0)) throw spdlog_ex("SetHandleInformation failed", errno); #else - auto fd = fileno(f); if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) throw spdlog_ex("fcntl with FD_CLOEXEC failed", errno); #endif } +inline void prevent_child_fd(FILE *f) +{ + prevent_child_fd(fileno_s(f)); +} //fopen_s on non windows for writing inline int fopen_s(FILE** fp, const filename_t& filename, const filename_t& mode) { #ifdef _WIN32 #ifdef SPDLOG_WCHAR_FILENAMES - *fp = _wfsopen((filename.c_str()), mode.c_str(), _SH_DENYWR); + *fp = _wfsopen((filename.c_str()), (mode.c_str()), _SH_DENYWR); #else - *fp = _fsopen((filename.c_str()), mode.c_str(), _SH_DENYWR); + *fp = _fsopen((filename.c_str()), (mode.c_str()), _SH_DENYWR); #endif #else //unix - *fp = fopen((filename.c_str()), mode.c_str()); + *fp = fopen((filename.c_str()), (mode.c_str())); #endif #ifdef SPDLOG_PREVENT_CHILD_FD @@ -177,6 +227,97 @@ inline int fopen_s(FILE** fp, const filename_t& filename, const filename_t& mode return *fp == nullptr; } +inline int fopen_s(int* fp, const filename_t& filename, const int& mode) +{ +#ifdef _WIN32 +#ifdef SPDLOG_WCHAR_FILENAMES + _wsopen_s(fp, (filename.c_str()), mode, _SH_DENYNO, (_S_IREAD | _S_IWRITE)); +#else + _sopen_s(fp, (filename.c_str()), mode, _SH_DENYNO, (_S_IREAD | _S_IWRITE)); +#endif +#else //unix + *fp = open((filename.c_str()), mode, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); +#endif + +#ifdef SPDLOG_PREVENT_CHILD_FD + if (*fp != nullptr) + prevent_child_fd(*fp); +#endif + + // Return true if something went wrong; false - if there was no errors and + // we get new file descriptor + return (fp != nullptr) && (*fp < 0) ? true : false; +} + +inline void write_s(int fd, const char* msg, std::size_t size) +{ +#ifdef _WIN32 + // TODO: what is max size of data for atomic write on Windows? + int result = _write(fd, msg, size); + if ( (result < 0) || (static_cast(result) != size) ) + { + throw spdlog_ex("Write to file failed", errno); + } + + // Immediately flush data to file + _commit(fd); + +#else // unix + // PIPE_BUF - max message length for atomic write + if (size <= static_cast(PIPE_BUF)) + { + if (write(fd, msg, size) != static_cast(size)) + { + throw spdlog_ex("Write to file failed", errno); + } + } + else + { + // Max message length should be less than SSIZE_MAX (see writev() docs) + std::size_t msg_size = std::min(size, static_cast(SSIZE_MAX)); + + // Calculate how many iovec structures we need + const std::size_t iovec_num = std::min((size / PIPE_BUF) + 1, static_cast(IOV_MAX)); + std::vector iovectors(iovec_num); + + // Warning: using const_cast so we could move along the message buffer + char* msg_ptr = const_cast(msg); + for (std::size_t i = 0; i < iovectors.size(); ++i) + { + iovectors[i].iov_base = static_cast(msg_ptr); + + std::size_t msg_part_length = 0; + if (i < iovectors.size() - 1) + { + msg_part_length = PIPE_BUF; + } + else + { + // Calc length of the last iovec + std::size_t previos_parts_length = PIPE_BUF * i; + msg_part_length = std::min(msg_size - previos_parts_length, static_cast(PIPE_BUF)); + } + + iovectors[i].iov_len = msg_part_length; + msg_ptr += msg_part_length; + } + + if (writev(fd, &iovectors[0], iovec_num) != static_cast(msg_size)) + { + throw spdlog::spdlog_ex("Failed writing to file", errno); + } + } +#endif +} + +inline void close_s(int fd) +{ +#ifdef _WIN32 + _close(fd); +#else // unix + close(fd); +#endif +} inline int remove(const filename_t &filename) { @@ -196,7 +337,6 @@ inline int rename(const filename_t& filename1, const filename_t& filename2) #endif } - //Return if file exists inline bool file_exists(const filename_t& filename) { @@ -213,29 +353,22 @@ inline bool file_exists(const filename_t& filename) #endif } - - - -//Return file size according to open FILE* object -inline size_t filesize(FILE *f) +//Return file size according to open file descriptor +inline size_t filesize(int fd) { - if (f == nullptr) - throw spdlog_ex("Failed getting file size. fd is null"); #ifdef _WIN32 - int fd = _fileno(f); #if _WIN64 //64 bits struct _stat64 st; if (_fstat64(fd, &st) == 0) return st.st_size; -#else //windows 32 bits +#else // windows 32 bits long ret = _filelength(fd); if (ret >= 0) return static_cast(ret); #endif #else // unix - int fd = fileno(f); //64 bits(but not in osx, where fstat64 is deprecated) #if !defined(__FreeBSD__) && !defined(__APPLE__) && (defined(__x86_64__) || defined(__ppc64__)) struct stat64 st; @@ -247,11 +380,39 @@ inline size_t filesize(FILE *f) return static_cast(st.st_size); #endif #endif + throw spdlog_ex("Failed getting file size from fd", errno); } +//Return file size according to open FILE* object +inline size_t filesize(FILE *f) +{ + if (f == nullptr) + throw spdlog_ex("Failed getting file size. fd is null"); + + return filesize(fileno_s(f)); +} + +inline size_t filesize(const filename_t& filename) +{ + int fd = -1; + const int open_tries = 5; + const int open_interval = 10; + for (int tries = 0; tries < open_tries; ++tries) + { + if (!fopen_s(&fd, filename, SPDLOG_O_RDONLY)) + { + size_t file_size = filesize(fd); + close_s(fd); + + return file_size; + } + std::this_thread::sleep_for(std::chrono::milliseconds(open_interval)); + } + throw spdlog_ex("Failed opening file for filesize command", errno); +} //Return utc offset in minutes or throw spdlog_ex on failure inline int utc_minutes_offset(const std::tm& tm = details::os::localtime()) @@ -414,13 +575,11 @@ inline std::string errno_str(int err_num) inline int pid() { - #ifdef _WIN32 return ::_getpid(); #else return static_cast(::getpid()); #endif - } @@ -457,11 +616,10 @@ inline bool is_color_terminal() // Source: https://github.com/agauniyal/rang/ inline bool in_terminal(FILE* file) { - #ifdef _WIN32 - return _isatty(_fileno(file)) ? true : false; + return _isatty(fileno_s(file)) ? true : false; #else - return isatty(fileno(file)) ? true : false; + return isatty(fileno_s(file)) ? true : false; #endif } } //os diff --git a/include/spdlog/sinks/file_sinks.h b/include/spdlog/sinks/file_sinks.h index 421acc8a..ec9a281e 100644 --- a/include/spdlog/sinks/file_sinks.h +++ b/include/spdlog/sinks/file_sinks.h @@ -145,6 +145,165 @@ private: typedef rotating_file_sink rotating_file_sink_mt; typedef rotating_file_sinkrotating_file_sink_st; +// Rotating file sink for multi-process environment +template +class rotating_file_sink_mp SPDLOG_FINAL : public spdlog::sinks::base_sink < Mutex > +{ +public: + rotating_file_sink_mp(const spdlog::filename_t & base_filename, + const std::size_t& max_size, + const std::size_t& max_files) : + _base_filename(base_filename), + _max_size(max_size), + _max_files(max_files), + _file_helper() + { + _rotating_file = get_folder_path(_base_filename) + "rotating"; + _file_helper.open(calc_filename(_base_filename, 0)); + } + +protected: + void _sink_it(const details::log_msg& msg) override + { + if (details::os::file_exists(_rotating_file)) + { + wait_till_rotation_completed(); + } + else + { + size_t file_size = _file_helper.size(); + if ( (file_size + msg.formatted.size()) > _max_size ) + { + // Check that _file_helper give us real size of a log file and not a size of already rotated (old) log file + if (file_size <= details::os::filesize(_file_helper.filename())) + { + // Because previous operation was long, check that other processes did not started rotation + if (details::os::file_exists(_rotating_file)) + { + wait_till_rotation_completed(); + } + else + { + safe_rotation(); + } + } + else + { + // File helper have file descriptor to rotated log file. We need to close it and open current log file + _file_helper.reopen(false); + } + } + } + + _file_helper.write(msg); + } + + void _flush() override + { + _file_helper.flush(); + } + +private: + static filename_t get_folder_path(const filename_t& filename) + { + size_t pos = filename.find_last_of('/'); + if (pos == std::string::npos) + { + return filename_t("/"); + } + + filename_t path; + path.assign(filename, 0, pos + 1); + return path; + } + + static filename_t calc_filename(const filename_t& filename, std::size_t index) + { + std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; + if (index) + w.write(SPDLOG_FILENAME_T("{}.{}"), filename, index); + else + w.write(SPDLOG_FILENAME_T("{}"), filename); + + return w.str(); + } + + void wait_till_rotation_completed() + { + _file_helper.close(); + + const int max_tries = 100; + const int sleep_time = 10; + for (int i = 0; i < max_tries; ++i) + { + if (!details::os::file_exists(_rotating_file)) + { + break; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time)); + } + + _file_helper.reopen(false); + } + + void safe_rotation() + { + // create rotating file + details::file_helper_mp rotating_file; + rotating_file.open(_rotating_file); + rotating_file.close(); + + // close log file + _file_helper.close(); + + // perform rotation + _rotate(); + + // open log file and truncate it + _file_helper.reopen(true); + + // delete rotating file + details::os::remove(_rotating_file); + } + + // Rotate files: + // log.txt -> log.txt.1 + // log.txt.1 -> log.txt.2 + // log.txt.2 -> log.txt.3 + // lo3.txt.3 -> delete + void _rotate() + { + for (auto i = _max_files; i > 0; --i) + { + filename_t src = calc_filename(_base_filename, i - 1); + filename_t target = calc_filename(_base_filename, i); + if (details::os::file_exists(target)) + { + if (details::os::remove(target) != 0) + { + throw spdlog_ex("rotating_file_sink: failed removing " + details::os::filename_to_str(target), errno); + } + } + + if (details::os::file_exists(src) && details::os::rename(src, target)) + { + throw spdlog_ex("rotating_file_sink: failed renaming " + details::os::filename_to_str(src) + " to " + details::os::filename_to_str(target), errno); + } + } + } + +private: + filename_t _base_filename; + filename_t _rotating_file; + std::size_t _max_size; + std::size_t _max_files; + details::file_helper_mp _file_helper; +}; + +typedef rotating_file_sink_mp rotating_file_sink_mp_mt; +typedef rotating_file_sink_mp rotating_file_sink_mp_st; + /* * Default generator of daily log file names. */ diff --git a/tests/file_helper.cpp b/tests/file_helper.cpp index 9a4ad603..8f65b334 100644 --- a/tests/file_helper.cpp +++ b/tests/file_helper.cpp @@ -7,15 +7,14 @@ using namespace spdlog::details; static const std::string target_filename = "logs/file_helper_test.txt"; -static void write_with_helper(file_helper &helper, size_t howmany) +static void write_with_helper(base_file_helper* helper, size_t howmany) { log_msg msg; msg.formatted << std::string(howmany, '1'); - helper.write(msg); - helper.flush(); + helper->write(msg); + helper->flush(); } - TEST_CASE("file_helper_filename", "[file_helper::filename()]]") { prepare_logdir(); @@ -25,8 +24,6 @@ TEST_CASE("file_helper_filename", "[file_helper::filename()]]") REQUIRE(helper.filename() == target_filename); } - - TEST_CASE("file_helper_size", "[file_helper::size()]]") { prepare_logdir(); @@ -34,13 +31,12 @@ TEST_CASE("file_helper_size", "[file_helper::size()]]") { file_helper helper; helper.open(target_filename); - write_with_helper(helper, expected_size); + write_with_helper(&helper, expected_size); REQUIRE(static_cast(helper.size()) == expected_size); } REQUIRE(get_filesize(target_filename) == expected_size); } - TEST_CASE("file_helper_exists", "[file_helper::file_exists()]]") { prepare_logdir(); @@ -55,7 +51,7 @@ TEST_CASE("file_helper_reopen", "[file_helper::reopen()]]") prepare_logdir(); file_helper helper; helper.open(target_filename); - write_with_helper(helper, 12); + write_with_helper(&helper, 12); REQUIRE(helper.size() == 12); helper.reopen(true); REQUIRE(helper.size() == 0); @@ -67,12 +63,62 @@ TEST_CASE("file_helper_reopen2", "[file_helper::reopen(false)]]") size_t expected_size = 14; file_helper helper; helper.open(target_filename); - write_with_helper(helper, expected_size); + write_with_helper(&helper, expected_size); REQUIRE(helper.size() == expected_size); helper.reopen(false); REQUIRE(helper.size() == expected_size); } +TEST_CASE("file_helper_mp_filename", "[file_helper_mp::filename()]]") +{ + prepare_logdir(); + file_helper_mp helper; + helper.open(target_filename); + REQUIRE(helper.filename() == target_filename); +} +TEST_CASE("file_helper_mp_size", "[file_helper_mp::size()]]") +{ + prepare_logdir(); + size_t expected_size = 123; + { + file_helper_mp helper; + helper.open(target_filename); + write_with_helper(&helper, expected_size); + REQUIRE(static_cast(helper.size()) == expected_size); + } + REQUIRE(get_filesize(target_filename) == expected_size); +} +TEST_CASE("file_helper_mp_exists", "[file_helper_mp::file_exists()]]") +{ + prepare_logdir(); + REQUIRE(!file_helper_mp::file_exists(target_filename)); + file_helper_mp helper; + helper.open(target_filename); + REQUIRE(file_helper_mp::file_exists(target_filename)); +} + +TEST_CASE("file_helper_mp_reopen", "[file_helper_mp::reopen()]]") +{ + prepare_logdir(); + file_helper_mp helper; + helper.open(target_filename); + write_with_helper(&helper, 12); + REQUIRE(helper.size() == 12); + helper.reopen(true); + REQUIRE(helper.size() == 0); +} + +TEST_CASE("file_helper_mp_reopen2", "[file_helper_mp::reopen(false)]]") +{ + prepare_logdir(); + size_t expected_size = 14; + file_helper_mp helper; + helper.open(target_filename); + write_with_helper(&helper, expected_size); + REQUIRE(helper.size() == expected_size); + helper.reopen(false); + REQUIRE(helper.size() == expected_size); +} diff --git a/tests/file_log.cpp b/tests/file_log.cpp index 45f6e8c1..296bd0c4 100644 --- a/tests/file_log.cpp +++ b/tests/file_log.cpp @@ -20,7 +20,6 @@ TEST_CASE("simple_file_logger", "[simple_logger]]") REQUIRE(count_lines(filename) == 2); } - TEST_CASE("flush_on", "[flush_on]]") { prepare_logdir(); @@ -54,7 +53,6 @@ TEST_CASE("rotating_file_logger1", "[rotating_logger]]") REQUIRE(count_lines(filename) == 10); } - TEST_CASE("rotating_file_logger2", "[rotating_logger]]") { prepare_logdir(); @@ -76,6 +74,40 @@ TEST_CASE("rotating_file_logger2", "[rotating_logger]]") } +TEST_CASE("rotating_file_logger_mp1", "[rotating_logger]]") +{ + prepare_logdir(); + std::string basename = "logs/rotating_log"; + auto logger = spdlog::create("logger", basename, 1024, 0); + + for (int i = 0; i < 10; ++i) + logger->info("Test message {}", i); + + logger->flush(); + auto filename = basename; + REQUIRE(count_lines(filename) == 10); +} + +TEST_CASE("rotating_file_logger_mp2", "[rotating_logger]]") +{ + prepare_logdir(); + std::string basename = "logs/rotating_log"; + auto logger = spdlog::create("logger", basename, 1024, 1); + for (int i = 0; i < 10; ++i) + logger->info("Test message {}", i); + + logger->flush(); + auto filename = basename; + REQUIRE(count_lines(filename) == 10); + for (int i = 0; i < 1000; i++) + logger->info("Test message {}", i); + + logger->flush(); + REQUIRE(get_filesize(filename) <= 1024); + auto filename1 = basename + ".1"; + REQUIRE(get_filesize(filename1) <= 1024); +} + TEST_CASE("daily_logger", "[daily_logger]]") { prepare_logdir(); @@ -94,7 +126,6 @@ TEST_CASE("daily_logger", "[daily_logger]]") REQUIRE(count_lines(filename) == 10); } - TEST_CASE("daily_logger with dateonly calculator", "[daily_logger_dateonly]]") { using sink_type = spdlog::sinks::daily_file_sink< @@ -148,4 +179,3 @@ TEST_CASE("daily_logger with custom calculator", "[daily_logger_custom]]") auto filename = w.str(); REQUIRE(count_lines(filename) == 10); } - diff --git a/tests/includes.h b/tests/includes.h index 0590fc60..83f43611 100644 --- a/tests/includes.h +++ b/tests/includes.h @@ -11,6 +11,7 @@ #include "utils.h" #include "../include/spdlog/spdlog.h" +#include "../include/spdlog/sinks/file_sinks.h" #include "../include/spdlog/sinks/null_sink.h" #include "../include/spdlog/sinks/ostream_sink.h"