Skip to content

Commit

Permalink
Support C++20 std::format as an alternative to fmtlib
Browse files Browse the repository at this point in the history
  • Loading branch information
sylveon committed Nov 13, 2021
1 parent ff9313e commit 44a4517
Show file tree
Hide file tree
Showing 34 changed files with 442 additions and 140 deletions.
15 changes: 12 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ option(SPDLOG_BUILD_WARNINGS "Enable compiler warnings" OFF)

# install options
option(SPDLOG_INSTALL "Generate the install target" ${SPDLOG_MASTER_PROJECT})
option(SPDLOG_USE_STD_FORMAT "Use std::format instead of fmt library. No compile-time format string checking." OFF)
option(SPDLOG_FMT_EXTERNAL "Use external fmt library instead of bundled" OFF)
option(SPDLOG_FMT_EXTERNAL_HO "Use external fmt header-only library instead of bundled" OFF)
option(SPDLOG_NO_EXCEPTIONS "Compile with -fno-exceptions. Call abort() on any spdlog exceptions" OFF)
Expand All @@ -88,6 +89,14 @@ if(SPDLOG_FMT_EXTERNAL AND SPDLOG_FMT_EXTERNAL_HO)
message(FATAL_ERROR "SPDLOG_FMT_EXTERNAL and SPDLOG_FMT_EXTERNAL_HO are mutually exclusive")
endif()

if(SPDLOG_USE_STD_FORMAT AND SPDLOG_FMT_EXTERNAL_HO)
message(FATAL_ERROR "SPDLOG_USE_STD_FORMAT and SPDLOG_FMT_EXTERNAL_HO are mutually exclusive")
endif()

if(SPDLOG_USE_STD_FORMAT AND SPDLOG_FMT_EXTERNAL)
message(FATAL_ERROR "SPDLOG_USE_STD_FORMAT and SPDLOG_FMT_EXTERNAL are mutually exclusive")
endif()

# misc tweakme options
if(WIN32)
option(SPDLOG_WCHAR_SUPPORT "Support wchar api" OFF)
Expand Down Expand Up @@ -130,7 +139,7 @@ message(STATUS "Build type: " ${CMAKE_BUILD_TYPE})
# ---------------------------------------------------------------------------------------
set(SPDLOG_SRCS src/spdlog.cpp src/stdout_sinks.cpp src/color_sinks.cpp src/file_sinks.cpp src/async.cpp src/cfg.cpp)

if(NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO)
if(NOT SPDLOG_USE_STD_FORMAT AND NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO)
list(APPEND SPDLOG_SRCS src/fmt.cpp)
endif()

Expand All @@ -145,7 +154,7 @@ if(SPDLOG_BUILD_SHARED OR BUILD_SHARED_LIBS)
target_compile_options(spdlog PUBLIC $<$<AND:$<CXX_COMPILER_ID:MSVC>,$<NOT:$<COMPILE_LANGUAGE:CUDA>>>:/wd4251
/wd4275>)
endif()
if(NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO)
if(NOT SPDLOG_USE_STD_FORMAT AND NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO)
target_compile_definitions(spdlog PRIVATE FMT_EXPORT PUBLIC FMT_SHARED)
endif()
else()
Expand Down Expand Up @@ -280,7 +289,7 @@ if(SPDLOG_INSTALL)
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})

