Merge remote-tracking branch 'origin/v1.x' into pwm1234/rotate_on_open

This commit is contained in:
Philip Miller 2019-01-11 10:04:42 -05:00
commit 4fe98bf6e6
77 changed files with 9788 additions and 8202 deletions

View File

@ -88,7 +88,7 @@ PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60 PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right PointerAlignment: Right
ReflowComments: true ReflowComments: true
SortIncludes: true SortIncludes: false
SortUsingDeclarations: true SortUsingDeclarations: true
SpaceAfterCStyleCast: false SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: false SpaceAfterTemplateKeyword: false

28
.clang-tidy Normal file
View File

@ -0,0 +1,28 @@
Checks: 'modernize-*,modernize-use-override,google-*,-google-runtime-references,misc-*,clang-analyzer-*'
WarningsAsErrors: ''
HeaderFilterRegex: 'async.h|async_logger.h|common.h|details|formatter.h|logger.h|sinks|spdlog.h|tweakme.h|version.h'
AnalyzeTemporaryDtors: false
FormatStyle: none
CheckOptions:
- key: google-readability-braces-around-statements.ShortStatementLines
value: '1'
- key: google-readability-function-size.StatementThreshold
value: '800'
- key: google-readability-namespace-comments.ShortNamespaceLines
value: '10'
- key: google-readability-namespace-comments.SpacesBeforeComments
value: '2'
- key: modernize-loop-convert.MaxCopySize
value: '16'
- key: modernize-loop-convert.MinConfidence
value: reasonable
- key: modernize-loop-convert.NamingStyle
value: CamelCase
- key: modernize-pass-by-value.IncludeStyle
value: llvm
- key: modernize-replace-auto-ptr.IncludeStyle
value: llvm
- key: modernize-use-nullptr.NullMacros
value: 'NULL'

View File

@ -104,6 +104,7 @@ script:
-DCMAKE_CXX_STANDARD=$CPP \ -DCMAKE_CXX_STANDARD=$CPP \
-DSPDLOG_BUILD_EXAMPLES=ON \ -DSPDLOG_BUILD_EXAMPLES=ON \
-DSPDLOG_BUILD_BENCH=OFF \ -DSPDLOG_BUILD_BENCH=OFF \
-DSPDLOG_BUILD_TESTS=ON \
-DSPDLOG_SANITIZE_ADDRESS=$ASAN \ -DSPDLOG_SANITIZE_ADDRESS=$ASAN \
-DSPDLOG_SANITIZE_THREAD=$TSAN -DSPDLOG_SANITIZE_THREAD=$TSAN
- make VERBOSE=1 -j2 - make VERBOSE=1 -j2

View File

@ -4,8 +4,7 @@
# #
cmake_minimum_required(VERSION 3.1) cmake_minimum_required(VERSION 3.1)
project(spdlog VERSION 1.1.0 LANGUAGES CXX) project(spdlog VERSION 1.3.0 LANGUAGES CXX)
include(CTest)
include(CMakeDependentOption) include(CMakeDependentOption)
include(GNUInstallDirs) include(GNUInstallDirs)
@ -45,13 +44,20 @@ include(cmake/sanitizers.cmake)
add_library(spdlog INTERFACE) add_library(spdlog INTERFACE)
add_library(spdlog::spdlog ALIAS spdlog) add_library(spdlog::spdlog ALIAS spdlog)
option(SPDLOG_BUILD_EXAMPLES "Build examples" ON) # Check if spdlog is being used directly or via add_subdirectory
option(SPDLOG_BUILD_BENCH "Build benchmarks" ON) set(SPDLOG_MASTER_PROJECT OFF)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set(SPDLOG_MASTER_PROJECT ON)
endif()
cmake_dependent_option(SPDLOG_BUILD_TESTING option(SPDLOG_BUILD_EXAMPLES "Build examples" ${SPDLOG_MASTER_PROJECT})
"Build spdlog tests" ON option(SPDLOG_BUILD_BENCH "Build benchmarks" ${SPDLOG_MASTER_PROJECT})
"BUILD_TESTING" OFF option(SPDLOG_BUILD_TESTS "Build tests" ${SPDLOG_MASTER_PROJECT})
) option(SPDLOG_FMT_EXTERNAL "Use external fmt library instead of bundled" OFF)
if(SPDLOG_FMT_EXTERNAL)
find_package(fmt REQUIRED CONFIG)
endif()
target_include_directories( target_include_directories(
spdlog spdlog
@ -60,13 +66,19 @@ target_include_directories(
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
) )
if(SPDLOG_FMT_EXTERNAL)
target_compile_definitions(spdlog INTERFACE SPDLOG_FMT_EXTERNAL)
target_link_libraries(spdlog INTERFACE fmt::fmt)
endif()
set(HEADER_BASE "${CMAKE_CURRENT_SOURCE_DIR}/include") set(HEADER_BASE "${CMAKE_CURRENT_SOURCE_DIR}/include")
if(SPDLOG_BUILD_EXAMPLES) if(SPDLOG_BUILD_EXAMPLES)
add_subdirectory(example) add_subdirectory(example)
endif() endif()
if(SPDLOG_BUILD_TESTING) if(SPDLOG_BUILD_TESTS)
include(CTest)
add_subdirectory(tests) add_subdirectory(tests)
endif() endif()
@ -82,7 +94,8 @@ set(config_install_dir "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
set(include_install_dir "${CMAKE_INSTALL_INCLUDEDIR}") set(include_install_dir "${CMAKE_INSTALL_INCLUDEDIR}")
set(pkgconfig_install_dir "${CMAKE_INSTALL_LIBDIR}/pkgconfig") set(pkgconfig_install_dir "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
set(version_config "${CMAKE_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake") set(version_config "${CMAKE_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake")
set(project_config "${PROJECT_NAME}Config.cmake") set(project_config "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Config.cmake")
set(targets_config "${PROJECT_NAME}Targets.cmake")
set(pkg_config "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc") set(pkg_config "${CMAKE_BINARY_DIR}/${PROJECT_NAME}.pc")
set(targets_export_name "${PROJECT_NAME}Targets") set(targets_export_name "${PROJECT_NAME}Targets")
set(namespace "${PROJECT_NAME}::") set(namespace "${PROJECT_NAME}::")
@ -95,6 +108,8 @@ write_basic_package_version_file(
# configure pkg config file # configure pkg config file
configure_file("cmake/spdlog.pc.in" "${pkg_config}" @ONLY) configure_file("cmake/spdlog.pc.in" "${pkg_config}" @ONLY)
# configure spdlogConfig.cmake file
configure_file("cmake/Config.cmake.in" "${project_config}" @ONLY)
# install targets # install targets
install( install(
@ -108,9 +123,9 @@ install(
DESTINATION "${include_install_dir}" DESTINATION "${include_install_dir}"
) )
# install project version file # install project config and version file
install( install(
FILES "${version_config}" FILES "${project_config}" "${version_config}"
DESTINATION "${config_install_dir}" DESTINATION "${config_install_dir}"
) )
@ -120,19 +135,19 @@ install(
DESTINATION "${pkgconfig_install_dir}" DESTINATION "${pkgconfig_install_dir}"
) )
# install project config file # install targets config file
install( install(
EXPORT "${targets_export_name}" EXPORT "${targets_export_name}"
NAMESPACE "${namespace}" NAMESPACE "${namespace}"
DESTINATION "${config_install_dir}" DESTINATION "${config_install_dir}"
FILE ${project_config} FILE ${targets_config}
) )
# export build directory config file # export build directory targets file
export( export(
EXPORT ${targets_export_name} EXPORT ${targets_export_name}
NAMESPACE "${namespace}" NAMESPACE "${namespace}"
FILE ${project_config} FILE ${targets_config}
) )
# register project in CMake user registry # register project in CMake user registry

122
README.md
View File

@ -21,18 +21,17 @@ Very fast, header only, C++ logging library. [![Build Status](https://travis-ci.
## Platforms ## Platforms
* Linux, FreeBSD, Solaris, AIX * Linux, FreeBSD, OpenBSD, Solaris, AIX
* Windows (vc 2013+, cygwin) * Windows (msvc 2013+, cygwin)
* Mac OSX (clang 3.5+) * macOS (clang 3.5+)
* Android * Android
## Features ## Features
* Very fast - performance is the primary goal (see [benchmarks](#benchmarks) below). * Very fast (see [benchmarks](#benchmarks) below).
* Headers only, just copy and use. * Headers only, just copy and use.
* Feature rich using the excellent [fmt](https://github.com/fmtlib/fmt) library. * Feature rich formatting, using the excellent [fmt](https://github.com/fmtlib/fmt) library.
* Fast asynchronous mode (optional) * Fast asynchronous mode (optional)
* [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting. * [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting.
* Conditional Logging
* Multi/Single threaded loggers. * Multi/Single threaded loggers.
* Various log targets: * Various log targets:
* Rotating log files. * Rotating log files.
@ -42,7 +41,7 @@ Very fast, header only, C++ logging library. [![Build Status](https://travis-ci.
* Windows debugger (```OutputDebugString(..)```) * Windows debugger (```OutputDebugString(..)```)
* Easily extendable with custom log targets (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface). * Easily extendable with custom log targets (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface).
* Severity based filtering - threshold levels can be modified in runtime as well as in compile time. * Severity based filtering - threshold levels can be modified in runtime as well as in compile time.
* Binary data logging.
## Benchmarks ## Benchmarks
@ -54,32 +53,58 @@ Below are some [benchmarks](https://github.com/gabime/spdlog/blob/v1.x/bench/ben
******************************************************************************* *******************************************************************************
Single thread, 1,000,000 iterations Single thread, 1,000,000 iterations
******************************************************************************* *******************************************************************************
basic_st... Elapsed: 0.226664 4,411,806/sec basic_st... Elapsed: 0.181652 5,505,042/sec
rotating_st... Elapsed: 0.214339 4,665,499/sec rotating_st... Elapsed: 0.181781 5,501,117/sec
daily_st... Elapsed: 0.211292 4,732,797/sec daily_st... Elapsed: 0.187595 5,330,630/sec
null_st... Elapsed: 0.102815 9,726,227/sec null_st... Elapsed: 0.0504704 19,813,602/sec
******************************************************************************* *******************************************************************************
10 threads sharing same logger, 1,000,000 iterations 10 threads sharing same logger, 1,000,000 iterations
******************************************************************************* *******************************************************************************
basic_mt... Elapsed: 0.882268 1,133,441/sec basic_mt... Elapsed: 0.616035 1,623,284/sec
rotating_mt... Elapsed: 0.875515 1,142,184/sec rotating_mt... Elapsed: 0.620344 1,612,008/sec
daily_mt... Elapsed: 0.879573 1,136,915/sec daily_mt... Elapsed: 0.648353 1,542,369/sec
null_mt... Elapsed: 0.220114 4,543,105/sec null_mt... Elapsed: 0.151972 6,580,166/sec
``` ```
#### Asynchronous mode #### Asynchronous mode
``` ```
******************************************************************************* *******************************************************************************
10 threads sharing same logger, 1,000,000 iterations 10 threads sharing same logger, 1,000,000 iterations
******************************************************************************* *******************************************************************************
async... Elapsed: 0.429088 2,330,524/sec async... Elapsed: 0.350066 2,856,606/sec
async... Elapsed: 0.411501 2,430,126/sec async... Elapsed: 0.314865 3,175,960/sec
async... Elapsed: 0.428979 2,331,116/sec async... Elapsed: 0.349851 2,858,358/sec
``` ```
## Usage samples ## Usage samples
#### Basic usage
```c++
#include "spdlog/spdlog.h"
int main()
{
spdlog::info("Welcome to spdlog!");
spdlog::error("Some error message with arg: {}", 1);
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("{:<30}", "left aligned");
spdlog::set_level(spdlog::level::debug/ Set global log level to debug
spdlog::debug("This message should be displayed..");
// change log pattern
spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v");
// Compile time log levels
// define SPDLOG_ACTIVE_LEVEL to desired level
SPDLOG_TRACE("Some trace message with param {}", {});
SPDLOG_DEBUG("Some debug message");
}
```
#### create stdout/stderr logger object
```c++ ```c++
#include "spdlog/spdlog.h" #include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h" #include "spdlog/sinks/stdout_color_sinks.h"
@ -87,35 +112,8 @@ void stdout_example()
{ {
// create color multi threaded logger // create color multi threaded logger
auto console = spdlog::stdout_color_mt("console"); auto console = spdlog::stdout_color_mt("console");
console->info("Welcome to spdlog!");
console->error("Some error message with arg: {}", 1);
auto err_logger = spdlog::stderr_color_mt("stderr"); auto err_logger = spdlog::stderr_color_mt("stderr");
err_logger->error("Some error message");
// Formatting examples
console->warn("Easy padding in numbers like {:08d}", 12);
console->critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
console->info("Support for floats {:03.2f}", 1.23456);
console->info("Positional args are {1} {0}..", "too", "supported");
console->info("{:<30}", "left aligned");
spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)"); spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)");
// Runtime log levels
spdlog::set_level(spdlog::level::info); // Set global log level to info
console->debug("This message should not be displayed!");
console->set_level(spdlog::level::trace); // Set specific logger's log level
console->debug("This message should be displayed..");
// Customize msg format for all loggers
spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v");
console->info("This an info message with custom format");
// Compile time log levels
// define SPDLOG_DEBUG_ON or SPDLOG_TRACE_ON
SPDLOG_TRACE(console, "Enabled only #ifdef SPDLOG_TRACE_ON..{} ,{}", 1, 3.23);
SPDLOG_DEBUG(console, "Enabled only #ifdef SPDLOG_DEBUG_ON.. {} ,{}", 1, 3.23);
} }
``` ```
--- ---
@ -131,7 +129,6 @@ void basic_logfile_example()
catch (const spdlog::spdlog_ex &ex) catch (const spdlog::spdlog_ex &ex)
{ {
std::cout << "Log init failed: " << ex.what() << std::endl; std::cout << "Log init failed: " << ex.what() << std::endl;
return 1;
} }
} }
``` ```
@ -180,6 +177,33 @@ spdlog::flush_every(std::chrono::seconds(3));
``` ```
---
#### Binary logging
```c++
// log binary data as hex.
// many types of std::container<char> types can be used.
// ranges are supported too.
// format flags:
// {:X} - print in uppercase.
// {:s} - don't separate each byte with space.
// {:p} - don't print the position on each line start.
// {:n} - don't split the output to lines.
#include "spdlog/fmt/bin_to_hex.h"
void binary_example()
{
auto console = spdlog::get("console");
std::array<char, 80> buf;
console->info("Binary example: {}", spdlog::to_hex(buf));
console->info("Another binary example:{:n}", spdlog::to_hex(std::begin(buf), std::begin(buf) + 10));
// more examples:
// logger->info("uppercase: {:X}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf));
}
```
--- ---
#### Logger with multi sinks - each with different format and log level #### Logger with multi sinks - each with different format and log level
@ -282,7 +306,7 @@ void syslog_example()
--- ---
#### Android example #### Android example
```c++ ```c++
#incude "spdlog/sinks/android_sink.h" #include "spdlog/sinks/android_sink.h"
void android_example() void android_example()
{ {
std::string tag = "spdlog-android"; std::string tag = "spdlog-android";

View File

@ -26,7 +26,9 @@ build_script:
set PATH=C:\mingw-w64\i686-5.3.0-posix-dwarf-rt_v4-rev0\mingw32\bin;%PATH% set PATH=C:\mingw-w64\i686-5.3.0-posix-dwarf-rt_v4-rev0\mingw32\bin;%PATH%
cmake .. -G %GENERATOR% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% cmake .. -G %GENERATOR% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DSPDLOG_BUILD_BENCH=OFF
cmake --build . --config %BUILD_TYPE% cmake --build . --config %BUILD_TYPE%
test: off
test_script:
- ctest -VV -C "%BUILD_TYPE%"

View File

@ -38,6 +38,12 @@ add_executable(async_bench async_bench.cpp)
target_link_libraries(async_bench spdlog::spdlog Threads::Threads) target_link_libraries(async_bench spdlog::spdlog Threads::Threads)
add_executable(latency latency.cpp) add_executable(latency latency.cpp)
set(CMAKE_CXX_STANDARD_LIBRARIES -lbenchmark)
target_link_libraries(latency spdlog::spdlog Threads::Threads) target_link_libraries(latency spdlog::spdlog Threads::Threads)
add_executable(formatter-bench formatter-bench.cpp)
set(CMAKE_CXX_STANDARD_LIBRARIES -lbenchmark)
target_link_libraries(formatter-bench spdlog::spdlog Threads::Threads)
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/logs") file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/logs")

View File

@ -1,9 +1,9 @@
CXX ?= g++ CXX ?= g++
CXXFLAGS = -march=native -Wall -Wextra -pedantic -std=c++11 -pthread -I../include -fmax-errors=1 CXXFLAGS = -march=native -Wall -Wextra -pedantic -Wconversion -std=c++11 -pthread -I../include -fmax-errors=1
CXX_RELEASE_FLAGS = -Ofast -flto -Wl,--no-as-needed CXX_RELEASE_FLAGS = -O3 -flto -Wl,--no-as-needed
binaries=bench latency async_bench binaries=bench async_bench latency formatter-bench
all: $(binaries) all: $(binaries)
@ -16,12 +16,16 @@ async_bench: async_bench.cpp
latency: latency.cpp latency: latency.cpp
$(CXX) latency.cpp -o latency $(CXXFLAGS) $(CXX_RELEASE_FLAGS) $(CXX) latency.cpp -o latency $(CXXFLAGS) $(CXX_RELEASE_FLAGS) -lbenchmark
formatter-bench: formatter-bench.cpp
$(CXX) formatter-bench.cpp -o formatter-bench $(CXXFLAGS) $(CXX_RELEASE_FLAGS) -lbenchmark
.PHONY: clean .PHONY: clean
clean: clean:
rm -f *.o logs/* $(binaries) rm -f *.o logs/* latecy_logs $(binaries)
rebuild: clean all rebuild: clean all

View File

@ -6,10 +6,11 @@
// //
// bench.cpp : spdlog benchmarks // bench.cpp : spdlog benchmarks
// //
#include "spdlog/spdlog.h"
#include "spdlog/async.h" #include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h" #include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/stdout_color_sinks.h" #include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/spdlog.h"
#include "utils.h" #include "utils.h"
#include <atomic> #include <atomic>
#include <iostream> #include <iostream>
@ -35,6 +36,7 @@ int count_lines(const char *filename)
if ('\n' == ch) if ('\n' == ch)
counter++; counter++;
} }
fclose(infile);
return counter; return counter;
} }
@ -48,11 +50,10 @@ int main(int argc, char *argv[])
try try
{ {
auto console = spdlog::stdout_color_mt("console");
if (argc == 1) if (argc == 1)
{ {
console->set_pattern("%v"); spdlog::set_pattern("%v");
console->info("Usage: {} <message_count> <threads> <q_size> <iterations>", argv[0]); spdlog::info("Usage: {} <message_count> <threads> <q_size> <iterations>", argv[0]);
return 0; return 0;
} }
@ -66,12 +67,12 @@ int main(int argc, char *argv[])
if (argc > 4) if (argc > 4)
iters = atoi(argv[4]); iters = atoi(argv[4]);
console->info("-------------------------------------------------"); spdlog::info("-------------------------------------------------");
console->info("Messages: {:14n}", howmany); spdlog::info("Messages: {:14n}", howmany);
console->info("Threads : {:14n}", threads); spdlog::info("Threads : {:14n}", threads);
console->info("Queue : {:14n}", queue_size); spdlog::info("Queue : {:14n}", queue_size);
console->info("Iters : {:>14n}", iters); spdlog::info("Iters : {:>14n}", iters);
console->info("-------------------------------------------------"); spdlog::info("-------------------------------------------------");
const char *filename = "logs/basic_async.log"; const char *filename = "logs/basic_async.log";
@ -85,14 +86,15 @@ int main(int argc, char *argv[])
if (count != howmany) if (count != howmany)
{ {
console->error("Test failed. {} has {:n} lines instead of {:n}", filename, count, howmany); spdlog::error("Test failed. {} has {:n} lines instead of {:n}", filename, count, howmany);
exit(1); exit(1);
} }
else else
{ {
console->info("Line count OK ({:n})\n", count); spdlog::info("Line count OK ({:n})\n", count);
} }
} }
spdlog::shutdown();
} }
catch (std::exception &ex) catch (std::exception &ex)
{ {
@ -100,6 +102,7 @@ int main(int argc, char *argv[])
perror("Last error"); perror("Last error");
return 1; return 1;
} }
return 0; return 0;
} }
@ -134,5 +137,5 @@ void bench_mt(int howmany, std::shared_ptr<spdlog::logger> logger, int thread_co
auto delta = high_resolution_clock::now() - start; auto delta = high_resolution_clock::now() - start;
auto delta_d = duration_cast<duration<double>>(delta).count(); auto delta_d = duration_cast<duration<double>>(delta).count();
spdlog::get("console")->info("Elapsed: {} secs\t {:n}/sec", delta_d, int(howmany / delta_d)); spdlog::info("Elapsed: {} secs\t {:n}/sec", delta_d, int(howmany / delta_d));
} }

View File

@ -6,16 +6,16 @@
// //
// bench.cpp : spdlog benchmarks // bench.cpp : spdlog benchmarks
// //
#include "spdlog/spdlog.h"
#include "spdlog/async.h" #include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h" #include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/daily_file_sink.h" #include "spdlog/sinks/daily_file_sink.h"
#include "spdlog/sinks/null_sink.h" #include "spdlog/sinks/null_sink.h"
#include "spdlog/sinks/rotating_file_sink.h" #include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/spdlog.h"
#include "utils.h" #include "utils.h"
#include <atomic> #include <atomic>
#include <cstdlib> // EXIT_FAILURE #include <cstdlib> // EXIT_FAILURE
#include <iostream>
#include <memory> #include <memory>
#include <string> #include <string>
#include <thread> #include <thread>
@ -28,10 +28,13 @@ using namespace utils;
void bench(int howmany, std::shared_ptr<spdlog::logger> log); void bench(int howmany, std::shared_ptr<spdlog::logger> log);
void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count); void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count);
void bench_default_api(int howmany, std::shared_ptr<spdlog::logger> log);
void bench_c_string(int howmany, std::shared_ptr<spdlog::logger> log);
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
spdlog::default_logger()->set_pattern("[%^%l%$] %v");
int howmany = 1000000; int howmany = 1000000;
int queue_size = howmany + 2; int queue_size = howmany + 2;
int threads = 10; int threads = 10;
@ -48,57 +51,65 @@ int main(int argc, char *argv[])
if (argc > 3) if (argc > 3)
queue_size = atoi(argv[3]); queue_size = atoi(argv[3]);
cout << "******************************************************************" spdlog::info("**************************************************************");
"*************\n"; spdlog::info("Single thread, {:n} iterations", howmany);
cout << "Single thread, " << format(howmany) << " iterations" << endl; spdlog::info("**************************************************************");
cout << "******************************************************************"
"*************\n";
auto basic_st = spdlog::basic_logger_mt("basic_st", "logs/basic_st.log", true); auto basic_st = spdlog::basic_logger_st("basic_st", "logs/basic_st.log", true);
bench(howmany, basic_st); bench(howmany, std::move(basic_st));
basic_st.reset();
auto rotating_st = spdlog::rotating_logger_st("rotating_st", "logs/rotating_st.log", file_size, rotating_files); auto rotating_st = spdlog::rotating_logger_st("rotating_st", "logs/rotating_st.log", file_size, rotating_files);
bench(howmany, rotating_st); bench(howmany, std::move(rotating_st));
auto daily_st = spdlog::daily_logger_st("daily_st", "logs/daily_st.log"); auto daily_st = spdlog::daily_logger_st("daily_st", "logs/daily_st.log");
bench(howmany, daily_st); bench(howmany, std::move(daily_st));
bench(howmany, spdlog::create<null_sink_st>("null_st")); bench(howmany, spdlog::create<null_sink_st>("null_st"));
cout << "\n****************************************************************" spdlog::info("**************************************************************");
"***************\n"; spdlog::info("C-string (400 bytes). Single thread, {:n} iterations", howmany);
cout << threads << " threads sharing same logger, " << format(howmany) << " iterations" << endl; spdlog::info("**************************************************************");
cout << "******************************************************************"
"*************\n"; basic_st = spdlog::basic_logger_st("basic_st", "logs/basic_cs.log", true);
bench_c_string(howmany, std::move(basic_st));
rotating_st = spdlog::rotating_logger_st("rotating_st", "logs/rotating_cs.log", file_size, rotating_files);
bench_c_string(howmany, std::move(rotating_st));
daily_st = spdlog::daily_logger_st("daily_st", "logs/daily_cs.log");
bench_c_string(howmany, std::move(daily_st));
bench_c_string(howmany, spdlog::create<null_sink_st>("null_st"));
spdlog::info("**************************************************************");
spdlog::info("{:n} threads sharing same logger, {:n} iterations", threads, howmany);
spdlog::info("**************************************************************");
auto basic_mt = spdlog::basic_logger_mt("basic_mt", "logs/basic_mt.log", true); auto basic_mt = spdlog::basic_logger_mt("basic_mt", "logs/basic_mt.log", true);
bench_mt(howmany, basic_mt, threads); bench_mt(howmany, std::move(basic_mt), threads);
auto rotating_mt = spdlog::rotating_logger_mt("rotating_mt", "logs/rotating_mt.log", file_size, rotating_files); auto rotating_mt = spdlog::rotating_logger_mt("rotating_mt", "logs/rotating_mt.log", file_size, rotating_files);
bench_mt(howmany, rotating_mt, threads); bench_mt(howmany, std::move(rotating_mt), threads);
auto daily_mt = spdlog::daily_logger_mt("daily_mt", "logs/daily_mt.log"); auto daily_mt = spdlog::daily_logger_mt("daily_mt", "logs/daily_mt.log");
bench_mt(howmany, daily_mt, threads); bench_mt(howmany, std::move(daily_mt), threads);
bench_mt(howmany, spdlog::create<null_sink_mt>("null_mt"), threads); bench_mt(howmany, spdlog::create<null_sink_mt>("null_mt"), threads);
cout << "\n****************************************************************" spdlog::info("**************************************************************");
"***************\n"; spdlog::info("Asyncronous.. {:n} threads sharing same logger, {:n} iterations", threads, howmany);
cout << "async logging.. " << threads << " threads sharing same logger, " << format(howmany) << " iterations " << endl; spdlog::info("**************************************************************");
cout << "******************************************************************"
"*************\n";
for (int i = 0; i < 3; ++i) for (int i = 0; i < 3; ++i)
{ {
spdlog::init_thread_pool(static_cast<size_t>(queue_size), 1); spdlog::init_thread_pool(static_cast<size_t>(queue_size), 1);
auto as = spdlog::basic_logger_mt<spdlog::async_factory>("async", "logs/basic_async.log", true); auto as = spdlog::basic_logger_mt<spdlog::async_factory>("async", "logs/basic_async.log", true);
bench_mt(howmany, as, threads); bench_mt(howmany, std::move(as), threads);
spdlog::drop("async");
} }
} }
catch (std::exception &ex) catch (std::exception &ex)
{ {
std::cerr << "Error: " << ex.what() << std::endl; spdlog::error(ex.what());
perror("Last error");
return EXIT_FAILURE; return EXIT_FAILURE;
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;
@ -107,7 +118,6 @@ int main(int argc, char *argv[])
void bench(int howmany, std::shared_ptr<spdlog::logger> log) void bench(int howmany, std::shared_ptr<spdlog::logger> log)
{ {
using std::chrono::high_resolution_clock; using std::chrono::high_resolution_clock;
cout << log->name() << "...\t\t" << flush;
auto start = high_resolution_clock::now(); auto start = high_resolution_clock::now();
for (auto i = 0; i < howmany; ++i) for (auto i = 0; i < howmany; ++i)
{ {
@ -117,14 +127,13 @@ void bench(int howmany, std::shared_ptr<spdlog::logger> log)
auto delta = high_resolution_clock::now() - start; auto delta = high_resolution_clock::now() - start;
auto delta_d = duration_cast<duration<double>>(delta).count(); auto delta_d = duration_cast<duration<double>>(delta).count();
cout << "Elapsed: " << delta_d << "\t" << format(int(howmany / delta_d)) << "/sec" << endl; spdlog::info("{:<16} Elapsed: {:0.2f} secs {:>16n}/sec", log->name(), delta_d, int(howmany / delta_d));
spdlog::drop(log->name()); spdlog::drop(log->name());
} }
void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count) void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count)
{ {
using std::chrono::high_resolution_clock; using std::chrono::high_resolution_clock;
cout << log->name() << "...\t\t" << flush;
vector<thread> threads; vector<thread> threads;
auto start = high_resolution_clock::now(); auto start = high_resolution_clock::now();
for (int t = 0; t < thread_count; ++t) for (int t = 0; t < thread_count; ++t)
@ -144,5 +153,47 @@ void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count
auto delta = high_resolution_clock::now() - start; auto delta = high_resolution_clock::now() - start;
auto delta_d = duration_cast<duration<double>>(delta).count(); auto delta_d = duration_cast<duration<double>>(delta).count();
cout << "Elapsed: " << delta_d << "\t" << format(int(howmany / delta_d)) << "/sec" << endl; spdlog::info("{:<16} Elapsed: {:0.2f} secs {:>16n}/sec", log->name(), delta_d, int(howmany / delta_d));
spdlog::drop(log->name());
}
void bench_default_api(int howmany, std::shared_ptr<spdlog::logger> log)
{
using std::chrono::high_resolution_clock;
auto orig_default = spdlog::default_logger();
spdlog::set_default_logger(log);
auto start = high_resolution_clock::now();
for (auto i = 0; i < howmany; ++i)
{
spdlog::info("Hello logger: msg number {}", i);
}
auto delta = high_resolution_clock::now() - start;
auto delta_d = duration_cast<duration<double>>(delta).count();
spdlog::drop(log->name());
spdlog::set_default_logger(std::move(orig_default));
spdlog::info("{:<16} Elapsed: {:0.2f} secs {:>16n}/sec", log->name(), delta_d, int(howmany / delta_d));
}
void bench_c_string(int howmany, std::shared_ptr<spdlog::logger> log)
{
const char *msg = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum pharetra metus cursus "
"lacus placerat congue. Nulla egestas, mauris a tincidunt tempus, enim lectus volutpat mi, eu consequat sem "
"libero nec massa. In dapibus ipsum a diam rhoncus gravida. Etiam non dapibus eros. Donec fringilla dui sed "
"augue pretium, nec scelerisque est maximus. Nullam convallis, sem nec blandit maximus, nisi turpis ornare "
"nisl, sit amet volutpat neque massa eu odio. Maecenas malesuada quam ex, posuere congue nibh turpis duis.";
using std::chrono::high_resolution_clock;
auto orig_default = spdlog::default_logger();
spdlog::set_default_logger(log);
auto start = high_resolution_clock::now();
for (auto i = 0; i < howmany; ++i)
{
spdlog::log(level::info, msg);
}
auto delta = high_resolution_clock::now() - start;
auto delta_d = duration_cast<duration<double>>(delta).count();
spdlog::drop(log->name());
spdlog::set_default_logger(std::move(orig_default));
spdlog::info("{:<16} Elapsed: {:0.2f} secs {:>16n}/sec", log->name(), delta_d, int(howmany / delta_d));
} }

92
bench/formatter-bench.cpp Normal file
View File

@ -0,0 +1,92 @@
//
// Copyright(c) 2018 Gabi Melman.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
//
#include "benchmark/benchmark.h"
#include "spdlog/spdlog.h"
#include "spdlog/details/pattern_formatter.h"
void bench_scoped_pad(benchmark::State &state, size_t wrapped_size, spdlog::details::padding_info padinfo)
{
fmt::memory_buffer dest;
for (auto _ : state)
{
{
spdlog::details::scoped_pad p(wrapped_size, padinfo, dest);
benchmark::DoNotOptimize(p);
dest.clear();
}
}
}
void bench_formatter(benchmark::State &state, std::string pattern)
{
auto formatter = spdlog::details::make_unique<spdlog::pattern_formatter>(pattern);
fmt::memory_buffer dest;
std::string logger_name = "logger-name";
const char *text = "Hello. This is some message with length of 80 ";
spdlog::details::log_msg msg(&logger_name, spdlog::level::info, text);
for (auto _ : state)
{
dest.clear();
formatter->format(msg, dest);
benchmark::DoNotOptimize(dest);
}
}
void bench_formatters()
{
// basic patterns(single flag)
std::string all_flags = "+vtPnlLaAbBcCYDmdHIMSefFprRTXzEi%";
std::vector<std::string> basic_patterns;
for (auto &flag : all_flags)
{
auto pattern = std::string("%") + flag;
benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
// pattern = std::string("%16") + flag;
// benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
//
// // bench center padding
// pattern = std::string("%=16") + flag;
// benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
}
// complex patterns
std::vector<std::string> patterns = {
"[%D %X] [%l] [%n] %v",
"[%Y-%m-%d %H:%M:%S.%e] [%l] [%n] %v",
"[%Y-%m-%d %H:%M:%S.%e] [%l] [%n] [%t] %v",
};
for (auto &pattern : patterns)
{
benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern)->Iterations(2500000);
}
}
int main(int argc, char *argv[])
{
spdlog::set_pattern("[%^%l%$] %v");
if (argc != 2)
{
spdlog::error("Usage: {} <pattern> (or \"all\" to bench all)", argv[0]);
exit(1);
}
std::string pattern = argv[1];
if (pattern == "all")
{
bench_formatters();
}
else
{
benchmark::RegisterBenchmark(pattern.c_str(), bench_formatter, pattern);
}
benchmark::Initialize(&argc, argv);
benchmark::RunSpecifiedBenchmarks();
}

View File

@ -6,146 +6,138 @@
// //
// latency.cpp : spdlog latency benchmarks // latency.cpp : spdlog latency benchmarks
// //
#include "benchmark/benchmark.h"
#include "spdlog/spdlog.h"
#include "spdlog/async.h" #include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h" #include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/daily_file_sink.h" #include "spdlog/sinks/daily_file_sink.h"
#include "spdlog/sinks/null_sink.h" #include "spdlog/sinks/null_sink.h"
#include "spdlog/sinks/rotating_file_sink.h" #include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/spdlog.h"
#include "utils.h"
#include <atomic>
#include <cstdlib>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
using namespace std; void prepare_logdir()
using namespace std::chrono;
using namespace spdlog;
using namespace spdlog::sinks;
using namespace utils;
void bench(int howmany, std::shared_ptr<spdlog::logger> log);
void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count);
int main(int, char *[])
{ {
std::srand(static_cast<unsigned>(std::time(nullptr))); // use current time as seed for random generator spdlog::info("Preparing latency_logs directory..");
int howmany = 1000000; #ifdef _WIN32
int queue_size = howmany + 2; system("if not exist logs mkdir latency_logs");
int threads = 10; system("del /F /Q logs\\*");
#else
auto rv = system("mkdir -p latency_logs");
if (rv != 0)
{
throw std::runtime_error("Failed to mkdir -p latency_logs");
}
rv = system("rm -f latency_logs/*");
if (rv != 0)
{
throw std::runtime_error("Failed to rm -f latency_logs/*");
}
#endif
}
void bench_c_string(benchmark::State &state, std::shared_ptr<spdlog::logger> logger)
{
const char *msg = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum pharetra metus cursus "
"lacus placerat congue. Nulla egestas, mauris a tincidunt tempus, enim lectus volutpat mi, eu consequat sem "
"libero nec massa. In dapibus ipsum a diam rhoncus gravida. Etiam non dapibus eros. Donec fringilla dui sed "
"augue pretium, nec scelerisque est maximus. Nullam convallis, sem nec blandit maximus, nisi turpis ornare "
"nisl, sit amet volutpat neque massa eu odio. Maecenas malesuada quam ex, posuere congue nibh turpis duis.";
for (auto _ : state)
{
logger->info(msg);
}
}
void bench_logger(benchmark::State &state, std::shared_ptr<spdlog::logger> logger)
{
int i = 0;
for (auto _ : state)
{
logger->info("Hello logger: msg number {}...............", ++i);
}
}
void bench_disabled_macro(benchmark::State &state, std::shared_ptr<spdlog::logger> logger)
{
int i = 0;
benchmark::DoNotOptimize(i); // prevent unused warnings
benchmark::DoNotOptimize(logger); // prevent unused warnings
for (auto _ : state)
{
SPDLOG_LOGGER_DEBUG(logger, "Hello logger: msg number {}...............", i++);
SPDLOG_DEBUG("Hello logger: msg number {}...............", i++);
}
}
int main(int argc, char *argv[])
{
using spdlog::sinks::basic_file_sink_mt;
using spdlog::sinks::basic_file_sink_st;
using spdlog::sinks::null_sink_mt;
using spdlog::sinks::null_sink_st;
size_t file_size = 30 * 1024 * 1024; size_t file_size = 30 * 1024 * 1024;
size_t rotating_files = 5; size_t rotating_files = 5;
int n_threads = benchmark::CPUInfo::Get().num_cpus;
try prepare_logdir();
{
cout << "******************************************************************" // disabled loggers
"*************\n"; auto disabled_logger = std::make_shared<spdlog::logger>("bench", std::make_shared<null_sink_mt>());
cout << "Single thread\n"; disabled_logger->set_level(spdlog::level::off);
cout << "******************************************************************" benchmark::RegisterBenchmark("disabled-at-compile-time", bench_disabled_macro, disabled_logger);
"*************\n"; benchmark::RegisterBenchmark("disabled-at-runtime", bench_logger, disabled_logger);
auto basic_st = spdlog::basic_logger_mt("basic_st", "logs/basic_st.log", true); auto null_logger_st = std::make_shared<spdlog::logger>("bench", std::make_shared<null_sink_st>());
bench(howmany, basic_st); benchmark::RegisterBenchmark("null_sink_st (500_bytes c_str)", bench_c_string, std::move(null_logger_st));
benchmark::RegisterBenchmark("null_sink_st", bench_logger, null_logger_st);
auto rotating_st = spdlog::rotating_logger_st("rotating_st", "logs/rotating_st.log", file_size, rotating_files); // basic_st
bench(howmany, rotating_st); auto basic_st = spdlog::basic_logger_st("basic_st", "latency_logs/basic_st.log", true);
benchmark::RegisterBenchmark("basic_st", bench_logger, std::move(basic_st))->UseRealTime();
spdlog::drop("basic_st");
auto daily_st = spdlog::daily_logger_st("daily_st", "logs/daily_st.log"); // rotating st
bench(howmany, daily_st); auto rotating_st = spdlog::rotating_logger_st("rotating_st", "latency_logs/rotating_st.log", file_size, rotating_files);
benchmark::RegisterBenchmark("rotating_st", bench_logger, std::move(rotating_st))->UseRealTime();
spdlog::drop("rotating_st");
bench(howmany, spdlog::create<null_sink_st>("null_st")); // daily st
auto daily_st = spdlog::daily_logger_mt("daily_st", "latency_logs/daily_st.log");
benchmark::RegisterBenchmark("daily_st", bench_logger, std::move(daily_st))->UseRealTime();
spdlog::drop("daily_st");
cout << "\n****************************************************************" // //
"***************\n"; // // Multi threaded bench, 10 loggers using same logger concurrently
cout << threads << " threads sharing same logger\n"; // //
cout << "******************************************************************" auto null_logger_mt = std::make_shared<spdlog::logger>("bench", std::make_shared<null_sink_mt>());
"*************\n"; benchmark::RegisterBenchmark("null_sink_mt", bench_logger, null_logger_mt)->Threads(n_threads)->UseRealTime();
auto basic_mt = spdlog::basic_logger_mt("basic_mt", "logs/basic_mt.log", true); // basic_mt
bench_mt(howmany, basic_mt, threads); auto basic_mt = spdlog::basic_logger_mt("basic_mt", "latency_logs/basic_mt.log", true);
benchmark::RegisterBenchmark("basic_mt", bench_logger, std::move(basic_mt))->Threads(n_threads)->UseRealTime();
spdlog::drop("basic_mt");
auto rotating_mt = spdlog::rotating_logger_mt("rotating_mt", "logs/rotating_mt.log", file_size, rotating_files); // rotating mt
bench_mt(howmany, rotating_mt, threads); auto rotating_mt = spdlog::rotating_logger_mt("rotating_mt", "latency_logs/rotating_mt.log", file_size, rotating_files);
benchmark::RegisterBenchmark("rotating_mt", bench_logger, std::move(rotating_mt))->Threads(n_threads)->UseRealTime();
spdlog::drop("rotating_mt");
auto daily_mt = spdlog::daily_logger_mt("daily_mt", "logs/daily_mt.log"); // daily mt
bench_mt(howmany, daily_mt, threads); auto daily_mt = spdlog::daily_logger_mt("daily_mt", "latency_logs/daily_mt.log");
bench(howmany, spdlog::create<null_sink_st>("null_mt")); benchmark::RegisterBenchmark("daily_mt", bench_logger, std::move(daily_mt))->Threads(n_threads)->UseRealTime();
spdlog::drop("daily_mt");
cout << "\n****************************************************************" // async
"***************\n"; auto queue_size = 1024 * 1024 * 3;
cout << "async logging.. " << threads << " threads sharing same logger\n"; auto tp = std::make_shared<spdlog::details::thread_pool>(queue_size, 1);
cout << "******************************************************************" auto async_logger = std::make_shared<spdlog::async_logger>(
"*************\n"; "async_logger", std::make_shared<null_sink_mt>(), std::move(tp), spdlog::async_overflow_policy::overrun_oldest);
benchmark::RegisterBenchmark("async_logger", bench_logger, async_logger)->Threads(n_threads)->UseRealTime();
for (int i = 0; i < 3; ++i) benchmark::Initialize(&argc, argv);
{ benchmark::RunSpecifiedBenchmarks();
spdlog::init_thread_pool(static_cast<size_t>(queue_size), 1);
auto as = spdlog::basic_logger_mt<spdlog::async_factory>("async", "logs/basic_async.log", true);
bench_mt(howmany, as, threads);
spdlog::drop("async");
}
}
catch (std::exception &ex)
{
std::cerr << "Error: " << ex.what() << std::endl;
perror("Last error");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
void bench(int howmany, std::shared_ptr<spdlog::logger> log)
{
using namespace std::chrono;
using chrono::high_resolution_clock;
using chrono::milliseconds;
using chrono::nanoseconds;
cout << log->name() << "...\t\t" << flush;
nanoseconds total_nanos = nanoseconds::zero();
for (auto i = 0; i < howmany; ++i)
{
auto start = high_resolution_clock::now();
log->info("Hello logger: msg number {}", i);
auto delta_nanos = chrono::duration_cast<nanoseconds>(high_resolution_clock::now() - start);
total_nanos += delta_nanos;
}
auto avg = total_nanos.count() / howmany;
cout << format(avg) << " ns/call" << endl;
}
void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count)
{
using namespace std::chrono;
using chrono::high_resolution_clock;
using chrono::milliseconds;
using chrono::nanoseconds;
cout << log->name() << "...\t\t" << flush;
vector<thread> threads;
std::atomic<nanoseconds::rep> total_nanos{0};
for (int t = 0; t < thread_count; ++t)
{
threads.push_back(std::thread([&]() {
for (int j = 0; j < howmany / thread_count; j++)
{
auto start = high_resolution_clock::now();
log->info("Hello logger: msg number {}", j);
auto delta_nanos = chrono::duration_cast<nanoseconds>(high_resolution_clock::now() - start);
total_nanos += delta_nanos.count();
}
}));
}
for (auto &t : threads)
{
t.join();
};
auto avg = total_nanos / howmany;
cout << format(avg) << " ns/call" << endl;
} }

View File

@ -1,77 +0,0 @@
//
// Copyright(c) 2015 Gabi Melman.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
//
#include <atomic>
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <thread>
#include <vector>
#include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h"
using namespace std;
int main(int argc, char *argv[])
{
using namespace std::chrono;
using clock = steady_clock;
int thread_count = 10;
if (argc > 1)
thread_count = std::atoi(argv[1]);
int howmany = 1000000;
spdlog::init_thread_pool(howmany, 1);
auto logger = spdlog::create_async_logger<spdlog::sinks::basic_file_sink_mt>("file_logger", "logs/spdlog-bench-async.log", false);
logger->set_pattern("[%Y-%m-%d %T.%F]: %L %t %v");
std::cout << "To stop, press <Enter>" << std::endl;
std::atomic<bool> run{true};
std::thread stoper(std::thread([&run]() {
std::cin.get();
run = false;
}));
while (run)
{
std::atomic<int> msg_counter{0};
std::vector<std::thread> threads;
auto start = clock::now();
for (int t = 0; t < thread_count; ++t)
{
threads.push_back(std::thread([&]() {
while (true)
{
int counter = ++msg_counter;
if (counter > howmany)
break;
logger->info("spdlog message #{}: This is some text for your pleasure", counter);
}
}));
}
for (auto &t : threads)
{
t.join();
}
duration<float> delta = clock::now() - start;
float deltaf = delta.count();
auto rate = howmany / deltaf;
std::cout << "Total: " << howmany << std::endl;
std::cout << "Threads: " << thread_count << std::endl;
std::cout << "Delta = " << std::fixed << deltaf << " seconds" << std::endl;
std::cout << "Rate = " << std::fixed << rate << "/sec" << std::endl;
} // while
stoper.join();
return 0;
}

2
clang_tidy.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
clang-tidy example/example.cpp -- -I ./include

View File

@ -21,4 +21,11 @@
# * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ # * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
# *************************************************************************/ # *************************************************************************/
set(SPDLOG_FMT_EXTERNAL @SPDLOG_FMT_EXTERNAL@)
include("${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake") include("${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake")
if(SPDLOG_FMT_EXTERNAL)
include(CMakeFindDependencyMacro)
find_dependency(fmt CONFIG)
endif()

View File

@ -32,7 +32,13 @@ endif()
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
add_executable(example example.cpp) add_executable(example example.cpp)
if(CMAKE_SYSTEM_NAME STREQUAL "Android")
find_library(log-lib log)
target_link_libraries(example spdlog::spdlog Threads::Threads log)
else()
target_link_libraries(example spdlog::spdlog Threads::Threads) target_link_libraries(example spdlog::spdlog Threads::Threads)
endif()
add_executable(multisink multisink.cpp) add_executable(multisink multisink.cpp)
target_link_libraries(multisink spdlog::spdlog Threads::Threads) target_link_libraries(multisink spdlog::spdlog Threads::Threads)

View File

@ -7,13 +7,15 @@
// //
// //
#include <iostream> #include <cstdio>
void stdout_example(); void stdout_logger_example();
void basic_example(); void basic_example();
void rotating_example(); void rotating_example();
void daily_example(); void daily_example();
void async_example(); void async_example();
void binary_example();
void trace_example();
void multi_sink_example(); void multi_sink_example();
void user_defined_example(); void user_defined_example();
void err_handler_example(); void err_handler_example();
@ -24,119 +26,101 @@ void clone_example();
int main(int, char *[]) int main(int, char *[])
{ {
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
try try
{ {
// console logging example stdout_logger_example();
stdout_example();
// various file loggers
basic_example(); basic_example();
rotating_example(); rotating_example();
daily_example(); daily_example();
clone_example(); clone_example();
// async logging using a backing thread pool
async_example(); async_example();
binary_example();
// a logger can have multiple targets with different formats
multi_sink_example(); multi_sink_example();
// user defined types logging by implementing operator<<
user_defined_example(); user_defined_example();
// custom error handler
err_handler_example(); err_handler_example();
trace_example();
// flush all *registered* loggers using a worker thread every 3 seconds. // Flush all *registered* loggers using a worker thread every 3 seconds.
// note: registered loggers *must* be thread safe for this to work correctly! // note: registered loggers *must* be thread safe for this to work correctly!
spdlog::flush_every(std::chrono::seconds(3)); spdlog::flush_every(std::chrono::seconds(3));
// apply some function on all registered loggers // Apply some function on all registered loggers
spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) { l->info("End of example."); }); spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) { l->info("End of example."); });
// release any threads created by spdlog, and drop all loggers in the registry. // Release all spdlog resources, and drop all loggers in the registry.
// This is optional (only mandatory if using windows + async log).
spdlog::shutdown(); spdlog::shutdown();
} }
// Exceptions will only be thrown upon failed logger or sink construction (not during logging)
// Exceptions will only be thrown upon failed logger or sink construction (not during logging).
catch (const spdlog::spdlog_ex &ex) catch (const spdlog::spdlog_ex &ex)
{ {
std::cout << "Log init failed: " << ex.what() << std::endl; std::printf("Log initialization failed: %s\n", ex.what());
return 1; return 1;
} }
} }
#include "spdlog/sinks/stdout_color_sinks.h" #include "spdlog/sinks/stdout_color_sinks.h"
// or #include "spdlog/sinks/stdout_sinks.h" if no colors needed // or #include "spdlog/sinks/stdout_sinks.h" if no colors needed.
void stdout_example() void stdout_logger_example()
{ {
// create color multi threaded logger // Create color multi threaded logger.
auto console = spdlog::stdout_color_mt("console"); auto console = spdlog::stdout_color_mt("console");
console->info("Welcome to spdlog version {}.{}.{} !", SPDLOG_VER_MAJOR, SPDLOG_VER_MINOR, SPDLOG_VER_PATCH); // or for stderr:
console->error("Some error message with arg: {}", 1); // auto console = spdlog::stderr_color_mt("error-logger");
auto err_logger = spdlog::stderr_color_mt("stderr");
err_logger->error("Some error message");
// Formatting examples
console->warn("Easy padding in numbers like {:08d}", 12);
console->critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42);
console->info("Support for floats {:03.2f}", 1.23456);
console->info("Positional args are {1} {0}..", "too", "supported");
console->info("{:<30}", "left aligned");
spdlog::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name)");
// Runtime log levels
spdlog::set_level(spdlog::level::info); // Set global log level to info
console->debug("This message should not be displayed!");
console->set_level(spdlog::level::trace); // Set specific logger's log level
console->debug("This message should be displayed..");
// Customize msg format for all loggers
spdlog::set_pattern("[%H:%M:%S %z] [%n] [%^---%L---%$] [thread %t] %v");
console->info("This an info message with custom format");
// Compile time log levels
// define SPDLOG_DEBUG_ON or SPDLOG_TRACE_ON
SPDLOG_TRACE(console, "Enabled only #ifdef SPDLOG_TRACE_ON..{} ,{}", 1, 3.23);
SPDLOG_DEBUG(console, "Enabled only #ifdef SPDLOG_DEBUG_ON.. {} ,{}", 1, 3.23);
} }
#include "spdlog/sinks/basic_file_sink.h" #include "spdlog/sinks/basic_file_sink.h"
void basic_example() void basic_example()
{ {
// Create basic file logger (not rotated) // Create basic file logger (not rotated).
auto my_logger = spdlog::basic_logger_mt("basic_logger", "logs/basic-log.txt"); auto my_logger = spdlog::basic_logger_mt("file_logger", "logs/basic-log.txt");
} }
#include "spdlog/sinks/rotating_file_sink.h" #include "spdlog/sinks/rotating_file_sink.h"
void rotating_example() void rotating_example()
{ {
// Create a file rotating logger with 5mb size max and 3 rotated files // Create a file rotating logger with 5mb size max and 3 rotated files.
auto rotating_logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3); auto rotating_logger = spdlog::rotating_logger_mt("some_logger_name", "logs/rotating.txt", 1048576 * 5, 3);
} }
#include "spdlog/sinks/daily_file_sink.h" #include "spdlog/sinks/daily_file_sink.h"
void daily_example() void daily_example()
{ {
// Create a daily logger - a new file is created every day on 2:30am // Create a daily logger - a new file is created every day on 2:30am.
auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30); auto daily_logger = spdlog::daily_logger_mt("daily_logger", "logs/daily.txt", 2, 30);
} }
// clone a logger and give it new name. // Clone a logger and give it new name.
// Useful for creating component/subsystem loggers from some "root" logger // Useful for creating component/subsystem loggers from some "root" logger.
void clone_example() void clone_example()
{ {
auto network_logger = spdlog::get("console")->clone("network"); auto network_logger = spdlog::default_logger()->clone("network");
network_logger->info("Logging network stuff.."); network_logger->info("Logging network stuff..");
} }
#include "spdlog/async.h" #include "spdlog/async.h"
void async_example() void async_example()
{ {
// default thread pool settings can be modified *before* creating the async logger: // Default thread pool settings can be modified *before* creating the async logger:
// spdlog::init_thread_pool(32768, 1); // queue with max 32k items 1 backing thread. // spdlog::init_thread_pool(32768, 1); // queue with max 32k items 1 backing thread.
auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "logs/async_log.txt"); auto async_file = spdlog::basic_logger_mt<spdlog::async_factory>("async_file_logger", "logs/async_log.txt");
// alternatively: // alternatively:
@ -148,9 +132,46 @@ void async_example()
} }
} }
// create logger with 2 targets with different log levels and formats // Log binary data as hex.
// the console will show only warnings or errors, while the file will log all // Many types of std::container<char> types can be used.
// Iterator ranges are supported too.
// Format flags:
// {:X} - print in uppercase.
// {:s} - don't separate each byte with space.
// {:p} - don't print the position on each line start.
// {:n} - don't split the output to lines.
#include "spdlog/fmt/bin_to_hex.h"
void binary_example()
{
std::vector<char> buf;
for (int i = 0; i < 80; i++)
{
buf.push_back(static_cast<char>(i & 0xff));
}
spdlog::info("Binary example: {}", spdlog::to_hex(buf));
spdlog::info("Another binary example:{:n}", spdlog::to_hex(std::begin(buf), std::begin(buf) + 10));
// more examples:
// logger->info("uppercase: {:X}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters: {:Xs}", spdlog::to_hex(buf));
// logger->info("uppercase, no delimiters, no position info: {:Xsp}", spdlog::to_hex(buf));
}
// Compile time log levels.
// define SPDLOG_ACTIVE_LEVEL to required level (e.g. SPDLOG_LEVEL_TRACE)
void trace_example()
{
// trace from default logger
SPDLOG_TRACE("Some trace message.. {} ,{}", 1, 3.23);
// debug from default logger
SPDLOG_DEBUG("Some debug message.. {} ,{}", 1, 3.23);
// trace from logger object
auto logger = spdlog::get("file_logger");
SPDLOG_LOGGER_TRACE(logger, "another trace message");
}
// A logger with multiple sinks (stdout and file) - each with a different format and log level.
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>();
@ -165,7 +186,8 @@ void multi_sink_example()
logger.warn("this should appear in both console and file"); logger.warn("this should appear in both console and file");
logger.info("this message should not appear in the console, only in the file"); logger.info("this message should not appear in the console, only in the file");
} }
// user defined types logging by implementing operator<<
// User defined types logging by implementing operator<<
#include "spdlog/fmt/ostr.h" // must be included #include "spdlog/fmt/ostr.h" // must be included
struct my_type struct my_type
{ {
@ -179,17 +201,14 @@ struct my_type
void user_defined_example() void user_defined_example()
{ {
spdlog::get("console")->info("user defined type: {}", my_type{14}); spdlog::info("user defined type: {}", my_type{14});
} }
// // Custom error handler. Will be triggered on log failure.
// custom error handler
//
void err_handler_example() void err_handler_example()
{ {
// can be set globally or per logger(logger->set_error_handler(..)) // can be set globally or per logger(logger->set_error_handler(..))
spdlog::set_error_handler([](const std::string &msg) { spdlog::get("console")->error("*** ERROR HANDLER EXAMPLE ***: {}", msg); }); spdlog::set_error_handler([](const std::string &msg) { printf("*** Custom log error handler: %s ***\n", msg.c_str()); });
spdlog::get("console")->info("some invalid message to trigger an error {}{}{}{}", 3);
} }
// syslog example (linux/osx/freebsd) // syslog example (linux/osx/freebsd)
@ -203,7 +222,7 @@ void syslog_example()
} }
#endif #endif
// Android example // Android example.
#if defined(__ANDROID__) #if defined(__ANDROID__)
#include "spdlog/sinks/android_sink.h" #include "spdlog/sinks/android_sink.h"
void android_example() void android_example()

