diff --git a/README.md b/README.md index d2038bca..a4293247 100644 --- a/README.md +++ b/README.md @@ -113,12 +113,12 @@ int main(int, char*[]) my_logger->info("Some log message"); // Create a file rotating logger with 5mb size max and 3 rotated files - auto rotating_logger = spd::rotating_logger_mt("some_logger_name", "logs/mylogfile", 1048576 * 5, 3); + auto rotating_logger = spd::rotating_logger_mt("some_logger_name", "logs/mylogfile.txt", 1048576 * 5, 3); for (int i = 0; i < 10; ++i) rotating_logger->info("{} * {} equals {:>10}", i, i, i*i); // Create a daily logger - a new file is created every day on 2:30am - auto daily_logger = spd::daily_logger_mt("daily_logger", "logs/daily", 2, 30); + auto daily_logger = spd::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); // trigger flush if the log severity is error or higher daily_logger->flush_on(spd::level::err); daily_logger->info(123.44); diff --git a/example/example.cpp b/example/example.cpp index 01bf5796..f1980c1c 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -1,4 +1,4 @@ -// +// // Copyright(c) 2015 Gabi Melman. // Distributed under the MIT License (http://opensource.org/licenses/MIT) // @@ -43,16 +43,16 @@ int main(int, char*[]) // Create basic file logger (not rotated) - auto my_logger = spd::basic_logger_mt("basic_logger", "logs/basic"); + auto my_logger = spd::basic_logger_mt("basic_logger", "logs/basic-log.txt"); my_logger->info("Some log message"); // Create a file rotating logger with 5mb size max and 3 rotated files - auto rotating_logger = spd::rotating_logger_mt("some_logger_name", "logs/mylogfile", 1048576 * 5, 3); + auto rotating_logger = spd::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3); for (int i = 0; i < 10; ++i) rotating_logger->info("{} * {} equals {:>10}", i, i, i*i); - + // Create a daily logger - a new file is created every day on 2:30am - auto daily_logger = spd::daily_logger_mt("daily_logger", "logs/daily", 2, 30); + auto daily_logger = spd::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); // trigger flush if the log severity is error or higher daily_logger->flush_on(spd::level::err); daily_logger->info(123.44); @@ -111,8 +111,7 @@ void async_example() { size_t q_size = 4096; //queue size must be power of 2 spdlog::set_async_mode(q_size); - auto async_file = spd::daily_logger_st("async_file_logger", "logs/async_log"); - + auto async_file = spd::daily_logger_st("async_file_logger", "logs/async_log.txt"); for (int i = 0; i < 100; ++i) async_file->info("Async message #{}", i); } diff --git a/include/spdlog/details/file_helper.h b/include/spdlog/details/file_helper.h index 0d6b7038..8511f147 100644 --- a/include/spdlog/details/file_helper.h +++ b/include/spdlog/details/file_helper.h @@ -16,6 +16,7 @@ #include #include #include +#include #include namespace spdlog @@ -84,14 +85,13 @@ public: void write(const log_msg& msg) { - 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); } - size_t size() + size_t size() const { if (!_fd) throw spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(_filename)); @@ -103,12 +103,32 @@ public: return _filename; } - static bool file_exists(const filename_t& name) + static bool file_exists(const filename_t& fname) { - - return os::file_exists(name); + return os::file_exists(fname); } + // + // return basename and extension: + // + // "mylog.txt" => ("mylog", ".txt") + // "mylog" => ("mylog", "") + // + // the starting dot in filenames is ignored (hidden files): + // + // "my_folder/.mylog" => ("my_folder/.mylog") + // "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") + + static std::tuple split_by_extenstion(const filename_t& fname) + { + auto index = fname.rfind('.'); + bool found_ext = index != filename_t::npos && index !=0 && fname[index - 1] != details::os::folder_sep; + if (found_ext) + return std::make_tuple(fname.substr(0, index), fname.substr(index)); + else + return std::make_tuple(fname, filename_t()); + } + private: FILE* _fd; filename_t _filename; diff --git a/include/spdlog/details/os.h b/include/spdlog/details/os.h index dc668da3..aaf949ee 100644 --- a/include/spdlog/details/os.h +++ b/include/spdlog/details/os.h @@ -143,6 +143,16 @@ 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; + + +// folder separator +#ifdef _WIN32 +SPDLOG_CONSTEXPR static const char folder_sep = '\\'; +#else +SPDLOG_CONSTEXPR static const char folder_sep = '/'; +#endif + + inline void prevent_child_fd(FILE *f) { #ifdef _WIN32 diff --git a/include/spdlog/sinks/file_sinks.h b/include/spdlog/sinks/file_sinks.h index 0f444ec4..deb77815 100644 --- a/include/spdlog/sinks/file_sinks.h +++ b/include/spdlog/sinks/file_sinks.h @@ -77,6 +77,23 @@ public: _current_size = _file_helper.size(); //expensive. called only once } + // calc filename according to index and file extension if exists. + // e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". + static filename_t calc_filename(const filename_t& filename, std::size_t index) + { + std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; + if (index) + { + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + w.write(SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); + } + else + { + w.write(SPDLOG_FILENAME_T("{}"), filename); + } + return w.str(); + } protected: void _sink_it(const details::log_msg& msg) override @@ -94,24 +111,14 @@ protected: { _file_helper.flush(); } + -private: - 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(); - } - +private: // Rotate files: - // log.txt -> log.txt.1 - // log.txt.1 -> log.txt.2 - // log.txt.2 -> log.txt.3 - // lo3.txt.3 -> delete - + // log.txt -> log.1.txt + // log.1.txt -> log.2.txt + // log.2.txt -> log.3.txt + // log.3.txt -> delete void _rotate() { using details::os::filename_to_str; @@ -150,27 +157,31 @@ typedef rotating_file_sinkrotating_file_sink_st; */ struct default_daily_file_name_calculator { - // Create filename for the form basename.YYYY-MM-DD_hh-mm - static filename_t calc_filename(const filename_t& basename) + // Create filename for the form filename.YYYY-MM-DD_hh-mm.ext + static filename_t calc_filename(const filename_t& filename) { std::tm tm = spdlog::details::os::localtime(); + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; - w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}_{:02d}-{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min); + w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}_{:02d}-{:02d}{}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, ext); return w.str(); } }; /* - * Generator of daily log file names in format basename.YYYY-MM-DD + * Generator of daily log file names in format basename.YYYY-MM-DD.ext */ struct dateonly_daily_file_name_calculator { // Create filename for the form basename.YYYY-MM-DD - static filename_t calc_filename(const filename_t& basename) + static filename_t calc_filename(const filename_t& filename) { std::tm tm = spdlog::details::os::localtime(); + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; - w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, ext); return w.str(); } }; diff --git a/tests/file_helper.cpp b/tests/file_helper.cpp index 9a4ad603..f3b90d28 100644 --- a/tests/file_helper.cpp +++ b/tests/file_helper.cpp @@ -73,6 +73,77 @@ TEST_CASE("file_helper_reopen2", "[file_helper::reopen(false)]]") REQUIRE(helper.size() == expected_size); } +TEST_CASE("file_helper_split_by_extenstion", "[file_helper::split_by_extenstion()]]") +{ + std::string basename, ext; + std::tie(basename, ext) = file_helper::split_by_extenstion("mylog.txt"); + REQUIRE(basename == "mylog"); + REQUIRE(ext == ".txt"); +} + +TEST_CASE("file_helper_split_by_extenstion2", "[file_helper::split_by_extenstion()]]") +{ + std::string basename, ext; + std::tie(basename, ext) = file_helper::split_by_extenstion("mylog"); + REQUIRE(basename == "mylog"); + REQUIRE(ext == ""); +} + +TEST_CASE("file_helper_split_by_extenstion3", "[file_helper::split_by_extenstion()]]") +{ + std::string basename, ext; + std::tie(basename, ext) = file_helper::split_by_extenstion("mylog.xyz.txt"); + REQUIRE(basename == "mylog.xyz"); + REQUIRE(ext == ".txt"); +} + + +TEST_CASE("file_helper_split_by_extenstion4", "[file_helper::split_by_extenstion()]]") +{ + std::string basename, ext; + std::tie(basename, ext) = file_helper::split_by_extenstion("mylog.xyz....txt"); + REQUIRE(basename == "mylog.xyz..."); + REQUIRE(ext == ".txt"); +} + +TEST_CASE("file_helper_split_by_extenstion5", "[file_helper::split_by_extenstion(hidden_file)]]") +{ + std::string basename, ext; + std::tie(basename, ext) = file_helper::split_by_extenstion(".mylog"); + REQUIRE(basename == ".mylog"); + REQUIRE(ext == ""); +} + +TEST_CASE("file_helper_split_by_extenstion6", "[file_helper::split_by_extenstion(hidden_file)]]") +{ +#ifdef _WIN32 + auto filename = "folder\\.mylog"; + auto expected_basename = "folder\\.mylog"; +#else + auto filename = "folder/.mylog"; + auto expected_basename = "folder/.mylog"; +#endif + std::string basename, ext; + std::tie(basename, ext) = file_helper::split_by_extenstion(filename); + REQUIRE(basename == expected_basename); + REQUIRE(ext == ""); +} + +TEST_CASE("file_helper_split_by_extenstion7", "[file_helper::split_by_extenstion(hidden_file)]]") +{ +#ifdef _WIN32 + auto filename = "folder\\.mylog.txt"; + auto expected_basename = "folder\\.mylog"; +#else + auto filename = "folder/.mylog.txt"; + auto expected_basename = "folder//.mylog"; +#endif + std::string basename, ext; + std::tie(basename, ext) = file_helper::split_by_extenstion(filename); + REQUIRE(basename == expected_basename); + REQUIRE(ext == ".txt"); +} + diff --git a/tests/file_log.cpp b/tests/file_log.cpp index ff29a898..4580f8c3 100644 --- a/tests/file_log.cpp +++ b/tests/file_log.cpp @@ -188,3 +188,55 @@ TEST_CASE("daily_logger with custom calculator", "[daily_logger_custom]]") REQUIRE(count_lines(filename) == 10); } + +/* + * File name calculations + */ + +TEST_CASE("rotating_file_sink::calc_filename1", "[rotating_file_sink]]") +{ + auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename("rotated.txt", 3); + REQUIRE(filename == "rotated.3.txt"); +} + +TEST_CASE("rotating_file_sink::calc_filename2", "[rotating_file_sink]]") +{ + auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename("rotated", 3); + REQUIRE(filename == "rotated.3"); +} + +TEST_CASE("rotating_file_sink::calc_filename3", "[rotating_file_sink]]") +{ + auto filename = spdlog::sinks::rotating_file_sink_st::calc_filename("rotated.txt", 0); + REQUIRE(filename == "rotated.txt"); +} + + +TEST_CASE("daily_file_sink::default_daily_file_name_calculator1", "[daily_file_sink]]") +{ + // daily.YYYY-MM-DD_hh-mm.txt + auto filename = spdlog::sinks::default_daily_file_name_calculator::calc_filename("daily.txt"); + std::regex re(R"(^daily_(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])_\d\d-[0-5][0-9].txt$)"); + std::smatch match; + REQUIRE(std::regex_match(filename, match, re)); +} + +TEST_CASE("daily_file_sink::default_daily_file_name_calculator2", "[daily_file_sink]]") +{ + // daily.YYYY-MM-DD_hh-mm.txt + auto filename = spdlog::sinks::default_daily_file_name_calculator::calc_filename("daily"); + std::regex re(R"(^daily_(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])_\d\d-[0-5][0-9]$)"); + std::smatch match; + REQUIRE(std::regex_match(filename, match, re)); +} + +TEST_CASE("daily_file_sink::dateonly_daily_file_name_calculator", "[daily_file_sink]]") +{ + // daily.YYYY-MM-DD_hh-mm.txt + auto filename = spdlog::sinks::dateonly_daily_file_name_calculator::calc_filename("daily.txt"); + // date regex based on https://www.regular-expressions.info/dates.html + std::regex re(R"(^daily_(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])\.txt$)"); + std::smatch match; + REQUIRE(std::regex_match(filename, match, re)); +} + diff --git a/tests/includes.h b/tests/includes.h index 637e6bc2..6ca780e7 100644 --- a/tests/includes.h +++ b/tests/includes.h @@ -6,7 +6,7 @@ #include #include #include - +#include #include "catch.hpp" #include "utils.h"