if(NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO)
if(NOT SPDLOG_USE_STD_FORMAT AND NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO)
install(DIRECTORY include/${PROJECT_NAME}/fmt/bundled/
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/fmt/bundled/")
endif()
Expand Down
19 changes: 18 additions & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,55 +8,72 @@ environment:
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
- GENERATOR: '"Visual Studio 14 2015"'
BUILD_TYPE: Release
BUILD_SHARED: 'OFF'
WCHAR: 'ON'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
- GENERATOR: '"Visual Studio 14 2015 Win64"'
BUILD_TYPE: Debug
BUILD_SHARED: 'OFF'
WCHAR: 'ON'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
- GENERATOR: '"Visual Studio 14 2015 Win64"'
BUILD_TYPE: Release
BUILD_SHARED: 'OFF'
WCHAR: 'ON'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
- GENERATOR: '"Visual Studio 15 2017 Win64"'
BUILD_TYPE: Debug
BUILD_SHARED: 'OFF'
WCHAR: 'ON'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
- GENERATOR: '"Visual Studio 15 2017 Win64"'
BUILD_TYPE: Release
BUILD_SHARED: 'OFF'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
- GENERATOR: '"Visual Studio 15 2017 Win64"'
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
- GENERATOR: '"Visual Studio 15 2017 Win64"'
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
WCHAR: 'ON'
WCHAR_FILES: 'ON'
BUILD_EXAMPLE: 'OFF'
USE_STD_FORMAT: 'OFF'
- GENERATOR: '"Visual Studio 16 2019" -A x64'
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'OFF'
USE_STD_FORMAT: 'OFF'
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
- GENERATOR: '"Visual Studio 16 2022" -A x64'
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'OFF'
USE_STD_FORMAT: 'ON'
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
build_script:
- cmd: >-
set
Expand All @@ -67,7 +84,7 @@ build_script:
set PATH=%PATH%;C:\Program Files\Git\usr\bin
cmake -G %GENERATOR% -D CMAKE_BUILD_TYPE=%BUILD_TYPE% -D BUILD_SHARED_LIBS=%BUILD_SHARED% -D SPDLOG_WCHAR_SUPPORT=%WCHAR% -D SPDLOG_WCHAR_FILENAMES=%WCHAR_FILES% -D SPDLOG_BUILD_EXAMPLE=%BUILD_EXAMPLE% -D SPDLOG_BUILD_EXAMPLE_HO=%BUILD_EXAMPLE% -D SPDLOG_BUILD_TESTS=ON -D SPDLOG_BUILD_TESTS_HO=OFF -D SPDLOG_BUILD_WARNINGS=ON ..
cmake -G %GENERATOR% -D CMAKE_BUILD_TYPE=%BUILD_TYPE% -D BUILD_SHARED_LIBS=%BUILD_SHARED% -D SPDLOG_WCHAR_SUPPORT=%WCHAR% -D SPDLOG_WCHAR_FILENAMES=%WCHAR_FILES% -D SPDLOG_BUILD_EXAMPLE=%BUILD_EXAMPLE% -D SPDLOG_BUILD_EXAMPLE_HO=%BUILD_EXAMPLE% -D SPDLOG_BUILD_TESTS=ON -D SPDLOG_BUILD_TESTS_HO=OFF -D SPDLOG_BUILD_WARNINGS=ON -D SPDLOG_USE_STD_FORMAT=%USE_STD_FORMAT% ..
cmake --build . --config %BUILD_TYPE%
Expand Down
4 changes: 3 additions & 1 deletion bench/async_bench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
#include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h"

#ifdef SPDLOG_FMT_EXTERNAL
#if defined(SPDLOG_USE_STD_FORMAT)
# include <format>
#elif defined(SPDLOG_FMT_EXTERNAL)
# include <fmt/format.h>
#else
# include "spdlog/fmt/bundled/format.h"
Expand Down
14 changes: 8 additions & 6 deletions bench/bench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
#include "spdlog/sinks/null_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"

#ifdef SPDLOG_FMT_EXTERNAL
#if defined(SPDLOG_USE_STD_FORMAT)
# include <format>
#elif defined(SPDLOG_FMT_EXTERNAL)
# include <fmt/locale.h>
#else
# include "spdlog/fmt/bundled/format.h"
Expand All @@ -38,7 +40,7 @@ static const int max_threads = 1000;
void bench_threaded_logging(size_t threads, int iters)
{
spdlog::info("**************************************************************");
spdlog::info(fmt::format(std::locale("en_US.UTF-8"), "Multi threaded: {:L} threads, {:L} messages", threads, iters));
spdlog::info(spdlog::fmt_lib::format(std::locale("en_US.UTF-8"), "Multi threaded: {:L} threads, {:L} messages", threads, iters));
spdlog::info("**************************************************************");

auto basic_mt = spdlog::basic_logger_mt("basic_mt", "logs/basic_mt.log", true);
Expand Down Expand Up @@ -74,7 +76,7 @@ void bench_threaded_logging(size_t threads, int iters)
void bench_single_threaded(int iters)
{
spdlog::info("**************************************************************");
spdlog::info(fmt::format(std::locale("en_US.UTF-8"), "Single threaded: {} messages", iters));
spdlog::info(spdlog::fmt_lib::format(std::locale("en_US.UTF-8"), "Single threaded: {} messages", iters));
spdlog::info("**************************************************************");

auto basic_st = spdlog::basic_logger_st("basic_st", "logs/basic_st.log", true);
Expand Down Expand Up @@ -128,7 +130,7 @@ int main(int argc, char *argv[])

if (threads > max_threads)
{
throw std::runtime_error(fmt::format("Number of threads exceeds maximum({})", max_threads));
throw std::runtime_error(spdlog::fmt_lib::format("Number of threads exceeds maximum({})", max_threads));
}

bench_single_threaded(iters);
Expand Down Expand Up @@ -159,7 +161,7 @@ void bench(int howmany, std::shared_ptr<spdlog::logger> log)
auto delta_d = duration_cast<duration<double>>(delta).count();

spdlog::info(
fmt::format(std::locale("en_US.UTF-8"), "{:<30} Elapsed: {:0.2f} secs {:>16L}/sec", log->name(), delta_d, int(howmany / delta_d)));
spdlog::fmt_lib::format(std::locale("en_US.UTF-8"), "{:<30} Elapsed: {:0.2f} secs {:>16L}/sec", log->name(), delta_d, int(howmany / delta_d)));
spdlog::drop(log->name());
}

Expand Down Expand Up @@ -190,7 +192,7 @@ void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, size_t thread_co
auto delta = high_resolution_clock::now() - start;
auto delta_d = duration_cast<duration<double>>(delta).count();
spdlog::info(
fmt::format(std::locale("en_US.UTF-8"), "{:<30} Elapsed: {:0.2f} secs {:>16L}/sec", log->name(), delta_d, int(howmany / delta_d)));
spdlog::fmt_lib::format(std::locale("en_US.UTF-8"), "{:<30} Elapsed: {:0.2f} secs {:>16L}/sec", log->name(), delta_d, int(howmany / delta_d)));
spdlog::drop(log->name());
}