View File

@ -25,7 +25,6 @@
<ProjectGuid>{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}</ProjectGuid> <ProjectGuid>{9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2}</ProjectGuid>
<Keyword>Win32Proj</Keyword> <Keyword>Win32Proj</Keyword>
<RootNamespace>.</RootNamespace> <RootNamespace>.</RootNamespace>
<WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">

View File

@ -1,6 +1,7 @@
#include "spdlog/spdlog.h"
#include "spdlog/sinks/basic_file_sink.h" #include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/stdout_sinks.h" #include "spdlog/sinks/stdout_sinks.h"
#include "spdlog/spdlog.h"
#include <iostream> #include <iostream>
#include <memory> #include <memory>

View File

@ -1,9 +1,9 @@
#!/bin/bash #!/bin/bash
echo -n "Running dos2unix " echo -n "Running dos2unix "
find . -name "*\.h" -o -name "*\.cpp"|xargs -I {} sh -c "dos2unix '{}' 2>/dev/null; echo -n '.'" find . -name "*\.h" -o -name "*\.cpp"|grep -v bundled|xargs -I {} sh -c "dos2unix '{}' 2>/dev/null; echo -n '.'"
echo echo
echo -n "Running clang-format " echo -n "Running clang-format "
find . -name "*\.h" -o -name "*\.cpp"|xargs -I {} sh -c "clang-format -i {}; echo -n '.'" find . -name "*\.h" -o -name "*\.cpp"|grep -v bundled|xargs -I {} sh -c "clang-format -i {}; echo -n '.'"
echo echo

View File

@ -37,7 +37,7 @@ template<async_overflow_policy OverflowPolicy = async_overflow_policy::block>
struct async_factory_impl struct async_factory_impl
{ {
template<typename Sink, typename... SinkArgs> template<typename Sink, typename... SinkArgs>
static std::shared_ptr<async_logger> create(const std::string &logger_name, SinkArgs &&... args) static std::shared_ptr<async_logger> create(std::string logger_name, SinkArgs &&... args)
{ {
auto &registry_inst = details::registry::instance(); auto &registry_inst = details::registry::instance();
@ -51,8 +51,8 @@ struct async_factory_impl
} }
auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...); auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...);
auto new_logger = std::make_shared<async_logger>(logger_name, std::move(sink), std::move(tp), OverflowPolicy); auto new_logger = std::make_shared<async_logger>(std::move(logger_name), std::move(sink), std::move(tp), OverflowPolicy);
registry_inst.register_and_init(new_logger); registry_inst.initialize_logger(new_logger);
return new_logger; return new_logger;
} }
}; };
@ -61,15 +61,15 @@ using async_factory = async_factory_impl<async_overflow_policy::block>;
using async_factory_nonblock = async_factory_impl<async_overflow_policy::overrun_oldest>; using async_factory_nonblock = async_factory_impl<async_overflow_policy::overrun_oldest>;
template<typename Sink, typename... SinkArgs> template<typename Sink, typename... SinkArgs>
inline std::shared_ptr<spdlog::logger> create_async(const std::string &logger_name, SinkArgs &&... sink_args) inline std::shared_ptr<spdlog::logger> create_async(std::string logger_name, SinkArgs &&... sink_args)
{ {
return async_factory::create<Sink>(logger_name, std::forward<SinkArgs>(sink_args)...); return async_factory::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...);
} }
template<typename Sink, typename... SinkArgs> template<typename Sink, typename... SinkArgs>
inline std::shared_ptr<spdlog::logger> create_async_nb(const std::string &logger_name, SinkArgs &&... sink_args) inline std::shared_ptr<spdlog::logger> create_async_nb(std::string logger_name, SinkArgs &&... sink_args)
{ {
return async_factory_nonblock::create<Sink>(logger_name, std::forward<SinkArgs>(sink_args)...); return async_factory_nonblock::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...);
} }
// set global thread pool. // set global thread pool.

View File

