From f6901606f5a36047036072d0c6c58f0c9d90bb9f Mon Sep 17 00:00:00 2001 From: Charless Milette Date: Mon, 15 Nov 2021 14:57:13 -0500 Subject: [PATCH] Add std::tm formatter, fix spdlog::stopwatch formatter, conditionally use fmt::runtime in test_errors --- include/spdlog/fmt/chrono.h | 86 ++++++++++++++++++++++++++++++++++++- include/spdlog/stopwatch.h | 11 ++++- tests/test_errors.cpp | 12 ++++++ 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/include/spdlog/fmt/chrono.h b/include/spdlog/fmt/chrono.h index 83fad2ff..17013bc1 100644 --- a/include/spdlog/fmt/chrono.h +++ b/include/spdlog/fmt/chrono.h @@ -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 +#include +#include +#include +#include +#include +#include + +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 + SPDLOG_CONSTEXPR auto to_unsigned(Int value) -> typename std::make_unsigned::type + { + assert(value >= 0, "negative value"); + return static_cast::type>(value); + } +} + +template +struct std::formatter +{ + template + 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 + auto format(const std::tm &tm, FormatContext &ctx) const -> decltype(ctx.out()) { + basic_string 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 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 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 diff --git a/include/spdlog/stopwatch.h b/include/spdlog/stopwatch.h index bb976b19..52b4b6ad 100644 --- a/include/spdlog/stopwatch.h +++ b/include/spdlog/stopwatch.h @@ -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 : formatter { @@ -58,4 +65,4 @@ struct formatter : formatter return formatter::format(sw.elapsed().count(), ctx); } }; -} // namespace fmt +} // namespace fmt/std diff --git a/tests/test_errors.cpp b/tests/test_errors.cpp index 41441d0d..c7aef812 100644 --- a/tests/test_errors.cpp +++ b/tests/test_errors.cpp @@ -29,7 +29,11 @@ TEST_CASE("default_error_handler", "[errors]]") auto logger = spdlog::create("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 }