Expand Down
4 changes: 4 additions & 0 deletions include/spdlog/common-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,13 @@ SPDLOG_INLINE spdlog_ex::spdlog_ex(std::string msg)

SPDLOG_INLINE spdlog_ex::spdlog_ex(const std::string &msg, int last_errno)
{
#ifdef SPDLOG_USE_STD_FORMAT
msg_ = std::system_error(std::error_code(last_errno, std::generic_category()), msg).what();
#else
memory_buf_t outbuf;
fmt::format_system_error(outbuf, last_errno, msg.c_str());
msg_ = fmt::to_string(outbuf);
#endif
}

SPDLOG_INLINE const char *spdlog_ex::what() const SPDLOG_NOEXCEPT
Expand Down
48 changes: 41 additions & 7 deletions include/spdlog/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
#include <functional>
#include <cstdio>

#ifdef SPDLOG_USE_STD_FORMAT
#include <string_view>
#endif

#ifdef SPDLOG_COMPILED_LIB
# undef SPDLOG_HEADER_ONLY
# if defined(_WIN32) && defined(SPDLOG_SHARED_LIB)
Expand All @@ -36,14 +40,15 @@

#include <spdlog/fmt/fmt.h>

// backward compatibility with fmt versions older than 8
#if FMT_VERSION >= 80000
# define SPDLOG_FMT_RUNTIME(format_string) fmt::runtime(format_string)
# if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT)
# include <spdlog/fmt/xchar.h>
#ifndef SPDLOG_USE_STD_FORMAT
# if FMT_VERSION >= 80000 // backward compatibility with fmt versions older than 8
# define SPDLOG_FMT_RUNTIME(format_string) fmt::runtime(format_string)
# if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT)
# include <spdlog/fmt/xchar.h>
# endif
# else
# define SPDLOG_FMT_RUNTIME(format_string) format_string
# endif
#else
# define SPDLOG_FMT_RUNTIME(format_string) format_string
#endif

// visual studio upto 2013 does not support noexcept nor constexpr
Expand Down Expand Up @@ -112,11 +117,39 @@ using log_clock = std::chrono::system_clock;
using sink_ptr = std::shared_ptr<sinks::sink>;
using sinks_init_list = std::initializer_list<sink_ptr>;
using err_handler = std::function<void(const std::string &err_msg)>;
#ifdef SPDLOG_USE_STD_FORMAT
namespace fmt_lib = std;

using string_view_t = std::string_view;
using wstring_view_t = std::wstring_view;
using memory_buf_t = std::string;
using wmemory_buf_t = std::wstring;

