diff --git a/README.md b/README.md index 37eb997d..8e85b9d6 100644 --- a/README.md +++ b/README.md @@ -305,7 +305,7 @@ void syslog_example() --- #### Android example ```c++ -#incude "spdlog/sinks/android_sink.h" +#include "spdlog/sinks/android_sink.h" void android_example() { std::string tag = "spdlog-android"; diff --git a/appveyor.yml b/appveyor.yml index 2a176c1b..d7843cde 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,7 +26,7 @@ build_script: 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% test: off diff --git a/bench/CMakeLists.txt b/bench/CMakeLists.txt index 5e492c0b..1a3856ac 100644 --- a/bench/CMakeLists.txt +++ b/bench/CMakeLists.txt @@ -38,6 +38,12 @@ add_executable(async_bench async_bench.cpp) target_link_libraries(async_bench spdlog::spdlog Threads::Threads) add_executable(latency latency.cpp) +set(CMAKE_CXX_STANDARD_LIBRARIES -lbenchmark) target_link_libraries(latency spdlog::spdlog Threads::Threads) + +add_executable(padder_bench padder_bench.cpp) +set(CMAKE_CXX_STANDARD_LIBRARIES -lbenchmark) +target_link_libraries(padder_bench spdlog::spdlog Threads::Threads) + file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/logs") diff --git a/bench/Makefile b/bench/Makefile index ae23d3aa..181f4664 100644 --- a/bench/Makefile +++ b/bench/Makefile @@ -1,27 +1,31 @@ CXX ?= g++ -CXXFLAGS = -march=native -Wall -Wextra -pedantic -std=c++11 -pthread -I../include -fmax-errors=1 -CXX_RELEASE_FLAGS = -Ofast -flto -Wl,--no-as-needed +CXXFLAGS = -march=native -Wall -Wextra -pedantic -Wconversion -std=c++11 -pthread -I../include -fmax-errors=1 +CXX_RELEASE_FLAGS = -O3 -flto -Wl,--no-as-needed -binaries=bench latency async_bench +binaries=bench.cpp async_bench latency padder_bench all: $(binaries) bench: bench.cpp $(CXX) bench.cpp -o bench $(CXXFLAGS) $(CXX_RELEASE_FLAGS) - + async_bench: async_bench.cpp $(CXX) async_bench.cpp -o async_bench $(CXXFLAGS) $(CXX_RELEASE_FLAGS) - - + + latency: latency.cpp - $(CXX) latency.cpp -o latency $(CXXFLAGS) $(CXX_RELEASE_FLAGS) - + $(CXX) latency.cpp -o latency $(CXXFLAGS) $(CXX_RELEASE_FLAGS) -lbenchmark + + +padder_bench: padder_bench.cpp + $(CXX) padder_bench.cpp -o padder_bench $(CXXFLAGS) $(CXX_RELEASE_FLAGS) -lbenchmark + .PHONY: clean clean: - rm -f *.o logs/* $(binaries) + rm -f *.o logs/* latecy_logs $(binaries) rebuild: clean all diff --git a/bench/async_bench.cpp b/bench/async_bench.cpp index 5dd18bfa..a708797a 100644 --- a/bench/async_bench.cpp +++ b/bench/async_bench.cpp @@ -94,6 +94,7 @@ int main(int argc, char *argv[]) spdlog::info("Line count OK ({:n})\n", count); } } + spdlog::shutdown(); } catch (std::exception &ex) { @@ -101,6 +102,7 @@ int main(int argc, char *argv[]) perror("Last error"); return 1; } + return 0; } diff --git a/bench/bench.cpp b/bench/bench.cpp index 0386be80..756b04a2 100644 --- a/bench/bench.cpp +++ b/bench/bench.cpp @@ -29,6 +29,7 @@ using namespace utils; void bench(int howmany, std::shared_ptr log); void bench_mt(int howmany, std::shared_ptr log, int thread_count); void bench_default_api(int howmany, std::shared_ptr log); +void bench_c_string(int howmany, std::shared_ptr log); int main(int argc, char *argv[]) { @@ -81,6 +82,21 @@ int main(int argc, char *argv[]) bench_default_api(howmany, spdlog::create("null_st")); + spdlog::info("**************************************************************"); + spdlog::info("C-string (500 bytes). Single thread, {:n} iterations", howmany); + spdlog::info("**************************************************************"); + + 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_st")); + spdlog::info("**************************************************************"); spdlog::info("{:n} threads sharing same logger, {:n} iterations", threads, howmany); spdlog::info("**************************************************************"); @@ -173,3 +189,26 @@ void bench_default_api(int howmany, std::shared_ptr log) 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 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>(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)); +} diff --git a/bench/latency.cpp b/bench/latency.cpp index a696276a..25b99f5c 100644 --- a/bench/latency.cpp +++ b/bench/latency.cpp @@ -7,6 +7,8 @@ // latency.cpp : spdlog latency benchmarks // +#include "benchmark/benchmark.h" + #include "spdlog/spdlog.h" #include "spdlog/async.h" #include "spdlog/sinks/basic_file_sink.h" @@ -14,140 +16,129 @@ #include "spdlog/sinks/null_sink.h" #include "spdlog/sinks/rotating_file_sink.h" -#include "utils.h" -#include -#include -#include -#include -#include -#include - -using namespace std; -using namespace std::chrono; -using namespace spdlog; -using namespace spdlog::sinks; -using namespace utils; - -void bench(int howmany, std::shared_ptr log); -void bench_mt(int howmany, std::shared_ptr log, int thread_count); - -int main(int, char *[]) +void prepare_logdir() { - std::srand(static_cast(std::time(nullptr))); // use current time as seed for random generator - int howmany = 1000000; - int queue_size = howmany + 2; - int threads = 10; - size_t file_size = 30 * 1024 * 1024; - size_t rotating_files = 5; - - try + spdlog::info("Preparing latency_logs directory.."); +#ifdef _WIN32 + system("if not exist logs mkdir latency_logs"); + system("del /F /Q logs\\*"); +#else + auto rv = system("mkdir -p latency_logs"); + if (rv != 0) { - - cout << "******************************************************************" - "*************\n"; - cout << "Single thread\n"; - cout << "******************************************************************" - "*************\n"; - - auto basic_st = spdlog::basic_logger_mt("basic_st", "logs/basic_st.log", true); - bench(howmany, basic_st); - - auto rotating_st = spdlog::rotating_logger_st("rotating_st", "logs/rotating_st.log", file_size, rotating_files); - bench(howmany, rotating_st); - - auto daily_st = spdlog::daily_logger_st("daily_st", "logs/daily_st.log"); - bench(howmany, daily_st); - - bench(howmany, spdlog::create("null_st")); - - cout << "\n****************************************************************" - "***************\n"; - cout << threads << " threads sharing same logger\n"; - cout << "******************************************************************" - "*************\n"; - - auto basic_mt = spdlog::basic_logger_mt("basic_mt", "logs/basic_mt.log", true); - bench_mt(howmany, basic_mt, threads); - - auto rotating_mt = spdlog::rotating_logger_mt("rotating_mt", "logs/rotating_mt.log", file_size, rotating_files); - bench_mt(howmany, rotating_mt, threads); - - auto daily_mt = spdlog::daily_logger_mt("daily_mt", "logs/daily_mt.log"); - bench_mt(howmany, daily_mt, threads); - bench(howmany, spdlog::create("null_mt")); - - cout << "\n****************************************************************" - "***************\n"; - cout << "async logging.. " << threads << " threads sharing same logger\n"; - cout << "******************************************************************" - "*************\n"; - - for (int i = 0; i < 3; ++i) - { - spdlog::init_thread_pool(static_cast(queue_size), 1); - auto as = spdlog::basic_logger_mt("async", "logs/basic_async.log", true); - bench_mt(howmany, as, threads); - spdlog::drop("async"); - } + throw std::runtime_error("Failed to mkdir -p latency_logs"); } - catch (std::exception &ex) + rv = system("rm -f latency_logs/*"); + if (rv != 0) { - std::cerr << "Error: " << ex.what() << std::endl; - perror("Last error"); - return EXIT_FAILURE; + throw std::runtime_error("Failed to rm -f latency_logs/*"); } - return EXIT_SUCCESS; +#endif } -void bench(int howmany, std::shared_ptr log) +void bench_c_string(benchmark::State &state, std::shared_ptr logger) { - 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) + 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) { - auto start = high_resolution_clock::now(); - log->info("Hello logger: msg number {}", i); - auto delta_nanos = chrono::duration_cast(high_resolution_clock::now() - start); - total_nanos += delta_nanos; + logger->info(msg); } - - auto avg = total_nanos.count() / howmany; - cout << format(avg) << " ns/call" << endl; } -void bench_mt(int howmany, std::shared_ptr log, int thread_count) +void bench_logger(benchmark::State &state, std::shared_ptr logger) { - using namespace std::chrono; - using chrono::high_resolution_clock; - using chrono::milliseconds; - using chrono::nanoseconds; - - cout << log->name() << "...\t\t" << flush; - vector threads; - std::atomic total_nanos{0}; - for (int t = 0; t < thread_count; ++t) + int i = 0; + for (auto _ : state) { - 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(high_resolution_clock::now() - start); - total_nanos += delta_nanos.count(); - } - })); + logger->info("Hello logger: msg number {}...............", ++i); } +} - for (auto &t : threads) +void bench_disabled_macro(benchmark::State &state, std::shared_ptr logger) +{ + int i = 0; + benchmark::DoNotOptimize(i); // prevent unused warnings + benchmark::DoNotOptimize(logger); // prevent unused warnings + for (auto _ : state) { - t.join(); - }; + SPDLOG_LOGGER_DEBUG(logger, "Hello logger: msg number {}...............", i++); + SPDLOG_DEBUG("Hello logger: msg number {}...............", i++); + } +} + +int main(int argc, char *argv[]) +{ - auto avg = total_nanos / howmany; - cout << format(avg) << " ns/call" << endl; + spdlog::set_pattern("[tid %t] %v"); + 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 rotating_files = 5; + int n_threads = benchmark::CPUInfo::Get().num_cpus; + + prepare_logdir(); + + // disabled loggers + auto disabled_logger = std::make_shared("bench", std::make_shared()); + disabled_logger->set_level(spdlog::level::off); + benchmark::RegisterBenchmark("disabled-at-compile-time", bench_disabled_macro, disabled_logger); + benchmark::RegisterBenchmark("disabled-at-runtime", bench_logger, disabled_logger); + + auto null_logger_st = std::make_shared("bench", std::make_shared()); + 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); + + // basic_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"); + + // rotating 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"); + + // 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"); + + // // + // // Multi threaded bench, 10 loggers using same logger concurrently + // // + auto null_logger_mt = std::make_shared("bench", std::make_shared()); + benchmark::RegisterBenchmark("null_sink_mt", bench_logger, null_logger_mt)->Threads(n_threads)->UseRealTime(); + + // basic_mt + 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"); + + // rotating mt + 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"); + + // daily mt + auto daily_mt = spdlog::daily_logger_mt("daily_mt", "latency_logs/daily_mt.log"); + benchmark::RegisterBenchmark("daily_mt", bench_logger, std::move(daily_mt))->Threads(n_threads)->UseRealTime(); + spdlog::drop("daily_mt"); + + // async + auto queue_size = 1024 * 1024 * 3; + auto tp = std::make_shared(queue_size, 1); + auto async_logger = std::make_shared( + "async_logger", std::make_shared(), std::move(tp), spdlog::async_overflow_policy::overrun_oldest); + benchmark::RegisterBenchmark("async_logger", bench_logger, async_logger)->Threads(n_threads)->UseRealTime(); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); } diff --git a/bench/padder_bench.cpp b/bench/padder_bench.cpp new file mode 100644 index 00000000..cbb0e67f --- /dev/null +++ b/bench/padder_bench.cpp @@ -0,0 +1,51 @@ +// +// 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); + } + // if(dest.size() != (padinfo.width_-wrapped_size)) + // { + // printf("NOT GOOD wrapped_size=%zu\t padinfo.width= %zu\tdest = %zu\n", wrapped_size, padinfo.width_, dest.size()); + // } + dest.clear(); + } +} + +int main(int argc, char *argv[]) +{ + using spdlog::details::padding_info; + std::vector sizes = {0, 2, 4, 8, 16, 32, 64, 128}; + + for (auto size : sizes) + { + size_t wrapped_size = 8; + size_t padding_size = wrapped_size + size; + + std::string title = "scoped_pad::left::" + std::to_string(size); + + benchmark::RegisterBenchmark(title.c_str(), bench_scoped_pad, wrapped_size, padding_info(padding_size, padding_info::left)); + + title = "scoped_pad::right::" + std::to_string(size); + benchmark::RegisterBenchmark(title.c_str(), bench_scoped_pad, wrapped_size, padding_info(padding_size, padding_info::right)); + + title = "scoped_pad::center::" + std::to_string(size); + benchmark::RegisterBenchmark(title.c_str(), bench_scoped_pad, wrapped_size, padding_info(padding_size, padding_info::center)); + } + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); +} diff --git a/example/example.cpp b/example/example.cpp index 15ca856e..8a103fc3 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -22,8 +22,6 @@ void err_handler_example(); void syslog_example(); void clone_example(); -#define SPDLOG_TRACE_ON - #include "spdlog/spdlog.h" int main(int, char *[]) @@ -146,7 +144,11 @@ void async_example() #include "spdlog/fmt/bin_to_hex.h" void binary_example() { - std::array buf; + std::vector buf; + for (int i = 0; i < 80; i++) + { + buf.push_back(static_cast(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: @@ -156,12 +158,18 @@ void binary_example() } // Compile time log levels. -// Must define SPDLOG_DEBUG_ON or SPDLOG_TRACE_ON before including spdlog.h to turn them on. +// define SPDLOG_ACTIVE_LEVEL to required level (e.g. SPDLOG_LEVEL_TRACE) void trace_example() { + + // trace from default logger + SPDLOG_TRACE("Enabled only #ifdef SPDLOG_TRACE_ON..{} ,{}", 1, 3.23); + // debug from default logger + SPDLOG_DEBUG("Enabled only #ifdef SPDLOG_DEBUG_ON.. {} ,{}", 1, 3.23); + + // trace from logger object auto logger = spdlog::get("file_logger"); - SPDLOG_TRACE(logger, "Enabled only #ifdef SPDLOG_TRACE_ON..{} ,{}", 1, 3.23); - SPDLOG_DEBUG(logger, "Enabled only #ifdef SPDLOG_DEBUG_ON.. {} ,{}", 1, 3.23); + SPDLOG_LOGGER_TRACE(logger, "another trace message"); } // A logger with multiple sinks (stdout and file) - each with a different format and log level. diff --git a/example/example.vcxproj b/example/example.vcxproj index 0cedd620..c752a8e9 100644 --- a/example/example.vcxproj +++ b/example/example.vcxproj @@ -25,7 +25,6 @@ {9E5AB93A-0CCE-4BAC-9FCB-0FC9CB5EB8D2} Win32Proj . - 10.0.16299.0 diff --git a/include/spdlog/async.h b/include/spdlog/async.h index 9264a4e3..971becd7 100644 --- a/include/spdlog/async.h +++ b/include/spdlog/async.h @@ -52,7 +52,7 @@ struct async_factory_impl auto sink = std::make_shared(std::forward(args)...); auto new_logger = std::make_shared(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; } }; diff --git a/include/spdlog/common.h b/include/spdlog/common.h index b6147346..0ce54a9b 100644 --- a/include/spdlog/common.h +++ b/include/spdlog/common.h @@ -56,23 +56,42 @@ using sink_ptr = std::shared_ptr; using sinks_init_list = std::initializer_list; using log_err_handler = std::function; +// 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) using level_t = details::null_atomic_int; #else using level_t = std::atomic; #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 namespace level { enum level_enum { - trace = 0, - debug = 1, - info = 2, - warn = 3, - err = 4, - critical = 5, - off = 6 + trace = SPDLOG_LEVEL_TRACE, + debug = SPDLOG_LEVEL_DEBUG, + info = SPDLOG_LEVEL_INFO, + warn = SPDLOG_LEVEL_WARN, + err = SPDLOG_LEVEL_ERROR, + critical = SPDLOG_LEVEL_CRITICAL, + off = SPDLOG_LEVEL_OFF, }; #if !defined(SPDLOG_LEVEL_NAMES) @@ -159,16 +178,6 @@ using filename_t = std::wstring; using filename_t = std::string; #endif -#define SPDLOG_CATCH_AND_HANDLE \ - catch (const std::exception &ex) \ - { \ - err_handler_(ex.what()); \ - } \ - catch (...) \ - { \ - err_handler_("Unknown exeption in logger"); \ - } - namespace details { // make_unique support for pre c++14 diff --git a/include/spdlog/details/async_logger_impl.h b/include/spdlog/details/async_logger_impl.h index 2841ab2b..aafcae65 100644 --- a/include/spdlog/details/async_logger_impl.h +++ b/include/spdlog/details/async_logger_impl.h @@ -43,7 +43,7 @@ inline void spdlog::async_logger::sink_it_(details::log_msg &msg) #endif 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 { diff --git a/include/spdlog/details/fmt_helper.h b/include/spdlog/details/fmt_helper.h index 1518b2c0..1f716ac5 100644 --- a/include/spdlog/details/fmt_helper.h +++ b/include/spdlog/details/fmt_helper.h @@ -13,19 +13,10 @@ namespace details { namespace fmt_helper { template -inline void append_str(const std::string &str, fmt::basic_memory_buffer &dest) +inline spdlog::string_view_t to_string_view(const fmt::basic_memory_buffer &buf) SPDLOG_NOEXCEPT { - auto *str_ptr = str.data(); - dest.append(str_ptr, str_ptr + str.size()); + return spdlog::string_view_t(buf.data(), buf.size()); } - -template -inline void append_c_str(const char *c_str, fmt::basic_memory_buffer &dest) -{ - auto len = std::char_traits::length(c_str); - dest.append(c_str, c_str + len); -} - template inline void append_buf(const fmt::basic_memory_buffer &buf, fmt::basic_memory_buffer &dest) { @@ -33,6 +24,16 @@ inline void append_buf(const fmt::basic_memory_buffer &buf, dest.append(buf_ptr, buf_ptr + buf.size()); } +template +inline void append_string_view(spdlog::string_view_t view, fmt::basic_memory_buffer &dest) +{ + auto *buf_ptr = view.data(); + if (buf_ptr != nullptr) + { + dest.append(buf_ptr, buf_ptr + view.size()); + } +} + template inline void append_int(T n, fmt::basic_memory_buffer &dest) { diff --git a/include/spdlog/details/log_msg.h b/include/spdlog/details/log_msg.h index 49987515..6244f3a9 100644 --- a/include/spdlog/details/log_msg.h +++ b/include/spdlog/details/log_msg.h @@ -15,9 +15,8 @@ namespace spdlog { namespace details { struct log_msg { - log_msg() = default; - log_msg(const std::string *loggers_name, level::level_enum lvl) + log_msg(const std::string *loggers_name, level::level_enum lvl, string_view_t view) : logger_name(loggers_name) , level(lvl) #ifndef SPDLOG_NO_DATETIME @@ -26,24 +25,25 @@ struct log_msg #ifndef SPDLOG_NO_THREAD_ID , thread_id(os::thread_id()) + , payload(view) #endif { } - log_msg(const log_msg &other) = delete; - log_msg(log_msg &&other) = delete; - log_msg &operator=(log_msg &&other) = delete; + log_msg(const log_msg &other) = default; + log_msg &operator=(const log_msg &other) = default; const std::string *logger_name{nullptr}; - level::level_enum level; + level::level_enum level{level::off}; log_clock::time_point time; - size_t thread_id; - fmt::memory_buffer raw; - size_t msg_id; + size_t thread_id{0}; + size_t msg_id{0}; - // info about wrapping the formatted text with color (updated by pattern_formatter). + // wrapping the formatted text with color (updated by pattern_formatter). mutable size_t color_range_start{0}; mutable size_t color_range_end{0}; + + const string_view_t payload; }; } // namespace details } // namespace spdlog diff --git a/include/spdlog/details/logger_impl.h b/include/spdlog/details/logger_impl.h index 46301ea1..efe6a66a 100644 --- a/include/spdlog/details/logger_impl.h +++ b/include/spdlog/details/logger_impl.h @@ -10,19 +10,23 @@ #include #include +#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 // all other ctors will call this one template inline spdlog::logger::logger(std::string logger_name, It begin, It end) : name_(std::move(logger_name)) , 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 @@ -63,30 +67,31 @@ inline void spdlog::logger::log(level::level_enum lvl, const char *fmt, const Ar try { - details::log_msg log_msg(&name_, lvl); - fmt::format_to(log_msg.raw, fmt, args...); + using details::fmt_helper::to_string_view; + fmt::memory_buffer buf; + fmt::format_to(buf, fmt, args...); + details::log_msg log_msg(&name_, lvl, to_string_view(buf)); sink_it_(log_msg); } SPDLOG_CATCH_AND_HANDLE } -template inline void spdlog::logger::log(level::level_enum lvl, const char *msg) { if (!should_log(lvl)) { return; } + try { - details::log_msg log_msg(&name_, lvl); - details::fmt_helper::append_c_str(msg, log_msg.raw); + details::log_msg log_msg(&name_, lvl, spdlog::string_view_t(msg)); sink_it_(log_msg); } SPDLOG_CATCH_AND_HANDLE } -template +template::value, T>::type *> inline void spdlog::logger::log(level::level_enum lvl, const T &msg) { if (!should_log(lvl)) @@ -95,8 +100,25 @@ inline void spdlog::logger::log(level::level_enum lvl, const T &msg) } try { - details::log_msg log_msg(&name_, lvl); - fmt::format_to(log_msg.raw, "{}", msg); + details::log_msg log_msg(&name_, lvl, msg); + sink_it_(log_msg); + } + SPDLOG_CATCH_AND_HANDLE +} + +template::value, T>::type *> +inline void spdlog::logger::log(level::level_enum lvl, const T &msg) +{ + if (!should_log(lvl)) + { + return; + } + try + { + using details::fmt_helper::to_string_view; + fmt::memory_buffer buf; + fmt::format_to(buf, "{}", msg); + details::log_msg log_msg(&name_, lvl, to_string_view(buf)); sink_it_(log_msg); } SPDLOG_CATCH_AND_HANDLE @@ -208,10 +230,12 @@ inline void spdlog::logger::log(level::level_enum lvl, const wchar_t *fmt, const try { // format to wmemory_buffer and convert to utf8 - details::log_msg log_msg(&name_, lvl); + using details::fmt_helper::to_string_view; fmt::wmemory_buffer wbuf; fmt::format_to(wbuf, fmt, args...); - wbuf_to_utf8buf(wbuf, log_msg.raw); + fmt::memory_buffer buf; + wbuf_to_utf8buf(wbuf, buf); + details::log_msg log_msg(&name_, lvl, to_string_view(buf)); sink_it_(log_msg); } SPDLOG_CATCH_AND_HANDLE @@ -273,7 +297,7 @@ inline void spdlog::logger::set_error_handler(spdlog::log_err_handler err_handle 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_; } @@ -303,6 +327,11 @@ inline bool spdlog::logger::should_flush_(const details::log_msg &msg) return (msg.level >= flush_level) && (msg.level != level::off); } +inline spdlog::level::level_enum spdlog::logger::default_level() +{ + return static_cast(SPDLOG_ACTIVE_LEVEL); +} + inline spdlog::level::level_enum spdlog::logger::level() const { return static_cast(level_.load(std::memory_order_relaxed)); diff --git a/include/spdlog/details/pattern_formatter.h b/include/spdlog/details/pattern_formatter.h index f5a8406e..9d018fb8 100644 --- a/include/spdlog/details/pattern_formatter.h +++ b/include/spdlog/details/pattern_formatter.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -24,39 +25,148 @@ namespace spdlog { namespace details { +// padding information. +struct padding_info +{ + enum pad_side + { + left, + right, + center + }; + + padding_info() = default; + padding_info(size_t width, padding_info::pad_side side) + : width_(width) + , side_(side) + { + } + const size_t width_ = 0; + const pad_side side_ = left; +}; + +class scoped_pad +{ +public: + scoped_pad(size_t wrapped_size, padding_info &padinfo, fmt::memory_buffer &dest) + : padinfo_(padinfo) + , dest_(dest) + { + + if (padinfo_.width_ <= wrapped_size) + { + total_pad_ = 0; + return; + } + + total_pad_ = padinfo.width_ - wrapped_size; + if (padinfo_.side_ == padding_info::left) + { + pad_it(total_pad_); + total_pad_ = 0; + } + else if (padinfo_.side_ == padding_info::center) + { + auto half_pad = total_pad_ / 2; + auto reminder = total_pad_ & 1; + pad_it(half_pad); + total_pad_ = half_pad + reminder; // for the right side + } + } + + scoped_pad(spdlog::string_view_t txt, padding_info &padinfo, fmt::memory_buffer &dest) + : scoped_pad(txt.size(), padinfo, dest) + { + } + + ~scoped_pad() + { + if (total_pad_) + { + pad_it(total_pad_); + } + } + +private: + void pad_it(size_t count) + { + // count = std::min(count, spaces_.size()); + assert(count <= spaces_.size()); + fmt_helper::append_string_view(string_view_t(spaces_.data(), count), dest_); + } + + const padding_info &padinfo_; + fmt::memory_buffer &dest_; + size_t total_pad_; + string_view_t spaces_{" " + " ", + 128}; +}; + class flag_formatter { public: + explicit flag_formatter(padding_info padinfo) + : padinfo_(padinfo) + { + } + flag_formatter() = default; virtual ~flag_formatter() = default; virtual void format(const details::log_msg &msg, const std::tm &tm_time, fmt::memory_buffer &dest) = 0; + +protected: + padding_info padinfo_; }; /////////////////////////////////////////////////////////////////////// -// name & level pattern appenders +// name & level pattern appender /////////////////////////////////////////////////////////////////////// class name_formatter : public flag_formatter { +public: + explicit name_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override { - fmt_helper::append_str(*msg.logger_name, dest); + scoped_pad p(*msg.logger_name, padinfo_, dest); + fmt_helper::append_string_view(*msg.logger_name, dest); } }; // log level appender class level_formatter : public flag_formatter { +public: + explicit level_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override { - fmt_helper::append_c_str(level::to_c_str(msg.level), dest); + string_view_t level_name{level::to_c_str(msg.level)}; + scoped_pad p(level_name, padinfo_, dest); + fmt_helper::append_string_view(level_name, dest); } }; // short log level appender class short_level_formatter : public flag_formatter { +public: + explicit short_level_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override { - fmt_helper::append_c_str(level::to_short_c_str(msg.level), dest); + string_view_t level_name{level::to_short_c_str(msg.level)}; + scoped_pad p(level_name, padinfo_, dest); + fmt_helper::append_string_view(level_name, dest); } }; @@ -78,9 +188,17 @@ static int to12h(const tm &t) static const char *days[]{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; class a_formatter : public flag_formatter { +public: + explicit a_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { - fmt_helper::append_c_str(days[tm_time.tm_wday], dest); + string_view_t field_value{days[tm_time.tm_wday]}; + scoped_pad p(field_value, padinfo_, dest); + fmt_helper::append_string_view(field_value, dest); } }; @@ -88,9 +206,17 @@ class a_formatter : public flag_formatter static const char *full_days[]{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; class A_formatter : public flag_formatter { +public: + explicit A_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { - fmt_helper::append_c_str(full_days[tm_time.tm_wday], dest); + string_view_t field_value{full_days[tm_time.tm_wday]}; + scoped_pad p(field_value, padinfo_, dest); + fmt_helper::append_string_view(field_value, dest); } }; @@ -98,9 +224,17 @@ class A_formatter : public flag_formatter static const char *months[]{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"}; class b_formatter : public flag_formatter { +public: + explicit b_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { - fmt_helper::append_c_str(months[tm_time.tm_mon], dest); + string_view_t field_value{months[tm_time.tm_mon]}; + scoped_pad p(field_value, padinfo_, dest); + fmt_helper::append_string_view(field_value, dest); } }; @@ -109,23 +243,37 @@ static const char *full_months[]{ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; class B_formatter : public flag_formatter { +public: + explicit B_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { - fmt_helper::append_c_str(full_months[tm_time.tm_mon], dest); + string_view_t field_value{full_months[tm_time.tm_mon]}; + scoped_pad p(field_value, padinfo_, dest); + fmt_helper::append_string_view(field_value, dest); } }; // Date and time representation (Thu Aug 23 15:35:46 2014) class c_formatter final : public flag_formatter { +public: + explicit c_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { - // fmt::format_to(dest, "{} {} {} ", days[tm_time.tm_wday], - // months[tm_time.tm_mon], tm_time.tm_mday); - // date - fmt_helper::append_c_str(days[tm_time.tm_wday], dest); + const size_t field_size = 24; + scoped_pad p(field_size, padinfo_, dest); + + fmt_helper::append_string_view(days[tm_time.tm_wday], dest); dest.push_back(' '); - fmt_helper::append_c_str(months[tm_time.tm_mon], dest); + fmt_helper::append_string_view(months[tm_time.tm_mon], dest); dest.push_back(' '); fmt_helper::append_int(tm_time.tm_mday, dest); dest.push_back(' '); @@ -144,8 +292,16 @@ class c_formatter final : public flag_formatter // year - 2 digit class C_formatter final : public flag_formatter { +public: + explicit C_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { + const size_t field_size = 2; + scoped_pad p(field_size, padinfo_, dest); fmt_helper::pad2(tm_time.tm_year % 100, dest); } }; @@ -153,8 +309,17 @@ class C_formatter final : public flag_formatter // Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01 class D_formatter final : public flag_formatter { +public: + explicit D_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { + const size_t field_size = 10; + scoped_pad p(field_size, padinfo_, dest); + fmt_helper::pad2(tm_time.tm_mon + 1, dest); dest.push_back('/'); fmt_helper::pad2(tm_time.tm_mday, dest); @@ -166,8 +331,14 @@ class D_formatter final : public flag_formatter // year - 4 digit class Y_formatter final : public flag_formatter { +public: + explicit Y_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { + const size_t field_size = 4; + scoped_pad p(field_size, padinfo_, dest); fmt_helper::append_int(tm_time.tm_year + 1900, dest); } }; @@ -175,8 +346,16 @@ class Y_formatter final : public flag_formatter // month 1-12 class m_formatter final : public flag_formatter { +public: + explicit m_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { + const size_t field_size = 2; + scoped_pad p(field_size, padinfo_, dest); fmt_helper::pad2(tm_time.tm_mon + 1, dest); } }; @@ -184,8 +363,16 @@ class m_formatter final : public flag_formatter // day of month 1-31 class d_formatter final : public flag_formatter { +public: + explicit d_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { + const size_t field_size = 2; + scoped_pad p(field_size, padinfo_, dest); fmt_helper::pad2(tm_time.tm_mday, dest); } }; @@ -193,8 +380,16 @@ class d_formatter final : public flag_formatter // hours in 24 format 0-23 class H_formatter final : public flag_formatter { +public: + explicit H_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { + const size_t field_size = 2; + scoped_pad p(field_size, padinfo_, dest); fmt_helper::pad2(tm_time.tm_hour, dest); } }; @@ -202,8 +397,14 @@ class H_formatter final : public flag_formatter // hours in 12 format 1-12 class I_formatter final : public flag_formatter { +public: + explicit I_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { + const size_t field_size = 2; + scoped_pad p(field_size, padinfo_, dest); fmt_helper::pad2(to12h(tm_time), dest); } }; @@ -211,8 +412,14 @@ class I_formatter final : public flag_formatter // minutes 0-59 class M_formatter final : public flag_formatter { +public: + explicit M_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { + const size_t field_size = 2; + scoped_pad p(field_size, padinfo_, dest); fmt_helper::pad2(tm_time.tm_min, dest); } }; @@ -220,8 +427,14 @@ class M_formatter final : public flag_formatter // seconds 0-59 class S_formatter final : public flag_formatter { +public: + explicit S_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { + const size_t field_size = 2; + scoped_pad p(field_size, padinfo_, dest); fmt_helper::pad2(tm_time.tm_sec, dest); } }; @@ -229,8 +442,15 @@ class S_formatter final : public flag_formatter // milliseconds class e_formatter final : public flag_formatter { +public: + explicit e_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override { + const size_t field_size = 3; + scoped_pad p(field_size, padinfo_, dest); + auto millis = fmt_helper::time_fraction(msg.time); fmt_helper::pad3(static_cast(millis.count()), dest); } @@ -239,8 +459,15 @@ class e_formatter final : public flag_formatter // microseconds class f_formatter final : public flag_formatter { +public: + explicit f_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override { + const size_t field_size = 6; + scoped_pad p(field_size, padinfo_, dest); + auto micros = fmt_helper::time_fraction(msg.time); fmt_helper::pad6(static_cast(micros.count()), dest); } @@ -249,8 +476,15 @@ class f_formatter final : public flag_formatter // nanoseconds class F_formatter final : public flag_formatter { +public: + explicit F_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override { + const size_t field_size = 9; + scoped_pad p(field_size, padinfo_, dest); + auto ns = fmt_helper::time_fraction(msg.time); fmt::format_to(dest, "{:09}", ns.count()); } @@ -259,8 +493,15 @@ class F_formatter final : public flag_formatter // seconds since epoch class E_formatter final : public flag_formatter { +public: + explicit E_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override { + const size_t field_size = 10; + scoped_pad p(field_size, padinfo_, dest); + auto duration = msg.time.time_since_epoch(); auto seconds = std::chrono::duration_cast(duration).count(); fmt_helper::append_int(seconds, dest); @@ -270,32 +511,52 @@ class E_formatter final : public flag_formatter // AM/PM class p_formatter final : public flag_formatter { +public: + explicit p_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { - fmt_helper::append_c_str(ampm(tm_time), dest); + const size_t field_size = 2; + scoped_pad p(field_size, padinfo_, dest); + fmt_helper::append_string_view(ampm(tm_time), dest); } }; // 12 hour clock 02:55:02 pm class r_formatter final : public flag_formatter { +public: + explicit r_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { + const size_t field_size = 11; + scoped_pad p(field_size, padinfo_, dest); + fmt_helper::pad2(to12h(tm_time), dest); dest.push_back(':'); fmt_helper::pad2(tm_time.tm_min, dest); dest.push_back(':'); fmt_helper::pad2(tm_time.tm_sec, dest); dest.push_back(' '); - fmt_helper::append_c_str(ampm(tm_time), dest); + fmt_helper::append_string_view(ampm(tm_time), dest); } }; // 24-hour HH:MM time, equivalent to %H:%M class R_formatter final : public flag_formatter { +public: + explicit R_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { + const size_t field_size = 5; + scoped_pad p(field_size, padinfo_, dest); + fmt_helper::pad2(tm_time.tm_hour, dest); dest.push_back(':'); fmt_helper::pad2(tm_time.tm_min, dest); @@ -305,10 +566,16 @@ class R_formatter final : public flag_formatter // ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S class T_formatter final : public flag_formatter { +public: + explicit T_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override { - // fmt::format_to(dest, "{:02}:{:02}:{:02}", tm_time.tm_hour, - // tm_time.tm_min, tm_time.tm_sec); + + const size_t field_size = 8; + scoped_pad p(field_size, padinfo_, dest); + fmt_helper::pad2(tm_time.tm_hour, dest); dest.push_back(':'); fmt_helper::pad2(tm_time.tm_min, dest); @@ -321,6 +588,9 @@ class T_formatter final : public flag_formatter class z_formatter final : public flag_formatter { public: + explicit z_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + const std::chrono::seconds cache_refresh = std::chrono::seconds(5); z_formatter() = default; @@ -329,6 +599,9 @@ public: void format(const details::log_msg &msg, const std::tm &tm_time, fmt::memory_buffer &dest) override { + const size_t field_size = 6; + scoped_pad p(field_size, padinfo_, dest); + #ifdef _WIN32 int total_minutes = get_cached_offset(msg, tm_time); #else @@ -373,8 +646,14 @@ private: // Thread id class t_formatter final : public flag_formatter { +public: + explicit t_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override { + const size_t field_size = 6; + scoped_pad p(field_size, padinfo_, dest); fmt_helper::pad6(msg.thread_id, dest); } }; @@ -382,38 +661,59 @@ class t_formatter final : public flag_formatter // Current pid class pid_formatter final : public flag_formatter { +public: + explicit pid_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &, const std::tm &, fmt::memory_buffer &dest) override { - fmt_helper::append_int(details::os::pid(), dest); + const size_t field_size = 6; + scoped_pad p(field_size, padinfo_, dest); + fmt_helper::pad6(static_cast(details::os::pid()), dest); } }; // message counter formatter class i_formatter final : public flag_formatter { +public: + explicit i_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override { + const size_t field_size = 6; + scoped_pad p(field_size, padinfo_, dest); fmt_helper::pad6(msg.msg_id, dest); } }; class v_formatter final : public flag_formatter { +public: + explicit v_formatter(padding_info padinfo) + : flag_formatter(padinfo){}; + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override { - fmt_helper::append_buf(msg.raw, dest); + scoped_pad p(msg.payload, padinfo_, dest); + fmt_helper::append_string_view(msg.payload, dest); } }; class ch_formatter final : public flag_formatter { +public: public: explicit ch_formatter(char ch) : ch_(ch) { } + void format(const details::log_msg &, const std::tm &, fmt::memory_buffer &dest) override { + const size_t field_size = 1; + scoped_pad p(field_size, padinfo_, dest); dest.push_back(ch_); } @@ -433,7 +733,7 @@ public: } void format(const details::log_msg &, const std::tm &, fmt::memory_buffer &dest) override { - fmt_helper::append_str(str_, dest); + fmt_helper::append_string_view(str_, dest); } private: @@ -443,6 +743,12 @@ private: // mark the color range. expect it to be in the form of "%^colored text%$" class color_start_formatter final : public flag_formatter { +public: + explicit color_start_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override { msg.color_range_start = dest.size(); @@ -450,6 +756,12 @@ class color_start_formatter final : public flag_formatter }; class color_stop_formatter final : public flag_formatter { +public: + explicit color_stop_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override { msg.color_range_end = dest.size(); @@ -460,6 +772,12 @@ class color_stop_formatter final : public flag_formatter // pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v class full_formatter final : public flag_formatter { +public: + explicit full_formatter(padding_info padinfo) + : flag_formatter(padinfo) + { + } + void format(const details::log_msg &msg, const std::tm &tm_time, fmt::memory_buffer &dest) override { using std::chrono::duration_cast; @@ -511,7 +829,8 @@ class full_formatter final : public flag_formatter if (!msg.logger_name->empty()) { dest.push_back('['); - fmt_helper::append_str(*msg.logger_name, dest); + // fmt_helper::append_str(*msg.logger_name, dest); + fmt_helper::append_string_view(*msg.logger_name, dest); dest.push_back(']'); dest.push_back(' '); } @@ -520,11 +839,13 @@ class full_formatter final : public flag_formatter dest.push_back('['); // wrap the level name with color msg.color_range_start = dest.size(); - fmt_helper::append_c_str(level::to_c_str(msg.level), dest); + // fmt_helper::append_string_view(level::to_c_str(msg.level), dest); + fmt_helper::append_string_view(level::to_c_str(msg.level), dest); msg.color_range_end = dest.size(); dest.push_back(']'); dest.push_back(' '); - fmt_helper::append_buf(msg.raw, dest); + // fmt_helper::append_string_view(msg.msg(), dest); + fmt_helper::append_string_view(msg.payload, dest); } private: @@ -548,6 +869,17 @@ public: compile_pattern_(pattern_); } + // use by default full formatter for if pattern is not given + explicit pattern_formatter(pattern_time_type time_type = pattern_time_type::local, std::string eol = spdlog::details::os::default_eol) + : pattern_("%+") + , eol_(std::move(eol)) + , pattern_time_type_(time_type) + , last_log_secs_(0) + { + std::memset(&cached_tm_, 0, sizeof(cached_tm_)); + formatters_.push_back(details::make_unique(details::padding_info{})); + } + pattern_formatter(const pattern_formatter &other) = delete; pattern_formatter &operator=(const pattern_formatter &other) = delete; @@ -571,7 +903,7 @@ public: f->format(msg, cached_tm_, dest); } // write eol - details::fmt_helper::append_c_str(eol_.c_str(), dest); + details::fmt_helper::append_string_view(eol_, dest); } private: @@ -592,149 +924,198 @@ private: return details::os::gmtime(log_clock::to_time_t(msg.time)); } - void handle_flag_(char flag) + void handle_flag_(char flag, details::padding_info padding) { switch (flag) { // logger name case 'n': - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case 'l': - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case 'L': - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('t'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('v'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('a'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('A'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('b'): case ('h'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('B'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('c'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('C'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('Y'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('D'): case ('x'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('m'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('d'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('H'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('I'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('M'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('S'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('e'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('f'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('F'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('E'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('p'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('r'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('R'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('T'): case ('X'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('z'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('+'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('P'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; #ifdef SPDLOG_ENABLE_MESSAGE_COUNTER case ('i'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; #endif case ('^'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; case ('$'): - formatters_.push_back(details::make_unique()); + formatters_.push_back(details::make_unique(padding)); break; - default: // Unknown flag appears as is + case ('%'): formatters_.push_back(details::make_unique('%')); - formatters_.push_back(details::make_unique(flag)); break; + + default: // Unknown flag appears as is + auto unknown_flag = details::make_unique(); + unknown_flag->add_ch('%'); + unknown_flag->add_ch(flag); + formatters_.push_back((std::move(unknown_flag))); + break; + } + } + + // Extract given pad spec (e.g. %8X) + // Advance the given it pass the end of the padding spec found (if any) + // Return padding. + details::padding_info handle_padspec_(std::string::const_iterator &it, std::string::const_iterator end) + { + using details::padding_info; + using details::scoped_pad; + const size_t max_width = 128; + if (it == end) + { + return padding_info{}; + } + + padding_info::pad_side side; + switch (*it) + { + case '-': + side = padding_info::right; + ++it; + break; + case '=': + side = padding_info::center; + ++it; + break; + default: + side = details::padding_info::left; + break; + } + + if (it == end || !std::isdigit(static_cast(*it))) + { + return padding_info{0, side}; } + + auto width = static_cast(*it - '0'); + for (++it; it != end && std::isdigit(static_cast(*it)); ++it) + { + auto digit = static_cast(*it - '0'); + width = width * 10 + digit; + } + return details::padding_info{std::min(width, max_width), side}; } void compile_pattern_(const std::string &pattern) @@ -750,9 +1131,12 @@ private: { formatters_.push_back(std::move(user_chars)); } - if (++it != end) + + auto padding = handle_padspec_(++it, end); + + if (it != end) { - handle_flag_(*it); + handle_flag_(*it, padding); } else { diff --git a/include/spdlog/details/periodic_worker.h b/include/spdlog/details/periodic_worker.h index 57e5fa77..fa6488d1 100644 --- a/include/spdlog/details/periodic_worker.h +++ b/include/spdlog/details/periodic_worker.h @@ -10,7 +10,7 @@ // // RAII over the owned thread: // 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 #include diff --git a/include/spdlog/details/registry.h b/include/spdlog/details/registry.h index 439395f7..ccd53955 100644 --- a/include/spdlog/details/registry.h +++ b/include/spdlog/details/registry.h @@ -42,18 +42,12 @@ public: void register_logger(std::shared_ptr new_logger) { std::lock_guard lock(logger_map_mutex_); - auto logger_name = new_logger->name(); - throw_if_exists_(logger_name); - loggers_[logger_name] = std::move(new_logger); + register_logger_(std::move(new_logger)); } - void register_and_init(std::shared_ptr new_logger) + void initialize_logger(std::shared_ptr new_logger) { std::lock_guard 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()); if (err_handler_) @@ -64,8 +58,10 @@ public: new_logger->set_level(level_); new_logger->flush_on(flush_level_); - // add to registry - loggers_[logger_name] = std::move(new_logger); + if (automatic_registration_) + { + register_logger_(std::move(new_logger)); + } } std::shared_ptr get(const std::string &logger_name) @@ -223,6 +219,12 @@ public: return tp_mutex_; } + void set_automatic_registration(bool automatic_regsistration) + { + std::lock_guard lock(logger_map_mutex_); + automatic_registration_ = automatic_regsistration; + } + static registry &instance() { static registry s_instance; @@ -231,7 +233,7 @@ public: private: registry() - : formatter_(new pattern_formatter("%+")) + : formatter_(new pattern_formatter()) { #ifndef SPDLOG_DISABLE_DEFAULT_LOGGER @@ -259,16 +261,24 @@ private: } } + void register_logger_(std::shared_ptr 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::recursive_mutex tp_mutex_; std::unordered_map> loggers_; std::unique_ptr formatter_; - level::level_enum level_ = level::info; + level::level_enum level_ = spdlog::logger::default_level(); level::level_enum flush_level_ = level::off; log_err_handler err_handler_; std::shared_ptr tp_; std::unique_ptr periodic_flusher_; std::shared_ptr default_logger_; + bool automatic_registration_ = true; }; } // namespace details diff --git a/include/spdlog/details/thread_pool.h b/include/spdlog/details/thread_pool.h index 41ca0e9c..da030a79 100644 --- a/include/spdlog/details/thread_pool.h +++ b/include/spdlog/details/thread_pool.h @@ -1,5 +1,6 @@ #pragma once +#include "spdlog/details/fmt_helper.h" #include "spdlog/details/log_msg.h" #include "spdlog/details/mpmc_blocking_q.h" #include "spdlog/details/os.h" @@ -69,7 +70,7 @@ struct async_msg #endif // 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) , level(m.level) , time(m.time) @@ -77,30 +78,34 @@ struct async_msg , msg_id(m.msg_id) , 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(std::move(worker), the_type, details::log_msg()) + : msg_type(the_type) + , level(level::off) + , time() + , thread_id(0) + , msg_id(0) + , worker_ptr(std::move(worker)) { } 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 - void to_log_msg(log_msg &msg) + log_msg to_log_msg() { - msg.logger_name = &worker_ptr->name(); - msg.level = level; + log_msg msg(&worker_ptr->name(), level, string_view_t(raw.data(), raw.size())); msg.time = time; msg.thread_id = thread_id; - fmt_helper::append_buf(raw, msg.raw); msg.msg_id = msg_id; msg.color_range_start = 0; msg.color_range_end = 0; + return msg; } }; @@ -158,9 +163,9 @@ public: 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) + void post_log(async_logger_ptr &&worker_ptr, details::log_msg &msg, async_overflow_policy overflow_policy) { - async_msg async_m(std::move(worker_ptr), async_msg_type::log, std::move(msg)); + async_msg async_m(std::move(worker_ptr), async_msg_type::log, msg); post_async_msg_(std::move(async_m), overflow_policy); } @@ -230,8 +235,7 @@ private: { case async_msg_type::log: { - log_msg msg; - incoming_async_msg.to_log_msg(msg); + auto msg = incoming_async_msg.to_log_msg(); incoming_async_msg.worker_ptr->backend_log_(msg); return true; } diff --git a/include/spdlog/logger.h b/include/spdlog/logger.h index 1bcf4bc2..d1da624f 100644 --- a/include/spdlog/logger.h +++ b/include/spdlog/logger.h @@ -46,7 +46,6 @@ public: template void log(level::level_enum lvl, const char *fmt, const Args &... args); - template void log(level::level_enum lvl, const char *msg); template @@ -94,7 +93,12 @@ public: #endif // _WIN32 #endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT - template + // T can be statically converted to string_view + template::value, T>::type * = nullptr> + void log(level::level_enum lvl, const T &); + + // T cannot be statically converted to string_view + template::value, T>::type * = nullptr> void log(level::level_enum lvl, const T &); template @@ -117,6 +121,8 @@ public: bool should_log(level::level_enum msg_level) const; void set_level(level::level_enum log_level); + + static level::level_enum default_level(); level::level_enum level() const; const std::string &name() const; @@ -136,7 +142,7 @@ public: // error 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. virtual std::shared_ptr clone(std::string logger_name); @@ -147,8 +153,8 @@ protected: bool should_flush_(const details::log_msg &msg); - // default error handler: print the error to stderr with the max rate of 1 - // message/minute + // default error handler. + // print the error to stderr with the max rate of 1 message/minute. void default_err_handler_(const std::string &msg); // increment the message count (only if defined(SPDLOG_ENABLE_MESSAGE_COUNTER)) @@ -156,11 +162,11 @@ protected: const std::string name_; std::vector sinks_; - spdlog::level_t level_; - spdlog::level_t flush_level_; - log_err_handler err_handler_; - std::atomic last_err_time_; - std::atomic msg_counter_; + spdlog::level_t level_{spdlog::logger::default_level()}; + spdlog::level_t flush_level_{level::off}; + log_err_handler err_handler_{[this](const std::string &msg) { this->default_err_handler_(msg); }}; + std::atomic last_err_time_{0}; + std::atomic msg_counter_{1}; }; } // namespace spdlog diff --git a/include/spdlog/sinks/android_sink.h b/include/spdlog/sinks/android_sink.h index 8c3383c1..0742b247 100644 --- a/include/spdlog/sinks/android_sink.h +++ b/include/spdlog/sinks/android_sink.h @@ -47,7 +47,7 @@ protected: fmt::memory_buffer formatted; if (use_raw_msg_) { - details::fmt_helper::append_buf(msg.raw, formatted); + details::fmt_helper::append_string_view(msg.payload, formatted); } else { diff --git a/include/spdlog/sinks/rotating_file_sink.h b/include/spdlog/sinks/rotating_file_sink.h index adf12b6f..14fd0bb6 100644 --- a/include/spdlog/sinks/rotating_file_sink.h +++ b/include/spdlog/sinks/rotating_file_sink.h @@ -1,154 +1,154 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#ifndef SPDLOG_H -#error "spdlog.h must be included before this file." -#endif - -#include "spdlog/details/file_helper.h" -#include "spdlog/details/null_mutex.h" -#include "spdlog/fmt/fmt.h" -#include "spdlog/sinks/base_sink.h" - -#include -#include -#include -#include -#include -#include - -namespace spdlog { -namespace sinks { - -// -// Rotating file sink based on size -// -template -class rotating_file_sink final : public base_sink -{ -public: - rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files) - : base_filename_(std::move(base_filename)) - , max_size_(max_size) - , max_files_(max_files) - { - file_helper_.open(calc_filename(base_filename_, 0)); - current_size_ = file_helper_.size(); // expensive. called only once - } - - // calc filename according to index and file extension if exists. - // e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". - static filename_t calc_filename(const filename_t &filename, std::size_t index) - { - typename std::conditional::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w; - if (index != 0u) - { - filename_t basename, ext; - std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); - fmt::format_to(w, SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); - } - else - { - fmt::format_to(w, SPDLOG_FILENAME_T("{}"), filename); - } - return fmt::to_string(w); - } - -protected: - void sink_it_(const details::log_msg &msg) override - { - fmt::memory_buffer formatted; - sink::formatter_->format(msg, formatted); - current_size_ += formatted.size(); - if (current_size_ > max_size_) - { - rotate_(); - current_size_ = formatted.size(); - } - file_helper_.write(formatted); - } - - void flush_() override - { - file_helper_.flush(); - } - -private: - // Rotate files: - // log.txt -> log.1.txt - // log.1.txt -> log.2.txt - // log.2.txt -> log.3.txt - // log.3.txt -> delete - void rotate_() - { - using details::os::filename_to_str; - file_helper_.close(); - for (auto i = max_files_; i > 0; --i) - { - 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); - - if (!rename_file(src, target)) - { - // if failed try again after a small delay. - // this is a workaround to a windows issue, where very high rotation - // rates can cause the rename to fail with permission denied (because of antivirus?). - details::os::sleep_for_millis(100); - if (!rename_file(src, target)) - { - file_helper_.reopen(true); // truncate the log file anyway to prevent it to grow beyond its limit! - throw spdlog_ex( - "rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); - } - } - } - 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_; - std::size_t max_size_; - std::size_t max_files_; - std::size_t current_size_; - details::file_helper file_helper_; -}; - -using rotating_file_sink_mt = rotating_file_sink; -using rotating_file_sink_st = rotating_file_sink; - -} // namespace sinks - -// -// factory functions -// - -template -inline std::shared_ptr rotating_logger_mt( - const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files) -{ - return Factory::template create(logger_name, filename, max_file_size, max_files); -} - -template -inline std::shared_ptr rotating_logger_st( - const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files) -{ - return Factory::template create(logger_name, filename, max_file_size, max_files); -} -} // namespace spdlog +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#ifndef SPDLOG_H +#error "spdlog.h must be included before this file." +#endif + +#include "spdlog/details/file_helper.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/fmt/fmt.h" +#include "spdlog/sinks/base_sink.h" + +#include +#include +#include +#include +#include +#include + +namespace spdlog { +namespace sinks { + +// +// Rotating file sink based on size +// +template +class rotating_file_sink final : public base_sink +{ +public: + rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files) + : base_filename_(std::move(base_filename)) + , max_size_(max_size) + , max_files_(max_files) + { + file_helper_.open(calc_filename(base_filename_, 0)); + current_size_ = file_helper_.size(); // expensive. called only once + } + + // calc filename according to index and file extension if exists. + // e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". + static filename_t calc_filename(const filename_t &filename, std::size_t index) + { + typename std::conditional::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w; + if (index != 0u) + { + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + fmt::format_to(w, SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); + } + else + { + fmt::format_to(w, SPDLOG_FILENAME_T("{}"), filename); + } + return fmt::to_string(w); + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + current_size_ += formatted.size(); + if (current_size_ > max_size_) + { + rotate_(); + current_size_ = formatted.size(); + } + file_helper_.write(formatted); + } + + void flush_() override + { + file_helper_.flush(); + } + +private: + // Rotate files: + // log.txt -> log.1.txt + // log.1.txt -> log.2.txt + // log.2.txt -> log.3.txt + // log.3.txt -> delete + void rotate_() + { + using details::os::filename_to_str; + file_helper_.close(); + for (auto i = max_files_; i > 0; --i) + { + 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); + + if (!rename_file(src, target)) + { + // if failed try again after a small delay. + // this is a workaround to a windows issue, where very high rotation + // rates can cause the rename to fail with permission denied (because of antivirus?). + details::os::sleep_for_millis(100); + if (!rename_file(src, target)) + { + file_helper_.reopen(true); // truncate the log file anyway to prevent it to grow beyond its limit! + throw spdlog_ex( + "rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); + } + } + } + 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_; + std::size_t max_size_; + std::size_t max_files_; + std::size_t current_size_; + details::file_helper file_helper_; +}; + +using rotating_file_sink_mt = rotating_file_sink; +using rotating_file_sink_st = rotating_file_sink; + +} // namespace sinks + +// +// factory functions +// + +template +inline std::shared_ptr rotating_logger_mt( + const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files) +{ + return Factory::template create(logger_name, filename, max_file_size, max_files); +} + +template +inline std::shared_ptr rotating_logger_st( + const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files) +{ + return Factory::template create(logger_name, filename, max_file_size, max_files); +} +} // namespace spdlog diff --git a/include/spdlog/sinks/sink.h b/include/spdlog/sinks/sink.h index 9f84c378..2f1adc10 100644 --- a/include/spdlog/sinks/sink.h +++ b/include/spdlog/sinks/sink.h @@ -16,7 +16,7 @@ class sink public: sink() : level_(level::trace) - , formatter_(new pattern_formatter("%+")) + , formatter_(new pattern_formatter()) { } diff --git a/include/spdlog/sinks/syslog_sink.h b/include/spdlog/sinks/syslog_sink.h index cb76613d..4df49b87 100644 --- a/include/spdlog/sinks/syslog_sink.h +++ b/include/spdlog/sinks/syslog_sink.h @@ -53,7 +53,7 @@ public: protected: 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 {} diff --git a/include/spdlog/spdlog.h b/include/spdlog/spdlog.h index bcca2e84..200cbd1d 100644 --- a/include/spdlog/spdlog.h +++ b/include/spdlog/spdlog.h @@ -29,7 +29,7 @@ struct synchronous_factory { auto sink = std::make_shared(std::forward(args)...); auto new_logger = std::make_shared(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; } }; @@ -125,6 +125,12 @@ inline void 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) +{ + details::registry::instance().set_automatic_registration(automatic_registation); +} + // API for using default logger (stdout_color_mt), // e.g: spdlog::info("Message {}", 1); // @@ -285,35 +291,56 @@ inline void critical(const wchar_t *fmt, const Args &... args) #endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT // -// Trace & Debug can be switched on/off at compile time with zero cost. -// 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, "another trace message {} {}", 1, 2); +// compile time macros. +// can be enabled/disabled using SPDLOG_ACTIVE_LEVEL (info by default). // -#ifdef SPDLOG_TRACE_ON -#define SPDLOG_STR_H(x) #x -#define SPDLOG_STR_HELPER(x) SPDLOG_STR_H(x) -#ifdef _MSC_VER -#define SPDLOG_TRACE(logger, ...) \ - logger->trace("[ "__FILE__ \ - "(" SPDLOG_STR_HELPER(__LINE__) ")] " __VA_ARGS__) +#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_TRACE +#define SPDLOG_LOGGER_TRACE(logger, ...) logger->trace(__VA_ARGS__) +#define SPDLOG_TRACE(...) spdlog::trace(__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, ...) logger->debug(__VA_ARGS__) +#define SPDLOG_DEBUG(...) spdlog::debug(__VA_ARGS__) #else -#define SPDLOG_TRACE(logger, ...) \ - logger->trace("[" __FILE__ ":" SPDLOG_STR_HELPER(__LINE__) "]" \ - " " __VA_ARGS__) +#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, ...) logger->info(__VA_ARGS__) +#define SPDLOG_INFO(...) spdlog::info(__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, ...) logger->warn(__VA_ARGS__) +#define SPDLOG_WARN(...) spdlog::warn(__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, ...) logger->error(__VA_ARGS__) +#define SPDLOG_ERROR(...) spdlog::error(__VA_ARGS__) #else -#define SPDLOG_TRACE(logger, ...) (void)0 +#define SPDLOG_LOGGER_ERROR(logger, ...) (void)0 +#define SPDLOG_ERROR(...) (void)0 #endif -#ifdef SPDLOG_DEBUG_ON -#define SPDLOG_DEBUG(logger, ...) logger->debug(__VA_ARGS__) +#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_CRITICAL +#define SPDLOG_LOGGER_CRITICAL(logger, ...) logger->critical(__VA_ARGS__) +#define SPDLOG_CRITICAL(...) spdlog::critical(__VA_ARGS__) #else -#define SPDLOG_DEBUG(logger, ...) (void)0 +#define SPDLOG_LOGGER_CRITICAL(logger, ...) (void)0 +#define SPDLOG_CRITICAL(...) (void)0 #endif } // namespace spdlog diff --git a/include/spdlog/tweakme.h b/include/spdlog/tweakme.h index d91159f7..0d77dadc 100644 --- a/include/spdlog/tweakme.h +++ b/include/spdlog/tweakme.h @@ -128,3 +128,10 @@ // // #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 +/////////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/tests/file_helper.cpp b/tests/file_helper.cpp index bfe7eb9d..ecdc5afe 100644 --- a/tests/file_helper.cpp +++ b/tests/file_helper.cpp @@ -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) { - log_msg msg; fmt::memory_buffer formatted; fmt::format_to(formatted, "{}", std::string(howmany, '1')); helper.write(formatted); diff --git a/tests/includes.h b/tests/includes.h index 3379f2c2..54601602 100644 --- a/tests/includes.h +++ b/tests/includes.h @@ -10,8 +10,7 @@ #include #include -#define SPDLOG_TRACE_ON -#define SPDLOG_DEBUG_ON +#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG #define SPDLOG_ENABLE_MESSAGE_COUNTER #include "spdlog/spdlog.h" diff --git a/tests/test_macros.cpp b/tests/test_macros.cpp index 0b3e0b7d..cf2bb136 100644 --- a/tests/test_macros.cpp +++ b/tests/test_macros.cpp @@ -4,8 +4,13 @@ #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]]") { + prepare_logdir(); std::string filename = "logs/simple_log"; @@ -13,28 +18,15 @@ TEST_CASE("debug and trace w/o format string", "[macros]]") logger->set_pattern("%v"); logger->set_level(spdlog::level::trace); - SPDLOG_TRACE(logger, "Test message 1"); - SPDLOG_DEBUG(logger, "Test message 2"); + SPDLOG_LOGGER_TRACE(logger, "Test message 1"); + SPDLOG_LOGGER_DEBUG(logger, "Test message 2"); logger->flush(); 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]]") +TEST_CASE("disable param evaluation", "[macros]") { - prepare_logdir(); - std::string filename = "logs/simple_log"; - - auto logger = spdlog::create("logger", filename); - logger->set_pattern("%v"); - 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(); - - REQUIRE(ends_with(file_contents(filename), "Test message 222\n")); - REQUIRE(count_lines(filename) == 2); + SPDLOG_TRACE("Test message {}", throw std::runtime_error("Should not be evaluated")); } diff --git a/tests/test_pattern_formatter.cpp b/tests/test_pattern_formatter.cpp index 17b61612..a098ab26 100644 --- a/tests/test_pattern_formatter.cpp +++ b/tests/test_pattern_formatter.cpp @@ -60,9 +60,12 @@ TEST_CASE("date MM/DD/YY ", "[pattern_formatter]") TEST_CASE("color range test1", "[pattern_formatter]") { auto formatter = std::make_shared("%^%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; + 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); REQUIRE(msg.color_range_start == 0); REQUIRE(msg.color_range_end == 5); @@ -72,7 +75,8 @@ TEST_CASE("color range test1", "[pattern_formatter]") TEST_CASE("color range test2", "[pattern_formatter]") { auto formatter = std::make_shared("%^%$", 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; formatter->format(msg, formatted); REQUIRE(msg.color_range_start == 0); @@ -83,7 +87,8 @@ TEST_CASE("color range test2", "[pattern_formatter]") TEST_CASE("color range test3", "[pattern_formatter]") { auto formatter = std::make_shared("%^***%$"); - 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; formatter->format(msg, formatted); REQUIRE(msg.color_range_start == 0); @@ -93,8 +98,9 @@ TEST_CASE("color range test3", "[pattern_formatter]") TEST_CASE("color range test4", "[pattern_formatter]") { auto formatter = std::make_shared("XX%^YYY%$", spdlog::pattern_time_type::local, "\n"); - spdlog::details::log_msg msg; - fmt::format_to(msg.raw, "ignored"); + std::string logger_name = "test"; + spdlog::details::log_msg msg(&logger_name, spdlog::level::info, "ignored"); + fmt::memory_buffer formatted; formatter->format(msg, formatted); REQUIRE(msg.color_range_start == 2); @@ -105,7 +111,8 @@ TEST_CASE("color range test4", "[pattern_formatter]") TEST_CASE("color range test5", "[pattern_formatter]") { auto formatter = std::make_shared("**%^"); - 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; formatter->format(msg, formatted); REQUIRE(msg.color_range_start == 2); @@ -115,9 +122,75 @@ TEST_CASE("color range test5", "[pattern_formatter]") TEST_CASE("color range test6", "[pattern_formatter]") { auto formatter = std::make_shared("**%$"); - 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; formatter->format(msg, formatted); REQUIRE(msg.color_range_start == 0); 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"); +} diff --git a/tests/test_registry.cpp b/tests/test_registry.cpp index 68833012..d306dba2 100644 --- a/tests/test_registry.cpp +++ b/tests/test_registry.cpp @@ -93,3 +93,21 @@ 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(tested_logger_name, "filename", 11, 59); + auto logger2 = spdlog::create_async(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); +}