Merge branch 'gabime:v1.x' into v1.x

pull/2315/head
Muhammed Galib Uludag 4 years ago committed by GitHub
commit 60907bbabd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

9
.gitignore vendored

@ -1,4 +1,6 @@
# Auto generated files # Auto generated files
[Dd]ebug/
[Rr]elease/
build/* build/*
*.slo *.slo
*.lo *.lo
@ -55,6 +57,7 @@ example/*
# generated files # generated files
generated generated
version.rc
# Cmake # Cmake
CMakeCache.txt CMakeCache.txt
@ -67,6 +70,8 @@ install_manifest.txt
/tests/tests.VC.db /tests/tests.VC.db
/tests/tests /tests/tests
/tests/logs/* /tests/logs/*
spdlogConfig.cmake
spdlogConfigVersion.cmake
# idea # idea
.idea/ .idea/
@ -81,3 +86,7 @@ cmake-build-*/
*.tcl *.tcl
*.user *.user
*.sln *.sln
# macos
*.DS_store
*.xcodeproj/

@ -5,11 +5,11 @@
sudo: required sudo: required
language: cpp language: cpp
# gcc t # gcc 4.9
addons: &gcc48 addons: &gcc49
apt: apt:
packages: packages:
- g++-4.8 - g++-4.9
sources: sources:
- ubuntu-toolchain-r-test - ubuntu-toolchain-r-test
@ -69,13 +69,16 @@ addons: &clang12
- sourceline: "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-12 main" - sourceline: "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-12 main"
key_url: "https://apt.llvm.org/llvm-snapshot.gpg.key" key_url: "https://apt.llvm.org/llvm-snapshot.gpg.key"
env:
global:
- BUILD_EXAMPLE='ON'
matrix: matrix:
include: include:
# Test gcc-4.8: C++11, Build=Release # Test gcc-4.9: C++11, Build=Release
- env: GCC_VERSION=4.8 BUILD_TYPE=Release CPP=11 - env: GCC_VERSION=4.9 BUILD_TYPE=Release CPP=11 BUILD_EXAMPLE='OFF'
os: linux os: linux
addons: *gcc48 addons: *gcc49
# Test gcc-7: C++11, Build=Release # Test gcc-7: C++11, Build=Release
- env: GCC_VERSION=7 BUILD_TYPE=Release CPP=11 - env: GCC_VERSION=7 BUILD_TYPE=Release CPP=11
@ -139,8 +142,8 @@ script:
--warn-uninitialized \ --warn-uninitialized \
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \ -DCMAKE_BUILD_TYPE=$BUILD_TYPE \
-DCMAKE_CXX_STANDARD=$CPP \ -DCMAKE_CXX_STANDARD=$CPP \
-DSPDLOG_BUILD_EXAMPLE=ON \ -DSPDLOG_BUILD_EXAMPLE=$BUILD_EXAMPLE \
-DSPDLOG_BUILD_EXAMPLE_HO=ON \ -DSPDLOG_BUILD_EXAMPLE_HO=$BUILD_EXAMPLE \
-DSPDLOG_BUILD_WARNINGS=ON \ -DSPDLOG_BUILD_WARNINGS=ON \
-DSPDLOG_BUILD_BENCH=OFF \ -DSPDLOG_BUILD_BENCH=OFF \
-DSPDLOG_BUILD_TESTS=ON \ -DSPDLOG_BUILD_TESTS=ON \

@ -80,6 +80,7 @@ option(SPDLOG_BUILD_WARNINGS "Enable compiler warnings" OFF)
# install options # install options
option(SPDLOG_INSTALL "Generate the install target" ${SPDLOG_MASTER_PROJECT}) 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 "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_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) option(SPDLOG_NO_EXCEPTIONS "Compile with -fno-exceptions. Call abort() on any spdlog exceptions" OFF)
@ -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") message(FATAL_ERROR "SPDLOG_FMT_EXTERNAL and SPDLOG_FMT_EXTERNAL_HO are mutually exclusive")
endif() 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 # misc tweakme options
if(WIN32) if(WIN32)
option(SPDLOG_WCHAR_SUPPORT "Support wchar api" OFF) option(SPDLOG_WCHAR_SUPPORT "Support wchar api" OFF)
@ -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) 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) list(APPEND SPDLOG_SRCS src/fmt.cpp)
endif() endif()
@ -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 target_compile_options(spdlog PUBLIC $<$<AND:$<CXX_COMPILER_ID:MSVC>,$<NOT:$<COMPILE_LANGUAGE:CUDA>>>:/wd4251
/wd4275>) /wd4275>)
endif() 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) target_compile_definitions(spdlog PRIVATE FMT_EXPORT PUBLIC FMT_SHARED)
endif() endif()
else() else()
@ -222,7 +231,8 @@ foreach(
SPDLOG_NO_THREAD_ID SPDLOG_NO_THREAD_ID
SPDLOG_NO_TLS SPDLOG_NO_TLS
SPDLOG_NO_ATOMIC_LEVELS SPDLOG_NO_ATOMIC_LEVELS
SPDLOG_DISABLE_DEFAULT_LOGGER) SPDLOG_DISABLE_DEFAULT_LOGGER
SPDLOG_USE_STD_FORMAT)
if(${SPDLOG_OPTION}) if(${SPDLOG_OPTION})
target_compile_definitions(spdlog PUBLIC ${SPDLOG_OPTION}) target_compile_definitions(spdlog PUBLIC ${SPDLOG_OPTION})
target_compile_definitions(spdlog_header_only INTERFACE ${SPDLOG_OPTION}) target_compile_definitions(spdlog_header_only INTERFACE ${SPDLOG_OPTION})
@ -280,7 +290,7 @@ if(SPDLOG_INSTALL)
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 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/ install(DIRECTORY include/${PROJECT_NAME}/fmt/bundled/
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/fmt/bundled/") DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}/fmt/bundled/")
endif() endif()

@ -1,6 +1,6 @@
# spdlog # spdlog
Very fast, header-only/compiled, C++ logging library. [![Build Status](https://app.travis-ci.com/gabime/spdlog.svg?branch=v1.x)](https://app.travis-ci.com/gabime/spdlog)&nbsp; [![Build status](https://ci.appveyor.com/api/projects/status/d2jnxclg20vd0o50?svg=true)](https://ci.appveyor.com/project/gabime/spdlog) [![Release](https://img.shields.io/github/release/gabime/spdlog.svg)](https://github.com/gabime/spdlog/releases/latest) Very fast, header-only/compiled, C++ logging library. [![Build Status](https://app.travis-ci.com/gabime/spdlog.svg?branch=v1.x)](https://app.travis-ci.com/gabime/spdlog)&nbsp; [![Build status](https://ci.appveyor.com/api/projects/status/d2jnxclg20vd0o50?svg=true&branch=v1.x)](https://ci.appveyor.com/project/gabime/spdlog) [![Release](https://img.shields.io/github/release/gabime/spdlog.svg)](https://github.com/gabime/spdlog/releases/latest)
## Install ## Install
#### Header only version #### Header only version
@ -373,6 +373,35 @@ $ export SPDLOG_LEVEL=info,mylogger=trace
$ ./example $ ./example
``` ```
---
#### Log file open/close event handlers
```c++
// You can get callbacks from spdlog before/after log file has been opened or closed.
// This is useful for cleanup procedures or for adding someting the start/end of the log files.
void file_events_example()
{
// pass the spdlog::file_event_handlers to file sinks for open/close log file notifications
spdlog::file_event_handlers handlers;
handlers.before_open = [](spdlog::filename_t filename) { spdlog::info("Before opening {}", filename); };
handlers.after_open = [](spdlog::filename_t filename, std::FILE *fstream) { fputs("After opening\n", fstream); };
handlers.before_close = [](spdlog::filename_t filename, std::FILE *fstream) { fputs("Before closing\n", fstream); };
handlers.after_close = [](spdlog::filename_t filename) { spdlog::info("After closing {}", filename); };
auto my_logger = spdlog::basic_logger_st("some_logger", "logs/events-sample.txt", true, handlers);
}
```
---
#### Replace the Default Logger
```c++
void replace_default_logger_example()
{
auto new_logger = spdlog::basic_logger_mt("new_default_logger", "logs/new-default-log.txt", true);
spdlog::set_default_logger(new_logger);
spdlog::info("new logger log message");
}
```
--- ---
## Benchmarks ## Benchmarks

@ -8,55 +8,91 @@ environment:
WCHAR: 'OFF' WCHAR: 'OFF'
WCHAR_FILES: 'OFF' WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON' BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 11
- GENERATOR: '"Visual Studio 14 2015"' - GENERATOR: '"Visual Studio 14 2015"'
BUILD_TYPE: Release BUILD_TYPE: Release
BUILD_SHARED: 'OFF' BUILD_SHARED: 'OFF'
WCHAR: 'ON' WCHAR: 'ON'
WCHAR_FILES: 'OFF' WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON' BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 11
- GENERATOR: '"Visual Studio 14 2015 Win64"' - GENERATOR: '"Visual Studio 14 2015 Win64"'
BUILD_TYPE: Debug BUILD_TYPE: Debug
BUILD_SHARED: 'OFF' BUILD_SHARED: 'OFF'
WCHAR: 'ON' WCHAR: 'ON'
WCHAR_FILES: 'OFF' WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON' BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 11
- GENERATOR: '"Visual Studio 14 2015 Win64"' - GENERATOR: '"Visual Studio 14 2015 Win64"'
BUILD_TYPE: Release BUILD_TYPE: Release
BUILD_SHARED: 'OFF' BUILD_SHARED: 'OFF'
WCHAR: 'ON' WCHAR: 'ON'
WCHAR_FILES: 'OFF' WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON' BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 11
- GENERATOR: '"Visual Studio 15 2017 Win64"' - GENERATOR: '"Visual Studio 15 2017 Win64"'
BUILD_TYPE: Debug BUILD_TYPE: Debug
BUILD_SHARED: 'OFF' BUILD_SHARED: 'OFF'
WCHAR: 'ON' WCHAR: 'ON'
WCHAR_FILES: 'OFF' WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON' BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 11
- GENERATOR: '"Visual Studio 15 2017 Win64"' - GENERATOR: '"Visual Studio 15 2017 Win64"'
BUILD_TYPE: Release BUILD_TYPE: Release
BUILD_SHARED: 'OFF' BUILD_SHARED: 'OFF'
WCHAR: 'OFF' WCHAR: 'OFF'
WCHAR_FILES: 'OFF' WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON' BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 11
- GENERATOR: '"Visual Studio 15 2017 Win64"' - GENERATOR: '"Visual Studio 15 2017 Win64"'
BUILD_TYPE: Release BUILD_TYPE: Release
BUILD_SHARED: 'ON' BUILD_SHARED: 'ON'
WCHAR: 'OFF' WCHAR: 'OFF'
WCHAR_FILES: 'OFF' WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON' BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 11
- GENERATOR: '"Visual Studio 15 2017 Win64"' - GENERATOR: '"Visual Studio 15 2017 Win64"'
BUILD_TYPE: Release BUILD_TYPE: Release
BUILD_SHARED: 'ON' BUILD_SHARED: 'ON'
WCHAR: 'ON' WCHAR: 'ON'
WCHAR_FILES: 'ON' WCHAR_FILES: 'ON'
BUILD_EXAMPLE: 'OFF' BUILD_EXAMPLE: 'OFF'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 11
- GENERATOR: '"Visual Studio 16 2019" -A x64' - GENERATOR: '"Visual Studio 16 2019" -A x64'
BUILD_TYPE: Release BUILD_TYPE: Release
BUILD_SHARED: 'ON' BUILD_SHARED: 'ON'
WCHAR: 'OFF' WCHAR: 'OFF'
WCHAR_FILES: 'OFF' WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'OFF' BUILD_EXAMPLE: 'OFF'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 17
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
- GENERATOR: '"Visual Studio 17 2022" -A x64'
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'OFF'
USE_STD_FORMAT: 'ON'
CXX_STANDARD: 23 # std::format is only available with /std:c++latest at the moment.
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
- GENERATOR: '"Visual Studio 17 2022" -A x64'
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
WCHAR: 'ON'
WCHAR_FILES: 'ON'
BUILD_EXAMPLE: 'OFF'
USE_STD_FORMAT: 'ON'
CXX_STANDARD: 23 # std::format is only available with /std:c++latest at the moment.
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022
build_script: build_script:
- cmd: >- - cmd: >-
set set
@ -67,7 +103,7 @@ build_script:
set PATH=%PATH%;C:\Program Files\Git\usr\bin 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% -D CMAKE_CXX_STANDARD=%CXX_STANDARD% ..
cmake --build . --config %BUILD_TYPE% cmake --build . --config %BUILD_TYPE%

@ -16,10 +16,11 @@ if(NOT benchmark_FOUND)
# User can fetch googlebenchmark # User can fetch googlebenchmark
message(STATUS "Downloading GoogleBenchmark") message(STATUS "Downloading GoogleBenchmark")
include(FetchContent) include(FetchContent)
set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE INTERNAL "")
# Do not build and run googlebenchmark tests
FetchContent_Declare(googlebenchmark GIT_REPOSITORY https://github.com/google/benchmark.git GIT_TAG v1.5.2)
# disable tests
set(BENCHMARK_ENABLE_TESTING OFF CACHE INTERNAL "")
# Do not build and run googlebenchmark tests
FetchContent_Declare(googlebenchmark GIT_REPOSITORY https://github.com/google/benchmark.git GIT_TAG v1.6.0)
FetchContent_MakeAvailable(googlebenchmark) FetchContent_MakeAvailable(googlebenchmark)
else() else()
message(FATAL_ERROR "GoogleBenchmark is missing. Use CMake >= 3.11 or download it") message(FATAL_ERROR "GoogleBenchmark is missing. Use CMake >= 3.11 or download it")

@ -10,7 +10,9 @@
#include "spdlog/async.h" #include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.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> # include <fmt/format.h>
#else #else
# include "spdlog/fmt/bundled/format.h" # include "spdlog/fmt/bundled/format.h"
@ -160,7 +162,7 @@ void thread_fun(std::shared_ptr<spdlog::logger> logger, int howmany)
void bench_mt(int howmany, std::shared_ptr<spdlog::logger> logger, int thread_count) void bench_mt(int howmany, std::shared_ptr<spdlog::logger> logger, int thread_count)
{ {
using std::chrono::high_resolution_clock; using std::chrono::high_resolution_clock;
vector<thread> threads; vector<std::thread> threads;
auto start = high_resolution_clock::now(); auto start = high_resolution_clock::now();
int msgs_per_thread = howmany / thread_count; int msgs_per_thread = howmany / thread_count;

@ -12,7 +12,9 @@
#include "spdlog/sinks/null_sink.h" #include "spdlog/sinks/null_sink.h"
#include "spdlog/sinks/rotating_file_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> # include <fmt/locale.h>
#else #else
# include "spdlog/fmt/bundled/format.h" # include "spdlog/fmt/bundled/format.h"
@ -38,7 +40,7 @@ static const int max_threads = 1000;
void bench_threaded_logging(size_t threads, int iters) void bench_threaded_logging(size_t threads, int iters)
{ {
spdlog::info("**************************************************************"); 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("**************************************************************"); spdlog::info("**************************************************************");
auto basic_mt = spdlog::basic_logger_mt("basic_mt", "logs/basic_mt.log", true); auto basic_mt = spdlog::basic_logger_mt("basic_mt", "logs/basic_mt.log", true);
@ -74,7 +76,7 @@ void bench_threaded_logging(size_t threads, int iters)
void bench_single_threaded(int iters) void bench_single_threaded(int iters)
{ {
spdlog::info("**************************************************************"); 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("**************************************************************"); spdlog::info("**************************************************************");
auto basic_st = spdlog::basic_logger_st("basic_st", "logs/basic_st.log", true); auto basic_st = spdlog::basic_logger_st("basic_st", "logs/basic_st.log", true);
@ -128,7 +130,7 @@ int main(int argc, char *argv[])
if (threads > max_threads) 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); bench_single_threaded(iters);
@ -158,8 +160,8 @@ void bench(int howmany, std::shared_ptr<spdlog::logger> log)
auto delta = high_resolution_clock::now() - start; auto delta = high_resolution_clock::now() - start;
auto delta_d = duration_cast<duration<double>>(delta).count(); auto delta_d = duration_cast<duration<double>>(delta).count();
spdlog::info( spdlog::info(spdlog::fmt_lib::format(
fmt::format(std::locale("en_US.UTF-8"), "{:<30} Elapsed: {:0.2f} secs {:>16L}/sec", log->name(), delta_d, int(howmany / delta_d))); 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()); spdlog::drop(log->name());
} }
@ -189,8 +191,8 @@ void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, size_t thread_co
auto delta = high_resolution_clock::now() - start; auto delta = high_resolution_clock::now() - start;
auto delta_d = duration_cast<duration<double>>(delta).count(); auto delta_d = duration_cast<duration<double>>(delta).count();
spdlog::info( spdlog::info(spdlog::fmt_lib::format(
fmt::format(std::locale("en_US.UTF-8"), "{:<30} Elapsed: {:0.2f} secs {:>16L}/sec", log->name(), delta_d, int(howmany / delta_d))); 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()); spdlog::drop(log->name());
} }

@ -6,9 +6,10 @@
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
set(SPDLOG_FMT_EXTERNAL @SPDLOG_FMT_EXTERNAL@) set(SPDLOG_FMT_EXTERNAL @SPDLOG_FMT_EXTERNAL@)
set(SPDLOG_FMT_EXTERNAL_HO @SPDLOG_FMT_EXTERNAL_HO@)
set(config_targets_file @config_targets_file@) set(config_targets_file @config_targets_file@)
if(SPDLOG_FMT_EXTERNAL) if(SPDLOG_FMT_EXTERNAL OR SPDLOG_FMT_EXTERNAL_HO)
include(CMakeFindDependencyMacro) include(CMakeFindDependencyMacro)
find_dependency(fmt CONFIG) find_dependency(fmt CONFIG)
endif() endif()

@ -14,6 +14,7 @@ void rotating_example();
void daily_example(); void daily_example();
void async_example(); void async_example();
void binary_example(); void binary_example();
void vector_example();
void stopwatch_example(); void stopwatch_example();
void trace_example(); void trace_example();
void multi_sink_example(); void multi_sink_example();
@ -22,6 +23,8 @@ void err_handler_example();
void syslog_example(); void syslog_example();
void udp_example(); void udp_example();
void custom_flags_example(); void custom_flags_example();
void file_events_example();
void replace_default_logger_example();
#include "spdlog/spdlog.h" #include "spdlog/spdlog.h"
#include "spdlog/cfg/env.h" // support for loading levels from the environment variable #include "spdlog/cfg/env.h" // support for loading levels from the environment variable
@ -71,6 +74,7 @@ int main(int, char *[])
daily_example(); daily_example();
async_example(); async_example();
binary_example(); binary_example();
vector_example();
multi_sink_example(); multi_sink_example();
user_defined_example(); user_defined_example();
err_handler_example(); err_handler_example();
@ -78,6 +82,8 @@ int main(int, char *[])
stopwatch_example(); stopwatch_example();
udp_example(); udp_example();
custom_flags_example(); custom_flags_example();
file_events_example();
replace_default_logger_example();
// Flush all *registered* loggers using a worker thread every 3 seconds. // Flush all *registered* loggers using a worker thread every 3 seconds.
// note: registered loggers *must* be thread safe for this to work correctly! // note: registered loggers *must* be thread safe for this to work correctly!
@ -113,7 +119,7 @@ void stdout_logger_example()
void basic_example() void basic_example()
{ {
// Create basic file logger (not rotated). // Create basic file logger (not rotated).
auto my_logger = spdlog::basic_logger_mt("file_logger", "logs/basic-log.txt"); auto my_logger = spdlog::basic_logger_mt("file_logger", "logs/basic-log.txt", true);
} }
#include "spdlog/sinks/rotating_file_sink.h" #include "spdlog/sinks/rotating_file_sink.h"
@ -184,6 +190,21 @@ void binary_example()
// logger->info("hexdump style, 20 chars per line {:a}", spdlog::to_hex(buf, 20)); // logger->info("hexdump style, 20 chars per line {:a}", spdlog::to_hex(buf, 20));
} }
// Log a vector of numbers
#ifndef SPDLOG_USE_STD_FORMAT
# include "spdlog/fmt/ranges.h"
void vector_example()
{
std::vector<int> vec = {1, 2, 3};
spdlog::info("Vector example: {}", vec);
}
#else
void vector_example() {}
#endif
// ! DSPDLOG_USE_STD_FORMAT
// Compile time log levels. // Compile time log levels.
// define SPDLOG_ACTIVE_LEVEL to required level (e.g. SPDLOG_LEVEL_TRACE) // define SPDLOG_ACTIVE_LEVEL to required level (e.g. SPDLOG_LEVEL_TRACE)
void trace_example() void trace_example()
@ -233,20 +254,27 @@ void multi_sink_example()
logger.info("this message should not appear in the console, only in the file"); logger.info("this message should not appear in the console, only in the file");
} }
// User defined types logging by implementing operator<< // User defined types logging
struct my_type struct my_type
{ {
int i; int i = 0;
template<typename OStream> explicit my_type(int i)
friend OStream &operator<<(OStream &os, const my_type &c) : i(i){};
};
namespace fmt_lib = spdlog::fmt_lib;
template<>
struct fmt_lib::formatter<my_type> : fmt_lib::formatter<std::string>
{
auto format(my_type my, format_context &ctx) -> decltype(ctx.out())
{ {
return os << "[my_type i=" << c.i << "]"; return fmt_lib::format_to(ctx.out(), "[my_type i={}]", my.i);
} }
}; };
void user_defined_example() void user_defined_example()
{ {
spdlog::info("user defined type: {}", my_type{14}); spdlog::info("user defined type: {}", my_type(14));
} }
// Custom error handler. Will be triggered on log failure. // Custom error handler. Will be triggered on log failure.
@ -302,5 +330,40 @@ void custom_flags_example()
using spdlog::details::make_unique; // for pre c++14 using spdlog::details::make_unique; // for pre c++14
auto formatter = make_unique<spdlog::pattern_formatter>(); auto formatter = make_unique<spdlog::pattern_formatter>();
formatter->add_flag<my_formatter_flag>('*').set_pattern("[%n] [%*] [%^%l%$] %v"); formatter->add_flag<my_formatter_flag>('*').set_pattern("[%n] [%*] [%^%l%$] %v");
spdlog::set_formatter(std::move(formatter)); // set the new formatter using spdlog::set_formatter(formatter) or logger->set_formatter(formatter)
// spdlog::set_formatter(std::move(formatter));
}
void file_events_example()
{
// pass the spdlog::file_event_handlers to file sinks for open/close log file notifications
spdlog::file_event_handlers handlers;
handlers.before_open = [](spdlog::filename_t filename) { spdlog::info("Before opening {}", filename); };
handlers.after_open = [](spdlog::filename_t filename, std::FILE *fstream) {
spdlog::info("After opening {}", filename);
fputs("After opening\n", fstream);
};
handlers.before_close = [](spdlog::filename_t filename, std::FILE *fstream) {
spdlog::info("Before closing {}", filename);
fputs("Before closing\n", fstream);
};
handlers.after_close = [](spdlog::filename_t filename) { spdlog::info("After closing {}", filename); };
auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/events-sample.txt", true, handlers);
spdlog::logger my_logger("some_logger", file_sink);
my_logger.info("Some log line");
}
void replace_default_logger_example()
{
// store the old logger so we don't break other examples.
auto old_logger = spdlog::default_logger();
auto new_logger = spdlog::basic_logger_mt("new_default_logger", "logs/new-default-log.txt", true);
spdlog::set_default_logger(new_logger);
spdlog::set_level(spdlog::level::info);
spdlog::debug("This message should not be displayed!");
spdlog::set_level(spdlog::level::trace);
spdlog::debug("This message should be displayed..");
spdlog::set_default_logger(old_logger);
} }

@ -73,16 +73,22 @@ inline std::shared_ptr<spdlog::logger> create_async_nb(std::string logger_name,
} }
// set global thread pool. // set global thread pool.
inline void init_thread_pool(size_t q_size, size_t thread_count, std::function<void()> on_thread_start) inline void init_thread_pool(
size_t q_size, size_t thread_count, std::function<void()> on_thread_start, std::function<void()> on_thread_stop)
{ {
auto tp = std::make_shared<details::thread_pool>(q_size, thread_count, on_thread_start); auto tp = std::make_shared<details::thread_pool>(q_size, thread_count, on_thread_start, on_thread_stop);
details::registry::instance().set_tp(std::move(tp)); details::registry::instance().set_tp(std::move(tp));
} }
// set global thread pool. inline void init_thread_pool(size_t q_size, size_t thread_count, std::function<void()> on_thread_start)
{
init_thread_pool(q_size, thread_count, on_thread_start, [] {});
}
inline void init_thread_pool(size_t q_size, size_t thread_count) inline void init_thread_pool(size_t q_size, size_t thread_count)
{ {
init_thread_pool(q_size, thread_count, [] {}); init_thread_pool(
q_size, thread_count, [] {}, [] {});
} }
// get the global thread pool. // get the global thread pool.

@ -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) 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; memory_buf_t outbuf;
fmt::format_system_error(outbuf, last_errno, msg.c_str()); fmt::format_system_error(outbuf, last_errno, msg.c_str());
msg_ = fmt::to_string(outbuf); msg_ = fmt::to_string(outbuf);
#endif
} }
SPDLOG_INLINE const char *spdlog_ex::what() const SPDLOG_NOEXCEPT SPDLOG_INLINE const char *spdlog_ex::what() const SPDLOG_NOEXCEPT

