diff --git a/CMakeLists.txt b/CMakeLists.txt index f8941df3..7b792cab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,7 @@ option(SPDLOG_BUILD_EXAMPLE_HO "Build header only example" OFF) # testing options option(SPDLOG_BUILD_TESTS "Build tests" OFF) option(SPDLOG_BUILD_TESTS_HO "Build tests using the header only version" OFF) +option(SPDLOG_BUILD_FAILING_TESTS "Add unit test targets that test for compilation failures" OFF) # bench options option(SPDLOG_BUILD_BENCH "Build benchmarks (Requires https://github.com/google/benchmark.git to be installed)" OFF) diff --git a/include/spdlog/logger.h b/include/spdlog/logger.h index 67f808ba..ed44f526 100644 --- a/include/spdlog/logger.h +++ b/include/spdlog/logger.h @@ -73,78 +73,74 @@ public: void swap(spdlog::logger &other) SPDLOG_NOEXCEPT; - template - void log(source_loc loc, level::level_enum lvl, string_view_t fmt, const Args &... args) + // FormatString is a type derived from fmt::compile_string + template::value, int>::type = 0, + typename... Args> + void log(source_loc loc, level::level_enum lvl, FormatString &&fmt, Args &&... args) { - bool log_enabled = should_log(lvl); - bool traceback_enabled = tracer_.enabled(); - if (!log_enabled && !traceback_enabled) - { - return; - } - SPDLOG_TRY - { - memory_buf_t buf; - fmt::format_to(buf, fmt, args...); - details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); - log_it_(log_msg, log_enabled, traceback_enabled); - } - SPDLOG_LOGGER_CATCH() + log_(loc, lvl, std::forward(fmt), std::forward(args)...); } + // FormatString is NOT a type derived from fmt::compile_string but is a string_view_t or can be implicitly converted to one template - void log(level::level_enum lvl, string_view_t fmt, const Args &... args) + void log(source_loc loc, level::level_enum lvl, string_view_t fmt, Args &&... args) { - log(source_loc{}, lvl, fmt, args...); + log_(loc, lvl, fmt, std::forward(args)...); } - template - void trace(string_view_t fmt, const Args &... args) + template + void log(level::level_enum lvl, FormatString &&fmt, Args &&... args) { - log(level::trace, fmt, args...); + log(source_loc{}, lvl, std::forward(fmt), std::forward(args)...); } - template - void debug(string_view_t fmt, const Args &... args) + template + void trace(FormatString &&fmt, Args &&... args) { - log(level::debug, fmt, args...); + log(level::trace, std::forward(fmt), std::forward(args)...); } - template - void info(string_view_t fmt, const Args &... args) + template + void debug(FormatString &&fmt, Args &&... args) { - log(level::info, fmt, args...); + log(level::debug, std::forward(fmt), std::forward(args)...); } - template - void warn(string_view_t fmt, const Args &... args) + template + void info(FormatString &&fmt, Args &&... args) { - log(level::warn, fmt, args...); + log(level::info, std::forward(fmt), std::forward(args)...); } - template - void error(string_view_t fmt, const Args &... args) + template + void warn(FormatString &&fmt, Args &&... args) { - log(level::err, fmt, args...); + log(level::warn, std::forward(fmt), std::forward(args)...); } - template - void critical(string_view_t fmt, const Args &... args) + template + void error(FormatString &&fmt, Args &&... args) { - log(level::critical, fmt, args...); + log(level::err, std::forward(fmt), std::forward(args)...); + } + + template + void critical(FormatString &&fmt, Args &&... args) + { + log(level::critical, std::forward(fmt), std::forward(args)...); } template - void log(level::level_enum lvl, const T &msg) + void log(level::level_enum lvl, T &&msg) { - log(source_loc{}, lvl, msg); + log(source_loc{}, lvl, std::forward(msg)); } // T can be statically converted to string_view - template::value, T>::type * = nullptr> - void log(source_loc loc, level::level_enum lvl, const T &msg) + template::value, T>::type * = nullptr> + void log(source_loc loc, level::level_enum lvl, T &&msg) { - log(loc, lvl, string_view_t{msg}); + log(loc, lvl, string_view_t{std::forward(msg)}); } void log(log_clock::time_point log_time, source_loc loc, level::level_enum lvl, string_view_t msg) @@ -179,48 +175,48 @@ public: } // T cannot be statically converted to string_view or wstring_view - template::value && - !is_convertible_to_wstring_view::value, - T>::type * = nullptr> - void log(source_loc loc, level::level_enum lvl, const T &msg) + template::value && !is_convertible_to_wstring_view::value, + T>::type * = nullptr> + void log(source_loc loc, level::level_enum lvl, T &&msg) { - log(loc, lvl, "{}", msg); + log(loc, lvl, "{}", std::forward(msg)); } template - void trace(const T &msg) + void trace(T &&msg) { - log(level::trace, msg); + log(level::trace, std::forward(msg)); } template - void debug(const T &msg) + void debug(T &&msg) { - log(level::debug, msg); + log(level::debug, std::forward(msg)); } template - void info(const T &msg) + void info(T &&msg) { - log(level::info, msg); + log(level::info, std::forward(msg)); } template - void warn(const T &msg) + void warn(T &&msg) { - log(level::warn, msg); + log(level::warn, std::forward(msg)); } template - void error(const T &msg) + void error(T &&msg) { - log(level::err, msg); + log(level::err, std::forward(msg)); } template - void critical(const T &msg) + void critical(T &&msg) { - log(level::critical, msg); + log(level::critical, std::forward(msg)); } #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT @@ -229,7 +225,7 @@ public: #else template - void log(source_loc loc, level::level_enum lvl, wstring_view_t fmt, const Args &... args) + void log(source_loc loc, level::level_enum lvl, wstring_view_t fmt, Args &&... args) { bool log_enabled = should_log(lvl); bool traceback_enabled = tracer_.enabled(); @@ -241,7 +237,7 @@ public: { // format to wmemory_buffer and convert to utf8 fmt::wmemory_buffer wbuf; - fmt::format_to(wbuf, fmt, args...); + fmt::format_to(wbuf, fmt, std::forward(args)...); memory_buf_t buf; details::os::wstr_to_utf8buf(wstring_view_t(wbuf.data(), wbuf.size()), buf); @@ -251,51 +247,9 @@ public: SPDLOG_LOGGER_CATCH() } - template - void log(level::level_enum lvl, wstring_view_t fmt, const Args &... args) - { - log(source_loc{}, lvl, fmt, args...); - } - - template - void trace(wstring_view_t fmt, const Args &... args) - { - log(level::trace, fmt, args...); - } - - template - void debug(wstring_view_t fmt, const Args &... args) - { - log(level::debug, fmt, args...); - } - - template - void info(wstring_view_t fmt, const Args &... args) - { - log(level::info, fmt, args...); - } - - template - void warn(wstring_view_t fmt, const Args &... args) - { - log(level::warn, fmt, args...); - } - - template - void error(wstring_view_t fmt, const Args &... args) - { - log(level::err, fmt, args...); - } - - template - void critical(wstring_view_t fmt, const Args &... args) - { - log(level::critical, fmt, args...); - } - // T can be statically converted to wstring_view - template::value, T>::type * = nullptr> - void log(source_loc loc, level::level_enum lvl, const T &msg) + template::value, T>::type * = nullptr> + void log(source_loc loc, level::level_enum lvl, T &&msg) { bool log_enabled = should_log(lvl); bool traceback_enabled = tracer_.enabled(); @@ -307,7 +261,7 @@ public: SPDLOG_TRY { memory_buf_t buf; - details::os::wstr_to_utf8buf(msg, buf); + details::os::wstr_to_utf8buf(std::forward(msg), buf); details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); log_it_(log_msg, log_enabled, traceback_enabled); } @@ -370,6 +324,26 @@ protected: err_handler custom_err_handler_{nullptr}; details::backtracer tracer_; + // common implementation for after templated public api has been resolved + template + void log_(source_loc loc, level::level_enum lvl, FormatString &&fmt, Args &&... args) + { + bool log_enabled = should_log(lvl); + bool traceback_enabled = tracer_.enabled(); + if (!log_enabled && !traceback_enabled) + { + return; + } + SPDLOG_TRY + { + memory_buf_t buf; + fmt::format_to(buf, std::forward(fmt), std::forward(args)...); + details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); + log_it_(log_msg, log_enabled, traceback_enabled); + } + SPDLOG_LOGGER_CATCH() + } + // log the given message (if the given log level is high enough), // and save backtrace (if backtrace is enabled). void log_it_(const details::log_msg &log_msg, bool log_enabled, bool traceback_enabled); diff --git a/include/spdlog/spdlog.h b/include/spdlog/spdlog.h index 55de6676..1407e7c0 100644 --- a/include/spdlog/spdlog.h +++ b/include/spdlog/spdlog.h @@ -121,153 +121,102 @@ SPDLOG_API spdlog::logger *default_logger_raw(); SPDLOG_API void set_default_logger(std::shared_ptr default_logger); -template -inline void log(source_loc source, level::level_enum lvl, string_view_t fmt, const Args &... args) +template +inline void log(source_loc source, level::level_enum lvl, FormatString &&fmt, Args &&... args) { - default_logger_raw()->log(source, lvl, fmt, args...); + default_logger_raw()->log(source, lvl, std::forward(fmt), std::forward(args)...); } -template -inline void log(level::level_enum lvl, string_view_t fmt, const Args &... args) +template +inline void log(level::level_enum lvl, FormatString &&fmt, Args &&... args) { - default_logger_raw()->log(source_loc{}, lvl, fmt, args...); + default_logger_raw()->log(source_loc{}, lvl, std::forward(fmt), std::forward(args)...); } -template -inline void trace(string_view_t fmt, const Args &... args) +template +inline void trace(FormatString &&fmt, Args &&... args) { - default_logger_raw()->trace(fmt, args...); + default_logger_raw()->trace(std::forward(fmt), std::forward(args)...); } -template -inline void debug(string_view_t fmt, const Args &... args) +template +inline void debug(FormatString &&fmt, Args &&... args) { - default_logger_raw()->debug(fmt, args...); + default_logger_raw()->debug(std::forward(fmt), std::forward(args)...); } -template -inline void info(string_view_t fmt, const Args &... args) +template +inline void info(FormatString &&fmt, Args &&... args) { - default_logger_raw()->info(fmt, args...); + default_logger_raw()->info(std::forward(fmt), std::forward(args)...); } -template -inline void warn(string_view_t fmt, const Args &... args) +template +inline void warn(FormatString &&fmt, Args &&... args) { - default_logger_raw()->warn(fmt, args...); + default_logger_raw()->warn(std::forward(fmt), std::forward(args)...); } -template -inline void error(string_view_t fmt, const Args &... args) +template +inline void error(FormatString &&fmt, Args &&... args) { - default_logger_raw()->error(fmt, args...); + default_logger_raw()->error(std::forward(fmt), std::forward(args)...); } -template -inline void critical(string_view_t fmt, const Args &... args) +template +inline void critical(FormatString &&fmt, const Args &... args) { - default_logger_raw()->critical(fmt, args...); + default_logger_raw()->critical(std::forward(fmt), args...); } template -inline void log(source_loc source, level::level_enum lvl, const T &msg) +inline void log(source_loc source, level::level_enum lvl, T &&msg) { - default_logger_raw()->log(source, lvl, msg); + default_logger_raw()->log(source, lvl, std::forward(msg)); } template -inline void log(level::level_enum lvl, const T &msg) +inline void log(level::level_enum lvl, T &&msg) { - default_logger_raw()->log(lvl, msg); + default_logger_raw()->log(lvl, std::forward(msg)); } template -inline void trace(const T &msg) +inline void trace(T &&msg) { - default_logger_raw()->trace(msg); + default_logger_raw()->trace(std::forward(msg)); } template -inline void debug(const T &msg) +inline void debug(T &&msg) { - default_logger_raw()->debug(msg); + default_logger_raw()->debug(std::forward(msg)); } template -inline void info(const T &msg) +inline void info(T &&msg) { - default_logger_raw()->info(msg); + default_logger_raw()->info(std::forward(msg)); } template -inline void warn(const T &msg) +inline void warn(T &&msg) { - default_logger_raw()->warn(msg); + default_logger_raw()->warn(std::forward(msg)); } template -inline void error(const T &msg) +inline void error(T &&msg) { - default_logger_raw()->error(msg); + default_logger_raw()->error(std::forward(msg)); } template -inline void critical(const T &msg) +inline void critical(T &&msg) { - default_logger_raw()->critical(msg); + default_logger_raw()->critical(std::forward(msg)); } -#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT -template -inline void log(source_loc source, level::level_enum lvl, wstring_view_t fmt, const Args &... args) -{ - default_logger_raw()->log(source, lvl, fmt, args...); -} - -template -inline void log(level::level_enum lvl, wstring_view_t fmt, const Args &... args) -{ - default_logger_raw()->log(lvl, fmt, args...); -} - -template -inline void trace(wstring_view_t fmt, const Args &... args) -{ - default_logger_raw()->trace(fmt, args...); -} - -template -inline void debug(wstring_view_t fmt, const Args &... args) -{ - default_logger_raw()->debug(fmt, args...); -} - -template -inline void info(wstring_view_t fmt, const Args &... args) -{ - default_logger_raw()->info(fmt, args...); -} - -template -inline void warn(wstring_view_t fmt, const Args &... args) -{ - default_logger_raw()->warn(fmt, args...); -} - -template -inline void error(wstring_view_t fmt, const Args &... args) -{ - default_logger_raw()->error(fmt, args...); -} - -template -inline void critical(wstring_view_t fmt, const Args &... args) -{ - default_logger_raw()->critical(fmt, args...); -} - -#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT - } // namespace spdlog // diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2dd4958b..6cb0c44d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -68,3 +68,29 @@ endif() if(SPDLOG_BUILD_TESTS_HO OR SPDLOG_BUILD_ALL) spdlog_prepare_test(spdlog-utests-ho spdlog::spdlog_header_only) endif() + +# Set up compilation failure test case (only available if compiler supports relaxed constexpr and c++ standard is > 11) +set(HAVE_CXX_RELAXED_CONSTEXPR) +if(CMAKE_CXX_STANDARD GREATER 11) # If we're in c++11 mode we don't have relaxed constexpr, even if our compiler is new enough. + list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_relaxed_constexpr" HAVE_CXX_RELAXED_CONSTEXPR) +endif() +if(HAVE_CXX_RELAXED_CONSTEXPR AND (SPDLOG_BUILD_FAILING_TESTS OR SPDLOG_BUILD_ALL)) + message(STATUS "Enabling negative compilation unit test target") + set(SPDLOG_FAIL_COMPILATION_TARGET spdlog_fail_compilation_utests) + add_executable(${SPDLOG_FAIL_COMPILATION_TARGET} + test_compilation_failures.cpp + main.cpp) + spdlog_enable_warnings(${SPDLOG_FAIL_COMPILATION_TARGET}) + target_link_libraries(${SPDLOG_FAIL_COMPILATION_TARGET} PRIVATE spdlog::spdlog) + set_target_properties(${SPDLOG_FAIL_COMPILATION_TARGET} PROPERTIES + EXCLUDE_FROM_ALL TRUE + EXCLUDE_FROM_DEFAULT_BUILD TRUE) + add_test(NAME ${SPDLOG_FAIL_COMPILATION_TARGET} + COMMAND ${CMAKE_COMMAND} --build . --target ${SPDLOG_FAIL_COMPILATION_TARGET} --config $ + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + set_tests_properties(${SPDLOG_FAIL_COMPILATION_TARGET} PROPERTIES PASS_REGULAR_EXPRESSION "invalid type specifier") + + add_custom_target(${SPDLOG_FAIL_COMPILATION_TARGET}_run_target + COMMAND ${CMAKE_CTEST_COMMAND} -R ${SPDLOG_FAIL_COMPILATION_TARGET} --output-on-failure + COMMENT "Running tests that fail to compile.") +endif() diff --git a/tests/test_compilation_failures.cpp b/tests/test_compilation_failures.cpp new file mode 100644 index 00000000..640adbdb --- /dev/null +++ b/tests/test_compilation_failures.cpp @@ -0,0 +1,14 @@ +#include "includes.h" + +TEST_CASE("{fmt} FMT_STRING functionality preserved (negative test)", "[fmt][fail][fail compilation]") +{ + std::ostringstream oss; + auto oss_sink = std::make_shared(oss); + + spdlog::set_default_logger(std::make_shared("oss", oss_sink)); + spdlog::default_logger()->set_level(spdlog::level::trace); + + spdlog::info(FMT_STRING("The best part of {{fmt}} is the compile time checking: {:d}"), "I shouldn't compile"); + // This should never be able to compile, so running is a failure. + FAIL("This test case isn't meant to compile, let alone run."); +} diff --git a/tests/test_misc.cpp b/tests/test_misc.cpp index 1d67e879..cf43f6f4 100644 --- a/tests/test_misc.cpp +++ b/tests/test_misc.cpp @@ -3,7 +3,7 @@ #include "spdlog/fmt/bin_to_hex.h" template -std::string log_info(const T &what, spdlog::level::level_enum logger_level = spdlog::level::info) +std::string log_info(T &&what, spdlog::level::level_enum logger_level = spdlog::level::info) { std::ostringstream oss; @@ -12,7 +12,7 @@ std::string log_info(const T &what, spdlog::level::level_enum logger_level = spd spdlog::logger oss_logger("oss", oss_sink); oss_logger.set_level(logger_level); oss_logger.set_pattern("%v"); - oss_logger.info(what); + oss_logger.info(std::forward(what)); return oss.str().substr(0, oss.str().length() - strlen(spdlog::details::os::default_eol)); } @@ -269,3 +269,34 @@ TEST_CASE("default logger API", "[default logger]") spdlog::drop_all(); spdlog::set_pattern("%v"); } + +TEST_CASE("{fmt} FMT_STRING functionality preserved (positive test)", "[fmt]") +{ + std::ostringstream oss; + auto oss_sink = std::make_shared(oss); + + spdlog::set_default_logger(std::make_shared("oss", oss_sink)); + spdlog::default_logger()->set_level(spdlog::level::trace); + spdlog::set_pattern("%v"); + + const std::string expected_output_all( + std::string("The best part of {fmt} is the compile time checking: 42") + std::string(spdlog::details::os::default_eol)); + spdlog::trace(FMT_STRING("The best part of {{fmt}} is the compile time checking: {:d}"), 42); + REQUIRE(oss.str() == expected_output_all); + oss.str(""); + spdlog::debug(FMT_STRING("The best part of {{fmt}} is the compile time checking: {:d}"), 42); + REQUIRE(oss.str() == expected_output_all); + oss.str(""); + spdlog::info(FMT_STRING("The best part of {{fmt}} is the compile time checking: {:d}"), 42); + REQUIRE(oss.str() == expected_output_all); + oss.str(""); + spdlog::warn(FMT_STRING("The best part of {{fmt}} is the compile time checking: {:d}"), 42); + REQUIRE(oss.str() == expected_output_all); + oss.str(""); + spdlog::error(FMT_STRING("The best part of {{fmt}} is the compile time checking: {:d}"), 42); + REQUIRE(oss.str() == expected_output_all); + oss.str(""); + spdlog::critical(FMT_STRING("The best part of {{fmt}} is the compile time checking: {:d}"), 42); + REQUIRE(oss.str() == expected_output_all); + oss.str(""); +}