template<typename... Args>
using format_string_t = std::string_view;

template<typename... Args>
using wformat_string_t = std::wstring_view;

template<class T, class Char = char>
struct is_convertible_to_basic_format_string
: std::integral_constant<bool,
std::is_convertible<T, std::basic_string_view<Char>>::value>
{};
#else
namespace fmt_lib = fmt;

using string_view_t = fmt::basic_string_view<char>;
using wstring_view_t = fmt::basic_string_view<wchar_t>;
using memory_buf_t = fmt::basic_memory_buffer<char, 250>;
using wmemory_buf_t = fmt::basic_memory_buffer<wchar_t, 250>;

template<typename... Args>
using format_string_t = fmt::format_string<Args...>;

template<typename... Args>
using wformat_string_t = fmt::wformat_string<Args...>;

template<class T>
using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;

Expand All @@ -127,6 +160,7 @@ struct is_convertible_to_basic_format_string
: std::integral_constant<bool,
std::is_convertible<T, fmt::basic_string_view<Char>>::value || std::is_same<remove_cvref_t<T>, fmt::basic_runtime<Char>>::value>
{};
#endif

#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
# ifndef _WIN32
Expand Down
54 changes: 53 additions & 1 deletion include/spdlog/details/fmt_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
#include <spdlog/fmt/fmt.h>
#include <spdlog/common.h>

#ifdef SPDLOG_USE_STD_FORMAT
#include <charconv>
#include <limits>
#endif

// Some fmt helpers to efficiently format and pad ints and strings
namespace spdlog {
namespace details {
Expand All @@ -24,17 +29,63 @@ inline void append_string_view(spdlog::string_view_t view, memory_buf_t &dest)
dest.append(buf_ptr, buf_ptr + view.size());
}

#ifdef SPDLOG_USE_STD_FORMAT
template<typename T>
inline void append_int(T n, memory_buf_t &dest)
{
// Buffer should be large enough to hold all digits (digits10 + 1) and a sign
SPDLOG_CONSTEXPR auto BUF_SIZE = std::numeric_limits<T>::digits10 + 2;
char buf[BUF_SIZE];

auto [ptr, ec] = std::to_chars(buf, buf + BUF_SIZE, n, 10);
if (ec == std::errc())
{
dest.append(buf, ptr);
}
else
{
throw_spdlog_ex("Failed to format int", static_cast<int>(ec));
}
}
#else
template<typename T>
inline void append_int(T n, memory_buf_t &dest)
{
fmt::format_int i(n);
dest.append(i.data(), i.data() + i.size());
}
#endif

template<typename T>
SPDLOG_CONSTEXPR unsigned int count_digits_fallback(T n)
{
// taken from fmt.
unsigned int count = 1;
for (;;)
{
// Integer division is slow so do it for a group of four digits instead
// of for every digit. The idea comes from the talk by Alexandrescu
// "Three Optimization Tips for C++". See speed-test for a comparison.
if (n < 10)
return count;
if (n < 100)
return count + 1;
if (n < 1000)
return count + 2;
if (n < 10000)
return count + 3;
n /= 10000u;
count += 4;
}
}

template<typename T>
inline unsigned int count_digits(T n)
{
using count_type = typename std::conditional<(sizeof(T) > sizeof(uint32_t)), uint64_t, uint32_t>::type;
#ifdef SPDLOG_USE_STD_FORMAT
return count_digits_fallback(static_cast<count_type>(n));
#else
return static_cast<unsigned int>(fmt::
// fmt 7.0.0 renamed the internal namespace to detail.
// See: https://github.com/fmtlib/fmt/issues/1538
Expand All @@ -44,6 +95,7 @@ inline unsigned int count_digits(T n)
detail
#endif
::count_digits(static_cast<count_type>(n)));
#endif
}

inline void pad2(int n, memory_buf_t &dest)
Expand All @@ -55,7 +107,7 @@ inline void pad2(int n, memory_buf_t &dest)
}
else // unlikely, but just in case, let fmt deal with it
{
fmt::format_to(std::back_inserter(dest), SPDLOG_FMT_RUNTIME("{:02}"), n);
fmt_lib::format_to(std::back_inserter(dest), "{:02}", n);
}
}

Expand Down
Loading

0 comments on commit 44a4517

Please sign in to comment.