Handle file extensions in rotating and daily loggers
This commit is contained in:
		@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <thread>
 | 
			
		||||
#include <tuple>
 | 
			
		||||
#include <cerrno>
 | 
			
		||||
 | 
			
		||||
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<filename_t, filename_t> 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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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<std::is_same<filename_t::value_type, char>::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
 | 
			
		||||
@@ -95,23 +112,13 @@ protected:
 | 
			
		||||
        _file_helper.flush();
 | 
			
		||||
    }
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
private:	
 | 
			
		||||
    static filename_t calc_filename(const filename_t& filename, std::size_t index)
 | 
			
		||||
    {
 | 
			
		||||
        std::conditional<std::is_same<filename_t::value_type, char>::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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 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_sink<details::null_mutex>rotating_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<std::is_same<filename_t::value_type, char>::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<std::is_same<filename_t::value_type, char>::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();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -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");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@
 | 
			
		||||
#include <ostream>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
#include <exception>
 | 
			
		||||
 | 
			
		||||
#include <regex>
 | 
			
		||||
#include "catch.hpp"
 | 
			
		||||
#include "utils.h"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user