@ -14,16 +14,25 @@
#include <string> #include <string>
#include <type_traits> #include <type_traits>
#include <functional> #include <functional>
#include <cstdio>
#ifdef SPDLOG_USE_STD_FORMAT
# include <string_view>
#endif
#ifdef SPDLOG_COMPILED_LIB #ifdef SPDLOG_COMPILED_LIB
# undef SPDLOG_HEADER_ONLY # undef SPDLOG_HEADER_ONLY
# if defined(_WIN32) && defined(SPDLOG_SHARED_LIB) # if defined(SPDLOG_SHARED_LIB)
# if defined(_WIN32)
# ifdef spdlog_EXPORTS # ifdef spdlog_EXPORTS
# define SPDLOG_API __declspec(dllexport) # define SPDLOG_API __declspec(dllexport)
# else # else // !spdlog_EXPORTS
# define SPDLOG_API __declspec(dllimport) # define SPDLOG_API __declspec(dllimport)
# endif # endif
# else // !defined(_WIN32) || !defined(SPDLOG_SHARED_LIB) # else // !defined(_WIN32)
# define SPDLOG_API __attribute__((visibility("default")))
# endif
# else // !defined(SPDLOG_SHARED_LIB)
# define SPDLOG_API # define SPDLOG_API
# endif # endif
# define SPDLOG_INLINE # define SPDLOG_INLINE
@ -35,8 +44,8 @@
#include <spdlog/fmt/fmt.h> #include <spdlog/fmt/fmt.h>
// backward compatibility with fmt versions older than 8 #ifndef SPDLOG_USE_STD_FORMAT
#if FMT_VERSION >= 80000 # if FMT_VERSION >= 80000 // backward compatibility with fmt versions older than 8
# define SPDLOG_FMT_RUNTIME(format_string) fmt::runtime(format_string) # define SPDLOG_FMT_RUNTIME(format_string) fmt::runtime(format_string)
# if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) # if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT)
# include <spdlog/fmt/xchar.h> # include <spdlog/fmt/xchar.h>
@ -44,14 +53,21 @@
# else # else
# define SPDLOG_FMT_RUNTIME(format_string) format_string # define SPDLOG_FMT_RUNTIME(format_string) format_string
# endif # endif
#endif
// visual studio up to 2013 does not support noexcept nor constexpr // visual studio up to 2013 does not support noexcept nor constexpr
#if defined(_MSC_VER) && (_MSC_VER < 1900) #if defined(_MSC_VER) && (_MSC_VER < 1900)
# define SPDLOG_NOEXCEPT _NOEXCEPT # define SPDLOG_NOEXCEPT _NOEXCEPT
# define SPDLOG_CONSTEXPR # define SPDLOG_CONSTEXPR
# define SPDLOG_CONSTEXPR_FUNC
#else #else
# define SPDLOG_NOEXCEPT noexcept # define SPDLOG_NOEXCEPT noexcept
# define SPDLOG_CONSTEXPR constexpr # define SPDLOG_CONSTEXPR constexpr
# if __cplusplus >= 201402L
# define SPDLOG_CONSTEXPR_FUNC constexpr
# else
# define SPDLOG_CONSTEXPR_FUNC
# endif
#endif #endif
#if defined(__GNUC__) || defined(__clang__) #if defined(__GNUC__) || defined(__clang__)
@ -111,10 +127,35 @@ using log_clock = std::chrono::system_clock;
using sink_ptr = std::shared_ptr<sinks::sink>; using sink_ptr = std::shared_ptr<sinks::sink>;
using sinks_init_list = std::initializer_list<sink_ptr>; using sinks_init_list = std::initializer_list<sink_ptr>;
using err_handler = std::function<void(const std::string &err_msg)>; 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 memory_buf_t = std::string;
template<typename... Args>
using format_string_t = std::string_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>
{};
# if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT)
using wstring_view_t = std::wstring_view;
using wmemory_buf_t = std::wstring;
template<typename... Args>
using wformat_string_t = std::wstring_view;
# endif
#else // use fmt lib instead of std::format
namespace fmt_lib = fmt;
using string_view_t = fmt::basic_string_view<char>; 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 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<class T> template<class T>
using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type; using remove_cvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
@ -127,6 +168,15 @@ struct is_convertible_to_basic_format_string
std::is_convertible<T, fmt::basic_string_view<Char>>::value || std::is_same<remove_cvref_t<T>, fmt::basic_runtime<Char>>::value> std::is_convertible<T, fmt::basic_string_view<Char>>::value || std::is_same<remove_cvref_t<T>, fmt::basic_runtime<Char>>::value>
{}; {};
# if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT)
using wstring_view_t = fmt::basic_string_view<wchar_t>;
using wmemory_buf_t = fmt::basic_memory_buffer<wchar_t, 250>;
template<typename... Args>
using wformat_string_t = fmt::wformat_string<Args...>;
# endif
#endif
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
# ifndef _WIN32 # ifndef _WIN32
# error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows # error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows
@ -158,7 +208,7 @@ using level_t = std::atomic<int>;
// Log level enum // Log level enum
namespace level { namespace level {
enum level_enum enum level_enum : int
{ {
trace = SPDLOG_LEVEL_TRACE, trace = SPDLOG_LEVEL_TRACE,
debug = SPDLOG_LEVEL_DEBUG, debug = SPDLOG_LEVEL_DEBUG,
@ -255,12 +305,31 @@ struct source_loc
const char *funcname{nullptr}; const char *funcname{nullptr};
}; };
struct file_event_handlers
{
std::function<void(const filename_t &filename)> before_open;
std::function<void(const filename_t &filename, std::FILE *file_stream)> after_open;
std::function<void(const filename_t &filename, std::FILE *file_stream)> before_close;
std::function<void(const filename_t &filename)> after_close;
file_event_handlers()
: before_open{nullptr}
, after_open{nullptr}
, before_close{nullptr}
, after_close{nullptr}
{}
};
namespace details { namespace details {
// make_unique support for pre c++14 // make_unique support for pre c++14
#if __cplusplus >= 201402L // C++14 and beyond #if __cplusplus >= 201402L // C++14 and beyond
using std::enable_if_t;
using std::make_unique; using std::make_unique;
#else #else
template<bool B, class T = void>
using enable_if_t = typename std::enable_if<B, T>::type;
template<typename T, typename... Args> template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args &&... args) std::unique_ptr<T> make_unique(Args &&... args)
{ {
@ -268,6 +337,20 @@ std::unique_ptr<T> make_unique(Args &&...args)
return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
} }
#endif #endif
// to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324)
template<typename T, typename U, enable_if_t<!std::is_same<T, U>::value, int> = 0>
constexpr T conditional_static_cast(U value)
{
return static_cast<T>(value);
}
template<typename T, typename U, enable_if_t<std::is_same<T, U>::value, int> = 0>
constexpr T conditional_static_cast(U value)
{
return value;
}
} // namespace details } // namespace details
} // namespace spdlog } // namespace spdlog

@ -20,6 +20,10 @@
namespace spdlog { namespace spdlog {
namespace details { namespace details {
SPDLOG_INLINE file_helper::file_helper(const file_event_handlers &event_handlers)
: event_handlers_(event_handlers)
{}
SPDLOG_INLINE file_helper::~file_helper() SPDLOG_INLINE file_helper::~file_helper()
{ {
close(); close();
@ -33,6 +37,10 @@ SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate)
auto *mode = SPDLOG_FILENAME_T("ab"); auto *mode = SPDLOG_FILENAME_T("ab");
auto *trunc_mode = SPDLOG_FILENAME_T("wb"); auto *trunc_mode = SPDLOG_FILENAME_T("wb");
if (event_handlers_.before_open)
{
event_handlers_.before_open(filename_);
}
for (int tries = 0; tries < open_tries_; ++tries) for (int tries = 0; tries < open_tries_; ++tries)
{ {
// create containing folder if not exists already. // create containing folder if not exists already.
@ -52,6 +60,10 @@ SPDLOG_INLINE void file_helper::open(const filename_t &fname, bool truncate)
} }
if (!os::fopen_s(&fd_, fname, mode)) if (!os::fopen_s(&fd_, fname, mode))
{ {
if (event_handlers_.after_open)
{
event_handlers_.after_open(filename_, fd_);
}
return; return;
} }
@ -72,15 +84,28 @@ SPDLOG_INLINE void file_helper::reopen(bool truncate)
SPDLOG_INLINE void file_helper::flush() SPDLOG_INLINE void file_helper::flush()
{ {
std::fflush(fd_); if (std::fflush(fd_) != 0)
{
throw_spdlog_ex("Failed flush to file " + os::filename_to_str(filename_), errno);
}
} }
SPDLOG_INLINE void file_helper::close() SPDLOG_INLINE void file_helper::close()
{ {
if (fd_ != nullptr) if (fd_ != nullptr)
{ {
if (event_handlers_.before_close)
{
event_handlers_.before_close(filename_, fd_);
}
std::fclose(fd_); std::fclose(fd_);
fd_ = nullptr; fd_ = nullptr;
if (event_handlers_.after_close)
{
event_handlers_.after_close(filename_);
}
} }
} }

@ -16,7 +16,8 @@ namespace details {
class SPDLOG_API file_helper class SPDLOG_API file_helper
{ {
public: public:
explicit file_helper() = default; file_helper() = default;
explicit file_helper(const file_event_handlers &event_handlers);
file_helper(const file_helper &) = delete; file_helper(const file_helper &) = delete;
file_helper &operator=(const file_helper &) = delete; file_helper &operator=(const file_helper &) = delete;
@ -50,6 +51,7 @@ private:
const unsigned int open_interval_ = 10; const unsigned int open_interval_ = 10;
std::FILE *fd_{nullptr}; std::FILE *fd_{nullptr};
filename_t filename_; filename_t filename_;
file_event_handlers event_handlers_;
}; };
} // namespace details } // namespace details
} // namespace spdlog } // namespace spdlog

@ -8,6 +8,11 @@
#include <spdlog/fmt/fmt.h> #include <spdlog/fmt/fmt.h>
#include <spdlog/common.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 // Some fmt helpers to efficiently format and pad ints and strings
namespace spdlog { namespace spdlog {
namespace details { namespace details {
@ -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()); 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 const 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> template<typename T>
inline void append_int(T n, memory_buf_t &dest) inline void append_int(T n, memory_buf_t &dest)
{ {
fmt::format_int i(n); fmt::format_int i(n);
dest.append(i.data(), i.data() + i.size()); dest.append(i.data(), i.data() + i.size());
} }
#endif
template<typename T>
SPDLOG_CONSTEXPR_FUNC unsigned int count_digits_fallback(T n)
{
// taken from fmt: https://github.com/fmtlib/fmt/blob/8.0.1/include/fmt/format.h#L899-L912
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> template<typename T>
inline unsigned int count_digits(T n) inline unsigned int count_digits(T n)
{ {
using count_type = typename std::conditional<(sizeof(T) > sizeof(uint32_t)), uint64_t, uint32_t>::type; 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:: return static_cast<unsigned int>(fmt::
// fmt 7.0.0 renamed the internal namespace to detail. // fmt 7.0.0 renamed the internal namespace to detail.
// See: https://github.com/fmtlib/fmt/issues/1538 // See: https://github.com/fmtlib/fmt/issues/1538
@ -44,6 +95,7 @@ inline unsigned int count_digits(T n)
detail detail
# endif # endif
::count_digits(static_cast<count_type>(n))); ::count_digits(static_cast<count_type>(n)));
#endif
} }
inline void pad2(int n, memory_buf_t &dest) inline void pad2(int n, memory_buf_t &dest)
@ -55,7 +107,7 @@ inline void pad2(int n, memory_buf_t &dest)
} }
else // unlikely, but just in case, let fmt deal with it 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);
} }
} }

@ -46,7 +46,7 @@
# include <sys/syscall.h> //Use gettid() syscall under linux to get thread id # include <sys/syscall.h> //Use gettid() syscall under linux to get thread id
# elif defined(_AIX) # elif defined(_AIX)
# include <pthread.h> // for pthread_getthreadid_np # include <pthread.h> // for pthread_getthrds_np
# elif defined(__DragonFly__) || defined(__FreeBSD__) # elif defined(__DragonFly__) || defined(__FreeBSD__)
# include <pthread_np.h> // for pthread_getthreadid_np # include <pthread_np.h> // for pthread_getthreadid_np
@ -145,7 +145,7 @@ SPDLOG_INLINE bool fopen_s(FILE **fp, const filename_t &filename, const filename
const int fd = ::open((filename.c_str()), O_CREAT | O_WRONLY | O_CLOEXEC | mode_flag, mode_t(0644)); const int fd = ::open((filename.c_str()), O_CREAT | O_WRONLY | O_CLOEXEC | mode_flag, mode_t(0644));
if (fd == -1) if (fd == -1)
{ {
return false; return true;
} }
*fp = ::fdopen(fd, mode.c_str()); *fp = ::fdopen(fd, mode.c_str());
if (*fp == nullptr) if (*fp == nullptr)
@ -230,8 +230,8 @@ SPDLOG_INLINE size_t filesize(FILE *f)
# endif # endif
#else // unix #else // unix
// OpenBSD doesn't compile with :: before the fileno(..) // OpenBSD and AIX doesn't compile with :: before the fileno(..)
# if defined(__OpenBSD__) # if defined(__OpenBSD__) || defined(_AIX)
int fd = fileno(f); int fd = fileno(f);
# else # else
int fd = ::fileno(f); int fd = ::fileno(f);
@ -305,7 +305,7 @@ SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm)
((local_year / 100 >> 2) - (gmt_year / 100 >> 2)) ((local_year / 100 >> 2) - (gmt_year / 100 >> 2))
// + difference in years * 365 */ // + difference in years * 365 */
+ (long int)(local_year - gmt_year) * 365); + static_cast<long int>(local_year - gmt_year) * 365);
long int hours = (24 * days) + (localtm.tm_hour - gmtm.tm_hour); long int hours = (24 * days) + (localtm.tm_hour - gmtm.tm_hour);
long int mins = (60 * hours) + (localtm.tm_min - gmtm.tm_min); long int mins = (60 * hours) + (localtm.tm_min - gmtm.tm_min);
@ -336,7 +336,14 @@ SPDLOG_INLINE size_t _thread_id() SPDLOG_NOEXCEPT
# define SYS_gettid __NR_gettid # define SYS_gettid __NR_gettid
# endif # endif
return static_cast<size_t>(::syscall(SYS_gettid)); return static_cast<size_t>(::syscall(SYS_gettid));
#elif defined(_AIX) || defined(__DragonFly__) || defined(__FreeBSD__) #elif defined(_AIX)
struct __pthrdsinfo buf;
int reg_size = 0;
pthread_t pt = pthread_self();
int retval = pthread_getthrds_np(&pt, PTHRDSINFO_QUERY_TID, &buf, sizeof(buf), NULL, &reg_size);
int tid = (!retval) ? buf.__pi_tid : 0;
return static_cast<size_t>(tid);
#elif defined(__DragonFly__) || defined(__FreeBSD__)
return static_cast<size_t>(::pthread_getthreadid_np()); return static_cast<size_t>(::pthread_getthreadid_np());
#elif defined(__NetBSD__) #elif defined(__NetBSD__)
return static_cast<size_t>(::_lwp_self()); return static_cast<size_t>(::_lwp_self());
@ -381,7 +388,11 @@ SPDLOG_INLINE std::string filename_to_str(const filename_t &filename)
{ {
memory_buf_t buf; memory_buf_t buf;
wstr_to_utf8buf(filename, buf); wstr_to_utf8buf(filename, buf);
# ifdef SPDLOG_USE_STD_FORMAT
return buf;
# else
return fmt::to_string(buf); return fmt::to_string(buf);
# endif
} }
#else #else
SPDLOG_INLINE std::string filename_to_str(const filename_t &filename) SPDLOG_INLINE std::string filename_to_str(const filename_t &filename)
@ -394,9 +405,9 @@ SPDLOG_INLINE int pid() SPDLOG_NOEXCEPT
{ {
#ifdef _WIN32 #ifdef _WIN32
return static_cast<int>(::GetCurrentProcessId()); return conditional_static_cast<int>(::GetCurrentProcessId());
#else #else
return static_cast<int>(::getpid()); return conditional_static_cast<int>(::getpid());
#endif #endif
} }
@ -476,7 +487,7 @@ SPDLOG_INLINE void wstr_to_utf8buf(wstring_view_t wstr, memory_buf_t &target)
} }
} }
throw_spdlog_ex(fmt::format("WideCharToMultiByte failed. Last error: {}", ::GetLastError())); throw_spdlog_ex(fmt_lib::format("WideCharToMultiByte failed. Last error: {}", ::GetLastError()));
} }
SPDLOG_INLINE void utf8_to_wstrbuf(string_view_t str, wmemory_buf_t &target) SPDLOG_INLINE void utf8_to_wstrbuf(string_view_t str, wmemory_buf_t &target)
@ -511,7 +522,7 @@ SPDLOG_INLINE void utf8_to_wstrbuf(string_view_t str, wmemory_buf_t &target)
} }
} }
throw_spdlog_ex(fmt::format("MultiByteToWideChar failed. Last error: {}", ::GetLastError())); throw_spdlog_ex(fmt_lib::format("MultiByteToWideChar failed. Last error: {}", ::GetLastError()));
} }
#endif // (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32) #endif // (defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) || defined(SPDLOG_WCHAR_FILENAMES)) && defined(_WIN32)
@ -531,7 +542,7 @@ static SPDLOG_INLINE bool mkdir_(const filename_t &path)
// create the given directory - and all directories leading to it // create the given directory - and all directories leading to it
// return true on success or if the directory already exists // return true on success or if the directory already exists
SPDLOG_INLINE bool create_dir(filename_t path) SPDLOG_INLINE bool create_dir(const filename_t &path)
{ {
if (path_exists(path)) if (path_exists(path))
{ {
@ -570,7 +581,7 @@ SPDLOG_INLINE bool create_dir(filename_t path)
// "abc/" => "abc" // "abc/" => "abc"
// "abc" => "" // "abc" => ""
// "abc///" => "abc//" // "abc///" => "abc//"
SPDLOG_INLINE filename_t dir_name(filename_t path) SPDLOG_INLINE filename_t dir_name(const filename_t &path)
{ {
auto pos = path.find_last_of(folder_seps_filename); auto pos = path.find_last_of(folder_seps_filename);
return pos != filename_t::npos ? path.substr(0, pos) : filename_t{}; return pos != filename_t::npos ? path.substr(0, pos) : filename_t{};

@ -99,11 +99,11 @@ SPDLOG_API void utf8_to_wstrbuf(string_view_t str, wmemory_buf_t &target);
// "abc/" => "abc" // "abc/" => "abc"
// "abc" => "" // "abc" => ""
// "abc///" => "abc//" // "abc///" => "abc//"
SPDLOG_API filename_t dir_name(filename_t path); SPDLOG_API filename_t dir_name(const filename_t &path);
// Create a dir from the given path. // Create a dir from the given path.
// Return true if succeeded or if this dir already exists. // Return true if succeeded or if this dir already exists.
SPDLOG_API bool create_dir(filename_t path); SPDLOG_API bool create_dir(const filename_t &path);
// non thread safe, cross platform getenv/getenv_s // non thread safe, cross platform getenv/getenv_s
// return empty string if field not found // return empty string if field not found

@ -38,10 +38,10 @@ class tcp_client
static void throw_winsock_error_(const std::string &msg, int last_error) static void throw_winsock_error_(const std::string &msg, int last_error)
{ {
char buf[512]; char buf[512];
::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_error, ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (sizeof(buf) / sizeof(char)), NULL); MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (sizeof(buf) / sizeof(char)), NULL);
throw_spdlog_ex(fmt::format("tcp_sink - {}: {}", msg, buf)); throw_spdlog_ex(fmt_lib::format("tcp_sink - {}: {}", msg, buf));
} }
public: public:
@ -56,7 +56,6 @@ public:
::WSACleanup(); ::WSACleanup();
} }
bool is_connected() const bool is_connected() const
{ {
return socket_ != INVALID_SOCKET; return socket_ != INVALID_SOCKET;
@ -73,7 +72,6 @@ public:
return socket_; return socket_;
} }
// try to connect or throw on failure // try to connect or throw on failure
void connect(const std::string &host, int port) void connect(const std::string &host, int port)
{ {

@ -67,8 +67,7 @@ public:
auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result); auto rv = ::getaddrinfo(host.c_str(), port_str.c_str(), &hints, &addrinfo_result);
if (rv != 0) if (rv != 0)
{ {
auto msg = fmt::format("::getaddrinfo failed: {}", gai_strerror(rv)); throw_spdlog_ex(fmt_lib::format("::getaddrinfo failed: {}", gai_strerror(rv)));
throw_spdlog_ex(msg);
} }
// Try each address until we successfully connect(2). // Try each address until we successfully connect(2).

@ -13,7 +13,8 @@
namespace spdlog { namespace spdlog {
namespace details { namespace details {
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start) SPDLOG_INLINE thread_pool::thread_pool(
size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start, std::function<void()> on_thread_stop)
: q_(q_max_items) : q_(q_max_items)
{ {
if (threads_n == 0 || threads_n > 1000) if (threads_n == 0 || threads_n > 1000)
@ -23,15 +24,21 @@ SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n, std
} }
for (size_t i = 0; i < threads_n; i++) for (size_t i = 0; i < threads_n; i++)
{ {
threads_.emplace_back([this, on_thread_start] { threads_.emplace_back([this, on_thread_start, on_thread_stop] {
on_thread_start(); on_thread_start();
this->thread_pool::worker_loop_(); this->thread_pool::worker_loop_();
on_thread_stop();
}); });
} }
} }
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start)
: thread_pool(q_max_items, threads_n, on_thread_start, [] {})
{}
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n) SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, size_t threads_n)
: thread_pool(q_max_items, threads_n, [] {}) : thread_pool(
q_max_items, threads_n, [] {}, [] {})
{} {}
// message all threads to terminate gracefully join them // message all threads to terminate gracefully join them

@ -84,10 +84,11 @@ public:
using item_type = async_msg; using item_type = async_msg;
using q_type = details::mpmc_blocking_queue<item_type>; using q_type = details::mpmc_blocking_queue<item_type>;
thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start, std::function<void()> on_thread_stop);
thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start); thread_pool(size_t q_max_items, size_t threads_n, std::function<void()> on_thread_start);
thread_pool(size_t q_max_items, size_t threads_n); thread_pool(size_t q_max_items, size_t threads_n);
// message all threads to terminate gracefully join them // message all threads to terminate gracefully and join them
~thread_pool(); ~thread_pool();
thread_pool(const thread_pool &) = delete; thread_pool(const thread_pool &) = delete;

