Compare commits

..

4 Commits

Author SHA1 Message Date
gabime
7f07e5e39f Fixed GMT offset test 2025-03-29 13:07:00 +03:00
gabime
1fdd210bd2 Merge branch 'fix-zformatter-apple' of https://github.com/toh-ableton/spdlog into toh-ableton-fix-zformatter-apple 2025-03-29 12:58:49 +03:00
toh
b524a4a661 Fix #3352 (Missing test for Apple / POSIX.1-2024 chooses buggy workaround)
Apple platforms have had the tm_gmtoff-field at least since Mac OS X 10.0,
as are POSIX.1-2024 conforming systems, which are also required to support
it.

This has the unfortunate effect to use the SunOS/Solaris fallback, which
doesn't compute the correct value if the passed value of tm isn't the
current system time, i.e. localtime(::time()) (#3351).
2025-03-06 11:54:59 +01:00
toh
2a7f8054a3 Add test case for #3351 (wrong GMT offset in SunOS/Solaris fallback) 2025-03-06 11:46:35 +01:00
59 changed files with 808 additions and 1183 deletions

View File

@@ -1,52 +0,0 @@
name: coverity-linux
on: [push, pull_request]
permissions:
contents: read
jobs:
coverity_scan:
runs-on: ubuntu-latest
name: Coverity Scan
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y curl build-essential cmake pkg-config libsystemd-dev
- name: Download Coverity Tool
run: |
curl -s -L --output coverity_tool.tgz "https://scan.coverity.com/download/linux64?token=${{ secrets.COVERITY_TOKEN }}&project=gabime%2Fspdlog"
mkdir coverity_tool
tar -C coverity_tool --strip-components=1 -xf coverity_tool.tgz
echo "$PWD/coverity_tool/bin" >> $GITHUB_PATH
- name: Build with Coverity
run: |
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=17
cd ..
cov-build --dir cov-int make -C build -j4
- name: Submit results to Coverity
run: |
tar czf cov-int.tgz cov-int
response=$(curl --silent --show-error --fail \
--form email="${{ secrets.EMAIL }}" \
--form token="${{ secrets.COVERITY_TOKEN }}" \
--form file=@cov-int.tgz \
--form version="GitHub PR #${{ github.event.pull_request.number }}" \
--form description="CI run for PR" \
https://scan.coverity.com/builds?project=gabime%2Fspdlog)
echo "$response"
if echo "$response" | grep -qi "Build successfully submitted"; then
echo "Coverity upload succeeded"
else
echo "Coverity upload failed or was rejected"
exit 1
fi

View File

@@ -18,8 +18,9 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
config: config:
- { compiler: gcc, version: 9, build_type: Release, cppstd: 11 } - { compiler: gcc, version: 7, build_type: Release, cppstd: 11 }
- { compiler: gcc, version: 11, build_type: Debug, cppstd: 17 } - { compiler: gcc, version: 9, build_type: Release, cppstd: 17 }
- { compiler: gcc, version: 11, build_type: Debug, cppstd: 20 }
- { compiler: gcc, version: 12, build_type: Release, cppstd: 20 } - { compiler: gcc, version: 12, build_type: Release, cppstd: 20 }
- { compiler: gcc, version: 12, build_type: Debug, cppstd: 20, asan: ON } - { compiler: gcc, version: 12, build_type: Debug, cppstd: 20, asan: ON }
- { compiler: clang, version: 12, build_type: Debug, cppstd: 17 } - { compiler: clang, version: 12, build_type: Debug, cppstd: 17 }

View File

@@ -75,5 +75,74 @@ jobs:
run: | run: |
build\tests\${{ matrix.config.BUILD_TYPE }}\spdlog-utests.exe build\tests\${{ matrix.config.BUILD_TYPE }}\spdlog-utests.exe
# -----------------------------------------------------------------------
# MSVC 2019 build matrix
# -----------------------------------------------------------------------
build_2019:
runs-on: windows-2019
strategy:
fail-fast: true
matrix:
config:
- GENERATOR: "Visual Studio 16 2019"
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
FATAL_ERRORS: 'ON'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 17
- GENERATOR: "Visual Studio 16 2019"
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
FATAL_ERRORS: 'ON'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 14
- GENERATOR: "Visual Studio 16 2019"
BUILD_TYPE: Release
BUILD_SHARED: 'ON'
FATAL_ERRORS: 'ON'
WCHAR: 'OFF'
WCHAR_FILES: 'OFF'
BUILD_EXAMPLE: 'ON'
USE_STD_FORMAT: 'OFF'
CXX_STANDARD: 11
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: CMake ${{ matrix.config.GENERATOR }} CXX=${{matrix.config.CXX_STANDARD}} WCHAR=${{matrix.config.WCHAR_FILES}} STD_FORMAT=${{matrix.config.USE_STD_FORMAT}}
shell: pwsh
run: |
mkdir build
cd build
cmake -G "${{ matrix.config.GENERATOR }}" -A x64 `
-D CMAKE_BUILD_TYPE=${{ matrix.config.BUILD_TYPE }} `
-D BUILD_SHARED_LIBS=${{ matrix.config.BUILD_SHARED }} `
-D SPDLOG_WCHAR_SUPPORT=${{ matrix.config.WCHAR }} `
-D SPDLOG_WCHAR_FILENAMES=${{ matrix.config.WCHAR_FILES }} `
-D SPDLOG_BUILD_EXAMPLE=${{ matrix.config.BUILD_EXAMPLE }} `
-D SPDLOG_BUILD_EXAMPLE_HO=${{ matrix.config.BUILD_EXAMPLE }} `
-D SPDLOG_BUILD_TESTS=ON `
-D SPDLOG_BUILD_TESTS_HO=OFF `
-D SPDLOG_BUILD_WARNINGS=${{ matrix.config.FATAL_ERRORS }} `
-D SPDLOG_USE_STD_FORMAT=${{ matrix.config.USE_STD_FORMAT }} `
-D CMAKE_CXX_STANDARD=${{ matrix.config.CXX_STANDARD }} ..
- name: Build
shell: pwsh
run: |
cd build
cmake --build . --parallel --config ${{ matrix.config.BUILD_TYPE }}
- name: Run Tests
shell: pwsh
env:
PATH: ${{ env.PATH }};${{ github.workspace }}\build\_deps\catch2-build\src\${{ matrix.config.BUILD_TYPE }};${{ github.workspace }}\build\${{ matrix.config.BUILD_TYPE }}
run: |
build\tests\${{ matrix.config.BUILD_TYPE }}\spdlog-utests.exe

View File

@@ -18,38 +18,39 @@ include(GNUInstallDirs)
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Set default build to release # Set default build to release
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE) set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose Release or Debug" FORCE)
endif() endif ()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Compiler config # Compiler config
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if(SPDLOG_USE_STD_FORMAT) if (SPDLOG_USE_STD_FORMAT)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
elseif(NOT CMAKE_CXX_STANDARD) elseif (NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif() endif ()
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
if(CMAKE_SYSTEM_NAME MATCHES "CYGWIN" OR CMAKE_SYSTEM_NAME MATCHES "MSYS" OR CMAKE_SYSTEM_NAME MATCHES "MINGW") if (CMAKE_SYSTEM_NAME MATCHES "CYGWIN" OR CMAKE_SYSTEM_NAME MATCHES "MSYS" OR CMAKE_SYSTEM_NAME MATCHES "MINGW")
set(CMAKE_CXX_EXTENSIONS ON) set(CMAKE_CXX_EXTENSIONS ON)
endif() endif ()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Set SPDLOG_MASTER_PROJECT to ON if we are building spdlog # Set SPDLOG_MASTER_PROJECT to ON if we are building spdlog
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Check if spdlog is being used directly or via add_subdirectory, but allow overriding # Check if spdlog is being used directly or via add_subdirectory, but allow overriding
if(NOT DEFINED SPDLOG_MASTER_PROJECT) if (NOT DEFINED SPDLOG_MASTER_PROJECT)
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(SPDLOG_MASTER_PROJECT ON) set(SPDLOG_MASTER_PROJECT ON)
else() else ()
set(SPDLOG_MASTER_PROJECT OFF) set(SPDLOG_MASTER_PROJECT OFF)
endif() endif ()
endif() endif ()
option(SPDLOG_BUILD_ALL "Build all artifacts" OFF) option(SPDLOG_BUILD_ALL "Build all artifacts" OFF)
@@ -62,9 +63,6 @@ option(SPDLOG_ENABLE_PCH "Build static or shared library using precompiled heade
# build position independent code # build position independent code
option(SPDLOG_BUILD_PIC "Build position independent code (-fPIC)" OFF) option(SPDLOG_BUILD_PIC "Build position independent code (-fPIC)" OFF)
# debug build postfix
set(SPDLOG_DEBUG_POSTFIX "d" CACHE STRING "Filename postfix for libraries in debug builds")
# example options # example options
option(SPDLOG_BUILD_EXAMPLE "Build example" ${SPDLOG_MASTER_PROJECT}) option(SPDLOG_BUILD_EXAMPLE "Build example" ${SPDLOG_MASTER_PROJECT})
option(SPDLOG_BUILD_EXAMPLE_HO "Build header only example" OFF) option(SPDLOG_BUILD_EXAMPLE_HO "Build header only example" OFF)
@@ -79,9 +77,9 @@ option(SPDLOG_BUILD_BENCH "Build benchmarks (Requires https://github.com/google/
# sanitizer options # sanitizer options
option(SPDLOG_SANITIZE_ADDRESS "Enable address sanitizer in tests" OFF) option(SPDLOG_SANITIZE_ADDRESS "Enable address sanitizer in tests" OFF)
option(SPDLOG_SANITIZE_THREAD "Enable thread sanitizer in tests" OFF) option(SPDLOG_SANITIZE_THREAD "Enable thread sanitizer in tests" OFF)
if(SPDLOG_SANITIZE_ADDRESS AND SPDLOG_SANITIZE_THREAD) if (SPDLOG_SANITIZE_ADDRESS AND SPDLOG_SANITIZE_THREAD)
message(FATAL_ERROR "SPDLOG_SANITIZE_ADDRESS and SPDLOG_SANITIZE_THREAD are mutually exclusive") message(FATAL_ERROR "SPDLOG_SANITIZE_ADDRESS and SPDLOG_SANITIZE_THREAD are mutually exclusive")
endif() endif ()
# warning options # warning options
option(SPDLOG_BUILD_WARNINGS "Enable compiler warnings" OFF) option(SPDLOG_BUILD_WARNINGS "Enable compiler warnings" OFF)
@@ -94,61 +92,61 @@ 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)
if(SPDLOG_FMT_EXTERNAL AND SPDLOG_FMT_EXTERNAL_HO) 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) 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") message(FATAL_ERROR "SPDLOG_USE_STD_FORMAT and SPDLOG_FMT_EXTERNAL_HO are mutually exclusive")
endif() endif ()
if(SPDLOG_USE_STD_FORMAT AND SPDLOG_FMT_EXTERNAL) if (SPDLOG_USE_STD_FORMAT AND SPDLOG_FMT_EXTERNAL)
message(FATAL_ERROR "SPDLOG_USE_STD_FORMAT and SPDLOG_FMT_EXTERNAL are mutually exclusive") message(FATAL_ERROR "SPDLOG_USE_STD_FORMAT and SPDLOG_FMT_EXTERNAL are mutually exclusive")
endif() 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)
option(SPDLOG_WCHAR_FILENAMES "Support wchar filenames" OFF) option(SPDLOG_WCHAR_FILENAMES "Support wchar filenames" OFF)
option(SPDLOG_WCHAR_CONSOLE "Support wchar output to console" OFF) option(SPDLOG_WCHAR_CONSOLE "Support wchar output to console" OFF)
else() else ()
set(SPDLOG_WCHAR_SUPPORT OFF CACHE BOOL "non supported option" FORCE) set(SPDLOG_WCHAR_SUPPORT OFF CACHE BOOL "non supported option" FORCE)
set(SPDLOG_WCHAR_FILENAMES OFF CACHE BOOL "non supported option" FORCE) set(SPDLOG_WCHAR_FILENAMES OFF CACHE BOOL "non supported option" FORCE)
set(SPDLOG_WCHAR_CONSOLE OFF CACHE BOOL "non supported option" FORCE) set(SPDLOG_WCHAR_CONSOLE OFF CACHE BOOL "non supported option" FORCE)
endif() endif ()
if(MSVC) if (MSVC)
option(SPDLOG_MSVC_UTF8 "Enable/disable msvc /utf-8 flag required by fmt lib" ON) option(SPDLOG_MSVC_UTF8 "Enable/disable msvc /utf-8 flag required by fmt lib" ON)
endif() endif ()
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
option(SPDLOG_CLOCK_COARSE "Use CLOCK_REALTIME_COARSE instead of the regular clock," OFF) option(SPDLOG_CLOCK_COARSE "Use CLOCK_REALTIME_COARSE instead of the regular clock," OFF)
else() else ()
set(SPDLOG_CLOCK_COARSE OFF CACHE BOOL "non supported option" FORCE) set(SPDLOG_CLOCK_COARSE OFF CACHE BOOL "non supported option" FORCE)
endif() endif ()
option(SPDLOG_PREVENT_CHILD_FD "Prevent from child processes to inherit log file descriptors" OFF) option(SPDLOG_PREVENT_CHILD_FD "Prevent from child processes to inherit log file descriptors" OFF)
option(SPDLOG_NO_THREAD_ID "prevent spdlog from querying the thread id on each log call if thread id is not needed" OFF) option(SPDLOG_NO_THREAD_ID "prevent spdlog from querying the thread id on each log call if thread id is not needed" OFF)
option(SPDLOG_NO_TLS "prevent spdlog from using thread local storage" OFF) option(SPDLOG_NO_TLS "prevent spdlog from using thread local storage" OFF)
option( option(
SPDLOG_NO_ATOMIC_LEVELS SPDLOG_NO_ATOMIC_LEVELS
"prevent spdlog from using of std::atomic log levels (use only if your code never modifies log levels concurrently" "prevent spdlog from using of std::atomic log levels (use only if your code never modifies log levels concurrently"
OFF) OFF)
option(SPDLOG_DISABLE_DEFAULT_LOGGER "Disable default logger creation" OFF) option(SPDLOG_DISABLE_DEFAULT_LOGGER "Disable default logger creation" OFF)
option(SPDLOG_FWRITE_UNLOCKED "Use the unlocked variant of fwrite. Leave this on unless your libc doesn't have it" ON) option(SPDLOG_FWRITE_UNLOCKED "Use the unlocked variant of fwrite. Leave this on unless your libc doesn't have it" ON)
# clang-tidy # clang-tidy
option(SPDLOG_TIDY "run clang-tidy" OFF) option(SPDLOG_TIDY "run clang-tidy" OFF)
if(SPDLOG_TIDY) if (SPDLOG_TIDY)
set(CMAKE_CXX_CLANG_TIDY "clang-tidy") set(CMAKE_CXX_CLANG_TIDY "clang-tidy")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
message(STATUS "Enabled clang-tidy") message(STATUS "Enabled clang-tidy")
endif() endif ()
if(SPDLOG_BUILD_PIC) if (SPDLOG_BUILD_PIC)
set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON)
endif() endif ()
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
message(STATUS "Build type: " ${CMAKE_BUILD_TYPE}) message(STATUS "Build type: " ${CMAKE_BUILD_TYPE})
@@ -157,56 +155,56 @@ 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_USE_STD_FORMAT AND 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/bundled_fmtlib_format.cpp) list(APPEND SPDLOG_SRCS src/bundled_fmtlib_format.cpp)
endif() endif ()
if(SPDLOG_BUILD_SHARED OR BUILD_SHARED_LIBS) if (SPDLOG_BUILD_SHARED OR BUILD_SHARED_LIBS)
if(WIN32) if (WIN32)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/version.rc @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.rc.in ${CMAKE_CURRENT_BINARY_DIR}/version.rc @ONLY)
list(APPEND SPDLOG_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc) list(APPEND SPDLOG_SRCS ${CMAKE_CURRENT_BINARY_DIR}/version.rc)
endif() endif ()
add_library(spdlog SHARED ${SPDLOG_SRCS} ${SPDLOG_ALL_HEADERS}) add_library(spdlog SHARED ${SPDLOG_SRCS} ${SPDLOG_ALL_HEADERS})
target_compile_definitions(spdlog PUBLIC SPDLOG_SHARED_LIB) target_compile_definitions(spdlog PUBLIC SPDLOG_SHARED_LIB)
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
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_USE_STD_FORMAT AND 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_LIB_EXPORT PUBLIC FMT_SHARED) target_compile_definitions(spdlog PRIVATE FMT_LIB_EXPORT PUBLIC FMT_SHARED)
endif() endif ()
else() else ()
add_library(spdlog STATIC ${SPDLOG_SRCS} ${SPDLOG_ALL_HEADERS}) add_library(spdlog STATIC ${SPDLOG_SRCS} ${SPDLOG_ALL_HEADERS})
endif() endif ()
add_library(spdlog::spdlog ALIAS spdlog) add_library(spdlog::spdlog ALIAS spdlog)
set(SPDLOG_INCLUDES_LEVEL "") set(SPDLOG_INCLUDES_LEVEL "")
if(SPDLOG_SYSTEM_INCLUDES) if (SPDLOG_SYSTEM_INCLUDES)
set(SPDLOG_INCLUDES_LEVEL "SYSTEM") set(SPDLOG_INCLUDES_LEVEL "SYSTEM")
endif() endif ()
target_compile_definitions(spdlog PUBLIC SPDLOG_COMPILED_LIB) target_compile_definitions(spdlog PUBLIC SPDLOG_COMPILED_LIB)
target_include_directories(spdlog ${SPDLOG_INCLUDES_LEVEL} PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>" target_include_directories(spdlog ${SPDLOG_INCLUDES_LEVEL} PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>") "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
target_link_libraries(spdlog PUBLIC Threads::Threads) target_link_libraries(spdlog PUBLIC Threads::Threads)
spdlog_enable_warnings(spdlog) spdlog_enable_warnings(spdlog)
set_target_properties(spdlog PROPERTIES VERSION ${SPDLOG_VERSION} SOVERSION set_target_properties(spdlog PROPERTIES VERSION ${SPDLOG_VERSION} SOVERSION
${SPDLOG_VERSION_MAJOR}.${SPDLOG_VERSION_MINOR}) ${SPDLOG_VERSION_MAJOR}.${SPDLOG_VERSION_MINOR})
set_target_properties(spdlog PROPERTIES DEBUG_POSTFIX ${SPDLOG_DEBUG_POSTFIX}) set_target_properties(spdlog PROPERTIES DEBUG_POSTFIX d)
if(COMMAND target_precompile_headers AND SPDLOG_ENABLE_PCH) if (COMMAND target_precompile_headers AND SPDLOG_ENABLE_PCH)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/pch.h.in ${PROJECT_BINARY_DIR}/spdlog_pch.h @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/pch.h.in ${PROJECT_BINARY_DIR}/spdlog_pch.h @ONLY)
target_precompile_headers(spdlog PRIVATE ${PROJECT_BINARY_DIR}/spdlog_pch.h) target_precompile_headers(spdlog PRIVATE ${PROJECT_BINARY_DIR}/spdlog_pch.h)
endif() endif ()
# sanitizer support # sanitizer support
if(SPDLOG_SANITIZE_ADDRESS) if (SPDLOG_SANITIZE_ADDRESS)
spdlog_enable_addr_sanitizer(spdlog) spdlog_enable_addr_sanitizer(spdlog)
elseif(SPDLOG_SANITIZE_THREAD) elseif (SPDLOG_SANITIZE_THREAD)
spdlog_enable_thread_sanitizer(spdlog) spdlog_enable_thread_sanitizer(spdlog)
endif() endif ()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Header only version # Header only version
@@ -215,134 +213,132 @@ add_library(spdlog_header_only INTERFACE)
add_library(spdlog::spdlog_header_only ALIAS spdlog_header_only) add_library(spdlog::spdlog_header_only ALIAS spdlog_header_only)
target_include_directories( target_include_directories(
spdlog_header_only ${SPDLOG_INCLUDES_LEVEL} INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>" spdlog_header_only ${SPDLOG_INCLUDES_LEVEL} INTERFACE "$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>") "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
target_link_libraries(spdlog_header_only INTERFACE Threads::Threads) target_link_libraries(spdlog_header_only INTERFACE Threads::Threads)
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Use fmt package if using external fmt # Use fmt package if using external fmt
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if(SPDLOG_FMT_EXTERNAL OR SPDLOG_FMT_EXTERNAL_HO) if (SPDLOG_FMT_EXTERNAL OR SPDLOG_FMT_EXTERNAL_HO)
if(NOT TARGET fmt::fmt) if (NOT TARGET fmt::fmt)
find_package(fmt CONFIG REQUIRED) find_package(fmt CONFIG REQUIRED)
endif() endif ()
target_compile_definitions(spdlog PUBLIC SPDLOG_FMT_EXTERNAL) target_compile_definitions(spdlog PUBLIC SPDLOG_FMT_EXTERNAL)
target_compile_definitions(spdlog_header_only INTERFACE SPDLOG_FMT_EXTERNAL) target_compile_definitions(spdlog_header_only INTERFACE SPDLOG_FMT_EXTERNAL)
# use external fmt-header-only # use external fmt-header-only
if(SPDLOG_FMT_EXTERNAL_HO) if (SPDLOG_FMT_EXTERNAL_HO)
target_link_libraries(spdlog PUBLIC fmt::fmt-header-only) target_link_libraries(spdlog PUBLIC fmt::fmt-header-only)
target_link_libraries(spdlog_header_only INTERFACE fmt::fmt-header-only) target_link_libraries(spdlog_header_only INTERFACE fmt::fmt-header-only)
else() # use external compile fmt else () # use external compile fmt
target_link_libraries(spdlog PUBLIC fmt::fmt) target_link_libraries(spdlog PUBLIC fmt::fmt)
target_link_libraries(spdlog_header_only INTERFACE fmt::fmt) target_link_libraries(spdlog_header_only INTERFACE fmt::fmt)
endif() endif ()
set(PKG_CONFIG_REQUIRES fmt) # add dependency to pkg-config set(PKG_CONFIG_REQUIRES fmt) # add dependency to pkg-config
endif() endif ()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Check if fwrite_unlocked/_fwrite_nolock is available # Check if fwrite_unlocked/_fwrite_nolock is available
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if(SPDLOG_FWRITE_UNLOCKED) if (SPDLOG_FWRITE_UNLOCKED)
include(CheckSymbolExists) include(CheckSymbolExists)
if(WIN32) if (WIN32)
check_symbol_exists(_fwrite_nolock "stdio.h" HAVE_FWRITE_UNLOCKED) check_symbol_exists(_fwrite_nolock "stdio.h" HAVE_FWRITE_UNLOCKED)
else() else ()
check_symbol_exists(fwrite_unlocked "stdio.h" HAVE_FWRITE_UNLOCKED) check_symbol_exists(fwrite_unlocked "stdio.h" HAVE_FWRITE_UNLOCKED)
endif() endif ()
if(HAVE_FWRITE_UNLOCKED) if (HAVE_FWRITE_UNLOCKED)
target_compile_definitions(spdlog PRIVATE SPDLOG_FWRITE_UNLOCKED) target_compile_definitions(spdlog PRIVATE SPDLOG_FWRITE_UNLOCKED)
target_compile_definitions(spdlog_header_only INTERFACE SPDLOG_FWRITE_UNLOCKED) target_compile_definitions(spdlog_header_only INTERFACE SPDLOG_FWRITE_UNLOCKED)
endif() endif ()
endif() endif ()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Add required libraries for Android CMake build # Add required libraries for Android CMake build
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if(ANDROID) if (ANDROID)
target_link_libraries(spdlog PUBLIC log) target_link_libraries(spdlog PUBLIC log)
target_link_libraries(spdlog_header_only INTERFACE log) target_link_libraries(spdlog_header_only INTERFACE log)
endif() endif ()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Misc definitions according to tweak options # Misc definitions according to tweak options
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
set(SPDLOG_WCHAR_TO_UTF8_SUPPORT ${SPDLOG_WCHAR_SUPPORT}) set(SPDLOG_WCHAR_TO_UTF8_SUPPORT ${SPDLOG_WCHAR_SUPPORT})
set(SPDLOG_UTF8_TO_WCHAR_CONSOLE ${SPDLOG_WCHAR_CONSOLE}) set(SPDLOG_UTF8_TO_WCHAR_CONSOLE ${SPDLOG_WCHAR_CONSOLE})
foreach( foreach (
SPDLOG_OPTION SPDLOG_OPTION
SPDLOG_WCHAR_TO_UTF8_SUPPORT SPDLOG_WCHAR_TO_UTF8_SUPPORT
SPDLOG_UTF8_TO_WCHAR_CONSOLE SPDLOG_UTF8_TO_WCHAR_CONSOLE
SPDLOG_WCHAR_FILENAMES SPDLOG_WCHAR_FILENAMES
SPDLOG_NO_EXCEPTIONS SPDLOG_NO_EXCEPTIONS
SPDLOG_CLOCK_COARSE SPDLOG_CLOCK_COARSE
SPDLOG_PREVENT_CHILD_FD SPDLOG_PREVENT_CHILD_FD
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) 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})
endif() endif ()
endforeach() endforeach ()
if(MSVC) if (MSVC)
target_compile_options(spdlog PRIVATE "/Zc:__cplusplus") target_compile_options(spdlog PRIVATE "/Zc:__cplusplus")
target_compile_options(spdlog_header_only INTERFACE "/Zc:__cplusplus") target_compile_options(spdlog_header_only INTERFACE "/Zc:__cplusplus")
if(SPDLOG_MSVC_UTF8) if (SPDLOG_MSVC_UTF8)
# fmtlib requires the /utf-8 flag when building with msvc. see https://github.com/fmtlib/fmt/pull/4159 on the # fmtlib requires the /utf-8 flag when building with msvc.
# purpose of the additional # see https://github.com/fmtlib/fmt/pull/4159 on the purpose of the additional
# "$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>" # "$<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>"
target_compile_options(spdlog PUBLIC $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>) target_compile_options(spdlog PUBLIC $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
target_compile_options(spdlog_header_only target_compile_options(spdlog_header_only INTERFACE $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
INTERFACE $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>) endif ()
endif() endif ()
endif()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# If exceptions are disabled, disable them in the bundled fmt as well # If exceptions are disabled, disable them in the bundled fmt as well
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if(SPDLOG_NO_EXCEPTIONS) if (SPDLOG_NO_EXCEPTIONS)
if(NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO) if (NOT SPDLOG_FMT_EXTERNAL AND NOT SPDLOG_FMT_EXTERNAL_HO)
target_compile_definitions(spdlog PUBLIC FMT_USE_EXCEPTIONS=0) target_compile_definitions(spdlog PUBLIC FMT_EXCEPTIONS=0)
endif() endif ()
if(NOT MSVC) if (NOT MSVC)
target_compile_options(spdlog PRIVATE -fno-exceptions) target_compile_options(spdlog PRIVATE -fno-exceptions)
else() else ()
target_compile_options(spdlog PRIVATE /EHs-c-) target_compile_options(spdlog PRIVATE /EHs-c-)
target_compile_definitions(spdlog PRIVATE _HAS_EXCEPTIONS=0) endif ()
endif() endif ()
endif()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Build binaries # Build binaries
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if(SPDLOG_BUILD_EXAMPLE OR SPDLOG_BUILD_EXAMPLE_HO OR SPDLOG_BUILD_ALL) if (SPDLOG_BUILD_EXAMPLE OR SPDLOG_BUILD_EXAMPLE_HO OR SPDLOG_BUILD_ALL)
message(STATUS "Generating example(s)") message(STATUS "Generating example(s)")
add_subdirectory(example) add_subdirectory(example)
spdlog_enable_warnings(example) spdlog_enable_warnings(example)
if(SPDLOG_BUILD_EXAMPLE_HO) if (SPDLOG_BUILD_EXAMPLE_HO)
spdlog_enable_warnings(example_header_only) spdlog_enable_warnings(example_header_only)
endif() endif ()
endif() endif ()
if(SPDLOG_BUILD_TESTS OR SPDLOG_BUILD_TESTS_HO OR SPDLOG_BUILD_ALL) if (SPDLOG_BUILD_TESTS OR SPDLOG_BUILD_TESTS_HO OR SPDLOG_BUILD_ALL)
message(STATUS "Generating tests") message(STATUS "Generating tests")
enable_testing() enable_testing()
add_subdirectory(tests) add_subdirectory(tests)
endif() endif ()
if(SPDLOG_BUILD_BENCH OR SPDLOG_BUILD_ALL) if (SPDLOG_BUILD_BENCH OR SPDLOG_BUILD_ALL)
message(STATUS "Generating benchmarks") message(STATUS "Generating benchmarks")
add_subdirectory(bench) add_subdirectory(bench)
endif() endif ()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Install # Install
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if(SPDLOG_INSTALL) if (SPDLOG_INSTALL)
message(STATUS "Generating install") message(STATUS "Generating install")
set(project_config_in "${CMAKE_CURRENT_LIST_DIR}/cmake/spdlogConfig.cmake.in") set(project_config_in "${CMAKE_CURRENT_LIST_DIR}/cmake/spdlogConfig.cmake.in")
set(project_config_out "${CMAKE_CURRENT_BINARY_DIR}/spdlogConfig.cmake") set(project_config_out "${CMAKE_CURRENT_BINARY_DIR}/spdlogConfig.cmake")
@@ -357,30 +353,30 @@ if(SPDLOG_INSTALL)
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
install(DIRECTORY include/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" PATTERN "fmt/bundled" EXCLUDE) install(DIRECTORY include/ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" PATTERN "fmt/bundled" EXCLUDE)
install( install(
TARGETS spdlog spdlog_header_only TARGETS spdlog spdlog_header_only
EXPORT spdlog EXPORT spdlog
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
if(NOT SPDLOG_USE_STD_FORMAT AND 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 ()
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
# Install pkg-config file # Install pkg-config file
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") if (IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}")
set(PKG_CONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}") set(PKG_CONFIG_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}")
else() else ()
set(PKG_CONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") set(PKG_CONFIG_INCLUDEDIR "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}")
endif() endif ()
if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") if (IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}")
set(PKG_CONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}") set(PKG_CONFIG_LIBDIR "${CMAKE_INSTALL_LIBDIR}")
else() else ()
set(PKG_CONFIG_LIBDIR "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}") set(PKG_CONFIG_LIBDIR "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}")
endif() endif ()
get_target_property(PKG_CONFIG_DEFINES spdlog INTERFACE_COMPILE_DEFINITIONS) get_target_property(PKG_CONFIG_DEFINES spdlog INTERFACE_COMPILE_DEFINITIONS)
string(REPLACE ";" " -D" PKG_CONFIG_DEFINES "${PKG_CONFIG_DEFINES}") string(REPLACE ";" " -D" PKG_CONFIG_DEFINES "${PKG_CONFIG_DEFINES}")
string(CONCAT PKG_CONFIG_DEFINES "-D" "${PKG_CONFIG_DEFINES}") string(CONCAT PKG_CONFIG_DEFINES "-D" "${PKG_CONFIG_DEFINES}")
@@ -391,7 +387,7 @@ if(SPDLOG_INSTALL)
# Install CMake config files # Install CMake config files
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
export(TARGETS spdlog spdlog_header_only NAMESPACE spdlog:: export(TARGETS spdlog spdlog_header_only NAMESPACE spdlog::
FILE "${CMAKE_CURRENT_BINARY_DIR}/${config_targets_file}") FILE "${CMAKE_CURRENT_BINARY_DIR}/${config_targets_file}")
install(EXPORT spdlog DESTINATION ${export_dest_dir} NAMESPACE spdlog:: FILE ${config_targets_file}) install(EXPORT spdlog DESTINATION ${export_dest_dir} NAMESPACE spdlog:: FILE ${config_targets_file})
include(CMakePackageConfigHelpers) include(CMakePackageConfigHelpers)
@@ -404,4 +400,4 @@ if(SPDLOG_INSTALL)
# Support creation of installable packages # Support creation of installable packages
# --------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------
include(cmake/spdlogCPack.cmake) include(cmake/spdlogCPack.cmake)
endif() endif ()

View File

@@ -11,7 +11,7 @@ Fast C++ logging library
## Install ## Install
#### Header-only version #### Header-only version
Copy the include [folder](include/spdlog) to your build tree and use a C++11 compiler. Copy the include [folder](https://github.com/gabime/spdlog/tree/v1.x/include/spdlog) to your build tree and use a C++11 compiler.
#### Compiled version (recommended - much faster compile times) #### Compiled version (recommended - much faster compile times)
```console ```console
@@ -19,7 +19,7 @@ $ git clone https://github.com/gabime/spdlog.git
$ cd spdlog && mkdir build && cd build $ cd spdlog && mkdir build && cd build
$ cmake .. && cmake --build . $ cmake .. && cmake --build .
``` ```
see example [CMakeLists.txt](example/CMakeLists.txt) on how to use. see example [CMakeLists.txt](https://github.com/gabime/spdlog/blob/v1.x/example/CMakeLists.txt) on how to use.
## Platforms ## Platforms
* Linux, FreeBSD, OpenBSD, Solaris, AIX * Linux, FreeBSD, OpenBSD, Solaris, AIX
@@ -48,7 +48,7 @@ see example [CMakeLists.txt](example/CMakeLists.txt) on how to use.
* Headers only or compiled * Headers only or compiled
* Feature-rich formatting, using the excellent [fmt](https://github.com/fmtlib/fmt) library. * Feature-rich formatting, using the excellent [fmt](https://github.com/fmtlib/fmt) library.
* Asynchronous mode (optional) * Asynchronous mode (optional)
* [Custom](https://github.com/gabime/spdlog/wiki/Custom-formatting) formatting. * [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting.
* Multi/Single threaded loggers. * Multi/Single threaded loggers.
* Various log targets: * Various log targets:
* Rotating log files. * Rotating log files.
@@ -58,7 +58,7 @@ see example [CMakeLists.txt](example/CMakeLists.txt) on how to use.
* Windows event log. * Windows event log.
* Windows debugger (```OutputDebugString(..)```). * Windows debugger (```OutputDebugString(..)```).
* Log to Qt widgets ([example](#log-to-qt-with-nice-colors)). * Log to Qt widgets ([example](#log-to-qt-with-nice-colors)).
* Easily [extendable](https://github.com/gabime/spdlog/wiki/Sinks#implementing-your-own-sink) with custom log targets. * Easily [extendable](https://github.com/gabime/spdlog/wiki/4.-Sinks#implementing-your-own-sink) with custom log targets.
* Log filtering - log levels can be modified at runtime as well as compile time. * Log filtering - log levels can be modified at runtime as well as compile time.
* Support for loading log levels from argv or environment var. * Support for loading log levels from argv or environment var.
* [Backtrace](#backtrace-support) support - store debug messages in a ring buffer and display them later on demand. * [Backtrace](#backtrace-support) support - store debug messages in a ring buffer and display them later on demand.
@@ -80,7 +80,7 @@ int main()
spdlog::info("Positional args are {1} {0}..", "too", "supported"); spdlog::info("Positional args are {1} {0}..", "too", "supported");
spdlog::info("{:<30}", "left aligned"); spdlog::info("{:<30}", "left aligned");
spdlog::set_level(spdlog::level::debug); // Set *global* log level to debug spdlog::set_level(spdlog::level::debug); // Set global log level to debug
spdlog::debug("This message should be displayed.."); spdlog::debug("This message should be displayed..");
// change log pattern // change log pattern
@@ -224,7 +224,7 @@ void binary_example()
```c++ ```c++
// create a logger with 2 targets, with different log levels and formats. // create a logger with 2 targets, with different log levels and formats.
// The console will show only warnings or errors, while the file will log all. // The console will show only warnings or errors, while the file will log all.
void multi_sink_example() void multi_sink_example()
{ {
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
@@ -241,29 +241,6 @@ void multi_sink_example()
} }
``` ```
---
#### Register several loggers - change global level
```c++
// Creation of loggers. Set levels to all registered loggers.
void set_level_example()
{
auto logger1 = spdlog::basic_logger_mt("logger1", "logs/logger1.txt");
auto logger2 = spdlog::basic_logger_mt("logger2", "logs/logger2.txt");
spdlog::set_default_logger(logger2);
spdlog::default_logger()->set_level(spdlog::level::trace); // set level for the default logger (logger2) to trace
spdlog::trace("trace message to the logger2 (specified as default)");
spdlog::set_level(spdlog::level::off) // (sic!) set level for *all* registered loggers to off (disable)
logger1.warn("warn message will not appear because the level set to off");
logger2.warn("warn message will not appear because the level set to off");
spdlog::warn("warn message will not appear because the level set to off");
}
```
--- ---
#### User-defined callbacks about log events #### User-defined callbacks about log events
```c++ ```c++
@@ -490,7 +467,7 @@ void mdc_example()
--- ---
## Benchmarks ## Benchmarks
Below are some [benchmarks](bench/bench.cpp) done in Ubuntu 64 bit, Intel i7-4770 CPU @ 3.40GHz Below are some [benchmarks](https://github.com/gabime/spdlog/blob/v1.x/bench/bench.cpp) done in Ubuntu 64 bit, Intel i7-4770 CPU @ 3.40GHz
#### Synchronous mode #### Synchronous mode
``` ```
@@ -542,12 +519,10 @@ Below are some [benchmarks](bench/bench.cpp) done in Ubuntu 64 bit, Intel i7-477
``` ```
## Documentation ## Documentation
Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki/1.-QuickStart) pages.
Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki) pages.
--- ---
### Powered by Thanks to [JetBrains](https://www.jetbrains.com/?from=spdlog) for donating product licenses to help develop **spdlog** <a href="https://www.jetbrains.com/?from=spdlog"><img src="logos/jetbrains-variant-4.svg" width="94" align="center" /></a>
<a href="https://jb.gg/OpenSource">
<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" alt="JetBrains logo" width="200">
</a>

View File

@@ -10,6 +10,7 @@
// details/file_helper-inl.h // details/file_helper-inl.h
// details/os-inl.h // details/os-inl.h
// fmt/bundled/core.h
// fmt/bundled/posix.h // fmt/bundled/posix.h
// logger-inl.h // logger-inl.h
// sinks/daily_file_sink.h // sinks/daily_file_sink.h
@@ -22,6 +23,7 @@
// details/os-inl.h // details/os-inl.h
// details/pattern_formatter-inl.h // details/pattern_formatter-inl.h
// fmt/bundled/core.h
// fmt/bundled/format-inl.h // fmt/bundled/format-inl.h
#include <cstring> #include <cstring>
@@ -71,6 +73,7 @@
// details/registry.h // details/registry.h
// details/tcp_client-windows.h // details/tcp_client-windows.h
// details/tcp_client.h // details/tcp_client.h
// fmt/bundled/core.h
// sinks/android_sink.h // sinks/android_sink.h
// sinks/ansicolor_sink.h // sinks/ansicolor_sink.h
// sinks/basic_file_sink.h // sinks/basic_file_sink.h
@@ -147,6 +150,7 @@
// common.h // common.h
// details/fmt_helper.h // details/fmt_helper.h
// fmt/bundled/core.h
// fmt/bundled/ranges.h // fmt/bundled/ranges.h
#include <type_traits> #include <type_traits>
@@ -251,4 +255,4 @@
#include <mutex> #include <mutex>
// spdlog // spdlog
#include <spdlog/common.h> #include <spdlog/common.h>

View File

@@ -33,41 +33,41 @@ void mdc_example();
#include "spdlog/fmt/ostr.h" // support for user defined types #include "spdlog/fmt/ostr.h" // support for user defined types
int main(int, char *[]) { int main(int, char *[]) {
// Log levels can be loaded from argv/env using "SPDLOG_LEVEL"
load_levels_example();
spdlog::info("Welcome to spdlog version {}.{}.{} !", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR,
SPDLOG_VER_PATCH);
spdlog::warn("Easy padding in numbers like {:08d}", 12);
spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
spdlog::info("Support for floats {:03.2f}", 1.23456);
spdlog::info("Positional args are {1} {0}..", "too", "supported");
spdlog::info("{:>8} aligned, {:<8} aligned", "right", "left");
// Runtime log levels
spdlog::set_level(spdlog::level::info); // Set global log level to info
spdlog::debug("This message should not be displayed!");
spdlog::set_level(spdlog::level::trace); // Set specific logger's log level
spdlog::debug("This message should be displayed..");
// Customize msg format for all loggers
spdlog::set_pattern("[%H:%M:%S %z] [%^%L%$] [thread %t] %v");
spdlog::info("This an info message with custom format");
spdlog::set_pattern("%+"); // back to default format
spdlog::set_level(spdlog::level::info);
// Backtrace support
// Loggers can store in a ring buffer all messages (including debug/trace) for later inspection.
// When needed, call dump_backtrace() to see what happened:
spdlog::enable_backtrace(10); // create ring buffer with capacity of 10 messages
for (int i = 0; i < 100; i++) {
spdlog::debug("Backtrace message {}", i); // not logged..
}
// e.g. if some error happened:
spdlog::dump_backtrace(); // log them now!
try { try {
// Log levels can be loaded from argv/env using "SPDLOG_LEVEL"
load_levels_example();
spdlog::info("Welcome to spdlog version {}.{}.{} !", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR,
SPDLOG_VER_PATCH);
spdlog::warn("Easy padding in numbers like {:08d}", 12);
spdlog::critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
spdlog::info("Support for floats {:03.2f}", 1.23456);
spdlog::info("Positional args are {1} {0}..", "too", "supported");
spdlog::info("{:>8} aligned, {:<8} aligned", "right", "left");
// Runtime log levels
spdlog::set_level(spdlog::level::info); // Set global log level to info
spdlog::debug("This message should not be displayed!");
spdlog::set_level(spdlog::level::trace); // Set specific logger's log level
spdlog::debug("This message should be displayed..");
// Customize msg format for all loggers
spdlog::set_pattern("[%H:%M:%S %z] [%^%L%$] [thread %t] %v");
spdlog::info("This an info message with custom format");
spdlog::set_pattern("%+"); // back to default format
spdlog::set_level(spdlog::level::info);
// Backtrace support
// Loggers can store in a ring buffer all messages (including debug/trace) for later
// inspection. When needed, call dump_backtrace() to see what happened:
spdlog::enable_backtrace(10); // create ring buffer with capacity of 10 messages
for (int i = 0; i < 100; i++) {
spdlog::debug("Backtrace message {}", i); // not logged..
}
// e.g. if some error happened:
spdlog::dump_backtrace(); // log them now!
stdout_logger_example(); stdout_logger_example();
basic_example(); basic_example();
rotating_example(); rotating_example();
@@ -269,7 +269,7 @@ void multi_sink_example() {
struct my_type { struct my_type {
int i = 0; int i = 0;
explicit my_type(int i) explicit my_type(int i)
: i(i) {} : i(i){}
}; };
#ifndef SPDLOG_USE_STD_FORMAT // when using fmtlib #ifndef SPDLOG_USE_STD_FORMAT // when using fmtlib
@@ -371,23 +371,25 @@ void replace_default_logger_example() {
// store the old logger so we don't break other examples. // store the old logger so we don't break other examples.
auto old_logger = spdlog::default_logger(); auto old_logger = spdlog::default_logger();
auto new_logger = spdlog::basic_logger_mt("new_default_logger", "logs/somelog.txt", true); auto new_logger =
spdlog::set_default_logger(std::move(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::set_level(spdlog::level::info);
spdlog::debug("This message should not be displayed!"); spdlog::debug("This message should not be displayed!");
spdlog::set_level(spdlog::level::trace); spdlog::set_level(spdlog::level::trace);
spdlog::debug("This message should be displayed.."); spdlog::debug("This message should be displayed..");
spdlog::set_default_logger(std::move(old_logger));
spdlog::set_default_logger(old_logger);
} }
// Mapped Diagnostic Context (MDC) is a map that stores key-value pairs (string values) in thread // Mapped Diagnostic Context (MDC) is a map that stores key-value pairs (string values) in thread local storage.
// local storage. Each thread maintains its own MDC, which loggers use to append diagnostic // Each thread maintains its own MDC, which loggers use to append diagnostic information to log outputs.
// information to log outputs. Note: it is not supported in asynchronous mode due to its reliance on // Note: it is not supported in asynchronous mode due to its reliance on thread-local storage.
// thread-local storage.
#ifndef SPDLOG_NO_TLS #ifndef SPDLOG_NO_TLS
#include "spdlog/mdc.h" #include "spdlog/mdc.h"
void mdc_example() { void mdc_example()
{
spdlog::mdc::put("key1", "value1"); spdlog::mdc::put("key1", "value1");
spdlog::mdc::put("key2", "value2"); spdlog::mdc::put("key2", "value2");
// if not using the default format, you can use the %& formatter to print mdc data as well // if not using the default format, you can use the %& formatter to print mdc data as well

View File

@@ -89,7 +89,8 @@ inline void init_thread_pool(size_t q_size,
} }
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.

View File

@@ -33,7 +33,7 @@ SPDLOG_INLINE spdlog::async_logger::async_logger(std::string logger_name,
// send the log message to the thread pool // send the log message to the thread pool
SPDLOG_INLINE void spdlog::async_logger::sink_it_(const details::log_msg &msg){ SPDLOG_INLINE void spdlog::async_logger::sink_it_(const details::log_msg &msg){
SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){ SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){
pool_ptr -> post_log(shared_from_this(), msg, overflow_policy_); pool_ptr->post_log(shared_from_this(), msg, overflow_policy_);
} }
else { else {
throw_spdlog_ex("async log: thread pool doesn't exist anymore"); throw_spdlog_ex("async log: thread pool doesn't exist anymore");
@@ -45,7 +45,7 @@ SPDLOG_LOGGER_CATCH(msg.source)
// send flush request to the thread pool // send flush request to the thread pool
SPDLOG_INLINE void spdlog::async_logger::flush_(){ SPDLOG_INLINE void spdlog::async_logger::flush_(){
SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){ SPDLOG_TRY{if (auto pool_ptr = thread_pool_.lock()){
pool_ptr -> post_flush(shared_from_this(), overflow_policy_); pool_ptr->post_flush(shared_from_this(), overflow_policy_);
} }
else { else {
throw_spdlog_ex("async flush: thread pool doesn't exist anymore"); throw_spdlog_ex("async flush: thread pool doesn't exist anymore");

View File

@@ -9,6 +9,7 @@
#include <spdlog/details/os.h> #include <spdlog/details/os.h>
#include <spdlog/details/registry.h> #include <spdlog/details/registry.h>
#include <spdlog/spdlog.h>
#include <algorithm> #include <algorithm>
#include <sstream> #include <sstream>
@@ -35,7 +36,7 @@ inline std::string &trim_(std::string &str) {
return str; return str;
} }
// return (name,value) trimmed pair from the given "name = value" string. // return (name,value) trimmed pair from given "name=value" string.
// return empty string on missing parts // return empty string on missing parts
// "key=val" => ("key", "val") // "key=val" => ("key", "val")
// " key = val " => ("key", "val") // " key = val " => ("key", "val")
@@ -54,7 +55,7 @@ inline std::pair<std::string, std::string> extract_kv_(char sep, const std::stri
return std::make_pair(trim_(k), trim_(v)); return std::make_pair(trim_(k), trim_(v));
} }
// return vector of key/value pairs from a sequence of "K1=V1,K2=V2,.." // return vector of key/value pairs from sequence of "K1=V1,K2=V2,.."
// "a=AAA,b=BBB,c=CCC,.." => {("a","AAA"),("b","BBB"),("c", "CCC"),...} // "a=AAA,b=BBB,c=CCC,.." => {("a","AAA"),("b","BBB"),("c", "CCC"),...}
inline std::unordered_map<std::string, std::string> extract_key_vals_(const std::string &str) { inline std::unordered_map<std::string, std::string> extract_key_vals_(const std::string &str) {
std::string token; std::string token;
@@ -71,7 +72,7 @@ inline std::unordered_map<std::string, std::string> extract_key_vals_(const std:
} }
SPDLOG_INLINE void load_levels(const std::string &input) { SPDLOG_INLINE void load_levels(const std::string &input) {
if (input.empty() || input.size() >= 32768) { if (input.empty() || input.size() > 512) {
return; return;
} }
@@ -81,14 +82,14 @@ SPDLOG_INLINE void load_levels(const std::string &input) {
bool global_level_found = false; bool global_level_found = false;
for (auto &name_level : key_vals) { for (auto &name_level : key_vals) {
const auto &logger_name = name_level.first; auto &logger_name = name_level.first;
const auto &level_name = to_lower_(name_level.second); auto level_name = to_lower_(name_level.second);
auto level = level::from_str(level_name); auto level = level::from_str(level_name);
// ignore unrecognized level names // ignore unrecognized level names
if (level == level::off && level_name != "off") { if (level == level::off && level_name != "off") {
continue; continue;
} }
if (logger_name.empty()) // no logger name indicates global level if (logger_name.empty()) // no logger name indicate global level
{ {
global_level_found = true; global_level_found = true;
global_level = level; global_level = level;

View File

@@ -364,7 +364,7 @@ SPDLOG_CONSTEXPR_FUNC spdlog::wstring_view_t to_string_view(spdlog::wstring_view
} }
#endif #endif
#if defined(SPDLOG_USE_STD_FORMAT) && __cpp_lib_format >= 202207L #if defined(SPDLOG_USE_STD_FORMAT) && __cpp_lib_format >= 202207L
template <typename T, typename... Args> template <typename T, typename... Args>
SPDLOG_CONSTEXPR_FUNC std::basic_string_view<T> to_string_view( SPDLOG_CONSTEXPR_FUNC std::basic_string_view<T> to_string_view(
std::basic_format_string<T, Args...> fmt) SPDLOG_NOEXCEPT { std::basic_format_string<T, Args...> fmt) SPDLOG_NOEXCEPT {

View File

@@ -11,8 +11,10 @@
#include <spdlog/details/os.h> #include <spdlog/details/os.h>
#include <cerrno> #include <cerrno>
#include <chrono>
#include <cstdio> #include <cstdio>
#include <string> #include <string>
#include <thread>
#include <tuple> #include <tuple>
namespace spdlog { namespace spdlog {

View File

@@ -23,6 +23,7 @@
#ifdef _WIN32 #ifdef _WIN32
#include <spdlog/details/windows_include.h> #include <spdlog/details/windows_include.h>
#include <fileapi.h> // for FlushFileBuffers
#include <io.h> // for _get_osfhandle, _isatty, _fileno #include <io.h> // for _get_osfhandle, _isatty, _fileno
#include <process.h> // for _get_pid #include <process.h> // for _get_pid
@@ -264,10 +265,10 @@ SPDLOG_INLINE int utc_minutes_offset(const std::tm &tm) {
return offset; return offset;
#else #else
#if defined(sun) || defined(__sun) || defined(_AIX) || \ #if defined(sun) || defined(__sun) || defined(_AIX) || \
(defined(__NEWLIB__) && !defined(__TM_GMTOFF)) || \ (defined(__NEWLIB__) && !defined(__TM_GMTOFF)) || \
(!defined(__APPLE__) && !defined(_BSD_SOURCE) && !defined(_GNU_SOURCE) && \ (!defined(__APPLE__) && !defined(_BSD_SOURCE) && !defined(_GNU_SOURCE) && \
(!defined(_POSIX_VERSION) || (_POSIX_VERSION < 202405L))) (!defined(_POSIX_VERSION) || (_POSIX_VERSION < 202405L)))
// 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris // 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris
struct helper { struct helper {
static long int calculate_gmt_offset(const std::tm &localtm = details::os::localtime(), static long int calculate_gmt_offset(const std::tm &localtm = details::os::localtime(),
@@ -482,12 +483,13 @@ SPDLOG_INLINE void utf8_to_wstrbuf(string_view_t str, wmemory_buf_t &target) {
} }
// find the size to allocate for the result buffer // find the size to allocate for the result buffer
int result_size = ::MultiByteToWideChar(CP_UTF8, 0, str.data(), str_size, NULL, 0); int result_size =
::MultiByteToWideChar(CP_UTF8, 0, str.data(), str_size, NULL, 0);
if (result_size > 0) { if (result_size > 0) {
target.resize(result_size); target.resize(result_size);
result_size = result_size = ::MultiByteToWideChar(CP_UTF8, 0, str.data(), str_size, target.data(),
::MultiByteToWideChar(CP_UTF8, 0, str.data(), str_size, target.data(), result_size); result_size);
if (result_size > 0) { if (result_size > 0) {
assert(result_size == target.size()); assert(result_size == target.size());
return; return;
@@ -562,21 +564,21 @@ SPDLOG_INLINE filename_t dir_name(const filename_t &path) {
return pos != filename_t::npos ? path.substr(0, pos) : filename_t{}; return pos != filename_t::npos ? path.substr(0, pos) : filename_t{};
} }
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4996)
#endif // _MSC_VER
std::string SPDLOG_INLINE getenv(const char *field) { std::string SPDLOG_INLINE getenv(const char *field) {
#if defined(_MSC_VER) && defined(__cplusplus_winrt) #if defined(_MSC_VER)
#if defined(__cplusplus_winrt)
return std::string{}; // not supported under uwp return std::string{}; // not supported under uwp
#else #else
char *buf = std::getenv(field); size_t len = 0;
char buf[128];
bool ok = ::getenv_s(&len, buf, sizeof(buf), field) == 0;
return ok ? buf : std::string{};
#endif
#else // revert to getenv
char *buf = ::getenv(field);
return buf ? buf : std::string{}; return buf ? buf : std::string{};
#endif #endif
} }
#ifdef _MSC_VER
#pragma warning(pop)
#endif // _MSC_VER
// Do fsync by FILE handlerpointer // Do fsync by FILE handlerpointer
// Return true on success // Return true on success
@@ -591,13 +593,13 @@ SPDLOG_INLINE bool fsync(FILE *fp) {
// Do non-locking fwrite if possible by the os or use the regular locking fwrite // Do non-locking fwrite if possible by the os or use the regular locking fwrite
// Return true on success. // Return true on success.
SPDLOG_INLINE bool fwrite_bytes(const void *ptr, const size_t n_bytes, FILE *fp) { SPDLOG_INLINE bool fwrite_bytes(const void *ptr, const size_t n_bytes, FILE *fp) {
#if defined(_WIN32) && defined(SPDLOG_FWRITE_UNLOCKED) #if defined(_WIN32) && defined(SPDLOG_FWRITE_UNLOCKED)
return _fwrite_nolock(ptr, 1, n_bytes, fp) == n_bytes; return _fwrite_nolock(ptr, 1, n_bytes, fp) == n_bytes;
#elif defined(SPDLOG_FWRITE_UNLOCKED) #elif defined(SPDLOG_FWRITE_UNLOCKED)
return ::fwrite_unlocked(ptr, 1, n_bytes, fp) == n_bytes; return ::fwrite_unlocked(ptr, 1, n_bytes, fp) == n_bytes;
#else #else
return std::fwrite(ptr, 1, n_bytes, fp) == n_bytes; return std::fwrite(ptr, 1, n_bytes, fp) == n_bytes;
#endif #endif
} }
} // namespace os } // namespace os

View File

@@ -54,11 +54,6 @@ SPDLOG_INLINE void registry::register_logger(std::shared_ptr<logger> new_logger)
register_logger_(std::move(new_logger)); register_logger_(std::move(new_logger));
} }
SPDLOG_INLINE void registry::register_or_replace(std::shared_ptr<logger> new_logger) {
std::lock_guard<std::mutex> lock(logger_map_mutex_);
register_or_replace_(std::move(new_logger));
}
SPDLOG_INLINE void registry::initialize_logger(std::shared_ptr<logger> new_logger) { SPDLOG_INLINE void registry::initialize_logger(std::shared_ptr<logger> new_logger) {
std::lock_guard<std::mutex> lock(logger_map_mutex_); std::lock_guard<std::mutex> lock(logger_map_mutex_);
new_logger->set_formatter(formatter_->clone()); new_logger->set_formatter(formatter_->clone());
@@ -101,7 +96,7 @@ SPDLOG_INLINE std::shared_ptr<logger> registry::default_logger() {
SPDLOG_INLINE logger *registry::get_default_raw() { return default_logger_.get(); } SPDLOG_INLINE logger *registry::get_default_raw() { return default_logger_.get(); }
// set default logger. // set default logger.
// the default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map. // default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map.
SPDLOG_INLINE void registry::set_default_logger(std::shared_ptr<logger> new_default_logger) { SPDLOG_INLINE void registry::set_default_logger(std::shared_ptr<logger> new_default_logger) {
std::lock_guard<std::mutex> lock(logger_map_mutex_); std::lock_guard<std::mutex> lock(logger_map_mutex_);
if (new_default_logger != nullptr) { if (new_default_logger != nullptr) {
@@ -257,14 +252,10 @@ SPDLOG_INLINE void registry::throw_if_exists_(const std::string &logger_name) {
} }
SPDLOG_INLINE void registry::register_logger_(std::shared_ptr<logger> new_logger) { SPDLOG_INLINE void registry::register_logger_(std::shared_ptr<logger> new_logger) {
auto &logger_name = new_logger->name(); auto logger_name = new_logger->name();
throw_if_exists_(logger_name); throw_if_exists_(logger_name);
loggers_[logger_name] = std::move(new_logger); loggers_[logger_name] = std::move(new_logger);
} }
SPDLOG_INLINE void registry::register_or_replace_(std::shared_ptr<logger> new_logger) {
loggers_[new_logger->name()] = std::move(new_logger);
}
} // namespace details } // namespace details
} // namespace spdlog } // namespace spdlog

View File

@@ -31,7 +31,6 @@ public:
registry &operator=(const registry &) = delete; registry &operator=(const registry &) = delete;
void register_logger(std::shared_ptr<logger> new_logger); void register_logger(std::shared_ptr<logger> new_logger);
void register_or_replace(std::shared_ptr<logger> new_logger);
void initialize_logger(std::shared_ptr<logger> new_logger); void initialize_logger(std::shared_ptr<logger> new_logger);
std::shared_ptr<logger> get(const std::string &logger_name); std::shared_ptr<logger> get(const std::string &logger_name);
std::shared_ptr<logger> default_logger(); std::shared_ptr<logger> default_logger();
@@ -106,7 +105,6 @@ private:
void throw_if_exists_(const std::string &logger_name); void throw_if_exists_(const std::string &logger_name);
void register_logger_(std::shared_ptr<logger> new_logger); void register_logger_(std::shared_ptr<logger> new_logger);
void register_or_replace_(std::shared_ptr<logger> new_logger);
bool set_level_from_cfg_(logger *logger); bool set_level_from_cfg_(logger *logger);
std::mutex logger_map_mutex_, flusher_mutex_; std::mutex logger_map_mutex_, flusher_mutex_;
std::recursive_mutex tp_mutex_; std::recursive_mutex tp_mutex_;

View File

@@ -57,82 +57,9 @@ public:
} }
SOCKET fd() const { return socket_; } SOCKET fd() const { return socket_; }
int connect_socket_with_timeout(SOCKET sockfd,
const struct sockaddr *addr,
int addrlen,
const timeval &tv) {
// If no timeout requested, do a normal blocking connect.
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
int rv = ::connect(sockfd, addr, addrlen);
if (rv == SOCKET_ERROR && WSAGetLastError() == WSAEISCONN) {
return 0;
}
return rv;
}
// Switch to nonblocking mode
u_long mode = 1UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) {
return SOCKET_ERROR;
}
int rv = ::connect(sockfd, addr, addrlen);
int last_error = WSAGetLastError();
if (rv == 0 || last_error == WSAEISCONN) {
mode = 0UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) {
return SOCKET_ERROR;
}
return 0;
}
if (last_error != WSAEWOULDBLOCK) {
// Real error
mode = 0UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode)) {
return SOCKET_ERROR;
}
return SOCKET_ERROR;
}
// Wait until socket is writable or timeout expires
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(sockfd, &wfds);
rv = ::select(0, nullptr, &wfds, nullptr, const_cast<timeval *>(&tv));
// Restore blocking mode regardless of select result
mode = 0UL;
if (::ioctlsocket(sockfd, FIONBIO, &mode) == SOCKET_ERROR) {
return SOCKET_ERROR;
}
if (rv == 0) {
WSASetLastError(WSAETIMEDOUT);
return SOCKET_ERROR;
}
if (rv == SOCKET_ERROR) {
return SOCKET_ERROR;
}
int so_error = 0;
int len = sizeof(so_error);
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, reinterpret_cast<char *>(&so_error), &len) ==
SOCKET_ERROR) {
return SOCKET_ERROR;
}
if (so_error != 0 && so_error != WSAEISCONN) {
// connection failed
WSASetLastError(so_error);
return SOCKET_ERROR;
}
return 0; // success
}
// try to connect or throw on failure // try to connect or throw on failure
void connect(const std::string &host, int port, int timeout_ms = 0) { void connect(const std::string &host, int port) {
if (is_connected()) { if (is_connected()) {
close(); close();
} }
@@ -144,10 +71,6 @@ public:
hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value
hints.ai_protocol = 0; hints.ai_protocol = 0;
timeval tv;
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
auto port_str = std::to_string(port); auto port_str = std::to_string(port);
struct addrinfo *addrinfo_result; struct addrinfo *addrinfo_result;
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);
@@ -159,6 +82,7 @@ public:
} }
// Try each address until we successfully connect(2). // Try each address until we successfully connect(2).
for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) { for (auto *rp = addrinfo_result; rp != nullptr; rp = rp->ai_next) {
socket_ = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); socket_ = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (socket_ == INVALID_SOCKET) { if (socket_ == INVALID_SOCKET) {
@@ -166,24 +90,18 @@ public:
WSACleanup(); WSACleanup();
continue; continue;
} }
if (connect_socket_with_timeout(socket_, rp->ai_addr, (int)rp->ai_addrlen, tv) == 0) { if (::connect(socket_, rp->ai_addr, (int)rp->ai_addrlen) == 0) {
last_error = 0;
break; break;
} else {
last_error = ::WSAGetLastError();
close();
} }
last_error = WSAGetLastError();
::closesocket(socket_);
socket_ = INVALID_SOCKET;
} }
::freeaddrinfo(addrinfo_result); ::freeaddrinfo(addrinfo_result);
if (socket_ == INVALID_SOCKET) { if (socket_ == INVALID_SOCKET) {
WSACleanup(); WSACleanup();
throw_winsock_error_("connect failed", last_error); throw_winsock_error_("connect failed", last_error);
} }
if (timeout_ms > 0) {
DWORD tv = static_cast<DWORD>(timeout_ms);
::setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));
::setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv));
}
// set TCP_NODELAY // set TCP_NODELAY
int enable_flag = 1; int enable_flag = 1;

View File

@@ -39,72 +39,8 @@ public:
~tcp_client() { close(); } ~tcp_client() { close(); }
int connect_socket_with_timeout(int sockfd,
const struct sockaddr *addr,
socklen_t addrlen,
const timeval &tv) {
// Blocking connect if timeout is zero
if (tv.tv_sec == 0 && tv.tv_usec == 0) {
int rv = ::connect(sockfd, addr, addrlen);
if (rv < 0 && errno == EISCONN) {
// already connected, treat as success
return 0;
}
return rv;
}
// Non-blocking path
int orig_flags = ::fcntl(sockfd, F_GETFL, 0);
if (orig_flags < 0) {
return -1;
}
if (::fcntl(sockfd, F_SETFL, orig_flags | O_NONBLOCK) < 0) {
return -1;
}
int rv = ::connect(sockfd, addr, addrlen);
if (rv == 0 || (rv < 0 && errno == EISCONN)) {
// immediate connect or already connected
::fcntl(sockfd, F_SETFL, orig_flags);
return 0;
}
if (errno != EINPROGRESS) {
::fcntl(sockfd, F_SETFL, orig_flags);
return -1;
}
// wait for writability
fd_set wfds;
FD_ZERO(&wfds);
FD_SET(sockfd, &wfds);
struct timeval tv_copy = tv;
rv = ::select(sockfd + 1, nullptr, &wfds, nullptr, &tv_copy);
if (rv <= 0) {
// timeout or error
::fcntl(sockfd, F_SETFL, orig_flags);
if (rv == 0) errno = ETIMEDOUT;
return -1;
}
// check socket error
int so_error = 0;
socklen_t len = sizeof(so_error);
if (::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len) < 0) {
::fcntl(sockfd, F_SETFL, orig_flags);
return -1;
}
::fcntl(sockfd, F_SETFL, orig_flags);
if (so_error != 0 && so_error != EISCONN) {
errno = so_error;
return -1;
}
return 0;
}
// try to connect or throw on failure // try to connect or throw on failure
void connect(const std::string &host, int port, int timeout_ms = 0) { void connect(const std::string &host, int port) {
close(); close();
struct addrinfo hints {}; struct addrinfo hints {};
memset(&hints, 0, sizeof(struct addrinfo)); memset(&hints, 0, sizeof(struct addrinfo));
@@ -113,10 +49,6 @@ public:
hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value hints.ai_flags = AI_NUMERICSERV; // port passed as as numeric value
hints.ai_protocol = 0; hints.ai_protocol = 0;
struct timeval tv;
tv.tv_sec = timeout_ms / 1000;
tv.tv_usec = (timeout_ms % 1000) * 1000;
auto port_str = std::to_string(port); auto port_str = std::to_string(port);
struct addrinfo *addrinfo_result; struct addrinfo *addrinfo_result;
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);
@@ -137,9 +69,8 @@ public:
last_errno = errno; last_errno = errno;
continue; continue;
} }
::fcntl(socket_, F_SETFD, FD_CLOEXEC); rv = ::connect(socket_, rp->ai_addr, rp->ai_addrlen);
if (connect_socket_with_timeout(socket_, rp->ai_addr, rp->ai_addrlen, tv) == 0) { if (rv == 0) {
last_errno = 0;
break; break;
} }
last_errno = errno; last_errno = errno;
@@ -151,12 +82,6 @@ public:
throw_spdlog_ex("::connect failed", last_errno); throw_spdlog_ex("::connect failed", last_errno);
} }
if (timeout_ms > 0) {
// Set timeouts for send and recv
::setsockopt(socket_, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));
::setsockopt(socket_, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv));
}
// set TCP_NODELAY // set TCP_NODELAY
int enable_flag = 1; int enable_flag = 1;
::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&enable_flag), ::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<char *>(&enable_flag),

View File

@@ -35,10 +35,11 @@ SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items,
SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items, SPDLOG_INLINE thread_pool::thread_pool(size_t q_max_items,
size_t threads_n, size_t threads_n,
std::function<void()> on_thread_start) std::function<void()> on_thread_start)
: thread_pool(q_max_items, threads_n, std::move(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
SPDLOG_INLINE thread_pool::~thread_pool() { SPDLOG_INLINE thread_pool::~thread_pool() {
@@ -94,7 +95,8 @@ void SPDLOG_INLINE thread_pool::worker_loop_() {
} }
// process next message in the queue // process next message in the queue
// returns true if this thread should still be active (while no terminated msg was received) // return true if this thread should still be active (while no terminate msg
// was received)
bool SPDLOG_INLINE thread_pool::process_next_msg_() { bool SPDLOG_INLINE thread_pool::process_next_msg_() {
async_msg incoming_async_msg; async_msg incoming_async_msg;
q_.dequeue(incoming_async_msg); q_.dequeue(incoming_async_msg);

View File

@@ -142,8 +142,8 @@ struct formatter<spdlog::details::dump_info<T>, char> {
// format the given bytes range as hex // format the given bytes range as hex
template <typename FormatContext, typename Container> template <typename FormatContext, typename Container>
auto format(const spdlog::details::dump_info<Container> &the_range, auto format(const spdlog::details::dump_info<Container> &the_range, FormatContext &ctx) const
FormatContext &ctx) const -> decltype(ctx.out()) { -> decltype(ctx.out()) {
SPDLOG_CONSTEXPR const char *hex_upper = "0123456789ABCDEF"; SPDLOG_CONSTEXPR const char *hex_upper = "0123456789ABCDEF";
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;

View File

@@ -21,7 +21,7 @@
#endif #endif
// The fmt library version in the form major * 10000 + minor * 100 + patch. // The fmt library version in the form major * 10000 + minor * 100 + patch.
#define FMT_VERSION 110200 #define FMT_VERSION 110104
// Detect compiler versions. // Detect compiler versions.
#if defined(__clang__) && !defined(__ibmxl__) #if defined(__clang__) && !defined(__ibmxl__)
@@ -209,6 +209,20 @@
# define FMT_DEPRECATED /* deprecated */ # define FMT_DEPRECATED /* deprecated */
#endif #endif
#ifdef FMT_ALWAYS_INLINE
// Use the provided definition.
#elif FMT_GCC_VERSION || FMT_CLANG_VERSION
# define FMT_ALWAYS_INLINE inline __attribute__((always_inline))
#else
# define FMT_ALWAYS_INLINE inline
#endif
// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode.
#ifdef NDEBUG
# define FMT_INLINE FMT_ALWAYS_INLINE
#else
# define FMT_INLINE inline
#endif
#if FMT_GCC_VERSION || FMT_CLANG_VERSION #if FMT_GCC_VERSION || FMT_CLANG_VERSION
# define FMT_VISIBILITY(value) __attribute__((visibility(value))) # define FMT_VISIBILITY(value) __attribute__((visibility(value)))
#else #else
@@ -235,28 +249,6 @@
# define FMT_MSC_WARNING(...) # define FMT_MSC_WARNING(...)
#endif #endif
// Enable minimal optimizations for more compact code in debug mode.
FMT_PRAGMA_GCC(push_options)
#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE)
FMT_PRAGMA_GCC(optimize("Og"))
# define FMT_GCC_OPTIMIZED
#endif
FMT_PRAGMA_CLANG(diagnostic push)
#ifdef FMT_ALWAYS_INLINE
// Use the provided definition.
#elif FMT_GCC_VERSION || FMT_CLANG_VERSION
# define FMT_ALWAYS_INLINE inline __attribute__((always_inline))
#else
# define FMT_ALWAYS_INLINE inline
#endif
// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode.
#if defined(NDEBUG) || defined(FMT_GCC_OPTIMIZED)
# define FMT_INLINE FMT_ALWAYS_INLINE
#else
# define FMT_INLINE inline
#endif
#ifndef FMT_BEGIN_NAMESPACE #ifndef FMT_BEGIN_NAMESPACE
# define FMT_BEGIN_NAMESPACE \ # define FMT_BEGIN_NAMESPACE \
namespace fmt { \ namespace fmt { \
@@ -305,6 +297,13 @@ FMT_PRAGMA_CLANG(diagnostic push)
using unused = int[]; \ using unused = int[]; \
(void)unused { 0, (expr, 0)... } (void)unused { 0, (expr, 0)... }
// Enable minimal optimizations for more compact code in debug mode.
FMT_PRAGMA_GCC(push_options)
#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE)
FMT_PRAGMA_GCC(optimize("Og"))
#endif
FMT_PRAGMA_CLANG(diagnostic push)
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
// Implementations of enable_if_t and other metafunctions for older systems. // Implementations of enable_if_t and other metafunctions for older systems.
@@ -326,8 +325,8 @@ using underlying_t = typename std::underlying_type<T>::type;
template <typename T> using decay_t = typename std::decay<T>::type; template <typename T> using decay_t = typename std::decay<T>::type;
using nullptr_t = decltype(nullptr); using nullptr_t = decltype(nullptr);
#if (FMT_GCC_VERSION && FMT_GCC_VERSION < 500) || FMT_MSC_VERSION #if FMT_GCC_VERSION && FMT_GCC_VERSION < 500
// A workaround for gcc 4.9 & MSVC v141 to make void_t work in a SFINAE context. // A workaround for gcc 4.9 to make void_t work in a SFINAE context.
template <typename...> struct void_t_impl { template <typename...> struct void_t_impl {
using type = void; using type = void;
}; };
@@ -527,20 +526,20 @@ template <typename Char> class basic_string_view {
constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {} constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {}
/// Constructs a string view object from a C string and a size. /// Constructs a string reference object from a C string and a size.
constexpr basic_string_view(const Char* s, size_t count) noexcept constexpr basic_string_view(const Char* s, size_t count) noexcept
: data_(s), size_(count) {} : data_(s), size_(count) {}
constexpr basic_string_view(nullptr_t) = delete; constexpr basic_string_view(nullptr_t) = delete;
/// Constructs a string view object from a C string. /// Constructs a string reference object from a C string.
#if FMT_GCC_VERSION #if FMT_GCC_VERSION
FMT_ALWAYS_INLINE FMT_ALWAYS_INLINE
#endif #endif
FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) { FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) {
#if FMT_HAS_BUILTIN(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION #if FMT_HAS_BUILTIN(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION
if (std::is_same<Char, char>::value && !detail::is_constant_evaluated()) { if (std::is_same<Char, char>::value) {
size_ = __builtin_strlen(detail::narrow(s)); // strlen is not costexpr. size_ = __builtin_strlen(detail::narrow(s));
return; return;
} }
#endif #endif
@@ -549,7 +548,7 @@ template <typename Char> class basic_string_view {
size_ = len; size_ = len;
} }
/// Constructs a string view from a `std::basic_string` or a /// Constructs a string reference from a `std::basic_string` or a
/// `std::basic_string_view` object. /// `std::basic_string_view` object.
template <typename S, template <typename S,
FMT_ENABLE_IF(detail::is_std_string_like<S>::value&& std::is_same< FMT_ENABLE_IF(detail::is_std_string_like<S>::value&& std::is_same<
@@ -586,6 +585,7 @@ template <typename Char> class basic_string_view {
return starts_with(basic_string_view<Char>(s)); return starts_with(basic_string_view<Char>(s));
} }
// Lexicographically compare this string reference to other.
FMT_CONSTEXPR auto compare(basic_string_view other) const -> int { FMT_CONSTEXPR auto compare(basic_string_view other) const -> int {
int result = int result =
detail::compare(data_, other.data_, min_of(size_, other.size_)); detail::compare(data_, other.data_, min_of(size_, other.size_));
@@ -616,7 +616,7 @@ template <typename Char> class basic_string_view {
using string_view = basic_string_view<char>; using string_view = basic_string_view<char>;
// DEPRECATED! Will be merged with is_char and moved to detail. /// Specifies if `T` is an extended character type. Can be specialized by users.
template <typename T> struct is_xchar : std::false_type {}; template <typename T> struct is_xchar : std::false_type {};
template <> struct is_xchar<wchar_t> : std::true_type {}; template <> struct is_xchar<wchar_t> : std::true_type {};
template <> struct is_xchar<char16_t> : std::true_type {}; template <> struct is_xchar<char16_t> : std::true_type {};
@@ -625,7 +625,7 @@ template <> struct is_xchar<char32_t> : std::true_type {};
template <> struct is_xchar<char8_t> : std::true_type {}; template <> struct is_xchar<char8_t> : std::true_type {};
#endif #endif
// Specifies if `T` is a character (code unit) type. // DEPRECATED! Will be replaced with an alias to prevent specializations.
template <typename T> struct is_char : is_xchar<T> {}; template <typename T> struct is_char : is_xchar<T> {};
template <> struct is_char<char> : std::true_type {}; template <> struct is_char<char> : std::true_type {};
@@ -1032,11 +1032,6 @@ enum {
struct view {}; struct view {};
template <typename T, typename Enable = std::true_type>
struct is_view : std::false_type {};
template <typename T>
struct is_view<T, bool_constant<sizeof(T) != 0>> : std::is_base_of<view, T> {};
template <typename Char, typename T> struct named_arg; template <typename Char, typename T> struct named_arg;
template <typename T> struct is_named_arg : std::false_type {}; template <typename T> struct is_named_arg : std::false_type {};
template <typename T> struct is_static_named_arg : std::false_type {}; template <typename T> struct is_static_named_arg : std::false_type {};
@@ -1069,16 +1064,6 @@ template <typename Char> struct named_arg_info {
int id; int id;
}; };
// named_args is non-const to suppress a bogus -Wmaybe-uninitalized in gcc 13.
template <typename Char>
FMT_CONSTEXPR void check_for_duplicate(named_arg_info<Char>* named_args,
int named_arg_index,
basic_string_view<Char> arg_name) {
for (int i = 0; i < named_arg_index; ++i) {
if (named_args[i].name == arg_name) report_error("duplicate named arg");
}
}
template <typename Char, typename T, FMT_ENABLE_IF(!is_named_arg<T>::value)> template <typename Char, typename T, FMT_ENABLE_IF(!is_named_arg<T>::value)>
void init_named_arg(named_arg_info<Char>*, int& arg_index, int&, const T&) { void init_named_arg(named_arg_info<Char>*, int& arg_index, int&, const T&) {
++arg_index; ++arg_index;
@@ -1086,7 +1071,6 @@ void init_named_arg(named_arg_info<Char>*, int& arg_index, int&, const T&) {
template <typename Char, typename T, FMT_ENABLE_IF(is_named_arg<T>::value)> template <typename Char, typename T, FMT_ENABLE_IF(is_named_arg<T>::value)>
void init_named_arg(named_arg_info<Char>* named_args, int& arg_index, void init_named_arg(named_arg_info<Char>* named_args, int& arg_index,
int& named_arg_index, const T& arg) { int& named_arg_index, const T& arg) {
check_for_duplicate<Char>(named_args, named_arg_index, arg.name);
named_args[named_arg_index++] = {arg.name, arg_index++}; named_args[named_arg_index++] = {arg.name, arg_index++};
} }
@@ -1100,13 +1084,12 @@ template <typename T, typename Char,
FMT_ENABLE_IF(is_static_named_arg<T>::value)> FMT_ENABLE_IF(is_static_named_arg<T>::value)>
FMT_CONSTEXPR void init_static_named_arg(named_arg_info<Char>* named_args, FMT_CONSTEXPR void init_static_named_arg(named_arg_info<Char>* named_args,
int& arg_index, int& named_arg_index) { int& arg_index, int& named_arg_index) {
check_for_duplicate<Char>(named_args, named_arg_index, T::name);
named_args[named_arg_index++] = {T::name, arg_index++}; named_args[named_arg_index++] = {T::name, arg_index++};
} }
// To minimize the number of types we need to deal with, long is translated // To minimize the number of types we need to deal with, long is translated
// either to int or to long long depending on its size. // either to int or to long long depending on its size.
enum { long_short = sizeof(long) == sizeof(int) && FMT_BUILTIN_TYPES }; enum { long_short = sizeof(long) == sizeof(int) };
using long_type = conditional_t<long_short, int, long long>; using long_type = conditional_t<long_short, int, long long>;
using ulong_type = conditional_t<long_short, unsigned, unsigned long long>; using ulong_type = conditional_t<long_short, unsigned, unsigned long long>;
@@ -1723,17 +1706,7 @@ class format_string_checker {
-> const Char* { -> const Char* {
context_.advance_to(begin); context_.advance_to(begin);
if (id >= 0 && id < NUM_ARGS) return parse_funcs_[id](context_); if (id >= 0 && id < NUM_ARGS) return parse_funcs_[id](context_);
while (begin != end && *begin != '}') ++begin;
// If id is out of range, it means we do not know the type and cannot parse
// the format at compile time. Instead, skip over content until we finish
// the format spec, accounting for any nested replacements.
for (int bracket_count = 0;
begin != end && (bracket_count > 0 || *begin != '}'); ++begin) {
if (*begin == '{')
++bracket_count;
else if (*begin == '}')
--bracket_count;
}
return begin; return begin;
} }
@@ -2730,7 +2703,7 @@ template <typename... T> struct fstring {
template <size_t N> template <size_t N>
FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) { FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) {
using namespace detail; using namespace detail;
static_assert(count<(is_view<remove_cvref_t<T>>::value && static_assert(count<(std::is_base_of<view, remove_reference_t<T>>::value &&
std::is_reference<T>::value)...>() == 0, std::is_reference<T>::value)...>() == 0,
"passing views as lvalues is disallowed"); "passing views as lvalues is disallowed");
if (FMT_USE_CONSTEVAL) parse_format_string<char>(s, checker(s, arg_pack())); if (FMT_USE_CONSTEVAL) parse_format_string<char>(s, checker(s, arg_pack()));

View File

@@ -22,6 +22,21 @@
#include "format.h" #include "format.h"
namespace fmt_detail {
struct time_zone {
template <typename Duration, typename T>
auto to_sys(T)
-> std::chrono::time_point<std::chrono::system_clock, Duration> {
return {};
}
};
template <typename... T> inline auto current_zone(T...) -> time_zone* {
return nullptr;
}
template <typename... T> inline void _tzset(T...) {}
} // namespace fmt_detail
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
// Enable safe chrono durations, unless explicitly disabled. // Enable safe chrono durations, unless explicitly disabled.
@@ -420,11 +435,14 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc,
return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc);
} }
template <typename T, typename U> template <typename Rep1, typename Rep2>
using is_similar_arithmetic_type = struct is_same_arithmetic_type
bool_constant<(std::is_integral<T>::value && std::is_integral<U>::value) || : public std::integral_constant<bool,
(std::is_floating_point<T>::value && (std::is_integral<Rep1>::value &&
std::is_floating_point<U>::value)>; std::is_integral<Rep2>::value) ||
(std::is_floating_point<Rep1>::value &&
std::is_floating_point<Rep2>::value)> {
};
FMT_NORETURN inline void throw_duration_error() { FMT_NORETURN inline void throw_duration_error() {
FMT_THROW(format_error("cannot format duration")); FMT_THROW(format_error("cannot format duration"));
@@ -483,9 +501,9 @@ auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
#endif #endif
} }
template <typename To, typename FromRep, typename FromPeriod, template <
FMT_ENABLE_IF( typename To, typename FromRep, typename FromPeriod,
!is_similar_arithmetic_type<FromRep, typename To::rep>::value)> FMT_ENABLE_IF(!is_same_arithmetic_type<FromRep, typename To::rep>::value)>
auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To { auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
// Mixed integer <-> float cast is not supported by safe_duration_cast. // Mixed integer <-> float cast is not supported by safe_duration_cast.
return std::chrono::duration_cast<To>(from); return std::chrono::duration_cast<To>(from);
@@ -501,30 +519,12 @@ auto to_time_t(sys_time<Duration> time_point) -> std::time_t {
.count(); .count();
} }
namespace tz { // Workaround a bug in libstdc++ which sets __cpp_lib_chrono to 201907 without
// providing current_zone(): https://github.com/fmtlib/fmt/issues/4160.
// DEPRECATED! template <typename T> FMT_CONSTEXPR auto has_current_zone() -> bool {
struct time_zone { using namespace std::chrono;
template <typename Duration, typename LocalTime> using namespace fmt_detail;
auto to_sys(LocalTime) -> sys_time<Duration> { return !std::is_same<decltype(current_zone()), fmt_detail::time_zone*>::value;
return {};
}
};
template <typename... T> auto current_zone(T...) -> time_zone* {
return nullptr;
}
template <typename... T> void _tzset(T...) {}
} // namespace tz
// DEPRECATED!
inline void tzset_once() {
static bool init = []() {
using namespace tz;
_tzset();
return false;
}();
ignore_unused(init);
} }
} // namespace detail } // namespace detail
@@ -535,7 +535,7 @@ FMT_BEGIN_EXPORT
* expressed in local time. Unlike `std::localtime`, this function is * expressed in local time. Unlike `std::localtime`, this function is
* thread-safe on most platforms. * thread-safe on most platforms.
*/ */
FMT_DEPRECATED inline auto localtime(std::time_t time) -> std::tm { inline auto localtime(std::time_t time) -> std::tm {
struct dispatcher { struct dispatcher {
std::time_t time_; std::time_t time_;
std::tm tm_; std::tm tm_;
@@ -572,11 +572,11 @@ FMT_DEPRECATED inline auto localtime(std::time_t time) -> std::tm {
} }
#if FMT_USE_LOCAL_TIME #if FMT_USE_LOCAL_TIME
template <typename Duration> template <typename Duration,
FMT_DEPRECATED auto localtime(std::chrono::local_time<Duration> time) FMT_ENABLE_IF(detail::has_current_zone<Duration>())>
-> std::tm { inline auto localtime(std::chrono::local_time<Duration> time) -> std::tm {
using namespace std::chrono; using namespace std::chrono;
using namespace detail::tz; using namespace fmt_detail;
return localtime(detail::to_time_t(current_zone()->to_sys<Duration>(time))); return localtime(detail::to_time_t(current_zone()->to_sys<Duration>(time)));
} }
#endif #endif
@@ -911,14 +911,7 @@ template <typename Derived> struct null_chrono_spec_handler {
FMT_CONSTEXPR void on_tz_name() { unsupported(); } FMT_CONSTEXPR void on_tz_name() { unsupported(); }
}; };
class tm_format_checker : public null_chrono_spec_handler<tm_format_checker> { struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> {
private:
bool has_timezone_ = false;
public:
constexpr explicit tm_format_checker(bool has_timezone)
: has_timezone_(has_timezone) {}
FMT_NORETURN inline void unsupported() { FMT_NORETURN inline void unsupported() {
FMT_THROW(format_error("no format")); FMT_THROW(format_error("no format"));
} }
@@ -956,12 +949,8 @@ class tm_format_checker : public null_chrono_spec_handler<tm_format_checker> {
FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_24_hour_time() {}
FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_iso_time() {}
FMT_CONSTEXPR void on_am_pm() {} FMT_CONSTEXPR void on_am_pm() {}
FMT_CONSTEXPR void on_utc_offset(numeric_system) { FMT_CONSTEXPR void on_utc_offset(numeric_system) {}
if (!has_timezone_) FMT_THROW(format_error("no timezone")); FMT_CONSTEXPR void on_tz_name() {}
}
FMT_CONSTEXPR void on_tz_name() {
if (!has_timezone_) FMT_THROW(format_error("no timezone"));
}
}; };
inline auto tm_wday_full_name(int wday) -> const char* { inline auto tm_wday_full_name(int wday) -> const char* {
@@ -991,27 +980,24 @@ inline auto tm_mon_short_name(int mon) -> const char* {
} }
template <typename T, typename = void> template <typename T, typename = void>
struct has_tm_gmtoff : std::false_type {}; struct has_member_data_tm_gmtoff : std::false_type {};
template <typename T> template <typename T>
struct has_tm_gmtoff<T, void_t<decltype(T::tm_gmtoff)>> : std::true_type {}; struct has_member_data_tm_gmtoff<T, void_t<decltype(T::tm_gmtoff)>>
: std::true_type {};
template <typename T, typename = void> struct has_tm_zone : std::false_type {}; template <typename T, typename = void>
struct has_member_data_tm_zone : std::false_type {};
template <typename T> template <typename T>
struct has_tm_zone<T, void_t<decltype(T::tm_zone)>> : std::true_type {}; struct has_member_data_tm_zone<T, void_t<decltype(T::tm_zone)>>
: std::true_type {};
template <typename T, FMT_ENABLE_IF(has_tm_zone<T>::value)> inline void tzset_once() {
bool set_tm_zone(T& time, char* tz) { static bool init = []() {
time.tm_zone = tz; using namespace fmt_detail;
return true; _tzset();
} return false;
template <typename T, FMT_ENABLE_IF(!has_tm_zone<T>::value)> }();
bool set_tm_zone(T&, char*) { ignore_unused(init);
return false;
}
inline char* utc() {
static char tz[] = "UTC";
return tz;
} }
// Converts value to Int and checks that it's in the range [0, upper). // Converts value to Int and checks that it's in the range [0, upper).
@@ -1019,7 +1005,7 @@ template <typename T, typename Int, FMT_ENABLE_IF(std::is_integral<T>::value)>
inline auto to_nonnegative_int(T value, Int upper) -> Int { inline auto to_nonnegative_int(T value, Int upper) -> Int {
if (!std::is_unsigned<Int>::value && if (!std::is_unsigned<Int>::value &&
(value < 0 || to_unsigned(value) > to_unsigned(upper))) { (value < 0 || to_unsigned(value) > to_unsigned(upper))) {
FMT_THROW(format_error("chrono value is out of range")); FMT_THROW(fmt::format_error("chrono value is out of range"));
} }
return static_cast<Int>(value); return static_cast<Int>(value);
} }
@@ -1104,7 +1090,7 @@ void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) {
// Format subseconds which are given as a floating point type with an // Format subseconds which are given as a floating point type with an
// appropriate number of digits. We cannot pass the Duration here, as we // appropriate number of digits. We cannot pass the Duration here, as we
// explicitly need to pass the Rep value in the duration_formatter. // explicitly need to pass the Rep value in the chrono_formatter.
template <typename Duration> template <typename Duration>
void write_floating_seconds(memory_buffer& buf, Duration duration, void write_floating_seconds(memory_buffer& buf, Duration duration,
int num_fractional_digits = -1) { int num_fractional_digits = -1) {
@@ -1138,7 +1124,7 @@ class tm_writer {
static constexpr int days_per_week = 7; static constexpr int days_per_week = 7;
const std::locale& loc_; const std::locale& loc_;
bool is_classic_; const bool is_classic_;
OutputIt out_; OutputIt out_;
const Duration* subsecs_; const Duration* subsecs_;
const std::tm& tm_; const std::tm& tm_;
@@ -1174,8 +1160,8 @@ class tm_writer {
} }
auto tm_hour12() const noexcept -> int { auto tm_hour12() const noexcept -> int {
auto h = tm_hour(); const auto h = tm_hour();
auto z = h < 12 ? h : h - 12; const auto z = h < 12 ? h : h - 12;
return z == 0 ? 12 : z; return z == 0 ? 12 : z;
} }
@@ -1191,11 +1177,11 @@ class tm_writer {
// Algorithm: https://en.wikipedia.org/wiki/ISO_week_date. // Algorithm: https://en.wikipedia.org/wiki/ISO_week_date.
auto iso_year_weeks(long long curr_year) const noexcept -> int { auto iso_year_weeks(long long curr_year) const noexcept -> int {
auto prev_year = curr_year - 1; const auto prev_year = curr_year - 1;
auto curr_p = const auto curr_p =
(curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) % (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) %
days_per_week; days_per_week;
auto prev_p = const auto prev_p =
(prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) % (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) %
days_per_week; days_per_week;
return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0); return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0);
@@ -1205,15 +1191,15 @@ class tm_writer {
days_per_week; days_per_week;
} }
auto tm_iso_week_year() const noexcept -> long long { auto tm_iso_week_year() const noexcept -> long long {
auto year = tm_year(); const auto year = tm_year();
auto w = iso_week_num(tm_yday(), tm_wday()); const auto w = iso_week_num(tm_yday(), tm_wday());
if (w < 1) return year - 1; if (w < 1) return year - 1;
if (w > iso_year_weeks(year)) return year + 1; if (w > iso_year_weeks(year)) return year + 1;
return year; return year;
} }
auto tm_iso_week_of_year() const noexcept -> int { auto tm_iso_week_of_year() const noexcept -> int {
auto year = tm_year(); const auto year = tm_year();
auto w = iso_week_num(tm_yday(), tm_wday()); const auto w = iso_week_num(tm_yday(), tm_wday());
if (w < 1) return iso_year_weeks(year - 1); if (w < 1) return iso_year_weeks(year - 1);
if (w > iso_year_weeks(year)) return 1; if (w > iso_year_weeks(year)) return 1;
return w; return w;
@@ -1250,8 +1236,9 @@ class tm_writer {
uint32_or_64_or_128_t<long long> n = to_unsigned(year); uint32_or_64_or_128_t<long long> n = to_unsigned(year);
const int num_digits = count_digits(n); const int num_digits = count_digits(n);
if (negative && pad == pad_type::zero) *out_++ = '-'; if (negative && pad == pad_type::zero) *out_++ = '-';
if (width > num_digits) if (width > num_digits) {
out_ = detail::write_padding(out_, pad, width - num_digits); out_ = detail::write_padding(out_, pad, width - num_digits);
}
if (negative && pad != pad_type::zero) *out_++ = '-'; if (negative && pad != pad_type::zero) *out_++ = '-';
out_ = format_decimal<Char>(out_, n, num_digits); out_ = format_decimal<Char>(out_, n, num_digits);
} }
@@ -1272,22 +1259,45 @@ class tm_writer {
write2(static_cast<int>(offset % 60)); write2(static_cast<int>(offset % 60));
} }
template <typename T, FMT_ENABLE_IF(has_tm_gmtoff<T>::value)> template <typename T, FMT_ENABLE_IF(has_member_data_tm_gmtoff<T>::value)>
void format_utc_offset(const T& tm, numeric_system ns) { void format_utc_offset_impl(const T& tm, numeric_system ns) {
write_utc_offset(tm.tm_gmtoff, ns); write_utc_offset(tm.tm_gmtoff, ns);
} }
template <typename T, FMT_ENABLE_IF(!has_tm_gmtoff<T>::value)> template <typename T, FMT_ENABLE_IF(!has_member_data_tm_gmtoff<T>::value)>
void format_utc_offset(const T&, numeric_system ns) { void format_utc_offset_impl(const T& tm, numeric_system ns) {
write_utc_offset(0, ns); #if defined(_WIN32) && defined(_UCRT)
tzset_once();
long offset = 0;
_get_timezone(&offset);
if (tm.tm_isdst) {
long dstbias = 0;
_get_dstbias(&dstbias);
offset += dstbias;
}
write_utc_offset(-offset, ns);
#else
if (ns == numeric_system::standard) return format_localized('z');
// Extract timezone offset from timezone conversion functions.
std::tm gtm = tm;
std::time_t gt = std::mktime(&gtm);
std::tm ltm = gmtime(gt);
std::time_t lt = std::mktime(&ltm);
long long offset = gt - lt;
write_utc_offset(offset, ns);
#endif
} }
template <typename T, FMT_ENABLE_IF(has_tm_zone<T>::value)> template <typename T, FMT_ENABLE_IF(has_member_data_tm_zone<T>::value)>
void format_tz_name(const T& tm) { void format_tz_name_impl(const T& tm) {
out_ = write_tm_str<Char>(out_, tm.tm_zone, loc_); if (is_classic_)
out_ = write_tm_str<Char>(out_, tm.tm_zone, loc_);
else
format_localized('Z');
} }
template <typename T, FMT_ENABLE_IF(!has_tm_zone<T>::value)> template <typename T, FMT_ENABLE_IF(!has_member_data_tm_zone<T>::value)>
void format_tz_name(const T&) { void format_tz_name_impl(const T&) {
out_ = std::copy_n(utc(), 3, out_); format_localized('Z');
} }
void format_localized(char format, char modifier = 0) { void format_localized(char format, char modifier = 0) {
@@ -1398,8 +1408,8 @@ class tm_writer {
out_ = copy<Char>(std::begin(buf) + offset, std::end(buf), out_); out_ = copy<Char>(std::begin(buf) + offset, std::end(buf), out_);
} }
void on_utc_offset(numeric_system ns) { format_utc_offset(tm_, ns); } void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); }
void on_tz_name() { format_tz_name(tm_); } void on_tz_name() { format_tz_name_impl(tm_); }
void on_year(numeric_system ns, pad_type pad) { void on_year(numeric_system ns, pad_type pad) {
if (is_classic_ || ns == numeric_system::standard) if (is_classic_ || ns == numeric_system::standard)
@@ -1473,10 +1483,11 @@ class tm_writer {
void on_day_of_year(pad_type pad) { void on_day_of_year(pad_type pad) {
auto yday = tm_yday() + 1; auto yday = tm_yday() + 1;
auto digit1 = yday / 100; auto digit1 = yday / 100;
if (digit1 != 0) if (digit1 != 0) {
write1(digit1); write1(digit1);
else } else {
out_ = detail::write_padding(out_, pad); out_ = detail::write_padding(out_, pad);
}
write2(yday % 100, pad); write2(yday % 100, pad);
} }
@@ -1613,16 +1624,18 @@ template <typename Rep, typename Period,
FMT_ENABLE_IF(std::is_integral<Rep>::value)> FMT_ENABLE_IF(std::is_integral<Rep>::value)>
inline auto get_milliseconds(std::chrono::duration<Rep, Period> d) inline auto get_milliseconds(std::chrono::duration<Rep, Period> d)
-> std::chrono::duration<Rep, std::milli> { -> std::chrono::duration<Rep, std::milli> {
// This may overflow and/or the result may not fit in the target type. // this may overflow and/or the result may not fit in the
// target type.
#if FMT_SAFE_DURATION_CAST #if FMT_SAFE_DURATION_CAST
using common_seconds_type = using CommonSecondsType =
typename std::common_type<decltype(d), std::chrono::seconds>::type; typename std::common_type<decltype(d), std::chrono::seconds>::type;
auto d_as_common = detail::duration_cast<common_seconds_type>(d); const auto d_as_common = detail::duration_cast<CommonSecondsType>(d);
auto d_as_whole_seconds = const auto d_as_whole_seconds =
detail::duration_cast<std::chrono::seconds>(d_as_common); detail::duration_cast<std::chrono::seconds>(d_as_common);
// This conversion should be nonproblematic. // this conversion should be nonproblematic
auto diff = d_as_common - d_as_whole_seconds; const auto diff = d_as_common - d_as_whole_seconds;
auto ms = detail::duration_cast<std::chrono::duration<Rep, std::milli>>(diff); const auto ms =
detail::duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
return ms; return ms;
#else #else
auto s = detail::duration_cast<std::chrono::seconds>(d); auto s = detail::duration_cast<std::chrono::seconds>(d);
@@ -1694,28 +1707,32 @@ class get_locale {
} }
}; };
template <typename Char, typename Rep, typename Period> template <typename FormatContext, typename OutputIt, typename Rep,
struct duration_formatter { typename Period>
using iterator = basic_appender<Char>; struct chrono_formatter {
iterator out; FormatContext& context;
OutputIt out;
int precision;
bool localized = false;
// rep is unsigned to avoid overflow. // rep is unsigned to avoid overflow.
using rep = using rep =
conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int), conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int),
unsigned, typename make_unsigned_or_unchanged<Rep>::type>; unsigned, typename make_unsigned_or_unchanged<Rep>::type>;
rep val; rep val;
int precision;
locale_ref locale;
bool localized = false;
using seconds = std::chrono::duration<rep>; using seconds = std::chrono::duration<rep>;
seconds s; seconds s;
using milliseconds = std::chrono::duration<rep, std::milli>; using milliseconds = std::chrono::duration<rep, std::milli>;
bool negative; bool negative;
using tm_writer_type = tm_writer<iterator, Char>; using char_type = typename FormatContext::char_type;
using tm_writer_type = tm_writer<OutputIt, char_type>;
duration_formatter(iterator o, std::chrono::duration<Rep, Period> d, chrono_formatter(FormatContext& ctx, OutputIt o,
locale_ref loc) std::chrono::duration<Rep, Period> d)
: out(o), val(static_cast<rep>(d.count())), locale(loc), negative(false) { : context(ctx),
out(o),
val(static_cast<rep>(d.count())),
negative(false) {
if (d.count() < 0) { if (d.count() < 0) {
val = 0 - val; val = 0 - val;
negative = true; negative = true;
@@ -1729,16 +1746,19 @@ struct duration_formatter {
// returns true if nan or inf, writes to out. // returns true if nan or inf, writes to out.
auto handle_nan_inf() -> bool { auto handle_nan_inf() -> bool {
if (isfinite(val)) return false; if (isfinite(val)) {
return false;
}
if (isnan(val)) { if (isnan(val)) {
write_nan(); write_nan();
return true; return true;
} }
// must be +-inf // must be +-inf
if (val > 0) if (val > 0) {
std::copy_n("inf", 3, out); write_pinf();
else } else {
std::copy_n("-inf", 4, out); write_ninf();
}
return true; return true;
} }
@@ -1766,9 +1786,10 @@ struct duration_formatter {
} }
void write_sign() { void write_sign() {
if (!negative) return; if (negative) {
*out++ = '-'; *out++ = '-';
negative = false; negative = false;
}
} }
void write(Rep value, int width, pad_type pad = pad_type::zero) { void write(Rep value, int width, pad_type pad = pad_type::zero) {
@@ -1780,22 +1801,24 @@ struct duration_formatter {
if (width > num_digits) { if (width > num_digits) {
out = detail::write_padding(out, pad, width - num_digits); out = detail::write_padding(out, pad, width - num_digits);
} }
out = format_decimal<Char>(out, n, num_digits); out = format_decimal<char_type>(out, n, num_digits);
} }
void write_nan() { std::copy_n("nan", 3, out); } void write_nan() { std::copy_n("nan", 3, out); }
void write_pinf() { std::copy_n("inf", 3, out); }
void write_ninf() { std::copy_n("-inf", 4, out); }
template <typename Callback, typename... Args> template <typename Callback, typename... Args>
void format_tm(const tm& time, Callback cb, Args... args) { void format_tm(const tm& time, Callback cb, Args... args) {
if (isnan(val)) return write_nan(); if (isnan(val)) return write_nan();
get_locale loc(localized, locale); get_locale loc(localized, context.locale());
auto w = tm_writer_type(loc, out, time); auto w = tm_writer_type(loc, out, time);
(w.*cb)(args...); (w.*cb)(args...);
out = w.out(); out = w.out();
} }
void on_text(const Char* begin, const Char* end) { void on_text(const char_type* begin, const char_type* end) {
copy<Char>(begin, end, out); copy<char_type>(begin, end, out);
} }
// These are not implemented because durations don't have date information. // These are not implemented because durations don't have date information.
@@ -1865,12 +1888,13 @@ struct duration_formatter {
write_floating_seconds(buf, std::chrono::duration<rep, Period>(val), write_floating_seconds(buf, std::chrono::duration<rep, Period>(val),
precision); precision);
if (negative) *out++ = '-'; if (negative) *out++ = '-';
if (buf.size() < 2 || buf[1] == '.') if (buf.size() < 2 || buf[1] == '.') {
out = detail::write_padding(out, pad); out = detail::write_padding(out, pad);
out = copy<Char>(buf.begin(), buf.end(), out); }
out = copy<char_type>(buf.begin(), buf.end(), out);
} else { } else {
write(second(), 2, pad); write(second(), 2, pad);
write_fractional_seconds<Char>( write_fractional_seconds<char_type>(
out, std::chrono::duration<rep, Period>(val), precision); out, std::chrono::duration<rep, Period>(val), precision);
} }
return; return;
@@ -1912,10 +1936,12 @@ struct duration_formatter {
void on_duration_value() { void on_duration_value() {
if (handle_nan_inf()) return; if (handle_nan_inf()) return;
write_sign(); write_sign();
out = format_duration_value<Char>(out, val, precision); out = format_duration_value<char_type>(out, val, precision);
} }
void on_duration_unit() { out = format_duration_unit<Char, Period>(out); } void on_duration_unit() {
out = format_duration_unit<char_type, Period>(out);
}
}; };
} // namespace detail } // namespace detail
@@ -1985,11 +2011,12 @@ class year_month_day {
constexpr auto month() const noexcept -> fmt::month { return month_; } constexpr auto month() const noexcept -> fmt::month { return month_; }
constexpr auto day() const noexcept -> fmt::day { return day_; } constexpr auto day() const noexcept -> fmt::day { return day_; }
}; };
#endif // __cpp_lib_chrono >= 201907 #endif
template <typename Char> template <typename Char>
struct formatter<weekday, Char> : private formatter<std::tm, Char> { struct formatter<weekday, Char> : private formatter<std::tm, Char> {
private: private:
bool localized_ = false;
bool use_tm_formatter_ = false; bool use_tm_formatter_ = false;
public: public:
@@ -1997,7 +2024,8 @@ struct formatter<weekday, Char> : private formatter<std::tm, Char> {
auto it = ctx.begin(), end = ctx.end(); auto it = ctx.begin(), end = ctx.end();
if (it != end && *it == 'L') { if (it != end && *it == 'L') {
++it; ++it;
this->set_localized(); localized_ = true;
return it;
} }
use_tm_formatter_ = it != end && *it != '}'; use_tm_formatter_ = it != end && *it != '}';
return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it; return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it;
@@ -2008,7 +2036,7 @@ struct formatter<weekday, Char> : private formatter<std::tm, Char> {
auto time = std::tm(); auto time = std::tm();
time.tm_wday = static_cast<int>(wd.c_encoding()); time.tm_wday = static_cast<int>(wd.c_encoding());
if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx); if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);
detail::get_locale loc(this->localized(), ctx.locale()); detail::get_locale loc(localized_, ctx.locale());
auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time); auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);
w.on_abbr_weekday(); w.on_abbr_weekday();
return w.out(); return w.out();
@@ -2042,6 +2070,7 @@ struct formatter<day, Char> : private formatter<std::tm, Char> {
template <typename Char> template <typename Char>
struct formatter<month, Char> : private formatter<std::tm, Char> { struct formatter<month, Char> : private formatter<std::tm, Char> {
private: private:
bool localized_ = false;
bool use_tm_formatter_ = false; bool use_tm_formatter_ = false;
public: public:
@@ -2049,7 +2078,8 @@ struct formatter<month, Char> : private formatter<std::tm, Char> {
auto it = ctx.begin(), end = ctx.end(); auto it = ctx.begin(), end = ctx.end();
if (it != end && *it == 'L') { if (it != end && *it == 'L') {
++it; ++it;
this->set_localized(); localized_ = true;
return it;
} }
use_tm_formatter_ = it != end && *it != '}'; use_tm_formatter_ = it != end && *it != '}';
return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it; return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it;
@@ -2060,7 +2090,7 @@ struct formatter<month, Char> : private formatter<std::tm, Char> {
auto time = std::tm(); auto time = std::tm();
time.tm_mon = static_cast<int>(static_cast<unsigned>(m)) - 1; time.tm_mon = static_cast<int>(static_cast<unsigned>(m)) - 1;
if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx); if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);
detail::get_locale loc(this->localized(), ctx.locale()); detail::get_locale loc(localized_, ctx.locale());
auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time); auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);
w.on_abbr_month(); w.on_abbr_month();
return w.out(); return w.out();
@@ -2124,6 +2154,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
format_specs specs_; format_specs specs_;
detail::arg_ref<Char> width_ref_; detail::arg_ref<Char> width_ref_;
detail::arg_ref<Char> precision_ref_; detail::arg_ref<Char> precision_ref_;
bool localized_ = false;
basic_string_view<Char> fmt_; basic_string_view<Char> fmt_;
public: public:
@@ -2146,7 +2177,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
it = detail::parse_precision(it, end, specs_, precision_ref_, ctx); it = detail::parse_precision(it, end, specs_, precision_ref_, ctx);
} }
if (it != end && *it == 'L') { if (it != end && *it == 'L') {
specs_.set_localized(); localized_ = true;
++it; ++it;
} }
end = detail::parse_chrono_format(it, end, checker); end = detail::parse_chrono_format(it, end, checker);
@@ -2173,10 +2204,11 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
out = detail::format_duration_value<Char>(out, d.count(), precision); out = detail::format_duration_value<Char>(out, d.count(), precision);
detail::format_duration_unit<Char, Period>(out); detail::format_duration_unit<Char, Period>(out);
} else { } else {
auto f = using chrono_formatter =
detail::duration_formatter<Char, Rep, Period>(out, d, ctx.locale()); detail::chrono_formatter<FormatContext, decltype(out), Rep, Period>;
auto f = chrono_formatter(ctx, out, d);
f.precision = precision; f.precision = precision;
f.localized = specs_.localized(); f.localized = localized_;
detail::parse_chrono_format(begin, end, f); detail::parse_chrono_format(begin, end, f);
} }
return detail::write( return detail::write(
@@ -2188,15 +2220,30 @@ template <typename Char> struct formatter<std::tm, Char> {
private: private:
format_specs specs_; format_specs specs_;
detail::arg_ref<Char> width_ref_; detail::arg_ref<Char> width_ref_;
basic_string_view<Char> fmt_ =
detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>();
protected: protected:
auto localized() const -> bool { return specs_.localized(); } basic_string_view<Char> fmt_;
FMT_CONSTEXPR void set_localized() { specs_.set_localized(); }
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx, bool has_timezone) template <typename Duration, typename FormatContext>
-> const Char* { auto do_format(const std::tm& tm, FormatContext& ctx,
const Duration* subsecs) const -> decltype(ctx.out()) {
auto specs = specs_;
auto buf = basic_memory_buffer<Char>();
auto out = basic_appender<Char>(buf);
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx);
auto loc_ref = ctx.locale();
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
auto w =
detail::tm_writer<decltype(out), Char, Duration>(loc, out, tm, subsecs);
detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w);
return detail::write(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
}
public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
auto it = ctx.begin(), end = ctx.end(); auto it = ctx.begin(), end = ctx.end();
if (it == end || *it == '}') return it; if (it == end || *it == '}') return it;
@@ -2209,41 +2256,12 @@ template <typename Char> struct formatter<std::tm, Char> {
if (it == end) return it; if (it == end) return it;
} }
if (*it == 'L') { end = detail::parse_chrono_format(it, end, detail::tm_format_checker());
specs_.set_localized();
++it;
}
end = detail::parse_chrono_format(it, end,
detail::tm_format_checker(has_timezone));
// Replace the default format string only if the new spec is not empty. // Replace the default format string only if the new spec is not empty.
if (end != it) fmt_ = {it, detail::to_unsigned(end - it)}; if (end != it) fmt_ = {it, detail::to_unsigned(end - it)};
return end; return end;
} }
template <typename Duration, typename FormatContext>
auto do_format(const std::tm& tm, FormatContext& ctx,
const Duration* subsecs) const -> decltype(ctx.out()) {
auto specs = specs_;
auto buf = basic_memory_buffer<Char>();
auto out = basic_appender<Char>(buf);
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx);
auto loc_ref = specs.localized() ? ctx.locale() : detail::locale_ref();
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
auto w = detail::tm_writer<basic_appender<Char>, Char, Duration>(
loc, out, tm, subsecs);
detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w);
return detail::write(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
}
public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return do_parse(ctx, detail::has_tm_gmtoff<std::tm>::value);
}
template <typename FormatContext> template <typename FormatContext>
auto format(const std::tm& tm, FormatContext& ctx) const auto format(const std::tm& tm, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
@@ -2251,11 +2269,10 @@ template <typename Char> struct formatter<std::tm, Char> {
} }
}; };
// DEPRECATED! Reversed order of template parameters.
template <typename Char, typename Duration> template <typename Char, typename Duration>
struct formatter<sys_time<Duration>, Char> : private formatter<std::tm, Char> { struct formatter<sys_time<Duration>, Char> : formatter<std::tm, Char> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* { FMT_CONSTEXPR formatter() {
return this->do_parse(ctx, true); this->fmt_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>();
} }
template <typename FormatContext> template <typename FormatContext>
@@ -2266,7 +2283,6 @@ struct formatter<sys_time<Duration>, Char> : private formatter<std::tm, Char> {
if (detail::const_check( if (detail::const_check(
period::num == 1 && period::den == 1 && period::num == 1 && period::den == 1 &&
!std::is_floating_point<typename Duration::rep>::value)) { !std::is_floating_point<typename Duration::rep>::value)) {
detail::set_tm_zone(tm, detail::utc());
return formatter<std::tm, Char>::format(tm, ctx); return formatter<std::tm, Char>::format(tm, ctx);
} }
Duration epoch = val.time_since_epoch(); Duration epoch = val.time_since_epoch();
@@ -2274,13 +2290,11 @@ struct formatter<sys_time<Duration>, Char> : private formatter<std::tm, Char> {
epoch - detail::duration_cast<std::chrono::seconds>(epoch)); epoch - detail::duration_cast<std::chrono::seconds>(epoch));
if (subsecs.count() < 0) { if (subsecs.count() < 0) {
auto second = detail::duration_cast<Duration>(std::chrono::seconds(1)); auto second = detail::duration_cast<Duration>(std::chrono::seconds(1));
if (tm.tm_sec != 0) { if (tm.tm_sec != 0)
--tm.tm_sec; --tm.tm_sec;
} else { else
tm = gmtime(val - second); tm = gmtime(val - second);
detail::set_tm_zone(tm, detail::utc()); subsecs += detail::duration_cast<Duration>(std::chrono::seconds(1));
}
subsecs += second;
} }
return formatter<std::tm, Char>::do_format(tm, ctx, &subsecs); return formatter<std::tm, Char>::do_format(tm, ctx, &subsecs);
} }
@@ -2298,29 +2312,23 @@ struct formatter<utc_time<Duration>, Char>
}; };
template <typename Duration, typename Char> template <typename Duration, typename Char>
struct formatter<local_time<Duration>, Char> struct formatter<local_time<Duration>, Char> : formatter<std::tm, Char> {
: private formatter<std::tm, Char> { FMT_CONSTEXPR formatter() {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* { this->fmt_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>();
return this->do_parse(ctx, false);
} }
template <typename FormatContext> template <typename FormatContext>
auto format(local_time<Duration> val, FormatContext& ctx) const auto format(local_time<Duration> val, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
auto time_since_epoch = val.time_since_epoch();
auto seconds_since_epoch =
detail::duration_cast<std::chrono::seconds>(time_since_epoch);
// Use gmtime to prevent time zone conversion since local_time has an
// unspecified time zone.
std::tm t = gmtime(seconds_since_epoch.count());
using period = typename Duration::period; using period = typename Duration::period;
if (period::num == 1 && period::den == 1 && if (period::num == 1 && period::den == 1 &&
!std::is_floating_point<typename Duration::rep>::value) { !std::is_floating_point<typename Duration::rep>::value) {
return formatter<std::tm, Char>::format(t, ctx); return formatter<std::tm, Char>::format(localtime(val), ctx);
} }
auto subsecs = auto epoch = val.time_since_epoch();
detail::duration_cast<Duration>(time_since_epoch - seconds_since_epoch); auto subsecs = detail::duration_cast<Duration>(
return formatter<std::tm, Char>::do_format(t, ctx, &subsecs); epoch - detail::duration_cast<std::chrono::seconds>(epoch));
return formatter<std::tm, Char>::do_format(localtime(val), ctx, &subsecs);
} }
}; };

View File

@@ -190,11 +190,11 @@ enum class emphasis : uint8_t {
// rgb is a struct for red, green and blue colors. // rgb is a struct for red, green and blue colors.
// Using the name "rgb" makes some editors show the color in a tooltip. // Using the name "rgb" makes some editors show the color in a tooltip.
struct rgb { struct rgb {
constexpr rgb() : r(0), g(0), b(0) {} FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
constexpr rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
constexpr rgb(uint32_t hex) FMT_CONSTEXPR rgb(uint32_t hex)
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
constexpr rgb(color hex) FMT_CONSTEXPR rgb(color hex)
: r((uint32_t(hex) >> 16) & 0xFF), : r((uint32_t(hex) >> 16) & 0xFF),
g((uint32_t(hex) >> 8) & 0xFF), g((uint32_t(hex) >> 8) & 0xFF),
b(uint32_t(hex) & 0xFF) {} b(uint32_t(hex) & 0xFF) {}
@@ -205,135 +205,97 @@ struct rgb {
namespace detail { namespace detail {
// A bit-packed variant of an RGB color, a terminal color, or unset color. // color is a struct of either a rgb color or a terminal color.
// see text_style for the bit-packing scheme.
struct color_type { struct color_type {
constexpr color_type() noexcept = default; FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {}
constexpr color_type(color rgb_color) noexcept FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} {
: value_(static_cast<uint32_t>(rgb_color) | (1 << 24)) {} value.rgb_color = static_cast<uint32_t>(rgb_color);
constexpr color_type(rgb rgb_color) noexcept
: color_type(static_cast<color>(
(static_cast<uint32_t>(rgb_color.r) << 16) |
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b)) {}
constexpr color_type(terminal_color term_color) noexcept
: value_(static_cast<uint32_t>(term_color) | (3 << 24)) {}
constexpr auto is_terminal_color() const noexcept -> bool {
return (value_ & (1 << 25)) != 0;
} }
FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} {
constexpr auto value() const noexcept -> uint32_t { value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
return value_ & 0xFFFFFF; (static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
} }
FMT_CONSTEXPR color_type(terminal_color term_color) noexcept
constexpr color_type(uint32_t value) noexcept : value_(value) {} : is_rgb(), value{} {
value.term_color = static_cast<uint8_t>(term_color);
uint32_t value_ = 0; }
bool is_rgb;
union color_union {
uint8_t term_color;
uint32_t rgb_color;
} value;
}; };
} // namespace detail } // namespace detail
/// A text style consisting of foreground and background colors and emphasis. /// A text style consisting of foreground and background colors and emphasis.
class text_style { class text_style {
// The information is packed as follows:
// ┌──┐
// │ 0│─┐
// │..│ ├── foreground color value
// │23│─┘
// ├──┤
// │24│─┬── discriminator for the above value. 00 if unset, 01 if it's
// │25│─┘ an RGB color, or 11 if it's a terminal color (10 is unused)
// ├──┤
// │26│──── overflow bit, always zero (see below)
// ├──┤
// │27│─┐
// │..│ │
// │50│ │
// ├──┤ │
// │51│ ├── background color (same format as the foreground color)
// │52│ │
// ├──┤ │
// │53│─┘
// ├──┤
// │54│─┐
// │..│ ├── emphases
// │61│─┘
// ├──┤
// │62│─┬── unused
// │63│─┘
// └──┘
// The overflow bits are there to make operator|= efficient.
// When ORing, we must throw if, for either the foreground or background,
// one style specifies a terminal color and the other specifies any color
// (terminal or RGB); in other words, if one discriminator is 11 and the
// other is 11 or 01.
//
// We do that check by adding the styles. Consider what adding does to each
// possible pair of discriminators:
// 00 + 00 = 000
// 01 + 00 = 001
// 11 + 00 = 011
// 01 + 01 = 010
// 11 + 01 = 100 (!!)
// 11 + 11 = 110 (!!)
// In the last two cases, the ones we want to catch, the third bit——the
// overflow bit——is set. Bingo.
//
// We must take into account the possible carry bit from the bits
// before the discriminator. The only potentially problematic case is
// 11 + 00 = 011 (a carry bit would make it 100, not good!), but a carry
// bit is impossible in that case, because 00 (unset color) means the
// 24 bits that precede the discriminator are all zero.
//
// This test can be applied to both colors simultaneously.
public: public:
FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
: style_(static_cast<uint64_t>(em) << 54) {} : set_foreground_color(), set_background_color(), ems(em) {}
FMT_CONSTEXPR auto operator|=(text_style rhs) -> text_style& { FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& {
if (((style_ + rhs.style_) & ((1ULL << 26) | (1ULL << 53))) != 0) if (!set_foreground_color) {
report_error("can't OR a terminal color"); set_foreground_color = rhs.set_foreground_color;
style_ |= rhs.style_; foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
report_error("can't OR a terminal color");
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
report_error("can't OR a terminal color");
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
static_cast<uint8_t>(rhs.ems));
return *this; return *this;
} }
friend FMT_CONSTEXPR auto operator|(text_style lhs, text_style rhs) friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs)
-> text_style { -> text_style {
return lhs |= rhs; return lhs |= rhs;
} }
FMT_CONSTEXPR auto operator==(text_style rhs) const noexcept -> bool {
return style_ == rhs.style_;
}
FMT_CONSTEXPR auto operator!=(text_style rhs) const noexcept -> bool {
return !(*this == rhs);
}
FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
return (style_ & (1 << 24)) != 0; return set_foreground_color;
} }
FMT_CONSTEXPR auto has_background() const noexcept -> bool { FMT_CONSTEXPR auto has_background() const noexcept -> bool {
return (style_ & (1ULL << 51)) != 0; return set_background_color;
} }
FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
return (style_ >> 54) != 0; return static_cast<uint8_t>(ems) != 0;
} }
FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
FMT_ASSERT(has_foreground(), "no foreground specified for this style"); FMT_ASSERT(has_foreground(), "no foreground specified for this style");
return style_ & 0x3FFFFFF; return foreground_color;
} }
FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
FMT_ASSERT(has_background(), "no background specified for this style"); FMT_ASSERT(has_background(), "no background specified for this style");
return (style_ >> 27) & 0x3FFFFFF; return background_color;
} }
FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
return static_cast<emphasis>(style_ >> 54); return ems;
} }
private: private:
FMT_CONSTEXPR text_style(uint64_t style) noexcept : style_(style) {} FMT_CONSTEXPR text_style(bool is_foreground,
detail::color_type text_color) noexcept
: set_foreground_color(), set_background_color(), ems() {
if (is_foreground) {
foreground_color = text_color;
set_foreground_color = true;
} else {
background_color = text_color;
set_background_color = true;
}
}
friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
-> text_style; -> text_style;
@@ -341,19 +303,23 @@ class text_style {
friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
-> text_style; -> text_style;
uint64_t style_ = 0; detail::color_type foreground_color;
detail::color_type background_color;
bool set_foreground_color;
bool set_background_color;
emphasis ems;
}; };
/// Creates a text style from the foreground (text) color. /// Creates a text style from the foreground (text) color.
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
-> text_style { -> text_style {
return foreground.value_; return text_style(true, foreground);
} }
/// Creates a text style from the background color. /// Creates a text style from the background color.
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
-> text_style { -> text_style {
return static_cast<uint64_t>(background.value_) << 27; return text_style(false, background);
} }
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
@@ -368,9 +334,9 @@ template <typename Char> struct ansi_color_escape {
const char* esc) noexcept { const char* esc) noexcept {
// If we have a terminal color, we need to output another escape code // If we have a terminal color, we need to output another escape code
// sequence. // sequence.
if (text_color.is_terminal_color()) { if (!text_color.is_rgb) {
bool is_background = esc == string_view("\x1b[48;2;"); bool is_background = esc == string_view("\x1b[48;2;");
uint32_t value = text_color.value(); uint32_t value = text_color.value.term_color;
// Background ASCII codes are the same as the foreground ones but with // Background ASCII codes are the same as the foreground ones but with
// 10 more. // 10 more.
if (is_background) value += 10u; if (is_background) value += 10u;
@@ -394,7 +360,7 @@ template <typename Char> struct ansi_color_escape {
for (int i = 0; i < 7; i++) { for (int i = 0; i < 7; i++) {
buffer[i] = static_cast<Char>(esc[i]); buffer[i] = static_cast<Char>(esc[i]);
} }
rgb color(text_color.value()); rgb color(text_color.value.rgb_color);
to_esc(color.r, buffer + 7, ';'); to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';'); to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm'); to_esc(color.b, buffer + 15, 'm');
@@ -475,26 +441,32 @@ template <typename T> struct styled_arg : view {
}; };
template <typename Char> template <typename Char>
void vformat_to(buffer<Char>& buf, text_style ts, basic_string_view<Char> fmt, void vformat_to(buffer<Char>& buf, const text_style& ts,
basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args) { basic_format_args<buffered_context<Char>> args) {
bool has_style = false;
if (ts.has_emphasis()) { if (ts.has_emphasis()) {
has_style = true;
auto emphasis = make_emphasis<Char>(ts.get_emphasis()); auto emphasis = make_emphasis<Char>(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end()); buf.append(emphasis.begin(), emphasis.end());
} }
if (ts.has_foreground()) { if (ts.has_foreground()) {
has_style = true;
auto foreground = make_foreground_color<Char>(ts.get_foreground()); auto foreground = make_foreground_color<Char>(ts.get_foreground());
buf.append(foreground.begin(), foreground.end()); buf.append(foreground.begin(), foreground.end());
} }
if (ts.has_background()) { if (ts.has_background()) {
has_style = true;
auto background = make_background_color<Char>(ts.get_background()); auto background = make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end()); buf.append(background.begin(), background.end());
} }
vformat_to(buf, fmt, args); vformat_to(buf, fmt, args);
if (ts != text_style()) reset_color<Char>(buf); if (has_style) reset_color<Char>(buf);
} }
} // namespace detail } // namespace detail
inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) { inline void vprint(FILE* f, const text_style& ts, string_view fmt,
format_args args) {
auto buf = memory_buffer(); auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args); detail::vformat_to(buf, ts, fmt, args);
print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size())); print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size()));
@@ -510,7 +482,8 @@ inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) {
* "Elapsed time: {0:.2f} seconds", 1.23); * "Elapsed time: {0:.2f} seconds", 1.23);
*/ */
template <typename... T> template <typename... T>
void print(FILE* f, text_style ts, format_string<T...> fmt, T&&... args) { void print(FILE* f, const text_style& ts, format_string<T...> fmt,
T&&... args) {
vprint(f, ts, fmt.str, vargs<T...>{{args...}}); vprint(f, ts, fmt.str, vargs<T...>{{args...}});
} }
@@ -524,11 +497,11 @@ void print(FILE* f, text_style ts, format_string<T...> fmt, T&&... args) {
* "Elapsed time: {0:.2f} seconds", 1.23); * "Elapsed time: {0:.2f} seconds", 1.23);
*/ */
template <typename... T> template <typename... T>
void print(text_style ts, format_string<T...> fmt, T&&... args) { void print(const text_style& ts, format_string<T...> fmt, T&&... args) {
return print(stdout, ts, fmt, std::forward<T>(args)...); return print(stdout, ts, fmt, std::forward<T>(args)...);
} }
inline auto vformat(text_style ts, string_view fmt, format_args args) inline auto vformat(const text_style& ts, string_view fmt, format_args args)
-> std::string { -> std::string {
auto buf = memory_buffer(); auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args); detail::vformat_to(buf, ts, fmt, args);
@@ -548,7 +521,7 @@ inline auto vformat(text_style ts, string_view fmt, format_args args)
* ``` * ```
*/ */
template <typename... T> template <typename... T>
inline auto format(text_style ts, format_string<T...> fmt, T&&... args) inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args)
-> std::string { -> std::string {
return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}}); return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}});
} }
@@ -556,8 +529,8 @@ inline auto format(text_style ts, format_string<T...> fmt, T&&... args)
/// Formats a string with the given text_style and writes the output to `out`. /// Formats a string with the given text_style and writes the output to `out`.
template <typename OutputIt, template <typename OutputIt,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)> FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
auto vformat_to(OutputIt out, text_style ts, string_view fmt, format_args args) auto vformat_to(OutputIt out, const text_style& ts, string_view fmt,
-> OutputIt { format_args args) -> OutputIt {
auto&& buf = detail::get_buffer<char>(out); auto&& buf = detail::get_buffer<char>(out);
detail::vformat_to(buf, ts, fmt, args); detail::vformat_to(buf, ts, fmt, args);
return detail::get_iterator(buf, out); return detail::get_iterator(buf, out);
@@ -575,8 +548,8 @@ auto vformat_to(OutputIt out, text_style ts, string_view fmt, format_args args)
*/ */
template <typename OutputIt, typename... T, template <typename OutputIt, typename... T,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)> FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
inline auto format_to(OutputIt out, text_style ts, format_string<T...> fmt, inline auto format_to(OutputIt out, const text_style& ts,
T&&... args) -> OutputIt { format_string<T...> fmt, T&&... args) -> OutputIt {
return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}}); return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}});
} }

View File

@@ -1,4 +1,4 @@
Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors Copyright (c) 2012 - present, Victor Zverovich
Permission is hereby granted, free of charge, to any person obtaining Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the a copy of this software and associated documentation files (the

View File

@@ -212,7 +212,7 @@ inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int {
return (e * 631305 - 261663) >> 21; return (e * 631305 - 261663) >> 21;
} }
FMT_INLINE_VARIABLE constexpr struct div_small_pow10_infos_struct { FMT_INLINE_VARIABLE constexpr struct {
uint32_t divisor; uint32_t divisor;
int shift_amount; int shift_amount;
} div_small_pow10_infos[] = {{10, 16}, {100, 16}}; } div_small_pow10_infos[] = {{10, 16}, {100, 16}};
@@ -1097,7 +1097,7 @@ template <> struct cache_accessor<double> {
return {r.high(), r.low() == 0}; return {r.high(), r.low() == 0};
} }
static auto compute_delta(const cache_entry_type& cache, int beta) noexcept static auto compute_delta(cache_entry_type const& cache, int beta) noexcept
-> uint32_t { -> uint32_t {
return static_cast<uint32_t>(cache.high() >> (64 - 1 - beta)); return static_cast<uint32_t>(cache.high() >> (64 - 1 - beta));
} }
@@ -1526,8 +1526,9 @@ template <typename F> class glibc_file : public file_base<F> {
} }
void init_buffer() { void init_buffer() {
if (this->file_->_IO_write_ptr < this->file_->_IO_write_end) return; if (this->file_->_IO_write_ptr) return;
// Force buffer initialization by placing and removing a char in a buffer. // Force buffer initialization by placing and removing a char in a buffer.
assume(this->file_->_IO_write_ptr >= this->file_->_IO_write_end);
putc_unlocked(0, this->file_); putc_unlocked(0, this->file_);
--this->file_->_IO_write_ptr; --this->file_->_IO_write_ptr;
} }

View File

@@ -117,7 +117,6 @@
# define FMT_NOINLINE # define FMT_NOINLINE
#endif #endif
// GCC 4.9 doesn't support qualified names in specializations.
namespace std { namespace std {
template <typename T> struct iterator_traits<fmt::basic_appender<T>> { template <typename T> struct iterator_traits<fmt::basic_appender<T>> {
using iterator_category = output_iterator_tag; using iterator_category = output_iterator_tag;
@@ -706,7 +705,7 @@ using is_integer =
#if defined(FMT_USE_FLOAT128) #if defined(FMT_USE_FLOAT128)
// Use the provided definition. // Use the provided definition.
#elif FMT_CLANG_VERSION >= 309 && FMT_HAS_INCLUDE(<quadmath.h>) #elif FMT_CLANG_VERSION && FMT_HAS_INCLUDE(<quadmath.h>)
# define FMT_USE_FLOAT128 1 # define FMT_USE_FLOAT128 1
#elif FMT_GCC_VERSION && defined(_GLIBCXX_USE_FLOAT128) && \ #elif FMT_GCC_VERSION && defined(_GLIBCXX_USE_FLOAT128) && \
!defined(__STRICT_ANSI__) !defined(__STRICT_ANSI__)
@@ -722,10 +721,11 @@ struct float128 {};
template <typename T> using is_float128 = std::is_same<T, float128>; template <typename T> using is_float128 = std::is_same<T, float128>;
template <typename T> struct is_floating_point : std::is_floating_point<T> {}; template <typename T>
template <> struct is_floating_point<float128> : std::true_type {}; using is_floating_point =
bool_constant<std::is_floating_point<T>::value || is_float128<T>::value>;
template <typename T, bool = is_floating_point<T>::value> template <typename T, bool = std::is_floating_point<T>::value>
struct is_fast_float : bool_constant<std::numeric_limits<T>::is_iec559 && struct is_fast_float : bool_constant<std::numeric_limits<T>::is_iec559 &&
sizeof(T) <= sizeof(double)> {}; sizeof(T) <= sizeof(double)> {};
template <typename T> struct is_fast_float<T, false> : std::false_type {}; template <typename T> struct is_fast_float<T, false> : std::false_type {};
@@ -1613,7 +1613,7 @@ constexpr auto convert_float(T value) -> convert_float_result<T> {
} }
template <typename Char, typename OutputIt> template <typename Char, typename OutputIt>
FMT_CONSTEXPR FMT_NOINLINE auto fill(OutputIt it, size_t n, FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n,
const basic_specs& specs) -> OutputIt { const basic_specs& specs) -> OutputIt {
auto fill_size = specs.fill_size(); auto fill_size = specs.fill_size();
if (fill_size == 1) return detail::fill_n(it, n, specs.fill_unit<Char>()); if (fill_size == 1) return detail::fill_n(it, n, specs.fill_unit<Char>());
@@ -2472,8 +2472,8 @@ template <typename T>
struct has_isfinite<T, enable_if_t<sizeof(std::isfinite(T())) != 0>> struct has_isfinite<T, enable_if_t<sizeof(std::isfinite(T())) != 0>>
: std::true_type {}; : std::true_type {};
template <typename T, template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value&&
FMT_ENABLE_IF(is_floating_point<T>::value&& has_isfinite<T>::value)> has_isfinite<T>::value)>
FMT_CONSTEXPR20 auto isfinite(T value) -> bool { FMT_CONSTEXPR20 auto isfinite(T value) -> bool {
constexpr T inf = T(std::numeric_limits<double>::infinity()); constexpr T inf = T(std::numeric_limits<double>::infinity());
if (is_constant_evaluated()) if (is_constant_evaluated())

View File

@@ -158,8 +158,7 @@ void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
FMT_EXPORT template <typename... T> FMT_EXPORT template <typename... T>
void println(std::ostream& os, format_string<T...> fmt, T&&... args) { void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmt::print(os, FMT_STRING("{}\n"), fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
fmt::format(fmt, std::forward<T>(args)...));
} }
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@@ -774,13 +774,13 @@ struct formatter<
: formatter<detail::all<typename T::container_type>, Char> { : formatter<detail::all<typename T::container_type>, Char> {
using all = detail::all<typename T::container_type>; using all = detail::all<typename T::container_type>;
template <typename FormatContext> template <typename FormatContext>
auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) { auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {
struct getter : T { struct getter : T {
static auto get(const T& v) -> all { static auto get(const T& t) -> all {
return {v.*(&getter::c)}; // Access c through the derived class. return {t.*(&getter::c)}; // Access c through the derived class.
} }
}; };
return formatter<all>::format(getter::get(value), ctx); return formatter<all>::format(getter::get(t), ctx);
} }
}; };

View File

@@ -113,6 +113,7 @@ void write_escaped_path(basic_memory_buffer<Char>& quoted,
} // namespace detail } // namespace detail
FMT_EXPORT
template <typename Char> struct formatter<std::filesystem::path, Char> { template <typename Char> struct formatter<std::filesystem::path, Char> {
private: private:
format_specs specs_; format_specs specs_;
@@ -181,6 +182,7 @@ FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_FILESYSTEM #endif // FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <std::size_t N, typename Char> template <std::size_t N, typename Char>
struct formatter<std::bitset<N>, Char> struct formatter<std::bitset<N>, Char>
: nested_formatter<basic_string_view<Char>, Char> { : nested_formatter<basic_string_view<Char>, Char> {
@@ -207,12 +209,14 @@ struct formatter<std::bitset<N>, Char>
} }
}; };
FMT_EXPORT
template <typename Char> template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {}; struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
FMT_END_NAMESPACE FMT_END_NAMESPACE
#ifdef __cpp_lib_optional #ifdef __cpp_lib_optional
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename T, typename Char> template <typename T, typename Char>
struct formatter<std::optional<T>, Char, struct formatter<std::optional<T>, Char,
std::enable_if_t<is_formattable<T, Char>::value>> { std::enable_if_t<is_formattable<T, Char>::value>> {
@@ -275,6 +279,7 @@ FMT_END_NAMESPACE
#ifdef __cpp_lib_expected #ifdef __cpp_lib_expected
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename T, typename E, typename Char> template <typename T, typename E, typename Char>
struct formatter<std::expected<T, E>, Char, struct formatter<std::expected<T, E>, Char,
std::enable_if_t<(std::is_void<T>::value || std::enable_if_t<(std::is_void<T>::value ||
@@ -306,6 +311,7 @@ FMT_END_NAMESPACE
#ifdef __cpp_lib_source_location #ifdef __cpp_lib_source_location
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <> struct formatter<std::source_location> { template <> struct formatter<std::source_location> {
FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); } FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); }
@@ -361,6 +367,7 @@ template <typename T, typename C> struct is_variant_formattable {
detail::is_variant_formattable_<T, C>::value; detail::is_variant_formattable_<T, C>::value;
}; };
FMT_EXPORT
template <typename Char> struct formatter<std::monostate, Char> { template <typename Char> struct formatter<std::monostate, Char> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* { FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin(); return ctx.begin();
@@ -373,6 +380,7 @@ template <typename Char> struct formatter<std::monostate, Char> {
} }
}; };
FMT_EXPORT
template <typename Variant, typename Char> template <typename Variant, typename Char>
struct formatter< struct formatter<
Variant, Char, Variant, Char,
@@ -406,11 +414,11 @@ FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_VARIANT #endif // FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <> struct formatter<std::error_code> { template <> struct formatter<std::error_code> {
private: private:
format_specs specs_; format_specs specs_;
detail::arg_ref<char> width_ref_; detail::arg_ref<char> width_ref_;
bool debug_ = false;
public: public:
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
@@ -418,19 +426,11 @@ template <> struct formatter<std::error_code> {
if (it == end) return it; if (it == end) return it;
it = detail::parse_align(it, end, specs_); it = detail::parse_align(it, end, specs_);
if (it == end) return it;
char c = *it; char c = *it;
if (it != end && ((c >= '0' && c <= '9') || c == '{')) if ((c >= '0' && c <= '9') || c == '{')
it = detail::parse_width(it, end, specs_, width_ref_, ctx); it = detail::parse_width(it, end, specs_, width_ref_, ctx);
if (it != end && *it == '?') {
debug_ = true;
++it;
}
if (it != end && *it == 's') {
specs_.set_type(presentation_type::string);
++it;
}
return it; return it;
} }
@@ -440,21 +440,12 @@ template <> struct formatter<std::error_code> {
auto specs = specs_; auto specs = specs_;
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx); ctx);
auto buf = memory_buffer(); memory_buffer buf;
if (specs_.type() == presentation_type::string) { buf.append(string_view(ec.category().name()));
buf.append(ec.message()); buf.push_back(':');
} else { detail::write<char>(appender(buf), ec.value());
buf.append(string_view(ec.category().name())); return detail::write<char>(ctx.out(), string_view(buf.data(), buf.size()),
buf.push_back(':'); specs);
detail::write<char>(appender(buf), ec.value());
}
auto quoted = memory_buffer();
auto str = string_view(buf.data(), buf.size());
if (debug_) {
detail::write_escaped_string<char>(std::back_inserter(quoted), str);
str = string_view(quoted.data(), quoted.size());
}
return detail::write<char>(ctx.out(), str, specs);
} }
}; };
@@ -529,6 +520,7 @@ auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
} // namespace detail } // namespace detail
FMT_EXPORT
template <typename Char> template <typename Char>
struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types. struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
> { > {
@@ -545,6 +537,7 @@ struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
}; };
#endif #endif
FMT_EXPORT
template <typename T, typename Char> template <typename T, typename Char>
struct formatter< struct formatter<
T, Char, // DEPRECATED! Mixing code unit types. T, Char, // DEPRECATED! Mixing code unit types.
@@ -610,6 +603,7 @@ struct is_bit_reference_like<std::__bit_const_reference<C>> {
// We can't use std::vector<bool, Allocator>::reference and // We can't use std::vector<bool, Allocator>::reference and
// std::bitset<N>::reference because the compiler can't deduce Allocator and N // std::bitset<N>::reference because the compiler can't deduce Allocator and N
// in partial specialization. // in partial specialization.
FMT_EXPORT
template <typename BitRef, typename Char> template <typename BitRef, typename Char>
struct formatter<BitRef, Char, struct formatter<BitRef, Char,
enable_if_t<detail::is_bit_reference_like<BitRef>::value>> enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
@@ -629,6 +623,7 @@ template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
return p.get(); return p.get();
} }
FMT_EXPORT
template <typename T, typename Char> template <typename T, typename Char>
struct formatter<std::atomic<T>, Char, struct formatter<std::atomic<T>, Char,
enable_if_t<is_formattable<T, Char>::value>> enable_if_t<is_formattable<T, Char>::value>>
@@ -641,6 +636,7 @@ struct formatter<std::atomic<T>, Char,
}; };
#ifdef __cpp_lib_atomic_flag_test #ifdef __cpp_lib_atomic_flag_test
FMT_EXPORT
template <typename Char> template <typename Char>
struct formatter<std::atomic_flag, Char> : formatter<bool, Char> { struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
template <typename FormatContext> template <typename FormatContext>
@@ -651,6 +647,7 @@ struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
}; };
#endif // __cpp_lib_atomic_flag_test #endif // __cpp_lib_atomic_flag_test
FMT_EXPORT
template <typename T, typename Char> struct formatter<std::complex<T>, Char> { template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
private: private:
detail::dynamic_format_specs<Char> specs_; detail::dynamic_format_specs<Char> specs_;
@@ -713,6 +710,7 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
} }
}; };
FMT_EXPORT
template <typename T, typename Char> template <typename T, typename Char>
struct formatter<std::reference_wrapper<T>, Char, struct formatter<std::reference_wrapper<T>, Char,
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value>> enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value>>

View File

@@ -112,6 +112,10 @@ inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}}; return {{s}};
} }
template <> struct is_char<wchar_t> : std::true_type {};
template <> struct is_char<char16_t> : std::true_type {};
template <> struct is_char<char32_t> : std::true_type {};
#ifdef __cpp_char8_t #ifdef __cpp_char8_t
template <> struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled> {}; template <> struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled> {};
#endif #endif
@@ -318,7 +322,7 @@ template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...)); return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
} }
inline auto vformat(text_style ts, wstring_view fmt, wformat_args args) inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args)
-> std::wstring { -> std::wstring {
auto buf = wmemory_buffer(); auto buf = wmemory_buffer();
detail::vformat_to(buf, ts, fmt, args); detail::vformat_to(buf, ts, fmt, args);
@@ -326,19 +330,19 @@ inline auto vformat(text_style ts, wstring_view fmt, wformat_args args)
} }
template <typename... T> template <typename... T>
inline auto format(text_style ts, wformat_string<T...> fmt, T&&... args) inline auto format(const text_style& ts, wformat_string<T...> fmt, T&&... args)
-> std::wstring { -> std::wstring {
return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...)); return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
} }
template <typename... T> template <typename... T>
FMT_DEPRECATED void print(std::FILE* f, text_style ts, wformat_string<T...> fmt, FMT_DEPRECATED void print(std::FILE* f, const text_style& ts,
const T&... args) { wformat_string<T...> fmt, const T&... args) {
vprint(f, ts, fmt, fmt::make_wformat_args(args...)); vprint(f, ts, fmt, fmt::make_wformat_args(args...));
} }
template <typename... T> template <typename... T>
FMT_DEPRECATED void print(text_style ts, wformat_string<T...> fmt, FMT_DEPRECATED void print(const text_style& ts, wformat_string<T...> fmt,
const T&... args) { const T&... args) {
return print(stdout, ts, fmt, args...); return print(stdout, ts, fmt, args...);
} }

View File

@@ -20,7 +20,11 @@
#ifndef FMT_USE_WINDOWS_H #ifndef FMT_USE_WINDOWS_H
#define FMT_USE_WINDOWS_H 0 #define FMT_USE_WINDOWS_H 0
#endif #endif
#include <spdlog/fmt/bundled/core.h>
#include <spdlog/fmt/bundled/format.h> #include <spdlog/fmt/bundled/format.h>
#else // SPDLOG_FMT_EXTERNAL is defined - use external fmtlib #else // SPDLOG_FMT_EXTERNAL is defined - use external fmtlib
#include <fmt/core.h>
#include <fmt/format.h> #include <fmt/format.h>
#endif #endif

View File

@@ -57,7 +57,7 @@ SPDLOG_INLINE void logger::swap(spdlog::logger &other) SPDLOG_NOEXCEPT {
std::swap(tracer_, other.tracer_); std::swap(tracer_, other.tracer_);
} }
SPDLOG_INLINE void swap(logger &a, logger &b) noexcept { a.swap(b); } SPDLOG_INLINE void swap(logger &a, logger &b) { a.swap(b); }
SPDLOG_INLINE void logger::set_level(level::level_enum log_level) { level_.store(log_level); } SPDLOG_INLINE void logger::set_level(level::level_enum log_level) { level_.store(log_level); }
@@ -163,12 +163,12 @@ SPDLOG_INLINE void logger::dump_backtrace_() {
} }
} }
SPDLOG_INLINE bool logger::should_flush_(const details::log_msg &msg) const { SPDLOG_INLINE bool logger::should_flush_(const details::log_msg &msg) {
auto flush_level = flush_level_.load(std::memory_order_relaxed); auto flush_level = flush_level_.load(std::memory_order_relaxed);
return (msg.level >= flush_level) && (msg.level != level::off); return (msg.level >= flush_level) && (msg.level != level::off);
} }
SPDLOG_INLINE void logger::err_handler_(const std::string &msg) const { SPDLOG_INLINE void logger::err_handler_(const std::string &msg) {
if (custom_err_handler_) { if (custom_err_handler_) {
custom_err_handler_(msg); custom_err_handler_(msg);
} else { } else {

View File

@@ -363,14 +363,14 @@ protected:
virtual void sink_it_(const details::log_msg &msg); virtual void sink_it_(const details::log_msg &msg);
virtual void flush_(); virtual void flush_();
void dump_backtrace_(); void dump_backtrace_();
bool should_flush_(const details::log_msg &msg) const; bool should_flush_(const details::log_msg &msg);
// handle errors during logging. // handle errors during logging.
// default handler prints the error to stderr at max rate of 1 message/sec. // default handler prints the error to stderr at max rate of 1 message/sec.
void err_handler_(const std::string &msg) const; void err_handler_(const std::string &msg);
}; };
void swap(logger &a, logger &b) noexcept; void swap(logger &a, logger &b);
} // namespace spdlog } // namespace spdlog

View File

@@ -12,14 +12,12 @@
#include <spdlog/common.h> #include <spdlog/common.h>
// MDC is a simple map of key->string values stored in thread local storage whose content will be // MDC is a simple map of key->string values stored in thread local storage whose content will be printed by the loggers.
// printed by the loggers. Note: Not supported in async mode (thread local storage - so the async // Note: Not supported in async mode (thread local storage - so the async thread pool have different copy).
// thread pool have different copy).
// //
// Usage example: // Usage example:
// spdlog::mdc::put("mdc_key_1", "mdc_value_1"); // spdlog::mdc::put("mdc_key_1", "mdc_value_1");
// spdlog::info("Hello, {}", "World!"); // => [2024-04-26 02:08:05.040] [info] // spdlog::info("Hello, {}", "World!"); // => [2024-04-26 02:08:05.040] [info] [mdc_key_1:mdc_value_1] Hello, World!
// [mdc_key_1:mdc_value_1] Hello, World!
namespace spdlog { namespace spdlog {
class SPDLOG_API mdc { class SPDLOG_API mdc {

View File

@@ -111,8 +111,7 @@ SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::set_color_mode_(color_mode mode
} }
template <typename ConsoleMutex> template <typename ConsoleMutex>
SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_ccode_( SPDLOG_INLINE void ansicolor_sink<ConsoleMutex>::print_ccode_(const string_view_t &color_code) const {
const string_view_t &color_code) const {
details::os::fwrite_bytes(color_code.data(), color_code.size(), target_file_); details::os::fwrite_bytes(color_code.data(), color_code.size(), target_file_);
} }

View File

@@ -40,7 +40,7 @@ public:
void log(const details::log_msg &msg) override; void log(const details::log_msg &msg) override;
void flush() override; void flush() override;
void set_pattern(const std::string &pattern) override; void set_pattern(const std::string &pattern) final override;
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override; void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override;
// Formatting codes // Formatting codes

View File

@@ -27,7 +27,7 @@ public:
protected: protected:
void sink_it_(const details::log_msg &msg) override { callback_(msg); } void sink_it_(const details::log_msg &msg) override { callback_(msg); }
void flush_() override {} void flush_() override{}
private: private:
custom_log_callback callback_; custom_log_callback callback_;

View File

@@ -40,21 +40,22 @@ template <typename Mutex>
class dup_filter_sink : public dist_sink<Mutex> { class dup_filter_sink : public dist_sink<Mutex> {
public: public:
template <class Rep, class Period> template <class Rep, class Period>
explicit dup_filter_sink(std::chrono::duration<Rep, Period> max_skip_duration) explicit dup_filter_sink(std::chrono::duration<Rep, Period> max_skip_duration,
: max_skip_duration_{max_skip_duration} {} level::level_enum notification_level = level::info)
: max_skip_duration_{max_skip_duration},
log_level_{notification_level} {}
protected: protected:
std::chrono::microseconds max_skip_duration_; std::chrono::microseconds max_skip_duration_;
log_clock::time_point last_msg_time_; log_clock::time_point last_msg_time_;
std::string last_msg_payload_; std::string last_msg_payload_;
size_t skip_counter_ = 0; size_t skip_counter_ = 0;
level::level_enum skipped_msg_log_level_ = spdlog::level::level_enum::off; level::level_enum log_level_;
void sink_it_(const details::log_msg &msg) override { void sink_it_(const details::log_msg &msg) override {
bool filtered = filter_(msg); bool filtered = filter_(msg);
if (!filtered) { if (!filtered) {
skip_counter_ += 1; skip_counter_ += 1;
skipped_msg_log_level_ = msg.level;
return; return;
} }
@@ -64,7 +65,7 @@ protected:
auto msg_size = ::snprintf(buf, sizeof(buf), "Skipped %u duplicate messages..", auto msg_size = ::snprintf(buf, sizeof(buf), "Skipped %u duplicate messages..",
static_cast<unsigned>(skip_counter_)); static_cast<unsigned>(skip_counter_));
if (msg_size > 0 && static_cast<size_t>(msg_size) < sizeof(buf)) { if (msg_size > 0 && static_cast<size_t>(msg_size) < sizeof(buf)) {
details::log_msg skipped_msg{msg.source, msg.logger_name, skipped_msg_log_level_, details::log_msg skipped_msg{msg.source, msg.logger_name, log_level_,
string_view_t{buf, static_cast<size_t>(msg_size)}}; string_view_t{buf, static_cast<size_t>(msg_size)}};
dist_sink<Mutex>::sink_it_(skipped_msg); dist_sink<Mutex>::sink_it_(skipped_msg);
} }

View File

@@ -21,11 +21,7 @@ template <typename Mutex>
class ringbuffer_sink final : public base_sink<Mutex> { class ringbuffer_sink final : public base_sink<Mutex> {
public: public:
explicit ringbuffer_sink(size_t n_items) explicit ringbuffer_sink(size_t n_items)
: q_{n_items} { : q_{n_items} {}
if (n_items == 0) {
throw_spdlog_ex("ringbuffer_sink: n_items cannot be zero");
}
}
std::vector<details::log_msg_buffer> last_raw(size_t lim = 0) { std::vector<details::log_msg_buffer> last_raw(size_t lim = 0) {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);

View File

@@ -14,6 +14,7 @@
#include <spdlog/fmt/fmt.h> #include <spdlog/fmt/fmt.h>
#include <cerrno> #include <cerrno>
#include <chrono>
#include <ctime> #include <ctime>
#include <mutex> #include <mutex>
#include <string> #include <string>
@@ -37,8 +38,8 @@ SPDLOG_INLINE rotating_file_sink<Mutex>::rotating_file_sink(
throw_spdlog_ex("rotating sink constructor: max_size arg cannot be zero"); throw_spdlog_ex("rotating sink constructor: max_size arg cannot be zero");
} }
if (max_files > MaxFiles) { if (max_files > 200000) {
throw_spdlog_ex("rotating sink constructor: max_files arg cannot exceed MaxFiles"); 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
@@ -53,12 +54,11 @@ SPDLOG_INLINE rotating_file_sink<Mutex>::rotating_file_sink(
template <typename Mutex> template <typename Mutex>
SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::calc_filename(const filename_t &filename, SPDLOG_INLINE filename_t rotating_file_sink<Mutex>::calc_filename(const filename_t &filename,
std::size_t index) { std::size_t index) {
if (index == 0U) { if (index == 0u) {
return filename; return filename;
} }
filename_t basename; filename_t basename, ext;
filename_t 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_lib::format(SPDLOG_FMT_STRING(SPDLOG_FILENAME_T("{}.{}{}")), basename, index, ext); return fmt_lib::format(SPDLOG_FMT_STRING(SPDLOG_FILENAME_T("{}.{}{}")), basename, index, ext);
} }
@@ -74,35 +74,6 @@ SPDLOG_INLINE void rotating_file_sink<Mutex>::rotate_now() {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
rotate_(); rotate_();
} }
template <typename Mutex>
SPDLOG_INLINE void rotating_file_sink<Mutex>::set_max_size(std::size_t max_size) {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
if (max_size == 0) {
throw_spdlog_ex("rotating sink set_max_size: max_size arg cannot be zero");
}
max_size_ = max_size;
}
template <typename Mutex>
SPDLOG_INLINE std::size_t rotating_file_sink<Mutex>::get_max_size() {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
return max_size_;
}
template <typename Mutex>
SPDLOG_INLINE void rotating_file_sink<Mutex>::set_max_files(std::size_t max_files) {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
if (max_files > MaxFiles) {
throw_spdlog_ex("rotating sink set_max_files: max_files arg cannot exceed 200000");
}
max_files_ = max_files;
}
template <typename Mutex>
std::size_t rotating_file_sink<Mutex>::get_max_files() {
std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
return max_files_;
}
template <typename Mutex> template <typename Mutex>
SPDLOG_INLINE void rotating_file_sink<Mutex>::sink_it_(const details::log_msg &msg) { SPDLOG_INLINE void rotating_file_sink<Mutex>::sink_it_(const details::log_msg &msg) {

View File

@@ -8,6 +8,7 @@
#include <spdlog/details/synchronous_factory.h> #include <spdlog/details/synchronous_factory.h>
#include <spdlog/sinks/base_sink.h> #include <spdlog/sinks/base_sink.h>
#include <chrono>
#include <mutex> #include <mutex>
#include <string> #include <string>
@@ -20,7 +21,6 @@ namespace sinks {
template <typename Mutex> template <typename Mutex>
class rotating_file_sink final : public base_sink<Mutex> { class rotating_file_sink final : public base_sink<Mutex> {
public: public:
static constexpr size_t MaxFiles = 200000;
rotating_file_sink(filename_t base_filename, rotating_file_sink(filename_t base_filename,
std::size_t max_size, std::size_t max_size,
std::size_t max_files, std::size_t max_files,
@@ -29,10 +29,6 @@ public:
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();
void rotate_now(); void rotate_now();
void set_max_size(std::size_t max_size);
std::size_t get_max_size();
void set_max_files(std::size_t max_files);
std::size_t get_max_files();
protected: protected:
void sink_it_(const details::log_msg &msg) override; void sink_it_(const details::log_msg &msg) override;
@@ -46,7 +42,7 @@ private:
// log.3.txt -> delete // log.3.txt -> delete
void rotate_(); void rotate_();
// delete the target if exists, and rename the src file to target // delete the target if exists, and rename the src file to target
// return true on success, false otherwise. // return true on success, false otherwise.
bool rename_file_(const filename_t &src_filename, const filename_t &target_filename); bool rename_file_(const filename_t &src_filename, const filename_t &target_filename);
@@ -65,24 +61,25 @@ using rotating_file_sink_st = rotating_file_sink<details::null_mutex>;
// //
// factory functions // factory functions
// //
template <typename Factory = spdlog::synchronous_factory> template <typename Factory = spdlog::synchronous_factory>
std::shared_ptr<logger> rotating_logger_mt(const std::string &logger_name, inline std::shared_ptr<logger> rotating_logger_mt(const std::string &logger_name,
const filename_t &filename, const filename_t &filename,
size_t max_file_size, size_t max_file_size,
size_t max_files, size_t max_files,
bool rotate_on_open = false, bool rotate_on_open = false,
const file_event_handlers &event_handlers = {}) { const file_event_handlers &event_handlers = {}) {
return Factory::template create<sinks::rotating_file_sink_mt>( return Factory::template create<sinks::rotating_file_sink_mt>(
logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers); 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>
std::shared_ptr<logger> rotating_logger_st(const std::string &logger_name, inline std::shared_ptr<logger> rotating_logger_st(const std::string &logger_name,
const filename_t &filename, const filename_t &filename,
size_t max_file_size, size_t max_file_size,
size_t max_files, size_t max_files,
bool rotate_on_open = false, bool rotate_on_open = false,
const file_event_handlers &event_handlers = {}) { const file_event_handlers &event_handlers = {}) {
return Factory::template create<sinks::rotating_file_sink_st>( return Factory::template create<sinks::rotating_file_sink_st>(
logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers); logger_name, filename, max_file_size, max_files, rotate_on_open, event_handlers);
} }

View File

@@ -31,7 +31,6 @@ namespace sinks {
struct tcp_sink_config { struct tcp_sink_config {
std::string server_host; std::string server_host;
int server_port; int server_port;
int timeout_ms = 0; // The timeout for all 3 major socket operations that is connect, send, and recv
bool lazy_connect = false; // if true connect on first log call instead of on construction bool lazy_connect = false; // if true connect on first log call instead of on construction
tcp_sink_config(std::string host, int port) tcp_sink_config(std::string host, int port)
@@ -45,22 +44,10 @@ public:
// connect to tcp host/port or throw if failed // connect to tcp host/port or throw if failed
// host can be hostname or ip address // host can be hostname or ip address
explicit tcp_sink(const std::string &host,
int port,
int timeout_ms = 0,
bool lazy_connect = false)
: config_{host, port} {
config_.timeout_ms = timeout_ms;
config_.lazy_connect = lazy_connect;
if (!config_.lazy_connect) {
client_.connect(config_.server_host, config_.server_port, config_.timeout_ms);
}
}
explicit tcp_sink(tcp_sink_config sink_config) explicit tcp_sink(tcp_sink_config sink_config)
: config_{std::move(sink_config)} { : config_{std::move(sink_config)} {
if (!config_.lazy_connect) { if (!config_.lazy_connect) {
client_.connect(config_.server_host, config_.server_port, config_.timeout_ms); this->client_.connect(config_.server_host, config_.server_port);
} }
} }
@@ -71,7 +58,7 @@ protected:
spdlog::memory_buf_t formatted; spdlog::memory_buf_t formatted;
spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted); spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted);
if (!client_.is_connected()) { if (!client_.is_connected()) {
client_.connect(config_.server_host, config_.server_port, config_.timeout_ms); client_.connect(config_.server_host, config_.server_port);
} }
client_.send(formatted.data(), formatted.size()); client_.send(formatted.data(), formatted.size());
} }

View File

@@ -137,10 +137,10 @@ void SPDLOG_INLINE wincolor_sink<ConsoleMutex>::print_range_(const memory_buf_t
#if defined(SPDLOG_UTF8_TO_WCHAR_CONSOLE) #if defined(SPDLOG_UTF8_TO_WCHAR_CONSOLE)
wmemory_buf_t wformatted; wmemory_buf_t wformatted;
details::os::utf8_to_wstrbuf(string_view_t(formatted.data() + start, end - start), details::os::utf8_to_wstrbuf(string_view_t(formatted.data() + start, end - start),
wformatted); wformatted);
auto size = static_cast<DWORD>(wformatted.size()); auto size = static_cast<DWORD>(wformatted.size());
auto ignored = ::WriteConsoleW(static_cast<HANDLE>(out_handle_), wformatted.data(), size, auto ignored = ::WriteConsoleW(static_cast<HANDLE>(out_handle_), wformatted.data(), size,
nullptr, nullptr); nullptr, nullptr);
#else #else
auto size = static_cast<DWORD>(end - start); auto size = static_cast<DWORD>(end - start);
auto ignored = ::WriteConsoleA(static_cast<HANDLE>(out_handle_), formatted.data() + start, auto ignored = ::WriteConsoleA(static_cast<HANDLE>(out_handle_), formatted.data() + start,

View File

@@ -31,10 +31,10 @@ public:
// change the color for the given level // change the color for the given level
void set_color(level::level_enum level, std::uint16_t color); void set_color(level::level_enum level, std::uint16_t color);
void log(const details::log_msg &msg) override; void log(const details::log_msg &msg) final override;
void flush() override; void flush() final override;
void set_pattern(const std::string &pattern) override; void set_pattern(const std::string &pattern) override final;
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override; void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override final;
void set_color_mode(color_mode mode); void set_color_mode(color_mode mode);
protected: protected:

View File

@@ -59,10 +59,6 @@ SPDLOG_INLINE void register_logger(std::shared_ptr<logger> logger) {
details::registry::instance().register_logger(std::move(logger)); details::registry::instance().register_logger(std::move(logger));
} }
SPDLOG_INLINE void register_or_replace(std::shared_ptr<logger> logger) {
details::registry::instance().register_or_replace(std::move(logger));
}
SPDLOG_INLINE void apply_all(const std::function<void(std::shared_ptr<logger>)> &fun) { SPDLOG_INLINE void apply_all(const std::function<void(std::shared_ptr<logger>)> &fun) {
details::registry::instance().apply_all(fun); details::registry::instance().apply_all(fun);
} }

View File

@@ -25,7 +25,7 @@ namespace spdlog {
using default_factory = synchronous_factory; using default_factory = synchronous_factory;
// Create and register a logger with a templated sink type // Create and register a logger with a templated sink type
// The logger's level, formatter and flush level will be set according to the // The logger's level, formatter and flush level will be set according the
// global settings. // global settings.
// //
// Example: // Example:
@@ -46,7 +46,7 @@ inline std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs
// spdlog::initialize_logger(mylogger); // spdlog::initialize_logger(mylogger);
SPDLOG_API void initialize_logger(std::shared_ptr<logger> logger); SPDLOG_API void initialize_logger(std::shared_ptr<logger> logger);
// Return an existing logger or nullptr if a logger with such a name doesn't // Return an existing logger or nullptr if a logger with such name doesn't
// exist. // exist.
// example: spdlog::get("my_logger")->info("hello {}", "world"); // example: spdlog::get("my_logger")->info("hello {}", "world");
SPDLOG_API std::shared_ptr<logger> get(const std::string &name); SPDLOG_API std::shared_ptr<logger> get(const std::string &name);
@@ -71,13 +71,13 @@ SPDLOG_API void dump_backtrace();
// Get global logging level // Get global logging level
SPDLOG_API level::level_enum get_level(); SPDLOG_API level::level_enum get_level();
// Set the global logging level // Set global logging level
SPDLOG_API void set_level(level::level_enum log_level); SPDLOG_API void set_level(level::level_enum log_level);
// Determine whether the default logger should log messages with a certain level // Determine whether the default logger should log messages with a certain level
SPDLOG_API bool should_log(level::level_enum lvl); SPDLOG_API bool should_log(level::level_enum lvl);
// Set a global flush level // Set global flush level
SPDLOG_API void flush_on(level::level_enum log_level); SPDLOG_API void flush_on(level::level_enum log_level);
// Start/Restart a periodic flusher thread // Start/Restart a periodic flusher thread
@@ -91,14 +91,9 @@ inline void flush_every(std::chrono::duration<Rep, Period> interval) {
SPDLOG_API void set_error_handler(void (*handler)(const std::string &msg)); SPDLOG_API void set_error_handler(void (*handler)(const std::string &msg));
// Register the given logger with the given name // Register the given logger with the given name
// Will throw if a logger with the same name already exists.
SPDLOG_API void register_logger(std::shared_ptr<logger> logger); SPDLOG_API void register_logger(std::shared_ptr<logger> logger);
// Register the given logger with the given name // Apply a user defined function on all registered loggers
// Will replace any existing logger with the same name.
SPDLOG_API void register_or_replace(std::shared_ptr<logger> logger);
// Apply a user-defined function on all registered loggers
// Example: // Example:
// spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) {l->flush();}); // spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) {l->flush();});
SPDLOG_API void apply_all(const std::function<void(std::shared_ptr<logger>)> &fun); SPDLOG_API void apply_all(const std::function<void(std::shared_ptr<logger>)> &fun);
@@ -116,19 +111,19 @@ SPDLOG_API void shutdown();
SPDLOG_API void set_automatic_registration(bool automatic_registration); SPDLOG_API void set_automatic_registration(bool automatic_registration);
// API for using default logger (stdout_color_mt), // API for using default logger (stdout_color_mt),
// e.g.: spdlog::info("Message {}", 1); // e.g: spdlog::info("Message {}", 1);
// //
// The default logger object can be accessed using the spdlog::default_logger(): // The default logger object can be accessed using the spdlog::default_logger():
// For example, to add another sink to it: // For example, to add another sink to it:
// spdlog::default_logger()->sinks().push_back(some_sink); // spdlog::default_logger()->sinks().push_back(some_sink);
// //
// The default logger can be replaced using spdlog::set_default_logger(new_logger). // The default logger can replaced using spdlog::set_default_logger(new_logger).
// For example, to replace it with a file logger. // For example, to replace it with a file logger.
// //
// IMPORTANT: // IMPORTANT:
// The default API is thread safe (for _mt loggers), but: // The default API is thread safe (for _mt loggers), but:
// set_default_logger() *should not* be used concurrently with the default API. // set_default_logger() *should not* be used concurrently with the default API.
// e.g., do not call set_default_logger() from one thread while calling spdlog::info() from another. // e.g do not call set_default_logger() from one thread while calling spdlog::info() from another.
SPDLOG_API std::shared_ptr<spdlog::logger> default_logger(); SPDLOG_API std::shared_ptr<spdlog::logger> default_logger();

View File

@@ -109,8 +109,8 @@
// //
// #include <string_view> // #include <string_view>
// using namespace std::string_view_literals; // using namespace std::string_view_literals;
// #define SPDLOG_LEVEL_NAMES { "MY TRACE"sv, "MY DEBUG"sv, "MY INFO"sv, "MY WARNING"sv, "MY // #define SPDLOG_LEVEL_NAMES { "MY TRACE"sv, "MY DEBUG"sv, "MY INFO"sv, "MY WARNING"sv, "MY ERROR"sv, "MY
// ERROR"sv, "MY CRITICAL"sv, "OFF"sv } // CRITICAL"sv, "OFF"sv }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

View File

@@ -5,7 +5,7 @@
#define SPDLOG_VER_MAJOR 1 #define SPDLOG_VER_MAJOR 1
#define SPDLOG_VER_MINOR 15 #define SPDLOG_VER_MINOR 15
#define SPDLOG_VER_PATCH 3 #define SPDLOG_VER_PATCH 1
#define SPDLOG_TO_VERSION(major, minor, patch) (major * 10000 + minor * 100 + patch) #define SPDLOG_TO_VERSION(major, minor, patch) (major * 10000 + minor * 100 + patch)
#define SPDLOG_VERSION SPDLOG_TO_VERSION(SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, SPDLOG_VER_PATCH) #define SPDLOG_VERSION SPDLOG_TO_VERSION(SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, SPDLOG_VER_PATCH)

View File

@@ -13,32 +13,34 @@
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
template FMT_API auto dragonbox::to_decimal(float x) noexcept -> dragonbox::decimal_fp<float>; template FMT_API auto dragonbox::to_decimal(float x) noexcept
template FMT_API auto dragonbox::to_decimal(double x) noexcept -> dragonbox::decimal_fp<double>; -> dragonbox::decimal_fp<float>;
template FMT_API auto dragonbox::to_decimal(double x) noexcept
-> dragonbox::decimal_fp<double>;
#if FMT_USE_LOCALE #if FMT_USE_LOCALE
// DEPRECATED! locale_ref in the detail namespace // DEPRECATED! locale_ref in the detail namespace
template FMT_API locale_ref::locale_ref(const std::locale& loc); template FMT_API locale_ref::locale_ref(const std::locale& loc);
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale; template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif #endif
// Explicit instantiations for char. // Explicit instantiations for char.
template FMT_API auto thousands_sep_impl(locale_ref) -> thousands_sep_result<char>; template FMT_API auto thousands_sep_impl(locale_ref)
-> thousands_sep_result<char>;
template FMT_API auto decimal_point_impl(locale_ref) -> char; template FMT_API auto decimal_point_impl(locale_ref) -> char;
// DEPRECATED! // DEPRECATED!
template FMT_API void buffer<char>::append(const char*, const char*); template FMT_API void buffer<char>::append(const char*, const char*);
// DEPRECATED! // DEPRECATED!
template FMT_API void vformat_to(buffer<char>&, template FMT_API void vformat_to(buffer<char>&, string_view,
string_view, typename vformat_args<>::type, locale_ref);
typename vformat_args<>::type,
locale_ref);
// Explicit instantiations for wchar_t. // Explicit instantiations for wchar_t.
template FMT_API auto thousands_sep_impl(locale_ref) -> thousands_sep_result<wchar_t>; template FMT_API auto thousands_sep_impl(locale_ref)
-> thousands_sep_result<wchar_t>;
template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*); template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);
@@ -46,4 +48,5 @@ template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);
} // namespace detail } // namespace detail
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // !SPDLOG_FMT_EXTERNAL #endif // !SPDLOG_FMT_EXTERNAL

View File

@@ -19,9 +19,9 @@ if(Catch2_FOUND)
else() else()
message(STATUS "Bundled version of Catch will be downloaded and used.") message(STATUS "Bundled version of Catch will be downloaded and used.")
include(FetchContent) include(FetchContent)
FetchContent_Declare( FetchContent_Declare(Catch2
Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2.git GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG 53d0d913a422d356b23dd927547febdf69ee9081 # v3.5.0 GIT_TAG 53d0d913a422d356b23dd927547febdf69ee9081 # v3.5.0
) )
FetchContent_MakeAvailable(Catch2) FetchContent_MakeAvailable(Catch2)
endif() endif()
@@ -49,8 +49,7 @@ set(SPDLOG_UTESTS_SOURCES
test_time_point.cpp test_time_point.cpp
test_stopwatch.cpp test_stopwatch.cpp
test_circular_q.cpp test_circular_q.cpp
test_bin_to_hex.cpp test_bin_to_hex.cpp)
test_ringbuffer.cpp)
if(NOT SPDLOG_NO_EXCEPTIONS) if(NOT SPDLOG_NO_EXCEPTIONS)
list(APPEND SPDLOG_UTESTS_SOURCES test_errors.cpp) list(APPEND SPDLOG_UTESTS_SOURCES test_errors.cpp)
@@ -72,10 +71,10 @@ function(spdlog_prepare_test test_target spdlog_lib)
target_link_libraries(${test_target} PRIVATE Catch2::Catch2WithMain) target_link_libraries(${test_target} PRIVATE Catch2::Catch2WithMain)
if(SPDLOG_SANITIZE_ADDRESS) if(SPDLOG_SANITIZE_ADDRESS)
spdlog_enable_addr_sanitizer(${test_target}) spdlog_enable_addr_sanitizer(${test_target})
elseif(SPDLOG_SANITIZE_THREAD) elseif (SPDLOG_SANITIZE_THREAD)
spdlog_enable_thread_sanitizer(${test_target}) spdlog_enable_thread_sanitizer(${test_target})
endif() endif ()
add_test(NAME ${test_target} COMMAND ${test_target} --order decl) add_test(NAME ${test_target} COMMAND ${test_target})
set_tests_properties(${test_target} PROPERTIES RUN_SERIAL ON) set_tests_properties(${test_target} PROPERTIES RUN_SERIAL ON)
endfunction() endfunction()

View File

@@ -16,8 +16,7 @@ TEST_CASE("custom_callback_logger", "[custom_callback_logger]") {
spdlog::memory_buf_t formatted; spdlog::memory_buf_t formatted;
formatter.format(msg, formatted); formatter.format(msg, formatted);
auto eol_len = strlen(spdlog::details::os::default_eol); auto eol_len = strlen(spdlog::details::os::default_eol);
using diff_t = using diff_t = typename std::iterator_traits<decltype(formatted.end())>::difference_type;
typename std::iterator_traits<decltype(formatted.end())>::difference_type;
lines.emplace_back(formatted.begin(), formatted.end() - static_cast<diff_t>(eol_len)); lines.emplace_back(formatted.begin(), formatted.end() - static_cast<diff_t>(eol_len));
}); });
std::shared_ptr<spdlog::sinks::test_sink_st> test_sink(new spdlog::sinks::test_sink_st); std::shared_ptr<spdlog::sinks::test_sink_st> test_sink(new spdlog::sinks::test_sink_st);

View File

@@ -46,8 +46,10 @@ TEST_CASE("daily_logger with dateonly calculator", "[daily_logger]") {
struct custom_daily_file_name_calculator { 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) {
return spdlog::fmt_lib::format(SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"), basename,
now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday); return spdlog::fmt_lib::format(SPDLOG_FILENAME_T("{}{:04d}{:02d}{:02d}"),
basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1,
now_tm.tm_mday);
} }
}; };

View File

@@ -56,7 +56,7 @@ TEST_CASE("simple_file_logger", "[truncate]") {
logger->info("Test message {}", 2.71); logger->info("Test message {}", 2.71);
logger->flush(); logger->flush();
REQUIRE(count_lines(SIMPLE_LOG) == 2); REQUIRE(count_lines(SIMPLE_LOG) == 2);
sink->truncate(); sink->truncate();
REQUIRE(count_lines(SIMPLE_LOG) == 0); REQUIRE(count_lines(SIMPLE_LOG) == 0);
@@ -94,11 +94,14 @@ TEST_CASE("rotating_file_logger2", "[rotating_logger]") {
// next logger can rename the first output file. // next logger can rename the first output file.
spdlog::drop(logger->name()); spdlog::drop(logger->name());
} }
auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 2, true); auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 2, true);
for (int i = 0; i < 10; ++i) { for (int i = 0; i < 10; ++i) {
logger->info("Test message {}", i); logger->info("Test message {}", i);
} }
logger->flush(); logger->flush();
require_message_count(ROTATING_LOG, 10); require_message_count(ROTATING_LOG, 10);
for (int i = 0; i < 1000; i++) { for (int i = 0; i < 1000; i++) {
@@ -138,50 +141,3 @@ TEST_CASE("rotating_file_logger4", "[rotating_logger]") {
REQUIRE(get_filesize(ROTATING_LOG) > 0); REQUIRE(get_filesize(ROTATING_LOG) > 0);
REQUIRE(get_filesize(ROTATING_LOG ".1") > 0); REQUIRE(get_filesize(ROTATING_LOG ".1") > 0);
} }
// test changing the max size of the rotating file sink
TEST_CASE("rotating_file_logger5", "[rotating_logger]") {
prepare_logdir();
size_t max_size = 5 * 1024;
size_t max_files = 2;
spdlog::filename_t basename = SPDLOG_FILENAME_T(ROTATING_LOG);
auto sink =
std::make_shared<spdlog::sinks::rotating_file_sink_st>(basename, max_size, max_files);
auto logger = std::make_shared<spdlog::logger>("rotating_sink_logger", sink);
logger->set_pattern("%v");
REQUIRE(sink->get_max_size() == max_size);
REQUIRE(sink->get_max_files() == max_files);
max_size = 7 * 1024;
max_files = 3;
sink->set_max_size(max_size);
sink->set_max_files(max_files);
REQUIRE(sink->get_max_size() == max_size);
REQUIRE(sink->get_max_files() == max_files);
const auto message = std::string(200, 'x');
assert(message.size() < max_size);
const auto n_messages = max_files * max_size / message.size();
for (size_t i = 0; i < n_messages; ++i) {
logger->info(message);
}
logger.reset(); // force flush and close the file
// validate that the files were rotated correctly with the new max size and max files
for (size_t i = 0; i <= max_files; i++) {
// calc filenames
// e.g. rotating_log, rotating_log.0 rotating_log.1, rotating_log.2, etc.
std::ostringstream oss;
oss << ROTATING_LOG;
if (i > 0) {
oss << '.' << i;
}
const auto filename = oss.str();
const auto filesize = get_filesize(filename);
REQUIRE(filesize <= max_size);
if (i > 0) {
REQUIRE(filesize >= max_size - message.size() - 2);
}
}
}

View File

@@ -1,12 +1,12 @@
#ifdef _WIN32 // to prevent fopen warning on windows #ifdef _WIN32 // to prevent fopen warning on windows
#define _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS
#endif #endif
#include "includes.h" #include "includes.h"
#include "test_sink.h" #include "test_sink.h"
template <class T> template <class T>
std::string log_info(const T& what, spdlog::level::level_enum logger_level = spdlog::level::info) { std::string log_info(const T &what, spdlog::level::level_enum logger_level = spdlog::level::info) {
std::ostringstream oss; std::ostringstream oss;
auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss); auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss);
@@ -182,19 +182,17 @@ TEST_CASE("utf8 to utf16 conversion using windows api", "[windows utf]") {
spdlog::details::os::utf8_to_wstrbuf("abc", buffer); spdlog::details::os::utf8_to_wstrbuf("abc", buffer);
REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"abc")); REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"abc"));
spdlog::details::os::utf8_to_wstrbuf("\xc3\x28", buffer); // Invalid UTF-8 sequence. spdlog::details::os::utf8_to_wstrbuf("\xc3\x28", buffer); // Invalid UTF-8 sequence.
REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"\xfffd(")); REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"\xfffd("));
spdlog::details::os::utf8_to_wstrbuf("\xe3\x81\xad\xe3\x81\x93", spdlog::details::os::utf8_to_wstrbuf("\xe3\x81\xad\xe3\x81\x93", buffer); // "Neko" in hiragana.
buffer); // "Neko" in hiragana.
REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"\x306d\x3053")); REQUIRE(std::wstring(buffer.data(), buffer.size()) == std::wstring(L"\x306d\x3053"));
} }
#endif #endif
struct auto_closer { struct auto_closer {
FILE* fp = nullptr; FILE* fp = nullptr;
explicit auto_closer(FILE* f) explicit auto_closer(FILE* f) : fp(f) {}
: fp(f) {}
auto_closer(const auto_closer&) = delete; auto_closer(const auto_closer&) = delete;
auto_closer& operator=(const auto_closer&) = delete; auto_closer& operator=(const auto_closer&) = delete;
~auto_closer() { ~auto_closer() {
@@ -203,10 +201,10 @@ struct auto_closer {
}; };
TEST_CASE("os::fwrite_bytes", "[os]") { TEST_CASE("os::fwrite_bytes", "[os]") {
using spdlog::details::os::create_dir;
using spdlog::details::os::fwrite_bytes; using spdlog::details::os::fwrite_bytes;
using spdlog::details::os::create_dir;
const char* filename = "log_tests/test_fwrite_bytes.txt"; const char* filename = "log_tests/test_fwrite_bytes.txt";
const char* msg = "hello"; const char *msg = "hello";
prepare_logdir(); prepare_logdir();
REQUIRE(create_dir(SPDLOG_FILENAME_T("log_tests")) == true); REQUIRE(create_dir(SPDLOG_FILENAME_T("log_tests")) == true);
{ {

View File

@@ -23,9 +23,7 @@ static std::string log_to_str(const std::string &msg, const Args &...args) {
// log to str and return it with time // log to str and return it with time
template <typename... Args> template <typename... Args>
static std::string log_to_str_with_time(spdlog::log_clock::time_point log_time, static std::string log_to_str_with_time(spdlog::log_clock::time_point log_time, const std::string &msg, const Args &...args) {
const std::string &msg,
const Args &...args) {
std::ostringstream oss; std::ostringstream oss;
auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss); auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss);
spdlog::logger oss_logger("pattern_tester", oss_sink); spdlog::logger oss_logger("pattern_tester", oss_sink);
@@ -82,8 +80,8 @@ TEST_CASE("GMT offset ", "[pattern_formatter]") {
const auto now = std::chrono::system_clock::now(); const auto now = std::chrono::system_clock::now();
const auto yesterday = now - 24h; const auto yesterday = now - 24h;
REQUIRE(log_to_str_with_time(yesterday, "Some message", "%z", spdlog::pattern_time_type::utc, REQUIRE(log_to_str_with_time(yesterday, "Some message", "%z", spdlog::pattern_time_type::utc, "\n") ==
"\n") == "+00:00\n"); "+00:00\n");
} }
TEST_CASE("color range test1", "[pattern_formatter]") { TEST_CASE("color range test1", "[pattern_formatter]") {

View File

@@ -25,19 +25,6 @@ TEST_CASE("explicit register", "[registry]") {
} }
#endif #endif
TEST_CASE("register_or_replace", "[registry]") {
spdlog::drop_all();
auto logger1 = std::make_shared<spdlog::logger>(
tested_logger_name, std::make_shared<spdlog::sinks::null_sink_st>());
spdlog::register_logger(logger1);
REQUIRE(spdlog::get(tested_logger_name) == logger1);
auto logger2 = std::make_shared<spdlog::logger>(
tested_logger_name, std::make_shared<spdlog::sinks::null_sink_st>());
spdlog::register_or_replace(logger2);
REQUIRE(spdlog::get(tested_logger_name) == logger2);
}
TEST_CASE("apply_all", "[registry]") { TEST_CASE("apply_all", "[registry]") {
spdlog::drop_all(); spdlog::drop_all();
auto logger = std::make_shared<spdlog::logger>(tested_logger_name, auto logger = std::make_shared<spdlog::logger>(tested_logger_name,

View File

@@ -1,53 +0,0 @@
#include "includes.h"
#include "spdlog/sinks/ringbuffer_sink.h"
TEST_CASE("ringbuffer invalid size", "[ringbuffer]") {
REQUIRE_THROWS_AS(spdlog::sinks::ringbuffer_sink_mt(0), spdlog::spdlog_ex);
}
TEST_CASE("ringbuffer stores formatted messages", "[ringbuffer]") {
spdlog::sinks::ringbuffer_sink_st sink(3);
sink.set_pattern("%v");
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "msg1"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "msg2"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "msg3"});
auto formatted = sink.last_formatted();
REQUIRE(formatted.size() == 3);
using spdlog::details::os::default_eol;
REQUIRE(formatted[0] == spdlog::fmt_lib::format("msg1{}", default_eol));
REQUIRE(formatted[1] == spdlog::fmt_lib::format("msg2{}", default_eol));
REQUIRE(formatted[2] == spdlog::fmt_lib::format("msg3{}", default_eol));
}
TEST_CASE("ringbuffer overrun keeps last items", "[ringbuffer]") {
spdlog::sinks::ringbuffer_sink_st sink(2);
sink.set_pattern("%v");
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "first"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "second"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "third"});
auto formatted = sink.last_formatted();
REQUIRE(formatted.size() == 2);
using spdlog::details::os::default_eol;
REQUIRE(formatted[0] == spdlog::fmt_lib::format("second{}", default_eol));
REQUIRE(formatted[1] == spdlog::fmt_lib::format("third{}", default_eol));
}
TEST_CASE("ringbuffer retrieval limit", "[ringbuffer]") {
spdlog::sinks::ringbuffer_sink_st sink(3);
sink.set_pattern("%v");
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "A"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "B"});
sink.log(spdlog::details::log_msg{"test", spdlog::level::info, "C"});
auto formatted = sink.last_formatted(2);
REQUIRE(formatted.size() == 2);
using spdlog::details::os::default_eol;
REQUIRE(formatted[0] == spdlog::fmt_lib::format("B{}", default_eol));
REQUIRE(formatted[1] == spdlog::fmt_lib::format("C{}", default_eol));
}

View File

@@ -47,7 +47,7 @@ protected:
base_sink<Mutex>::formatter_->format(msg, formatted); base_sink<Mutex>::formatter_->format(msg, formatted);
// save the line without the eol // save the line without the eol
auto eol_len = strlen(details::os::default_eol); auto eol_len = strlen(details::os::default_eol);
using diff_t = typename std::iterator_traits<decltype(formatted.end())>::difference_type; using diff_t = typename std::iterator_traits<decltype(formatted.end())>::difference_type;
if (lines_.size() < lines_to_save) { if (lines_.size() < lines_to_save) {
lines_.emplace_back(formatted.begin(), formatted.end() - static_cast<diff_t>(eol_len)); lines_.emplace_back(formatted.begin(), formatted.end() - static_cast<diff_t>(eol_len));
} }

View File

@@ -5,7 +5,7 @@
TEST_CASE("stopwatch1", "[stopwatch]") { TEST_CASE("stopwatch1", "[stopwatch]") {
using std::chrono::milliseconds; using std::chrono::milliseconds;
using clock = std::chrono::steady_clock; using clock = std::chrono::steady_clock;
milliseconds wait_ms(500); milliseconds wait_ms(200);
milliseconds tolerance_ms(250); milliseconds tolerance_ms(250);
auto start = clock::now(); auto start = clock::now();
spdlog::stopwatch sw; spdlog::stopwatch sw;
@@ -22,7 +22,7 @@ TEST_CASE("stopwatch2", "[stopwatch]") {
using std::chrono::milliseconds; using std::chrono::milliseconds;
using clock = std::chrono::steady_clock; using clock = std::chrono::steady_clock;
clock::duration wait_duration(milliseconds(500)); clock::duration wait_duration(milliseconds(200));
clock::duration tolerance_duration(milliseconds(250)); clock::duration tolerance_duration(milliseconds(250));
auto test_sink = std::make_shared<test_sink_st>(); auto test_sink = std::make_shared<test_sink_st>();

View File

@@ -50,8 +50,9 @@ void require_message_count(const std::string &filename, const std::size_t messag
std::size_t get_filesize(const std::string &filename) { std::size_t get_filesize(const std::string &filename) {
std::ifstream ifs(filename, std::ifstream::ate | std::ifstream::binary); std::ifstream ifs(filename, std::ifstream::ate | std::ifstream::binary);
if (!ifs) { if (!ifs) {
throw std::runtime_error("Failed open file " + filename); throw std::runtime_error("Failed open file ");
} }
return static_cast<std::size_t>(ifs.tellg()); return static_cast<std::size_t>(ifs.tellg());
} }