Add std::tm formatter, fix spdlog::stopwatch formatter, conditionally use fmt::runtime in test_errors
This commit is contained in:
		@@ -4,11 +4,93 @@
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#if defined(SPDLOG_USE_STD_FORMAT)
 | 
			
		||||
// Add a formatter for std::tm, since std::format only supports std::chrono
 | 
			
		||||
// taken from fmtlib
 | 
			
		||||
#include <cassert>
 | 
			
		||||
#include <ctime>
 | 
			
		||||
#include <format>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <string_view>
 | 
			
		||||
#include <type_traits>
 | 
			
		||||
#include <spdlog/common.h>
 | 
			
		||||
 | 
			
		||||
namespace spdlog::details
 | 
			
		||||
{
 | 
			
		||||
    inline size_t strftime(char *str, size_t count, const char *format, const std::tm *time)
 | 
			
		||||
    {
 | 
			
		||||
        // Assign to a pointer to suppress GCCs -Wformat-nonliteral
 | 
			
		||||
        // First assign the nullptr to suppress -Wsuggest-attribute=format
 | 
			
		||||
        std::size_t (*strftime)(char*, std::size_t, const char*, const std::tm*) = nullptr;
 | 
			
		||||
        strftime = std::strftime;
 | 
			
		||||
        return strftime(str, count, format, time);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    inline size_t strftime(wchar_t *str, size_t count, const wchar_t *format, const std::tm *time)
 | 
			
		||||
    {
 | 
			
		||||
        // See above
 | 
			
		||||
        std::size_t (*wcsftime)(wchar_t*, std::size_t, const wchar_t*, const std::tm*) = nullptr;
 | 
			
		||||
        wcsftime = std::wcsftime;
 | 
			
		||||
        return wcsftime(str, count, format, time);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Casts a nonnegative integer to unsigned.
 | 
			
		||||
    template <typename Int>
 | 
			
		||||
    SPDLOG_CONSTEXPR auto to_unsigned(Int value) -> typename std::make_unsigned<Int>::type
 | 
			
		||||
    {
 | 
			
		||||
        assert(value >= 0, "negative value");
 | 
			
		||||
        return static_cast<typename std::make_unsigned<Int>::type>(value);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
template <typename Char>
 | 
			
		||||
struct std::formatter<std::tm, Char>
 | 
			
		||||
{
 | 
			
		||||
    template <typename ParseContext>
 | 
			
		||||
    SPDLOG_CONSTEXPR_FUNC auto parse(ParseContext &ctx) -> decltype(ctx.begin())
 | 
			
		||||
    {
 | 
			
		||||
        auto it = ctx.begin();
 | 
			
		||||
        if (it != ctx.end() && *it == ':') ++it;
 | 
			
		||||
        auto end = it;
 | 
			
		||||
        while (end != ctx.end() && *end != '}') ++end;
 | 
			
		||||
        specs = {it, spdlog::details::to_unsigned(end - it)};
 | 
			
		||||
        return end;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    template <typename FormatContext>
 | 
			
		||||
    auto format(const std::tm &tm, FormatContext &ctx) const -> decltype(ctx.out()) {
 | 
			
		||||
        basic_string<Char> tm_format;
 | 
			
		||||
        tm_format.append(specs);
 | 
			
		||||
        // By appending an extra space we can distinguish an empty result that
 | 
			
		||||
        // indicates insufficient buffer size from a guaranteed non-empty result
 | 
			
		||||
        // https://github.com/fmtlib/fmt/issues/2238
 | 
			
		||||
        tm_format.push_back(' ');
 | 
			
		||||
 | 
			
		||||
        const size_t MIN_SIZE = 10;
 | 
			
		||||
        basic_string<Char> buf;
 | 
			
		||||
        buf.resize(MIN_SIZE);
 | 
			
		||||
        for (;;)
 | 
			
		||||
        {
 | 
			
		||||
            size_t count = spdlog::details::strftime(buf.data(), buf.size(), tm_format.c_str(), &tm);
 | 
			
		||||
            if (count != 0)
 | 
			
		||||
            {
 | 
			
		||||
                buf.resize(count);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            buf.resize(buf.size() * 2);
 | 
			
		||||
        }
 | 
			
		||||
        // Remove the extra space.
 | 
			
		||||
        return std::copy(buf.begin(), buf.end() - 1, ctx.out());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    basic_string_view<Char> specs;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
//
 | 
			
		||||
// include bundled or external copy of fmtlib's chrono support
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#if !defined(SPDLOG_USE_STD_FORMAT)
 | 
			
		||||
#    if !defined(SPDLOG_FMT_EXTERNAL)
 | 
			
		||||
#        ifdef SPDLOG_HEADER_ONLY
 | 
			
		||||
#            ifndef FMT_HEADER_ONLY
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,14 @@ public:
 | 
			
		||||
} // namespace spdlog
 | 
			
		||||
 | 
			
		||||
// Support for fmt formatting  (e.g. "{:012.9}" or just "{}")
 | 
			
		||||
namespace fmt {
 | 
			
		||||
namespace
 | 
			
		||||
#ifdef SPDLOG_USE_STD_FORMAT
 | 
			
		||||
    std
 | 
			
		||||
#else
 | 
			
		||||
    fmt
 | 
			
		||||
#endif
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
template<>
 | 
			
		||||
struct formatter<spdlog::stopwatch> : formatter<double>
 | 
			
		||||
{
 | 
			
		||||
@@ -58,4 +65,4 @@ struct formatter<spdlog::stopwatch> : formatter<double>
 | 
			
		||||
        return formatter<double>::format(sw.elapsed().count(), ctx);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
} // namespace fmt
 | 
			
		||||
} // namespace fmt/std
 | 
			
		||||
 
 | 
			
		||||
@@ -29,7 +29,11 @@ TEST_CASE("default_error_handler", "[errors]]")
 | 
			
		||||
 | 
			
		||||
    auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("test-error", filename, true);
 | 
			
		||||
    logger->set_pattern("%v");
 | 
			
		||||
#ifdef SPDLOG_USE_STD_FORMAT
 | 
			
		||||
    logger->info("Test message {} {}", 1);
 | 
			
		||||
#else
 | 
			
		||||
    logger->info(fmt::runtime("Test message {} {}"), 1);
 | 
			
		||||
#endif
 | 
			
		||||
    logger->info("Test message {}", 2);
 | 
			
		||||
    logger->flush();
 | 
			
		||||
 | 
			
		||||
@@ -49,7 +53,11 @@ TEST_CASE("custom_error_handler", "[errors]]")
 | 
			
		||||
    logger->set_error_handler([=](const std::string &) { throw custom_ex(); });
 | 
			
		||||
    logger->info("Good message #1");
 | 
			
		||||
 | 
			
		||||
#ifdef SPDLOG_USE_STD_FORMAT
 | 
			
		||||
    REQUIRE_THROWS_AS(logger->info("Bad format msg {} {}", "xxx"), custom_ex);
 | 
			
		||||
#else
 | 
			
		||||
    REQUIRE_THROWS_AS(logger->info(fmt::runtime("Bad format msg {} {}"), "xxx"), custom_ex);
 | 
			
		||||
#endif
 | 
			
		||||
    logger->info("Good message #2");
 | 
			
		||||
    require_message_count(SIMPLE_LOG, 2);
 | 
			
		||||
}
 | 
			
		||||
@@ -88,7 +96,11 @@ TEST_CASE("async_error_handler", "[errors]]")
 | 
			
		||||
            ofs << err_msg;
 | 
			
		||||
        });
 | 
			
		||||
        logger->info("Good message #1");
 | 
			
		||||
#ifdef SPDLOG_USE_STD_FORMAT
 | 
			
		||||
        logger->info("Bad format msg {} {}", "xxx");
 | 
			
		||||
#else
 | 
			
		||||
        logger->info(fmt::runtime("Bad format msg {} {}"), "xxx");
 | 
			
		||||
#endif
 | 
			
		||||
        logger->info("Good message #2");
 | 
			
		||||
        spdlog::drop("logger"); // force logger to drain the queue and shutdown
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user