@ -40,13 +40,13 @@ namespace details {
class thread_pool; class thread_pool;
} }
class async_logger SPDLOG_FINAL : public std::enable_shared_from_this<async_logger>, public logger class async_logger final : public std::enable_shared_from_this<async_logger>, public logger
{ {
friend class details::thread_pool; friend class details::thread_pool;
public: public:
template<typename It> template<typename It>
async_logger(std::string logger_name, const It &begin, const It &end, std::weak_ptr<details::thread_pool> tp, async_logger(std::string logger_name, It begin, It end, std::weak_ptr<details::thread_pool> tp,
async_overflow_policy overflow_policy = async_overflow_policy::block); async_overflow_policy overflow_policy = async_overflow_policy::block);
async_logger(std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp, async_logger(std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp,
@ -61,7 +61,7 @@ protected:
void sink_it_(details::log_msg &msg) override; void sink_it_(details::log_msg &msg) override;
void flush_() override; void flush_() override;
void backend_log_(details::log_msg &incoming_log_msg); void backend_log_(const details::log_msg &incoming_log_msg);
void backend_flush_(); void backend_flush_();
private: private:

View File

@ -14,6 +14,8 @@
#include <memory> #include <memory>
#include <stdexcept> #include <stdexcept>
#include <string> #include <string>
#include <cstring>
#include <type_traits>
#include <unordered_map> #include <unordered_map>
#if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) #if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT)
@ -23,6 +25,8 @@
#include "spdlog/details/null_mutex.h" #include "spdlog/details/null_mutex.h"
#include "spdlog/fmt/fmt.h"
// visual studio upto 2013 does not support noexcept nor constexpr // visual studio upto 2013 does not support noexcept nor constexpr
#if defined(_MSC_VER) && (_MSC_VER < 1900) #if defined(_MSC_VER) && (_MSC_VER < 1900)
#define SPDLOG_NOEXCEPT throw() #define SPDLOG_NOEXCEPT throw()
@ -32,13 +36,6 @@
#define SPDLOG_CONSTEXPR constexpr #define SPDLOG_CONSTEXPR constexpr
#endif #endif
// final keyword support. On by default. See tweakme.h
#if defined(SPDLOG_NO_FINAL)
#define SPDLOG_FINAL
#else
#define SPDLOG_FINAL final
#endif
#if defined(__GNUC__) || defined(__clang__) #if defined(__GNUC__) || defined(__clang__)
#define SPDLOG_DEPRECATED __attribute__((deprecated)) #define SPDLOG_DEPRECATED __attribute__((deprecated))
#elif defined(_MSC_VER) #elif defined(_MSC_VER)
@ -47,7 +44,29 @@
#define SPDLOG_DEPRECATED #define SPDLOG_DEPRECATED
#endif #endif
#include "spdlog/fmt/fmt.h" // disable thread local on msvc 2013
#ifndef SPDLOG_NO_TLS
#if (defined(_MSC_VER) && (_MSC_VER < 1900)) || defined(__cplusplus_winrt)
#define SPDLOG_NO_TLS 1
#endif
#endif
// Get the basename of __FILE__ (at compile time if possible)
#if FMT_HAS_FEATURE(__builtin_strrchr)
#define SPDLOG_STRRCHR(str, sep) __builtin_strrchr(str, sep)
#else
#define SPDLOG_STRRCHR(str, sep) strrchr(str, sep)
#endif //__builtin_strrchr not defined
#ifdef _WIN32
#define SPDLOG_FILE_BASENAME(file) SPDLOG_STRRCHR("\\" file, '\\') + 1
#else
#define SPDLOG_FILE_BASENAME(file) SPDLOG_STRRCHR("/" file, '/') + 1
#endif
#ifndef SPDLOG_FUNCTION
#define SPDLOG_FUNCTION __FUNCTION__
#endif
namespace spdlog { namespace spdlog {
@ -62,23 +81,42 @@ using sink_ptr = std::shared_ptr<sinks::sink>;
using sinks_init_list = std::initializer_list<sink_ptr>; using sinks_init_list = std::initializer_list<sink_ptr>;
using log_err_handler = std::function<void(const std::string &err_msg)>; using log_err_handler = std::function<void(const std::string &err_msg)>;
// string_view type - either std::string_view or fmt::string_view (pre c++17)
#if defined(FMT_USE_STD_STRING_VIEW)
using string_view_t = std::string_view;
#else
using string_view_t = fmt::string_view;
#endif
#if defined(SPDLOG_NO_ATOMIC_LEVELS) #if defined(SPDLOG_NO_ATOMIC_LEVELS)
using level_t = details::null_atomic_int; using level_t = details::null_atomic_int;
#else #else
using level_t = std::atomic<int>; using level_t = std::atomic<int>;
#endif #endif
#define SPDLOG_LEVEL_TRACE 0
#define SPDLOG_LEVEL_DEBUG 1
#define SPDLOG_LEVEL_INFO 2
#define SPDLOG_LEVEL_WARN 3
#define SPDLOG_LEVEL_ERROR 4
#define SPDLOG_LEVEL_CRITICAL 5
#define SPDLOG_LEVEL_OFF 6
#if !defined(SPDLOG_ACTIVE_LEVEL)
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO
#endif
// Log level enum // Log level enum
namespace level { namespace level {
enum level_enum enum level_enum
{ {
trace = 0, trace = SPDLOG_LEVEL_TRACE,
debug = 1, debug = SPDLOG_LEVEL_DEBUG,
info = 2, info = SPDLOG_LEVEL_INFO,
warn = 3, warn = SPDLOG_LEVEL_WARN,
err = 4, err = SPDLOG_LEVEL_ERROR,
critical = 5, critical = SPDLOG_LEVEL_CRITICAL,
off = 6 off = SPDLOG_LEVEL_OFF,
}; };
#if !defined(SPDLOG_LEVEL_NAMES) #if !defined(SPDLOG_LEVEL_NAMES)
@ -87,33 +125,32 @@ enum level_enum
"trace", "debug", "info", "warning", "error", "critical", "off" \ "trace", "debug", "info", "warning", "error", "critical", "off" \
} }
#endif #endif
static const char *level_names[] SPDLOG_LEVEL_NAMES;
static string_view_t level_string_views[] SPDLOG_LEVEL_NAMES;
static const char *short_level_names[]{"T", "D", "I", "W", "E", "C", "O"}; static const char *short_level_names[]{"T", "D", "I", "W", "E", "C", "O"};
inline const char *to_c_str(spdlog::level::level_enum l) inline string_view_t &to_string_view(spdlog::level::level_enum l) SPDLOG_NOEXCEPT
{ {
return level_names[l]; return level_string_views[l];
} }
inline const char *to_short_c_str(spdlog::level::level_enum l) inline const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT
{ {
return short_level_names[l]; return short_level_names[l];
} }
inline spdlog::level::level_enum from_str(const std::string &name) inline spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT
{ {
static std::unordered_map<std::string, level_enum> name_to_level = // map string->level int level = 0;
{{level_names[0], level::trace}, // trace for (const auto &level_str : level_string_views)
{level_names[1], level::debug}, // debug {
{level_names[2], level::info}, // info if (level_str == name)
{level_names[3], level::warn}, // warn {
{level_names[4], level::err}, // err return static_cast<level::level_enum>(level);
{level_names[5], level::critical}, // critical }
{level_names[6], level::off}}; // off level++;
}
auto lvl_it = name_to_level.find(name); return level::off;
return lvl_it != name_to_level.end() ? lvl_it->second : level::off;
} }
using level_hasher = std::hash<int>; using level_hasher = std::hash<int>;
@ -135,8 +172,8 @@ enum class pattern_time_type
class spdlog_ex : public std::exception class spdlog_ex : public std::exception
{ {
public: public:
explicit spdlog_ex(const std::string &msg) explicit spdlog_ex(std::string msg)
: msg_(msg) : msg_(std::move(msg))
{ {
} }
@ -165,26 +202,42 @@ using filename_t = std::wstring;
using filename_t = std::string; using filename_t = std::string;
#endif #endif
#define SPDLOG_CATCH_AND_HANDLE \ struct source_loc
catch (const std::exception &ex) \ {
{ \ SPDLOG_CONSTEXPR source_loc()
err_handler_(ex.what()); \ : filename{""}
} \ , line{0}
catch (...) \ , funcname{""}
{ \ {
err_handler_("Unknown exeption in logger"); \ }
SPDLOG_CONSTEXPR source_loc(const char *filename, int line, const char *funcname)
: filename{filename}
, line{static_cast<uint32_t>(line)}
, funcname{funcname}
{
} }
// SPDLOG_CONSTEXPR bool empty() const SPDLOG_NOEXCEPT
// make_unique support {
// return line == 0;
}
const char *filename;
uint32_t line;
const char *funcname;
};
namespace details {
// make_unique support for pre c++14
#if __cplusplus >= 201402L // C++14 and beyond #if __cplusplus >= 201402L // C++14 and beyond
using std::make_unique; using std::make_unique;
#else #else
template<typename T, typename... Args> template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args &&... args) std::unique_ptr<T> make_unique(Args &&... args)
{ {
static_assert(!std::is_array<T>::value, "arrays not supported");
return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
} }
#endif #endif
} // namespace details
} // namespace spdlog } // namespace spdlog

View File

@ -16,22 +16,22 @@
template<typename It> template<typename It>
inline spdlog::async_logger::async_logger( inline spdlog::async_logger::async_logger(
std::string logger_name, const It &begin, const It &end, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy) std::string logger_name, It begin, It end, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy)
: logger(std::move(logger_name), begin, end) : logger(std::move(logger_name), begin, end)
, thread_pool_(tp) , thread_pool_(std::move(tp))
, overflow_policy_(overflow_policy) , overflow_policy_(overflow_policy)
{ {
} }
inline spdlog::async_logger::async_logger( inline spdlog::async_logger::async_logger(
std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy) std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy)
: async_logger(std::move(logger_name), sinks_list.begin(), sinks_list.end(), tp, overflow_policy) : async_logger(std::move(logger_name), sinks_list.begin(), sinks_list.end(), std::move(tp), overflow_policy)
{ {
} }
inline spdlog::async_logger::async_logger( inline spdlog::async_logger::async_logger(
std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy) std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy)
: async_logger(std::move(logger_name), {single_sink}, tp, overflow_policy) : async_logger(std::move(logger_name), {std::move(single_sink)}, std::move(tp), overflow_policy)
{ {
} }
@ -43,7 +43,7 @@ inline void spdlog::async_logger::sink_it_(details::log_msg &msg)
#endif #endif
if (auto pool_ptr = thread_pool_.lock()) if (auto pool_ptr = thread_pool_.lock())
{ {
pool_ptr->post_log(shared_from_this(), std::move(msg), overflow_policy_); pool_ptr->post_log(shared_from_this(), msg, overflow_policy_);
} }
else else
{ {
@ -67,7 +67,7 @@ inline void spdlog::async_logger::flush_()
// //
// backend functions - called from the thread pool to do the actual job // backend functions - called from the thread pool to do the actual job
// //
inline void spdlog::async_logger::backend_log_(details::log_msg &incoming_log_msg) inline void spdlog::async_logger::backend_log_(const details::log_msg &incoming_log_msg)
{ {
try try
{ {

View File

@ -122,7 +122,7 @@ public:
// ".mylog" => (".mylog". "") // ".mylog" => (".mylog". "")
// "my_folder/.mylog" => ("my_folder/.mylog", "") // "my_folder/.mylog" => ("my_folder/.mylog", "")
// "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") // "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt")
static std::tuple<filename_t, filename_t> split_by_extenstion(const spdlog::filename_t &fname) static std::tuple<filename_t, filename_t> split_by_extension(const spdlog::filename_t &fname)
{ {
auto ext_index = fname.rfind('.'); auto ext_index = fname.rfind('.');

View File

@ -4,7 +4,8 @@
#pragma once #pragma once
#include "chrono" #include <chrono>
#include <type_traits>
#include "spdlog/fmt/fmt.h" #include "spdlog/fmt/fmt.h"
// Some fmt helpers to efficiently format and pad ints and strings // Some fmt helpers to efficiently format and pad ints and strings
@ -13,17 +14,9 @@ namespace details {
namespace fmt_helper { namespace fmt_helper {
template<size_t Buffer_Size> template<size_t Buffer_Size>
inline void append_str(const std::string &str, fmt::basic_memory_buffer<char, Buffer_Size> &dest) inline spdlog::string_view_t to_string_view(const fmt::basic_memory_buffer<char, Buffer_Size> &buf) SPDLOG_NOEXCEPT
{ {
auto *str_ptr = str.data(); return spdlog::string_view_t(buf.data(), buf.size());
dest.append(str_ptr, str_ptr + str.size());
}
template<size_t Buffer_Size>
inline void append_c_str(const char *c_str, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
{
auto len = std::char_traits<char>::length(c_str);
dest.append(c_str, c_str + len);
} }
template<size_t Buffer_Size1, size_t Buffer_Size2> template<size_t Buffer_Size1, size_t Buffer_Size2>
@ -33,6 +26,16 @@ inline void append_buf(const fmt::basic_memory_buffer<char, Buffer_Size1> &buf,
dest.append(buf_ptr, buf_ptr + buf.size()); dest.append(buf_ptr, buf_ptr + buf.size());
} }
template<size_t Buffer_Size>
inline void append_string_view(spdlog::string_view_t view, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
{
auto *buf_ptr = view.data();
if (buf_ptr != nullptr)
{
dest.append(buf_ptr, buf_ptr + view.size());
}
}
template<typename T, size_t Buffer_Size> template<typename T, size_t Buffer_Size>
inline void append_int(T n, fmt::basic_memory_buffer<char, Buffer_Size> &dest) inline void append_int(T n, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
{ {
@ -40,73 +43,65 @@ inline void append_int(T n, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
dest.append(i.data(), i.data() + i.size()); dest.append(i.data(), i.data() + i.size());
} }
template<typename T>
inline unsigned count_digits(T n)
{
using count_type = typename std::conditional<(sizeof(T) > sizeof(uint32_t)), uint64_t, uint32_t>::type;
return static_cast<unsigned>(fmt::internal::count_digits(static_cast<count_type>(n)));
}
template<size_t Buffer_Size> template<size_t Buffer_Size>
inline void pad2(int n, fmt::basic_memory_buffer<char, Buffer_Size> &dest) inline void pad2(int n, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
{ {
if (n > 99) if (n > 99)
{ {
append_int(n, dest); append_int(n, dest);
return;
} }
if (n > 9) // 10-99 else if (n > 9) // 10-99
{ {
dest.push_back(static_cast<char>('0' + n / 10)); dest.push_back(static_cast<char>('0' + n / 10));
dest.push_back(static_cast<char>('0' + n % 10)); dest.push_back(static_cast<char>('0' + n % 10));
return;
} }
if (n >= 0) // 0-9 else if (n >= 0) // 0-9
{ {
dest.push_back('0'); dest.push_back('0');
dest.push_back(static_cast<char>('0' + n)); dest.push_back(static_cast<char>('0' + n));
return;
} }
// negatives (unlikely, but just in case, let fmt deal with it) else // negatives (unlikely, but just in case, let fmt deal with it)
{
fmt::format_to(dest, "{:02}", n); fmt::format_to(dest, "{:02}", n);
} }
}
template<size_t Buffer_Size> template<typename T, size_t Buffer_Size>
inline void pad3(int n, fmt::basic_memory_buffer<char, Buffer_Size> &dest) inline void pad_uint(T n, unsigned int width, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
{ {
if (n > 999) static_assert(std::is_unsigned<T>::value, "pad_uint must get unsigned T");
auto digits = count_digits(n);
if (width > digits)
{ {
const char *zeroes = "0000000000000000000";
dest.append(zeroes, zeroes + width - digits);
}
append_int(n, dest); append_int(n, dest);
return;
} }
if (n > 99) // 100-999 template<typename T, size_t Buffer_Size>
inline void pad3(T n, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
{ {
append_int(n / 100, dest); pad_uint(n, 3, dest);
pad2(n % 100, dest);
return;
}
if (n > 9) // 10-99
{
dest.push_back('0');
dest.push_back(static_cast<char>('0' + n / 10));
dest.push_back(static_cast<char>('0' + n % 10));
return;
}
if (n >= 0)
{
dest.push_back('0');
dest.push_back('0');
dest.push_back(static_cast<char>('0' + n));
return;
}
// negatives (unlikely, but just in case let fmt deal with it)
fmt::format_to(dest, "{:03}", n);
} }
template<size_t Buffer_Size> template<typename T, size_t Buffer_Size>
inline void pad6(size_t n, fmt::basic_memory_buffer<char, Buffer_Size> &dest) inline void pad6(T n, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
{ {
if (n > 99999) pad_uint(n, 6, dest);
{
append_int(n, dest);
return;
} }
pad3(static_cast<int>(n / 1000), dest);
pad3(static_cast<int>(n % 1000), dest); template<typename T, size_t Buffer_Size>
inline void pad9(T n, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
{
pad_uint(n, 9, dest);
} }
// return fraction of a second of the given time_point. // return fraction of a second of the given time_point.
@ -115,7 +110,8 @@ inline void pad6(size_t n, fmt::basic_memory_buffer<char, Buffer_Size> &dest)
template<typename ToDuration> template<typename ToDuration>
inline ToDuration time_fraction(const log_clock::time_point &tp) inline ToDuration time_fraction(const log_clock::time_point &tp)
{ {
using namespace std::chrono; using std::chrono::duration_cast;
using std::chrono::seconds;
auto duration = tp.time_since_epoch(); auto duration = tp.time_since_epoch();
auto secs = duration_cast<seconds>(duration); auto secs = duration_cast<seconds>(duration);
return duration_cast<ToDuration>(duration) - duration_cast<ToDuration>(secs); return duration_cast<ToDuration>(duration) - duration_cast<ToDuration>(secs);

View File

@ -15,9 +15,8 @@ namespace spdlog {
namespace details { namespace details {
struct log_msg struct log_msg
{ {
log_msg() = default;
log_msg(const std::string *loggers_name, level::level_enum lvl) log_msg(source_loc loc, const std::string *loggers_name, level::level_enum lvl, string_view_t view)
: logger_name(loggers_name) : logger_name(loggers_name)
, level(lvl) , level(lvl)
#ifndef SPDLOG_NO_DATETIME #ifndef SPDLOG_NO_DATETIME
@ -26,23 +25,31 @@ struct log_msg
#ifndef SPDLOG_NO_THREAD_ID #ifndef SPDLOG_NO_THREAD_ID
, thread_id(os::thread_id()) , thread_id(os::thread_id())
, source(loc)
, payload(view)
#endif #endif
{ {
} }
log_msg(const log_msg &other) = delete; log_msg(const std::string *loggers_name, level::level_enum lvl, string_view_t view)
log_msg(log_msg &&other) = delete; : log_msg(source_loc{}, loggers_name, lvl, view)
log_msg &operator=(log_msg &&other) = delete; {
}
log_msg(const log_msg &other) = default;
const std::string *logger_name{nullptr}; const std::string *logger_name{nullptr};
level::level_enum level; level::level_enum level{level::off};
log_clock::time_point time; log_clock::time_point time;
size_t thread_id; size_t thread_id{0};
fmt::memory_buffer raw;
size_t msg_id{0}; size_t msg_id{0};
// info about wrapping the formatted text with color
// wrapping the formatted text with color (updated by pattern_formatter).
mutable size_t color_range_start{0}; mutable size_t color_range_start{0};
mutable size_t color_range_end{0}; mutable size_t color_range_end{0};
source_loc source;
const string_view_t payload;
}; };
} // namespace details } // namespace details
} // namespace spdlog } // namespace spdlog

View File

@ -10,19 +10,23 @@
#include <memory> #include <memory>
#include <string> #include <string>
#define SPDLOG_CATCH_AND_HANDLE \
catch (const std::exception &ex) \
{ \
err_handler_(ex.what()); \
} \
catch (...) \
{ \
err_handler_("Unknown exception in logger"); \
}
// create logger with given name, sinks and the default pattern formatter // create logger with given name, sinks and the default pattern formatter
// all other ctors will call this one // all other ctors will call this one
template<typename It> template<typename It>
inline spdlog::logger::logger(std::string logger_name, const It &begin, const It &end) inline spdlog::logger::logger(std::string logger_name, It begin, It end)
: name_(std::move(logger_name)) : name_(std::move(logger_name))
, sinks_(begin, end) , sinks_(begin, end)
, level_(level::info)
, flush_level_(level::off)
, last_err_time_(0)
, msg_counter_(1) // message counter will start from 1. 0-message id will be
// reserved for controll messages
{ {
err_handler_ = [this](const std::string &msg) { this->default_err_handler_(msg); };
} }
// ctor with sinks as init list // ctor with sinks as init list
@ -49,11 +53,12 @@ inline void spdlog::logger::set_formatter(std::unique_ptr<spdlog::formatter> f)
inline void spdlog::logger::set_pattern(std::string pattern, pattern_time_type time_type) inline void spdlog::logger::set_pattern(std::string pattern, pattern_time_type time_type)
{ {
set_formatter(std::unique_ptr<spdlog::formatter>(new pattern_formatter(std::move(pattern), time_type))); auto new_formatter = details::make_unique<spdlog::pattern_formatter>(std::move(pattern), time_type);
set_formatter(std::move(new_formatter));
} }
template<typename... Args> template<typename... Args>
inline void spdlog::logger::log(level::level_enum lvl, const char *fmt, const Args &... args) inline void spdlog::logger::log(source_loc source, level::level_enum lvl, const char *fmt, const Args &... args)
{ {
if (!should_log(lvl)) if (!should_log(lvl))
{ {
@ -62,15 +67,43 @@ inline void spdlog::logger::log(level::level_enum lvl, const char *fmt, const Ar
try try
{ {
details::log_msg log_msg(&name_, lvl); using details::fmt_helper::to_string_view;
fmt::format_to(log_msg.raw, fmt, args...); fmt::memory_buffer buf;
fmt::format_to(buf, fmt, args...);
details::log_msg log_msg(source, &name_, lvl, to_string_view(buf));
sink_it_(log_msg); sink_it_(log_msg);
} }
SPDLOG_CATCH_AND_HANDLE SPDLOG_CATCH_AND_HANDLE
} }
template<typename... Args> template<typename... Args>
inline void spdlog::logger::log(level::level_enum lvl, const char *fmt, const Args &... args)
{
log(source_loc{}, lvl, fmt, args...);
}
inline void spdlog::logger::log(source_loc source, level::level_enum lvl, const char *msg)
{
if (!should_log(lvl))
{
return;
}
try
{
details::log_msg log_msg(source, &name_, lvl, spdlog::string_view_t(msg));
sink_it_(log_msg);
}
SPDLOG_CATCH_AND_HANDLE
}
inline void spdlog::logger::log(level::level_enum lvl, const char *msg) inline void spdlog::logger::log(level::level_enum lvl, const char *msg)
{
log(source_loc{}, lvl, msg);
}
template<class T, typename std::enable_if<std::is_convertible<T, spdlog::string_view_t>::value, T>::type *>
inline void spdlog::logger::log(source_loc source, level::level_enum lvl, const T &msg)
{ {
if (!should_log(lvl)) if (!should_log(lvl))
{ {
@ -78,15 +111,20 @@ inline void spdlog::logger::log(level::level_enum lvl, const char *msg)
} }
try try
{ {
details::log_msg log_msg(&name_, lvl); details::log_msg log_msg(source, &name_, lvl, msg);
details::fmt_helper::append_c_str(msg, log_msg.raw);
sink_it_(log_msg); sink_it_(log_msg);
} }
SPDLOG_CATCH_AND_HANDLE SPDLOG_CATCH_AND_HANDLE
} }
template<typename T> template<class T, typename std::enable_if<std::is_convertible<T, spdlog::string_view_t>::value, T>::type *>
inline void spdlog::logger::log(level::level_enum lvl, const T &msg) inline void spdlog::logger::log(level::level_enum lvl, const T &msg)
{
log(source_loc{}, lvl, msg);
}
template<class T, typename std::enable_if<!std::is_convertible<T, spdlog::string_view_t>::value, T>::type *>
inline void spdlog::logger::log(source_loc source, level::level_enum lvl, const T &msg)
{ {
if (!should_log(lvl)) if (!should_log(lvl))
{ {
@ -94,13 +132,21 @@ inline void spdlog::logger::log(level::level_enum lvl, const T &msg)
} }
try try
{ {
details::log_msg log_msg(&name_, lvl); using details::fmt_helper::to_string_view;
fmt::format_to(log_msg.raw, "{}", msg); fmt::memory_buffer buf;
fmt::format_to(buf, "{}", msg);
details::log_msg log_msg(source, &name_, lvl, to_string_view(buf));
sink_it_(log_msg); sink_it_(log_msg);
} }
SPDLOG_CATCH_AND_HANDLE SPDLOG_CATCH_AND_HANDLE
} }
template<class T, typename std::enable_if<!std::is_convertible<T, spdlog::string_view_t>::value, T>::type *>
inline void spdlog::logger::log(level::level_enum lvl, const T &msg)
{
log(source_loc{}, lvl, msg);
}
template<typename... Args> template<typename... Args>
inline void spdlog::logger::trace(const char *fmt, const Args &... args) inline void spdlog::logger::trace(const char *fmt, const Args &... args)
{ {
@ -174,27 +220,56 @@ inline void spdlog::logger::critical(const T &msg)
} }
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
inline void wbuf_to_utf8buf(const fmt::wmemory_buffer &wbuf, fmt::memory_buffer &target)
{
int wbuf_size = static_cast<int>(wbuf.size());
if (wbuf_size == 0)
{
return;
}
auto result_size = ::WideCharToMultiByte(CP_UTF8, 0, wbuf.data(), wbuf_size, NULL, 0, NULL, NULL);
if (result_size > 0)
{
target.resize(result_size);
::WideCharToMultiByte(CP_UTF8, 0, wbuf.data(), wbuf_size, &target.data()[0], result_size, NULL, NULL);
}
else
{
throw spdlog::spdlog_ex(fmt::format("WideCharToMultiByte failed. Last error: {}", ::GetLastError()));
}
}
template<typename... Args> template<typename... Args>
inline void spdlog::logger::log(level::level_enum lvl, const wchar_t *fmt, const Args &... args) inline void spdlog::logger::log(source_loc source, level::level_enum lvl, const wchar_t *fmt, const Args &... args)
{ {
if (!should_log(lvl)) if (!should_log(lvl))
{ {
return; return;
} }
decltype(wstring_converter_)::byte_string utf8_string;
try try
{ {
{ // format to wmemory_buffer and convert to utf8
std::lock_guard<std::mutex> lock(wstring_converter_mutex_); using details::fmt_helper::to_string_view;
utf8_string = wstring_converter_.to_bytes(fmt); fmt::wmemory_buffer wbuf;
} fmt::format_to(wbuf, fmt, args...);
log(lvl, utf8_string.c_str(), args...); fmt::memory_buffer buf;
wbuf_to_utf8buf(wbuf, buf);
details::log_msg log_msg(source, &name_, lvl, to_string_view(buf));
sink_it_(log_msg);
} }
SPDLOG_CATCH_AND_HANDLE SPDLOG_CATCH_AND_HANDLE
} }
template<typename... Args>
inline void spdlog::logger::log(level::level_enum lvl, const wchar_t *fmt, const Args &... args)
{
log(source_loc{}, lvl, fmt, args...);
}
template<typename... Args> template<typename... Args>
inline void spdlog::logger::trace(const wchar_t *fmt, const Args &... args) inline void spdlog::logger::trace(const wchar_t *fmt, const Args &... args)
{ {
@ -251,7 +326,7 @@ inline void spdlog::logger::set_error_handler(spdlog::log_err_handler err_handle
err_handler_ = std::move(err_handler); err_handler_ = std::move(err_handler);
} }
inline spdlog::log_err_handler spdlog::logger::error_handler() inline spdlog::log_err_handler spdlog::logger::error_handler() const
{ {
return err_handler_; return err_handler_;
} }
@ -281,6 +356,11 @@ inline bool spdlog::logger::should_flush_(const details::log_msg &msg)
return (msg.level >= flush_level) && (msg.level != level::off); return (msg.level >= flush_level) && (msg.level != level::off);
} }
inline spdlog::level::level_enum spdlog::logger::default_level()
{
return static_cast<spdlog::level::level_enum>(SPDLOG_ACTIVE_LEVEL);
}
inline spdlog::level::level_enum spdlog::logger::level() const inline spdlog::level::level_enum spdlog::logger::level() const
{ {
return static_cast<spdlog::level::level_enum>(level_.load(std::memory_order_relaxed)); return static_cast<spdlog::level::level_enum>(level_.load(std::memory_order_relaxed));
@ -310,7 +390,7 @@ inline void spdlog::logger::sink_it_(details::log_msg &msg)
if (should_flush_(msg)) if (should_flush_(msg))
{ {
flush(); flush_();
} }
} }

View File

@ -57,7 +57,7 @@ namespace spdlog {
namespace details { namespace details {
namespace os { namespace os {
inline spdlog::log_clock::time_point now() inline spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT
{ {
#if defined __linux__ && defined SPDLOG_CLOCK_COARSE #if defined __linux__ && defined SPDLOG_CLOCK_COARSE
@ -70,7 +70,7 @@ inline spdlog::log_clock::time_point now()
return log_clock::now(); return log_clock::now();
#endif #endif
} }
inline std::tm localtime(const std::time_t &time_tt) inline std::tm localtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT
{ {
#ifdef _WIN32 #ifdef _WIN32
@ -83,13 +83,13 @@ inline std::tm localtime(const std::time_t &time_tt)
return tm; return tm;
} }
inline std::tm localtime() inline std::tm localtime() SPDLOG_NOEXCEPT
{ {
std::time_t now_t = time(nullptr); std::time_t now_t = time(nullptr);
return localtime(now_t); return localtime(now_t);
} }
inline std::tm gmtime(const std::time_t &time_tt) inline std::tm gmtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT
{ {
#ifdef _WIN32 #ifdef _WIN32
@ -102,21 +102,11 @@ inline std::tm gmtime(const std::time_t &time_tt)
return tm; return tm;
} }
inline std::tm gmtime() inline std::tm gmtime() SPDLOG_NOEXCEPT
{ {
std::time_t now_t = time(nullptr); std::time_t now_t = time(nullptr);
return gmtime(now_t); return gmtime(now_t);
} }
inline bool operator==(const std::tm &tm1, const std::tm &tm2)
{
return (tm1.tm_sec == tm2.tm_sec && tm1.tm_min == tm2.tm_min && tm1.tm_hour == tm2.tm_hour && tm1.tm_mday == tm2.tm_mday &&
tm1.tm_mon == tm2.tm_mon && tm1.tm_year == tm2.tm_year && tm1.tm_isdst == tm2.tm_isdst);
}
inline bool operator!=(const std::tm &tm1, const std::tm &tm2)
{
return !(tm1 == tm2);
}
// eol definition // eol definition
#if !defined(SPDLOG_EOL) #if !defined(SPDLOG_EOL)
@ -176,7 +166,7 @@ inline bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mod
return *fp == nullptr; return *fp == nullptr;
} }
inline int remove(const filename_t &filename) inline int remove(const filename_t &filename) SPDLOG_NOEXCEPT
{ {
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES)
return _wremove(filename.c_str()); return _wremove(filename.c_str());
@ -185,7 +175,7 @@ inline int remove(const filename_t &filename)
#endif #endif
} }
inline int rename(const filename_t &filename1, const filename_t &filename2) inline int rename(const filename_t &filename1, const filename_t &filename2) SPDLOG_NOEXCEPT
{ {
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES)
return _wrename(filename1.c_str(), filename2.c_str()); return _wrename(filename1.c_str(), filename2.c_str());
@ -195,7 +185,7 @@ inline int rename(const filename_t &filename1, const filename_t &filename2)
} }
// Return if file exists // Return if file exists
inline bool file_exists(const filename_t &filename) inline bool file_exists(const filename_t &filename) SPDLOG_NOEXCEPT
{ {
#ifdef _WIN32 #ifdef _WIN32
#ifdef SPDLOG_WCHAR_FILENAMES #ifdef SPDLOG_WCHAR_FILENAMES
@ -220,10 +210,10 @@ inline size_t filesize(FILE *f)
#if defined(_WIN32) && !defined(__CYGWIN__) #if defined(_WIN32) && !defined(__CYGWIN__)
int fd = _fileno(f); int fd = _fileno(f);
#if _WIN64 // 64 bits #if _WIN64 // 64 bits
struct _stat64 st; __int64 ret = _filelengthi64(fd);
if (_fstat64(fd, &st) == 0) if (ret >= 0)
{ {
return st.st_size; return static_cast<size_t>(ret);
} }
#else // windows 32 bits #else // windows 32 bits
@ -323,7 +313,7 @@ inline int utc_minutes_offset(const std::tm &tm = details::os::localtime())
// Return current thread id as size_t // Return current thread id as size_t
// It exists because the std::this_thread::get_id() is much slower(especially // It exists because the std::this_thread::get_id() is much slower(especially
// under VS 2013) // under VS 2013)
inline size_t _thread_id() inline size_t _thread_id() SPDLOG_NOEXCEPT
{ {
#ifdef _WIN32 #ifdef _WIN32
return static_cast<size_t>(::GetCurrentThreadId()); return static_cast<size_t>(::GetCurrentThreadId());
@ -346,10 +336,9 @@ inline size_t _thread_id()
} }
// Return current thread id as size_t (from thread local storage) // Return current thread id as size_t (from thread local storage)
inline size_t thread_id() inline size_t thread_id() SPDLOG_NOEXCEPT
{ {
#if defined(SPDLOG_DISABLE_TID_CACHING) || (defined(_MSC_VER) && (_MSC_VER < 1900)) || defined(__cplusplus_winrt) || \ #if defined(SPDLOG_NO_TLS)
(defined(__clang__) && !__has_feature(cxx_thread_local))
return _thread_id(); return _thread_id();
#else // cache thread id in tls #else // cache thread id in tls
static thread_local const size_t tid = _thread_id(); static thread_local const size_t tid = _thread_id();
@ -359,7 +348,7 @@ inline size_t thread_id()
// This is avoid msvc issue in sleep_for that happens if the clock changes. // This is avoid msvc issue in sleep_for that happens if the clock changes.
// See https://github.com/gabime/spdlog/issues/609 // See https://github.com/gabime/spdlog/issues/609
inline void sleep_for_millis(int milliseconds) inline void sleep_for_millis(int milliseconds) SPDLOG_NOEXCEPT
{ {
#if defined(_WIN32) #if defined(_WIN32)
::Sleep(milliseconds); ::Sleep(milliseconds);
@ -396,7 +385,7 @@ inline int pid()
// Determine if the terminal supports colors // Determine if the terminal supports colors
// Source: https://github.com/agauniyal/rang/ // Source: https://github.com/agauniyal/rang/
inline bool is_color_terminal() inline bool is_color_terminal() SPDLOG_NOEXCEPT
{ {
#ifdef _WIN32 #ifdef _WIN32
return true; return true;
@ -418,7 +407,7 @@ inline bool is_color_terminal()
// Detrmine if the terminal attached // Detrmine if the terminal attached
// Source: https://github.com/agauniyal/rang/ // Source: https://github.com/agauniyal/rang/
inline bool in_terminal(FILE *file) inline bool in_terminal(FILE *file) SPDLOG_NOEXCEPT
{ {
#ifdef _WIN32 #ifdef _WIN32

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@
// //
// RAII over the owned thread: // RAII over the owned thread:
// creates the thread on construction. // creates the thread on construction.
// stops and joins the thread on destruction. // stops and joins the thread on destruction (if the thread is executing a callback, wait for it to finish first).
#include <chrono> #include <chrono>
#include <condition_variable> #include <condition_variable>

View File

@ -14,6 +14,15 @@
#include "spdlog/details/periodic_worker.h" #include "spdlog/details/periodic_worker.h"
#include "spdlog/logger.h" #include "spdlog/logger.h"
#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER
// support for the default stdout color logger
#ifdef _WIN32
#include "spdlog/sinks/wincolor_sink.h"
#else
#include "spdlog/sinks/ansicolor_sink.h"
#endif
#endif // SPDLOG_DISABLE_DEFAULT_LOGGER
#include <chrono> #include <chrono>
#include <functional> #include <functional>
#include <memory> #include <memory>
@ -33,18 +42,12 @@ public:
void register_logger(std::shared_ptr<logger> new_logger) void register_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_);
auto logger_name = new_logger->name(); register_logger_(std::move(new_logger));
throw_if_exists_(logger_name);
loggers_[logger_name] = std::move(new_logger);
} }
void register_and_init(std::shared_ptr<logger> new_logger) void 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_);
auto logger_name = new_logger->name();
throw_if_exists_(logger_name);
// set the global formatter pattern
new_logger->set_formatter(formatter_->clone()); new_logger->set_formatter(formatter_->clone());
if (err_handler_) if (err_handler_)
@ -55,8 +58,10 @@ public:
new_logger->set_level(level_); new_logger->set_level(level_);
new_logger->flush_on(flush_level_); new_logger->flush_on(flush_level_);
// add to registry if (automatic_registration_)
loggers_[logger_name] = std::move(new_logger); {
register_logger_(std::move(new_logger));
}
} }
std::shared_ptr<logger> get(const std::string &logger_name) std::shared_ptr<logger> get(const std::string &logger_name)
@ -66,6 +71,38 @@ public:
return found == loggers_.end() ? nullptr : found->second; return found == loggers_.end() ? nullptr : found->second;
} }
std::shared_ptr<logger> default_logger()
{
std::lock_guard<std::mutex> lock(logger_map_mutex_);
return default_logger_;
}
// Return raw ptr to the default logger.
// To be used directly by the spdlog default api (e.g. spdlog::info)
// This make the default API faster, but cannot be used concurrently with set_default_logger().
// e.g do not call set_default_logger() from one thread while calling spdlog::info() from another.
logger *get_default_raw()
{
return default_logger_.get();
}
// set default logger.
// default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map.
void set_default_logger(std::shared_ptr<logger> new_default_logger)
{
std::lock_guard<std::mutex> lock(logger_map_mutex_);
// remove previous default logger from the map
if (default_logger_ != nullptr)
{
loggers_.erase(default_logger_->name());
}
if (new_default_logger != nullptr)
{
loggers_[new_default_logger->name()] = new_default_logger;
}
default_logger_ = std::move(new_default_logger);
}
void set_tp(std::shared_ptr<thread_pool> tp) void set_tp(std::shared_ptr<thread_pool> tp)
{ {
std::lock_guard<std::recursive_mutex> lock(tp_mutex_); std::lock_guard<std::recursive_mutex> lock(tp_mutex_);
@ -113,7 +150,7 @@ public:
{ {
std::lock_guard<std::mutex> lock(flusher_mutex_); std::lock_guard<std::mutex> lock(flusher_mutex_);
std::function<void()> clbk = std::bind(&registry::flush_all, this); std::function<void()> clbk = std::bind(&registry::flush_all, this);
periodic_flusher_ = spdlog::make_unique<periodic_worker>(clbk, interval); periodic_flusher_ = details::make_unique<periodic_worker>(clbk, interval);
} }
void set_error_handler(log_err_handler handler) void set_error_handler(log_err_handler handler)
@ -148,15 +185,20 @@ public:
{ {
std::lock_guard<std::mutex> lock(logger_map_mutex_); std::lock_guard<std::mutex> lock(logger_map_mutex_);
loggers_.erase(logger_name); loggers_.erase(logger_name);
if (default_logger_ && default_logger_->name() == logger_name)
{
default_logger_.reset();
}
} }
void drop_all() void drop_all()
{ {
std::lock_guard<std::mutex> lock(logger_map_mutex_); std::lock_guard<std::mutex> lock(logger_map_mutex_);
loggers_.clear(); loggers_.clear();
default_logger_.reset();
} }
// clean all reasources and threads started by the registry // clean all resources and threads started by the registry
void shutdown() void shutdown()
{ {
{ {
@ -177,6 +219,12 @@ public:
return tp_mutex_; return tp_mutex_;
} }
void set_automatic_registration(bool automatic_regsistration)
{
std::lock_guard<std::mutex> lock(logger_map_mutex_);
automatic_registration_ = automatic_regsistration;
}
static registry &instance() static registry &instance()
{ {
static registry s_instance; static registry s_instance;
@ -185,8 +233,22 @@ public:
private: private:
registry() registry()
: formatter_(new pattern_formatter("%+")) : formatter_(new pattern_formatter())
{ {
#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER
// create default logger (ansicolor_stdout_sink_mt or wincolor_stdout_sink_mt in windows).
#ifdef _WIN32
auto color_sink = std::make_shared<sinks::wincolor_stdout_sink_mt>();
#else
auto color_sink = std::make_shared<sinks::ansicolor_stdout_sink_mt>();
#endif
const char *default_logger_name = "";
default_logger_ = std::make_shared<spdlog::logger>(default_logger_name, std::move(color_sink));
loggers_[default_logger_name] = default_logger_;
#endif // SPDLOG_DISABLE_DEFAULT_LOGGER
} }
~registry() = default; ~registry() = default;
@ -199,15 +261,24 @@ private:
} }
} }
void register_logger_(std::shared_ptr<logger> new_logger)
{
auto logger_name = new_logger->name();
throw_if_exists_(logger_name);
loggers_[logger_name] = std::move(new_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_;
std::unordered_map<std::string, std::shared_ptr<logger>> loggers_; std::unordered_map<std::string, std::shared_ptr<logger>> loggers_;
std::unique_ptr<formatter> formatter_; std::unique_ptr<formatter> formatter_;
level::level_enum level_ = level::info; level::level_enum level_ = spdlog::logger::default_level();
level::level_enum flush_level_ = level::off; level::level_enum flush_level_ = level::off;
log_err_handler err_handler_; log_err_handler err_handler_;
std::shared_ptr<thread_pool> tp_; std::shared_ptr<thread_pool> tp_;
std::unique_ptr<periodic_worker> periodic_flusher_; std::unique_ptr<periodic_worker> periodic_flusher_;
std::shared_ptr<logger> default_logger_;
bool automatic_registration_ = true;
}; };
} // namespace details } // namespace details

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "spdlog/details/fmt_helper.h"
#include "spdlog/details/log_msg.h" #include "spdlog/details/log_msg.h"
#include "spdlog/details/mpmc_blocking_q.h" #include "spdlog/details/mpmc_blocking_q.h"
#include "spdlog/details/os.h" #include "spdlog/details/os.h"
@ -32,6 +33,7 @@ struct async_msg
fmt::basic_memory_buffer<char, 176> raw; fmt::basic_memory_buffer<char, 176> raw;
size_t msg_id; size_t msg_id;
source_loc source;
async_logger_ptr worker_ptr; async_logger_ptr worker_ptr;
async_msg() = default; async_msg() = default;
@ -48,6 +50,7 @@ struct async_msg
thread_id(other.thread_id), thread_id(other.thread_id),
raw(move(other.raw)), raw(move(other.raw)),
msg_id(other.msg_id), msg_id(other.msg_id),
source(other.source),
worker_ptr(std::move(other.worker_ptr)) worker_ptr(std::move(other.worker_ptr))
{ {
} }
@ -60,6 +63,7 @@ struct async_msg
thread_id = other.thread_id; thread_id = other.thread_id;
raw = std::move(other.raw); raw = std::move(other.raw);
msg_id = other.msg_id; msg_id = other.msg_id;
source = other.source;
worker_ptr = std::move(other.worker_ptr); worker_ptr = std::move(other.worker_ptr);
return *this; return *this;
} }
@ -69,38 +73,45 @@ struct async_msg
#endif #endif
// construct from log_msg with given type // construct from log_msg with given type
async_msg(async_logger_ptr &&worker, async_msg_type the_type, details::log_msg &&m) async_msg(async_logger_ptr &&worker, async_msg_type the_type, details::log_msg &m)
: msg_type(the_type) : msg_type(the_type)
, level(m.level) , level(m.level)
, time(m.time) , time(m.time)
, thread_id(m.thread_id) , thread_id(m.thread_id)
, msg_id(m.msg_id) , msg_id(m.msg_id)
, worker_ptr(std::forward<async_logger_ptr>(worker)) , source(m.source)
, worker_ptr(std::move(worker))
{ {
fmt_helper::append_buf(m.raw, raw); fmt_helper::append_string_view(m.payload, raw);
} }
async_msg(async_logger_ptr &&worker, async_msg_type the_type) async_msg(async_logger_ptr &&worker, async_msg_type the_type)
: async_msg(std::forward<async_logger_ptr>(worker), the_type, details::log_msg()) : msg_type(the_type)
, level(level::off)
, time()
, thread_id(0)
, msg_id(0)
, source()
, worker_ptr(std::move(worker))
{ {
} }
explicit async_msg(async_msg_type the_type) explicit async_msg(async_msg_type the_type)
: async_msg(nullptr, the_type, details::log_msg()) : async_msg(nullptr, the_type)
{ {
} }
// copy into log_msg // copy into log_msg
void to_log_msg(log_msg &msg) log_msg to_log_msg()
{ {
msg.logger_name = &worker_ptr->name(); log_msg msg(&worker_ptr->name(), level, string_view_t(raw.data(), raw.size()));
msg.level = level;
msg.time = time; msg.time = time;
msg.thread_id = thread_id; msg.thread_id = thread_id;
fmt_helper::append_buf(raw, msg.raw);
msg.msg_id = msg_id; msg.msg_id = msg_id;
msg.source = source;
msg.color_range_start = 0; msg.color_range_start = 0;
msg.color_range_end = 0; msg.color_range_end = 0;
return msg;
} }
}; };
@ -146,9 +157,12 @@ public:
} }
} }
void post_log(async_logger_ptr &&worker_ptr, details::log_msg &&msg, async_overflow_policy overflow_policy) thread_pool(const thread_pool &) = delete;
thread_pool &operator=(thread_pool &&) = delete;
void post_log(async_logger_ptr &&worker_ptr, details::log_msg &msg, async_overflow_policy overflow_policy)
{ {
async_msg async_m(std::forward<async_logger_ptr>(worker_ptr), async_msg_type::log, std::forward<log_msg>(msg)); async_msg async_m(std::move(worker_ptr), async_msg_type::log, msg);
post_async_msg_(std::move(async_m), overflow_policy); post_async_msg_(std::move(async_m), overflow_policy);
} }
@ -200,8 +214,7 @@ private:
{ {
case async_msg_type::log: case async_msg_type::log:
{ {
log_msg msg; auto msg = incoming_async_msg.to_log_msg();
incoming_async_msg.to_log_msg(msg);
incoming_async_msg.worker_ptr->backend_log_(msg); incoming_async_msg.worker_ptr->backend_log_(msg);
return true; return true;
} }

View File

@ -0,0 +1,172 @@
//
// Copyright(c) 2015 Gabi Melman.
// Distributed under the MIT License (http://opensource.org/licenses/MIT)
//
#pragma once
//
// Support for logging binary data as hex
// format flags:
// {:X} - print in uppercase.
// {:s} - don't separate each byte with space.
// {:p} - don't print the position on each line start.
// {:n} - don't split the output to lines.
//
// Examples:
//
// std::vector<char> v(200, 0x0b);
// logger->info("Some buffer {}", spdlog::to_hex(v));
// char buf[128];
// logger->info("Some buffer {:X}", spdlog::to_hex(std::begin(buf), std::end(buf)));
namespace spdlog {
namespace details {
template<typename It>
class bytes_range
{
public:
bytes_range(It range_begin, It range_end)
: begin_(range_begin)
, end_(range_end)
{
}
It begin() const
{
return begin_;
}
It end() const
{
return end_;
}
private:
It begin_, end_;
};
} // namespace details
// create a bytes_range that wraps the given container
template<typename Container>
inline details::bytes_range<typename Container::const_iterator> to_hex(const Container &container)
{
static_assert(sizeof(typename Container::value_type) == 1, "sizeof(Container::value_type) != 1");
using Iter = typename Container::const_iterator;
return details::bytes_range<Iter>(std::begin(container), std::end(container));
}
// create bytes_range from ranges
template<typename It>
inline details::bytes_range<It> to_hex(const It range_begin, const It range_end)
{
return details::bytes_range<It>(range_begin, range_end);
}
} // namespace spdlog
namespace fmt {
template<typename T>
struct formatter<spdlog::details::bytes_range<T>>
{
const std::size_t line_size = 100;
const char delimiter = ' ';
bool put_newlines = true;
bool put_delimiters = true;
bool use_uppercase = false;
bool put_positions = true; // position on start of each line
// parse the format string flags
template<typename ParseContext>
auto parse(ParseContext &ctx) -> decltype(ctx.begin())
{
auto it = ctx.begin();
while (*it && *it != '}')
{
switch (*it)
{
case 'X':
use_uppercase = true;
break;
case 's':
put_delimiters = false;
break;
case 'p':
put_positions = false;
break;
case 'n':
put_newlines = false;
break;
}
++it;
}
return it;
}
// format the given bytes range as hex
template<typename FormatContext, typename Container>
auto format(const spdlog::details::bytes_range<Container> &the_range, FormatContext &ctx) -> decltype(ctx.out())
{
SPDLOG_CONSTEXPR const char *hex_upper = "0123456789ABCDEF";
SPDLOG_CONSTEXPR const char *hex_lower = "0123456789abcdef";
const char *hex_chars = use_uppercase ? hex_upper : hex_lower;
std::size_t pos = 0;
std::size_t column = line_size;
auto inserter = ctx.begin();
for (auto &item : the_range)
{
auto ch = static_cast<unsigned char>(item);
pos++;
if (put_newlines && column >= line_size)
{
column = put_newline(inserter, pos);
// put first byte without delimiter in front of it
*inserter++ = hex_chars[(ch >> 4) & 0x0f];
*inserter++ = hex_chars[ch & 0x0f];
column += 2;
continue;
}
if (put_delimiters)
{
*inserter++ = delimiter;
++column;
}
*inserter++ = hex_chars[(ch >> 4) & 0x0f];
*inserter++ = hex_chars[ch & 0x0f];
column += 2;
}
return inserter;
}
// put newline(and position header)
// return the next column
template<typename It>
std::size_t put_newline(It inserter, std::size_t pos)
{
#ifdef _WIN32
*inserter++ = '\r';
#endif
*inserter++ = '\n';
if (put_positions)
{
fmt::format_to(inserter, "{:<04X}: ", pos - 1);
return 7;
}
else
{
return 1;
}
}
};
} // namespace fmt

View File

@ -0,0 +1,452 @@
// Formatting library for C++ - chrono support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_CHRONO_H_
#define FMT_CHRONO_H_
#include "format.h"
#include "locale.h"
#include <chrono>
#include <ctime>
#include <locale>
#include <sstream>
FMT_BEGIN_NAMESPACE
namespace internal{
enum class numeric_system {
standard,
// Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale.
alternative
};
// Parses a put_time-like format string and invokes handler actions.
template <typename Char, typename Handler>
FMT_CONSTEXPR const Char *parse_chrono_format(
const Char *begin, const Char *end, Handler &&handler) {
auto ptr = begin;
while (ptr != end) {
auto c = *ptr;
if (c == '}') break;
if (c != '%') {
++ptr;
continue;
}
if (begin != ptr)
handler.on_text(begin, ptr);
++ptr; // consume '%'
if (ptr == end)
throw format_error("invalid format");
c = *ptr++;
switch (c) {
case '%':
handler.on_text(ptr - 1, ptr);
break;
case 'n': {
const char newline[] = "\n";
handler.on_text(newline, newline + 1);
break;
}
case 't': {
const char tab[] = "\t";
handler.on_text(tab, tab + 1);
break;
}
// Day of the week:
case 'a':
handler.on_abbr_weekday();
break;
case 'A':
handler.on_full_weekday();
break;
case 'w':
handler.on_dec0_weekday(numeric_system::standard);
break;
case 'u':
handler.on_dec1_weekday(numeric_system::standard);
break;
// Month:
case 'b':
handler.on_abbr_month();
break;
case 'B':
handler.on_full_month();
break;
// Hour, minute, second:
case 'H':
handler.on_24_hour(numeric_system::standard);
break;
case 'I':
handler.on_12_hour(numeric_system::standard);
break;
case 'M':
handler.on_minute(numeric_system::standard);
break;
case 'S':
handler.on_second(numeric_system::standard);
break;
// Other:
case 'c':
handler.on_datetime(numeric_system::standard);
break;
case 'x':
handler.on_loc_date(numeric_system::standard);
break;
case 'X':
handler.on_loc_time(numeric_system::standard);
break;
case 'D':
handler.on_us_date();
break;
case 'F':
handler.on_iso_date();
break;
case 'r':
handler.on_12_hour_time();
break;
case 'R':
handler.on_24_hour_time();
break;
case 'T':
handler.on_iso_time();
break;
case 'p':
handler.on_am_pm();
break;
case 'z':
handler.on_utc_offset();
break;
case 'Z':
handler.on_tz_name();
break;
// Alternative representation:
case 'E': {
if (ptr == end)
throw format_error("invalid format");
c = *ptr++;
switch (c) {
case 'c':
handler.on_datetime(numeric_system::alternative);
break;
case 'x':
handler.on_loc_date(numeric_system::alternative);
break;
case 'X':
handler.on_loc_time(numeric_system::alternative);
break;
default:
throw format_error("invalid format");
}
break;
}
case 'O':
if (ptr == end)
throw format_error("invalid format");
c = *ptr++;
switch (c) {
case 'w':
handler.on_dec0_weekday(numeric_system::alternative);
break;
case 'u':
handler.on_dec1_weekday(numeric_system::alternative);
break;
case 'H':
handler.on_24_hour(numeric_system::alternative);
break;
case 'I':
handler.on_12_hour(numeric_system::alternative);
break;
case 'M':
handler.on_minute(numeric_system::alternative);
break;
case 'S':
handler.on_second(numeric_system::alternative);
break;
default:
throw format_error("invalid format");
}
break;
default:
throw format_error("invalid format");
}
begin = ptr;
}
if (begin != ptr)
handler.on_text(begin, ptr);
return ptr;
}
struct chrono_format_checker {
void report_no_date() { throw format_error("no date"); }
template <typename Char>
void on_text(const Char *, const Char *) {}
void on_abbr_weekday() { report_no_date(); }
void on_full_weekday() { report_no_date(); }
void on_dec0_weekday(numeric_system) { report_no_date(); }
void on_dec1_weekday(numeric_system) { report_no_date(); }
void on_abbr_month() { report_no_date(); }
void on_full_month() { report_no_date(); }
void on_24_hour(numeric_system) {}
void on_12_hour(numeric_system) {}
void on_minute(numeric_system) {}
void on_second(numeric_system) {}
void on_datetime(numeric_system) { report_no_date(); }
void on_loc_date(numeric_system) { report_no_date(); }
void on_loc_time(numeric_system) { report_no_date(); }
void on_us_date() { report_no_date(); }
void on_iso_date() { report_no_date(); }
void on_12_hour_time() {}
void on_24_hour_time() {}
void on_iso_time() {}
void on_am_pm() {}
void on_utc_offset() { report_no_date(); }
void on_tz_name() { report_no_date(); }
};
template <typename Int>
inline int to_int(Int value) {
FMT_ASSERT(value >= (std::numeric_limits<int>::min)() &&
value <= (std::numeric_limits<int>::max)(), "invalid value");
return static_cast<int>(value);
}
template <typename FormatContext, typename OutputIt>
struct chrono_formatter {
FormatContext &context;
OutputIt out;
std::chrono::seconds s;
std::chrono::milliseconds ms;
typedef typename FormatContext::char_type char_type;
explicit chrono_formatter(FormatContext &ctx, OutputIt o)
: context(ctx), out(o) {}
int hour() const { return to_int((s.count() / 3600) % 24); }
int hour12() const {
auto hour = to_int((s.count() / 3600) % 12);
return hour > 0 ? hour : 12;
}
int minute() const { return to_int((s.count() / 60) % 60); }
int second() const { return to_int(s.count() % 60); }
std::tm time() const {
auto time = std::tm();
time.tm_hour = hour();
time.tm_min = minute();
time.tm_sec = second();
return time;
}
void write(int value, int width) {
typedef typename int_traits<int>::main_type main_type;
main_type n = to_unsigned(value);
int num_digits = internal::count_digits(n);
if (width > num_digits)
out = std::fill_n(out, width - num_digits, '0');
out = format_decimal<char_type>(out, n, num_digits);
}
void format_localized(const tm &time, const char *format) {
auto locale = context.locale().template get<std::locale>();
auto &facet = std::use_facet<std::time_put<char_type>>(locale);
std::basic_ostringstream<char_type> os;
os.imbue(locale);
facet.put(os, os, ' ', &time, format, format + std::strlen(format));
auto str = os.str();
std::copy(str.begin(), str.end(), out);
}
void on_text(const char_type *begin, const char_type *end) {
std::copy(begin, end, out);
}
// These are not implemented because durations don't have date information.
void on_abbr_weekday() {}
void on_full_weekday() {}
void on_dec0_weekday(numeric_system) {}
void on_dec1_weekday(numeric_system) {}
void on_abbr_month() {}
void on_full_month() {}
void on_datetime(numeric_system) {}
void on_loc_date(numeric_system) {}
void on_loc_time(numeric_system) {}
void on_us_date() {}
void on_iso_date() {}
void on_utc_offset() {}
void on_tz_name() {}
void on_24_hour(numeric_system ns) {
if (ns == numeric_system::standard)
return write(hour(), 2);
auto time = tm();
time.tm_hour = hour();
format_localized(time, "%OH");
}
void on_12_hour(numeric_system ns) {
if (ns == numeric_system::standard)
return write(hour12(), 2);
auto time = tm();
time.tm_hour = hour();
format_localized(time, "%OI");
}
void on_minute(numeric_system ns) {
if (ns == numeric_system::standard)
return write(minute(), 2);
auto time = tm();
time.tm_min = minute();
format_localized(time, "%OM");
}
void on_second(numeric_system ns) {
if (ns == numeric_system::standard) {
write(second(), 2);
if (ms != std::chrono::milliseconds(0)) {
*out++ = '.';
write(to_int(ms.count()), 3);
}
return;
}
auto time = tm();
time.tm_sec = second();
format_localized(time, "%OS");
}
void on_12_hour_time() { format_localized(time(), "%r"); }
void on_24_hour_time() {
write(hour(), 2);
*out++ = ':';
write(minute(), 2);
}
void on_iso_time() {
on_24_hour_time();
*out++ = ':';
write(second(), 2);
}
void on_am_pm() { format_localized(time(), "%p"); }
};
} // namespace internal
template <typename Period> FMT_CONSTEXPR const char *get_units() {
return FMT_NULL;
}
template <> FMT_CONSTEXPR const char *get_units<std::atto>() { return "as"; }
template <> FMT_CONSTEXPR const char *get_units<std::femto>() { return "fs"; }
template <> FMT_CONSTEXPR const char *get_units<std::pico>() { return "ps"; }
template <> FMT_CONSTEXPR const char *get_units<std::nano>() { return "ns"; }
template <> FMT_CONSTEXPR const char *get_units<std::micro>() { return "µs"; }
template <> FMT_CONSTEXPR const char *get_units<std::milli>() { return "ms"; }
template <> FMT_CONSTEXPR const char *get_units<std::centi>() { return "cs"; }
template <> FMT_CONSTEXPR const char *get_units<std::deci>() { return "ds"; }
template <> FMT_CONSTEXPR const char *get_units<std::ratio<1>>() { return "s"; }
template <> FMT_CONSTEXPR const char *get_units<std::deca>() { return "das"; }
template <> FMT_CONSTEXPR const char *get_units<std::hecto>() { return "hs"; }
template <> FMT_CONSTEXPR const char *get_units<std::kilo>() { return "ks"; }
template <> FMT_CONSTEXPR const char *get_units<std::mega>() { return "Ms"; }
template <> FMT_CONSTEXPR const char *get_units<std::giga>() { return "Gs"; }
template <> FMT_CONSTEXPR const char *get_units<std::tera>() { return "Ts"; }
template <> FMT_CONSTEXPR const char *get_units<std::peta>() { return "Ps"; }
template <> FMT_CONSTEXPR const char *get_units<std::exa>() { return "Es"; }
template <> FMT_CONSTEXPR const char *get_units<std::ratio<60>>() {
return "m";
}
template <> FMT_CONSTEXPR const char *get_units<std::ratio<3600>>() {
return "h";
}
template <typename Rep, typename Period, typename Char>
struct formatter<std::chrono::duration<Rep, Period>, Char> {
private:
align_spec spec;
internal::arg_ref<Char> width_ref;
mutable basic_string_view<Char> format_str;
typedef std::chrono::duration<Rep, Period> duration;
struct spec_handler {
formatter &f;
basic_parse_context<Char> &context;
typedef internal::arg_ref<Char> arg_ref_type;
template <typename Id>
FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) {
context.check_arg_id(arg_id);
return arg_ref_type(arg_id);
}
FMT_CONSTEXPR arg_ref_type make_arg_ref(internal::auto_id) {
return arg_ref_type(context.next_arg_id());
}
void on_error(const char *msg) { throw format_error(msg); }
void on_fill(Char fill) { f.spec.fill_ = fill; }
void on_align(alignment align) { f.spec.align_ = align; }
void on_width(unsigned width) { f.spec.width_ = width; }
template <typename Id>
void on_dynamic_width(Id arg_id) {
f.width_ref = make_arg_ref(arg_id);
}
};
public:
formatter() : spec() {}
FMT_CONSTEXPR auto parse(basic_parse_context<Char> &ctx)
-> decltype(ctx.begin()) {
auto begin = ctx.begin(), end = ctx.end();
if (begin == end) return begin;
spec_handler handler{*this, ctx};
begin = internal::parse_align(begin, end, handler);
if (begin == end) return begin;
begin = internal::parse_width(begin, end, handler);
end = parse_chrono_format(begin, end, internal::chrono_format_checker());
format_str = basic_string_view<Char>(&*begin, internal::to_unsigned(end - begin));
return end;
}
template <typename FormatContext>
auto format(const duration &d, FormatContext &ctx)
-> decltype(ctx.out()) {
auto begin = format_str.begin(), end = format_str.end();
memory_buffer buf;
typedef output_range<decltype(ctx.out()), Char> range;
basic_writer<range> w(range(ctx.out()));
if (begin == end || *begin == '}') {
if (const char *unit = get_units<Period>())
format_to(buf, "{}{}", d.count(), unit);
else if (Period::den == 1)
format_to(buf, "{}[{}]s", d.count(), Period::num);
else
format_to(buf, "{}[{}/{}]s", d.count(), Period::num, Period::den);
internal::handle_dynamic_spec<internal::width_checker>(
spec.width_, width_ref, ctx);
} else {
auto out = std::back_inserter(buf);
internal::chrono_formatter<FormatContext, decltype(out)> f(ctx, out);
f.s = std::chrono::duration_cast<std::chrono::seconds>(d);
f.ms = std::chrono::duration_cast<std::chrono::milliseconds>(d - f.s);
parse_chrono_format(begin, end, f);
}
w.write(buf.data(), buf.size(), spec);
return w.out();
}
};
FMT_END_NAMESPACE
#endif // FMT_CHRONO_H_

View File

@ -0,0 +1,577 @@
// Formatting library for C++ - color support
//
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
// All rights reserved.
//
// For the license information refer to format.h.
#ifndef FMT_COLOR_H_
#define FMT_COLOR_H_
#include "format.h"
FMT_BEGIN_NAMESPACE
#ifdef FMT_DEPRECATED_COLORS
// color and (v)print_colored are deprecated.
enum color { black, red, green, yellow, blue, magenta, cyan, white };
FMT_API void vprint_colored(color c, string_view format, format_args args);
FMT_API void vprint_colored(color c, wstring_view format, wformat_args args);
template <typename... Args>
inline void print_colored(color c, string_view format_str,
const Args & ... args) {
vprint_colored(c, format_str, make_format_args(args...));
}
template <typename... Args>
inline void print_colored(color c, wstring_view format_str,
const Args & ... args) {
vprint_colored(c, format_str, make_format_args<wformat_context>(args...));
}
inline void vprint_colored(color c, string_view format, format_args args) {
char escape[] = "\x1b[30m";
escape[3] = static_cast<char>('0' + c);
std::fputs(escape, stdout);
vprint(format, args);
std::fputs(internal::data::RESET_COLOR, stdout);
}
inline void vprint_colored(color c, wstring_view format, wformat_args args) {
wchar_t escape[] = L"\x1b[30m";
escape[3] = static_cast<wchar_t>('0' + c);
std::fputws(escape, stdout);
vprint(format, args);
std::fputws(internal::data::WRESET_COLOR, stdout);
}
#else
enum class color : uint32_t {
alice_blue = 0xF0F8FF, // rgb(240,248,255)
antique_white = 0xFAEBD7, // rgb(250,235,215)
aqua = 0x00FFFF, // rgb(0,255,255)
aquamarine = 0x7FFFD4, // rgb(127,255,212)
azure = 0xF0FFFF, // rgb(240,255,255)
beige = 0xF5F5DC, // rgb(245,245,220)
bisque = 0xFFE4C4, // rgb(255,228,196)
black = 0x000000, // rgb(0,0,0)
blanched_almond = 0xFFEBCD, // rgb(255,235,205)
blue = 0x0000FF, // rgb(0,0,255)
blue_violet = 0x8A2BE2, // rgb(138,43,226)
brown = 0xA52A2A, // rgb(165,42,42)
burly_wood = 0xDEB887, // rgb(222,184,135)
cadet_blue = 0x5F9EA0, // rgb(95,158,160)
chartreuse = 0x7FFF00, // rgb(127,255,0)
chocolate = 0xD2691E, // rgb(210,105,30)
coral = 0xFF7F50, // rgb(255,127,80)
cornflower_blue = 0x6495ED, // rgb(100,149,237)
cornsilk = 0xFFF8DC, // rgb(255,248,220)
crimson = 0xDC143C, // rgb(220,20,60)
cyan = 0x00FFFF, // rgb(0,255,255)
dark_blue = 0x00008B, // rgb(0,0,139)
dark_cyan = 0x008B8B, // rgb(0,139,139)
dark_golden_rod = 0xB8860B, // rgb(184,134,11)
dark_gray = 0xA9A9A9, // rgb(169,169,169)
dark_green = 0x006400, // rgb(0,100,0)
dark_khaki = 0xBDB76B, // rgb(189,183,107)
dark_magenta = 0x8B008B, // rgb(139,0,139)
dark_olive_green = 0x556B2F, // rgb(85,107,47)
dark_orange = 0xFF8C00, // rgb(255,140,0)
dark_orchid = 0x9932CC, // rgb(153,50,204)
dark_red = 0x8B0000, // rgb(139,0,0)
dark_salmon = 0xE9967A, // rgb(233,150,122)
dark_sea_green = 0x8FBC8F, // rgb(143,188,143)
dark_slate_blue = 0x483D8B, // rgb(72,61,139)
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79)
dark_turquoise = 0x00CED1, // rgb(0,206,209)
dark_violet = 0x9400D3, // rgb(148,0,211)
deep_pink = 0xFF1493, // rgb(255,20,147)
deep_sky_blue = 0x00BFFF, // rgb(0,191,255)
dim_gray = 0x696969, // rgb(105,105,105)
dodger_blue = 0x1E90FF, // rgb(30,144,255)
fire_brick = 0xB22222, // rgb(178,34,34)
floral_white = 0xFFFAF0, // rgb(255,250,240)
forest_green = 0x228B22, // rgb(34,139,34)
fuchsia = 0xFF00FF, // rgb(255,0,255)
gainsboro = 0xDCDCDC, // rgb(220,220,220)
ghost_white = 0xF8F8FF, // rgb(248,248,255)
gold = 0xFFD700, // rgb(255,215,0)
golden_rod = 0xDAA520, // rgb(218,165,32)
gray = 0x808080, // rgb(128,128,128)
green = 0x008000, // rgb(0,128,0)
green_yellow = 0xADFF2F, // rgb(173,255,47)
honey_dew = 0xF0FFF0, // rgb(240,255,240)
hot_pink = 0xFF69B4, // rgb(255,105,180)
indian_red = 0xCD5C5C, // rgb(205,92,92)
indigo = 0x4B0082, // rgb(75,0,130)
ivory = 0xFFFFF0, // rgb(255,255,240)
khaki = 0xF0E68C, // rgb(240,230,140)
lavender = 0xE6E6FA, // rgb(230,230,250)
lavender_blush = 0xFFF0F5, // rgb(255,240,245)
lawn_green = 0x7CFC00, // rgb(124,252,0)
lemon_chiffon = 0xFFFACD, // rgb(255,250,205)
light_blue = 0xADD8E6, // rgb(173,216,230)
light_coral = 0xF08080, // rgb(240,128,128)
light_cyan = 0xE0FFFF, // rgb(224,255,255)
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210)
light_gray = 0xD3D3D3, // rgb(211,211,211)
light_green = 0x90EE90, // rgb(144,238,144)
light_pink = 0xFFB6C1, // rgb(255,182,193)
light_salmon = 0xFFA07A, // rgb(255,160,122)
light_sea_green = 0x20B2AA, // rgb(32,178,170)
light_sky_blue = 0x87CEFA, // rgb(135,206,250)
light_slate_gray = 0x778899, // rgb(119,136,153)
light_steel_blue = 0xB0C4DE, // rgb(176,196,222)
light_yellow = 0xFFFFE0, // rgb(255,255,224)
lime = 0x00FF00, // rgb(0,255,0)
lime_green = 0x32CD32, // rgb(50,205,50)
linen = 0xFAF0E6, // rgb(250,240,230)
magenta = 0xFF00FF, // rgb(255,0,255)
maroon = 0x800000, // rgb(128,0,0)
medium_aquamarine = 0x66CDAA, // rgb(102,205,170)
medium_blue = 0x0000CD, // rgb(0,0,205)
medium_orchid = 0xBA55D3, // rgb(186,85,211)
medium_purple = 0x9370DB, // rgb(147,112,219)
medium_sea_green = 0x3CB371, // rgb(60,179,113)
medium_slate_blue = 0x7B68EE, // rgb(123,104,238)
medium_spring_green = 0x00FA9A, // rgb(0,250,154)
medium_turquoise = 0x48D1CC, // rgb(72,209,204)
medium_violet_red = 0xC71585, // rgb(199,21,133)
midnight_blue = 0x191970, // rgb(25,25,112)
mint_cream = 0xF5FFFA, // rgb(245,255,250)
misty_rose = 0xFFE4E1, // rgb(255,228,225)
moccasin = 0xFFE4B5, // rgb(255,228,181)
navajo_white = 0xFFDEAD, // rgb(255,222,173)
navy = 0x000080, // rgb(0,0,128)
old_lace = 0xFDF5E6, // rgb(253,245,230)
olive = 0x808000, // rgb(128,128,0)
olive_drab = 0x6B8E23, // rgb(107,142,35)
orange = 0xFFA500, // rgb(255,165,0)
orange_red = 0xFF4500, // rgb(255,69,0)
orchid = 0xDA70D6, // rgb(218,112,214)
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170)
pale_green = 0x98FB98, // rgb(152,251,152)
pale_turquoise = 0xAFEEEE, // rgb(175,238,238)
pale_violet_red = 0xDB7093, // rgb(219,112,147)
papaya_whip = 0xFFEFD5, // rgb(255,239,213)
peach_puff = 0xFFDAB9, // rgb(255,218,185)
peru = 0xCD853F, // rgb(205,133,63)
pink = 0xFFC0CB, // rgb(255,192,203)
plum = 0xDDA0DD, // rgb(221,160,221)
powder_blue = 0xB0E0E6, // rgb(176,224,230)
purple = 0x800080, // rgb(128,0,128)
rebecca_purple = 0x663399, // rgb(102,51,153)
red = 0xFF0000, // rgb(255,0,0)
rosy_brown = 0xBC8F8F, // rgb(188,143,143)
royal_blue = 0x4169E1, // rgb(65,105,225)
saddle_brown = 0x8B4513, // rgb(139,69,19)
salmon = 0xFA8072, // rgb(250,128,114)
sandy_brown = 0xF4A460, // rgb(244,164,96)
sea_green = 0x2E8B57, // rgb(46,139,87)
sea_shell = 0xFFF5EE, // rgb(255,245,238)
sienna = 0xA0522D, // rgb(160,82,45)
silver = 0xC0C0C0, // rgb(192,192,192)
sky_blue = 0x87CEEB, // rgb(135,206,235)
slate_blue = 0x6A5ACD, // rgb(106,90,205)
slate_gray = 0x708090, // rgb(112,128,144)
snow = 0xFFFAFA, // rgb(255,250,250)
spring_green = 0x00FF7F, // rgb(0,255,127)
steel_blue = 0x4682B4, // rgb(70,130,180)
tan = 0xD2B48C, // rgb(210,180,140)
teal = 0x008080, // rgb(0,128,128)
thistle = 0xD8BFD8, // rgb(216,191,216)
tomato = 0xFF6347, // rgb(255,99,71)
turquoise = 0x40E0D0, // rgb(64,224,208)
violet = 0xEE82EE, // rgb(238,130,238)
wheat = 0xF5DEB3, // rgb(245,222,179)
white = 0xFFFFFF, // rgb(255,255,255)
white_smoke = 0xF5F5F5, // rgb(245,245,245)
yellow = 0xFFFF00, // rgb(255,255,0)
yellow_green = 0x9ACD32 // rgb(154,205,50)
}; // enum class color
enum class terminal_color : uint8_t {
black = 30,
red,
green,
yellow,
blue,
magenta,
cyan,
white,
bright_black = 90,
bright_red,
bright_green,
bright_yellow,
bright_blue,
bright_magenta,
bright_cyan,
bright_white
}; // enum class terminal_color
enum class emphasis : uint8_t {
bold = 1,
italic = 1 << 1,
underline = 1 << 2,
strikethrough = 1 << 3
}; // enum class emphasis
// rgb is a struct for red, green and blue colors.
// We use rgb as name because some editors will show it as color direct in the
// editor.
struct rgb {
FMT_CONSTEXPR_DECL rgb() : r(0), g(0), b(0) {}
FMT_CONSTEXPR_DECL rgb(uint8_t r_, uint8_t g_, uint8_t b_)
: r(r_), g(g_), b(b_) {}
FMT_CONSTEXPR_DECL rgb(uint32_t hex)
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b((hex) & 0xFF) {}
FMT_CONSTEXPR_DECL rgb(color hex)
: r((uint32_t(hex) >> 16) & 0xFF), g((uint32_t(hex) >> 8) & 0xFF),
b(uint32_t(hex) & 0xFF) {}
uint8_t r;
uint8_t g;
uint8_t b;
};
namespace internal {
// color is a struct of either a rgb color or a terminal color.
struct color_type {
FMT_CONSTEXPR color_type() FMT_NOEXCEPT
: is_rgb(), value{} {}
FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT
: is_rgb(true), value{} {
value.rgb_color = static_cast<uint32_t>(rgb_color);
}
FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT
: is_rgb(true), value{} {
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16)
| (static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
}
FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT
: is_rgb(), value{} {
value.term_color = static_cast<uint8_t>(term_color);
}
bool is_rgb;
union color_union {
uint8_t term_color;
uint32_t rgb_color;
} value;
};
} // namespace internal
// Experimental text formatting support.
class text_style {
public:
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
: set_foreground_color(), set_background_color(), ems(em) {}
FMT_CONSTEXPR text_style &operator|=(const text_style &rhs) {
if (!set_foreground_color) {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
throw format_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)
throw format_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;
}
friend FMT_CONSTEXPR
text_style operator|(text_style lhs, const text_style &rhs) {
return lhs |= rhs;
}
FMT_CONSTEXPR text_style &operator&=(const text_style &rhs) {
if (!set_foreground_color) {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
throw format_error("can't AND 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)
throw format_error("can't AND 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;
}
friend FMT_CONSTEXPR
text_style operator&(text_style lhs, const text_style &rhs) {
return lhs &= rhs;
}
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
return set_foreground_color;
}
FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT {
return set_background_color;
}
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
return static_cast<uint8_t>(ems) != 0;
}
FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT {
assert(has_foreground() && "no foreground specified for this style");
return foreground_color;
}
FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT {
assert(has_background() && "no background specified for this style");
return background_color;
}
FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT {
assert(has_emphasis() && "no emphasis specified for this style");
return ems;
}
private:
FMT_CONSTEXPR text_style(bool is_foreground,
internal::color_type text_color) FMT_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_DECL text_style fg(internal::color_type foreground)
FMT_NOEXCEPT;
friend FMT_CONSTEXPR_DECL text_style bg(internal::color_type background)
FMT_NOEXCEPT;
internal::color_type foreground_color;
internal::color_type background_color;
bool set_foreground_color;
bool set_background_color;
emphasis ems;
};
FMT_CONSTEXPR text_style fg(internal::color_type foreground) FMT_NOEXCEPT {
return text_style(/*is_foreground=*/true, foreground);
}
FMT_CONSTEXPR text_style bg(internal::color_type background) FMT_NOEXCEPT {
return text_style(/*is_foreground=*/false, background);
}
FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT {
return text_style(lhs) | rhs;
}
namespace internal {
template <typename Char>
struct ansi_color_escape {
FMT_CONSTEXPR ansi_color_escape(internal::color_type text_color,
const char * esc) FMT_NOEXCEPT {
// If we have a terminal color, we need to output another escape code
// sequence.
if (!text_color.is_rgb) {
bool is_background = esc == internal::data::BACKGROUND_COLOR;
uint32_t value = text_color.value.term_color;
// Background ASCII codes are the same as the foreground ones but with
// 10 more.
if (is_background)
value += 10u;
std::size_t index = 0;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
if (value >= 100u) {
buffer[index++] = static_cast<Char>('1');
value %= 100u;
}
buffer[index++] = static_cast<Char>('0' + value / 10u);
buffer[index++] = static_cast<Char>('0' + value % 10u);
buffer[index++] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>('\0');
return;
}
for (int i = 0; i < 7; i++) {
buffer[i] = static_cast<Char>(esc[i]);
}
rgb color(text_color.value.rgb_color);
to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm');
buffer[19] = static_cast<Char>(0);
}
FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT {
uint8_t em_codes[4] = {};
uint8_t em_bits = static_cast<uint8_t>(em);
if (em_bits & static_cast<uint8_t>(emphasis::bold))
em_codes[0] = 1;
if (em_bits & static_cast<uint8_t>(emphasis::italic))
em_codes[1] = 3;
if (em_bits & static_cast<uint8_t>(emphasis::underline))
em_codes[2] = 4;
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
em_codes[3] = 9;
std::size_t index = 0;
for (int i = 0; i < 4; ++i) {
if (!em_codes[i])
continue;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('m');
}
buffer[index++] = static_cast<Char>(0);
}
FMT_CONSTEXPR operator const Char *() const FMT_NOEXCEPT { return buffer; }
private:
Char buffer[7u + 3u * 4u + 1u];
static FMT_CONSTEXPR void to_esc(uint8_t c, Char *out,
char delimiter) FMT_NOEXCEPT {
out[0] = static_cast<Char>('0' + c / 100);
out[1] = static_cast<Char>('0' + c / 10 % 10);
out[2] = static_cast<Char>('0' + c % 10);
out[3] = static_cast<Char>(delimiter);
}
};
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char>
make_foreground_color(internal::color_type foreground) FMT_NOEXCEPT {
return ansi_color_escape<Char>(foreground, internal::data::FOREGROUND_COLOR);
}
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char>
make_background_color(internal::color_type background) FMT_NOEXCEPT {
return ansi_color_escape<Char>(background, internal::data::BACKGROUND_COLOR);
}
template <typename Char>
FMT_CONSTEXPR ansi_color_escape<Char>
make_emphasis(emphasis em) FMT_NOEXCEPT {
return ansi_color_escape<Char>(em);
}
template <typename Char>
inline void fputs(const Char *chars, FILE *stream) FMT_NOEXCEPT {
std::fputs(chars, stream);
}
template <>
inline void fputs<wchar_t>(const wchar_t *chars, FILE *stream) FMT_NOEXCEPT {
std::fputws(chars, stream);
}
template <typename Char>
inline void reset_color(FILE *stream) FMT_NOEXCEPT {
fputs(internal::data::RESET_COLOR, stream);
}
template <>
inline void reset_color<wchar_t>(FILE *stream) FMT_NOEXCEPT {
fputs(internal::data::WRESET_COLOR, stream);
}
// The following specialiazation disables using std::FILE as a character type,
// which is needed because or else
// fmt::print(stderr, fmt::emphasis::bold, "");
// would take stderr (a std::FILE *) as the format string.
template <>
struct is_string<std::FILE *> : std::false_type {};
template <>
struct is_string<const std::FILE *> : std::false_type {};
} // namespace internal
template <
typename S, typename Char = typename internal::char_t<S>::type>
void vprint(std::FILE *f, const text_style &ts, const S &format,
basic_format_args<typename buffer_context<Char>::type> args) {
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
internal::fputs<Char>(
internal::make_emphasis<Char>(ts.get_emphasis()), f);
}
if (ts.has_foreground()) {
has_style = true;
internal::fputs<Char>(
internal::make_foreground_color<Char>(ts.get_foreground()), f);
}
if (ts.has_background()) {
has_style = true;
internal::fputs<Char>(
internal::make_background_color<Char>(ts.get_background()), f);
}
vprint(f, format, args);
if (has_style) {
internal::reset_color<Char>(f);
}
}
/**
Formats a string and prints it to the specified file stream using ANSI
escape sequences to specify text formatting.
Example:
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename String, typename... Args>
typename std::enable_if<internal::is_string<String>::value>::type print(
std::FILE *f, const text_style &ts, const String &format_str,
const Args &... args) {
internal::check_format_string<Args...>(format_str);
typedef typename internal::char_t<String>::type char_t;
typedef typename buffer_context<char_t>::type context_t;
format_arg_store<context_t, Args...> as{args...};
vprint(f, ts, format_str, basic_format_args<context_t>(as));
}
/**
Formats a string and prints it to stdout using ANSI escape sequences to
specify text formatting.
Example:
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
"Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename String, typename... Args>
typename std::enable_if<internal::is_string<String>::value>::type print(
const text_style &ts, const String &format_str,
const Args &... args) {
return print(stdout, ts, format_str, args...);
}
#endif
FMT_END_NAMESPACE
#endif // FMT_COLOR_H_

View File

@ -1,257 +0,0 @@
// Formatting library for C++ - the core API
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
//
// Copyright (c) 2018 - present, Remotion (Igor Schulz)
// All Rights Reserved
// {fmt} support for rgb color output.
#ifndef FMT_COLORS_H_
#define FMT_COLORS_H_
#include "format.h"
FMT_BEGIN_NAMESPACE
// rgb is a struct for red, green and blue colors.
// We use rgb as name because some editors will show it as color direct in the
// editor.
struct rgb
{
FMT_CONSTEXPR_DECL rgb()
: r(0)
, g(0)
, b(0)
{
}
FMT_CONSTEXPR_DECL rgb(uint8_t r_, uint8_t g_, uint8_t b_)
: r(r_)
, g(g_)
, b(b_)
{
}
FMT_CONSTEXPR_DECL rgb(uint32_t hex)
: r((hex >> 16) & 0xFF)
, g((hex >> 8) & 0xFF)
, b((hex)&0xFF)
{
}
uint8_t r;
uint8_t g;
uint8_t b;
};
namespace internal {
FMT_CONSTEXPR inline void to_esc(uint8_t c, char out[], int offset)
{
out[offset + 0] = static_cast<char>('0' + c / 100);
out[offset + 1] = static_cast<char>('0' + c / 10 % 10);
out[offset + 2] = static_cast<char>('0' + c % 10);
}
} // namespace internal
FMT_FUNC void vprint_rgb(rgb fd, string_view format, format_args args)
{
char escape_fd[] = "\x1b[38;2;000;000;000m";
static FMT_CONSTEXPR_DECL const char RESET_COLOR[] = "\x1b[0m";
internal::to_esc(fd.r, escape_fd, 7);
internal::to_esc(fd.g, escape_fd, 11);
internal::to_esc(fd.b, escape_fd, 15);
std::fputs(escape_fd, stdout);
vprint(format, args);
std::fputs(RESET_COLOR, stdout);
}
FMT_FUNC void vprint_rgb(rgb fd, rgb bg, string_view format, format_args args)
{
char escape_fd[] = "\x1b[38;2;000;000;000m"; // foreground color
char escape_bg[] = "\x1b[48;2;000;000;000m"; // background color
static FMT_CONSTEXPR_DECL const char RESET_COLOR[] = "\x1b[0m";
internal::to_esc(fd.r, escape_fd, 7);
internal::to_esc(fd.g, escape_fd, 11);
internal::to_esc(fd.b, escape_fd, 15);
internal::to_esc(bg.r, escape_bg, 7);
internal::to_esc(bg.g, escape_bg, 11);
internal::to_esc(bg.b, escape_bg, 15);
std::fputs(escape_fd, stdout);
std::fputs(escape_bg, stdout);
vprint(format, args);
std::fputs(RESET_COLOR, stdout);
}
template<typename... Args>
inline void print_rgb(rgb fd, string_view format_str, const Args &... args)
{
vprint_rgb(fd, format_str, make_format_args(args...));
}
// rgb foreground color
template<typename... Args>
inline void print(rgb fd, string_view format_str, const Args &... args)
{
vprint_rgb(fd, format_str, make_format_args(args...));
}
// rgb foreground color and background color
template<typename... Args>
inline void print(rgb fd, rgb bg, string_view format_str, const Args &... args)
{
vprint_rgb(fd, bg, format_str, make_format_args(args...));
}
enum class color : uint32_t
{
alice_blue = 0xF0F8FF, // rgb(240,248,255)
antique_white = 0xFAEBD7, // rgb(250,235,215)
aqua = 0x00FFFF, // rgb(0,255,255)
aquamarine = 0x7FFFD4, // rgb(127,255,212)
azure = 0xF0FFFF, // rgb(240,255,255)
beige = 0xF5F5DC, // rgb(245,245,220)
bisque = 0xFFE4C4, // rgb(255,228,196)
black = 0x000000, // rgb(0,0,0)
blanched_almond = 0xFFEBCD, // rgb(255,235,205)
blue = 0x0000FF, // rgb(0,0,255)
blue_violet = 0x8A2BE2, // rgb(138,43,226)
brown = 0xA52A2A, // rgb(165,42,42)
burly_wood = 0xDEB887, // rgb(222,184,135)
cadet_blue = 0x5F9EA0, // rgb(95,158,160)
chartreuse = 0x7FFF00, // rgb(127,255,0)
chocolate = 0xD2691E, // rgb(210,105,30)
coral = 0xFF7F50, // rgb(255,127,80)
cornflower_blue = 0x6495ED, // rgb(100,149,237)
cornsilk = 0xFFF8DC, // rgb(255,248,220)
crimson = 0xDC143C, // rgb(220,20,60)
cyan = 0x00FFFF, // rgb(0,255,255)
dark_blue = 0x00008B, // rgb(0,0,139)
dark_cyan = 0x008B8B, // rgb(0,139,139)
dark_golden_rod = 0xB8860B, // rgb(184,134,11)
dark_gray = 0xA9A9A9, // rgb(169,169,169)
dark_green = 0x006400, // rgb(0,100,0)
dark_khaki = 0xBDB76B, // rgb(189,183,107)
dark_magenta = 0x8B008B, // rgb(139,0,139)
dark_olive_green = 0x556B2F, // rgb(85,107,47)
dark_orange = 0xFF8C00, // rgb(255,140,0)
dark_orchid = 0x9932CC, // rgb(153,50,204)
dark_red = 0x8B0000, // rgb(139,0,0)
dark_salmon = 0xE9967A, // rgb(233,150,122)
dark_sea_green = 0x8FBC8F, // rgb(143,188,143)
dark_slate_blue = 0x483D8B, // rgb(72,61,139)
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79)
dark_turquoise = 0x00CED1, // rgb(0,206,209)
dark_violet = 0x9400D3, // rgb(148,0,211)
deep_pink = 0xFF1493, // rgb(255,20,147)
deep_sky_blue = 0x00BFFF, // rgb(0,191,255)
dim_gray = 0x696969, // rgb(105,105,105)
dodger_blue = 0x1E90FF, // rgb(30,144,255)
fire_brick = 0xB22222, // rgb(178,34,34)
floral_white = 0xFFFAF0, // rgb(255,250,240)
forest_green = 0x228B22, // rgb(34,139,34)
fuchsia = 0xFF00FF, // rgb(255,0,255)
gainsboro = 0xDCDCDC, // rgb(220,220,220)
ghost_white = 0xF8F8FF, // rgb(248,248,255)
gold = 0xFFD700, // rgb(255,215,0)
golden_rod = 0xDAA520, // rgb(218,165,32)
gray = 0x808080, // rgb(128,128,128)
green = 0x008000, // rgb(0,128,0)
green_yellow = 0xADFF2F, // rgb(173,255,47)
honey_dew = 0xF0FFF0, // rgb(240,255,240)
hot_pink = 0xFF69B4, // rgb(255,105,180)
indian_red = 0xCD5C5C, // rgb(205,92,92)
indigo = 0x4B0082, // rgb(75,0,130)
ivory = 0xFFFFF0, // rgb(255,255,240)
khaki = 0xF0E68C, // rgb(240,230,140)
lavender = 0xE6E6FA, // rgb(230,230,250)
lavender_blush = 0xFFF0F5, // rgb(255,240,245)
lawn_green = 0x7CFC00, // rgb(124,252,0)
lemon_chiffon = 0xFFFACD, // rgb(255,250,205)
light_blue = 0xADD8E6, // rgb(173,216,230)
light_coral = 0xF08080, // rgb(240,128,128)
light_cyan = 0xE0FFFF, // rgb(224,255,255)
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210)
light_gray = 0xD3D3D3, // rgb(211,211,211)
light_green = 0x90EE90, // rgb(144,238,144)
light_pink = 0xFFB6C1, // rgb(255,182,193)
light_salmon = 0xFFA07A, // rgb(255,160,122)
light_sea_green = 0x20B2AA, // rgb(32,178,170)
light_sky_blue = 0x87CEFA, // rgb(135,206,250)
light_slate_gray = 0x778899, // rgb(119,136,153)
light_steel_blue = 0xB0C4DE, // rgb(176,196,222)
light_yellow = 0xFFFFE0, // rgb(255,255,224)
lime = 0x00FF00, // rgb(0,255,0)
lime_green = 0x32CD32, // rgb(50,205,50)
linen = 0xFAF0E6, // rgb(250,240,230)
magenta = 0xFF00FF, // rgb(255,0,255)
maroon = 0x800000, // rgb(128,0,0)
medium_aqua_marine = 0x66CDAA, // rgb(102,205,170)
medium_blue = 0x0000CD, // rgb(0,0,205)
medium_orchid = 0xBA55D3, // rgb(186,85,211)
medium_purple = 0x9370DB, // rgb(147,112,219)
medium_sea_green = 0x3CB371, // rgb(60,179,113)
medium_slate_blue = 0x7B68EE, // rgb(123,104,238)
medium_spring_green = 0x00FA9A, // rgb(0,250,154)
medium_turquoise = 0x48D1CC, // rgb(72,209,204)
medium_violet_red = 0xC71585, // rgb(199,21,133)
midnight_blue = 0x191970, // rgb(25,25,112)
mint_cream = 0xF5FFFA, // rgb(245,255,250)
misty_rose = 0xFFE4E1, // rgb(255,228,225)
moccasin = 0xFFE4B5, // rgb(255,228,181)
navajo_white = 0xFFDEAD, // rgb(255,222,173)
navy = 0x000080, // rgb(0,0,128)
old_lace = 0xFDF5E6, // rgb(253,245,230)
olive = 0x808000, // rgb(128,128,0)
olive_drab = 0x6B8E23, // rgb(107,142,35)
orange = 0xFFA500, // rgb(255,165,0)
orange_red = 0xFF4500, // rgb(255,69,0)
orchid = 0xDA70D6, // rgb(218,112,214)
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170)
pale_green = 0x98FB98, // rgb(152,251,152)
pale_turquoise = 0xAFEEEE, // rgb(175,238,238)
pale_violet_red = 0xDB7093, // rgb(219,112,147)
papaya_whip = 0xFFEFD5, // rgb(255,239,213)
peach_puff = 0xFFDAB9, // rgb(255,218,185)
peru = 0xCD853F, // rgb(205,133,63)
pink = 0xFFC0CB, // rgb(255,192,203)
plum = 0xDDA0DD, // rgb(221,160,221)
powder_blue = 0xB0E0E6, // rgb(176,224,230)
purple = 0x800080, // rgb(128,0,128)
rebecca_purple = 0x663399, // rgb(102,51,153)
red = 0xFF0000, // rgb(255,0,0)
rosy_brown = 0xBC8F8F, // rgb(188,143,143)
royal_blue = 0x4169E1, // rgb(65,105,225)
saddle_brown = 0x8B4513, // rgb(139,69,19)
salmon = 0xFA8072, // rgb(250,128,114)
sandy_brown = 0xF4A460, // rgb(244,164,96)
sea_green = 0x2E8B57, // rgb(46,139,87)
sea_shell = 0xFFF5EE, // rgb(255,245,238)
sienna = 0xA0522D, // rgb(160,82,45)
silver = 0xC0C0C0, // rgb(192,192,192)
sky_blue = 0x87CEEB, // rgb(135,206,235)
slate_blue = 0x6A5ACD, // rgb(106,90,205)
slate_gray = 0x708090, // rgb(112,128,144)
snow = 0xFFFAFA, // rgb(255,250,250)
spring_green = 0x00FF7F, // rgb(0,255,127)
steel_blue = 0x4682B4, // rgb(70,130,180)
tan = 0xD2B48C, // rgb(210,180,140)
teal = 0x008080, // rgb(0,128,128)
thistle = 0xD8BFD8, // rgb(216,191,216)
tomato = 0xFF6347, // rgb(255,99,71)
turquoise = 0x40E0D0, // rgb(64,224,208)
violet = 0xEE82EE, // rgb(238,130,238)
wheat = 0xF5DEB3, // rgb(245,222,179)
white = 0xFFFFFF, // rgb(255,255,255)
white_smoke = 0xF5F5F5, // rgb(245,245,245)
yellow = 0xFFFF00, // rgb(255,255,0)
yellow_green = 0x9ACD32, // rgb(154,205,50)
}; // enum class colors
FMT_END_NAMESPACE
#endif // FMT_COLORS_H_

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,27 +1,77 @@
// Formatting library for C++ - locale support // Formatting library for C++ - std::locale support
// //
// Copyright (c) 2012 - 2016, Victor Zverovich // Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved. // All rights reserved.
// //
// For the license information refer to format.h. // For the license information refer to format.h.
#ifndef FMT_LOCALE_H_
#define FMT_LOCALE_H_
#include "format.h" #include "format.h"
#include <locale> #include <locale>
namespace fmt { FMT_BEGIN_NAMESPACE
class locale
{
private:
std::locale locale_;
public: namespace internal {
explicit locale(std::locale loc = std::locale()) template <typename Char>
: locale_(loc) typename buffer_context<Char>::type::iterator vformat_to(
{ const std::locale &loc, basic_buffer<Char> &buf,
basic_string_view<Char> format_str,
basic_format_args<typename buffer_context<Char>::type> args) {
typedef back_insert_range<basic_buffer<Char> > range;
return vformat_to<arg_formatter<range>>(
buf, to_string_view(format_str), args, internal::locale_ref(loc));
} }
std::locale get()
{ template <typename Char>
return locale_; std::basic_string<Char> vformat(
const std::locale &loc, basic_string_view<Char> format_str,
basic_format_args<typename buffer_context<Char>::type> args) {
basic_memory_buffer<Char> buffer;
internal::vformat_to(loc, buffer, format_str, args);
return fmt::to_string(buffer);
} }
}; }
} // namespace fmt
template <typename S, typename Char = FMT_CHAR(S)>
inline std::basic_string<Char> vformat(
const std::locale &loc, const S &format_str,
basic_format_args<typename buffer_context<Char>::type> args) {
return internal::vformat(loc, to_string_view(format_str), args);
}
template <typename S, typename... Args>
inline std::basic_string<FMT_CHAR(S)> format(
const std::locale &loc, const S &format_str, const Args &... args) {
return internal::vformat(
loc, to_string_view(format_str),
*internal::checked_args<S, Args...>(format_str, args...));
}
template <typename String, typename OutputIt, typename... Args>
inline typename std::enable_if<internal::is_output_iterator<OutputIt>::value,
OutputIt>::type
vformat_to(OutputIt out, const std::locale &loc, const String &format_str,
typename format_args_t<OutputIt, FMT_CHAR(String)>::type args) {
typedef output_range<OutputIt, FMT_CHAR(String)> range;
return vformat_to<arg_formatter<range>>(
range(out), to_string_view(format_str), args, internal::locale_ref(loc));
}
template <typename OutputIt, typename S, typename... Args>
inline typename std::enable_if<
internal::is_string<S>::value &&
internal::is_output_iterator<OutputIt>::value, OutputIt>::type
format_to(OutputIt out, const std::locale &loc, const S &format_str,
const Args &... args) {
internal::check_format_string<Args...>(format_str);
typedef typename format_context_t<OutputIt, FMT_CHAR(S)>::type context;
format_arg_store<context, Args...> as{args...};
return vformat_to(out, loc, to_string_view(format_str),
basic_format_args<context>(as));
}
FMT_END_NAMESPACE
#endif // FMT_LOCALE_H_

View File

@ -1,6 +1,6 @@
// Formatting library for C++ - std::ostream support // Formatting library for C++ - std::ostream support
// //
// Copyright (c) 2012 - 2016, Victor Zverovich // Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved. // All rights reserved.
// //
// For the license information refer to format.h. // For the license information refer to format.h.
@ -15,8 +15,7 @@ FMT_BEGIN_NAMESPACE
namespace internal { namespace internal {
template <class Char> template <class Char>
class formatbuf : public std::basic_streambuf<Char> class formatbuf : public std::basic_streambuf<Char> {
{
private: private:
typedef typename std::basic_streambuf<Char>::int_type int_type; typedef typename std::basic_streambuf<Char>::int_type int_type;
typedef typename std::basic_streambuf<Char>::traits_type traits_type; typedef typename std::basic_streambuf<Char>::traits_type traits_type;
@ -24,10 +23,7 @@ private:
basic_buffer<Char> &buffer_; basic_buffer<Char> &buffer_;
public: public:
formatbuf(basic_buffer<Char> &buffer) formatbuf(basic_buffer<Char> &buffer) : buffer_(buffer) {}
: buffer_(buffer)
{
}
protected: protected:
// The put-area is actually always empty. This makes the implementation // The put-area is actually always empty. This makes the implementation
@ -37,37 +33,34 @@ protected:
// to overflow. There is no disadvantage here for sputn since this always // to overflow. There is no disadvantage here for sputn since this always
// results in a call to xsputn. // results in a call to xsputn.
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
{
if (!traits_type::eq_int_type(ch, traits_type::eof())) if (!traits_type::eq_int_type(ch, traits_type::eof()))
buffer_.push_back(static_cast<Char>(ch)); buffer_.push_back(static_cast<Char>(ch));
return ch; return ch;
} }
std::streamsize xsputn(const Char *s, std::streamsize count) FMT_OVERRIDE std::streamsize xsputn(const Char *s, std::streamsize count) FMT_OVERRIDE {
{
buffer_.append(s, s + count); buffer_.append(s, s + count);
return count; return count;
} }
}; };
template <typename Char> template <typename Char>
struct test_stream : std::basic_ostream<Char> struct test_stream : std::basic_ostream<Char> {
{
private: private:
struct null; struct null;
// Hide all operator<< from std::basic_ostream<Char>. // Hide all operator<< from std::basic_ostream<Char>.
void operator<<(null); void operator<<(null);
}; };
// Checks if T has a user-defined operator<< (e.g. not a member of // Checks if T has a user-defined operator<< (e.g. not a member of std::ostream).
// std::ostream).
template <typename T, typename Char> template <typename T, typename Char>
class is_streamable class is_streamable {
{
private: private:
template <typename U> template <typename U>
static decltype(internal::declval<test_stream<Char> &>() << internal::declval<U>(), std::true_type()) test(int); static decltype(
internal::declval<test_stream<Char>&>()
<< internal::declval<U>(), std::true_type()) test(int);
template <typename> template <typename>
static std::false_type test(...); static std::false_type test(...);
@ -75,31 +68,18 @@ private:
typedef decltype(test<T>(0)) result; typedef decltype(test<T>(0)) result;
public: public:
// std::string operator<< is not considered user-defined because we handle static const bool value = result::value;
// strings
// specially.
static const bool value = result::value && !std::is_same<T, std::string>::value;
};
// Disable conversion to int if T has an overloaded operator<< which is a free
// function (not a member of std::ostream).
template<typename T, typename Char>
class convert_to_int<T, Char, true>
{
public:
static const bool value = convert_to_int<T, Char, false>::value && !is_streamable<T, Char>::value;
}; };
// Write the content of buf to os. // Write the content of buf to os.
template <typename Char> template <typename Char>
void write(std::basic_ostream<Char> &os, basic_buffer<Char> &buf) void write(std::basic_ostream<Char> &os, basic_buffer<Char> &buf) {
{
const Char *data = buf.data(); const Char *data = buf.data();
typedef std::make_unsigned<std::streamsize>::type UnsignedStreamSize; typedef std::make_unsigned<std::streamsize>::type UnsignedStreamSize;
UnsignedStreamSize size = buf.size(); UnsignedStreamSize size = buf.size();
UnsignedStreamSize max_size = internal::to_unsigned((std::numeric_limits<std::streamsize>::max)()); UnsignedStreamSize max_size =
do internal::to_unsigned((std::numeric_limits<std::streamsize>::max)());
{ do {
UnsignedStreamSize n = size <= max_size ? size : max_size; UnsignedStreamSize n = size <= max_size ? size : max_size;
os.write(data, static_cast<std::streamsize>(n)); os.write(data, static_cast<std::streamsize>(n));
data += n; data += n;
@ -108,44 +88,48 @@ void write(std::basic_ostream<Char> &os, basic_buffer<Char> &buf)
} }
template <typename Char, typename T> template <typename Char, typename T>
void format_value(basic_buffer<Char> &buffer, const T &value) void format_value(basic_buffer<Char> &buffer, const T &value) {
{
internal::formatbuf<Char> format_buf(buffer); internal::formatbuf<Char> format_buf(buffer);
std::basic_ostream<Char> output(&format_buf); std::basic_ostream<Char> output(&format_buf);
output.exceptions(std::ios_base::failbit | std::ios_base::badbit); output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
output << value; output << value;
buffer.resize(buffer.size()); buffer.resize(buffer.size());
} }
// Disable builtin formatting of enums and use operator<< instead.
template<typename T>
struct format_enum<T, typename std::enable_if<std::is_enum<T>::value>::type> : std::false_type
{
};
} // namespace internal } // namespace internal
// Disable conversion to int if T has an overloaded operator<< which is a free
// function (not a member of std::ostream).
template <typename T, typename Char>
struct convert_to_int<T, Char, void> {
static const bool value =
convert_to_int<T, Char, int>::value &&
!internal::is_streamable<T, Char>::value;
};
// Formats an object of type T that has an overloaded ostream operator<<. // Formats an object of type T that has an overloaded ostream operator<<.
template <typename T, typename Char> template <typename T, typename Char>
struct formatter<T, Char, typename std::enable_if<internal::is_streamable<T, Char>::value>::type> : formatter<basic_string_view<Char>, Char> struct formatter<T, Char,
{ typename std::enable_if<
internal::is_streamable<T, Char>::value &&
!internal::format_type<
typename buffer_context<Char>::type, T>::value>::type>
: formatter<basic_string_view<Char>, Char> {
template <typename Context> template <typename Context>
auto format(const T &value, Context &ctx) -> decltype(ctx.out()) auto format(const T &value, Context &ctx) -> decltype(ctx.out()) {
{
basic_memory_buffer<Char> buffer; basic_memory_buffer<Char> buffer;
internal::format_value(buffer, value); internal::format_value(buffer, value);
basic_string_view<Char> str(buffer.data(), buffer.size()); basic_string_view<Char> str(buffer.data(), buffer.size());
formatter<basic_string_view<Char>, Char>::format(str, ctx); return formatter<basic_string_view<Char>, Char>::format(str, ctx);
return ctx.out();
} }
}; };
template <typename Char> template <typename Char>
inline void vprint( inline void vprint(std::basic_ostream<Char> &os,
std::basic_ostream<Char> &os, basic_string_view<Char> format_str, basic_format_args<typename buffer_context<Char>::type> args) basic_string_view<Char> format_str,
{ basic_format_args<typename buffer_context<Char>::type> args) {
basic_memory_buffer<Char> buffer; basic_memory_buffer<Char> buffer;
vformat_to(buffer, format_str, args); internal::vformat_to(buffer, format_str, args);
internal::write(os, buffer); internal::write(os, buffer);
} }
/** /**
@ -157,16 +141,12 @@ inline void vprint(
fmt::print(cerr, "Don't {}!", "panic"); fmt::print(cerr, "Don't {}!", "panic");
\endrst \endrst
*/ */
template<typename... Args> template <typename S, typename... Args>
inline void print(std::ostream &os, string_view format_str, const Args &... args) inline typename std::enable_if<internal::is_string<S>::value>::type
{ print(std::basic_ostream<FMT_CHAR(S)> &os, const S &format_str,
vprint<char>(os, format_str, make_format_args<format_context>(args...)); const Args & ... args) {
} internal::checked_args<S, Args...> ca(format_str, args...);
vprint(os, to_string_view(format_str), *ca);
template<typename... Args>
inline void print(std::wostream &os, wstring_view format_str, const Args &... args)
{
vprint<wchar_t>(os, format_str, make_format_args<wformat_context>(args...));
} }
FMT_END_NAMESPACE FMT_END_NAMESPACE

View File

@ -53,8 +53,7 @@
// equals to EINTR. // equals to EINTR.
#ifndef _WIN32 #ifndef _WIN32
# define FMT_RETRY_VAL(result, expression, error_result) \ # define FMT_RETRY_VAL(result, expression, error_result) \
do \ do { \
{ \
result = (expression); \ result = (expression); \
} while (result == error_result && errno == EINTR) } while (result == error_result && errno == EINTR)
#else #else
@ -91,140 +90,72 @@ FMT_BEGIN_NAMESPACE
\endrst \endrst
*/ */
template <typename Char> template <typename Char>
class basic_cstring_view class basic_cstring_view {
{
private: private:
const Char *data_; const Char *data_;
public: public:
/** Constructs a string reference object from a C string. */ /** Constructs a string reference object from a C string. */
basic_cstring_view(const Char *s) basic_cstring_view(const Char *s) : data_(s) {}
: data_(s)
{
}
/** /**
\rst \rst
Constructs a string reference from an ``std::string`` object. Constructs a string reference from an ``std::string`` object.
\endrst \endrst
*/ */
basic_cstring_view(const std::basic_string<Char> &s) basic_cstring_view(const std::basic_string<Char> &s) : data_(s.c_str()) {}
: data_(s.c_str())
{
}
/** Returns the pointer to a C string. */ /** Returns the pointer to a C string. */
const Char *c_str() const const Char *c_str() const { return data_; }
{
return data_;
}
}; };
typedef basic_cstring_view<char> cstring_view; typedef basic_cstring_view<char> cstring_view;
typedef basic_cstring_view<wchar_t> wcstring_view; typedef basic_cstring_view<wchar_t> wcstring_view;
// An error code. // An error code.
class error_code class error_code {
{
private: private:
int value_; int value_;
public: public:
explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {} explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {}
int get() const FMT_NOEXCEPT int get() const FMT_NOEXCEPT { return value_; }
{
return value_;
}
}; };
// A buffered file. // A buffered file.
class buffered_file class buffered_file {
{
private: private:
FILE *file_; FILE *file_;
friend class file; friend class file;
explicit buffered_file(FILE *f) explicit buffered_file(FILE *f) : file_(f) {}
: file_(f)
{
}
public: public:
// Constructs a buffered_file object which doesn't represent any file. // Constructs a buffered_file object which doesn't represent any file.
buffered_file() FMT_NOEXCEPT : file_(FMT_NULL) {} buffered_file() FMT_NOEXCEPT : file_(FMT_NULL) {}
// Destroys the object closing the file it represents if any. // Destroys the object closing the file it represents if any.
FMT_API ~buffered_file() FMT_DTOR_NOEXCEPT; FMT_API ~buffered_file() FMT_NOEXCEPT;
#if !FMT_USE_RVALUE_REFERENCES
// Emulate a move constructor and a move assignment operator if rvalue
// references are not supported.
private: private:
// A proxy object to emulate a move constructor. buffered_file(const buffered_file &) = delete;
// It is private to make it impossible call operator Proxy directly. void operator=(const buffered_file &) = delete;
struct Proxy
{
FILE *file;
};
public: public:
// A "move constructor" for moving from a temporary. buffered_file(buffered_file &&other) FMT_NOEXCEPT : file_(other.file_) {
buffered_file(Proxy p) FMT_NOEXCEPT : file_(p.file) {} other.file_ = FMT_NULL;
// A "move constructor" for moving from an lvalue.
buffered_file(buffered_file &f) FMT_NOEXCEPT : file_(f.file_)
{
f.file_ = FMT_NULL;
} }
// A "move assignment operator" for moving from a temporary. buffered_file& operator=(buffered_file &&other) {
buffered_file &operator=(Proxy p)
{
close();
file_ = p.file;
return *this;
}
// A "move assignment operator" for moving from an lvalue.
buffered_file &operator=(buffered_file &other)
{
close(); close();
file_ = other.file_; file_ = other.file_;
other.file_ = FMT_NULL; other.file_ = FMT_NULL;
return *this; return *this;
} }
// Returns a proxy object for moving from a temporary:
// buffered_file file = buffered_file(...);
operator Proxy() FMT_NOEXCEPT
{
Proxy p = {file_};
file_ = FMT_NULL;
return p;
}
#else
private:
FMT_DISALLOW_COPY_AND_ASSIGN(buffered_file);
public:
buffered_file(buffered_file &&other) FMT_NOEXCEPT : file_(other.file_)
{
other.file_ = FMT_NULL;
}
buffered_file &operator=(buffered_file &&other)
{
close();
file_ = other.file_;
other.file_ = FMT_NULL;
return *this;
}
#endif
// Opens a file. // Opens a file.
FMT_API buffered_file(cstring_view filename, cstring_view mode); FMT_API buffered_file(cstring_view filename, cstring_view mode);
@ -232,23 +163,18 @@ public:
FMT_API void close(); FMT_API void close();
// Returns the pointer to a FILE object representing this file. // Returns the pointer to a FILE object representing this file.
FILE *get() const FMT_NOEXCEPT FILE *get() const FMT_NOEXCEPT { return file_; }
{
return file_;
}
// We place parentheses around fileno to workaround a bug in some versions // We place parentheses around fileno to workaround a bug in some versions
// of MinGW that define fileno as a macro. // of MinGW that define fileno as a macro.
FMT_API int (fileno)() const; FMT_API int (fileno)() const;
void vprint(string_view format_str, format_args args) void vprint(string_view format_str, format_args args) {
{
fmt::vprint(file_, format_str, args); fmt::vprint(file_, format_str, args);
} }
template <typename... Args> template <typename... Args>
inline void print(string_view format_str, const Args &... args) inline void print(string_view format_str, const Args & ... args) {
{
vprint(format_str, make_format_args(args...)); vprint(format_str, make_format_args(args...));
} }
}; };
@ -259,21 +185,16 @@ public:
// closing the file multiple times will cause a crash on Windows rather // closing the file multiple times will cause a crash on Windows rather
// than an exception. You can get standard behavior by overriding the // than an exception. You can get standard behavior by overriding the
// invalid parameter handler with _set_invalid_parameter_handler. // invalid parameter handler with _set_invalid_parameter_handler.
class file class file {
{
private: private:
int fd_; // File descriptor. int fd_; // File descriptor.
// Constructs a file object with a given descriptor. // Constructs a file object with a given descriptor.
explicit file(int fd) explicit file(int fd) : fd_(fd) {}
: fd_(fd)
{
}
public: public:
// Possible values for the oflag argument to the constructor. // Possible values for the oflag argument to the constructor.
enum enum {
{
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing.
@ -285,81 +206,27 @@ public:
// Opens a file and constructs a file object representing this file. // Opens a file and constructs a file object representing this file.
FMT_API file(cstring_view path, int oflag); FMT_API file(cstring_view path, int oflag);
#if !FMT_USE_RVALUE_REFERENCES
// Emulate a move constructor and a move assignment operator if rvalue
// references are not supported.
private: private:
// A proxy object to emulate a move constructor. file(const file &) = delete;
// It is private to make it impossible call operator Proxy directly. void operator=(const file &) = delete;
struct Proxy
{
int fd;
};
public: public:
// A "move constructor" for moving from a temporary. file(file &&other) FMT_NOEXCEPT : fd_(other.fd_) {
file(Proxy p) FMT_NOEXCEPT : fd_(p.fd) {}
// A "move constructor" for moving from an lvalue.
file(file &other) FMT_NOEXCEPT : fd_(other.fd_)
{
other.fd_ = -1; other.fd_ = -1;
} }
// A "move assignment operator" for moving from a temporary. file& operator=(file &&other) {
file &operator=(Proxy p)
{
close();
fd_ = p.fd;
return *this;
}
// A "move assignment operator" for moving from an lvalue.
file &operator=(file &other)
{
close(); close();
fd_ = other.fd_; fd_ = other.fd_;
other.fd_ = -1; other.fd_ = -1;
return *this; return *this;
} }
// Returns a proxy object for moving from a temporary:
// file f = file(...);
operator Proxy() FMT_NOEXCEPT
{
Proxy p = {fd_};
fd_ = -1;
return p;
}
#else
private:
FMT_DISALLOW_COPY_AND_ASSIGN(file);
public:
file(file &&other) FMT_NOEXCEPT : fd_(other.fd_)
{
other.fd_ = -1;
}
file &operator=(file &&other)
{
close();
fd_ = other.fd_;
other.fd_ = -1;
return *this;
}
#endif
// Destroys the object closing the file it represents if any. // Destroys the object closing the file it represents if any.
FMT_API ~file() FMT_DTOR_NOEXCEPT; FMT_API ~file() FMT_NOEXCEPT;
// Returns the file descriptor. // Returns the file descriptor.
int descriptor() const FMT_NOEXCEPT int descriptor() const FMT_NOEXCEPT { return fd_; }
{
return fd_;
}
// Closes the file. // Closes the file.
FMT_API void close(); FMT_API void close();
@ -398,66 +265,53 @@ public:
// Returns the memory page size. // Returns the memory page size.
long getpagesize(); long getpagesize();
#if (defined(LC_NUMERIC_MASK) || defined(_MSC_VER)) && !defined(__ANDROID__) && !defined(__CYGWIN__) && !defined(__OpenBSD__) #if (defined(LC_NUMERIC_MASK) || defined(_MSC_VER)) && \
!defined(__ANDROID__) && !defined(__CYGWIN__) && !defined(__OpenBSD__) && \
!defined(__NEWLIB_H__)
# define FMT_LOCALE # define FMT_LOCALE
#endif #endif
#ifdef FMT_LOCALE #ifdef FMT_LOCALE
// A "C" numeric locale. // A "C" numeric locale.
class Locale class Locale {
{
private: private:
# ifdef _MSC_VER # ifdef _MSC_VER
typedef _locale_t locale_t; typedef _locale_t locale_t;
enum enum { LC_NUMERIC_MASK = LC_NUMERIC };
{
LC_NUMERIC_MASK = LC_NUMERIC
};
static locale_t newlocale(int category_mask, const char *locale, locale_t) static locale_t newlocale(int category_mask, const char *locale, locale_t) {
{
return _create_locale(category_mask, locale); return _create_locale(category_mask, locale);
} }
static void freelocale(locale_t locale) static void freelocale(locale_t locale) {
{
_free_locale(locale); _free_locale(locale);
} }
static double strtod_l(const char *nptr, char **endptr, _locale_t locale) static double strtod_l(const char *nptr, char **endptr, _locale_t locale) {
{
return _strtod_l(nptr, endptr, locale); return _strtod_l(nptr, endptr, locale);
} }
# endif # endif
locale_t locale_; locale_t locale_;
FMT_DISALLOW_COPY_AND_ASSIGN(Locale); Locale(const Locale &) = delete;
void operator=(const Locale &) = delete;
public: public:
typedef locale_t Type; typedef locale_t Type;
Locale() Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", FMT_NULL)) {
: locale_(newlocale(LC_NUMERIC_MASK, "C", FMT_NULL))
{
if (!locale_) if (!locale_)
FMT_THROW(system_error(errno, "cannot create locale")); FMT_THROW(system_error(errno, "cannot create locale"));
} }
~Locale() ~Locale() { freelocale(locale_); }
{
freelocale(locale_);
}
Type get() const Type get() const { return locale_; }
{
return locale_;
}
// Converts string to floating-point number and advances str past the end // Converts string to floating-point number and advances str past the end
// of the parsed input. // of the parsed input.
double strtod(const char *&str) const double strtod(const char *&str) const {
{
char *end = FMT_NULL; char *end = FMT_NULL;
double result = strtod_l(str, &end, locale_); double result = strtod_l(str, &end, locale_);
str = end; str = end;
@ -467,18 +321,4 @@ public:
#endif // FMT_LOCALE #endif // FMT_LOCALE
FMT_END_NAMESPACE FMT_END_NAMESPACE
#if !FMT_USE_RVALUE_REFERENCES
namespace std {
// For compatibility with C++98.
inline fmt::buffered_file &move(fmt::buffered_file &f)
{
return f;
}
inline fmt::file &move(fmt::file &f)
{
return f;
}
} // namespace std
#endif
#endif // FMT_POSIX_H_ #endif // FMT_POSIX_H_

File diff suppressed because it is too large Load Diff

View File

@ -23,45 +23,31 @@
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
template <typename Char> template <typename Char>
struct formatting_base struct formatting_base {
{
template <typename ParseContext> template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
{
return ctx.begin(); return ctx.begin();
} }
}; };
template <typename Char, typename Enable = void> template <typename Char, typename Enable = void>
struct formatting_range : formatting_base<Char> struct formatting_range : formatting_base<Char> {
{ static FMT_CONSTEXPR_DECL const std::size_t range_length_limit =
static FMT_CONSTEXPR_DECL const std::size_t range_length_limit = FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the range.
// range.
Char prefix; Char prefix;
Char delimiter; Char delimiter;
Char postfix; Char postfix;
formatting_range() formatting_range() : prefix('{'), delimiter(','), postfix('}') {}
: prefix('{')
, delimiter(',')
, postfix('}')
{
}
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
}; };
template <typename Char, typename Enable = void> template <typename Char, typename Enable = void>
struct formatting_tuple : formatting_base<Char> struct formatting_tuple : formatting_base<Char> {
{
Char prefix; Char prefix;
Char delimiter; Char delimiter;
Char postfix; Char postfix;
formatting_tuple() formatting_tuple() : prefix('('), delimiter(','), postfix(')') {}
: prefix('(')
, delimiter(',')
, postfix(')')
{
}
static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true;
static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false;
}; };
@ -69,71 +55,69 @@ struct formatting_tuple : formatting_base<Char>
namespace internal { namespace internal {
template <typename RangeT, typename OutputIterator> template <typename RangeT, typename OutputIterator>
void copy(const RangeT &range, OutputIterator out) void copy(const RangeT &range, OutputIterator out) {
{
for (auto it = range.begin(), end = range.end(); it != end; ++it) for (auto it = range.begin(), end = range.end(); it != end; ++it)
*out++ = *it; *out++ = *it;
} }
template <typename OutputIterator> template <typename OutputIterator>
void copy(const char *str, OutputIterator out) void copy(const char *str, OutputIterator out) {
{
const char *p_curr = str; const char *p_curr = str;
while (*p_curr) while (*p_curr) {
{
*out++ = *p_curr++; *out++ = *p_curr++;
} }
} }
template <typename OutputIterator> template <typename OutputIterator>
void copy(char ch, OutputIterator out) void copy(char ch, OutputIterator out) {
{
*out++ = ch; *out++ = ch;
} }
/// Return true value if T has std::string interface, like std::string_view. /// Return true value if T has std::string interface, like std::string_view.
template <typename T> template <typename T>
class is_like_std_string class is_like_std_string {
{
template <typename U> template <typename U>
static auto check(U *p) -> decltype(p->find('a'), p->length(), p->data(), int()); static auto check(U *p) ->
decltype(p->find('a'), p->length(), p->data(), int());
template <typename> template <typename>
static void check(...); static void check(...);
public: public:
static FMT_CONSTEXPR_DECL const bool value = !std::is_void<decltype(check<T>(FMT_NULL))>::value; static FMT_CONSTEXPR_DECL const bool value =
!std::is_void<decltype(check<T>(FMT_NULL))>::value;
}; };
template <typename Char>
struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {};
template <typename... Ts> template <typename... Ts>
struct conditional_helper struct conditional_helper {};
{
};
template <typename T, typename _ = void> template <typename T, typename _ = void>
struct is_range_ : std::false_type struct is_range_ : std::false_type {};
{
};
#if !FMT_MSC_VER || FMT_MSC_VER > 1800 #if !FMT_MSC_VER || FMT_MSC_VER > 1800
template <typename T> template <typename T>
struct is_range_<T, typename std::conditional<false, struct is_range_<T, typename std::conditional<
conditional_helper<decltype(internal::declval<T>().begin()), decltype(internal::declval<T>().end())>, void>::type> false,
: std::true_type conditional_helper<decltype(internal::declval<T>().begin()),
{ decltype(internal::declval<T>().end())>,
}; void>::type> : std::true_type {};
#endif #endif
/// tuple_size and tuple_element check. /// tuple_size and tuple_element check.
template <typename T> template <typename T>
class is_tuple_like_ class is_tuple_like_ {
{
template <typename U> template <typename U>
static auto check(U *p) -> decltype(std::tuple_size<U>::value, internal::declval<typename std::tuple_element<0, U>::type>(), int()); static auto check(U *p) ->
decltype(std::tuple_size<U>::value,
internal::declval<typename std::tuple_element<0, U>::type>(), int());
template <typename> template <typename>
static void check(...); static void check(...);
public: public:
static FMT_CONSTEXPR_DECL const bool value = !std::is_void<decltype(check<T>(FMT_NULL))>::value; static FMT_CONSTEXPR_DECL const bool value =
!std::is_void<decltype(check<T>(FMT_NULL))>::value;
}; };
// Check for integer_sequence // Check for integer_sequence
@ -146,12 +130,10 @@ template<std::size_t N>
using make_index_sequence = std::make_index_sequence<N>; using make_index_sequence = std::make_index_sequence<N>;
#else #else
template <typename T, T... N> template <typename T, T... N>
struct integer_sequence struct integer_sequence {
{
typedef T value_type; typedef T value_type;
static FMT_CONSTEXPR std::size_t size() static FMT_CONSTEXPR std::size_t size() {
{
return sizeof...(N); return sizeof...(N);
} }
}; };
@ -160,21 +142,16 @@ template<std::size_t... N>
using index_sequence = integer_sequence<std::size_t, N...>; using index_sequence = integer_sequence<std::size_t, N...>;
template <typename T, std::size_t N, T... Ns> template <typename T, std::size_t N, T... Ns>
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
{
};
template <typename T, T... Ns> template <typename T, T... Ns>
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
{
};
template <std::size_t N> template <std::size_t N>
using make_index_sequence = make_integer_sequence<std::size_t, N>; using make_index_sequence = make_integer_sequence<std::size_t, N>;
#endif #endif
template <class Tuple, class F, size_t... Is> template <class Tuple, class F, size_t... Is>
void for_each(index_sequence<Is...>, Tuple &&tup, F &&f) FMT_NOEXCEPT void for_each(index_sequence<Is...>, Tuple &&tup, F &&f) FMT_NOEXCEPT {
{
using std::get; using std::get;
// using free function get<I>(T) now. // using free function get<I>(T) now.
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...}; const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
@ -182,78 +159,70 @@ void for_each(index_sequence<Is...>, Tuple &&tup, F &&f) FMT_NOEXCEPT
} }
template <class T> template <class T>
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes(T const &) FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value>
{ get_indexes(T const &) { return {}; }
return {};
}
template <class Tuple, class F> template <class Tuple, class F>
void for_each(Tuple &&tup, F &&f) void for_each(Tuple &&tup, F &&f) {
{
const auto indexes = get_indexes(tup); const auto indexes = get_indexes(tup);
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f)); for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
} }
template<typename Arg> template<typename Arg>
FMT_CONSTEXPR const char *format_str_quoted( FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&,
bool add_space, const Arg &, typename std::enable_if<!is_like_std_string<typename std::decay<Arg>::type>::value>::type * = nullptr) typename std::enable_if<
{ !is_like_std_string<typename std::decay<Arg>::type>::value>::type* = nullptr) {
return add_space ? " {}" : "{}"; return add_space ? " {}" : "{}";
} }
template<typename Arg> template<typename Arg>
FMT_CONSTEXPR const char *format_str_quoted( FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&,
bool add_space, const Arg &, typename std::enable_if<is_like_std_string<typename std::decay<Arg>::type>::value>::type * = nullptr) typename std::enable_if<
{ is_like_std_string<typename std::decay<Arg>::type>::value>::type* = nullptr) {
return add_space ? " \"{}\"" : "\"{}\""; return add_space ? " \"{}\"" : "\"{}\"";
} }
FMT_CONSTEXPR const char *format_str_quoted(bool add_space, const char *) FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) {
{
return add_space ? " \"{}\"" : "\"{}\""; return add_space ? " \"{}\"" : "\"{}\"";
} }
FMT_CONSTEXPR const wchar_t *format_str_quoted(bool add_space, const wchar_t *) FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) {
{
return add_space ? L" \"{}\"" : L"\"{}\""; return add_space ? L" \"{}\"" : L"\"{}\"";
} }
FMT_CONSTEXPR const char *format_str_quoted(bool add_space, const char) FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) {
{
return add_space ? " '{}'" : "'{}'"; return add_space ? " '{}'" : "'{}'";
} }
FMT_CONSTEXPR const wchar_t *format_str_quoted(bool add_space, const wchar_t) FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) {
{
return add_space ? L" '{}'" : L"'{}'"; return add_space ? L" '{}'" : L"'{}'";
} }
} // namespace internal } // namespace internal
template <typename T> template <typename T>
struct is_tuple_like struct is_tuple_like {
{ static FMT_CONSTEXPR_DECL const bool value =
static FMT_CONSTEXPR_DECL const bool value = internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value; internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value;
}; };
template <typename TupleT, typename Char> template <typename TupleT, typename Char>
struct formatter<TupleT, Char, typename std::enable_if<fmt::is_tuple_like<TupleT>::value>::type> struct formatter<TupleT, Char,
{ typename std::enable_if<fmt::is_tuple_like<TupleT>::value>::type> {
private: private:
// C++11 generic lambda for format() // C++11 generic lambda for format()
template <typename FormatContext> template <typename FormatContext>
struct format_each struct format_each {
{
template <typename T> template <typename T>
void operator()(const T &v) void operator()(const T& v) {
{ if (i > 0) {
if (i > 0) if (formatting.add_prepostfix_space) {
{
if (formatting.add_prepostfix_space)
{
*out++ = ' '; *out++ = ' ';
} }
internal::copy(formatting.delimiter, out); internal::copy(formatting.delimiter, out);
} }
format_to(out, internal::format_str_quoted((formatting.add_delimiter_spaces && i > 0), v), v); format_to(out,
internal::format_str_quoted(
(formatting.add_delimiter_spaces && i > 0), v),
v);
++i; ++i;
} }
@ -266,21 +235,18 @@ public:
formatting_tuple<Char> formatting; formatting_tuple<Char> formatting;
template <typename ParseContext> template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
{
return formatting.parse(ctx); return formatting.parse(ctx);
} }
template <typename FormatContext = format_context> template <typename FormatContext = format_context>
auto format(const TupleT &values, FormatContext &ctx) -> decltype(ctx.out()) auto format(const TupleT &values, FormatContext &ctx) -> decltype(ctx.out()) {
{
auto out = ctx.out(); auto out = ctx.out();
std::size_t i = 0; std::size_t i = 0;
internal::copy(formatting.prefix, out); internal::copy(formatting.prefix, out);
internal::for_each(values, format_each<FormatContext>{formatting, i, out}); internal::for_each(values, format_each<FormatContext>{formatting, i, out});
if (formatting.add_prepostfix_space) if (formatting.add_prepostfix_space) {
{
*out++ = ' '; *out++ = ' ';
} }
internal::copy(formatting.postfix, out); internal::copy(formatting.postfix, out);
@ -290,48 +256,45 @@ public:
}; };
template <typename T> template <typename T>
struct is_range struct is_range {
{ static FMT_CONSTEXPR_DECL const bool value =
static FMT_CONSTEXPR_DECL const bool value = internal::is_range_<T>::value && !internal::is_like_std_string<T>::value; internal::is_range_<T>::value && !internal::is_like_std_string<T>::value;
}; };
template <typename RangeT, typename Char> template <typename RangeT, typename Char>
struct formatter<RangeT, Char, typename std::enable_if<fmt::is_range<RangeT>::value>::type> struct formatter<RangeT, Char,
{ typename std::enable_if<fmt::is_range<RangeT>::value>::type> {
formatting_range<Char> formatting; formatting_range<Char> formatting;
template <typename ParseContext> template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
{
return formatting.parse(ctx); return formatting.parse(ctx);
} }
template <typename FormatContext> template <typename FormatContext>
typename FormatContext::iterator format(const RangeT &values, FormatContext &ctx) typename FormatContext::iterator format(
{ const RangeT &values, FormatContext &ctx) {
auto out = ctx.out(); auto out = ctx.out();
internal::copy(formatting.prefix, out); internal::copy(formatting.prefix, out);
std::size_t i = 0; std::size_t i = 0;
for (auto it = values.begin(), end = values.end(); it != end; ++it) for (auto it = values.begin(), end = values.end(); it != end; ++it) {
{ if (i > 0) {
if (i > 0) if (formatting.add_prepostfix_space) {
{
if (formatting.add_prepostfix_space)
{
*out++ = ' '; *out++ = ' ';
} }
internal::copy(formatting.delimiter, out); internal::copy(formatting.delimiter, out);
} }
format_to(out, internal::format_str_quoted((formatting.add_delimiter_spaces && i > 0), *it), *it); format_to(out,
if (++i > formatting.range_length_limit) internal::format_str_quoted(
{ (formatting.add_delimiter_spaces && i > 0), *it),
*it);
if (++i > formatting.range_length_limit) {
format_to(out, " ... <other elements>"); format_to(out, " ... <other elements>");
break; break;
} }
} }
if (formatting.add_prepostfix_space) if (formatting.add_prepostfix_space) {
{
*out++ = ' '; *out++ = ' ';
} }
internal::copy(formatting.postfix, out); internal::copy(formatting.postfix, out);
@ -342,3 +305,4 @@ struct formatter<RangeT, Char, typename std::enable_if<fmt::is_range<RangeT>::va
FMT_END_NAMESPACE FMT_END_NAMESPACE
#endif // FMT_RANGES_H_ #endif // FMT_RANGES_H_

View File

@ -1,6 +1,6 @@
// Formatting library for C++ - time formatting // Formatting library for C++ - time formatting
// //
// Copyright (c) 2012 - 2016, Victor Zverovich // Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved. // All rights reserved.
// //
// For the license information refer to format.h. // For the license information refer to format.h.
@ -10,176 +10,137 @@
#include "format.h" #include "format.h"
#include <ctime> #include <ctime>
#include <locale>
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
// Prevents expansion of a preceding token as a function-style macro.
// Usage: f FMT_NOMACRO()
#define FMT_NOMACRO
namespace internal{ namespace internal{
inline null<> localtime_r(...) inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); }
{ inline null<> localtime_s(...) { return null<>(); }
return null<>(); inline null<> gmtime_r(...) { return null<>(); }
} inline null<> gmtime_s(...) { return null<>(); }
inline null<> localtime_s(...)
{
return null<>();
}
inline null<> gmtime_r(...)
{
return null<>();
}
inline null<> gmtime_s(...)
{
return null<>();
}
} // namespace internal } // namespace internal
// Thread-safe replacement for std::localtime // Thread-safe replacement for std::localtime
inline std::tm localtime(std::time_t time) inline std::tm localtime(std::time_t time) {
{ struct dispatcher {
struct dispatcher
{
std::time_t time_; std::time_t time_;
std::tm tm_; std::tm tm_;
dispatcher(std::time_t t) dispatcher(std::time_t t): time_(t) {}
: time_(t)
{
}
bool run() bool run() {
{
using namespace fmt::internal; using namespace fmt::internal;
return handle(localtime_r(&time_, &tm_)); return handle(localtime_r(&time_, &tm_));
} }
bool handle(std::tm *tm) bool handle(std::tm *tm) { return tm != FMT_NULL; }
{
return tm != FMT_NULL;
}
bool handle(internal::null<>) bool handle(internal::null<>) {
{
using namespace fmt::internal; using namespace fmt::internal;
return fallback(localtime_s(&tm_, &time_)); return fallback(localtime_s(&tm_, &time_));
} }
bool fallback(int res) bool fallback(int res) { return res == 0; }
{
return res == 0;
}
bool fallback(internal::null<>) #if !FMT_MSC_VER
{ bool fallback(internal::null<>) {
using namespace fmt::internal; using namespace fmt::internal;
std::tm *tm = std::localtime(&time_); std::tm *tm = std::localtime(&time_);
if (tm) if (tm) tm_ = *tm;
tm_ = *tm;
return tm != FMT_NULL; return tm != FMT_NULL;
} }
#endif
}; };
dispatcher lt(time); dispatcher lt(time);
if (lt.run())
return lt.tm_;
// Too big time values may be unsupported. // Too big time values may be unsupported.
if (!lt.run())
FMT_THROW(format_error("time_t value out of range")); FMT_THROW(format_error("time_t value out of range"));
return lt.tm_;
} }
// Thread-safe replacement for std::gmtime // Thread-safe replacement for std::gmtime
inline std::tm gmtime(std::time_t time) inline std::tm gmtime(std::time_t time) {
{ struct dispatcher {
struct dispatcher
{
std::time_t time_; std::time_t time_;
std::tm tm_; std::tm tm_;
dispatcher(std::time_t t) dispatcher(std::time_t t): time_(t) {}
: time_(t)
{
}
bool run() bool run() {
{
using namespace fmt::internal; using namespace fmt::internal;
return handle(gmtime_r(&time_, &tm_)); return handle(gmtime_r(&time_, &tm_));
} }
bool handle(std::tm *tm) bool handle(std::tm *tm) { return tm != FMT_NULL; }
{
return tm != FMT_NULL;
}
bool handle(internal::null<>) bool handle(internal::null<>) {
{
using namespace fmt::internal; using namespace fmt::internal;
return fallback(gmtime_s(&tm_, &time_)); return fallback(gmtime_s(&tm_, &time_));
} }
bool fallback(int res) bool fallback(int res) { return res == 0; }
{
return res == 0;
}
bool fallback(internal::null<>) #if !FMT_MSC_VER
{ bool fallback(internal::null<>) {
std::tm *tm = std::gmtime(&time_); std::tm *tm = std::gmtime(&time_);
if (tm) if (tm) tm_ = *tm;
tm_ = *tm;
return tm != FMT_NULL; return tm != FMT_NULL;
} }
#endif
}; };
dispatcher gt(time); dispatcher gt(time);
if (gt.run())
return gt.tm_;
// Too big time values may be unsupported. // Too big time values may be unsupported.
if (!gt.run())
FMT_THROW(format_error("time_t value out of range")); FMT_THROW(format_error("time_t value out of range"));
return gt.tm_;
} }
namespace internal { namespace internal {
inline std::size_t strftime(char *str, std::size_t count, const char *format, const std::tm *time) inline std::size_t strftime(char *str, std::size_t count, const char *format,
{ const std::tm *time) {
return std::strftime(str, count, format, time); return std::strftime(str, count, format, time);
} }
inline std::size_t strftime(wchar_t *str, std::size_t count, const wchar_t *format, const std::tm *time) inline std::size_t strftime(wchar_t *str, std::size_t count,
{ const wchar_t *format, const std::tm *time) {
return std::wcsftime(str, count, format, time); return std::wcsftime(str, count, format, time);
} }
} // namespace internal }
template <typename Char> template <typename Char>
struct formatter<std::tm, Char> struct formatter<std::tm, Char> {
{
template <typename ParseContext> template <typename ParseContext>
auto parse(ParseContext &ctx) -> decltype(ctx.begin()) auto parse(ParseContext &ctx) -> decltype(ctx.begin()) {
{ auto it = ctx.begin();
auto it = internal::null_terminating_iterator<Char>(ctx); if (it != ctx.end() && *it == ':')
if (*it == ':')
++it; ++it;
auto end = it; auto end = it;
while (*end && *end != '}') while (end != ctx.end() && *end != '}')
++end; ++end;
tm_format.reserve(end - it + 1); tm_format.reserve(internal::to_unsigned(end - it + 1));
using internal::pointer_from; tm_format.append(it, end);
tm_format.append(pointer_from(it), pointer_from(end));
tm_format.push_back('\0'); tm_format.push_back('\0');
return pointer_from(end); return end;
} }
template <typename FormatContext> template <typename FormatContext>
auto format(const std::tm &tm, FormatContext &ctx) -> decltype(ctx.out()) auto format(const std::tm &tm, FormatContext &ctx) -> decltype(ctx.out()) {
{ basic_memory_buffer<Char> buf;
internal::basic_buffer<Char> &buf = internal::get_container(ctx.out());
std::size_t start = buf.size(); std::size_t start = buf.size();
for (;;) for (;;) {
{
std::size_t size = buf.capacity() - start; std::size_t size = buf.capacity() - start;
std::size_t count = internal::strftime(&buf[start], size, &tm_format[0], &tm); std::size_t count =
if (count != 0) internal::strftime(&buf[start], size, &tm_format[0], &tm);
{ if (count != 0) {
buf.resize(start + count); buf.resize(start + count);
break; break;
} }
if (size >= tm_format.size() * 256) if (size >= tm_format.size() * 256) {
{
// If the buffer is 256 times larger than the format string, assume // If the buffer is 256 times larger than the format string, assume
// that `strftime` gives an empty result. There doesn't seem to be a // that `strftime` gives an empty result. There doesn't seem to be a
// better way to distinguish the two cases: // better way to distinguish the two cases:
@ -189,7 +150,7 @@ struct formatter<std::tm, Char>
const std::size_t MIN_GROWTH = 10; const std::size_t MIN_GROWTH = 10;
buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
} }
return ctx.out(); return std::copy(buf.begin(), buf.end(), ctx.out());
} }
basic_memory_buffer<Char> tm_format; basic_memory_buffer<Char> tm_format;

View File

@ -35,7 +35,7 @@ public:
logger(std::string name, sinks_init_list sinks); logger(std::string name, sinks_init_list sinks);
template<typename It> template<typename It>
logger(std::string name, const It &begin, const It &end); logger(std::string name, It begin, It end);
virtual ~logger(); virtual ~logger();
@ -46,8 +46,12 @@ public:
void log(level::level_enum lvl, const char *fmt, const Args &... args); void log(level::level_enum lvl, const char *fmt, const Args &... args);
template<typename... Args> template<typename... Args>
void log(source_loc loc, level::level_enum lvl, const char *fmt, const Args &... args);
void log(level::level_enum lvl, const char *msg); void log(level::level_enum lvl, const char *msg);
void log(source_loc loc, level::level_enum lvl, const char *msg);
template<typename... Args> template<typename... Args>
void trace(const char *fmt, const Args &... args); void trace(const char *fmt, const Args &... args);
@ -67,9 +71,15 @@ public:
void critical(const char *fmt, const Args &... args); void critical(const char *fmt, const Args &... args);
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT #ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
#ifndef _WIN32
#error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows
#else
template<typename... Args> template<typename... Args>
void log(level::level_enum lvl, const wchar_t *fmt, const Args &... args); void log(level::level_enum lvl, const wchar_t *fmt, const Args &... args);
template<typename... Args>
void log(source_loc source, level::level_enum lvl, const wchar_t *fmt, const Args &... args);
template<typename... Args> template<typename... Args>
void trace(const wchar_t *fmt, const Args &... args); void trace(const wchar_t *fmt, const Args &... args);
@ -87,11 +97,25 @@ public:
template<typename... Args> template<typename... Args>
void critical(const wchar_t *fmt, const Args &... args); void critical(const wchar_t *fmt, const Args &... args);
#endif // _WIN32
#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT #endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT
template<typename T> // T can be statically converted to string_view
template<class T, typename std::enable_if<std::is_convertible<T, spdlog::string_view_t>::value, T>::type * = nullptr>
void log(level::level_enum lvl, const T &); void log(level::level_enum lvl, const T &);
// T can be statically converted to string_view
template<class T, typename std::enable_if<std::is_convertible<T, spdlog::string_view_t>::value, T>::type * = nullptr>
void log(source_loc loc, level::level_enum lvl, const T &);
// T cannot be statically converted to string_view
template<class T, typename std::enable_if<!std::is_convertible<T, spdlog::string_view_t>::value, T>::type * = nullptr>
void log(level::level_enum lvl, const T &);
// T cannot be statically converted to string_view
template<class T, typename std::enable_if<!std::is_convertible<T, spdlog::string_view_t>::value, T>::type * = nullptr>
void log(source_loc loc, level::level_enum lvl, const T &);
template<typename T> template<typename T>
void trace(const T &msg); void trace(const T &msg);
@ -112,6 +136,8 @@ public:
bool should_log(level::level_enum msg_level) const; bool should_log(level::level_enum msg_level) const;
void set_level(level::level_enum log_level); void set_level(level::level_enum log_level);
static level::level_enum default_level();
level::level_enum level() const; level::level_enum level() const;
const std::string &name() const; const std::string &name() const;
@ -131,7 +157,7 @@ public:
// error handler // error handler
void set_error_handler(log_err_handler err_handler); void set_error_handler(log_err_handler err_handler);
log_err_handler error_handler(); log_err_handler error_handler() const;
// create new logger with same sinks and configuration. // create new logger with same sinks and configuration.
virtual std::shared_ptr<logger> clone(std::string logger_name); virtual std::shared_ptr<logger> clone(std::string logger_name);
@ -142,26 +168,20 @@ protected:
bool should_flush_(const details::log_msg &msg); bool should_flush_(const details::log_msg &msg);
// default error handler: print the error to stderr with the max rate of 1 // default error handler.
// message/minute // print the error to stderr with the max rate of 1 message/minute.
void default_err_handler_(const std::string &msg); void default_err_handler_(const std::string &msg);
// increment the message count (only if // increment the message count (only if defined(SPDLOG_ENABLE_MESSAGE_COUNTER))
// defined(SPDLOG_ENABLE_MESSAGE_COUNTER))
void incr_msg_counter_(details::log_msg &msg); void incr_msg_counter_(details::log_msg &msg);
const std::string name_; const std::string name_;
std::vector<sink_ptr> sinks_; std::vector<sink_ptr> sinks_;
spdlog::level_t level_; spdlog::level_t level_{spdlog::logger::default_level()};
spdlog::level_t flush_level_; spdlog::level_t flush_level_{level::off};
log_err_handler err_handler_; log_err_handler err_handler_{[this](const std::string &msg) { this->default_err_handler_(msg); }};
std::atomic<time_t> last_err_time_; std::atomic<time_t> last_err_time_{0};
std::atomic<size_t> msg_counter_; std::atomic<size_t> msg_counter_{1};
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
std::wstring_convert<std::codecvt_utf8<wchar_t>> wstring_converter_;
std::mutex wstring_converter_mutex_;
#endif
}; };
} // namespace spdlog } // namespace spdlog

View File

@ -5,6 +5,10 @@
#pragma once #pragma once
#ifndef SPDLOG_H
#error "spdlog.h must be included before this file."
#endif
#include "spdlog/details/fmt_helper.h" #include "spdlog/details/fmt_helper.h"
#include "spdlog/details/null_mutex.h" #include "spdlog/details/null_mutex.h"
#include "spdlog/details/os.h" #include "spdlog/details/os.h"
@ -27,7 +31,7 @@ namespace sinks {
* Android sink (logging using __android_log_write) * Android sink (logging using __android_log_write)
*/ */
template<typename Mutex> template<typename Mutex>
class android_sink SPDLOG_FINAL : public base_sink<Mutex> class android_sink final : public base_sink<Mutex>
{ {
public: public:
explicit android_sink(std::string tag = "spdlog", bool use_raw_msg = false) explicit android_sink(std::string tag = "spdlog", bool use_raw_msg = false)
@ -43,7 +47,7 @@ protected:
fmt::memory_buffer formatted; fmt::memory_buffer formatted;
if (use_raw_msg_) if (use_raw_msg_)
{ {
details::fmt_helper::append_buf(msg.raw, formatted); details::fmt_helper::append_string_view(msg.payload, formatted);
} }
else else
{ {

View File

@ -5,9 +5,14 @@
#pragma once #pragma once
#ifndef SPDLOG_H
#error "spdlog.h must be included before this file."
#endif
#include "spdlog/details/console_globals.h" #include "spdlog/details/console_globals.h"
#include "spdlog/details/null_mutex.h" #include "spdlog/details/null_mutex.h"
#include "spdlog/details/os.h" #include "spdlog/details/os.h"
#include "spdlog/sinks/sink.h"
#include <memory> #include <memory>
#include <mutex> #include <mutex>
@ -24,7 +29,7 @@ namespace sinks {
* If no color terminal detected, omit the escape codes. * If no color terminal detected, omit the escape codes.
*/ */
template<typename TargetStream, class ConsoleMutex> template<typename TargetStream, class ConsoleMutex>
class ansicolor_sink SPDLOG_FINAL : public sink class ansicolor_sink final : public sink
{ {
public: public:
using mutex_t = typename ConsoleMutex::mutex_t; using mutex_t = typename ConsoleMutex::mutex_t;
@ -116,7 +121,7 @@ public:
fflush(target_file_); fflush(target_file_);
} }
void set_pattern(const std::string &pattern) SPDLOG_FINAL void set_pattern(const std::string &pattern) final
{ {
std::lock_guard<mutex_t> lock(mutex_); std::lock_guard<mutex_t> lock(mutex_);
formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern));

View File

@ -22,41 +22,47 @@ template<typename Mutex>
class base_sink : public sink class base_sink : public sink
{ {
public: public:
base_sink() base_sink() = default;
: sink()
{
}
base_sink(const base_sink &) = delete; base_sink(const base_sink &) = delete;
base_sink &operator=(const base_sink &) = delete; base_sink &operator=(const base_sink &) = delete;
void log(const details::log_msg &msg) SPDLOG_FINAL void log(const details::log_msg &msg) final
{ {
std::lock_guard<Mutex> lock(mutex_); std::lock_guard<Mutex> lock(mutex_);
sink_it_(msg); sink_it_(msg);
} }
void flush() SPDLOG_FINAL override void flush() final
{ {
std::lock_guard<Mutex> lock(mutex_); std::lock_guard<Mutex> lock(mutex_);
flush_(); flush_();
} }
void set_pattern(const std::string &pattern) SPDLOG_FINAL override void set_pattern(const std::string &pattern) final
{ {
std::lock_guard<Mutex> lock(mutex_); std::lock_guard<Mutex> lock(mutex_);
formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); set_pattern_(pattern);
} }
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) SPDLOG_FINAL override void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) final
{ {
std::lock_guard<Mutex> lock(mutex_); std::lock_guard<Mutex> lock(mutex_);
formatter_ = std::move(sink_formatter); set_formatter_(std::move(sink_formatter));
} }
protected: protected:
virtual void sink_it_(const details::log_msg &msg) = 0; virtual void sink_it_(const details::log_msg &msg) = 0;
virtual void flush_() = 0; virtual void flush_() = 0;
virtual void set_pattern_(const std::string &pattern)
{
set_formatter_(details::make_unique<spdlog::pattern_formatter>(pattern));
}
virtual void set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter)
{
formatter_ = std::move(sink_formatter);
}
Mutex mutex_; Mutex mutex_;
}; };
} // namespace sinks } // namespace sinks

View File

@ -4,10 +4,14 @@
// //
#pragma once #pragma once
#ifndef SPDLOG_H
#error "spdlog.h must be included before this file."
#endif
#include "spdlog/details/file_helper.h" #include "spdlog/details/file_helper.h"
#include "spdlog/details/null_mutex.h" #include "spdlog/details/null_mutex.h"
#include "spdlog/sinks/base_sink.h" #include "spdlog/sinks/base_sink.h"
#include "spdlog/spdlog.h"
#include <mutex> #include <mutex>
#include <string> #include <string>
@ -18,7 +22,7 @@ namespace sinks {
* Trivial file sink with single file as target * Trivial file sink with single file as target
*/ */
template<typename Mutex> template<typename Mutex>
class basic_file_sink SPDLOG_FINAL : public base_sink<Mutex> class basic_file_sink final : public base_sink<Mutex>
{ {
public: public:
explicit basic_file_sink(const filename_t &filename, bool truncate = false) explicit basic_file_sink(const filename_t &filename, bool truncate = false)

View File

@ -4,11 +4,15 @@
// //
#pragma once #pragma once
#ifndef SPDLOG_H
#error "spdlog.h must be included before this file."
#endif
#include "spdlog/details/file_helper.h" #include "spdlog/details/file_helper.h"
#include "spdlog/details/null_mutex.h" #include "spdlog/details/null_mutex.h"
#include "spdlog/fmt/fmt.h" #include "spdlog/fmt/fmt.h"
#include "spdlog/sinks/base_sink.h" #include "spdlog/sinks/base_sink.h"
#include "spdlog/spdlog.h"
#include <chrono> #include <chrono>
#include <cstdio> #include <cstdio>
@ -28,7 +32,7 @@ struct daily_filename_calculator
static filename_t calc_filename(const filename_t &filename, const tm &now_tm) static filename_t calc_filename(const filename_t &filename, const tm &now_tm)
{ {
filename_t basename, ext; filename_t basename, ext;
std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); std::tie(basename, ext) = details::file_helper::split_by_extension(filename);
std::conditional<std::is_same<filename_t::value_type, char>::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w; std::conditional<std::is_same<filename_t::value_type, char>::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w;
fmt::format_to( fmt::format_to(
w, SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, ext); w, SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, ext);
@ -40,7 +44,7 @@ struct daily_filename_calculator
* Rotating file sink based on date. rotates at midnight * Rotating file sink based on date. rotates at midnight
*/ */
template<typename Mutex, typename FileNameCalc = daily_filename_calculator> template<typename Mutex, typename FileNameCalc = daily_filename_calculator>
class daily_file_sink SPDLOG_FINAL : public base_sink<Mutex> class daily_file_sink final : public base_sink<Mutex>
{ {
public: public:
// create daily file sink which rotates on given time // create daily file sink which rotates on given time

View File

@ -5,6 +5,10 @@
#pragma once #pragma once
#ifndef SPDLOG_H
#error "spdlog.h must be included before this file."
#endif
#include "base_sink.h" #include "base_sink.h"
#include "spdlog/details/log_msg.h" #include "spdlog/details/log_msg.h"
#include "spdlog/details/null_mutex.h" #include "spdlog/details/null_mutex.h"
@ -62,8 +66,24 @@ protected:
void flush_() override void flush_() override
{ {
for (auto &sink : sinks_) for (auto &sink : sinks_)
{
sink->flush(); sink->flush();
} }
}
void set_pattern_(const std::string &pattern) override
{
set_formatter_(details::make_unique<spdlog::pattern_formatter>(pattern));
}
void set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter) override
{
base_sink<Mutex>::formatter_ = std::move(sink_formatter);
for (auto &sink : sinks_)
{
sink->set_formatter(base_sink<Mutex>::formatter_->clone());
}
}
std::vector<std::shared_ptr<sink>> sinks_; std::vector<std::shared_ptr<sink>> sinks_;
}; };

View File

@ -5,6 +5,10 @@
#pragma once #pragma once
#ifndef SPDLOG_H
#error "spdlog.h must be included before this file."
#endif
#if defined(_WIN32) #if defined(_WIN32)
#include "spdlog/details/null_mutex.h" #include "spdlog/details/null_mutex.h"

View File

@ -5,6 +5,10 @@
#pragma once #pragma once
#ifndef SPDLOG_H
#error "spdlog.h must be included before this file."
#endif
#include "spdlog/details/null_mutex.h" #include "spdlog/details/null_mutex.h"
#include "spdlog/sinks/base_sink.h" #include "spdlog/sinks/base_sink.h"
@ -25,4 +29,21 @@ using null_sink_mt = null_sink<std::mutex>;
using null_sink_st = null_sink<details::null_mutex>; using null_sink_st = null_sink<details::null_mutex>;
} // namespace sinks } // namespace sinks
template<typename Factory = default_factory>
inline std::shared_ptr<logger> null_logger_mt(const std::string &logger_name)
{
auto null_logger = Factory::template create<sinks::null_sink_mt>(logger_name);
null_logger->set_level(level::off);
return null_logger;
}
template<typename Factory = default_factory>
inline std::shared_ptr<logger> null_logger_st(const std::string &logger_name)
{
auto null_logger = Factory::template create<sinks::null_sink_st>(logger_name);
null_logger->set_level(level::off);
return null_logger;
}
} // namespace spdlog } // namespace spdlog

View File

@ -5,6 +5,10 @@
#pragma once #pragma once
#ifndef SPDLOG_H
#error "spdlog.h must be included before this file."
#endif
#include "spdlog/details/null_mutex.h" #include "spdlog/details/null_mutex.h"
#include "spdlog/sinks/base_sink.h" #include "spdlog/sinks/base_sink.h"
@ -14,7 +18,7 @@
namespace spdlog { namespace spdlog {
namespace sinks { namespace sinks {
template<typename Mutex> template<typename Mutex>
class ostream_sink SPDLOG_FINAL : public base_sink<Mutex> class ostream_sink final : public base_sink<Mutex>
{ {
public: public:
explicit ostream_sink(std::ostream &os, bool force_flush = false) explicit ostream_sink(std::ostream &os, bool force_flush = false)
@ -32,8 +36,10 @@ protected:
sink::formatter_->format(msg, formatted); sink::formatter_->format(msg, formatted);
ostream_.write(formatted.data(), static_cast<std::streamsize>(formatted.size())); ostream_.write(formatted.data(), static_cast<std::streamsize>(formatted.size()));
if (force_flush_) if (force_flush_)
{
ostream_.flush(); ostream_.flush();
} }
}
void flush_() override void flush_() override
{ {

View File

@ -4,11 +4,15 @@
// //
#pragma once #pragma once
#ifndef SPDLOG_H
#error "spdlog.h must be included before this file."
#endif
#include "spdlog/details/file_helper.h" #include "spdlog/details/file_helper.h"
#include "spdlog/details/null_mutex.h" #include "spdlog/details/null_mutex.h"
#include "spdlog/fmt/fmt.h" #include "spdlog/fmt/fmt.h"
#include "spdlog/sinks/base_sink.h" #include "spdlog/sinks/base_sink.h"
#include "spdlog/spdlog.h"
#include <cerrno> #include <cerrno>
#include <chrono> #include <chrono>
@ -24,7 +28,7 @@ namespace sinks {
// Rotating file sink based on size // Rotating file sink based on size
// //
template<typename Mutex> template<typename Mutex>
class rotating_file_sink SPDLOG_FINAL : public base_sink<Mutex> class rotating_file_sink final : public base_sink<Mutex>
{ {
public: public:
rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open=false) rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files, bool rotate_on_open=false)
@ -46,7 +50,7 @@ public:
if (index != 0u) if (index != 0u)
{ {
filename_t basename, ext; filename_t basename, ext;
std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); std::tie(basename, ext) = details::file_helper::split_by_extension(filename);
fmt::format_to(w, SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); fmt::format_to(w, SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext);
} }
else else
@ -88,24 +92,22 @@ private:
for (auto i = max_files_; i > 0; --i) for (auto i = max_files_; i > 0; --i)
{ {
filename_t src = calc_filename(base_filename_, i - 1); filename_t src = calc_filename(base_filename_, i - 1);
if (!details::file_helper::file_exists(src))
{
continue;
}
filename_t target = calc_filename(base_filename_, i); filename_t target = calc_filename(base_filename_, i);
if (details::file_helper::file_exists(target)) if (!rename_file(src, target))
{ {
if (details::os::remove(target) != 0) // if failed try again after a small delay.
{
throw spdlog_ex("rotating_file_sink: failed removing " + filename_to_str(target), errno);
}
}
if (details::file_helper::file_exists(src) && details::os::rename(src, target) != 0)
{
// if failed try again after small delay.
// this is a workaround to a windows issue, where very high rotation // this is a workaround to a windows issue, where very high rotation
// rates sometimes fail (because of antivirus?). // rates can cause the rename to fail with permission denied (because of antivirus?).
details::os::sleep_for_millis(20); details::os::sleep_for_millis(100);
details::os::remove(target); if (!rename_file(src, target))
if (details::os::rename(src, target) != 0)
{ {
file_helper_.reopen(true); // truncate the log file anyway to prevent it to grow beyond its limit!
current_size_ = 0;
throw spdlog_ex( throw spdlog_ex(
"rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); "rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno);
} }
@ -114,6 +116,15 @@ private:
file_helper_.reopen(true); file_helper_.reopen(true);
} }
// delete the target if exists, and rename the src file to target
// return true on success, false otherwise.
bool rename_file(const filename_t &src_filename, const filename_t &target_filename)
{
// try to delete the target file in case it already exists.
(void)details::os::remove(target_filename);
return details::os::rename(src_filename, target_filename) == 0;
}
filename_t base_filename_; filename_t base_filename_;
std::size_t max_size_; std::size_t max_size_;
std::size_t max_files_; std::size_t max_files_;

View File

@ -16,7 +16,7 @@ class sink
public: public:
sink() sink()
: level_(level::trace) : level_(level::trace)
, formatter_(new pattern_formatter("%+")) , formatter_(new pattern_formatter())
{ {
} }

View File

@ -5,7 +5,10 @@
#pragma once #pragma once
#include "spdlog/spdlog.h" #ifndef SPDLOG_H
#error "spdlog.h must be included before this file."
#endif
#ifdef _WIN32 #ifdef _WIN32
#include "spdlog/sinks/wincolor_sink.h" #include "spdlog/sinks/wincolor_sink.h"
#else #else

View File

@ -5,9 +5,12 @@
#pragma once #pragma once
#ifndef SPDLOG_H
#error "spdlog.h must be included before this file."
#endif
#include "spdlog/details/console_globals.h" #include "spdlog/details/console_globals.h"
#include "spdlog/details/null_mutex.h" #include "spdlog/details/null_mutex.h"
#include "spdlog/spdlog.h"
#include <cstdio> #include <cstdio>
#include <memory> #include <memory>
@ -18,7 +21,7 @@ namespace spdlog {
namespace sinks { namespace sinks {
template<typename TargetStream, typename ConsoleMutex> template<typename TargetStream, typename ConsoleMutex>
class stdout_sink SPDLOG_FINAL : public sink class stdout_sink final : public sink
{ {
public: public:
using mutex_t = typename ConsoleMutex::mutex_t; using mutex_t = typename ConsoleMutex::mutex_t;

View File

@ -5,8 +5,11 @@
#pragma once #pragma once
#ifndef SPDLOG_H
#error "spdlog.h must be included before this file."
#endif
#include "spdlog/sinks/base_sink.h" #include "spdlog/sinks/base_sink.h"
#include "spdlog/spdlog.h"
#include <array> #include <array>
#include <string> #include <string>
@ -50,7 +53,7 @@ public:
protected: protected:
void sink_it_(const details::log_msg &msg) override void sink_it_(const details::log_msg &msg) override
{ {
::syslog(syslog_prio_from_level(msg), "%s", fmt::to_string(msg.raw).c_str()); ::syslog(syslog_prio_from_level(msg), "%s", fmt::to_string(msg.payload).c_str());
} }
void flush_() override {} void flush_() override {}

View File

@ -5,6 +5,10 @@
#pragma once #pragma once
#ifndef SPDLOG_H
#error "spdlog.h must be included before this file."
#endif
#include "spdlog/common.h" #include "spdlog/common.h"
#include "spdlog/details/console_globals.h" #include "spdlog/details/console_globals.h"
#include "spdlog/details/null_mutex.h" #include "spdlog/details/null_mutex.h"
@ -61,7 +65,7 @@ public:
colors_[level] = color; colors_[level] = color;
} }
void log(const details::log_msg &msg) SPDLOG_FINAL override void log(const details::log_msg &msg) final override
{ {
std::lock_guard<mutex_t> lock(mutex_); std::lock_guard<mutex_t> lock(mutex_);
fmt::memory_buffer formatted; fmt::memory_buffer formatted;
@ -85,18 +89,18 @@ public:
} }
} }
void flush() SPDLOG_FINAL override void flush() final override
{ {
// windows console always flushed? // windows console always flushed?
} }
void set_pattern(const std::string &pattern) override SPDLOG_FINAL void set_pattern(const std::string &pattern) override final
{ {
std::lock_guard<mutex_t> lock(mutex_); std::lock_guard<mutex_t> lock(mutex_);
formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern));
} }
void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override SPDLOG_FINAL void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override final
{ {
std::lock_guard<mutex_t> lock(mutex_); std::lock_guard<mutex_t> lock(mutex_);
formatter_ = std::move(sink_formatter); formatter_ = std::move(sink_formatter);

View File

@ -5,6 +5,8 @@
// spdlog main header file. // spdlog main header file.
// see example.cpp for usage example // see example.cpp for usage example
#ifndef SPDLOG_H
#define SPDLOG_H
#pragma once #pragma once
#include "spdlog/common.h" #include "spdlog/common.h"
@ -23,12 +25,11 @@ namespace spdlog {
struct synchronous_factory struct synchronous_factory
{ {
template<typename Sink, typename... SinkArgs> template<typename Sink, typename... SinkArgs>
static std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&... args) static std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&... args)
{ {
auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...); auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...);
auto new_logger = std::make_shared<logger>(std::move(logger_name), std::move(sink)); auto new_logger = std::make_shared<logger>(std::move(logger_name), std::move(sink));
details::registry::instance().register_and_init(new_logger); details::registry::instance().initialize_logger(new_logger);
return new_logger; return new_logger;
} }
}; };
@ -124,38 +125,242 @@ inline void shutdown()
details::registry::instance().shutdown(); details::registry::instance().shutdown();
} }
/////////////////////////////////////////////////////////////////////////////// // Automatic registration of loggers when using spdlog::create() or spdlog::create_async
// inline void set_automatic_registration(bool automatic_registation)
// Trace & Debug can be switched on/off at compile time for zero cost debug {
// statements. details::registry::instance().set_automatic_registration(automatic_registation);
// Uncomment SPDLOG_DEBUG_ON/SPDLOG_TRACE_ON in tweakme.h to enable. }
// SPDLOG_TRACE(..) will also print current file and line.
//
// Example:
// spdlog::set_level(spdlog::level::trace);
// SPDLOG_TRACE(my_logger, "some trace message");
// SPDLOG_TRACE(my_logger, "another trace message {} {}", 1, 2);
// SPDLOG_DEBUG(my_logger, "some debug message {} {}", 3, 4);
///////////////////////////////////////////////////////////////////////////////
#ifdef SPDLOG_TRACE_ON // API for using default logger (stdout_color_mt),
#define SPDLOG_STR_H(x) #x // e.g: spdlog::info("Message {}", 1);
#define SPDLOG_STR_HELPER(x) SPDLOG_STR_H(x) //
#ifdef _MSC_VER // The default logger object can be accessed using the spdlog::default_logger():
#define SPDLOG_TRACE(logger, ...) logger->trace("[ " __FILE__ "(" SPDLOG_STR_HELPER(__LINE__) ") ] " __VA_ARGS__) // For example, to add another sink to it:
#else // spdlog::default_logger()->sinks()->push_back(some_sink);
#define SPDLOG_TRACE(logger, ...) \ //
logger->trace("[ " __FILE__ ":" SPDLOG_STR_HELPER(__LINE__) " ]" \ // The default logger can replaced using spdlog::set_default_logger(new_logger).
" " __VA_ARGS__) // For example, to replace it with a file logger.
#endif //
#else // IMPORTANT:
#define SPDLOG_TRACE(logger, ...) (void)0 // The default API is thread safe (for _mt loggers), but:
#endif // 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.
#ifdef SPDLOG_DEBUG_ON inline std::shared_ptr<spdlog::logger> default_logger()
#define SPDLOG_DEBUG(logger, ...) logger->debug(__VA_ARGS__) {
#else return details::registry::instance().default_logger();
#define SPDLOG_DEBUG(logger, ...) (void)0 }
#endif
inline spdlog::logger *default_logger_raw()
{
return details::registry::instance().get_default_raw();
}
inline void set_default_logger(std::shared_ptr<spdlog::logger> default_logger)
{
details::registry::instance().set_default_logger(std::move(default_logger));
}
template<typename... Args>
inline void log(source_loc source, level::level_enum lvl, const char *fmt, const Args &... args)
{
default_logger_raw()->log(source, lvl, fmt, args...);
}
template<typename... Args>
inline void log(level::level_enum lvl, const char *fmt, const Args &... args)
{
default_logger_raw()->log(source_loc{}, lvl, fmt, args...);
}
template<typename... Args>
inline void trace(const char *fmt, const Args &... args)
{
default_logger_raw()->trace(fmt, args...);
}
template<typename... Args>
inline void debug(const char *fmt, const Args &... args)
{
default_logger_raw()->debug(fmt, args...);
}
template<typename... Args>
inline void info(const char *fmt, const Args &... args)
{
default_logger_raw()->info(fmt, args...);
}
template<typename... Args>
inline void warn(const char *fmt, const Args &... args)
{
default_logger_raw()->warn(fmt, args...);
}
template<typename... Args>
inline void error(const char *fmt, const Args &... args)
{
default_logger_raw()->error(fmt, args...);
}
template<typename... Args>
inline void critical(const char *fmt, const Args &... args)
{
default_logger_raw()->critical(fmt, args...);
}
template<typename T>
inline void log(level::level_enum lvl, const T &msg)
{
default_logger_raw()->log(lvl, msg);
}
template<typename T>
inline void trace(const T &msg)
{
default_logger_raw()->trace(msg);
}
template<typename T>
inline void debug(const T &msg)
{
default_logger_raw()->debug(msg);
}
template<typename T>
inline void info(const T &msg)
{
default_logger_raw()->info(msg);
}
template<typename T>
inline void warn(const T &msg)
{
default_logger_raw()->warn(msg);
}
template<typename T>
inline void error(const T &msg)
{
default_logger_raw()->error(msg);
}
template<typename T>
inline void critical(const T &msg)
{
default_logger_raw()->critical(msg);
}
#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT
template<typename... Args>
inline void log(level::level_enum lvl, const wchar_t *fmt, const Args &... args)
{
default_logger_raw()->log(lvl, fmt, args...);
}
template<typename... Args>
inline void trace(const wchar_t *fmt, const Args &... args)
{
default_logger_raw()->trace(fmt, args...);
}
template<typename... Args>
inline void debug(const wchar_t *fmt, const Args &... args)
{
default_logger_raw()->debug(fmt, args...);
}
template<typename... Args>
inline void info(const wchar_t *fmt, const Args &... args)
{
default_logger_raw()->info(fmt, args...);
}
template<typename... Args>
inline void warn(const wchar_t *fmt, const Args &... args)
{
default_logger_raw()->warn(fmt, args...);
}
template<typename... Args>
inline void error(const wchar_t *fmt, const Args &... args)
{
default_logger_raw()->error(fmt, args...);
}
template<typename... Args>
inline void critical(const wchar_t *fmt, const Args &... args)
{
default_logger_raw()->critical(fmt, args...);
}
#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT
} // namespace spdlog } // namespace spdlog
//
// enable/disable log calls at compile time according to global level.
//
// define SPDLOG_ACTIVE_LEVEL to one of those (before including spdlog.h):
// SPDLOG_LEVEL_TRACE,
// SPDLOG_LEVEL_DEBUG,
// SPDLOG_LEVEL_INFO,
// SPDLOG_LEVEL_WARN,
// SPDLOG_LEVEL_ERROR,
// SPDLOG_LEVEL_CRITICAL,
// SPDLOG_LEVEL_OFF
//
#define SPDLOG_LOGGER_CALL(logger, level, ...) \
if (logger->should_log(level)) \
logger->log(spdlog::source_loc{SPDLOG_FILE_BASENAME(__FILE__), __LINE__, SPDLOG_FUNCTION}, level, __VA_ARGS__)
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_TRACE
#define SPDLOG_LOGGER_TRACE(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::trace, __VA_ARGS__)
#define SPDLOG_TRACE(...) SPDLOG_LOGGER_TRACE(spdlog::default_logger_raw(), __VA_ARGS__)
#else
#define SPDLOG_LOGGER_TRACE(logger, ...) (void)0
#define SPDLOG_TRACE(...) (void)0
#endif
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG
#define SPDLOG_LOGGER_DEBUG(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::debug, __VA_ARGS__)
#define SPDLOG_DEBUG(...) SPDLOG_LOGGER_DEBUG(spdlog::default_logger_raw(), __VA_ARGS__)
#else
#define SPDLOG_LOGGER_DEBUG(logger, ...) (void)0
#define SPDLOG_DEBUG(...) (void)0
#endif
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_INFO
#define SPDLOG_LOGGER_INFO(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::info, __VA_ARGS__)
#define SPDLOG_INFO(...) SPDLOG_LOGGER_INFO(spdlog::default_logger_raw(), __VA_ARGS__)
#else
#define SPDLOG_LOGGER_INFO(logger, ...) (void)0
#define SPDLOG_INFO(...) (void)0
#endif
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_WARN
#define SPDLOG_LOGGER_WARN(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::warn, __VA_ARGS__)
#define SPDLOG_WARN(...) SPDLOG_LOGGER_WARN(spdlog::default_logger_raw(), __VA_ARGS__)
#else
#define SPDLOG_LOGGER_WARN(logger, ...) (void)0
#define SPDLOG_WARN(...) (void)0
#endif
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_ERROR
#define SPDLOG_LOGGER_ERROR(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::err, __VA_ARGS__)
#define SPDLOG_ERROR(...) SPDLOG_LOGGER_ERROR(spdlog::default_logger_raw(), __VA_ARGS__)
#else
#define SPDLOG_LOGGER_ERROR(logger, ...) (void)0
#define SPDLOG_ERROR(...) (void)0
#endif
#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_CRITICAL
#define SPDLOG_LOGGER_CRITICAL(logger, ...) SPDLOG_LOGGER_CALL(logger, spdlog::level::critical, __VA_ARGS__)
#define SPDLOG_CRITICAL(...) SPDLOG_LOGGER_CRITICAL(spdlog::default_logger_raw(), __VA_ARGS__)
#else
#define SPDLOG_LOGGER_CRITICAL(logger, ...) (void)0
#define SPDLOG_CRITICAL(...) (void)0
#endif
#endif // SPDLOG_H

View File

@ -45,13 +45,12 @@
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Uncomment to prevent spdlog from caching thread ids in thread local storage. // Uncomment to prevent spdlog from using thread local storage.
// By default spdlog saves thread ids in tls to gain a few micros for each call.
// //
// WARNING: if your program forks, UNCOMMENT this flag to prevent undefined // WARNING: if your program forks, UNCOMMENT this flag to prevent undefined
// thread ids in the children logs. // thread ids in the children logs.
// //
// #define SPDLOG_DISABLE_TID_CACHING // #define SPDLOG_NO_TLS
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -108,15 +107,6 @@
// #define SPDLOG_PREVENT_CHILD_FD // #define SPDLOG_PREVENT_CHILD_FD
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Uncomment if your compiler doesn't support the "final" keyword.
// The final keyword allows more optimizations in release
// mode with recent compilers. See GCC's documentation for -Wsuggest-final-types
// for instance.
//
// #define SPDLOG_NO_FINAL
///////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Uncomment to enable message counting feature. // Uncomment to enable message counting feature.
// Use the %i in the logger pattern to display log message sequence id. // Use the %i in the logger pattern to display log message sequence id.
@ -130,3 +120,26 @@
// #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING", // #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING",
// "MY ERROR", "MY CRITICAL", "OFF" } // "MY ERROR", "MY CRITICAL", "OFF" }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Uncomment to disable default logger creation.
// This might save some (very) small initialization time if no default logger is needed.
//
// #define SPDLOG_DISABLE_DEFAULT_LOGGER
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Uncomment and set to compile time level with zero cost (default is INFO).
// Macros like SPDLOG_DEBUG(..), SPDLOG_INFO(..) will expand to empty statements if not enabled
//
// #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_INFO
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Uncomment (and change if desired) macro to use for function names.
// This is compiler dependent.
// __PRETTY_FUNCTION__ might be nicer in clang/gcc, and __FUNCTION__ in msvc.
// Defaults to __FUNCTION__ (should work on all compilers) if not defined.
//
// #define SPDLOG_FUNCTION __PRETTY_FUNCTION__
///////////////////////////////////////////////////////////////////////////////

View File

@ -6,7 +6,7 @@
#pragma once #pragma once
#define SPDLOG_VER_MAJOR 1 #define SPDLOG_VER_MAJOR 1
#define SPDLOG_VER_MINOR 1 #define SPDLOG_VER_MINOR 3
#define SPDLOG_VER_PATCH 0 #define SPDLOG_VER_PATCH 0
#define SPDLOG_VERSION (SPDLOG_VER_MAJOR * 10000 + SPDLOG_VER_MINOR * 100 + SPDLOG_VER_PATCH) #define SPDLOG_VERSION (SPDLOG_VER_MAJOR * 10000 + SPDLOG_VER_MINOR * 100 + SPDLOG_VER_PATCH)

View File

@ -3,21 +3,21 @@ project(spdlog-utests CXX)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
set(SPDLOG_UTESTS_SOURCES set(SPDLOG_UTESTS_SOURCES
errors.cpp test_errors.cpp
file_helper.cpp test_file_helper.cpp
file_log.cpp test_file_logging.cpp
test_misc.cpp test_misc.cpp
test_pattern_formatter.cpp test_pattern_formatter.cpp
test_async.cpp test_async.cpp
includes.h includes.h
registry.cpp test_registry.cpp
test_macros.cpp test_macros.cpp
utils.cpp utils.cpp
utils.h utils.h
main.cpp main.cpp
test_mpmc_q.cpp test_mpmc_q.cpp
test_sink.h test_sink.h
) test_fmt_helper.cpp)
add_executable(${PROJECT_NAME} ${SPDLOG_UTESTS_SOURCES}) add_executable(${PROJECT_NAME} ${SPDLOG_UTESTS_SOURCES})
target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads) target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads)

View File

@ -10,9 +10,10 @@
#include <ostream> #include <ostream>
#include <string> #include <string>
#define SPDLOG_TRACE_ON #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG
#define SPDLOG_DEBUG_ON #define SPDLOG_ENABLE_MESSAGE_COUNTER
#include "spdlog/spdlog.h"
#include "spdlog/async.h" #include "spdlog/async.h"
#include "spdlog/sinks/basic_file_sink.h" #include "spdlog/sinks/basic_file_sink.h"
#include "spdlog/sinks/daily_file_sink.h" #include "spdlog/sinks/daily_file_sink.h"
@ -20,4 +21,3 @@
#include "spdlog/sinks/ostream_sink.h" #include "spdlog/sinks/ostream_sink.h"
#include "spdlog/sinks/rotating_file_sink.h" #include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/sinks/stdout_color_sinks.h" #include "spdlog/sinks/stdout_color_sinks.h"
#include "spdlog/spdlog.h"

View File

@ -83,7 +83,9 @@ TEST_CASE("async_error_handler", "[errors]]")
logger->set_error_handler([=](const std::string &) { logger->set_error_handler([=](const std::string &) {
std::ofstream ofs("logs/custom_err.txt"); std::ofstream ofs("logs/custom_err.txt");
if (!ofs) if (!ofs)
{
throw std::runtime_error("Failed open logs/custom_err.txt"); throw std::runtime_error("Failed open logs/custom_err.txt");
}
ofs << err_msg; ofs << err_msg;
}); });
logger->info("Good message #1"); logger->info("Good message #1");

View File

@ -10,7 +10,6 @@ static const std::string target_filename = "logs/file_helper_test.txt";
static void write_with_helper(file_helper &helper, size_t howmany) static void write_with_helper(file_helper &helper, size_t howmany)
{ {
log_msg msg;
fmt::memory_buffer formatted; fmt::memory_buffer formatted;
fmt::format_to(formatted, "{}", std::string(howmany, '1')); fmt::format_to(formatted, "{}", std::string(howmany, '1'));
helper.write(formatted); helper.write(formatted);
@ -82,12 +81,12 @@ static void test_split_ext(const char *fname, const char *expect_base, const cha
std::replace(expected_base.begin(), expected_base.end(), '/', '\\'); std::replace(expected_base.begin(), expected_base.end(), '/', '\\');
#endif #endif
spdlog::filename_t basename, ext; spdlog::filename_t basename, ext;
std::tie(basename, ext) = file_helper::split_by_extenstion(filename); std::tie(basename, ext) = file_helper::split_by_extension(filename);
REQUIRE(basename == expected_base); REQUIRE(basename == expected_base);
REQUIRE(ext == expected_ext); REQUIRE(ext == expected_ext);
} }
TEST_CASE("file_helper_split_by_extenstion", "[file_helper::split_by_extenstion()]]") TEST_CASE("file_helper_split_by_extension", "[file_helper::split_by_extension()]]")
{ {
test_split_ext("mylog.txt", "mylog", ".txt"); test_split_ext("mylog.txt", "mylog", ".txt");
test_split_ext(".mylog.txt", ".mylog", ".txt"); test_split_ext(".mylog.txt", ".mylog", ".txt");

View File

@ -62,7 +62,9 @@ TEST_CASE("rotating_file_logger2", "[rotating_logger]]")
std::string basename = "logs/rotating_log"; std::string basename = "logs/rotating_log";
auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 1, true); auto logger = spdlog::rotating_logger_mt("logger", basename, max_size, 1, 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();
auto filename = basename; auto filename = basename;

75
tests/test_fmt_helper.cpp Normal file
View File

@ -0,0 +1,75 @@
#include "includes.h"
void test_pad2(int n, const char *expected)
{
fmt::memory_buffer buf;
spdlog::details::fmt_helper::pad2(n, buf);
REQUIRE(fmt::to_string(buf) == expected);
}
void test_pad3(uint32_t n, const char *expected)
{
fmt::memory_buffer buf;
spdlog::details::fmt_helper::pad3(n, buf);
REQUIRE(fmt::to_string(buf) == expected);
}
void test_pad6(std::size_t n, const char *expected)
{
fmt::memory_buffer buf;
spdlog::details::fmt_helper::pad6(n, buf);
REQUIRE(fmt::to_string(buf) == expected);
}
void test_pad9(std::size_t n, const char *expected)
{
fmt::memory_buffer buf;
spdlog::details::fmt_helper::pad9(n, buf);
REQUIRE(fmt::to_string(buf) == expected);
}
TEST_CASE("pad2", "[fmt_helper]")
{
test_pad2(0, "00");
test_pad2(3, "03");
test_pad2(23, "23");
test_pad2(123, "123");
test_pad2(1234, "1234");
test_pad2(-5, "-5");
}
TEST_CASE("pad3", "[fmt_helper]")
{
test_pad3(0, "000");
test_pad3(3, "003");
test_pad3(23, "023");
test_pad3(123, "123");
test_pad3(1234, "1234");
}
TEST_CASE("pad6", "[fmt_helper]")
{
test_pad6(0, "000000");
test_pad6(3, "000003");
test_pad6(23, "000023");
test_pad6(123, "000123");
test_pad6(1234, "001234");
test_pad6(12345, "012345");
test_pad6(123456, "123456");
}
TEST_CASE("pad9", "[fmt_helper]")
{
test_pad9(0, "000000000");
test_pad9(3, "000000003");
test_pad9(23, "000000023");
test_pad9(123, "000000123");
test_pad9(1234, "000001234");
test_pad9(12345, "000012345");
test_pad9(123456, "000123456");
test_pad9(1234567, "001234567");
test_pad9(12345678, "012345678");
test_pad9(123456789, "123456789");
test_pad9(1234567891, "1234567891");
}

View File

@ -4,8 +4,13 @@
#include "includes.h" #include "includes.h"
#if SPDLOG_ACTIVE_LEVEL != SPDLOG_LEVEL_DEBUG
#error "Invalid SPDLOG_ACTIVE_LEVEL in test. Should be SPDLOG_LEVEL_DEBUG"
#endif
TEST_CASE("debug and trace w/o format string", "[macros]]") TEST_CASE("debug and trace w/o format string", "[macros]]")
{ {
prepare_logdir(); prepare_logdir();
std::string filename = "logs/simple_log"; std::string filename = "logs/simple_log";
@ -13,28 +18,24 @@ TEST_CASE("debug and trace w/o format string", "[macros]]")
logger->set_pattern("%v"); logger->set_pattern("%v");
logger->set_level(spdlog::level::trace); logger->set_level(spdlog::level::trace);
SPDLOG_TRACE(logger, "Test message 1"); SPDLOG_LOGGER_TRACE(logger, "Test message 1");
SPDLOG_DEBUG(logger, "Test message 2"); SPDLOG_LOGGER_DEBUG(logger, "Test message 2");
logger->flush(); logger->flush();
REQUIRE(ends_with(file_contents(filename), "Test message 2\n")); REQUIRE(ends_with(file_contents(filename), "Test message 2\n"));
REQUIRE(count_lines(filename) == 2); REQUIRE(count_lines(filename) == 1);
}
TEST_CASE("debug and trace with format strings", "[macros]]") spdlog::set_default_logger(logger);
{
prepare_logdir();
std::string filename = "logs/simple_log";
auto logger = spdlog::create<spdlog::sinks::basic_file_sink_mt>("logger", filename); SPDLOG_TRACE("Test message 3");
logger->set_pattern("%v"); SPDLOG_DEBUG("Test message {}", 4);
logger->set_level(spdlog::level::trace);
SPDLOG_TRACE(logger, "Test message {}", 1);
// SPDLOG_DEBUG(logger, "Test message 2");
SPDLOG_DEBUG(logger, "Test message {}", 222);
logger->flush(); logger->flush();
REQUIRE(ends_with(file_contents(filename), "Test message 222\n")); REQUIRE(ends_with(file_contents(filename), "Test message 4\n"));
REQUIRE(count_lines(filename) == 2); REQUIRE(count_lines(filename) == 2);
} }
TEST_CASE("disable param evaluation", "[macros]")
{
SPDLOG_TRACE("Test message {}", throw std::runtime_error("Should not be evaluated"));
}

View File

@ -43,15 +43,15 @@ TEST_CASE("log_levels", "[log_levels]")
REQUIRE(log_info("Hello", spdlog::level::trace) == "Hello"); REQUIRE(log_info("Hello", spdlog::level::trace) == "Hello");
} }
TEST_CASE("to_c_str", "[convert_to_c_str]") TEST_CASE("level_to_string_view", "[convert_to_string_view")
{ {
REQUIRE(std::string(spdlog::level::to_c_str(spdlog::level::trace)) == "trace"); REQUIRE(spdlog::level::to_string_view(spdlog::level::trace) == "trace");
REQUIRE(std::string(spdlog::level::to_c_str(spdlog::level::debug)) == "debug"); REQUIRE(spdlog::level::to_string_view(spdlog::level::debug) == "debug");
REQUIRE(std::string(spdlog::level::to_c_str(spdlog::level::info)) == "info"); REQUIRE(spdlog::level::to_string_view(spdlog::level::info) == "info");
REQUIRE(std::string(spdlog::level::to_c_str(spdlog::level::warn)) == "warning"); REQUIRE(spdlog::level::to_string_view(spdlog::level::warn) == "warning");
REQUIRE(std::string(spdlog::level::to_c_str(spdlog::level::err)) == "error"); REQUIRE(spdlog::level::to_string_view(spdlog::level::err) == "error");
REQUIRE(std::string(spdlog::level::to_c_str(spdlog::level::critical)) == "critical"); REQUIRE(spdlog::level::to_string_view(spdlog::level::critical) == "critical");
REQUIRE(std::string(spdlog::level::to_c_str(spdlog::level::off)) == "off"); REQUIRE(spdlog::level::to_string_view(spdlog::level::off) == "off");
} }
TEST_CASE("to_short_c_str", "[convert_to_short_c_str]") TEST_CASE("to_short_c_str", "[convert_to_short_c_str]")
@ -86,13 +86,13 @@ TEST_CASE("periodic flush", "[periodic_flush]")
auto test_sink = std::static_pointer_cast<sinks::test_sink_mt>(logger->sinks()[0]); auto test_sink = std::static_pointer_cast<sinks::test_sink_mt>(logger->sinks()[0]);
spdlog::flush_every(std::chrono::seconds(1)); spdlog::flush_every(std::chrono::seconds(1));
std::this_thread::sleep_for(std::chrono::milliseconds(1100)); std::this_thread::sleep_for(std::chrono::milliseconds(1250));
REQUIRE(test_sink->flush_counter() == 1); REQUIRE(test_sink->flush_counter() == 1);
spdlog::flush_every(std::chrono::seconds(0)); spdlog::flush_every(std::chrono::seconds(0));
spdlog::drop_all(); spdlog::drop_all();
} }
TEST_CASE("clone", "[clone]") TEST_CASE("clone-logger", "[clone]")
{ {
using namespace spdlog; using namespace spdlog;
@ -134,3 +134,100 @@ TEST_CASE("clone async", "[clone]")
spdlog::drop_all(); spdlog::drop_all();
} }
#include "spdlog/fmt/bin_to_hex.h"
TEST_CASE("to_hex", "[to_hex]")
{
std::ostringstream oss;
auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss);
spdlog::logger oss_logger("oss", oss_sink);
std::vector<unsigned char> v{9, 0xa, 0xb, 0xc, 0xff, 0xff};
oss_logger.info("{}", spdlog::to_hex(v));
auto output = oss.str();
REQUIRE(ends_with(output, "0000: 09 0a 0b 0c ff ff" + std::string(spdlog::details::os::default_eol)));
}
TEST_CASE("to_hex_upper", "[to_hex]")
{
std::ostringstream oss;
auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss);
spdlog::logger oss_logger("oss", oss_sink);
std::vector<unsigned char> v{9, 0xa, 0xb, 0xc, 0xff, 0xff};
oss_logger.info("{:X}", spdlog::to_hex(v));
auto output = oss.str();
REQUIRE(ends_with(output, "0000: 09 0A 0B 0C FF FF" + std::string(spdlog::details::os::default_eol)));
}
TEST_CASE("to_hex_no_delimiter", "[to_hex]")
{
std::ostringstream oss;
auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss);
spdlog::logger oss_logger("oss", oss_sink);
std::vector<unsigned char> v{9, 0xa, 0xb, 0xc, 0xff, 0xff};
oss_logger.info("{:sX}", spdlog::to_hex(v));
auto output = oss.str();
REQUIRE(ends_with(output, "0000: 090A0B0CFFFF" + std::string(spdlog::details::os::default_eol)));
}
TEST_CASE("message_counter", "[message_counter]")
{
std::ostringstream oss;
auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss);
spdlog::logger oss_logger("oss", oss_sink);
oss_logger.set_pattern("%i %v");
oss_logger.info("Hello");
REQUIRE(oss.str() == "000001 Hello" + std::string(spdlog::details::os::default_eol));
oss.str("");
oss_logger.info("Hello again");
REQUIRE(oss.str() == "000002 Hello again" + std::string(spdlog::details::os::default_eol));
}
TEST_CASE("default logger API", "[default logger]")
{
std::ostringstream oss;
auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss);
spdlog::set_default_logger(std::make_shared<spdlog::logger>("oss", oss_sink));
spdlog::set_pattern("*** %v");
spdlog::default_logger()->set_level(spdlog::level::trace);
spdlog::trace("hello trace");
REQUIRE(oss.str() == "*** hello trace" + std::string(spdlog::details::os::default_eol));
oss.str("");
spdlog::debug("hello debug");
REQUIRE(oss.str() == "*** hello debug" + std::string(spdlog::details::os::default_eol));
oss.str("");
spdlog::info("Hello");
REQUIRE(oss.str() == "*** Hello" + std::string(spdlog::details::os::default_eol));
oss.str("");
spdlog::warn("Hello again {}", 2);
REQUIRE(oss.str() == "*** Hello again 2" + std::string(spdlog::details::os::default_eol));
oss.str("");
spdlog::error(123);
REQUIRE(oss.str() == "*** 123" + std::string(spdlog::details::os::default_eol));
oss.str("");
spdlog::critical(std::string("some string"));
REQUIRE(oss.str() == "*** some string" + std::string(spdlog::details::os::default_eol));
oss.str("");
spdlog::set_level(spdlog::level::info);
spdlog::debug("should not be logged");
REQUIRE(oss.str().empty());
spdlog::drop_all();
spdlog::set_pattern("%v");
}

View File

@ -29,7 +29,7 @@ TEST_CASE("dequeue-empty-wait", "[mpmc_blocking_q]")
size_t q_size = 100; size_t q_size = 100;
milliseconds wait_ms(250); milliseconds wait_ms(250);
milliseconds tolerance_wait(100); milliseconds tolerance_wait(50);
spdlog::details::mpmc_blocking_queue<int> q(q_size); spdlog::details::mpmc_blocking_queue<int> q(q_size);
int popped_item; int popped_item;
@ -40,7 +40,7 @@ TEST_CASE("dequeue-empty-wait", "[mpmc_blocking_q]")
REQUIRE(rv == false); REQUIRE(rv == false);
INFO("Delta " << delta_ms.count() << " millis"); INFO("Delta " << delta_ms.count() << " millis");
REQUIRE(delta_ms >= wait_ms); REQUIRE(delta_ms >= wait_ms - tolerance_wait);
REQUIRE(delta_ms <= wait_ms + tolerance_wait); REQUIRE(delta_ms <= wait_ms + tolerance_wait);
} }

View File

@ -20,9 +20,6 @@ TEST_CASE("custom eol", "[pattern_formatter]")
{ {
std::string msg = "Hello custom eol test"; std::string msg = "Hello custom eol test";
std::string eol = ";)"; std::string eol = ";)";
// auto formatter = std::make_shared<spdlog::pattern_formatter>("%v", spdlog::pattern_time_type::local, ";)");
std::unique_ptr<spdlog::formatter>(new spdlog::pattern_formatter("%v", spdlog::pattern_time_type::local, ";)"));
REQUIRE(log_to_str(msg, "%v", spdlog::pattern_time_type::local, ";)") == msg + eol); REQUIRE(log_to_str(msg, "%v", spdlog::pattern_time_type::local, ";)") == msg + eol);
} }
@ -63,9 +60,12 @@ TEST_CASE("date MM/DD/YY ", "[pattern_formatter]")
TEST_CASE("color range test1", "[pattern_formatter]") TEST_CASE("color range test1", "[pattern_formatter]")
{ {
auto formatter = std::make_shared<spdlog::pattern_formatter>("%^%v%$", spdlog::pattern_time_type::local, "\n"); auto formatter = std::make_shared<spdlog::pattern_formatter>("%^%v%$", spdlog::pattern_time_type::local, "\n");
spdlog::details::log_msg msg;
fmt::format_to(msg.raw, "Hello"); fmt::memory_buffer buf;
fmt::format_to(buf, "Hello");
fmt::memory_buffer formatted; fmt::memory_buffer formatted;
std::string logger_name = "test";
spdlog::details::log_msg msg(&logger_name, spdlog::level::info, spdlog::string_view_t(buf.data(), buf.size()));
formatter->format(msg, formatted); formatter->format(msg, formatted);
REQUIRE(msg.color_range_start == 0); REQUIRE(msg.color_range_start == 0);
REQUIRE(msg.color_range_end == 5); REQUIRE(msg.color_range_end == 5);
@ -75,7 +75,8 @@ TEST_CASE("color range test1", "[pattern_formatter]")
TEST_CASE("color range test2", "[pattern_formatter]") TEST_CASE("color range test2", "[pattern_formatter]")
{ {
auto formatter = std::make_shared<spdlog::pattern_formatter>("%^%$", spdlog::pattern_time_type::local, "\n"); auto formatter = std::make_shared<spdlog::pattern_formatter>("%^%$", spdlog::pattern_time_type::local, "\n");
spdlog::details::log_msg msg; std::string logger_name = "test";
spdlog::details::log_msg msg(&logger_name, spdlog::level::info, "");
fmt::memory_buffer formatted; fmt::memory_buffer formatted;
formatter->format(msg, formatted); formatter->format(msg, formatted);
REQUIRE(msg.color_range_start == 0); REQUIRE(msg.color_range_start == 0);
@ -86,7 +87,8 @@ TEST_CASE("color range test2", "[pattern_formatter]")
TEST_CASE("color range test3", "[pattern_formatter]") TEST_CASE("color range test3", "[pattern_formatter]")
{ {
auto formatter = std::make_shared<spdlog::pattern_formatter>("%^***%$"); auto formatter = std::make_shared<spdlog::pattern_formatter>("%^***%$");
spdlog::details::log_msg msg; std::string logger_name = "test";
spdlog::details::log_msg msg(&logger_name, spdlog::level::info, "ignored");
fmt::memory_buffer formatted; fmt::memory_buffer formatted;
formatter->format(msg, formatted); formatter->format(msg, formatted);
REQUIRE(msg.color_range_start == 0); REQUIRE(msg.color_range_start == 0);
@ -96,8 +98,9 @@ TEST_CASE("color range test3", "[pattern_formatter]")
TEST_CASE("color range test4", "[pattern_formatter]") TEST_CASE("color range test4", "[pattern_formatter]")
{ {
auto formatter = std::make_shared<spdlog::pattern_formatter>("XX%^YYY%$", spdlog::pattern_time_type::local, "\n"); auto formatter = std::make_shared<spdlog::pattern_formatter>("XX%^YYY%$", spdlog::pattern_time_type::local, "\n");
spdlog::details::log_msg msg; std::string logger_name = "test";
fmt::format_to(msg.raw, "ignored"); spdlog::details::log_msg msg(&logger_name, spdlog::level::info, "ignored");
fmt::memory_buffer formatted; fmt::memory_buffer formatted;
formatter->format(msg, formatted); formatter->format(msg, formatted);
REQUIRE(msg.color_range_start == 2); REQUIRE(msg.color_range_start == 2);
@ -108,7 +111,8 @@ TEST_CASE("color range test4", "[pattern_formatter]")
TEST_CASE("color range test5", "[pattern_formatter]") TEST_CASE("color range test5", "[pattern_formatter]")
{ {
auto formatter = std::make_shared<spdlog::pattern_formatter>("**%^"); auto formatter = std::make_shared<spdlog::pattern_formatter>("**%^");
spdlog::details::log_msg msg; std::string logger_name = "test";
spdlog::details::log_msg msg(&logger_name, spdlog::level::info, "ignored");
fmt::memory_buffer formatted; fmt::memory_buffer formatted;
formatter->format(msg, formatted); formatter->format(msg, formatted);
REQUIRE(msg.color_range_start == 2); REQUIRE(msg.color_range_start == 2);
@ -118,9 +122,134 @@ TEST_CASE("color range test5", "[pattern_formatter]")
TEST_CASE("color range test6", "[pattern_formatter]") TEST_CASE("color range test6", "[pattern_formatter]")
{ {
auto formatter = std::make_shared<spdlog::pattern_formatter>("**%$"); auto formatter = std::make_shared<spdlog::pattern_formatter>("**%$");
spdlog::details::log_msg msg; std::string logger_name = "test";
spdlog::details::log_msg msg(&logger_name, spdlog::level::info, "ignored");
fmt::memory_buffer formatted; fmt::memory_buffer formatted;
formatter->format(msg, formatted); formatter->format(msg, formatted);
REQUIRE(msg.color_range_start == 0); REQUIRE(msg.color_range_start == 0);
REQUIRE(msg.color_range_end == 2); REQUIRE(msg.color_range_end == 2);
} }
//
// Test padding
//
TEST_CASE("level_left_padded", "[pattern_formatter]")
{
REQUIRE(log_to_str("Some message", "[%8l] %v", spdlog::pattern_time_type::local, "\n") == "[ info] Some message\n");
}
TEST_CASE("level_right_padded", "[pattern_formatter]")
{
REQUIRE(log_to_str("Some message", "[%-8l] %v", spdlog::pattern_time_type::local, "\n") == "[info ] Some message\n");
}
TEST_CASE("level_center_padded", "[pattern_formatter]")
{
REQUIRE(log_to_str("Some message", "[%=8l] %v", spdlog::pattern_time_type::local, "\n") == "[ info ] Some message\n");
}
TEST_CASE("short level_left_padded", "[pattern_formatter]")
{
REQUIRE(log_to_str("Some message", "[%3L] %v", spdlog::pattern_time_type::local, "\n") == "[ I] Some message\n");
}
TEST_CASE("short level_right_padded", "[pattern_formatter]")
{
REQUIRE(log_to_str("Some message", "[%-3L] %v", spdlog::pattern_time_type::local, "\n") == "[I ] Some message\n");
}
TEST_CASE("short level_center_padded", "[pattern_formatter]")
{
REQUIRE(log_to_str("Some message", "[%=3L] %v", spdlog::pattern_time_type::local, "\n") == "[ I ] Some message\n");
}
TEST_CASE("left_padded_short", "[pattern_formatter]")
{
REQUIRE(log_to_str("Some message", "[%3n] %v", spdlog::pattern_time_type::local, "\n") == "[pattern_tester] Some message\n");
}
TEST_CASE("right_padded_short", "[pattern_formatter]")
{
REQUIRE(log_to_str("Some message", "[%-3n] %v", spdlog::pattern_time_type::local, "\n") == "[pattern_tester] Some message\n");
}
TEST_CASE("center_padded_short", "[pattern_formatter]")
{
REQUIRE(log_to_str("Some message", "[%=3n] %v", spdlog::pattern_time_type::local, "\n") == "[pattern_tester] Some message\n");
}
TEST_CASE("left_padded_huge", "[pattern_formatter]")
{
REQUIRE(
log_to_str("Some message", "[%-300n] %v", spdlog::pattern_time_type::local, "\n") ==
"[pattern_tester ]"
" Some message\n");
}
TEST_CASE("left_padded_max", "[pattern_formatter]")
{
REQUIRE(
log_to_str("Some message", "[%-128n] %v", spdlog::pattern_time_type::local, "\n") ==
"[pattern_tester ]"
" Some message\n");
}
TEST_CASE("clone-default-formatter", "[pattern_formatter]")
{
auto formatter_1 = std::make_shared<spdlog::pattern_formatter>();
auto formatter_2 = formatter_1->clone();
std::string logger_name = "test";
spdlog::details::log_msg msg(&logger_name, spdlog::level::info, "some message");
fmt::memory_buffer formatted_1;
fmt::memory_buffer formatted_2;
formatter_1->format(msg, formatted_1);
formatter_2->format(msg, formatted_2);
REQUIRE(fmt::to_string(formatted_1) == fmt::to_string(formatted_2));
}
TEST_CASE("clone-default-formatter2", "[pattern_formatter]")
{
auto formatter_1 = std::make_shared<spdlog::pattern_formatter>("%+");
auto formatter_2 = formatter_1->clone();
std::string logger_name = "test";
spdlog::details::log_msg msg(&logger_name, spdlog::level::info, "some message");
fmt::memory_buffer formatted_1;
fmt::memory_buffer formatted_2;
formatter_1->format(msg, formatted_1);
formatter_2->format(msg, formatted_2);
REQUIRE(fmt::to_string(formatted_1) == fmt::to_string(formatted_2));
}
TEST_CASE("clone-formatter", "[pattern_formatter]")
{
auto formatter_1 = std::make_shared<spdlog::pattern_formatter>("%D %X [%] [%n] %v");
auto formatter_2 = formatter_1->clone();
std::string logger_name = "test";
spdlog::details::log_msg msg(&logger_name, spdlog::level::info, "some message");
fmt::memory_buffer formatted_1;
fmt::memory_buffer formatted_2;
formatter_1->format(msg, formatted_1);
formatter_2->format(msg, formatted_2);
REQUIRE(fmt::to_string(formatted_1) == fmt::to_string(formatted_2));
}
TEST_CASE("clone-formatter-2", "[pattern_formatter]")
{
using spdlog::pattern_time_type;
auto formatter_1 = std::make_shared<spdlog::pattern_formatter>("%D %X [%] [%n] %v", pattern_time_type::utc, "xxxxxx\n");
auto formatter_2 = formatter_1->clone();
std::string logger_name = "test2";
spdlog::details::log_msg msg(&logger_name, spdlog::level::info, "some message");
fmt::memory_buffer formatted_1;
fmt::memory_buffer formatted_2;
formatter_1->format(msg, formatted_1);
formatter_2->format(msg, formatted_2);
REQUIRE(fmt::to_string(formatted_1) == fmt::to_string(formatted_2));
}

View File

@ -12,8 +12,7 @@ TEST_CASE("register_drop", "[registry]")
REQUIRE_THROWS_AS(spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name), const spdlog::spdlog_ex &); REQUIRE_THROWS_AS(spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name), const spdlog::spdlog_ex &);
} }
TEST_CASE("explicit register" TEST_CASE("explicit register", "[registry]")
"[registry]")
{ {
spdlog::drop_all(); spdlog::drop_all();
auto logger = std::make_shared<spdlog::logger>(tested_logger_name, std::make_shared<spdlog::sinks::null_sink_st>()); auto logger = std::make_shared<spdlog::logger>(tested_logger_name, std::make_shared<spdlog::sinks::null_sink_st>());
@ -23,8 +22,7 @@ TEST_CASE("explicit register"
REQUIRE_THROWS_AS(spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name), const spdlog::spdlog_ex &); REQUIRE_THROWS_AS(spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name), const spdlog::spdlog_ex &);
} }
TEST_CASE("apply_all" TEST_CASE("apply_all", "[registry]")
"[registry]")
{ {
spdlog::drop_all(); spdlog::drop_all();
auto logger = std::make_shared<spdlog::logger>(tested_logger_name, std::make_shared<spdlog::sinks::null_sink_st>()); auto logger = std::make_shared<spdlog::logger>(tested_logger_name, std::make_shared<spdlog::sinks::null_sink_st>());
@ -45,8 +43,7 @@ TEST_CASE("apply_all"
REQUIRE(counter == 1); REQUIRE(counter == 1);
} }
TEST_CASE("drop" TEST_CASE("drop", "[registry]")
"[registry]")
{ {
spdlog::drop_all(); spdlog::drop_all();
spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name); spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name);
@ -54,8 +51,15 @@ TEST_CASE("drop"
REQUIRE_FALSE(spdlog::get(tested_logger_name)); REQUIRE_FALSE(spdlog::get(tested_logger_name));
} }
TEST_CASE("drop_all" TEST_CASE("drop-default", "[registry]")
"[registry]") {
spdlog::set_default_logger(spdlog::null_logger_st(tested_logger_name));
spdlog::drop(tested_logger_name);
REQUIRE_FALSE(spdlog::default_logger());
REQUIRE_FALSE(spdlog::get(tested_logger_name));
}
TEST_CASE("drop_all", "[registry]")
{ {
spdlog::drop_all(); spdlog::drop_all();
spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name); spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name);
@ -63,10 +67,10 @@ TEST_CASE("drop_all"
spdlog::drop_all(); spdlog::drop_all();
REQUIRE_FALSE(spdlog::get(tested_logger_name)); REQUIRE_FALSE(spdlog::get(tested_logger_name));
REQUIRE_FALSE(spdlog::get(tested_logger_name2)); REQUIRE_FALSE(spdlog::get(tested_logger_name2));
REQUIRE_FALSE(spdlog::default_logger());
} }
TEST_CASE("drop non existing" TEST_CASE("drop non existing", "[registry]")
"[registry]")
{ {
spdlog::drop_all(); spdlog::drop_all();
spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name); spdlog::create<spdlog::sinks::null_sink_mt>(tested_logger_name);
@ -75,3 +79,36 @@ TEST_CASE("drop non existing"
REQUIRE(spdlog::get(tested_logger_name)); REQUIRE(spdlog::get(tested_logger_name));
spdlog::drop_all(); spdlog::drop_all();
} }
TEST_CASE("default logger", "[registry]")
{
spdlog::drop_all();
spdlog::set_default_logger(spdlog::null_logger_st(tested_logger_name));
REQUIRE(spdlog::get(tested_logger_name) == spdlog::default_logger());
spdlog::drop_all();
}
TEST_CASE("set_default_logger(nullptr)", "[registry]")
{
spdlog::set_default_logger(nullptr);
REQUIRE_FALSE(spdlog::default_logger());
}
TEST_CASE("disable automatic registration", "[registry]")
{
// set some global parameters
spdlog::level::level_enum log_level = spdlog::level::level_enum::warn;
spdlog::set_level(log_level);
// but disable automatic registration
spdlog::set_automatic_registration(false);
auto logger1 = spdlog::create<spdlog::sinks::daily_file_sink_st>(tested_logger_name, "filename", 11, 59);
auto logger2 = spdlog::create_async<spdlog::sinks::stdout_color_sink_mt>(tested_logger_name2);
// loggers should not be part of the registry
REQUIRE_FALSE(spdlog::get(tested_logger_name));
REQUIRE_FALSE(spdlog::get(tested_logger_name2));
// but make sure they are still initialized according to global defaults
REQUIRE(logger1->level() == log_level);
REQUIRE(logger2->level() == log_level);
spdlog::set_level(spdlog::level::info);
spdlog::set_automatic_registration(true);
}

View File

@ -8,8 +8,15 @@ void prepare_logdir()
system("del /F /Q logs\\*"); system("del /F /Q logs\\*");
#else #else
auto rv = system("mkdir -p logs"); auto rv = system("mkdir -p logs");
if (rv != 0)
{
throw std::runtime_error("Failed to mkdir -p logs");
}
rv = system("rm -f logs/*"); rv = system("rm -f logs/*");
(void)rv; if (rv != 0)
{
throw std::runtime_error("Failed to rm -f logs/*");
}
#endif #endif
} }
@ -17,7 +24,9 @@ std::string file_contents(const std::string &filename)
{ {
std::ifstream ifs(filename); std::ifstream ifs(filename);
if (!ifs) if (!ifs)
{
throw std::runtime_error("Failed open file "); throw std::runtime_error("Failed open file ");
}
return std::string((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>())); return std::string((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
} }
@ -25,7 +34,9 @@ std::size_t count_lines(const std::string &filename)
{ {
std::ifstream ifs(filename); std::ifstream ifs(filename);
if (!ifs) if (!ifs)
{
throw std::runtime_error("Failed open file "); throw std::runtime_error("Failed open file ");
}
std::string line; std::string line;
size_t counter = 0; size_t counter = 0;
@ -38,7 +49,9 @@ 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 "); throw std::runtime_error("Failed open file ");
}
return static_cast<std::size_t>(ifs.tellg()); return static_cast<std::size_t>(ifs.tellg());
} }
@ -47,6 +60,8 @@ std::size_t get_filesize(const std::string &filename)
bool ends_with(std::string const &value, std::string const &ending) bool ends_with(std::string const &value, std::string const &ending)
{ {
if (ending.size() > value.size()) if (ending.size() > value.size())
{
return false; return false;
}
return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
} }