@ -40,10 +40,10 @@ class udp_client
static void throw_winsock_error_(const std::string &msg, int last_error) static void throw_winsock_error_(const std::string &msg, int last_error)
{ {
char buf[512]; char buf[512];
::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_error, ::FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, last_error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (sizeof(buf) / sizeof(char)), NULL); MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (sizeof(buf) / sizeof(char)), NULL);
throw_spdlog_ex(fmt::format("udp_sink - {}: {}", msg, buf)); throw_spdlog_ex(fmt_lib::format("udp_sink - {}: {}", msg, buf));
} }
void cleanup_() void cleanup_()
@ -64,7 +64,8 @@ public:
addr_.sin_family = PF_INET; addr_.sin_family = PF_INET;
addr_.sin_port = htons(port); addr_.sin_port = htons(port);
addr_.sin_addr.s_addr = INADDR_ANY; addr_.sin_addr.s_addr = INADDR_ANY;
if (InetPton(PF_INET, TEXT(host.c_str()), &addr_.sin_addr.s_addr) != 1) { if (InetPton(PF_INET, TEXT(host.c_str()), &addr_.sin_addr.s_addr) != 1)
{
int last_error = ::WSAGetLastError(); int last_error = ::WSAGetLastError();
::WSACleanup(); ::WSACleanup();
throw_winsock_error_("error: Invalid address!", last_error); throw_winsock_error_("error: Invalid address!", last_error);

@ -14,6 +14,7 @@
#include <spdlog/details/os.h> #include <spdlog/details/os.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <unistd.h> #include <unistd.h>
#include <netdb.h> #include <netdb.h>
@ -30,7 +31,6 @@ class udp_client
int socket_ = -1; int socket_ = -1;
struct sockaddr_in sockAddr_; struct sockaddr_in sockAddr_;
void cleanup_() void cleanup_()
{ {
if (socket_ != -1) if (socket_ != -1)
@ -59,7 +59,8 @@ public:
sockAddr_.sin_family = AF_INET; sockAddr_.sin_family = AF_INET;
sockAddr_.sin_port = htons(port); sockAddr_.sin_port = htons(port);
if (::inet_aton(host.c_str(), &sockAddr_.sin_addr) == 0) { if (::inet_aton(host.c_str(), &sockAddr_.sin_addr) == 0)
{
cleanup_(); cleanup_();
throw_spdlog_ex("error: Invalid address!"); throw_spdlog_ex("error: Invalid address!");
} }

@ -8,9 +8,17 @@
#include <cctype> #include <cctype>
#include <spdlog/common.h> #include <spdlog/common.h>
#if defined(__has_include) && __has_include(<version>)
# include <version>
#endif
#if __cpp_lib_span >= 202002L
# include <span>
#endif
// //
// Support for logging binary data as hex // Support for logging binary data as hex
// format flags, any combination of the followng: // format flags, any combination of the following:
// {:X} - print in uppercase. // {:X} - print in uppercase.
// {:s} - don't separate each byte with space. // {:s} - don't separate each byte with space.
// {:p} - don't print the position on each line start. // {:p} - don't print the position on each line start.
@ -39,11 +47,12 @@ public:
, size_per_line_(size_per_line) , size_per_line_(size_per_line)
{} {}
It begin() const // do not use begin() and end() to avoid collision with fmt/ranges
It get_begin() const
{ {
return begin_; return begin_;
} }
It end() const It get_end() const
{ {
return end_; return end_;
} }
@ -67,6 +76,20 @@ inline details::dump_info<typename Container::const_iterator> to_hex(const Conta
return details::dump_info<Iter>(std::begin(container), std::end(container), size_per_line); return details::dump_info<Iter>(std::begin(container), std::end(container), size_per_line);
} }
#if __cpp_lib_span >= 202002L
template<typename Value, size_t Extent>
inline details::dump_info<typename std::span<Value, Extent>::iterator> to_hex(
const std::span<Value, Extent> &container, size_t size_per_line = 32)
{
using Container = std::span<Value, Extent>;
static_assert(sizeof(typename Container::value_type) == 1, "sizeof(Container::value_type) != 1");
using Iter = typename Container::iterator;
return details::dump_info<Iter>(std::begin(container), std::end(container), size_per_line);
}
#endif
// create dump_info from ranges // create dump_info from ranges
template<typename It> template<typename It>
inline details::dump_info<It> to_hex(const It range_begin, const It range_end, size_t size_per_line = 32) inline details::dump_info<It> to_hex(const It range_begin, const It range_end, size_t size_per_line = 32)
@ -76,10 +99,16 @@ inline details::dump_info<It> to_hex(const It range_begin, const It range_end, s
} // namespace spdlog } // namespace spdlog
namespace fmt { namespace
#ifdef SPDLOG_USE_STD_FORMAT
std
#else
fmt
#endif
{
template<typename T> template<typename T>
struct formatter<spdlog::details::dump_info<T>> struct formatter<spdlog::details::dump_info<T>, char>
{ {
const char delimiter = ' '; const char delimiter = ' ';
bool put_newlines = true; bool put_newlines = true;
@ -90,7 +119,7 @@ struct formatter<spdlog::details::dump_info<T>>
// parse the format string flags // parse the format string flags
template<typename ParseContext> template<typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) SPDLOG_CONSTEXPR_FUNC auto parse(ParseContext &ctx) -> decltype(ctx.begin())
{ {
auto it = ctx.begin(); auto it = ctx.begin();
while (it != ctx.end() && *it != '}') while (it != ctx.end() && *it != '}')
@ -131,21 +160,21 @@ struct formatter<spdlog::details::dump_info<T>>
SPDLOG_CONSTEXPR const char *hex_lower = "0123456789abcdef"; SPDLOG_CONSTEXPR const char *hex_lower = "0123456789abcdef";
const char *hex_chars = use_uppercase ? hex_upper : hex_lower; const char *hex_chars = use_uppercase ? hex_upper : hex_lower;
#if FMT_VERSION < 60000 #if !defined(SPDLOG_USE_STD_FORMAT) && FMT_VERSION < 60000
auto inserter = ctx.begin(); auto inserter = ctx.begin();
#else #else
auto inserter = ctx.out(); auto inserter = ctx.out();
#endif #endif
int size_per_line = static_cast<int>(the_range.size_per_line()); int size_per_line = static_cast<int>(the_range.size_per_line());
auto start_of_line = the_range.begin(); auto start_of_line = the_range.get_begin();
for (auto i = the_range.begin(); i != the_range.end(); i++) for (auto i = the_range.get_begin(); i != the_range.get_end(); i++)
{ {
auto ch = static_cast<unsigned char>(*i); auto ch = static_cast<unsigned char>(*i);
if (put_newlines && (i == the_range.begin() || i - start_of_line >= size_per_line)) if (put_newlines && (i == the_range.get_begin() || i - start_of_line >= size_per_line))
{ {
if (show_ascii && i != the_range.begin()) if (show_ascii && i != the_range.get_begin())
{ {
*inserter++ = delimiter; *inserter++ = delimiter;
*inserter++ = delimiter; *inserter++ = delimiter;
@ -156,7 +185,7 @@ struct formatter<spdlog::details::dump_info<T>>
} }
} }
put_newline(inserter, static_cast<size_t>(i - the_range.begin())); put_newline(inserter, static_cast<size_t>(i - the_range.get_begin()));
// put first byte without delimiter in front of it // put first byte without delimiter in front of it
*inserter++ = hex_chars[(ch >> 4) & 0x0f]; *inserter++ = hex_chars[(ch >> 4) & 0x0f];
@ -175,9 +204,9 @@ struct formatter<spdlog::details::dump_info<T>>
} }
if (show_ascii) // add ascii to last line if (show_ascii) // add ascii to last line
{ {
if (the_range.end() - the_range.begin() > size_per_line) if (the_range.get_end() - the_range.get_begin() > size_per_line)
{ {
auto blank_num = size_per_line - (the_range.end() - start_of_line); auto blank_num = size_per_line - (the_range.get_end() - start_of_line);
while (blank_num-- > 0) while (blank_num-- > 0)
{ {
*inserter++ = delimiter; *inserter++ = delimiter;
@ -190,7 +219,7 @@ struct formatter<spdlog::details::dump_info<T>>
} }
*inserter++ = delimiter; *inserter++ = delimiter;
*inserter++ = delimiter; *inserter++ = delimiter;
for (auto j = start_of_line; j != the_range.end(); j++) for (auto j = start_of_line; j != the_range.get_end(); j++)
{ {
auto pc = static_cast<unsigned char>(*j); auto pc = static_cast<unsigned char>(*j);
*inserter++ = std::isprint(pc) ? static_cast<char>(*j) : '.'; *inserter++ = std::isprint(pc) ? static_cast<char>(*j) : '.';
@ -210,8 +239,8 @@ struct formatter<spdlog::details::dump_info<T>>
if (put_positions) if (put_positions)
{ {
fmt::format_to(inserter, "{:04X}: ", pos); spdlog::fmt_lib::format_to(inserter, "{:04X}: ", pos);
} }
} }
}; };
} // namespace fmt } // namespace std

@ -143,6 +143,8 @@ class dynamic_format_arg_store
} }
public: public:
constexpr dynamic_format_arg_store() = default;
/** /**
\rst \rst
Adds an argument into the dynamic store for later passing to a formatting Adds an argument into the dynamic store for later passing to a formatting

File diff suppressed because it is too large Load Diff

@ -185,9 +185,13 @@ enum class terminal_color : uint8_t {
enum class emphasis : uint8_t { enum class emphasis : uint8_t {
bold = 1, bold = 1,
italic = 1 << 1, faint = 1 << 1,
underline = 1 << 2, italic = 1 << 2,
strikethrough = 1 << 3 underline = 1 << 3,
blink = 1 << 4,
reverse = 1 << 5,
conceal = 1 << 6,
strikethrough = 1 << 7,
}; };
// rgb is a struct for red, green and blue colors. // rgb is a struct for red, green and blue colors.
@ -409,16 +413,18 @@ template <typename Char> struct ansi_color_escape {
buffer[19] = static_cast<Char>(0); buffer[19] = static_cast<Char>(0);
} }
FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT { FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT {
uint8_t em_codes[4] = {}; uint8_t em_codes[num_emphases] = {};
uint8_t em_bits = static_cast<uint8_t>(em); if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1;
if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1; if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2;
if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3; if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3;
if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4; if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4;
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough)) if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5;
em_codes[3] = 9; if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7;
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
size_t index = 0; size_t index = 0;
for (int i = 0; i < 4; ++i) { for (size_t i = 0; i < num_emphases; ++i) {
if (!em_codes[i]) continue; if (!em_codes[i]) continue;
buffer[index++] = static_cast<Char>('\x1b'); buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('['); buffer[index++] = static_cast<Char>('[');
@ -435,7 +441,8 @@ template <typename Char> struct ansi_color_escape {
} }
private: private:
Char buffer[7u + 3u * 4u + 1u]; static constexpr size_t num_emphases = 8;
Char buffer[7u + 3u * num_emphases + 1u];
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) FMT_NOEXCEPT { char delimiter) FMT_NOEXCEPT {
@ -444,6 +451,10 @@ template <typename Char> struct ansi_color_escape {
out[2] = static_cast<Char>('0' + c % 10); out[2] = static_cast<Char>('0' + c % 10);
out[3] = static_cast<Char>(delimiter); out[3] = static_cast<Char>(delimiter);
} }
static FMT_CONSTEXPR bool has_emphasis(emphasis em,
emphasis mask) FMT_NOEXCEPT {
return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);
}
}; };
template <typename Char> template <typename Char>

@ -156,7 +156,7 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
std::string s = fmt::format(FMT_COMPILE("{}"), 42); std::string s = fmt::format(FMT_COMPILE("{}"), 42);
\endrst \endrst
*/ */
#ifdef __cpp_if_constexpr #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
# define FMT_COMPILE(s) \ # define FMT_COMPILE(s) \
FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit) FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit)
#else #else
@ -179,7 +179,7 @@ const T& first(const T& value, const Tail&...) {
return value; return value;
} }
#ifdef __cpp_if_constexpr #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename... Args> struct type_list {}; template <typename... Args> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...]. // Returns a reference to the argument at index N from [first, rest...].
@ -190,7 +190,7 @@ constexpr const auto& get([[maybe_unused]] const T& first,
if constexpr (N == 0) if constexpr (N == 0)
return first; return first;
else else
return get<N - 1>(rest...); return detail::get<N - 1>(rest...);
} }
template <typename Char, typename... Args> template <typename Char, typename... Args>
@ -202,7 +202,8 @@ constexpr int get_arg_index_by_name(basic_string_view<Char> name,
template <int N, typename> struct get_type_impl; template <int N, typename> struct get_type_impl;
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> { template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>; using type =
remove_cvref_t<decltype(detail::get<N>(std::declval<Args>()...))>;
}; };
template <int N, typename T> template <int N, typename T>
@ -242,7 +243,7 @@ template <typename Char> struct code_unit {
// This ensures that the argument type is convertible to `const T&`. // This ensures that the argument type is convertible to `const T&`.
template <typename T, int N, typename... Args> template <typename T, int N, typename... Args>
constexpr const T& get_arg_checked(const Args&... args) { constexpr const T& get_arg_checked(const Args&... args) {
const auto& arg = get<N>(args...); const auto& arg = detail::get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) { if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value; return arg.value;
} else { } else {
@ -289,7 +290,7 @@ template <typename Char> struct runtime_named_field {
constexpr OutputIt format(OutputIt out, const Args&... args) const { constexpr OutputIt format(OutputIt out, const Args&... args) const {
bool found = (try_format_argument(out, name, args) || ...); bool found = (try_format_argument(out, name, args) || ...);
if (!found) { if (!found) {
throw format_error("argument with specified name is not found"); FMT_THROW(format_error("argument with specified name is not found"));
} }
return out; return out;
} }
@ -399,7 +400,9 @@ template <typename Char> struct arg_id_handler {
return 0; return 0;
} }
constexpr void on_error(const char* message) { throw format_error(message); } constexpr void on_error(const char* message) {
FMT_THROW(format_error(message));
}
}; };
template <typename Char> struct parse_arg_id_result { template <typename Char> struct parse_arg_id_result {
@ -451,7 +454,7 @@ constexpr auto compile_format_string(S format_str) {
constexpr auto str = basic_string_view<char_type>(format_str); constexpr auto str = basic_string_view<char_type>(format_str);
if constexpr (str[POS] == '{') { if constexpr (str[POS] == '{') {
if constexpr (POS + 1 == str.size()) if constexpr (POS + 1 == str.size())
throw format_error("unmatched '{' in format string"); FMT_THROW(format_error("unmatched '{' in format string"));
if constexpr (str[POS + 1] == '{') { if constexpr (str[POS + 1] == '{') {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str); return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
@ -500,7 +503,7 @@ constexpr auto compile_format_string(S format_str) {
} }
} else if constexpr (str[POS] == '}') { } else if constexpr (str[POS] == '}') {
if constexpr (POS + 1 == str.size()) if constexpr (POS + 1 == str.size())
throw format_error("unmatched '}' in format string"); FMT_THROW(format_error("unmatched '}' in format string"));
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str); return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else { } else {
constexpr auto end = parse_text(str, POS + 1); constexpr auto end = parse_text(str, POS + 1);
@ -527,12 +530,12 @@ constexpr auto compile(S format_str) {
return result; return result;
} }
} }
#endif // __cpp_if_constexpr #endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
} // namespace detail } // namespace detail
FMT_MODULE_EXPORT_BEGIN FMT_MODULE_EXPORT_BEGIN
#ifdef __cpp_if_constexpr #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename CompiledFormat, typename... Args, template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type, typename Char = typename CompiledFormat::char_type,

File diff suppressed because it is too large Load Diff

@ -0,0 +1,27 @@
Copyright (c) 2012 - present, Victor Zverovich
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--- Optional exception to the license ---
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into a machine-executable object form of such
source code, you may redistribute such embedded portions in such object form
without including the above copyright and permission notices.

@ -40,6 +40,10 @@ FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
std::terminate(); std::terminate();
} }
FMT_FUNC void throw_format_error(const char* message) {
FMT_THROW(format_error(message));
}
#ifndef _MSC_VER #ifndef _MSC_VER
# define FMT_SNPRINTF snprintf # define FMT_SNPRINTF snprintf
#else // _MSC_VER #else // _MSC_VER
@ -145,14 +149,76 @@ template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) {
return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1; return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1;
} }
// log10(2) = 0x0.4d104d427de7fbcc...
static constexpr uint64_t log10_2_significand = 0x4d104d427de7fbcc;
template <typename T = void> struct basic_impl_data {
// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340.
// These are generated by support/compute-powers.py.
static constexpr uint64_t pow10_significands[87] = {
0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76,
0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df,
0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c,
0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5,
0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57,
0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7,
0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e,
0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996,
0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126,
0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053,
0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f,
0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b,
0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06,
0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb,
0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000,
0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984,
0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068,
0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8,
0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758,
0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85,
0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d,
0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25,
0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2,
0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a,
0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410,
0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129,
0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85,
0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841,
0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b,
};
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wnarrowing"
#endif
// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding
// to significands above.
static constexpr int16_t pow10_exponents[87] = {
-1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954,
-927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661,
-635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369,
-343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77,
-50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216,
242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508,
534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800,
827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066};
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
# pragma GCC diagnostic pop
#endif
static constexpr uint64_t power_of_10_64[20] = {
1, FMT_POWERS_OF_10(1ULL), FMT_POWERS_OF_10(1000000000ULL),
10000000000000000000ULL};
};
// This is a struct rather than an alias to avoid shadowing warnings in gcc.
struct impl_data : basic_impl_data<> {};
#if __cplusplus < 201703L #if __cplusplus < 201703L
template <typename T> constexpr const char basic_data<T>::digits[][2];
template <typename T> constexpr const char basic_data<T>::hex_digits[];
template <typename T> constexpr const char basic_data<T>::signs[];
template <typename T> constexpr const unsigned basic_data<T>::prefixes[];
template <typename T> constexpr const char basic_data<T>::left_padding_shifts[];
template <typename T> template <typename T>
constexpr const char basic_data<T>::right_padding_shifts[]; constexpr uint64_t basic_impl_data<T>::pow10_significands[];
template <typename T> constexpr int16_t basic_impl_data<T>::pow10_exponents[];
template <typename T> constexpr uint64_t basic_impl_data<T>::power_of_10_64[];
#endif #endif
template <typename T> struct bits { template <typename T> struct bits {
@ -160,93 +226,76 @@ template <typename T> struct bits {
static_cast<int>(sizeof(T) * std::numeric_limits<unsigned char>::digits); static_cast<int>(sizeof(T) * std::numeric_limits<unsigned char>::digits);
}; };
class fp; // Returns the number of significand bits in Float excluding the implicit bit.
template <int SHIFT = 0> fp normalize(fp value); template <typename Float> constexpr int num_significand_bits() {
// Subtract 1 to account for an implicit most significant bit in the
// Lower (upper) boundary is a value half way between a floating-point value // normalized form.
// and its predecessor (successor). Boundaries have the same exponent as the return std::numeric_limits<Float>::digits - 1;
// value so only significands are stored. }
struct boundaries {
uint64_t lower;
uint64_t upper;
};
// A handmade floating-point number f * pow(2, e).
class fp {
private:
using significand_type = uint64_t;
template <typename Float>
using is_supported_float = bool_constant<sizeof(Float) == sizeof(uint64_t) ||
sizeof(Float) == sizeof(uint32_t)>;
public: // A floating-point number f * pow(2, e).
significand_type f; struct fp {
uint64_t f;
int e; int e;
// All sizes are in bits. static constexpr const int num_significand_bits = bits<decltype(f)>::value;
// Subtract 1 to account for an implicit most significant bit in the
// normalized form. constexpr fp() : f(0), e(0) {}
static FMT_CONSTEXPR_DECL const int double_significand_size = constexpr fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {}
std::numeric_limits<double>::digits - 1;
static FMT_CONSTEXPR_DECL const uint64_t implicit_bit =
1ULL << double_significand_size;
static FMT_CONSTEXPR_DECL const int significand_size =
bits<significand_type>::value;
fp() : f(0), e(0) {} // Constructs fp from an IEEE754 floating-point number. It is a template to
fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {} // prevent compile errors on systems where n is not IEEE754.
template <typename Float> explicit FMT_CONSTEXPR fp(Float n) { assign(n); }
// Constructs fp from an IEEE754 double. It is a template to prevent compile template <typename Float>
// errors on platforms where double is not IEEE754. using is_supported = bool_constant<sizeof(Float) == sizeof(uint64_t) ||
template <typename Double> explicit fp(Double d) { assign(d); } sizeof(Float) == sizeof(uint32_t)>;
// Assigns d to this and return true iff predecessor is closer than successor. // Assigns d to this and return true iff predecessor is closer than successor.
template <typename Float, FMT_ENABLE_IF(is_supported_float<Float>::value)> template <typename Float, FMT_ENABLE_IF(is_supported<Float>::value)>
bool assign(Float d) { FMT_CONSTEXPR bool assign(Float n) {
// Assume float is in the format [sign][exponent][significand]. // Assume float is in the format [sign][exponent][significand].
using limits = std::numeric_limits<Float>; const int num_float_significand_bits =
const int float_significand_size = limits::digits - 1; detail::num_significand_bits<Float>();
const int exponent_size = const uint64_t implicit_bit = 1ULL << num_float_significand_bits;
bits<Float>::value - float_significand_size - 1; // -1 for sign const uint64_t significand_mask = implicit_bit - 1;
const uint64_t float_implicit_bit = 1ULL << float_significand_size;
const uint64_t significand_mask = float_implicit_bit - 1;
const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask;
const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1;
constexpr bool is_double = sizeof(Float) == sizeof(uint64_t); constexpr bool is_double = sizeof(Float) == sizeof(uint64_t);
auto u = bit_cast<conditional_t<is_double, uint64_t, uint32_t>>(d); auto u = bit_cast<conditional_t<is_double, uint64_t, uint32_t>>(n);
f = u & significand_mask; f = u & significand_mask;
const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask;
int biased_e = int biased_e =
static_cast<int>((u & exponent_mask) >> float_significand_size); static_cast<int>((u & exponent_mask) >> num_float_significand_bits);
// Predecessor is closer if d is a normalized power of 2 (f == 0) other than // The predecessor is closer if n is a normalized power of 2 (f == 0) other
// the smallest normalized number (biased_e > 1). // than the smallest normalized number (biased_e > 1).
bool is_predecessor_closer = f == 0 && biased_e > 1; bool is_predecessor_closer = f == 0 && biased_e > 1;
if (biased_e != 0) if (biased_e != 0)
f += float_implicit_bit; f += implicit_bit;
else else
biased_e = 1; // Subnormals use biased exponent 1 (min exponent). biased_e = 1; // Subnormals use biased exponent 1 (min exponent).
e = biased_e - exponent_bias - float_significand_size; const int exponent_bias = std::numeric_limits<Float>::max_exponent - 1;
e = biased_e - exponent_bias - num_float_significand_bits;
return is_predecessor_closer; return is_predecessor_closer;
} }
template <typename Float, FMT_ENABLE_IF(!is_supported_float<Float>::value)> template <typename Float, FMT_ENABLE_IF(!is_supported<Float>::value)>
bool assign(Float) { bool assign(Float) {
*this = fp(); FMT_ASSERT(false, "");
return false; return false;
} }
}; };
// Normalizes the value converted from double and multiplied by (1 << SHIFT). // Normalizes the value converted from double and multiplied by (1 << SHIFT).
template <int SHIFT> fp normalize(fp value) { template <int SHIFT = 0> FMT_CONSTEXPR fp normalize(fp value) {
// Handle subnormals. // Handle subnormals.
const auto shifted_implicit_bit = fp::implicit_bit << SHIFT; const uint64_t implicit_bit = 1ULL << num_significand_bits<double>();
const auto shifted_implicit_bit = implicit_bit << SHIFT;
while ((value.f & shifted_implicit_bit) == 0) { while ((value.f & shifted_implicit_bit) == 0) {
value.f <<= 1; value.f <<= 1;
--value.e; --value.e;
} }
// Subtract 1 to account for hidden bit. // Subtract 1 to account for hidden bit.
const auto offset = const auto offset =
fp::significand_size - fp::double_significand_size - SHIFT - 1; fp::num_significand_bits - num_significand_bits<double>() - SHIFT - 1;
value.f <<= offset; value.f <<= offset;
value.e -= offset; value.e -= offset;
return value; return value;
@ -255,7 +304,7 @@ template <int SHIFT> fp normalize(fp value) {
inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; } inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; }
// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. // Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking.
inline uint64_t multiply(uint64_t lhs, uint64_t rhs) { FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) {
#if FMT_USE_INT128 #if FMT_USE_INT128
auto product = static_cast<__uint128_t>(lhs) * rhs; auto product = static_cast<__uint128_t>(lhs) * rhs;
auto f = static_cast<uint64_t>(product >> 64); auto f = static_cast<uint64_t>(product >> 64);
@ -272,61 +321,18 @@ inline uint64_t multiply(uint64_t lhs, uint64_t rhs) {
#endif #endif
} }
inline fp operator*(fp x, fp y) { return {multiply(x.f, y.f), x.e + y.e + 64}; } FMT_CONSTEXPR inline fp operator*(fp x, fp y) {
return {multiply(x.f, y.f), x.e + y.e + 64};
}
// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its // Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its
// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`. // (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`.
inline fp get_cached_power(int min_exponent, int& pow10_exponent) { FMT_CONSTEXPR inline fp get_cached_power(int min_exponent,
// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. int& pow10_exponent) {
// These are generated by support/compute-powers.py.
static constexpr const uint64_t pow10_significands[] = {
0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76,
0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df,
0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c,
0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5,
0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57,
0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7,
0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e,
0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996,
0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126,
0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053,
0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f,
0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b,
0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06,
0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb,
0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000,
0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984,
0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068,
0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8,
0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758,
0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85,
0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d,
0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25,
0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2,
0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a,
0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410,
0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129,
0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85,
0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841,
0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b,
};
// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding
// to significands above.
static constexpr const int16_t pow10_exponents[] = {
-1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954,
-927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661,
-635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369,
-343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77,
-50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216,
242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508,
534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800,
827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066};
const int shift = 32; const int shift = 32;
const auto significand = static_cast<int64_t>(data::log10_2_significand); const auto significand = static_cast<int64_t>(log10_2_significand);
int index = static_cast<int>( int index = static_cast<int>(
((min_exponent + fp::significand_size - 1) * (significand >> shift) + ((min_exponent + fp::num_significand_bits - 1) * (significand >> shift) +
((int64_t(1) << shift) - 1)) // ceil ((int64_t(1) << shift) - 1)) // ceil
>> 32 // arithmetic shift >> 32 // arithmetic shift
); );
@ -336,7 +342,8 @@ inline fp get_cached_power(int min_exponent, int& pow10_exponent) {
const int dec_exp_step = 8; const int dec_exp_step = 8;
index = (index - first_dec_exp - 1) / dec_exp_step + 1; index = (index - first_dec_exp - 1) / dec_exp_step + 1;
pow10_exponent = first_dec_exp + index * dec_exp_step; pow10_exponent = first_dec_exp + index * dec_exp_step;
return {pow10_significands[index], pow10_exponents[index]}; return {impl_data::pow10_significands[index],
impl_data::pow10_exponents[index]};
} }
// A simple accumulator to hold the sums of terms in bigint::square if uint128_t // A simple accumulator to hold the sums of terms in bigint::square if uint128_t
@ -345,14 +352,16 @@ struct accumulator {
uint64_t lower; uint64_t lower;
uint64_t upper; uint64_t upper;
accumulator() : lower(0), upper(0) {} constexpr accumulator() : lower(0), upper(0) {}
explicit operator uint32_t() const { return static_cast<uint32_t>(lower); } constexpr explicit operator uint32_t() const {
return static_cast<uint32_t>(lower);
}
void operator+=(uint64_t n) { FMT_CONSTEXPR void operator+=(uint64_t n) {
lower += n; lower += n;
if (lower < n) ++upper; if (lower < n) ++upper;
} }
void operator>>=(int shift) { FMT_CONSTEXPR void operator>>=(int shift) {
FMT_ASSERT(shift == 32, ""); FMT_ASSERT(shift == 32, "");
(void)shift; (void)shift;
lower = (upper << 32) | (lower >> 32); lower = (upper << 32) | (lower >> 32);
@ -370,27 +379,31 @@ class bigint {
basic_memory_buffer<bigit, bigits_capacity> bigits_; basic_memory_buffer<bigit, bigits_capacity> bigits_;
int exp_; int exp_;
bigit operator[](int index) const { return bigits_[to_unsigned(index)]; } FMT_CONSTEXPR20 bigit operator[](int index) const {
bigit& operator[](int index) { return bigits_[to_unsigned(index)]; } return bigits_[to_unsigned(index)];
}
FMT_CONSTEXPR20 bigit& operator[](int index) {
return bigits_[to_unsigned(index)];
}
static FMT_CONSTEXPR_DECL const int bigit_bits = bits<bigit>::value; static FMT_CONSTEXPR_DECL const int bigit_bits = bits<bigit>::value;
friend struct formatter<bigint>; friend struct formatter<bigint>;
void subtract_bigits(int index, bigit other, bigit& borrow) { FMT_CONSTEXPR20 void subtract_bigits(int index, bigit other, bigit& borrow) {
auto result = static_cast<double_bigit>((*this)[index]) - other - borrow; auto result = static_cast<double_bigit>((*this)[index]) - other - borrow;
(*this)[index] = static_cast<bigit>(result); (*this)[index] = static_cast<bigit>(result);
borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1)); borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1));
} }
void remove_leading_zeros() { FMT_CONSTEXPR20 void remove_leading_zeros() {
int num_bigits = static_cast<int>(bigits_.size()) - 1; int num_bigits = static_cast<int>(bigits_.size()) - 1;
while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits; while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits;
bigits_.resize(to_unsigned(num_bigits + 1)); bigits_.resize(to_unsigned(num_bigits + 1));
} }
// Computes *this -= other assuming aligned bigints and *this >= other. // Computes *this -= other assuming aligned bigints and *this >= other.
void subtract_aligned(const bigint& other) { FMT_CONSTEXPR20 void subtract_aligned(const bigint& other) {
FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints");
FMT_ASSERT(compare(*this, other) >= 0, ""); FMT_ASSERT(compare(*this, other) >= 0, "");
bigit borrow = 0; bigit borrow = 0;
@ -401,7 +414,7 @@ class bigint {
remove_leading_zeros(); remove_leading_zeros();
} }
void multiply(uint32_t value) { FMT_CONSTEXPR20 void multiply(uint32_t value) {
const double_bigit wide_value = value; const double_bigit wide_value = value;
bigit carry = 0; bigit carry = 0;
for (size_t i = 0, n = bigits_.size(); i < n; ++i) { for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
@ -412,7 +425,7 @@ class bigint {
if (carry != 0) bigits_.push_back(carry); if (carry != 0) bigits_.push_back(carry);
} }
void multiply(uint64_t value) { FMT_CONSTEXPR20 void multiply(uint64_t value) {
const bigit mask = ~bigit(0); const bigit mask = ~bigit(0);
const double_bigit lower = value & mask; const double_bigit lower = value & mask;
const double_bigit upper = value >> bigit_bits; const double_bigit upper = value >> bigit_bits;
@ -430,14 +443,16 @@ class bigint {
} }
public: public:
bigint() : exp_(0) {} FMT_CONSTEXPR20 bigint() : exp_(0) {}
explicit bigint(uint64_t n) { assign(n); } explicit bigint(uint64_t n) { assign(n); }
~bigint() { FMT_ASSERT(bigits_.capacity() <= bigits_capacity, ""); } FMT_CONSTEXPR20 ~bigint() {
FMT_ASSERT(bigits_.capacity() <= bigits_capacity, "");
}
bigint(const bigint&) = delete; bigint(const bigint&) = delete;
void operator=(const bigint&) = delete; void operator=(const bigint&) = delete;
void assign(const bigint& other) { FMT_CONSTEXPR20 void assign(const bigint& other) {
auto size = other.bigits_.size(); auto size = other.bigits_.size();
bigits_.resize(size); bigits_.resize(size);
auto data = other.bigits_.data(); auto data = other.bigits_.data();
@ -445,7 +460,7 @@ class bigint {
exp_ = other.exp_; exp_ = other.exp_;
} }
void assign(uint64_t n) { FMT_CONSTEXPR20 void assign(uint64_t n) {
size_t num_bigits = 0; size_t num_bigits = 0;
do { do {
bigits_[num_bigits++] = n & ~bigit(0); bigits_[num_bigits++] = n & ~bigit(0);
@ -455,9 +470,11 @@ class bigint {
exp_ = 0; exp_ = 0;
} }
int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; } FMT_CONSTEXPR20 int num_bigits() const {
return static_cast<int>(bigits_.size()) + exp_;
}
FMT_NOINLINE bigint& operator<<=(int shift) { FMT_NOINLINE FMT_CONSTEXPR20 bigint& operator<<=(int shift) {
FMT_ASSERT(shift >= 0, ""); FMT_ASSERT(shift >= 0, "");
exp_ += shift / bigit_bits; exp_ += shift / bigit_bits;
shift %= bigit_bits; shift %= bigit_bits;
@ -472,13 +489,13 @@ class bigint {
return *this; return *this;
} }
template <typename Int> bigint& operator*=(Int value) { template <typename Int> FMT_CONSTEXPR20 bigint& operator*=(Int value) {
FMT_ASSERT(value > 0, ""); FMT_ASSERT(value > 0, "");
multiply(uint32_or_64_or_128_t<Int>(value)); multiply(uint32_or_64_or_128_t<Int>(value));
return *this; return *this;
} }
friend int compare(const bigint& lhs, const bigint& rhs) { friend FMT_CONSTEXPR20 int compare(const bigint& lhs, const bigint& rhs) {
int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits(); int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits();
if (num_lhs_bigits != num_rhs_bigits) if (num_lhs_bigits != num_rhs_bigits)
return num_lhs_bigits > num_rhs_bigits ? 1 : -1; return num_lhs_bigits > num_rhs_bigits ? 1 : -1;
@ -495,7 +512,7 @@ class bigint {
} }
// Returns compare(lhs1 + lhs2, rhs). // Returns compare(lhs1 + lhs2, rhs).
friend int add_compare(const bigint& lhs1, const bigint& lhs2, friend FMT_CONSTEXPR20 int add_compare(const bigint& lhs1, const bigint& lhs2,
const bigint& rhs) { const bigint& rhs) {
int max_lhs_bigits = (std::max)(lhs1.num_bigits(), lhs2.num_bigits()); int max_lhs_bigits = (std::max)(lhs1.num_bigits(), lhs2.num_bigits());
int num_rhs_bigits = rhs.num_bigits(); int num_rhs_bigits = rhs.num_bigits();
@ -519,7 +536,7 @@ class bigint {
} }
// Assigns pow(10, exp) to this bigint. // Assigns pow(10, exp) to this bigint.
void assign_pow10(int exp) { FMT_CONSTEXPR20 void assign_pow10(int exp) {
FMT_ASSERT(exp >= 0, ""); FMT_ASSERT(exp >= 0, "");
if (exp == 0) return assign(1); if (exp == 0) return assign(1);
// Find the top bit. // Find the top bit.
@ -538,7 +555,7 @@ class bigint {
*this <<= exp; // Multiply by pow(2, exp) by shifting. *this <<= exp; // Multiply by pow(2, exp) by shifting.
} }
void square() { FMT_CONSTEXPR20 void square() {
int num_bigits = static_cast<int>(bigits_.size()); int num_bigits = static_cast<int>(bigits_.size());
int num_result_bigits = 2 * num_bigits; int num_result_bigits = 2 * num_bigits;
basic_memory_buffer<bigit, bigits_capacity> n(std::move(bigits_)); basic_memory_buffer<bigit, bigits_capacity> n(std::move(bigits_));
@ -569,7 +586,7 @@ class bigint {
// If this bigint has a bigger exponent than other, adds trailing zero to make // If this bigint has a bigger exponent than other, adds trailing zero to make
// exponents equal. This simplifies some operations such as subtraction. // exponents equal. This simplifies some operations such as subtraction.
void align(const bigint& other) { FMT_CONSTEXPR20 void align(const bigint& other) {
int exp_difference = exp_ - other.exp_; int exp_difference = exp_ - other.exp_;
if (exp_difference <= 0) return; if (exp_difference <= 0) return;
int num_bigits = static_cast<int>(bigits_.size()); int num_bigits = static_cast<int>(bigits_.size());
@ -582,7 +599,7 @@ class bigint {
// Divides this bignum by divisor, assigning the remainder to this and // Divides this bignum by divisor, assigning the remainder to this and
// returning the quotient. // returning the quotient.
int divmod_assign(const bigint& divisor) { FMT_CONSTEXPR20 int divmod_assign(const bigint& divisor) {
FMT_ASSERT(this != &divisor, ""); FMT_ASSERT(this != &divisor, "");
if (compare(*this, divisor) < 0) return 0; if (compare(*this, divisor) < 0) return 0;
FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, "");
@ -602,7 +619,8 @@ enum class round_direction { unknown, up, down };
// some number v and the error, returns whether v should be rounded up, down, or // some number v and the error, returns whether v should be rounded up, down, or
// whether the rounding direction can't be determined due to error. // whether the rounding direction can't be determined due to error.
// error should be less than divisor / 2. // error should be less than divisor / 2.
inline round_direction get_round_direction(uint64_t divisor, uint64_t remainder, FMT_CONSTEXPR inline round_direction get_round_direction(uint64_t divisor,
uint64_t remainder,
uint64_t error) { uint64_t error) {
FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow. FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow.
FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow. FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow.
@ -626,19 +644,52 @@ enum result {
}; };
} }
inline uint64_t power_of_10_64(int exp) { struct gen_digits_handler {
static constexpr const uint64_t data[] = {1, FMT_POWERS_OF_10(1), char* buf;
FMT_POWERS_OF_10(1000000000ULL), int size;
10000000000000000000ULL}; int precision;
return data[exp]; int exp10;
bool fixed;
FMT_CONSTEXPR digits::result on_digit(char digit, uint64_t divisor,
uint64_t remainder, uint64_t error,
bool integral) {
FMT_ASSERT(remainder < divisor, "");
buf[size++] = digit;
if (!integral && error >= remainder) return digits::error;
if (size < precision) return digits::more;
if (!integral) {
// Check if error * 2 < divisor with overflow prevention.
// The check is not needed for the integral part because error = 1
// and divisor > (1 << 32) there.
if (error >= divisor || error >= divisor - error) return digits::error;
} else {
FMT_ASSERT(error == 1 && divisor > 2, "");
} }
auto dir = get_round_direction(divisor, remainder, error);
if (dir != round_direction::up)
return dir == round_direction::down ? digits::done : digits::error;
++buf[size - 1];
for (int i = size - 1; i > 0 && buf[i] > '9'; --i) {
buf[i] = '0';
++buf[i - 1];
}
if (buf[0] > '9') {
buf[0] = '1';
if (fixed)
buf[size++] = '0';
else
++exp10;
}
return digits::done;
}
};
// Generates output using the Grisu digit-gen algorithm. // Generates output using the Grisu digit-gen algorithm.
// error: the size of the region (lower, upper) outside of which numbers // error: the size of the region (lower, upper) outside of which numbers
// definitely do not round to value (Delta in Grisu3). // definitely do not round to value (Delta in Grisu3).
template <typename Handler> FMT_INLINE FMT_CONSTEXPR20 digits::result grisu_gen_digits(
FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp, fp value, uint64_t error, int& exp, gen_digits_handler& handler) {
Handler& handler) {
const fp one(1ULL << -value.e, value.e); const fp one(1ULL << -value.e, value.e);
// The integral part of scaled value (p1 in Grisu) = value / one. It cannot be // The integral part of scaled value (p1 in Grisu) = value / one. It cannot be
// zero because it contains a product of two 64-bit numbers with MSB set (due // zero because it contains a product of two 64-bit numbers with MSB set (due
@ -649,10 +700,28 @@ FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp,
// The fractional part of scaled value (p2 in Grisu) c = value % one. // The fractional part of scaled value (p2 in Grisu) c = value % one.
uint64_t fractional = value.f & (one.f - 1); uint64_t fractional = value.f & (one.f - 1);
exp = count_digits(integral); // kappa in Grisu. exp = count_digits(integral); // kappa in Grisu.
// Non-fixed formats require at least one digit and no precision adjustment.
if (handler.fixed) {
// Adjust fixed precision by exponent because it is relative to decimal
// point.
int precision_offset = exp + handler.exp10;
if (precision_offset > 0 &&
handler.precision > max_value<int>() - precision_offset) {
FMT_THROW(format_error("number is too big"));
}
handler.precision += precision_offset;
// Check if precision is satisfied just by leading zeros, e.g.
// format("{:.2f}", 0.001) gives "0.00" without generating any digits.
if (handler.precision <= 0) {
if (handler.precision < 0) return digits::done;
// Divide by 10 to prevent overflow. // Divide by 10 to prevent overflow.
auto result = handler.on_start(power_of_10_64(exp - 1) << -one.e, uint64_t divisor = impl_data::power_of_10_64[exp - 1] << -one.e;
value.f / 10, error * 10, exp); auto dir = get_round_direction(divisor, value.f / 10, error * 10);
if (result != digits::more) return result; if (dir == round_direction::unknown) return digits::error;
handler.buf[handler.size++] = dir == round_direction::up ? '1' : '0';
return digits::done;
}
}
// Generate digits for the integral part. This can produce up to 10 digits. // Generate digits for the integral part. This can produce up to 10 digits.
do { do {
uint32_t digit = 0; uint32_t digit = 0;
@ -699,9 +768,9 @@ FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp,
} }
--exp; --exp;
auto remainder = (static_cast<uint64_t>(integral) << -one.e) + fractional; auto remainder = (static_cast<uint64_t>(integral) << -one.e) + fractional;
result = handler.on_digit(static_cast<char>('0' + digit), auto result = handler.on_digit(static_cast<char>('0' + digit),
power_of_10_64(exp) << -one.e, remainder, error, impl_data::power_of_10_64[exp] << -one.e,
exp, true); remainder, error, true);
if (result != digits::more) return result; if (result != digits::more) return result;
} while (exp > 0); } while (exp > 0);
// Generate digits for the fractional part. // Generate digits for the fractional part.
@ -711,69 +780,11 @@ FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp,
char digit = static_cast<char>('0' + (fractional >> -one.e)); char digit = static_cast<char>('0' + (fractional >> -one.e));
fractional &= one.f - 1; fractional &= one.f - 1;
--exp; --exp;
result = handler.on_digit(digit, one.f, fractional, error, exp, false); auto result = handler.on_digit(digit, one.f, fractional, error, false);
if (result != digits::more) return result; if (result != digits::more) return result;
} }
} }
// The fixed precision digit handler.
struct fixed_handler {
char* buf;
int size;
int precision;
int exp10;
bool fixed;
digits::result on_start(uint64_t divisor, uint64_t remainder, uint64_t error,
int& exp) {
// Non-fixed formats require at least one digit and no precision adjustment.
if (!fixed) return digits::more;
// Adjust fixed precision by exponent because it is relative to decimal
// point.
precision += exp + exp10;
// Check if precision is satisfied just by leading zeros, e.g.
// format("{:.2f}", 0.001) gives "0.00" without generating any digits.
if (precision > 0) return digits::more;
if (precision < 0) return digits::done;
auto dir = get_round_direction(divisor, remainder, error);
if (dir == round_direction::unknown) return digits::error;
buf[size++] = dir == round_direction::up ? '1' : '0';
return digits::done;
}
digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder,
uint64_t error, int, bool integral) {
FMT_ASSERT(remainder < divisor, "");
buf[size++] = digit;
if (!integral && error >= remainder) return digits::error;
if (size < precision) return digits::more;
if (!integral) {
// Check if error * 2 < divisor with overflow prevention.
// The check is not needed for the integral part because error = 1
// and divisor > (1 << 32) there.
if (error >= divisor || error >= divisor - error) return digits::error;
} else {
FMT_ASSERT(error == 1 && divisor > 2, "");
}
auto dir = get_round_direction(divisor, remainder, error);
if (dir != round_direction::up)
return dir == round_direction::down ? digits::done : digits::error;
++buf[size - 1];
for (int i = size - 1; i > 0 && buf[i] > '9'; --i) {
buf[i] = '0';
++buf[i - 1];
}
if (buf[0] > '9') {
buf[0] = '1';
if (fixed)
buf[size++] = '0';
else
++exp10;
}
return digits::done;
}
};
// A 128-bit integer type used internally, // A 128-bit integer type used internally,
struct uint128_wrapper { struct uint128_wrapper {
uint128_wrapper() = default; uint128_wrapper() = default;
@ -897,8 +908,7 @@ inline uint64_t umul96_lower64(uint32_t x, uint64_t y) FMT_NOEXCEPT {
inline int floor_log10_pow2(int e) FMT_NOEXCEPT { inline int floor_log10_pow2(int e) FMT_NOEXCEPT {
FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent"); FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent");
const int shift = 22; const int shift = 22;
return (e * static_cast<int>(data::log10_2_significand >> (64 - shift))) >> return (e * static_cast<int>(log10_2_significand >> (64 - shift))) >> shift;
shift;
} }
// Various fast log computations. // Various fast log computations.
@ -916,8 +926,7 @@ inline int floor_log10_pow2_minus_log10_4_over_3(int e) FMT_NOEXCEPT {
FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent"); FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent");
const uint64_t log10_4_over_3_fractional_digits = 0x1ffbfc2bbc780375; const uint64_t log10_4_over_3_fractional_digits = 0x1ffbfc2bbc780375;
const int shift_amount = 22; const int shift_amount = 22;
return (e * static_cast<int>(data::log10_2_significand >> return (e * static_cast<int>(log10_2_significand >> (64 - shift_amount)) -
(64 - shift_amount)) -
static_cast<int>(log10_4_over_3_fractional_digits >> static_cast<int>(log10_4_over_3_fractional_digits >>
(64 - shift_amount))) >> (64 - shift_amount))) >>
shift_amount; shift_amount;
@ -1042,7 +1051,7 @@ template <> struct cache_accessor<float> {
static uint64_t get_cached_power(int k) FMT_NOEXCEPT { static uint64_t get_cached_power(int k) FMT_NOEXCEPT {
FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k, FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,
"k is out of range"); "k is out of range");
constexpr const uint64_t pow10_significands[] = { static constexpr const uint64_t pow10_significands[] = {
0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f,
0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb,
0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28,
@ -2210,11 +2219,11 @@ small_divisor_case_label:
} }
} // namespace dragonbox } // namespace dragonbox
// Formats value using a variation of the Fixed-Precision Positive // Formats a floating-point number using a variation of the Fixed-Precision
// Floating-Point Printout ((FPP)^2) algorithm by Steele & White: // Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White:
// https://fmt.dev/papers/p372-steele.pdf. // https://fmt.dev/papers/p372-steele.pdf.
template <typename Double> FMT_CONSTEXPR20 inline void format_dragon(fp value, bool is_predecessor_closer,
void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf, int num_digits, buffer<char>& buf,
int& exp10) { int& exp10) {
bigint numerator; // 2 * R in (FPP)^2. bigint numerator; // 2 * R in (FPP)^2.
bigint denominator; // 2 * S in (FPP)^2. bigint denominator; // 2 * S in (FPP)^2.
@ -2222,12 +2231,9 @@ void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
bigint lower; // (M^- in (FPP)^2). bigint lower; // (M^- in (FPP)^2).
bigint upper_store; // upper's value if different from lower. bigint upper_store; // upper's value if different from lower.
bigint* upper = nullptr; // (M^+ in (FPP)^2). bigint* upper = nullptr; // (M^+ in (FPP)^2).
fp value;
// Shift numerator and denominator by an extra bit or two (if lower boundary // Shift numerator and denominator by an extra bit or two (if lower boundary
// is closer) to make lower and upper integers. This eliminates multiplication // is closer) to make lower and upper integers. This eliminates multiplication
// by 2 during later computations. // by 2 during later computations.
const bool is_predecessor_closer =
binary32 ? value.assign(static_cast<float>(d)) : value.assign(d);
int shift = is_predecessor_closer ? 2 : 1; int shift = is_predecessor_closer ? 2 : 1;
uint64_t significand = value.f << shift; uint64_t significand = value.f << shift;
if (value.e >= 0) { if (value.e >= 0) {
@ -2297,9 +2303,9 @@ void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
// Generate the given number of digits. // Generate the given number of digits.
exp10 -= num_digits - 1; exp10 -= num_digits - 1;
if (num_digits == 0) { if (num_digits == 0) {
buf.try_resize(1);
denominator *= 10; denominator *= 10;
buf[0] = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; auto digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0';
buf.push_back(digit);
return; return;
} }
buf.try_resize(to_unsigned(num_digits)); buf.try_resize(to_unsigned(num_digits));
@ -2330,9 +2336,12 @@ void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
buf[num_digits - 1] = static_cast<char>('0' + digit); buf[num_digits - 1] = static_cast<char>('0' + digit);
} }
template <typename T> template <typename Float>
int format_float(T value, int precision, float_specs specs, buffer<char>& buf) { FMT_HEADER_ONLY_CONSTEXPR20 int format_float(Float value, int precision,
static_assert(!std::is_same<T, float>::value, ""); float_specs specs,
buffer<char>& buf) {
// float is passed as double to reduce the number of instantiations.
static_assert(!std::is_same<Float, float>::value, "");
FMT_ASSERT(value >= 0, "value is negative"); FMT_ASSERT(value >= 0, "value is negative");
const bool fixed = specs.format == float_format::fixed; const bool fixed = specs.format == float_format::fixed;
@ -2342,13 +2351,13 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
return 0; return 0;
} }
buf.try_resize(to_unsigned(precision)); buf.try_resize(to_unsigned(precision));
std::uninitialized_fill_n(buf.data(), precision, '0'); fill_n(buf.data(), precision, '0');
return -precision; return -precision;
} }
if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf); if (specs.fallback) return snprintf_float(value, precision, specs, buf);
if (precision < 0) { if (!is_constant_evaluated() && precision < 0) {
// Use Dragonbox for the shortest format. // Use Dragonbox for the shortest format.
if (specs.binary32) { if (specs.binary32) {
auto dec = dragonbox::to_decimal(static_cast<float>(value)); auto dec = dragonbox::to_decimal(static_cast<float>(value));
@ -2360,26 +2369,37 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
return dec.exponent; return dec.exponent;
} }
int exp = 0;
bool use_dragon = true;
if (is_fast_float<Float>()) {
// Use Grisu + Dragon4 for the given precision: // Use Grisu + Dragon4 for the given precision:
// https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf. // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf.
int exp = 0;
const int min_exp = -60; // alpha in Grisu. const int min_exp = -60; // alpha in Grisu.
int cached_exp10 = 0; // K in Grisu. int cached_exp10 = 0; // K in Grisu.
fp normalized = normalize(fp(value)); fp normalized = normalize(fp(value));
const auto cached_pow = get_cached_power( const auto cached_pow = get_cached_power(
min_exp - (normalized.e + fp::significand_size), cached_exp10); min_exp - (normalized.e + fp::num_significand_bits), cached_exp10);
normalized = normalized * cached_pow; normalized = normalized * cached_pow;
// Limit precision to the maximum possible number of significant digits in an gen_digits_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
// IEEE754 double because we don't need to generate zeros. if (grisu_gen_digits(normalized, 1, exp, handler) != digits::error &&
const int max_double_digits = 767; !is_constant_evaluated()) {
if (precision > max_double_digits) precision = max_double_digits;
fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error) {
exp += handler.size - cached_exp10 - 1;
fallback_format(value, handler.precision, specs.binary32, buf, exp);
} else {
exp += handler.exp10; exp += handler.exp10;
buf.try_resize(to_unsigned(handler.size)); buf.try_resize(to_unsigned(handler.size));
use_dragon = false;
} else {
exp += handler.size - cached_exp10 - 1;
precision = handler.precision;
}
}
if (use_dragon) {
auto f = fp();
bool is_predecessor_closer =
specs.binary32 ? f.assign(static_cast<float>(value)) : f.assign(value);
// Limit precision to the maximum possible number of significant digits in
// an IEEE754 double because we don't need to generate zeros.
const int max_double_digits = 767;
if (precision > max_double_digits) precision = max_double_digits;
format_dragon(f, is_predecessor_closer, precision, buf, exp);
} }
if (!fixed && !specs.showpoint) { if (!fixed && !specs.showpoint) {
// Remove trailing zeros. // Remove trailing zeros.
@ -2391,7 +2411,7 @@ int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
buf.try_resize(num_digits); buf.try_resize(num_digits);
} }
return exp; return exp;
} // namespace detail }
template <typename T> template <typename T>
int snprintf_float(T value, int precision, float_specs specs, int snprintf_float(T value, int precision, float_specs specs,
@ -2525,8 +2545,8 @@ template <> struct formatter<detail::bigint> {
}; };
FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) {
for_each_codepoint(s, [this](uint32_t cp, int error) { for_each_codepoint(s, [this](uint32_t cp, string_view) {
if (error != 0) FMT_THROW(std::runtime_error("invalid utf8")); if (cp == invalid_code_point) FMT_THROW(std::runtime_error("invalid utf8"));
if (cp <= 0xFFFF) { if (cp <= 0xFFFF) {
buffer_.push_back(static_cast<wchar_t>(cp)); buffer_.push_back(static_cast<wchar_t>(cp));
} else { } else {
@ -2534,6 +2554,7 @@ FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) {
buffer_.push_back(static_cast<wchar_t>(0xD800 + (cp >> 10))); buffer_.push_back(static_cast<wchar_t>(0xD800 + (cp >> 10)));
buffer_.push_back(static_cast<wchar_t>(0xDC00 + (cp & 0x3FF))); buffer_.push_back(static_cast<wchar_t>(0xDC00 + (cp & 0x3FF)));
} }
return true;
}); });
buffer_.push_back(0); buffer_.push_back(0);
} }
@ -2549,15 +2570,17 @@ FMT_FUNC void format_system_error(detail::buffer<char>& out, int error_code,
format_error_code(out, error_code, message); format_error_code(out, error_code, message);
} }
FMT_FUNC void detail::error_handler::on_error(const char* message) {
FMT_THROW(format_error(message));
}
FMT_FUNC void report_system_error(int error_code, FMT_FUNC void report_system_error(int error_code,
const char* message) FMT_NOEXCEPT { const char* message) FMT_NOEXCEPT {
report_error(format_system_error, error_code, message); report_error(format_system_error, error_code, message);
} }
// DEPRECATED!
// This function is defined here and not inline for ABI compatiblity.
FMT_FUNC void detail::error_handler::on_error(const char* message) {
throw_format_error(message);
}
FMT_FUNC std::string vformat(string_view fmt, format_args args) { FMT_FUNC std::string vformat(string_view fmt, format_args args) {
// Don't optimize the "{}" case to keep the binary size small and because it // Don't optimize the "{}" case to keep the binary size small and because it
// can be better optimized in fmt::format anyway. // can be better optimized in fmt::format anyway.

File diff suppressed because it is too large Load Diff

@ -21,18 +21,21 @@
#include "format.h" #include "format.h"
#ifndef FMT_USE_FCNTL
// UWP doesn't provide _pipe. // UWP doesn't provide _pipe.
# if FMT_HAS_INCLUDE("winapifamily.h") # if FMT_HAS_INCLUDE("winapifamily.h")
# include <winapifamily.h> # include <winapifamily.h>
# endif # endif
# if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \ # if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \ defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) (!defined(WINAPI_FAMILY) || \
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
# include <fcntl.h> // for O_RDONLY # include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 1 # define FMT_USE_FCNTL 1
# else # else
# define FMT_USE_FCNTL 0 # define FMT_USE_FCNTL 0
# endif # endif
#endif
#ifndef FMT_POSIX #ifndef FMT_POSIX
# if defined(_WIN32) && !defined(__MINGW32__) # if defined(_WIN32) && !defined(__MINGW32__)
@ -390,23 +393,26 @@ struct ostream_params {
: ostream_params(params...) { : ostream_params(params...) {
this->buffer_size = bs.value; this->buffer_size = bs.value;
} }
// Intel has a bug that results in failure to deduce a constructor
// for empty parameter packs.
# if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000
ostream_params(int new_oflag) : oflag(new_oflag) {}
ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {}
# endif
}; };
FMT_END_DETAIL_NAMESPACE FMT_END_DETAIL_NAMESPACE
constexpr detail::buffer_size buffer_size; // Added {} below to work around default constructor error known to
// occur in Xcode versions 7.2.1 and 8.2.1.
constexpr detail::buffer_size buffer_size{};
/** A fast output stream which is not thread-safe. */ /** A fast output stream which is not thread-safe. */
class FMT_API ostream final : private detail::buffer<char> { class FMT_API ostream final : private detail::buffer<char> {
private: private:
file file_; file file_;
void flush() {
if (size() == 0) return;
file_.write(data(), size());
clear();
}
void grow(size_t) override; void grow(size_t) override;
ostream(cstring_view path, const detail::ostream_params& params) ostream(cstring_view path, const detail::ostream_params& params)
@ -426,6 +432,12 @@ class FMT_API ostream final : private detail::buffer<char> {
delete[] data(); delete[] data();
} }
void flush() {
if (size() == 0) return;
file_.write(data(), size());
clear();
}
template <typename... T> template <typename... T>
friend ostream output_file(cstring_view path, T... params); friend ostream output_file(cstring_view path, T... params);
@ -500,7 +512,7 @@ class locale {
// Converts string to floating-point number and advances str past the end // Converts string to floating-point number and advances str past the end
// of the parsed input. // of the parsed input.
double strtod(const char*& str) const { FMT_DEPRECATED double strtod(const char*& str) const {
char* end = nullptr; char* end = nullptr;
double result = strtod_l(str, &end, locale_); double result = strtod_l(str, &end, locale_);
str = end; str = end;

@ -14,73 +14,20 @@
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <typename Char> class basic_printf_parse_context;
template <typename OutputIt, typename Char> class basic_printf_context; template <typename OutputIt, typename Char> class basic_printf_context;
namespace detail { namespace detail {
template <class Char> class formatbuf : public std::basic_streambuf<Char> { // Checks if T has a user-defined operator<<.
private: template <typename T, typename Char, typename Enable = void>
using int_type = typename std::basic_streambuf<Char>::int_type; class is_streamable {
using traits_type = typename std::basic_streambuf<Char>::traits_type;
buffer<Char>& buffer_;
public:
formatbuf(buffer<Char>& buf) : buffer_(buf) {}
protected:
// The put-area is actually always empty. This makes the implementation
// simpler and has the advantage that the streambuf and the buffer are always
// in sync and sputc never writes into uninitialized memory. The obvious
// disadvantage is that each call to sputc always results in a (virtual) call
// to overflow. There is no disadvantage here for sputn since this always
// results in a call to xsputn.
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
if (!traits_type::eq_int_type(ch, traits_type::eof()))
buffer_.push_back(static_cast<Char>(ch));
return ch;
}
std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE {
buffer_.append(s, s + count);
return count;
}
};
struct converter {
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T);
};
template <typename Char> struct test_stream : std::basic_ostream<Char> {
private:
void_t<> operator<<(converter);
};
// Hide insertion operators for built-in types.
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
// Checks if T has a user-defined operator<< (e.g. not a member of
// std::ostream).
template <typename T, typename Char> class is_streamable {
private: private:
template <typename U> template <typename U>
static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>() static auto test(int)
<< std::declval<U>()), -> bool_constant<sizeof(std::declval<std::basic_ostream<Char>&>()
void_t<>>::value> << std::declval<U>()) != 0>;
test(int);
template <typename> static std::false_type test(...); template <typename> static auto test(...) -> std::false_type;
using result = decltype(test<T>(0)); using result = decltype(test<T>(0));
@ -90,7 +37,21 @@ template <typename T, typename Char> class is_streamable {
static const bool value = result::value; static const bool value = result::value;
}; };
// Formatting of built-in types and arrays is intentionally disabled because
// it's handled by standard (non-ostream) formatters.
template <typename T, typename Char>
struct is_streamable<
T, Char,
enable_if_t<
std::is_arithmetic<T>::value || std::is_array<T>::value ||
std::is_pointer<T>::value || std::is_same<T, char8_type>::value ||
std::is_same<T, std::basic_string<Char>>::value ||
std::is_same<T, std_string_view<Char>>::value ||
(std::is_convertible<T, int>::value && !std::is_enum<T>::value)>>
: std::false_type {};
// Write the content of buf to os. // Write the content of buf to os.
// It is a separate function rather than a part of vprint to simplify testing.
template <typename Char> template <typename Char>
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) { void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
const Char* buf_data = buf.data(); const Char* buf_data = buf.data();
@ -108,8 +69,8 @@ void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
template <typename Char, typename T> template <typename Char, typename T>
void format_value(buffer<Char>& buf, const T& value, void format_value(buffer<Char>& buf, const T& value,
locale_ref loc = locale_ref()) { locale_ref loc = locale_ref()) {
formatbuf<Char> format_buf(buf); auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
std::basic_ostream<Char> output(&format_buf); auto&& output = std::basic_ostream<Char>(&format_buf);
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
if (loc) output.imbue(loc.get<std::locale>()); if (loc) output.imbue(loc.get<std::locale>());
#endif #endif
@ -122,29 +83,22 @@ void format_value(buffer<Char>& buf, const T& value,
template <typename T, typename Char> template <typename T, typename Char>
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>> struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
: private formatter<basic_string_view<Char>, Char> { : private formatter<basic_string_view<Char>, Char> {
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx) using formatter<basic_string_view<Char>, Char>::parse;
-> decltype(ctx.begin()) {
return formatter<basic_string_view<Char>, Char>::parse(ctx);
}
template <typename ParseCtx,
FMT_ENABLE_IF(std::is_same<
ParseCtx, basic_printf_parse_context<Char>>::value)>
auto parse(ParseCtx& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename OutputIt> template <typename OutputIt>
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx) auto format(const T& value, basic_format_context<OutputIt, Char>& ctx)
-> OutputIt { -> OutputIt {
basic_memory_buffer<Char> buffer; auto buffer = basic_memory_buffer<Char>();
format_value(buffer, value, ctx.locale()); format_value(buffer, value, ctx.locale());
basic_string_view<Char> str(buffer.data(), buffer.size()); return formatter<basic_string_view<Char>, Char>::format(
return formatter<basic_string_view<Char>, Char>::format(str, ctx); {buffer.data(), buffer.size()}, ctx);
} }
// DEPRECATED!
template <typename OutputIt> template <typename OutputIt>
auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx) auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
-> OutputIt { -> OutputIt {
basic_memory_buffer<Char> buffer; auto buffer = basic_memory_buffer<Char>();
format_value(buffer, value, ctx.locale()); format_value(buffer, value, ctx.locale());
return std::copy(buffer.begin(), buffer.end(), ctx.out()); return std::copy(buffer.begin(), buffer.end(), ctx.out());
} }
@ -155,7 +109,7 @@ FMT_MODULE_EXPORT
template <typename Char> template <typename Char>
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str, void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) { basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer; auto buffer = basic_memory_buffer<Char>();
detail::vformat_to(buffer, format_str, args); detail::vformat_to(buffer, format_str, args);
detail::write_buffer(os, buffer); detail::write_buffer(os, buffer);
} }

@ -233,7 +233,7 @@ class printf_arg_formatter : public arg_formatter<Char> {
OutputIt write_null_pointer(bool is_string = false) { OutputIt write_null_pointer(bool is_string = false) {
auto s = this->specs; auto s = this->specs;
s.type = 0; s.type = presentation_type::none;
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s); return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
} }
@ -249,8 +249,10 @@ class printf_arg_formatter : public arg_formatter<Char> {
// std::is_same instead. // std::is_same instead.
if (std::is_same<T, Char>::value) { if (std::is_same<T, Char>::value) {
format_specs fmt_specs = this->specs; format_specs fmt_specs = this->specs;
if (fmt_specs.type && fmt_specs.type != 'c') if (fmt_specs.type != presentation_type::none &&
fmt_specs.type != presentation_type::chr) {
return (*this)(static_cast<int>(value)); return (*this)(static_cast<int>(value));
}
fmt_specs.sign = sign::none; fmt_specs.sign = sign::none;
fmt_specs.alt = false; fmt_specs.alt = false;
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types. fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
@ -271,13 +273,13 @@ class printf_arg_formatter : public arg_formatter<Char> {
/** Formats a null-terminated C string. */ /** Formats a null-terminated C string. */
OutputIt operator()(const char* value) { OutputIt operator()(const char* value) {
if (value) return base::operator()(value); if (value) return base::operator()(value);
return write_null_pointer(this->specs.type != 'p'); return write_null_pointer(this->specs.type != presentation_type::pointer);
} }
/** Formats a null-terminated wide C string. */ /** Formats a null-terminated wide C string. */
OutputIt operator()(const wchar_t* value) { OutputIt operator()(const wchar_t* value) {
if (value) return base::operator()(value); if (value) return base::operator()(value);
return write_null_pointer(this->specs.type != 'p'); return write_null_pointer(this->specs.type != presentation_type::pointer);
} }
OutputIt operator()(basic_string_view<Char> value) { OutputIt operator()(basic_string_view<Char> value) {
@ -490,13 +492,13 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
// Parse type. // Parse type.
if (it == end) FMT_THROW(format_error("invalid format string")); if (it == end) FMT_THROW(format_error("invalid format string"));
specs.type = static_cast<char>(*it++); char type = static_cast<char>(*it++);
if (arg.is_integral()) { if (arg.is_integral()) {
// Normalize type. // Normalize type.
switch (specs.type) { switch (type) {
case 'i': case 'i':
case 'u': case 'u':
specs.type = 'd'; type = 'd';
break; break;
case 'c': case 'c':
visit_format_arg( visit_format_arg(
@ -505,6 +507,9 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
break; break;
} }
} }
specs.type = parse_presentation_type(type);
if (specs.type == presentation_type::none)
parse_ctx.on_error("invalid type specifier");
start = it; start = it;

@ -13,37 +13,13 @@
#define FMT_RANGES_H_ #define FMT_RANGES_H_
#include <initializer_list> #include <initializer_list>
#include <tuple>
#include <type_traits> #include <type_traits>
#include "format.h" #include "format.h"
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <typename Char, typename Enable = void> struct formatting_range {
#ifdef FMT_DEPRECATED_BRACED_RANGES
Char prefix = '{';
Char postfix = '}';
#else
Char prefix = '[';
Char postfix = ']';
#endif
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
};
template <typename Char, typename Enable = void> struct formatting_tuple {
Char prefix = '(';
Char postfix = ')';
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
};
namespace detail { namespace detail {
template <typename RangeT, typename OutputIterator> template <typename RangeT, typename OutputIterator>
@ -71,7 +47,7 @@ OutputIterator copy(wchar_t ch, OutputIterator out) {
return out; return out;
} }
/// Return true value if T has std::string interface, like std::string_view. // Returns true if T has a std::string-like interface, like std::string_view.
template <typename T> class is_std_string_like { template <typename T> class is_std_string_like {
template <typename U> template <typename U>
static auto check(U* p) static auto check(U* p)
@ -80,12 +56,40 @@ template <typename T> class is_std_string_like {
public: public:
static FMT_CONSTEXPR_DECL const bool value = static FMT_CONSTEXPR_DECL const bool value =
is_string<T>::value || !std::is_void<decltype(check<T>(nullptr))>::value; is_string<T>::value ||
std::is_convertible<T, std_string_view<char>>::value ||
!std::is_void<decltype(check<T>(nullptr))>::value;
}; };
template <typename Char> template <typename Char>
struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {}; struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
template <typename T> class is_map {
template <typename U> static auto check(U*) -> typename U::mapped_type;
template <typename> static void check(...);
public:
#ifdef FMT_FORMAT_MAP_AS_LIST
static FMT_CONSTEXPR_DECL const bool value = false;
#else
static FMT_CONSTEXPR_DECL const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
#endif
};
template <typename T> class is_set {
template <typename U> static auto check(U*) -> typename U::key_type;
template <typename> static void check(...);
public:
#ifdef FMT_FORMAT_SET_AS_LIST
static FMT_CONSTEXPR_DECL const bool value = false;
#else
static FMT_CONSTEXPR_DECL const bool value =
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
#endif
};
template <typename... Ts> struct conditional_helper {}; template <typename... Ts> struct conditional_helper {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {}; template <typename T, typename _ = void> struct is_range_ : std::false_type {};
@ -143,16 +147,16 @@ struct has_mutable_begin_end : std::false_type {};
template <typename T> template <typename T>
struct has_const_begin_end< struct has_const_begin_end<
T, void_t<decltype(detail::range_begin( T,
std::declval<const remove_cvref_t<T>&>())), void_t<
decltype(detail::range_begin( decltype(detail::range_begin(std::declval<const remove_cvref_t<T>&>())),
std::declval<const remove_cvref_t<T>&>()))>> decltype(detail::range_end(std::declval<const remove_cvref_t<T>&>()))>>
: std::true_type {}; : std::true_type {};
template <typename T> template <typename T>
struct has_mutable_begin_end< struct has_mutable_begin_end<
T, void_t<decltype(detail::range_begin(std::declval<T>())), T, void_t<decltype(detail::range_begin(std::declval<T>())),
decltype(detail::range_begin(std::declval<T>())), decltype(detail::range_end(std::declval<T>())),
enable_if_t<std::is_copy_constructible<T>::value>>> enable_if_t<std::is_copy_constructible<T>::value>>>
: std::true_type {}; : std::true_type {};
@ -160,34 +164,10 @@ template <typename T>
struct is_range_<T, void> struct is_range_<T, void>
: std::integral_constant<bool, (has_const_begin_end<T>::value || : std::integral_constant<bool, (has_const_begin_end<T>::value ||
has_mutable_begin_end<T>::value)> {}; has_mutable_begin_end<T>::value)> {};
template <typename T, typename Enable = void> struct range_to_view;
template <typename T>
struct range_to_view<T, enable_if_t<has_const_begin_end<T>::value>> {
struct view_t {
const T* m_range_ptr;
auto begin() const FMT_DECLTYPE_RETURN(detail::range_begin(*m_range_ptr));
auto end() const FMT_DECLTYPE_RETURN(detail::range_end(*m_range_ptr));
};
static auto view(const T& range) -> view_t { return {&range}; }
};
template <typename T>
struct range_to_view<T, enable_if_t<!has_const_begin_end<T>::value &&
has_mutable_begin_end<T>::value>> {
struct view_t {
T m_range_copy;
auto begin() FMT_DECLTYPE_RETURN(detail::range_begin(m_range_copy));
auto end() FMT_DECLTYPE_RETURN(detail::range_end(m_range_copy));
};
static auto view(const T& range) -> view_t { return {range}; }
};
# undef FMT_DECLTYPE_RETURN # undef FMT_DECLTYPE_RETURN
#endif #endif
/// tuple_size and tuple_element check. // tuple_size and tuple_element check.
template <typename T> class is_tuple_like_ { template <typename T> class is_tuple_like_ {
template <typename U> template <typename U>
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int()); static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
@ -251,16 +231,295 @@ template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
return out; return out;
} }
template < struct singleton {
typename Char, typename OutputIt, typename Arg, unsigned char upper;
FMT_ENABLE_IF(is_std_string_like<typename std::decay<Arg>::type>::value)> unsigned char lower_count;
OutputIt write_range_entry(OutputIt out, const Arg& v) { };
inline auto is_printable(uint16_t x, const singleton* singletons,
size_t singletons_size,
const unsigned char* singleton_lowers,
const unsigned char* normal, size_t normal_size)
-> bool {
auto upper = x >> 8;
auto lower_start = 0;
for (size_t i = 0; i < singletons_size; ++i) {
auto s = singletons[i];
auto lower_end = lower_start + s.lower_count;
if (upper < s.upper) break;
if (upper == s.upper) {
for (auto j = lower_start; j < lower_end; ++j) {
if (singleton_lowers[j] == (x & 0xff)) return false;
}
}
lower_start = lower_end;
}
auto xsigned = static_cast<int>(x);
auto current = true;
for (size_t i = 0; i < normal_size; ++i) {
auto v = static_cast<int>(normal[i]);
auto len = (v & 0x80) != 0 ? (v & 0x7f) << 8 | normal[++i] : v;
xsigned -= len;
if (xsigned < 0) break;
current = !current;
}
return current;
}
// Returns true iff the code point cp is printable.
// This code is generated by support/printable.py.
inline auto is_printable(uint32_t cp) -> bool {
static constexpr singleton singletons0[] = {
{0x00, 1}, {0x03, 5}, {0x05, 6}, {0x06, 3}, {0x07, 6}, {0x08, 8},
{0x09, 17}, {0x0a, 28}, {0x0b, 25}, {0x0c, 20}, {0x0d, 16}, {0x0e, 13},
{0x0f, 4}, {0x10, 3}, {0x12, 18}, {0x13, 9}, {0x16, 1}, {0x17, 5},
{0x18, 2}, {0x19, 3}, {0x1a, 7}, {0x1c, 2}, {0x1d, 1}, {0x1f, 22},
{0x20, 3}, {0x2b, 3}, {0x2c, 2}, {0x2d, 11}, {0x2e, 1}, {0x30, 3},
{0x31, 2}, {0x32, 1}, {0xa7, 2}, {0xa9, 2}, {0xaa, 4}, {0xab, 8},
{0xfa, 2}, {0xfb, 5}, {0xfd, 4}, {0xfe, 3}, {0xff, 9},
};
static constexpr unsigned char singletons0_lower[] = {
0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90,
0x1c, 0x1d, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f,
0x5c, 0x5d, 0x5f, 0xb5, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1,
0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04,
0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d,
0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf,
0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a,
0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d,
0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x45, 0x49, 0x57, 0x64, 0x65, 0x8d,
0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d,
0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5,
0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7,
0xce, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49,
0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7,
0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7,
0xfe, 0xff, 0x80, 0x0d, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x0f, 0x1f, 0x6e,
0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0xbb, 0xbc, 0xfa, 0x16,
0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e,
0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f,
0x74, 0x75, 0x96, 0x2f, 0x5f, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf,
0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xc0,
0xc1, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27,
0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91,
0xfe, 0xff, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7,
0xfe, 0xff,
};
static constexpr singleton singletons1[] = {
{0x00, 6}, {0x01, 1}, {0x03, 1}, {0x04, 2}, {0x08, 8}, {0x09, 2},
{0x0a, 5}, {0x0b, 2}, {0x0e, 4}, {0x10, 1}, {0x11, 2}, {0x12, 5},
{0x13, 17}, {0x14, 1}, {0x15, 2}, {0x17, 2}, {0x19, 13}, {0x1c, 5},
{0x1d, 8}, {0x24, 1}, {0x6a, 3}, {0x6b, 2}, {0xbc, 2}, {0xd1, 2},
{0xd4, 12}, {0xd5, 9}, {0xd6, 2}, {0xd7, 2}, {0xda, 1}, {0xe0, 5},
{0xe1, 2}, {0xe8, 2}, {0xee, 32}, {0xf0, 4}, {0xf8, 2}, {0xf9, 2},
{0xfa, 2}, {0xfb, 1},
};
static constexpr unsigned char singletons1_lower[] = {
0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x06, 0x07,
0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36,
0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87,
0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a,
0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x5c, 0xb6, 0xb7, 0x1b,
0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9,
0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66,
0x69, 0x8f, 0x92, 0x6f, 0x5f, 0xee, 0xef, 0x5a, 0x62, 0x9a, 0x9b, 0x27,
0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc,
0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7,
0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xc5, 0xc6,
0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c,
0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66,
0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0,
0xae, 0xaf, 0x79, 0xcc, 0x6e, 0x6f, 0x93,
};
static constexpr unsigned char normal0[] = {
0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04,
0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x35, 0x28, 0x0b, 0x80, 0xe0,
0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01,
0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03,
0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03,
0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x3a,
0x03, 0x11, 0x07, 0x06, 0x05, 0x10, 0x07, 0x57, 0x07, 0x02, 0x07, 0x15,
0x0d, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f,
0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80,
0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07,
0x15, 0x0b, 0x17, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06,
0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04,
0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac,
0x06, 0x0a, 0x06, 0x21, 0x3f, 0x4c, 0x04, 0x2d, 0x03, 0x74, 0x08, 0x3c,
0x03, 0x0f, 0x03, 0x3c, 0x07, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11,
0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x20, 0x10, 0x21, 0x0f, 0x80, 0x8c,
0x04, 0x82, 0x97, 0x19, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b,
0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xb3, 0x2d, 0x74, 0x0c, 0x80, 0xd6,
0x1a, 0x0c, 0x05, 0x80, 0xff, 0x05, 0x80, 0xdf, 0x0c, 0xee, 0x0d, 0x03,
0x84, 0x8d, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80,
0xcb, 0x2a, 0x38, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06,
0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c,
0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17,
0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80,
0xa5, 0x11, 0x81, 0x6d, 0x10, 0x78, 0x28, 0x2a, 0x06, 0x4c, 0x04, 0x80,
0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d,
};
static constexpr unsigned char normal1[] = {
0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f,
0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e,
0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04,
0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x01, 0x80, 0x90, 0x81, 0x37, 0x09,
0x16, 0x0a, 0x08, 0x80, 0x98, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16,
0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f,
0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36,
0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33,
0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x81, 0x26, 0x52, 0x4e, 0x28, 0x08,
0x2a, 0x56, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x1e, 0x0f, 0x43, 0x0e,
0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x3f, 0x41,
0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03,
0x05, 0x80, 0x8b, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22,
0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x39, 0x07, 0x0a, 0x36, 0x2c, 0x04,
0x10, 0x80, 0xc0, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45,
0x1b, 0x48, 0x08, 0x53, 0x1d, 0x39, 0x81, 0x07, 0x46, 0x0a, 0x1d, 0x03,
0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81,
0x36, 0x19, 0x80, 0xb7, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75,
0x0b, 0x80, 0xc4, 0x8a, 0xbc, 0x84, 0x2f, 0x8f, 0xd1, 0x82, 0x47, 0xa1,
0xb9, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x02, 0x60, 0x26, 0x0a, 0x46, 0x0a,
0x28, 0x05, 0x13, 0x82, 0xb0, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11,
0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x2a, 0x09,
0xa2, 0xf7, 0x81, 0x1f, 0x31, 0x03, 0x11, 0x04, 0x08, 0x81, 0x8c, 0x89,
0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x93, 0x60, 0x80, 0xf6,
0x0a, 0x73, 0x08, 0x6e, 0x17, 0x46, 0x80, 0x9a, 0x14, 0x0c, 0x57, 0x09,
0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x85, 0x50,
0x2b, 0x80, 0xd5, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x70, 0x3a, 0x05,
0x01, 0x85, 0x00, 0x80, 0xd7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83,
0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05,
0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80,
0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80,
0x9a, 0x83, 0xd8, 0x08, 0x0d, 0x03, 0x0d, 0x03, 0x74, 0x0c, 0x59, 0x07,
0x0c, 0x14, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x22, 0x4e,
0x81, 0x54, 0x0c, 0x15, 0x03, 0x03, 0x05, 0x07, 0x09, 0x19, 0x07, 0x07,
0x09, 0x03, 0x0d, 0x07, 0x29, 0x80, 0xcb, 0x25, 0x0a, 0x84, 0x06,
};
auto lower = static_cast<uint16_t>(cp);
if (cp < 0x10000) {
return is_printable(lower, singletons0,
sizeof(singletons0) / sizeof(*singletons0),
singletons0_lower, normal0, sizeof(normal0));
}
if (cp < 0x20000) {
return is_printable(lower, singletons1,
sizeof(singletons1) / sizeof(*singletons1),
singletons1_lower, normal1, sizeof(normal1));
}
if (0x2a6de <= cp && cp < 0x2a700) return false;
if (0x2b735 <= cp && cp < 0x2b740) return false;
if (0x2b81e <= cp && cp < 0x2b820) return false;
if (0x2cea2 <= cp && cp < 0x2ceb0) return false;
if (0x2ebe1 <= cp && cp < 0x2f800) return false;
if (0x2fa1e <= cp && cp < 0x30000) return false;
if (0x3134b <= cp && cp < 0xe0100) return false;
if (0xe01f0 <= cp && cp < 0x110000) return false;
return cp < 0x110000;
}
inline auto needs_escape(uint32_t cp) -> bool {
return cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\' ||
!is_printable(cp);
}
template <typename Char> struct find_escape_result {
const Char* begin;
const Char* end;
uint32_t cp;
};
template <typename Char>
auto find_escape(const Char* begin, const Char* end)
-> find_escape_result<Char> {
for (; begin != end; ++begin) {
auto cp = static_cast<typename std::make_unsigned<Char>::type>(*begin);
if (sizeof(Char) == 1 && cp >= 0x80) continue;
if (needs_escape(cp)) return {begin, begin + 1, cp};
}
return {begin, nullptr, 0};
}
inline auto find_escape(const char* begin, const char* end)
-> find_escape_result<char> {
if (!is_utf8()) return find_escape<char>(begin, end);
auto result = find_escape_result<char>{end, nullptr, 0};
for_each_codepoint(string_view(begin, to_unsigned(end - begin)),
[&](uint32_t cp, string_view sv) {
if (needs_escape(cp)) {
result = {sv.begin(), sv.end(), cp};
return false;
}
return true;
});
return result;
}
template <typename Char, typename OutputIt>
auto write_range_entry(OutputIt out, basic_string_view<Char> str) -> OutputIt {
*out++ = '"'; *out++ = '"';
out = write<Char>(out, v); auto begin = str.begin(), end = str.end();
do {
auto escape = find_escape(begin, end);
out = copy_str<Char>(begin, escape.begin, out);
begin = escape.end;
if (!begin) break;
auto c = static_cast<Char>(escape.cp);
switch (escape.cp) {
case '\n':
*out++ = '\\';
c = 'n';
break;
case '\r':
*out++ = '\\';
c = 'r';
break;
case '\t':
*out++ = '\\';
c = 't';
break;
case '"':
FMT_FALLTHROUGH;
case '\\':
*out++ = '\\';
break;
default:
if (is_utf8()) {
if (escape.cp < 0x100) {
out = format_to(out, "\\x{:02x}", escape.cp);
continue;
}
if (escape.cp < 0x10000) {
out = format_to(out, "\\u{:04x}", escape.cp);
continue;
}
if (escape.cp < 0x110000) {
out = format_to(out, "\\U{:08x}", escape.cp);
continue;
}
}
for (Char escape_char : basic_string_view<Char>(
escape.begin, to_unsigned(escape.end - escape.begin))) {
out = format_to(
out, "\\x{:02x}",
static_cast<typename std::make_unsigned<Char>::type>(escape_char));
}
continue;
}
*out++ = c;
} while (begin != end);
*out++ = '"'; *out++ = '"';
return out; return out;
} }
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(std::is_convertible<T, std_string_view<char>>::value)>
inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt {
auto sv = std_string_view<Char>(str);
return write_range_entry<Char>(out, basic_string_view<Char>(sv));
}
template <typename Char, typename OutputIt, typename Arg, template <typename Char, typename OutputIt, typename Arg,
FMT_ENABLE_IF(std::is_same<Arg, Char>::value)> FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
OutputIt write_range_entry(OutputIt out, const Arg v) { OutputIt write_range_entry(OutputIt out, const Arg v) {
@ -288,43 +547,37 @@ template <typename T> struct is_tuple_like {
template <typename TupleT, typename Char> template <typename TupleT, typename Char>
struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> { struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
private: private:
// C++11 generic lambda for format() // C++11 generic lambda for format().
template <typename FormatContext> struct format_each { template <typename FormatContext> struct format_each {
template <typename T> void operator()(const T& v) { template <typename T> void operator()(const T& v) {
if (i > 0) out = detail::write_delimiter(out); if (i > 0) out = detail::write_delimiter(out);
out = detail::write_range_entry<Char>(out, v); out = detail::write_range_entry<Char>(out, v);
++i; ++i;
} }
formatting_tuple<Char>& formatting; int i;
size_t& i; typename FormatContext::iterator& out;
typename std::add_lvalue_reference<
decltype(std::declval<FormatContext>().out())>::type out;
}; };
public: public:
formatting_tuple<Char> formatting;
template <typename ParseContext> template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return formatting.parse(ctx); return ctx.begin();
} }
template <typename FormatContext = format_context> template <typename FormatContext = format_context>
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) { auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
auto out = ctx.out(); auto out = ctx.out();
size_t i = 0; *out++ = '(';
detail::for_each(values, format_each<FormatContext>{0, out});
detail::copy(formatting.prefix, out); *out++ = ')';
detail::for_each(values, format_each<FormatContext>{formatting, i, out}); return out;
detail::copy(formatting.postfix, out);
return ctx.out();
} }
}; };
template <typename T, typename Char> struct is_range { template <typename T, typename Char> struct is_range {
static FMT_CONSTEXPR_DECL const bool value = static FMT_CONSTEXPR_DECL const bool value =
detail::is_range_<T>::value && !detail::is_std_string_like<T>::value && detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
!detail::is_map<T>::value &&
!std::is_convertible<T, std::basic_string<Char>>::value && !std::is_convertible<T, std::basic_string<Char>>::value &&
!std::is_constructible<detail::std_string_view<Char>, T>::value; !std::is_constructible<detail::std_string_view<Char>, T>::value;
}; };
@ -334,32 +587,80 @@ struct formatter<
T, Char, T, Char,
enable_if_t< enable_if_t<
fmt::is_range<T, Char>::value fmt::is_range<T, Char>::value
// Workaround a bug in MSVC 2017 and earlier. // Workaround a bug in MSVC 2019 and earlier.
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927 #if !FMT_MSC_VER
&& (has_formatter<detail::value_type<T>, format_context>::value || && (is_formattable<detail::value_type<T>, Char>::value ||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value) detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
#endif #endif
>> { >> {
formatting_range<Char> formatting;
template <typename ParseContext> template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return formatting.parse(ctx); return ctx.begin();
} }
template <typename FormatContext> template <
typename FormatContext::iterator format(const T& values, FormatContext& ctx) { typename FormatContext, typename U,
auto out = detail::copy(formatting.prefix, ctx.out()); FMT_ENABLE_IF(
size_t i = 0; std::is_same<U, conditional_t<detail::has_const_begin_end<T>::value,
auto view = detail::range_to_view<T>::view(values); const T, T>>::value)>
auto it = view.begin(); auto format(U& range, FormatContext& ctx) -> decltype(ctx.out()) {
auto end = view.end(); #ifdef FMT_DEPRECATED_BRACED_RANGES
Char prefix = '{';
Char postfix = '}';
#else
Char prefix = detail::is_set<T>::value ? '{' : '[';
Char postfix = detail::is_set<T>::value ? '}' : ']';
#endif
auto out = ctx.out();
*out++ = prefix;
int i = 0;
auto it = std::begin(range);
auto end = std::end(range);
for (; it != end; ++it) { for (; it != end; ++it) {
if (i > 0) out = detail::write_delimiter(out); if (i > 0) out = detail::write_delimiter(out);
out = detail::write_range_entry<Char>(out, *it); out = detail::write_range_entry<Char>(out, *it);
++i; ++i;
} }
return detail::copy(formatting.postfix, out); *out++ = postfix;
return out;
}
};
template <typename T, typename Char>
struct formatter<
T, Char,
enable_if_t<
detail::is_map<T>::value
// Workaround a bug in MSVC 2019 and earlier.
#if !FMT_MSC_VER
&& (is_formattable<detail::value_type<T>, Char>::value ||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
#endif
>> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <
typename FormatContext, typename U,
FMT_ENABLE_IF(
std::is_same<U, conditional_t<detail::has_const_begin_end<T>::value,
const T, T>>::value)>
auto format(U& map, FormatContext& ctx) -> decltype(ctx.out()) {
auto out = ctx.out();
*out++ = '{';
int i = 0;
for (const auto& item : map) {
if (i > 0) out = detail::write_delimiter(out);
out = detail::write_range_entry<Char>(out, item.first);
*out++ = ':';
*out++ = ' ';
out = detail::write_range_entry<Char>(out, item.second);
++i;
}
*out++ = '}';
return out;
} }
}; };
@ -374,46 +675,70 @@ template <typename Char, typename... T> struct tuple_join_view : detail::view {
template <typename Char, typename... T> template <typename Char, typename... T>
using tuple_arg_join = tuple_join_view<Char, T...>; using tuple_arg_join = tuple_join_view<Char, T...>;
// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
// support in tuple_join. It is disabled by default because of issues with
// the dynamic width and precision.
#ifndef FMT_TUPLE_JOIN_SPECIFIERS
# define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif
template <typename Char, typename... T> template <typename Char, typename... T>
struct formatter<tuple_join_view<Char, T...>, Char> { struct formatter<tuple_join_view<Char, T...>, Char> {
template <typename ParseContext> template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin(); return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>());
} }
template <typename FormatContext> template <typename FormatContext>
auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx) -> auto format(const tuple_join_view<Char, T...>& value,
typename FormatContext::iterator { FormatContext& ctx) const -> typename FormatContext::iterator {
return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{}); return do_format(value, ctx,
std::integral_constant<size_t, sizeof...(T)>());
} }
private: private:
template <typename FormatContext, size_t... N> std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_;
auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
detail::index_sequence<N...>) -> template <typename ParseContext>
typename FormatContext::iterator { FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
using std::get; std::integral_constant<size_t, 0>)
return format_args(value, ctx, get<N>(value.tuple)...); -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename ParseContext, size_t N>
FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
std::integral_constant<size_t, N>)
-> decltype(ctx.begin()) {
auto end = ctx.begin();
#if FMT_TUPLE_JOIN_SPECIFIERS
end = std::get<sizeof...(T) - N>(formatters_).parse(ctx);
if (N > 1) {
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
if (end != end1)
FMT_THROW(format_error("incompatible format specs for tuple elements"));
}
#endif
return end;
} }
template <typename FormatContext> template <typename FormatContext>
auto format_args(const tuple_join_view<Char, T...>&, FormatContext& ctx) -> auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx,
std::integral_constant<size_t, 0>) const ->
typename FormatContext::iterator { typename FormatContext::iterator {
// NOTE: for compilers that support C++17, this empty function instantiation
// can be replaced with a constexpr branch in the variadic overload.
return ctx.out(); return ctx.out();
} }
template <typename FormatContext, typename Arg, typename... Args> template <typename FormatContext, size_t N>
auto format_args(const tuple_join_view<Char, T...>& value, FormatContext& ctx, auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
const Arg& arg, const Args&... args) -> std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator { typename FormatContext::iterator {
using base = formatter<typename std::decay<Arg>::type, Char>; auto out = std::get<sizeof...(T) - N>(formatters_)
auto out = base().format(arg, ctx); .format(std::get<sizeof...(T) - N>(value.tuple), ctx);
if (sizeof...(Args) > 0) { if (N > 1) {
out = std::copy(value.sep.begin(), value.sep.end(), out); out = std::copy(value.sep.begin(), value.sep.end(), out);
ctx.advance_to(out); ctx.advance_to(out);
return format_args(value, ctx, args...); return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
} }
return out; return out;
} }

@ -5,8 +5,8 @@
// //
// For the license information refer to format.h. // For the license information refer to format.h.
#ifndef FMT_WCHAR_H_ #ifndef FMT_XCHAR_H_
#define FMT_WCHAR_H_ #define FMT_XCHAR_H_
#include <cwchar> #include <cwchar>
#include <tuple> #include <tuple>
@ -217,11 +217,11 @@ inline void vprint(wstring_view fmt, wformat_args args) {
template <typename... T> template <typename... T>
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) { void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
return vprint(f, wstring_view(fmt), make_wformat_args(args...)); return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...));
} }
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) { template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
return vprint(wstring_view(fmt), make_wformat_args(args...)); return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
} }
/** /**
@ -233,4 +233,4 @@ template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
FMT_MODULE_EXPORT_END FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_WCHAR_H_ #endif // FMT_XCHAR_H_

@ -8,6 +8,7 @@
// include bundled or external copy of fmtlib's chrono support // include bundled or external copy of fmtlib's chrono support
// //
#if !defined(SPDLOG_USE_STD_FORMAT)
# if !defined(SPDLOG_FMT_EXTERNAL) # if !defined(SPDLOG_FMT_EXTERNAL)
# ifdef SPDLOG_HEADER_ONLY # ifdef SPDLOG_HEADER_ONLY
# ifndef FMT_HEADER_ONLY # ifndef FMT_HEADER_ONLY
@ -18,3 +19,4 @@
# else # else
# include <fmt/chrono.h> # include <fmt/chrono.h>
# endif # endif
#endif

@ -5,9 +5,10 @@
#pragma once #pragma once
// //
// include bundled or external copy of fmtlib's ostream support // include bundled or external copy of fmtlib's compile-time support
// //
#if !defined(SPDLOG_USE_STD_FORMAT)
# if !defined(SPDLOG_FMT_EXTERNAL) # if !defined(SPDLOG_FMT_EXTERNAL)
# ifdef SPDLOG_HEADER_ONLY # ifdef SPDLOG_HEADER_ONLY
# ifndef FMT_HEADER_ONLY # ifndef FMT_HEADER_ONLY
@ -18,3 +19,4 @@
# else # else
# include <fmt/compile.h> # include <fmt/compile.h>
# endif # endif
#endif

@ -10,7 +10,9 @@
// By default spdlog include its own copy. // By default spdlog include its own copy.
// //
#if !defined(SPDLOG_FMT_EXTERNAL) #if defined(SPDLOG_USE_STD_FORMAT) // SPDLOG_USE_STD_FORMAT is defined - use std::format
# include <format>
#elif !defined(SPDLOG_FMT_EXTERNAL)
# if !defined(SPDLOG_COMPILED_LIB) && !defined(FMT_HEADER_ONLY) # if !defined(SPDLOG_COMPILED_LIB) && !defined(FMT_HEADER_ONLY)
# define FMT_HEADER_ONLY # define FMT_HEADER_ONLY
# endif # endif

@ -8,6 +8,7 @@
// include bundled or external copy of fmtlib's ostream support // include bundled or external copy of fmtlib's ostream support
// //
#if !defined(SPDLOG_USE_STD_FORMAT)
# if !defined(SPDLOG_FMT_EXTERNAL) # if !defined(SPDLOG_FMT_EXTERNAL)
# ifdef SPDLOG_HEADER_ONLY # ifdef SPDLOG_HEADER_ONLY
# ifndef FMT_HEADER_ONLY # ifndef FMT_HEADER_ONLY
@ -18,3 +19,4 @@
# else # else
# include <fmt/ostream.h> # include <fmt/ostream.h>
# endif # endif
#endif

@ -0,0 +1,22 @@
//
// Copyright(c) 2016 Gabi Melman.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
//
#pragma once
//
// include bundled or external copy of fmtlib's ranges support
//
#if !defined(SPDLOG_USE_STD_FORMAT)
# if !defined(SPDLOG_FMT_EXTERNAL)
# ifdef SPDLOG_HEADER_ONLY
# ifndef FMT_HEADER_ONLY
# define FMT_HEADER_ONLY
# endif
# endif
# include <spdlog/fmt/bundled/ranges.h>
# else
# include <fmt/ranges.h>
# endif
#endif

@ -5,9 +5,10 @@
#pragma once #pragma once
// //
// include bundled or external copy of fmtlib's ostream support // include bundled or external copy of fmtlib's xchar support
// //
#if !defined(SPDLOG_USE_STD_FORMAT)
# if !defined(SPDLOG_FMT_EXTERNAL) # if !defined(SPDLOG_FMT_EXTERNAL)
# ifdef SPDLOG_HEADER_ONLY # ifdef SPDLOG_HEADER_ONLY
# ifndef FMT_HEADER_ONLY # ifndef FMT_HEADER_ONLY
@ -18,3 +19,4 @@
# else # else
# include <fmt/xchar.h> # include <fmt/xchar.h>
# endif # endif
#endif

@ -11,4 +11,8 @@ namespace sinks {
class sink; class sink;
} }
namespace level {
enum level_enum : int;
}
} // namespace spdlog } // namespace spdlog

@ -33,7 +33,7 @@
{ \ { \
if (location.filename) \ if (location.filename) \
{ \ { \
err_handler_(fmt::format("{} [{}({})]", ex.what(), location.filename, location.line)); \ err_handler_(fmt_lib::format("{} [{}({})]", ex.what(), location.filename, location.line)); \
} \ } \
else \ else \
{ \ { \
@ -85,13 +85,13 @@ public:
void swap(spdlog::logger &other) SPDLOG_NOEXCEPT; void swap(spdlog::logger &other) SPDLOG_NOEXCEPT;
template<typename... Args> template<typename... Args>
void log(source_loc loc, level::level_enum lvl, fmt::format_string<Args...> fmt, Args &&...args) void log(source_loc loc, level::level_enum lvl, format_string_t<Args...> fmt, Args &&... args)
{ {
log_(loc, lvl, fmt, std::forward<Args>(args)...); log_(loc, lvl, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
void log(level::level_enum lvl, fmt::format_string<Args...> fmt, Args &&...args) void log(level::level_enum lvl, format_string_t<Args...> fmt, Args &&... args)
{ {
log(source_loc{}, lvl, fmt, std::forward<Args>(args)...); log(source_loc{}, lvl, fmt, std::forward<Args>(args)...);
} }
@ -102,14 +102,7 @@ public:
log(source_loc{}, lvl, msg); log(source_loc{}, lvl, msg);
} }
// T can be statically converted to string_view // T cannot be statically converted to format string (including string_view/wstring_view)
template<class T, typename std::enable_if<std::is_convertible<const T &, spdlog::string_view_t>::value, int>::type = 0>
void log(source_loc loc, level::level_enum lvl, const T &msg)
{
log(loc, lvl, string_view_t{msg});
}
// T cannot be statically converted to format string (including string_view)
template<class T, typename std::enable_if<!is_convertible_to_any_format_string<const T &>::value, int>::type = 0> template<class T, typename std::enable_if<!is_convertible_to_any_format_string<const T &>::value, int>::type = 0>
void log(source_loc loc, level::level_enum lvl, const T &msg) void log(source_loc loc, level::level_enum lvl, const T &msg)
{ {
@ -148,86 +141,121 @@ public:
} }
template<typename... Args> template<typename... Args>
void trace(fmt::format_string<Args...> fmt, Args &&...args) void trace(format_string_t<Args...> fmt, Args &&... args)
{ {
log(level::trace, fmt, std::forward<Args>(args)...); log(level::trace, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
void debug(fmt::format_string<Args...> fmt, Args &&...args) void debug(format_string_t<Args...> fmt, Args &&... args)
{ {
log(level::debug, fmt, std::forward<Args>(args)...); log(level::debug, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
void info(fmt::format_string<Args...> fmt, Args &&...args) void info(format_string_t<Args...> fmt, Args &&... args)
{ {
log(level::info, fmt, std::forward<Args>(args)...); log(level::info, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
void warn(fmt::format_string<Args...> fmt, Args &&...args) void warn(format_string_t<Args...> fmt, Args &&... args)
{ {
log(level::warn, fmt, std::forward<Args>(args)...); log(level::warn, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
void error(fmt::format_string<Args...> fmt, Args &&...args) void error(format_string_t<Args...> fmt, Args &&... args)
{ {
log(level::err, fmt, std::forward<Args>(args)...); log(level::err, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
void critical(fmt::format_string<Args...> fmt, Args &&...args) void critical(format_string_t<Args...> fmt, Args &&... args)
{ {
log(level::critical, fmt, std::forward<Args>(args)...); log(level::critical, fmt, std::forward<Args>(args)...);
} }
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
template<typename... Args> template<typename... Args>
void log(level::level_enum lvl, fmt::wformat_string<Args...> fmt, Args &&...args) void log(source_loc loc, level::level_enum lvl, wformat_string_t<Args...> fmt, Args &&... args)
{ {
log(source_loc{}, lvl, fmt, std::forward<Args>(args)...); log_(loc, lvl, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
void log(source_loc loc, level::level_enum lvl, fmt::wformat_string<Args...> fmt, Args &&...args) void log(level::level_enum lvl, wformat_string_t<Args...> fmt, Args &&... args)
{ {
log_(loc, lvl, fmt, std::forward<Args>(args)...); log(source_loc{}, lvl, fmt, std::forward<Args>(args)...);
}
void log(log_clock::time_point log_time, source_loc loc, level::level_enum lvl, wstring_view_t msg)
{
bool log_enabled = should_log(lvl);
bool traceback_enabled = tracer_.enabled();
if (!log_enabled && !traceback_enabled)
{
return;
}
memory_buf_t buf;
details::os::wstr_to_utf8buf(wstring_view_t(msg.data(), msg.size()), buf);
details::log_msg log_msg(log_time, loc, name_, lvl, string_view_t(buf.data(), buf.size()));
log_it_(log_msg, log_enabled, traceback_enabled);
}
void log(source_loc loc, level::level_enum lvl, wstring_view_t msg)
{
bool log_enabled = should_log(lvl);
bool traceback_enabled = tracer_.enabled();
if (!log_enabled && !traceback_enabled)
{
return;
}
memory_buf_t buf;
details::os::wstr_to_utf8buf(wstring_view_t(msg.data(), msg.size()), buf);
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));
log_it_(log_msg, log_enabled, traceback_enabled);
}
void log(level::level_enum lvl, wstring_view_t msg)
{
log(source_loc{}, lvl, msg);
} }
template<typename... Args> template<typename... Args>
void trace(fmt::wformat_string<Args...> fmt, Args &&...args) void trace(wformat_string_t<Args...> fmt, Args &&... args)
{ {
log(level::trace, fmt, std::forward<Args>(args)...); log(level::trace, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
void debug(fmt::wformat_string<Args...> fmt, Args &&...args) void debug(wformat_string_t<Args...> fmt, Args &&... args)
{ {
log(level::debug, fmt, std::forward<Args>(args)...); log(level::debug, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
void info(fmt::wformat_string<Args...> fmt, Args &&...args) void info(wformat_string_t<Args...> fmt, Args &&... args)
{ {
log(level::info, fmt, std::forward<Args>(args)...); log(level::info, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
void warn(fmt::wformat_string<Args...> fmt, Args &&...args) void warn(wformat_string_t<Args...> fmt, Args &&... args)
{ {
log(level::warn, fmt, std::forward<Args>(args)...); log(level::warn, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
void error(fmt::wformat_string<Args...> fmt, Args &&...args) void error(wformat_string_t<Args...> fmt, Args &&... args)
{ {
log(level::err, fmt, std::forward<Args>(args)...); log(level::err, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
void critical(fmt::wformat_string<Args...> fmt, Args &&...args) void critical(wformat_string_t<Args...> fmt, Args &&... args)
{ {
log(level::critical, fmt, std::forward<Args>(args)...); log(level::critical, fmt, std::forward<Args>(args)...);
} }
@ -335,8 +363,12 @@ protected:
} }
SPDLOG_TRY SPDLOG_TRY
{ {
#ifdef SPDLOG_USE_STD_FORMAT
memory_buf_t buf = std::vformat(fmt, std::make_format_args(std::forward<Args>(args)...));
#else
memory_buf_t buf; memory_buf_t buf;
fmt::detail::vformat_to(buf, fmt, fmt::make_format_args(args...)); fmt::detail::vformat_to(buf, fmt, fmt::make_format_args(std::forward<Args>(args)...));
#endif
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));
log_it_(log_msg, log_enabled, traceback_enabled); log_it_(log_msg, log_enabled, traceback_enabled);
} }
@ -356,8 +388,13 @@ protected:
SPDLOG_TRY SPDLOG_TRY
{ {
// format to wmemory_buffer and convert to utf8 // format to wmemory_buffer and convert to utf8
fmt::wmemory_buffer wbuf; ;
fmt::detail::vformat_to(wbuf, fmt, fmt::make_format_args<fmt::wformat_context>(args...)); # ifdef SPDLOG_USE_STD_FORMAT
wmemory_buf_t wbuf = std::vformat(fmt, std::make_wformat_args(std::forward<Args>(args)...));
# else
wmemory_buf_t wbuf;
fmt::detail::vformat_to(wbuf, fmt, fmt::make_format_args<fmt::wformat_context>(std::forward<Args>(args)...));
# endif
memory_buf_t buf; memory_buf_t buf;
details::os::wstr_to_utf8buf(wstring_view_t(wbuf.data(), wbuf.size()), buf); details::os::wstr_to_utf8buf(wstring_view_t(wbuf.data(), wbuf.size()), buf);
details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size())); details::log_msg log_msg(loc, name_, lvl, string_view_t(buf.data(), buf.size()));

@ -766,6 +766,7 @@ public:
{ {
if (msg.source.empty()) if (msg.source.empty())
{ {
ScopedPadder p(0, padinfo_, dest);
return; return;
} }
@ -800,6 +801,7 @@ public:
{ {
if (msg.source.empty()) if (msg.source.empty())
{ {
ScopedPadder p(0, padinfo_, dest);
return; return;
} }
size_t text_size = padinfo_.enabled() ? std::char_traits<char>::length(msg.source.filename) : 0; size_t text_size = padinfo_.enabled() ? std::char_traits<char>::length(msg.source.filename) : 0;
@ -846,6 +848,7 @@ public:
{ {
if (msg.source.empty()) if (msg.source.empty())
{ {
ScopedPadder p(0, padinfo_, dest);
return; return;
} }
auto filename = basename(msg.source.filename); auto filename = basename(msg.source.filename);
@ -867,6 +870,7 @@ public:
{ {
if (msg.source.empty()) if (msg.source.empty())
{ {
ScopedPadder p(0, padinfo_, dest);
return; return;
} }
@ -889,6 +893,7 @@ public:
{ {
if (msg.source.empty()) if (msg.source.empty())
{ {
ScopedPadder p(0, padinfo_, dest);
return; return;
} }
size_t text_size = padinfo_.enabled() ? std::char_traits<char>::length(msg.source.funcname) : 0; size_t text_size = padinfo_.enabled() ? std::char_traits<char>::length(msg.source.funcname) : 0;
@ -1019,6 +1024,7 @@ SPDLOG_INLINE pattern_formatter::pattern_formatter(
: pattern_(std::move(pattern)) : pattern_(std::move(pattern))
, eol_(std::move(eol)) , eol_(std::move(eol))
, pattern_time_type_(time_type) , pattern_time_type_(time_type)
, need_localtime_(false)
, last_log_secs_(0) , last_log_secs_(0)
, custom_handlers_(std::move(custom_user_flags)) , custom_handlers_(std::move(custom_user_flags))
{ {
@ -1031,6 +1037,7 @@ SPDLOG_INLINE pattern_formatter::pattern_formatter(pattern_time_type time_type,
: pattern_("%+") : pattern_("%+")
, eol_(std::move(eol)) , eol_(std::move(eol))
, pattern_time_type_(time_type) , pattern_time_type_(time_type)
, need_localtime_(true)
, last_log_secs_(0) , last_log_secs_(0)
{ {
std::memset(&cached_tm_, 0, sizeof(cached_tm_)); std::memset(&cached_tm_, 0, sizeof(cached_tm_));
@ -1049,12 +1056,15 @@ SPDLOG_INLINE std::unique_ptr<formatter> pattern_formatter::clone() const
SPDLOG_INLINE void pattern_formatter::format(const details::log_msg &msg, memory_buf_t &dest) SPDLOG_INLINE void pattern_formatter::format(const details::log_msg &msg, memory_buf_t &dest)
{ {
auto secs = std::chrono::duration_cast<std::chrono::seconds>(msg.time.time_since_epoch()); if (need_localtime_)
{
const auto secs = std::chrono::duration_cast<std::chrono::seconds>(msg.time.time_since_epoch());
if (secs != last_log_secs_) if (secs != last_log_secs_)
{ {
cached_tm_ = get_time_(msg); cached_tm_ = get_time_(msg);
last_log_secs_ = secs; last_log_secs_ = secs;
} }
}
for (auto &f : formatters_) for (auto &f : formatters_)
{ {
@ -1067,6 +1077,7 @@ SPDLOG_INLINE void pattern_formatter::format(const details::log_msg &msg, memory
SPDLOG_INLINE void pattern_formatter::set_pattern(std::string pattern) SPDLOG_INLINE void pattern_formatter::set_pattern(std::string pattern)
{ {
pattern_ = std::move(pattern); pattern_ = std::move(pattern);
need_localtime_ = false;
compile_pattern_(pattern_); compile_pattern_(pattern_);
} }
@ -1097,6 +1108,7 @@ SPDLOG_INLINE void pattern_formatter::handle_flag_(char flag, details::padding_i
{ {
case ('+'): // default formatter case ('+'): // default formatter
formatters_.push_back(details::make_unique<details::full_formatter>(padding)); formatters_.push_back(details::make_unique<details::full_formatter>(padding));
need_localtime_ = true;
break; break;
case 'n': // logger name case 'n': // logger name
@ -1121,60 +1133,74 @@ SPDLOG_INLINE void pattern_formatter::handle_flag_(char flag, details::padding_i
case ('a'): // weekday case ('a'): // weekday
formatters_.push_back(details::make_unique<details::a_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::a_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('A'): // short weekday case ('A'): // short weekday
formatters_.push_back(details::make_unique<details::A_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::A_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('b'): case ('b'):
case ('h'): // month case ('h'): // month
formatters_.push_back(details::make_unique<details::b_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::b_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('B'): // short month case ('B'): // short month
formatters_.push_back(details::make_unique<details::B_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::B_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('c'): // datetime case ('c'): // datetime
formatters_.push_back(details::make_unique<details::c_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::c_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('C'): // year 2 digits case ('C'): // year 2 digits
formatters_.push_back(details::make_unique<details::C_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::C_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('Y'): // year 4 digits case ('Y'): // year 4 digits
formatters_.push_back(details::make_unique<details::Y_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::Y_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('D'): case ('D'):
case ('x'): // datetime MM/DD/YY case ('x'): // datetime MM/DD/YY
formatters_.push_back(details::make_unique<details::D_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::D_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('m'): // month 1-12 case ('m'): // month 1-12
formatters_.push_back(details::make_unique<details::m_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::m_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('d'): // day of month 1-31 case ('d'): // day of month 1-31
formatters_.push_back(details::make_unique<details::d_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::d_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('H'): // hours 24 case ('H'): // hours 24
formatters_.push_back(details::make_unique<details::H_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::H_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('I'): // hours 12 case ('I'): // hours 12
formatters_.push_back(details::make_unique<details::I_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::I_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('M'): // minutes case ('M'): // minutes
formatters_.push_back(details::make_unique<details::M_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::M_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('S'): // seconds case ('S'): // seconds
formatters_.push_back(details::make_unique<details::S_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::S_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('e'): // milliseconds case ('e'): // milliseconds
@ -1195,23 +1221,28 @@ SPDLOG_INLINE void pattern_formatter::handle_flag_(char flag, details::padding_i
case ('p'): // am/pm case ('p'): // am/pm
formatters_.push_back(details::make_unique<details::p_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::p_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('r'): // 12 hour clock 02:55:02 pm case ('r'): // 12 hour clock 02:55:02 pm
formatters_.push_back(details::make_unique<details::r_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::r_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('R'): // 24-hour HH:MM time case ('R'): // 24-hour HH:MM time
formatters_.push_back(details::make_unique<details::R_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::R_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('T'): case ('T'):
case ('X'): // ISO 8601 time format (HH:MM:SS) case ('X'): // ISO 8601 time format (HH:MM:SS)
formatters_.push_back(details::make_unique<details::T_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::T_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('z'): // timezone case ('z'): // timezone
formatters_.push_back(details::make_unique<details::z_formatter<Padder>>(padding)); formatters_.push_back(details::make_unique<details::z_formatter<Padder>>(padding));
need_localtime_ = true;
break; break;
case ('P'): // pid case ('P'): // pid
@ -1342,7 +1373,6 @@ SPDLOG_INLINE details::padding_info pattern_formatter::handle_padspec_(std::stri
{ {
truncate = false; truncate = false;
} }
return details::padding_info{std::min<size_t>(width, max_width), side, truncate}; return details::padding_info{std::min<size_t>(width, max_width), side, truncate};
} }

@ -68,7 +68,7 @@ class SPDLOG_API custom_flag_formatter : public details::flag_formatter
public: public:
virtual std::unique_ptr<custom_flag_formatter> clone() const = 0; virtual std::unique_ptr<custom_flag_formatter> clone() const = 0;
void set_padding_info(details::padding_info padding) void set_padding_info(const details::padding_info &padding)
{ {
flag_formatter::padinfo_ = padding; flag_formatter::padinfo_ = padding;
} }
@ -103,6 +103,7 @@ private:
std::string pattern_; std::string pattern_;
std::string eol_; std::string eol_;
pattern_time_type pattern_time_type_; pattern_time_type pattern_time_type_;
bool need_localtime_;
std::tm cached_tm_; std::tm cached_tm_;
std::chrono::seconds last_log_secs_; std::chrono::seconds last_log_secs_;
std::vector<std::unique_ptr<details::flag_formatter>> formatters_; std::vector<std::unique_ptr<details::flag_formatter>> formatters_;

@ -34,7 +34,7 @@ template<typename ConsoleMutex>
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color(level::level_enum color_level, string_view_t color) SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color(level::level_enum color_level, string_view_t color)
{ {
std::lock_guard<mutex_t> lock(mutex_); std::lock_guard<mutex_t> lock(mutex_);
colors_[color_level] = to_string_(color); colors_[static_cast<size_t>(color_level)] = to_string_(color);
} }
template<typename ConsoleMutex> template<typename ConsoleMutex>
@ -52,7 +52,7 @@ SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::log(const details::log_msg &msg
// before color range // before color range
print_range_(formatted, 0, msg.color_range_start); print_range_(formatted, 0, msg.color_range_start);
// in color range // in color range
print_ccode_(colors_[msg.level]); print_ccode_(colors_[static_cast<size_t>(msg.level)]);
print_range_(formatted, msg.color_range_start, msg.color_range_end); print_range_(formatted, msg.color_range_start, msg.color_range_end);
print_ccode_(reset); print_ccode_(reset);
// after color range // after color range

@ -16,7 +16,7 @@
namespace spdlog { namespace spdlog {
namespace sinks { namespace sinks {
template<typename Mutex> template<typename Mutex>
class base_sink : public sink class SPDLOG_API base_sink : public sink
{ {
public: public:
base_sink(); base_sink();
@ -37,7 +37,7 @@ public:
protected: protected:
// sink formatter // sink formatter
std::unique_ptr<spdlog::formatter> formatter_; std::unique_ptr<spdlog::formatter> formatter_;
mutable Mutex mutex_; Mutex mutex_;
virtual void sink_it_(const details::log_msg &msg) = 0; virtual void sink_it_(const details::log_msg &msg) = 0;
virtual void flush_() = 0; virtual void flush_() = 0;

@ -14,7 +14,8 @@ namespace spdlog {
namespace sinks { namespace sinks {
template<typename Mutex> template<typename Mutex>
SPDLOG_INLINE basic_file_sink<Mutex>::basic_file_sink(const filename_t &filename, bool truncate) SPDLOG_INLINE basic_file_sink<Mutex>::basic_file_sink(const filename_t &filename, bool truncate, const file_event_handlers &event_handlers)
: file_helper_{event_handlers}
{ {
file_helper_.open(filename, truncate); file_helper_.open(filename, truncate);
} }

@ -20,7 +20,7 @@ template<typename Mutex>
class basic_file_sink final : public base_sink<Mutex> class basic_file_sink final : public base_sink<Mutex>
{ {
public: public:
explicit basic_file_sink(const filename_t &filename, bool truncate = false); explicit basic_file_sink(const filename_t &filename, bool truncate = false, const file_event_handlers &event_handlers = {});
const filename_t &filename() const; const filename_t &filename() const;
protected: protected:
@ -40,15 +40,17 @@ using basic_file_sink_st = basic_file_sink<details::null_mutex>;
// factory functions // factory functions
// //
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> basic_logger_mt(const std::string &logger_name, const filename_t &filename, bool truncate = false) inline std::shared_ptr<logger> basic_logger_mt(
const std::string &logger_name, const filename_t &filename, bool truncate = false, const file_event_handlers &event_handlers = {})
{ {
return Factory::template create<sinks::basic_file_sink_mt>(logger_name, filename, truncate); return Factory::template create<sinks::basic_file_sink_mt>(logger_name, filename, truncate, event_handlers);
} }
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> basic_logger_st(const std::string &logger_name, const filename_t &filename, bool truncate = false) inline std::shared_ptr<logger> basic_logger_st(
const std::string &logger_name, const filename_t &filename, bool truncate = false, const file_event_handlers &event_handlers = {})
{ {
return Factory::template create<sinks::basic_file_sink_st>(logger_name, filename, truncate); return Factory::template create<sinks::basic_file_sink_st>(logger_name, filename, truncate, event_handlers);
} }
} // namespace spdlog } // namespace spdlog

@ -32,7 +32,7 @@ struct daily_filename_calculator
{ {
filename_t basename, ext; filename_t basename, ext;
std::tie(basename, ext) = details::file_helper::split_by_extension(filename); std::tie(basename, ext) = details::file_helper::split_by_extension(filename);
return fmt::format( return fmt_lib::format(
SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, ext); SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, ext);
} }
}; };
@ -48,14 +48,62 @@ struct daily_filename_format_calculator
{ {
static filename_t calc_filename(const filename_t &filename, const tm &now_tm) static filename_t calc_filename(const filename_t &filename, const tm &now_tm)
{ {
#ifdef SPDLOG_USE_STD_FORMAT
// adapted from fmtlib: https://github.com/fmtlib/fmt/blob/8.0.1/include/fmt/chrono.h#L522-L546
filename_t tm_format;
tm_format.append(filename);
// 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;
filename_t buf;
buf.resize(MIN_SIZE);
for (;;)
{
size_t count = strftime(buf.data(), buf.size(), tm_format.c_str(), &now_tm);
if (count != 0)
{
// Remove the extra space.
buf.resize(count - 1);
break;
}
buf.resize(buf.size() * 2);
}
return buf;
#else
// generate fmt datetime format string, e.g. {:%Y-%m-%d}. // generate fmt datetime format string, e.g. {:%Y-%m-%d}.
filename_t fmt_filename = fmt::format(SPDLOG_FILENAME_T("{{:{}}}"), filename); filename_t fmt_filename = fmt::format(SPDLOG_FILENAME_T("{{:{}}}"), filename);
#if defined(_MSC_VER) && defined(SPDLOG_WCHAR_FILENAMES) // for some reason msvc doesnt allow fmt::runtime(..) with wchar here # if defined(_MSC_VER) && defined(SPDLOG_WCHAR_FILENAMES) // for some reason msvc doesn't allow fmt::runtime(..) with wchar here
return fmt::format(fmt_filename, now_tm); return fmt::format(fmt_filename, now_tm);
# else # else
return fmt::format(SPDLOG_FMT_RUNTIME(fmt_filename), now_tm); return fmt::format(SPDLOG_FMT_RUNTIME(fmt_filename), now_tm);
# endif
#endif #endif
} }
private:
#if defined __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
static size_t strftime(char *str, size_t count, const char *format, const std::tm *time)
{
return std::strftime(str, count, format, time);
}
static size_t strftime(wchar_t *str, size_t count, const wchar_t *format, const std::tm *time)
{
return std::wcsftime(str, count, format, time);
}
#if defined(__GNUC__)
# pragma GCC diagnostic pop
#endif
}; };
/* /*
@ -68,10 +116,12 @@ class daily_file_sink final : public base_sink<Mutex>
{ {
public: public:
// create daily file sink which rotates on given time // create daily file sink which rotates on given time
daily_file_sink(filename_t base_filename, int rotation_hour, int rotation_minute, bool truncate = false, uint16_t max_files = 0) daily_file_sink(filename_t base_filename, int rotation_hour, int rotation_minute, bool truncate = false, uint16_t max_files = 0,
const file_event_handlers &event_handlers = {})
: base_filename_(std::move(base_filename)) : base_filename_(std::move(base_filename))
, rotation_h_(rotation_hour) , rotation_h_(rotation_hour)
, rotation_m_(rotation_minute) , rotation_m_(rotation_minute)
, file_helper_{event_handlers}
, truncate_(truncate) , truncate_(truncate)
, max_files_(max_files) , max_files_(max_files)
, filenames_q_() , filenames_q_()
@ -213,30 +263,32 @@ using daily_file_format_sink_st = daily_file_sink<details::null_mutex, daily_fil
// factory functions // factory functions
// //
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> daily_logger_mt( inline std::shared_ptr<logger> daily_logger_mt(const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0,
const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false, uint16_t max_files = 0) bool truncate = false, uint16_t max_files = 0, const file_event_handlers &event_handlers = {})
{ {
return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate, max_files); return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate, max_files, event_handlers);
} }
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> daily_logger_format_mt( inline std::shared_ptr<logger> daily_logger_format_mt(const std::string &logger_name, const filename_t &filename, int hour = 0,
const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false, uint16_t max_files = 0) int minute = 0, bool truncate = false, uint16_t max_files = 0, const file_event_handlers &event_handlers = {})
{ {
return Factory::template create<sinks::daily_file_format_sink_mt>(logger_name, filename, hour, minute, truncate, max_files); return Factory::template create<sinks::daily_file_format_sink_mt>(
logger_name, filename, hour, minute, truncate, max_files, event_handlers);
} }
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> daily_logger_st( inline std::shared_ptr<logger> daily_logger_st(const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0,
const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false, uint16_t max_files = 0) bool truncate = false, uint16_t max_files = 0, const file_event_handlers &event_handlers = {})
{ {
return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate, max_files); return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate, max_files, event_handlers);
} }
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> daily_logger_format_st( inline std::shared_ptr<logger> daily_logger_format_st(const std::string &logger_name, const filename_t &filename, int hour = 0,
const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false, uint16_t max_files = 0) int minute = 0, bool truncate = false, uint16_t max_files = 0, const file_event_handlers &event_handlers = {})
{ {
return Factory::template create<sinks::daily_file_format_sink_st>(logger_name, filename, hour, minute, truncate, max_files); return Factory::template create<sinks::daily_file_format_sink_st>(
logger_name, filename, hour, minute, truncate, max_files, event_handlers);
} }
} // namespace spdlog } // namespace spdlog

@ -31,7 +31,7 @@ struct hourly_filename_calculator
{ {
filename_t basename, ext; filename_t basename, ext;
std::tie(basename, ext) = details::file_helper::split_by_extension(filename); std::tie(basename, ext) = details::file_helper::split_by_extension(filename);
return fmt::format(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}_{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, return fmt_lib::format(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}_{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1,
now_tm.tm_mday, now_tm.tm_hour, ext); now_tm.tm_mday, now_tm.tm_hour, ext);
} }
}; };
@ -46,8 +46,10 @@ class hourly_file_sink final : public base_sink<Mutex>
{ {
public: public:
// create hourly file sink which rotates on given time // create hourly file sink which rotates on given time
hourly_file_sink(filename_t base_filename, bool truncate = false, uint16_t max_files = 0) hourly_file_sink(
filename_t base_filename, bool truncate = false, uint16_t max_files = 0, const file_event_handlers &event_handlers = {})
: base_filename_(std::move(base_filename)) : base_filename_(std::move(base_filename))
, file_helper_{event_handlers}
, truncate_(truncate) , truncate_(truncate)
, max_files_(max_files) , max_files_(max_files)
, filenames_q_() , filenames_q_()
@ -179,16 +181,16 @@ using hourly_file_sink_st = hourly_file_sink<details::null_mutex>;
// factory functions // factory functions
// //
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> hourly_logger_mt( inline std::shared_ptr<logger> hourly_logger_mt(const std::string &logger_name, const filename_t &filename, bool truncate = false,
const std::string &logger_name, const filename_t &filename, bool truncate = false, uint16_t max_files = 0) uint16_t max_files = 0, const file_event_handlers &event_handlers = {})
{ {
return Factory::template create<sinks::hourly_file_sink_mt>(logger_name, filename, truncate, max_files); return Factory::template create<sinks::hourly_file_sink_mt>(logger_name, filename, truncate, max_files, event_handlers);
} }
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> hourly_logger_st( inline std::shared_ptr<logger> hourly_logger_st(const std::string &logger_name, const filename_t &filename, bool truncate = false,
const std::string &logger_name, const filename_t &filename, bool truncate = false, uint16_t max_files = 0) uint16_t max_files = 0, const file_event_handlers &event_handlers = {})
{ {
return Factory::template create<sinks::hourly_file_sink_st>(logger_name, filename, truncate, max_files); return Factory::template create<sinks::hourly_file_sink_st>(logger_name, filename, truncate, max_files, event_handlers);
} }
} // namespace spdlog } // namespace spdlog

@ -30,7 +30,11 @@ protected:
{ {
memory_buf_t formatted; memory_buf_t formatted;
base_sink<Mutex>::formatter_->format(msg, formatted); base_sink<Mutex>::formatter_->format(msg, formatted);
# ifdef SPDLOG_USE_STD_FORMAT
OutputDebugStringA(formatted.c_str());
# else
OutputDebugStringA(fmt::to_string(formatted).c_str()); OutputDebugStringA(fmt::to_string(formatted).c_str());
# endif
} }
void flush_() override {} void flush_() override {}

@ -21,17 +21,24 @@
// //
namespace spdlog { namespace spdlog {
namespace sinks { namespace sinks {
template <typename Mutex> class qt_sink : public base_sink<Mutex> { template<typename Mutex>
class qt_sink : public base_sink<Mutex>
{
public: public:
qt_sink(QObject *qt_object, const std::string &meta_method) { qt_sink(QObject *qt_object, const std::string &meta_method)
{
qt_object_ = qt_object; qt_object_ = qt_object;
meta_method_ = meta_method; meta_method_ = meta_method;
} }
~qt_sink() { flush_(); } ~qt_sink()
{
flush_();
}
protected: protected:
void sink_it_(const details::log_msg &msg) override { void sink_it_(const details::log_msg &msg) override
{
memory_buf_t formatted; memory_buf_t formatted;
base_sink<Mutex>::formatter_->format(msg, formatted); base_sink<Mutex>::formatter_->format(msg, formatted);
string_view_t str = string_view_t(formatted.data(), formatted.size()); string_view_t str = string_view_t(formatted.data(), formatted.size());
@ -56,38 +63,40 @@ using qt_sink_st = qt_sink<spdlog::details::null_mutex>;
// Factory functions // Factory functions
// //
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> inline std::shared_ptr<logger> qt_logger_mt(const std::string &logger_name, QTextEdit *qt_object, const std::string &meta_method = "append")
qt_logger_mt(const std::string &logger_name, QTextEdit* qt_object, const std::string &meta_method = "append") { {
return Factory::template create<sinks::qt_sink_mt>(logger_name, qt_object, meta_method); return Factory::template create<sinks::qt_sink_mt>(logger_name, qt_object, meta_method);
} }
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> inline std::shared_ptr<logger> qt_logger_st(const std::string &logger_name, QTextEdit *qt_object, const std::string &meta_method = "append")
qt_logger_st(const std::string &logger_name, QTextEdit* qt_object, const std::string &meta_method = "append") { {
return Factory::template create<sinks::qt_sink_st>(logger_name, qt_object, meta_method); return Factory::template create<sinks::qt_sink_st>(logger_name, qt_object, meta_method);
} }
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> inline std::shared_ptr<logger> qt_logger_mt(
qt_logger_mt(const std::string &logger_name, QPlainTextEdit* qt_object , const std::string &meta_method = "appendPlainText") { const std::string &logger_name, QPlainTextEdit *qt_object, const std::string &meta_method = "appendPlainText")
{
return Factory::template create<sinks::qt_sink_mt>(logger_name, qt_object, meta_method); return Factory::template create<sinks::qt_sink_mt>(logger_name, qt_object, meta_method);
} }
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> inline std::shared_ptr<logger> qt_logger_st(
qt_logger_st(const std::string &logger_name, QPlainTextEdit* qt_object, const std::string &meta_method = "appendPlainText") { const std::string &logger_name, QPlainTextEdit *qt_object, const std::string &meta_method = "appendPlainText")
{
return Factory::template create<sinks::qt_sink_st>(logger_name, qt_object, meta_method); return Factory::template create<sinks::qt_sink_st>(logger_name, qt_object, meta_method);
} }
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> inline std::shared_ptr<logger> qt_logger_mt(const std::string &logger_name, QObject *qt_object, const std::string &meta_method)
qt_logger_mt(const std::string &logger_name, QObject* qt_object, const std::string &meta_method) { {
return Factory::template create<sinks::qt_sink_mt>(logger_name, qt_object, meta_method); return Factory::template create<sinks::qt_sink_mt>(logger_name, qt_object, meta_method);
} }
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> inline std::shared_ptr<logger> qt_logger_st(const std::string &logger_name, QObject *qt_object, const std::string &meta_method)
qt_logger_st(const std::string &logger_name, QObject* qt_object, const std::string &meta_method) { {
return Factory::template create<sinks::qt_sink_st>(logger_name, qt_object, meta_method); return Factory::template create<sinks::qt_sink_st>(logger_name, qt_object, meta_method);
} }
} // namespace spdlog } // namespace spdlog

@ -50,7 +50,11 @@ public:
{ {
memory_buf_t formatted; memory_buf_t formatted;
base_sink<Mutex>::formatter_->format(q_.at(i), formatted); base_sink<Mutex>::formatter_->format(q_.at(i), formatted);
#ifdef SPDLOG_USE_STD_FORMAT
ret.push_back(std::move(formatted));
#else
ret.push_back(fmt::to_string(formatted)); ret.push_back(fmt::to_string(formatted));
#endif
} }
return ret; return ret;
} }

@ -25,16 +25,27 @@ namespace sinks {
template<typename Mutex> template<typename Mutex>
SPDLOG_INLINE rotating_file_sink<Mutex>::rotating_file_sink( SPDLOG_INLINE rotating_file_sink<Mutex>::rotating_file_sink(
filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open) filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open, const file_event_handlers &event_handlers)
: base_filename_(std::move(base_filename)) : base_filename_(std::move(base_filename))
, max_size_(max_size) , max_size_(max_size)
, max_files_(max_files) , max_files_(max_files)
, file_helper_{event_handlers}
{ {
if (max_size == 0)
{
throw_spdlog_ex("rotating sink constructor: max_size arg cannot be zero");
}
if (max_files > 200000)
{
throw_spdlog_ex("rotating sink constructor: max_files arg cannot exceed 200000");
}
file_helper_.open(calc_filename(base_filename_, 0)); file_helper_.open(calc_filename(base_filename_, 0));
current_size_ = file_helper_.size(); // expensive. called only once current_size_ = file_helper_.size(); // expensive. called only once
if (rotate_on_open && current_size_ > 0) if (rotate_on_open && current_size_ > 0)
{ {
rotate_(); rotate_();
current_size_ = 0;
} }
} }
@ -50,7 +61,7 @@ SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::calc_filename(const filename
filename_t basename, ext; filename_t basename, ext;
std::tie(basename, ext) = details::file_helper::split_by_extension(filename); std::tie(basename, ext) = details::file_helper::split_by_extension(filename);
return fmt::format(SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); return fmt_lib::format(SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext);
} }
template<typename Mutex> template<typename Mutex>
@ -65,13 +76,22 @@ SPDLOG_INLINE void rotating_file_sink<Mutex>::sink_it_(const details::log_msg &m
{ {
memory_buf_t formatted; memory_buf_t formatted;
base_sink<Mutex>::formatter_->format(msg, formatted); base_sink<Mutex>::formatter_->format(msg, formatted);
current_size_ += formatted.size(); auto new_size = current_size_ + formatted.size();
if (current_size_ > max_size_)
// rotate if the new estimated file size exceeds max size.
// rotate only if the real size > 0 to better deal with full disk (see issue #2261).
// we only check the real size when new_size > max_size_ because it is relatively expensive.
if (new_size > max_size_)
{
file_helper_.flush();
if (file_helper_.size() > 0)
{ {
rotate_(); rotate_();
current_size_ = formatted.size(); new_size = formatted.size();
}
} }
file_helper_.write(formatted); file_helper_.write(formatted);
current_size_ = new_size;
} }
template<typename Mutex> template<typename Mutex>
@ -90,6 +110,7 @@ SPDLOG_INLINE void rotating_file_sink<Mutex>::rotate_()
{ {
using details::os::filename_to_str; using details::os::filename_to_str;
using details::os::path_exists; using details::os::path_exists;
file_helper_.close(); file_helper_.close();
for (auto i = max_files_; i > 0; --i) for (auto i = max_files_; i > 0; --i)
{ {

@ -22,7 +22,8 @@ template<typename Mutex>
class rotating_file_sink final : public base_sink<Mutex> class rotating_file_sink final : public base_sink<Mutex>
{ {
public: public:
rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open = false); rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open = false,
const file_event_handlers &event_handlers = {});
static filename_t calc_filename(const filename_t &filename, std::size_t index); static filename_t calc_filename(const filename_t &filename, std::size_t index);
filename_t filename(); filename_t filename();
@ -59,17 +60,19 @@ using rotating_file_sink_st = rotating_file_sink<details::null_mutex>;
// //
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> rotating_logger_mt( inline std::shared_ptr<logger> rotating_logger_mt(const std::string &logger_name, const filename_t &filename, size_t max_file_size,
const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files, bool rotate_on_open = false) size_t max_files, bool rotate_on_open = false, const file_event_handlers &event_handlers = {})
{ {
return Factory::template create<sinks::rotating_file_sink_mt>(logger_name, filename, max_file_size, max_files, rotate_on_open); return Factory::template create<sinks::rotating_file_sink_mt>(
logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers);
} }
template<typename Factory = spdlog::synchronous_factory> template<typename Factory = spdlog::synchronous_factory>
inline std::shared_ptr<logger> rotating_logger_st( inline std::shared_ptr<logger> rotating_logger_st(const std::string &logger_name, const filename_t &filename, size_t max_file_size,
const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files, bool rotate_on_open = false) size_t max_files, bool rotate_on_open = false, const file_event_handlers &event_handlers = {})
{ {
return Factory::template create<sinks::rotating_file_sink_st>(logger_name, filename, max_file_size, max_files, rotate_on_open); return Factory::template create<sinks::rotating_file_sink_st>(
logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers);
} }
} // namespace spdlog } // namespace spdlog

@ -16,7 +16,7 @@
// so instead we use ::FileWrite // so instead we use ::FileWrite
# include <spdlog/details/windows_include.h> # include <spdlog/details/windows_include.h>
# ifndef _USING_V110_SDK71_ // fileapi.h doesnt exist in winxp # ifndef _USING_V110_SDK71_ // fileapi.h doesn't exist in winxp
# include <fileapi.h> // WriteFile (..) # include <fileapi.h> // WriteFile (..)
# endif # endif
@ -37,7 +37,7 @@ SPDLOG_INLINE stdout_sink_base<ConsoleMutex>::stdout_sink_base(FILE *file)
#ifdef _WIN32 #ifdef _WIN32
// get windows handle from the FILE* object // get windows handle from the FILE* object
handle_ = (HANDLE)::_get_osfhandle(::_fileno(file_)); handle_ = reinterpret_cast<HANDLE>(::_get_osfhandle(::_fileno(file_)));
// don't throw to support cases where no console is attached, // don't throw to support cases where no console is attached,
// and let the log method to do nothing if (handle_ == INVALID_HANDLE_VALUE). // and let the log method to do nothing if (handle_ == INVALID_HANDLE_VALUE).
@ -60,7 +60,7 @@ SPDLOG_INLINE void stdout_sink_base<ConsoleMutex>::log(const details::log_msg &m
std::lock_guard<mutex_t> lock(mutex_); std::lock_guard<mutex_t> lock(mutex_);
memory_buf_t formatted; memory_buf_t formatted;
formatter_->format(msg, formatted); formatter_->format(msg, formatted);
::fflush(file_); // flush in case there is somthing in this file_ already ::fflush(file_); // flush in case there is something in this file_ already
auto size = static_cast<DWORD>(formatted.size()); auto size = static_cast<DWORD>(formatted.size());
DWORD bytes_written = 0; DWORD bytes_written = 0;
bool ok = ::WriteFile(handle_, formatted.data(), size, &bytes_written, nullptr) != 0; bool ok = ::WriteFile(handle_, formatted.data(), size, &bytes_written, nullptr) != 0;

@ -47,6 +47,24 @@ namespace win_eventlog {
namespace internal { namespace internal {
struct local_alloc_t
{
HLOCAL hlocal_;
SPDLOG_CONSTEXPR local_alloc_t() SPDLOG_NOEXCEPT : hlocal_(nullptr) {}
local_alloc_t(local_alloc_t const &) = delete;
local_alloc_t &operator=(local_alloc_t const &) = delete;
~local_alloc_t() SPDLOG_NOEXCEPT
{
if (hlocal_)
{
LocalFree(hlocal_);
}
}
};
/** Windows error */ /** Windows error */
struct win32_error : public spdlog_ex struct win32_error : public spdlog_ex
{ {
@ -55,22 +73,17 @@ struct win32_error : public spdlog_ex
{ {
std::string system_message; std::string system_message;
LPSTR format_message_result{}; local_alloc_t format_message_result{};
auto format_message_succeeded = auto format_message_succeeded =
::FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, ::FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&format_message_result, 0, nullptr); error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&format_message_result.hlocal_, 0, nullptr);
if (format_message_succeeded && format_message_result)
{
system_message = fmt::format(" ({})", format_message_result);
}
if (format_message_result) if (format_message_succeeded && format_message_result.hlocal_)
{ {
LocalFree((HLOCAL)format_message_result); system_message = fmt_lib::format(" ({})", (LPSTR)format_message_result.hlocal_);
} }
return fmt::format("{}: {}{}", user_message, error_code, system_message); return fmt_lib::format("{}: {}{}", user_message, error_code, system_message);
} }
explicit win32_error(std::string const &func_name, DWORD error = GetLastError()) explicit win32_error(std::string const &func_name, DWORD error = GetLastError())

@ -128,49 +128,49 @@ SPDLOG_API spdlog::logger *default_logger_raw();
SPDLOG_API void set_default_logger(std::shared_ptr<spdlog::logger> default_logger); SPDLOG_API void set_default_logger(std::shared_ptr<spdlog::logger> default_logger);
template<typename... Args> template<typename... Args>
inline void log(source_loc source, level::level_enum lvl, fmt::format_string<Args...> fmt, Args &&...args) inline void log(source_loc source, level::level_enum lvl, format_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->log(source, lvl, fmt, std::forward<Args>(args)...); default_logger_raw()->log(source, lvl, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
inline void log(level::level_enum lvl, fmt::format_string<Args...> fmt, Args &&...args) inline void log(level::level_enum lvl, format_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->log(source_loc{}, lvl, fmt, std::forward<Args>(args)...); default_logger_raw()->log(source_loc{}, lvl, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
inline void trace(fmt::format_string<Args...> fmt, Args &&...args) inline void trace(format_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->trace(fmt, std::forward<Args>(args)...); default_logger_raw()->trace(fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
inline void debug(fmt::format_string<Args...> fmt, Args &&...args) inline void debug(format_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->debug(fmt, std::forward<Args>(args)...); default_logger_raw()->debug(fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
inline void info(fmt::format_string<Args...> fmt, Args &&...args) inline void info(format_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->info(fmt, std::forward<Args>(args)...); default_logger_raw()->info(fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
inline void warn(fmt::format_string<Args...> fmt, Args &&...args) inline void warn(format_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->warn(fmt, std::forward<Args>(args)...); default_logger_raw()->warn(fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
inline void error(fmt::format_string<Args...> fmt, Args &&...args) inline void error(format_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->error(fmt, std::forward<Args>(args)...); default_logger_raw()->error(fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
inline void critical(fmt::format_string<Args...> fmt, Args &&...args) inline void critical(format_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->critical(fmt, std::forward<Args>(args)...); default_logger_raw()->critical(fmt, std::forward<Args>(args)...);
} }
@ -189,49 +189,49 @@ inline void log(level::level_enum lvl, const T &msg)
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
template<typename... Args> template<typename... Args>
inline void log(source_loc source, level::level_enum lvl, fmt::wformat_string<Args...> fmt, Args &&...args) inline void log(source_loc source, level::level_enum lvl, wformat_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->log(source, lvl, fmt, std::forward<Args>(args)...); default_logger_raw()->log(source, lvl, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
inline void log(level::level_enum lvl, fmt::wformat_string<Args...> fmt, Args &&...args) inline void log(level::level_enum lvl, wformat_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->log(source_loc{}, lvl, fmt, std::forward<Args>(args)...); default_logger_raw()->log(source_loc{}, lvl, fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
inline void trace(fmt::wformat_string<Args...> fmt, Args &&...args) inline void trace(wformat_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->trace(fmt, std::forward<Args>(args)...); default_logger_raw()->trace(fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
inline void debug(fmt::wformat_string<Args...> fmt, Args &&...args) inline void debug(wformat_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->debug(fmt, std::forward<Args>(args)...); default_logger_raw()->debug(fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
inline void info(fmt::wformat_string<Args...> fmt, Args &&...args) inline void info(wformat_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->info(fmt, std::forward<Args>(args)...); default_logger_raw()->info(fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
inline void warn(fmt::wformat_string<Args...> fmt, Args &&...args) inline void warn(wformat_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->warn(fmt, std::forward<Args>(args)...); default_logger_raw()->warn(fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
inline void error(fmt::wformat_string<Args...> fmt, Args &&...args) inline void error(wformat_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->error(fmt, std::forward<Args>(args)...); default_logger_raw()->error(fmt, std::forward<Args>(args)...);
} }
template<typename... Args> template<typename... Args>
inline void critical(fmt::wformat_string<Args...> fmt, Args &&...args) inline void critical(wformat_string_t<Args...> fmt, Args &&... args)
{ {
default_logger_raw()->critical(fmt, std::forward<Args>(args)...); default_logger_raw()->critical(fmt, std::forward<Args>(args)...);
} }

@ -48,7 +48,14 @@ public:
} // namespace spdlog } // namespace spdlog
// Support for fmt formatting (e.g. "{:012.9}" or just "{}") // Support for fmt formatting (e.g. "{:012.9}" or just "{}")
namespace fmt { namespace
#ifdef SPDLOG_USE_STD_FORMAT
std
#else
fmt
#endif
{
template<> template<>
struct formatter<spdlog::stopwatch> : formatter<double> struct formatter<spdlog::stopwatch> : formatter<double>
{ {
@ -58,4 +65,4 @@ struct formatter<spdlog::stopwatch> : formatter<double>
return formatter<double>::format(sw.elapsed().count(), ctx); return formatter<double>::format(sw.elapsed().count(), ctx);
} }
}; };
} // namespace fmt } // namespace std

@ -74,6 +74,13 @@
// #define SPDLOG_FMT_EXTERNAL // #define SPDLOG_FMT_EXTERNAL
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Uncomment to use C++20 std::format instead of fmt. This removes compile
// time checking of format strings, but doesn't depend on the fmt library.
//
// #define SPDLOG_USE_STD_FORMAT
///////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Uncomment to enable wchar_t support (convert to utf8) // Uncomment to enable wchar_t support (convert to utf8)
// //
@ -89,8 +96,7 @@
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Uncomment to customize level names (e.g. "MY TRACE") // Uncomment to customize level names (e.g. "MY TRACE")
// //
// #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING", // #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING", "MY ERROR", "MY CRITICAL", "OFF" }
// "MY ERROR", "MY CRITICAL", "OFF" }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -120,5 +126,9 @@
// __PRETTY_FUNCTION__ might be nicer in clang/gcc, and __FUNCTION__ in msvc. // __PRETTY_FUNCTION__ might be nicer in clang/gcc, and __FUNCTION__ in msvc.
// Defaults to __FUNCTION__ (should work on all compilers) if not defined. // Defaults to __FUNCTION__ (should work on all compilers) if not defined.
// //
// #ifdef __PRETTY_FUNCTION__
// # define SPDLOG_FUNCTION __PRETTY_FUNCTION__ // # define SPDLOG_FUNCTION __PRETTY_FUNCTION__
// #else
// # define SPDLOG_FUNCTION __FUNCTION__
// #endif
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

@ -6,12 +6,54 @@
# error Please define SPDLOG_COMPILED_LIB to compile this file. # error Please define SPDLOG_COMPILED_LIB to compile this file.
#endif #endif
#if !defined(SPDLOG_FMT_EXTERNAL) #if !defined(SPDLOG_FMT_EXTERNAL) && !defined(SPDLOG_USE_STD_FORMAT)
# include <spdlog/fmt/bundled/format-inl.h> # include <spdlog/fmt/bundled/format-inl.h>
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
// DEPRECATED!
template<typename T = void>
struct basic_data
{
FMT_API static constexpr const char digits[100][2] = {{'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'},
{'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'}, {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'},
{'1', '7'}, {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'},
{'2', '8'}, {'2', '9'}, {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'}, {'3', '6'}, {'3', '7'}, {'3', '8'},
{'3', '9'}, {'4', '0'}, {'4', '1'}, {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, {'4', '8'}, {'4', '9'},
{'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, {'6', '0'},
{'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'}, {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'},
{'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'},
{'8', '3'}, {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'},
{'9', '4'}, {'9', '5'}, {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};
FMT_API static constexpr const char hex_digits[] = "0123456789abcdef";
FMT_API static constexpr const char signs[4] = {0, '-', '+', ' '};
FMT_API static constexpr const char left_padding_shifts[5] = {31, 31, 0, 1, 0};
FMT_API static constexpr const char right_padding_shifts[5] = {0, 31, 0, 1, 0};
FMT_API static constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', 0x1000000u | ' '};
};
# ifdef FMT_SHARED
// Required for -flto, -fivisibility=hidden and -shared to work
extern template struct basic_data<void>;
# endif
# if __cplusplus < 201703L
// DEPRECATED! These are here only for ABI compatiblity.
template<typename T>
constexpr const char basic_data<T>::digits[][2];
template<typename T>
constexpr const char basic_data<T>::hex_digits[];
template<typename T>
constexpr const char basic_data<T>::signs[];
template<typename T>
constexpr const char basic_data<T>::left_padding_shifts[];
template<typename T>
constexpr const char basic_data<T>::right_padding_shifts[];
template<typename T>
constexpr const unsigned basic_data<T>::prefixes[];
# endif
template<typename T> template<typename T>
int format_float(char *buf, std::size_t size, const char *format, int precision, T value) int format_float(char *buf, std::size_t size, const char *format, int precision, T value)
{ {

@ -51,7 +51,7 @@ TEST_CASE("discard policy using factory ", "[async]")
auto logger = spdlog::create_async_nb<spdlog::sinks::test_sink_mt>("as2"); auto logger = spdlog::create_async_nb<spdlog::sinks::test_sink_mt>("as2");
auto test_sink = std::static_pointer_cast<spdlog::sinks::test_sink_mt>(logger->sinks()[0]); auto test_sink = std::static_pointer_cast<spdlog::sinks::test_sink_mt>(logger->sinks()[0]);
test_sink->set_delay(std::chrono::milliseconds(1)); test_sink->set_delay(std::chrono::milliseconds(3));
for (size_t i = 0; i < messages; i++) for (size_t i = 0; i < messages; i++)
{ {
@ -166,7 +166,7 @@ TEST_CASE("to_file", "[async]")
require_message_count(TEST_FILENAME, messages); require_message_count(TEST_FILENAME, messages);
auto contents = file_contents(TEST_FILENAME); auto contents = file_contents(TEST_FILENAME);
using spdlog::details::os::default_eol; using spdlog::details::os::default_eol;
REQUIRE(ends_with(contents, fmt::format("Hello message #1023{}", default_eol))); REQUIRE(ends_with(contents, spdlog::fmt_lib::format("Hello message #1023{}", default_eol)));
} }
TEST_CASE("to_file multi-workers", "[async]") TEST_CASE("to_file multi-workers", "[async]")

@ -3,7 +3,11 @@
*/ */
#include "includes.h" #include "includes.h"
#ifdef SPDLOG_USE_STD_FORMAT
using filename_memory_buf_t = std::basic_string<spdlog::filename_t::value_type>;
#else
using filename_memory_buf_t = fmt::basic_memory_buffer<spdlog::filename_t::value_type, 250>; using filename_memory_buf_t = fmt::basic_memory_buffer<spdlog::filename_t::value_type, 250>;
#endif
TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]") TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]")
{ {
@ -15,7 +19,7 @@ TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]")
spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_dateonly"); spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_dateonly");
std::tm tm = spdlog::details::os::localtime(); std::tm tm = spdlog::details::os::localtime();
filename_memory_buf_t w; filename_memory_buf_t w;
fmt::format_to( spdlog::fmt_lib::format_to(
std::back_inserter(w), SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); std::back_inserter(w), SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
auto logger = spdlog::create<sink_type>("logger", basename, 0, 0); auto logger = spdlog::create<sink_type>("logger", basename, 0, 0);
@ -28,10 +32,19 @@ TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]")
#ifdef SPDLOG_WCHAR_FILENAMES #ifdef SPDLOG_WCHAR_FILENAMES
spdlog::memory_buf_t buf; spdlog::memory_buf_t buf;
# ifdef SPDLOG_USE_STD_FORMAT
spdlog::details::os::wstr_to_utf8buf(w, buf);
auto &filename = buf;
# else
spdlog::details::os::wstr_to_utf8buf(fmt::to_string(w), buf); spdlog::details::os::wstr_to_utf8buf(fmt::to_string(w), buf);
auto filename = fmt::to_string(buf); auto filename = fmt::to_string(buf);
# endif
#else
# ifdef SPDLOG_USE_STD_FORMAT
auto &filename = w;
# else # else
auto filename = fmt::to_string(w); auto filename = fmt::to_string(w);
# endif
#endif #endif
require_message_count(filename, 10); require_message_count(filename, 10);
} }
@ -41,9 +54,13 @@ struct custom_daily_file_name_calculator
static spdlog::filename_t calc_filename(const spdlog::filename_t &basename, const tm &now_tm) static spdlog::filename_t calc_filename(const spdlog::filename_t &basename, const tm &now_tm)
{ {
filename_memory_buf_t w; filename_memory_buf_t w;
fmt::format_to(std::back_inserter(w), SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, spdlog::fmt_lib::format_to(std::back_inserter(w), SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"), basename, now_tm.tm_year + 1900,
now_tm.tm_mday); now_tm.tm_mon + 1, now_tm.tm_mday);
#ifdef SPDLOG_USE_STD_FORMAT
return w;
#else
return fmt::to_string(w); return fmt::to_string(w);
#endif
} }
}; };
@ -57,7 +74,7 @@ TEST_CASE("daily_logger with custom calculator", "[daily_logger]")
spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_dateonly"); spdlog::filename_t basename = SPDLOG_FILENAME_T("test_logs/daily_dateonly");
std::tm tm = spdlog::details::os::localtime(); std::tm tm = spdlog::details::os::localtime();
filename_memory_buf_t w; filename_memory_buf_t w;
fmt::format_to( spdlog::fmt_lib::format_to(
std::back_inserter(w), SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); std::back_inserter(w), SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
auto logger = spdlog::create<sink_type>("logger", basename, 0, 0); auto logger = spdlog::create<sink_type>("logger", basename, 0, 0);
@ -70,10 +87,19 @@ TEST_CASE("daily_logger with custom calculator", "[daily_logger]")
#ifdef SPDLOG_WCHAR_FILENAMES #ifdef SPDLOG_WCHAR_FILENAMES
spdlog::memory_buf_t buf; spdlog::memory_buf_t buf;
# ifdef SPDLOG_USE_STD_FORMAT
spdlog::details::os::wstr_to_utf8buf(w, buf);
auto &filename = buf;
# else
spdlog::details::os::wstr_to_utf8buf(fmt::to_string(w), buf); spdlog::details::os::wstr_to_utf8buf(fmt::to_string(w), buf);
auto filename = fmt::to_string(buf); auto filename = fmt::to_string(buf);
# endif
#else
# ifdef SPDLOG_USE_STD_FORMAT
auto &filename = w;
# else # else
auto filename = fmt::to_string(w); auto filename = fmt::to_string(w);
# endif
#endif #endif
require_message_count(filename, 10); require_message_count(filename, 10);
} }
@ -118,6 +144,16 @@ TEST_CASE("daily_file_sink::daily_filename_calculator", "[daily_file_sink]]")
} }
#endif #endif
TEST_CASE("daily_file_sink::daily_filename_format_calculator", "[daily_file_sink]]")
{
std::tm tm = spdlog::details::os::localtime();
// example-YYYY-MM-DD.log
auto filename = spdlog::sinks::daily_filename_format_calculator::calc_filename(SPDLOG_FILENAME_T("example-%Y-%m-%d.log"), tm);
REQUIRE(filename ==
spdlog::fmt_lib::format(SPDLOG_FILENAME_T("example-{:04d}-{:02d}-{:02d}.log"), tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday));
}
/* Test removal of old files */ /* Test removal of old files */
static spdlog::details::log_msg create_msg(std::chrono::seconds offset) static spdlog::details::log_msg create_msg(std::chrono::seconds offset)
{ {

@ -29,12 +29,16 @@ TEST_CASE("default_error_handler", "[errors]]")
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("test-error", filename, true); auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("test-error", filename, true);
logger->set_pattern("%v"); logger->set_pattern("%v");
#ifdef SPDLOG_USE_STD_FORMAT
logger->info("Test message {} {}", 1);
#else
logger->info(fmt::runtime("Test message {} {}"), 1); logger->info(fmt::runtime("Test message {} {}"), 1);
#endif
logger->info("Test message {}", 2); logger->info("Test message {}", 2);
logger->flush(); logger->flush();
using spdlog::details::os::default_eol; using spdlog::details::os::default_eol;
REQUIRE(file_contents(SIMPLE_LOG) == fmt::format("Test message 2{}", default_eol)); REQUIRE(file_contents(SIMPLE_LOG) == spdlog::fmt_lib::format("Test message 2{}", default_eol));
REQUIRE(count_lines(SIMPLE_LOG) == 1); REQUIRE(count_lines(SIMPLE_LOG) == 1);
} }
@ -49,7 +53,11 @@ TEST_CASE("custom_error_handler", "[errors]]")
logger->set_error_handler([=](const std::string &) { throw custom_ex(); }); logger->set_error_handler([=](const std::string &) { throw custom_ex(); });
logger->info("Good message #1"); 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); REQUIRE_THROWS_AS(logger->info(fmt::runtime("Bad format msg {} {}"), "xxx"), custom_ex);
#endif
logger->info("Good message #2"); logger->info("Good message #2");
require_message_count(SIMPLE_LOG, 2); require_message_count(SIMPLE_LOG, 2);
} }
@ -88,7 +96,11 @@ TEST_CASE("async_error_handler", "[errors]]")
ofs << err_msg; ofs << err_msg;
}); });
logger->info("Good message #1"); 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"); logger->info(fmt::runtime("Bad format msg {} {}"), "xxx");
#endif
logger->info("Good message #2"); logger->info("Good message #2");
spdlog::drop("logger"); // force logger to drain the queue and shutdown spdlog::drop("logger"); // force logger to drain the queue and shutdown
} }

@ -10,7 +10,7 @@ using spdlog::details::file_helper;
static void write_with_helper(file_helper &helper, size_t howmany) static void write_with_helper(file_helper &helper, size_t howmany)
{ {
spdlog::memory_buf_t formatted; spdlog::memory_buf_t formatted;
fmt::format_to(std::back_inserter(formatted), "{}", std::string(howmany, '1')); spdlog::fmt_lib::format_to(std::back_inserter(formatted), "{}", std::string(howmany, '1'));
helper.write(formatted); helper.write(formatted);
helper.flush(); helper.flush();
} }
@ -99,3 +99,70 @@ TEST_CASE("file_helper_split_by_extension", "[file_helper::split_by_extension()]
test_split_ext(SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T("")); test_split_ext(SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T(""));
test_split_ext(SPDLOG_FILENAME_T("..txt"), SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T(".txt")); test_split_ext(SPDLOG_FILENAME_T("..txt"), SPDLOG_FILENAME_T("."), SPDLOG_FILENAME_T(".txt"));
} }
TEST_CASE("file_event_handlers", "[file_helper]")
{
enum class flags
{
before_open,
after_open,
before_close,
after_close
};
prepare_logdir();
spdlog::filename_t test_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
// define event handles that update vector of flags when called
std::vector<flags> events;
spdlog::file_event_handlers handlers;
handlers.before_open = [&](spdlog::filename_t filename) {
REQUIRE(filename == test_filename);
events.push_back(flags::before_open);
};
handlers.after_open = [&](spdlog::filename_t filename, std::FILE *fstream) {
REQUIRE(filename == test_filename);
REQUIRE(fstream);
fputs("after_open\n", fstream);
events.push_back(flags::after_open);
};
handlers.before_close = [&](spdlog::filename_t filename, std::FILE *fstream) {
REQUIRE(filename == test_filename);
REQUIRE(fstream);
fputs("before_close\n", fstream);
events.push_back(flags::before_close);
};
handlers.after_close = [&](spdlog::filename_t filename) {
REQUIRE(filename == test_filename);
events.push_back(flags::after_close);
};
{
spdlog::details::file_helper helper{handlers};
REQUIRE(events.empty());
helper.open(test_filename);
REQUIRE(events == std::vector<flags>{flags::before_open, flags::after_open});
events.clear();
helper.close();
REQUIRE(events == std::vector<flags>{flags::before_close, flags::after_close});
REQUIRE(file_contents(TEST_FILENAME) == "after_open\nbefore_close\n");
helper.reopen(true);
events.clear();
}
// make sure that the file_helper destrcutor calls the close callbacks if needed
REQUIRE(events == std::vector<flags>{flags::before_close, flags::after_close});
REQUIRE(file_contents(TEST_FILENAME) == "after_open\nbefore_close\n");
}
TEST_CASE("file_helper_open", "[file_helper]")
{
prepare_logdir();
spdlog::filename_t target_filename = SPDLOG_FILENAME_T(TEST_FILENAME);
file_helper helper;
helper.open(target_filename);
helper.close();
target_filename += SPDLOG_FILENAME_T("/invalid");
REQUIRE_THROWS_AS(helper.open(target_filename), spdlog::spdlog_ex);
}

@ -20,7 +20,7 @@ TEST_CASE("simple_file_logger", "[simple_logger]]")
logger->flush(); logger->flush();
require_message_count(SIMPLE_LOG, 2); require_message_count(SIMPLE_LOG, 2);
using spdlog::details::os::default_eol; using spdlog::details::os::default_eol;
REQUIRE(file_contents(SIMPLE_LOG) == fmt::format("Test message 1{}Test message 2{}", default_eol, default_eol)); REQUIRE(file_contents(SIMPLE_LOG) == spdlog::fmt_lib::format("Test message 1{}Test message 2{}", default_eol, default_eol));
} }
TEST_CASE("flush_on", "[flush_on]]") TEST_CASE("flush_on", "[flush_on]]")
@ -41,7 +41,7 @@ TEST_CASE("flush_on", "[flush_on]]")
require_message_count(SIMPLE_LOG, 3); require_message_count(SIMPLE_LOG, 3);
using spdlog::details::os::default_eol; using spdlog::details::os::default_eol;
REQUIRE(file_contents(SIMPLE_LOG) == REQUIRE(file_contents(SIMPLE_LOG) ==
fmt::format("Should not be flushed{}Test message 1{}Test message 2{}", default_eol, default_eol, default_eol)); spdlog::fmt_lib::format("Should not be flushed{}Test message 1{}Test message 2{}", default_eol, default_eol, default_eol));
} }
TEST_CASE("rotating_file_logger1", "[rotating_logger]]") TEST_CASE("rotating_file_logger1", "[rotating_logger]]")
@ -98,3 +98,12 @@ TEST_CASE("rotating_file_logger2", "[rotating_logger]]")
REQUIRE(get_filesize(ROTATING_LOG) <= max_size); REQUIRE(get_filesize(ROTATING_LOG) <= max_size);
REQUIRE(get_filesize(ROTATING_LOG ".1") <= max_size); REQUIRE(get_filesize(ROTATING_LOG ".1") <= max_size);
} }
// test that passing max_size=0 throws
TEST_CASE("rotating_file_logger3", "[rotating_logger]]")
{
prepare_logdir();
size_t max_size = 0;
spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG);
REQUIRE_THROWS_AS(spdlog::rotating_logger_mt("logger", basename, max_size, 0), spdlog::spdlog_ex);
}

@ -8,28 +8,48 @@ void test_pad2(int n, const char *expected)
{ {
memory_buf_t buf; memory_buf_t buf;
spdlog::details::fmt_helper::pad2(n, buf); spdlog::details::fmt_helper::pad2(n, buf);
#ifdef SPDLOG_USE_STD_FORMAT
REQUIRE(buf == expected);
#else
REQUIRE(fmt::to_string(buf) == expected); REQUIRE(fmt::to_string(buf) == expected);
#endif
} }
void test_pad3(uint32_t n, const char *expected) void test_pad3(uint32_t n, const char *expected)
{ {
memory_buf_t buf; memory_buf_t buf;
spdlog::details::fmt_helper::pad3(n, buf); spdlog::details::fmt_helper::pad3(n, buf);
#ifdef SPDLOG_USE_STD_FORMAT
REQUIRE(buf == expected);
#else
REQUIRE(fmt::to_string(buf) == expected); REQUIRE(fmt::to_string(buf) == expected);
#endif
} }
void test_pad6(std::size_t n, const char *expected) void test_pad6(std::size_t n, const char *expected)
{ {
memory_buf_t buf; memory_buf_t buf;
spdlog::details::fmt_helper::pad6(n, buf); spdlog::details::fmt_helper::pad6(n, buf);
#ifdef SPDLOG_USE_STD_FORMAT
REQUIRE(buf == expected);
#else
REQUIRE(fmt::to_string(buf) == expected); REQUIRE(fmt::to_string(buf) == expected);
#endif
} }
void test_pad9(std::size_t n, const char *expected) void test_pad9(std::size_t n, const char *expected)
{ {
memory_buf_t buf; memory_buf_t buf;
spdlog::details::fmt_helper::pad9(n, buf); spdlog::details::fmt_helper::pad9(n, buf);
#ifdef SPDLOG_USE_STD_FORMAT
REQUIRE(buf == expected);
#else
REQUIRE(fmt::to_string(buf) == expected); REQUIRE(fmt::to_string(buf) == expected);
#endif
} }
TEST_CASE("pad2", "[fmt_helper]") TEST_CASE("pad2", "[fmt_helper]")

@ -25,9 +25,10 @@ TEST_CASE("debug and trace w/o format string", "[macros]]")
logger->flush(); logger->flush();
using spdlog::details::os::default_eol; using spdlog::details::os::default_eol;
REQUIRE(ends_with(file_contents(TEST_FILENAME), fmt::format("Test message 2{}", default_eol))); REQUIRE(ends_with(file_contents(TEST_FILENAME), spdlog::fmt_lib::format("Test message 2{}", default_eol)));
REQUIRE(count_lines(TEST_FILENAME) == 1); REQUIRE(count_lines(TEST_FILENAME) == 1);
auto orig_default_logger = spdlog::default_logger();
spdlog::set_default_logger(logger); spdlog::set_default_logger(logger);
SPDLOG_TRACE("Test message 3"); SPDLOG_TRACE("Test message 3");
@ -35,7 +36,8 @@ TEST_CASE("debug and trace w/o format string", "[macros]]")
logger->flush(); logger->flush();
require_message_count(TEST_FILENAME, 2); require_message_count(TEST_FILENAME, 2);
REQUIRE(ends_with(file_contents(TEST_FILENAME), fmt::format("Test message 4{}", default_eol))); REQUIRE(ends_with(file_contents(TEST_FILENAME), spdlog::fmt_lib::format("Test message 4{}", default_eol)));
spdlog::set_default_logger(std::move(orig_default_logger));
} }
TEST_CASE("disable param evaluation", "[macros]") TEST_CASE("disable param evaluation", "[macros]")
@ -50,13 +52,3 @@ TEST_CASE("pass logger pointer", "[macros]")
SPDLOG_LOGGER_TRACE(&ref, "Test message 1"); SPDLOG_LOGGER_TRACE(&ref, "Test message 1");
SPDLOG_LOGGER_DEBUG(&ref, "Test message 2"); SPDLOG_LOGGER_DEBUG(&ref, "Test message 2");
} }
// ensure that even if right macro level is on- don't evaluate if the logger's level is not high enough
// TEST_CASE("disable param evaluation2", "[macros]")
//{
// auto logger = std::make_shared<spdlog::logger>("test-macro");
// logger->set_level(spdlog::level::off);
// int x = 0;
// SPDLOG_LOGGER_DEBUG(logger, "Test message {}", ++x);
// REQUIRE(x == 0);
//}

@ -64,7 +64,7 @@ TEST_CASE("color range test1", "[pattern_formatter]")
auto formatter = std::make_shared<spdlog::pattern_formatter>("%^%v%$", spdlog::pattern_time_type::local, "\n"); auto formatter = std::make_shared<spdlog::pattern_formatter>("%^%v%$", spdlog::pattern_time_type::local, "\n");
memory_buf_t buf; memory_buf_t buf;
fmt::format_to(std::back_inserter(buf), "Hello"); spdlog::fmt_lib::format_to(std::back_inserter(buf), "Hello");
memory_buf_t formatted; memory_buf_t formatted;
std::string logger_name = "test"; std::string logger_name = "test";
spdlog::details::log_msg msg(logger_name, spdlog::level::info, spdlog::string_view_t(buf.data(), buf.size())); spdlog::details::log_msg msg(logger_name, spdlog::level::info, spdlog::string_view_t(buf.data(), buf.size()));
@ -273,7 +273,11 @@ TEST_CASE("clone-default-formatter", "[pattern_formatter]")
formatter_1->format(msg, formatted_1); formatter_1->format(msg, formatted_1);
formatter_2->format(msg, formatted_2); formatter_2->format(msg, formatted_2);
#ifdef SPDLOG_USE_STD_FORMAT
REQUIRE(formatted_1 == formatted_2);
#else
REQUIRE(fmt::to_string(formatted_1) == fmt::to_string(formatted_2)); REQUIRE(fmt::to_string(formatted_1) == fmt::to_string(formatted_2));
#endif
} }
TEST_CASE("clone-default-formatter2", "[pattern_formatter]") TEST_CASE("clone-default-formatter2", "[pattern_formatter]")
@ -288,7 +292,11 @@ TEST_CASE("clone-default-formatter2", "[pattern_formatter]")
formatter_1->format(msg, formatted_1); formatter_1->format(msg, formatted_1);
formatter_2->format(msg, formatted_2); formatter_2->format(msg, formatted_2);
#ifdef SPDLOG_USE_STD_FORMAT
REQUIRE(formatted_1 == formatted_2);
#else
REQUIRE(fmt::to_string(formatted_1) == fmt::to_string(formatted_2)); REQUIRE(fmt::to_string(formatted_1) == fmt::to_string(formatted_2));
#endif
} }
TEST_CASE("clone-formatter", "[pattern_formatter]") TEST_CASE("clone-formatter", "[pattern_formatter]")
@ -302,7 +310,12 @@ TEST_CASE("clone-formatter", "[pattern_formatter]")
memory_buf_t formatted_2; memory_buf_t formatted_2;
formatter_1->format(msg, formatted_1); formatter_1->format(msg, formatted_1);
formatter_2->format(msg, formatted_2); formatter_2->format(msg, formatted_2);
#ifdef SPDLOG_USE_STD_FORMAT
REQUIRE(formatted_1 == formatted_2);
#else
REQUIRE(fmt::to_string(formatted_1) == fmt::to_string(formatted_2)); REQUIRE(fmt::to_string(formatted_1) == fmt::to_string(formatted_2));
#endif
} }
TEST_CASE("clone-formatter-2", "[pattern_formatter]") TEST_CASE("clone-formatter-2", "[pattern_formatter]")
@ -317,7 +330,12 @@ TEST_CASE("clone-formatter-2", "[pattern_formatter]")
memory_buf_t formatted_2; memory_buf_t formatted_2;
formatter_1->format(msg, formatted_1); formatter_1->format(msg, formatted_1);
formatter_2->format(msg, formatted_2); formatter_2->format(msg, formatted_2);
#ifdef SPDLOG_USE_STD_FORMAT
REQUIRE(formatted_1 == formatted_2);
#else
REQUIRE(fmt::to_string(formatted_1) == fmt::to_string(formatted_2)); REQUIRE(fmt::to_string(formatted_1) == fmt::to_string(formatted_2));
#endif
} }
class custom_test_flag : public spdlog::custom_flag_formatter class custom_test_flag : public spdlog::custom_flag_formatter
@ -362,9 +380,15 @@ TEST_CASE("clone-custom_formatter", "[pattern_formatter]")
formatter_1->format(msg, formatted_1); formatter_1->format(msg, formatted_1);
formatter_2->format(msg, formatted_2); formatter_2->format(msg, formatted_2);
auto expected = fmt::format("[logger-name] [custom_output] some message{}", spdlog::details::os::default_eol); auto expected = spdlog::fmt_lib::format("[logger-name] [custom_output] some message{}", spdlog::details::os::default_eol);
#ifdef SPDLOG_USE_STD_FORMAT
REQUIRE(formatted_1 == expected);
REQUIRE(formatted_2 == expected);
#else
REQUIRE(fmt::to_string(formatted_1) == expected); REQUIRE(fmt::to_string(formatted_1) == expected);
REQUIRE(fmt::to_string(formatted_2) == expected); REQUIRE(fmt::to_string(formatted_2) == expected);
#endif
} }
// //
@ -385,7 +409,12 @@ TEST_CASE("short filename formatter-1", "[pattern_formatter]")
spdlog::source_loc source_loc{test_path, 123, "some_func()"}; spdlog::source_loc source_loc{test_path, 123, "some_func()"};
spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello"); spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello");
formatter.format(msg, formatted); formatter.format(msg, formatted);
#ifdef SPDLOG_USE_STD_FORMAT
REQUIRE(formatted == "myfile.cpp");
#else
REQUIRE(fmt::to_string(formatted) == "myfile.cpp"); REQUIRE(fmt::to_string(formatted) == "myfile.cpp");
#endif
} }
TEST_CASE("short filename formatter-2", "[pattern_formatter]") TEST_CASE("short filename formatter-2", "[pattern_formatter]")
@ -396,7 +425,12 @@ TEST_CASE("short filename formatter-2", "[pattern_formatter]")
spdlog::source_loc source_loc{"myfile.cpp", 123, "some_func()"}; spdlog::source_loc source_loc{"myfile.cpp", 123, "some_func()"};
spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello"); spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello");
formatter.format(msg, formatted); formatter.format(msg, formatted);
#ifdef SPDLOG_USE_STD_FORMAT
REQUIRE(formatted == "myfile.cpp:123");
#else
REQUIRE(fmt::to_string(formatted) == "myfile.cpp:123"); REQUIRE(fmt::to_string(formatted) == "myfile.cpp:123");
#endif
} }
TEST_CASE("short filename formatter-3", "[pattern_formatter]") TEST_CASE("short filename formatter-3", "[pattern_formatter]")
@ -407,7 +441,12 @@ TEST_CASE("short filename formatter-3", "[pattern_formatter]")
spdlog::source_loc source_loc{"", 123, "some_func()"}; spdlog::source_loc source_loc{"", 123, "some_func()"};
spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello"); spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello");
formatter.format(msg, formatted); formatter.format(msg, formatted);
#ifdef SPDLOG_USE_STD_FORMAT
REQUIRE(formatted == " Hello");
#else
REQUIRE(fmt::to_string(formatted) == " Hello"); REQUIRE(fmt::to_string(formatted) == " Hello");
#endif
} }
TEST_CASE("full filename formatter", "[pattern_formatter]") TEST_CASE("full filename formatter", "[pattern_formatter]")
@ -418,7 +457,12 @@ TEST_CASE("full filename formatter", "[pattern_formatter]")
spdlog::source_loc source_loc{test_path, 123, "some_func()"}; spdlog::source_loc source_loc{test_path, 123, "some_func()"};
spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello"); spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello");
formatter.format(msg, formatted); formatter.format(msg, formatted);
#ifdef SPDLOG_USE_STD_FORMAT
REQUIRE(formatted == test_path);
#else
REQUIRE(fmt::to_string(formatted) == test_path); REQUIRE(fmt::to_string(formatted) == test_path);
#endif
} }
TEST_CASE("custom flags", "[pattern_formatter]") TEST_CASE("custom flags", "[pattern_formatter]")
@ -430,8 +474,13 @@ TEST_CASE("custom flags", "[pattern_formatter]")
spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, "some message"); spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, "some message");
formatter->format(msg, formatted); formatter->format(msg, formatted);
auto expected = fmt::format("[logger-name] [custom1] [custom2] some message{}", spdlog::details::os::default_eol); auto expected = spdlog::fmt_lib::format("[logger-name] [custom1] [custom2] some message{}", spdlog::details::os::default_eol);
#ifdef SPDLOG_USE_STD_FORMAT
REQUIRE(formatted == expected);
#else
REQUIRE(fmt::to_string(formatted) == expected); REQUIRE(fmt::to_string(formatted) == expected);
#endif
} }
TEST_CASE("custom flags-padding", "[pattern_formatter]") TEST_CASE("custom flags-padding", "[pattern_formatter]")
@ -443,8 +492,13 @@ TEST_CASE("custom flags-padding", "[pattern_formatter]")
spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, "some message"); spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, "some message");
formatter->format(msg, formatted); formatter->format(msg, formatted);
auto expected = fmt::format("[logger-name] [custom1] [ custom2] some message{}", spdlog::details::os::default_eol); auto expected = spdlog::fmt_lib::format("[logger-name] [custom1] [ custom2] some message{}", spdlog::details::os::default_eol);
#ifdef SPDLOG_USE_STD_FORMAT
REQUIRE(formatted == expected);
#else
REQUIRE(fmt::to_string(formatted) == expected); REQUIRE(fmt::to_string(formatted) == expected);
#endif
} }
TEST_CASE("custom flags-exception", "[pattern_formatter]") TEST_CASE("custom flags-exception", "[pattern_formatter]")

Loading…
Cancel
Save