diff --git a/README.md b/README.md index 22c8180c..ec66d00e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,9 @@ Just copy the files to your build tree and use a C++11 compiler * Headers only. * No dependencies. * Cross platform - Linux / Windows on 32/64 bits. -* Variadic-template/stream call styles: ```logger.info("variadic", x, y) << "or stream" << z;``` +* **new**! Feature rich [cppfromat call style](http://cppformat.readthedocs.org/en/stable/syntax.html) using the excellent [cppformat](http://cppformat.github.io/) library:```logger.info("Hello {} !!", "world");``` +* ostream call style ```logger.info() << "Hello << "logger";``` +* Mixed cppformat/ostream call style ```logger.info("{} + {} = ", 1, 2) << "?";``` * [Custom](https://github.com/gabime/spdlog/wiki/Custom-formatting) formatting. * Multi/Single threaded loggers. * Various log targets: @@ -27,12 +29,11 @@ Just copy the files to your build tree and use a C++11 compiler * Console logging. * Linux syslog. * Easily extendable with custom log targets (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface). -* Optional async logging . +* Optional, (extremly fast!), async logging. * Log levels. - ## Benchmarks Below are some [benchmarks](bench) comparing the time needed to log 1,000,000 lines to file under Ubuntu 64 bit, Intel i7-4770 CPU @ 3.40GHz (the best of 3 runs for each logger): @@ -43,8 +44,6 @@ Below are some [benchmarks](bench) comparing the time needed to log 1,000,000 li |10|15.151ss|3.546s|3.500s|0.641s|0.199s| - - ## Usage Example ```c++ #include @@ -52,33 +51,60 @@ Below are some [benchmarks](bench) comparing the time needed to log 1,000,000 li int main(int, char* []) { + namespace spd = spdlog; try { - std::string filename = "spdlog_example"; + std::string filename = "logs/spdlog_example"; + // Set log level to all loggers to DEBUG and above + spd::set_level(spd::level::DEBUG); + + + //Create console, multithreaded logger auto console = spd::stdout_logger_mt("console"); console->info("Welcome to spdlog!") ; - console->info() << "Creating file " << filename << ".."; + console->info("An info message example {}..", 1); + console->info() << "Streams are supported too " << 1; + + console->info("Easy padding in numbers like {:08d}", 12); + console->info("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"); + console->info("{:>30}", "right aligned"); + console->info("{:^30}", "centered"); + + //Create a file rotating logger with 5mb size max and 3 rotated files auto file_logger = spd::rotating_logger_mt("file_logger", filename, 1024 * 1024 * 5, 3); file_logger->info("Log file message number", 1); - for (int i = 0; i < 100; ++i) - { - auto square = i*i; - file_logger->info() << i << '*' << i << '=' << square << " (" << "0x" << std::hex << square << ")"; - } - // Change log level to all loggers to warning and above - spd::set_level(spd::level::WARN); - console->info("This should not be displayed"); - console->warn("This should!"); - spd::set_level(spd::level::INFO); + spd::set_pattern("*** [%H:%M:%S %z] [thread %t] %v ***"); + file_logger->info("This is another message with custom format"); - // Change format pattern to all loggers - spd::set_pattern(" **** %Y-%m-%d %H:%M:%S.%e %l **** %v"); - spd::get("console")->info("This is another message with different format"); + spd::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name) function"); + + SPDLOG_TRACE(file_logger, "This is a trace message (only #ifdef _DEBUG)", 123); + + // + // Asynchronous logging is easy.. + // Just call spdlog::set_async_mode(max_q_size) and all created loggers from now on will be asynchronous.. + // + size_t max_q_size = 1048576; + spdlog::set_async_mode(max_q_size); + auto async_file= spd::daily_logger_st("async_file_logger", "logs/async_log.txt"); + async_file->info() << "This is async log.." << "Should be very fast!"; + + // + // syslog example + // +#ifdef __linux__ + auto syslog_logger = spd::syslog_logger("syslog"); + syslog_logger->warn("This is warning that will end up in syslog. This is Linux only!"); +#endif } catch (const spd::spdlog_ex& ex) { @@ -86,4 +112,5 @@ int main(int, char* []) } return 0; } + ``` diff --git a/bench/Makefile b/bench/Makefile index 8bbdbd70..6a1e93ca 100644 --- a/bench/Makefile +++ b/bench/Makefile @@ -1,5 +1,5 @@ CXX = g++ -CXXFLAGS = -g -march=native -Wall -Wextra -Wshadow -pedantic -std=c++11 -pthread -Wl,--no-as-needed -I../include +CXXFLAGS = -march=native -Wall -Wshadow -Wextra -pedantic -std=c++11 -pthread -Wl,--no-as-needed -I../include CXX_RELEASE_FLAGS = -O3 -flto diff --git a/bench/g2log-bench-mt.cpp b/bench/g2log-bench-mt.cpp index 60660ecf..9e32c3f4 100644 --- a/bench/g2log-bench-mt.cpp +++ b/bench/g2log-bench-mt.cpp @@ -19,7 +19,7 @@ int main(int argc, char* argv[]) g2LogWorker g2log(argv[0], "logs"); g2::initializeLogging(&g2log); - + std::atomic msg_counter {0}; vector threads; diff --git a/bench/g2log-bench.cpp b/bench/g2log-bench.cpp index d06131b8..f716940a 100644 --- a/bench/g2log-bench.cpp +++ b/bench/g2log-bench.cpp @@ -5,10 +5,10 @@ int main(int, char* argv[]) { int howmany = 1000000; - - g2LogWorker g2log(argv[0], "logs"); + + g2LogWorker g2log(argv[0], "logs"); g2::initializeLogging(&g2log); - + for(int i = 0 ; i < howmany; ++i) LOG(INFO) << "g2log message # " << i << ": This is some text for your pleasure"; diff --git a/bench/results.txt b/bench/results.txt deleted file mode 100644 index df7f94e3..00000000 --- a/bench/results.txt +++ /dev/null @@ -1,80 +0,0 @@ -Running benchmakrs (all with 1000,000 writes to the logs folder - -boost-bench (single thread).. - -real 0m4.779s -user 0m4.256s -sys 0m0.044s - - -glog-bench (single thread).. - -real 0m1.109s -user 0m0.967s -sys 0m0.140s - - -g2log-bench (single thread).. - -Exiting, log location: logs/g2log-bench.g2log.20141124-233419.log - -real 0m3.155s -user 0m4.255s -sys 0m0.874s - - -spdlog-bench (single thread) - -real 0m0.947s -user 0m0.914s -sys 0m0.032s - - ------------------------------------- -Multithreaded benchmarks.. ------------------------------------- -boost-bench-mt (10 threads, single logger).. - -real 0m15.151s -user 0m35.555s -sys 0m7.453s - - -glog-bench-mt (10 threads, single logger).. - -real 0m3.546s -user 0m9.836s -sys 0m10.985s - - -g2log-bench-mt (10 threads, single logger).. - -Exiting, log location: logs/g2log-bench-mt.g2log.20141124-233502.log - -real 0m3.500s -user 0m7.671s -sys 0m1.646s - - -spdlog-bench-mt (10 threads, single logger).. - -real 0m1.549s -user 0m6.994s -sys 0m2.969s - - ------------------------------------- -Async benchmarks.. ------------------------------------- -spdlog-bench-async (single thread).. - -real 0m1.455s -user 0m2.381s -sys 0m0.417s - - -spdlog-bench-mt-async (10 threads, single logger).. - -real 0m2.040s -user 0m4.076s -sys 0m2.822s diff --git a/bench/run_all.sh b/bench/run_all.sh index 4a437ece..4e0eb531 100755 --- a/bench/run_all.sh +++ b/bench/run_all.sh @@ -2,29 +2,29 @@ echo "Running benchmakrs (all with 1000,000 writes to the logs folder)" echo echo "boost-bench (single thread).." -time ./boost-bench -rm logs/* +for i in {1..3}; do time ./boost-bench; done +rm -f logs/* echo echo sleep 5 echo "glog-bench (single thread).." -time ./glog-bench -rm logs/* +for i in {1..3}; do time ./glog-bench; done +rm -f logs/* echo echo sleep 5 echo "g2log-bench (single thread).." -time ./g2log-bench -rm logs/* +for i in {1..3}; do time ./g2log-bench; done +rm -f logs/* echo echo sleep 5 echo "spdlog-bench (single thread)" -time ./spdlog-bench -rm logs/* +for i in {1..3}; do time ./spdlog-bench; done +rm -f logs/* echo echo sleep 5 @@ -32,29 +32,29 @@ echo "------------------------------------" echo "Multithreaded benchmarks.." echo "------------------------------------" echo "boost-bench-mt (10 threads, single logger)".. -time ./boost-bench-mt -rm logs/* +for i in {1..3}; do ./boost-bench-mt; done +rm -f logs/* echo echo sleep 5 echo "glog-bench-mt (10 threads, single logger)".. -time ./glog-bench-mt -rm logs/* +for i in {1..3}; do time ./glog-bench-mt; done +rm -f logs/* echo echo sleep 5 echo "g2log-bench-mt (10 threads, single logger)".. -time ./g2log-bench-mt -rm logs/* +for i in {1..3}; do time ./g2log-bench-mt; done +rm -f logs/* echo echo sleep 5 echo "spdlog-bench-mt (10 threads, single logger)".. -time ./spdlog-bench-mt -rm logs/* +for i in {1..3}; do time ./spdlog-bench-mt; done +rm -f logs/* echo echo sleep 5 @@ -64,14 +64,14 @@ echo "Async benchmarks.." echo "------------------------------------" echo "spdlog-bench-async (single thread)".. -time ./spdlog-bench-async -rm logs/* +for i in {1..3}; do time ./spdlog-bench-async; done +rm -f logs/* echo echo sleep 5 echo "spdlog-bench-mt-async (10 threads, single logger)".. -time ./spdlog-bench-mt-async +for i in {1..3}; do time ./spdlog-bench-mt-async; done diff --git a/bench/spdlog-bench-async.cpp b/bench/spdlog-bench-async.cpp index 9054cb6e..c9fea752 100644 --- a/bench/spdlog-bench-async.cpp +++ b/bench/spdlog-bench-async.cpp @@ -4,15 +4,19 @@ int main(int, char* []) { - int howmany = 1000000; + int howmany = 1048576; namespace spd = spdlog; - spd::set_async_mode(2500, std::chrono::seconds(0)); + spd::set_async_mode(howmany); ///Create a file rotating logger with 5mb size max and 3 rotated files auto logger = spd::rotating_logger_mt("file_logger", "logs/spd-sample", 10 *1024 * 1024 , 5); logger->set_pattern("[%Y-%b-%d %T.%e]: %v"); for(int i = 0 ; i < howmany; ++i) logger->info() << "spdlog message #" << i << ": This is some text for your pleasure"; - spd::stop(); + + + //because spdlog async logger waits for the back thread logger to finish all messages upon destrcuting, + //and we want to measure only the time it took to push those messages to the backthread.. + abort(); return 0; } diff --git a/bench/spdlog-bench-mt-async.cpp b/bench/spdlog-bench-mt-async.cpp index 17c72a8c..a0949b7f 100644 --- a/bench/spdlog-bench-mt-async.cpp +++ b/bench/spdlog-bench-mt-async.cpp @@ -14,10 +14,10 @@ int main(int argc, char* argv[]) if(argc > 1) thread_count = atoi(argv[1]); - int howmany = 1000000; + int howmany = 1048576; namespace spd = spdlog; - spd::set_async_mode(2500, std::chrono::seconds(0)); + spd::set_async_mode(howmany); ///Create a file rotating logger with 5mb size max and 3 rotated files auto logger = spd::rotating_logger_mt("file_logger", "logs/spd-sample", 10 *1024 * 1024 , 5); @@ -45,6 +45,7 @@ int main(int argc, char* argv[]) t.join(); }; - spd::stop(); - return 0; + //because spdlog async logger waits for the back thread logger to finish all messages upon destrcuting, + //and we want to measure only the time it took to push those messages to the backthread.. + abort(); } diff --git a/bench/spdlog-bench-mt.cpp b/bench/spdlog-bench-mt.cpp index 93a3cca0..778c7745 100644 --- a/bench/spdlog-bench-mt.cpp +++ b/bench/spdlog-bench-mt.cpp @@ -17,8 +17,8 @@ int main(int argc, char* argv[]) int howmany = 1000000; namespace spd = spdlog; - ///Create a file rotating logger with 5mb size max and 5 rotated files - auto logger = spd::rotating_logger_mt("file_logger", "logs/spd-sample", 10 *1024 * 1024 , 5); + ///Create a file rotating logger with 10mb size max and 5 rotated files + auto logger = spd::rotating_logger_mt("file_logger", "logs/spd-sample", 10 *1024 * 1024 , 5, false); logger->set_pattern("[%Y-%b-%d %T.%e]: %v"); diff --git a/example/Makefile b/example/Makefile index b576fa7b..4208786a 100644 --- a/example/Makefile +++ b/example/Makefile @@ -1,5 +1,5 @@ CXX = g++ -CXXFLAGS = -march=native -Wall -Wextra -Wshadow -pedantic -std=c++11 -pthread -Wl,--no-as-needed -I../include +CXXFLAGS = -march=native -Wall -Wshadow -Wextra -pedantic -std=c++11 -pthread -Wl,--no-as-needed -I../include CXX_RELEASE_FLAGS = -O3 -flto CXX_DEBUG_FLAGS= -g diff --git a/example/bench.cpp b/example/bench.cpp index b1e908af..12dcb923 100644 --- a/example/bench.cpp +++ b/example/bench.cpp @@ -50,9 +50,13 @@ void bench_mt(int howmany, std::shared_ptr log, int thread_count int main(int argc, char* argv[]) { - int howmany = 1000000; + int howmany = 1048576; int threads = 10; +<<<<<<< HEAD bool auto_flush = false; +======= + bool auto_flush = false; +>>>>>>> cppformat int file_size = 30 * 1024 * 1024; int rotating_files = 5; @@ -64,12 +68,13 @@ int main(int argc, char* argv[]) if (argc > 2) threads = atoi(argv[2]); + cout << "*******************************************************************************\n"; cout << "Single thread, " << format(howmany) << " iterations, auto flush=" << auto_flush << endl; cout << "*******************************************************************************\n"; - auto rotating_st = spdlog::rotating_logger_st("rotating_st", "logs/rotating_st", file_size, rotating_files, auto_flush); - bench(howmany, rotating_st); + auto rotating_st = spdlog::rotating_logger_st("rotating_st", "logs/rotating_st", file_size, rotating_files, auto_flush); + bench(howmany, rotating_st); auto daily_st = spdlog::daily_logger_st("daily_st", "logs/daily_st", auto_flush); bench(howmany, daily_st); bench(howmany, spdlog::create("null_st")); @@ -85,18 +90,20 @@ int main(int argc, char* argv[]) auto daily_mt = spdlog::daily_logger_mt("daily_mt", "logs/daily_mt", auto_flush); bench_mt(howmany, daily_mt, threads); bench(howmany, spdlog::create("null_mt")); - + cout << "\n*******************************************************************************\n"; cout << "async logging.. " << threads << " threads sharing same logger, " << format(howmany) << " iterations, auto_flush=" << auto_flush << endl; - cout << "*******************************************************************************\n"; + cout << "*******************************************************************************\n"; - - spdlog::set_async_mode(2500); - auto daily_st_async = spdlog::daily_logger_st("daily_async", "logs/daily_async", auto_flush); - bench_mt(howmany, daily_st_async, threads); - - spdlog::stop(); + spdlog::set_async_mode(howmany); + + for(int i = 0; i < 5; ++i) + { + auto as = spdlog::daily_logger_st("as", "logs/daily_async", auto_flush); + bench_mt(howmany, as, threads); + spdlog::drop("as"); + } } catch (std::exception &ex) { @@ -113,7 +120,7 @@ void bench(int howmany, std::shared_ptr log) auto start = system_clock::now(); for (auto i = 0; i < howmany; ++i) { - log->info("Hello logger: msg number ", i); + log->info("Hello logger: msg number {}", i); } @@ -138,7 +145,7 @@ void bench_mt(int howmany, std::shared_ptr log, int thread_count { int counter = ++msg_counter; if (counter > howmany) break; - log->info("Hello logger: msg number ") << counter; + log->info("Hello logger: msg number {}", counter); } })); } diff --git a/example/example.cpp b/example/example.cpp index fbd5fc3f..6acc94ba 100644 --- a/example/example.cpp +++ b/example/example.cpp @@ -26,7 +26,7 @@ // spdlog usage example // #include -#include "spdlog/spdlog.h" +#include "spdlog/spdlog.h"ls int main(int, char* []) @@ -44,17 +44,23 @@ int main(int, char* []) //Create console, multithreaded logger auto console = spd::stdout_logger_mt("console"); console->info("Welcome to spdlog!") ; - console->info("An info message example", "...", 1, 2, 3.5); - console->info() << "Streams are supported too " << std::setw(5) << std::setfill('0') << 1; + console->info("An info message example {}..", 1); + console->info() << "Streams are supported too " << 1; + + console->info("Easy padding in numbers like {:08d}", 12); + console->info("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"); + console->info("{:>30}", "right aligned"); + console->info("{:^30}", "centered"); + //Create a file rotating logger with 5mb size max and 3 rotated files auto file_logger = spd::rotating_logger_mt("file_logger", filename, 1024 * 1024 * 5, 3); file_logger->info("Log file message number", 1); - for (int i = 0; i < 100; ++i) - { - file_logger->info(i, "in hex is", "0x") << std::hex << std::uppercase << i; - } spd::set_pattern("*** [%H:%M:%S %z] [thread %t] %v ***"); file_logger->info("This is another message with custom format"); @@ -67,8 +73,7 @@ int main(int, char* []) // Asynchronous logging is easy.. // Just call spdlog::set_async_mode(max_q_size) and all created loggers from now on will be asynchronous.. // - - size_t max_q_size = 100000; + size_t max_q_size = 1048576; spdlog::set_async_mode(max_q_size); auto async_file= spd::daily_logger_st("async_file_logger", "logs/async_log.txt"); async_file->info() << "This is async log.." << "Should be very fast!"; diff --git a/example/utils.h b/example/utils.h index 25b0cb45..95dd9257 100644 --- a/example/utils.h +++ b/example/utils.h @@ -24,7 +24,6 @@ #pragma once -#include #include #include #include diff --git a/include/spdlog/async_logger.h b/include/spdlog/async_logger.h index d2302c23..518e61cf 100644 --- a/include/spdlog/async_logger.h +++ b/include/spdlog/async_logger.h @@ -24,10 +24,15 @@ #pragma once -// Async logger +// Very fast asynchronous logger (millions of logs per second on an average desktop) +// Uses pre allocated lockfree queue for maximum throughput even under large number of threads. +// Creates a single back thread to pop messages from the queue and log them. +// // Upon each log write the logger: -// 1. Checks if its log level is enough to log the message -// 2. Push a new copy of the message to a queue (uses sinks::async_sink for this) +// 1. Checks if its log level is enough to log the message +// 2. Push a new copy of the message to a queue (or block the caller until space is available in the queue) +// 3. will throw spdlog_ex upon log exceptions +// Upong destruction, logs all remaining messages in the queue before destructing.. #include #include "common.h" @@ -37,18 +42,18 @@ namespace spdlog { -namespace sinks +namespace details { -class async_sink; +class async_log_helper; } class async_logger :public logger { public: template - async_logger(const std::string& name, const It& begin, const It& end, size_t queue_size, const log_clock::duration& shutdown_duration); - async_logger(const std::string& logger_name, sinks_init_list sinks, size_t queue_size, const log_clock::duration& shutdown_duration); - async_logger(const std::string& logger_name, sink_ptr single_sink, size_t queue_size, const log_clock::duration& shutdown_duration); + async_logger(const std::string& name, const It& begin, const It& end, size_t queue_size); + async_logger(const std::string& logger_name, sinks_init_list sinks, size_t queue_size); + async_logger(const std::string& logger_name, sink_ptr single_sink, size_t queue_size); protected: @@ -58,8 +63,7 @@ protected: void _stop() override; private: - log_clock::duration _shutdown_duration; - std::unique_ptr _as; + std::unique_ptr _async_log_helper; }; } diff --git a/include/spdlog/details/async_log_helper.h b/include/spdlog/details/async_log_helper.h new file mode 100644 index 00000000..af6efa26 --- /dev/null +++ b/include/spdlog/details/async_log_helper.h @@ -0,0 +1,289 @@ +/*************************************************************************/ +/* spdlog - an extremely fast and easy to use c++11 logging library. */ +/* Copyright (c) 2014 Gabi Melman. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +// async log helper : +// Process logs asynchronously using a back thread. +// +// If the internal queue of log messages reaches its max size, +// then the client call will block until there is more room. +// +// If the back thread throws during logging, a spdlog::spdlog_ex exception +// will be thrown in client's thread when tries to log the next message + +#pragma once + +#include +#include +#include + +#include "../common.h" +#include "../sinks/sink.h" +#include "./mpmc_bounded_q.h" +#include "./log_msg.h" +#include "./format.h" + + +namespace spdlog +{ +namespace details +{ + + +class async_log_helper +{ + // Async msg to move to/from the queue + // Movable only. should never be copied + struct async_msg + { + std::string logger_name; + level::level_enum level; + log_clock::time_point time; + std::string txt; + + async_msg() = default; + ~async_msg() = default; + + async_msg(const async_msg&) = delete; + async_msg& operator=(async_msg& other) = delete; + + async_msg(const details::log_msg& m) : + logger_name(m.logger_name), + level(m.level), + time(m.time), + txt(m.raw.data(), m.raw.size()) + {} + + async_msg(async_msg&& other) : + logger_name(std::move(other.logger_name)), + level(std::move(other.level)), + time(std::move(other.time)), + txt(std::move(other.txt)) + {} + + async_msg& operator=(async_msg&& other) + { + logger_name = std::move(other.logger_name); + level = other.level; + time = std::move(other.time); + txt = std::move(other.txt); + return *this; + } + + + void fill_log_msg(log_msg &msg) + { + msg.clear(); + msg.logger_name = logger_name; + msg.level = level; + msg.time = time; + msg.raw << txt; + } + }; + +public: + + using item_type = async_msg; + using q_type = details::mpmc_bounded_queue; + + using clock = std::chrono::steady_clock; + + + async_log_helper(formatter_ptr formatter, const std::vector& sinks, size_t queue_size); + void log(const details::log_msg& msg); + + //Stop logging and join the back thread + ~async_log_helper(); + void set_formatter(formatter_ptr); + + +private: + std::atomic _active; + formatter_ptr _formatter; + std::vector> _sinks; + q_type _q; + std::thread _worker_thread; + + // last exception thrown from the worker thread + std::shared_ptr _last_workerthread_ex; + + + // throw last worker thread exception or if worker thread is not active + void throw_if_bad_worker(); + + // worker thread main loop + void worker_loop(); + + //pop next message from the queue and process it + //return true if a message was available (queue was not empty), will set the last_pop to the pop time + bool process_next_msg(clock::time_point& last_pop); + + // guess how much to sleep if queue is empty/full using last succesful op time as hint + static void sleep_or_yield(const clock::time_point& last_op_time); + + + // clear all remaining messages(if any), stop the _worker_thread and join it + void join_worker(); + +}; +} +} + +/////////////////////////////////////////////////////////////////////////////// +// async_sink class implementation +/////////////////////////////////////////////////////////////////////////////// +inline spdlog::details::async_log_helper::async_log_helper(formatter_ptr formatter, const std::vector& sinks, size_t queue_size): + _active(true), + _formatter(formatter), + _sinks(sinks), + _q(queue_size), + _worker_thread(&async_log_helper::worker_loop, this) +{} + +inline spdlog::details::async_log_helper::~async_log_helper() +{ + join_worker(); +} + + +//Try to push and block until succeeded +inline void spdlog::details::async_log_helper::log(const details::log_msg& msg) +{ + throw_if_bad_worker(); + async_msg new_msg(msg); + if (!_q.enqueue(std::move(new_msg))) + { + auto last_op_time = clock::now(); + do + { + sleep_or_yield(last_op_time); + } + while (!_q.enqueue(std::move(new_msg))); + } + +} + +inline void spdlog::details::async_log_helper::worker_loop() +{ + clock::time_point last_pop = clock::now(); + while (_active) + { + //Dont die if there are still messages in the q to process + while(process_next_msg(last_pop)); + } +} + + +inline bool spdlog::details::async_log_helper::process_next_msg(clock::time_point& last_pop) +{ + + async_msg incoming_async_msg; + log_msg incoming_log_msg; + + if (_q.dequeue(incoming_async_msg)) + { + last_pop = clock::now(); + try + { + incoming_async_msg.fill_log_msg(incoming_log_msg); + _formatter->format(incoming_log_msg); + for (auto &s : _sinks) + s->log(incoming_log_msg); + } + catch (const std::exception& ex) + { + _last_workerthread_ex = std::make_shared(std::string("async_logger worker thread exception: ") + ex.what()); + } + catch (...) + { + _last_workerthread_ex = std::make_shared("async_logger worker thread exception"); + } + return true; + } + // sleep or yield if queue is empty. + else + { + sleep_or_yield(last_pop); + return false; + } +} + +inline void spdlog::details::async_log_helper::set_formatter(formatter_ptr msg_formatter) +{ + _formatter = msg_formatter; +} + + +// Sleep,yield or return immediatly using the time passed since last message as a hint +inline void spdlog::details::async_log_helper::sleep_or_yield(const clock::time_point& last_op_time) +{ + using std::chrono::milliseconds; + using namespace std::this_thread; + + auto time_since_op = clock::now() - last_op_time; + + //spin upto 1 ms + if (time_since_op <= milliseconds(1)) + return; + + // yield upto 10ms + if (time_since_op <= milliseconds(10)) + return yield(); + + + // sleep for half of duration since last op + if (time_since_op <= milliseconds(100)) + return sleep_for(time_since_op / 2); + + return sleep_for(milliseconds(100)); +} + +//throw if the worker thread threw an exception or not active +inline void spdlog::details::async_log_helper::throw_if_bad_worker() +{ + if (_last_workerthread_ex) + { + auto ex = std::move(_last_workerthread_ex); + _last_workerthread_ex.reset(); + throw *ex; + } + if (!_active) + throw(spdlog_ex("async logger is not active")); +} + + +inline void spdlog::details::async_log_helper::join_worker() +{ + _active = false; + + try + { + _worker_thread.join(); + } + catch (const std::system_error&) //Dont crash if thread not joinable + {} +} + + + + diff --git a/include/spdlog/details/async_logger_impl.h b/include/spdlog/details/async_logger_impl.h index e84f897d..0b3022ec 100644 --- a/include/spdlog/details/async_logger_impl.h +++ b/include/spdlog/details/async_logger_impl.h @@ -25,8 +25,7 @@ #pragma once -#include -#include "../sinks/async_sink.h" +#include "./async_log_helper.h" // // Async Logger implementation @@ -35,38 +34,29 @@ template -inline spdlog::async_logger::async_logger(const std::string& logger_name, const It& begin, const It& end, size_t queue_size, const log_clock::duration& shutdown_duration) : +inline spdlog::async_logger::async_logger(const std::string& logger_name, const It& begin, const It& end, size_t queue_size) : logger(logger_name, begin, end), - _shutdown_duration(shutdown_duration), - _as(std::unique_ptr(new sinks::async_sink(queue_size))) + _async_log_helper(new details::async_log_helper(_formatter, _sinks, queue_size)) { - _as->set_formatter(_formatter); - for (auto &s : _sinks) - _as->add_sink(s); } -inline spdlog::async_logger::async_logger(const std::string& logger_name, sinks_init_list sinks, size_t queue_size, const log_clock::duration& shutdown_duration) : - async_logger(logger_name, sinks.begin(), sinks.end(), queue_size, shutdown_duration) {} +inline spdlog::async_logger::async_logger(const std::string& logger_name, sinks_init_list sinks, size_t queue_size) : + async_logger(logger_name, sinks.begin(), sinks.end(), queue_size) {} -inline spdlog::async_logger::async_logger(const std::string& logger_name, sink_ptr single_sink, size_t queue_size, const log_clock::duration& shutdown_duration) : - async_logger(logger_name, { single_sink }, queue_size, shutdown_duration) {} +inline spdlog::async_logger::async_logger(const std::string& logger_name, sink_ptr single_sink, size_t queue_size) : + async_logger(logger_name, { single_sink }, queue_size) {} -inline void spdlog::async_logger::_log_msg(details::log_msg& msg) -{ - _as->log(msg); -} - inline void spdlog::async_logger::_set_formatter(spdlog::formatter_ptr msg_formatter) { _formatter = msg_formatter; - _as->set_formatter(_formatter); + _async_log_helper->set_formatter(_formatter); } inline void spdlog::async_logger::_set_pattern(const std::string& pattern) { _formatter = std::make_shared(pattern); - _as->set_formatter(_formatter); + _async_log_helper->set_formatter(_formatter); } @@ -74,5 +64,9 @@ inline void spdlog::async_logger::_set_pattern(const std::string& pattern) inline void spdlog::async_logger::_stop() { set_level(level::OFF); - _as->shutdown(_shutdown_duration); +} + +inline void spdlog::async_logger::_log_msg(details::log_msg& msg) +{ + _async_log_helper->log(msg); } diff --git a/include/spdlog/details/blocking_queue.h b/include/spdlog/details/blocking_queue.h deleted file mode 100644 index 8fe01998..00000000 --- a/include/spdlog/details/blocking_queue.h +++ /dev/null @@ -1,155 +0,0 @@ -/*************************************************************************/ -/* spdlog - an extremely fast and easy to use c++11 logging library. */ -/* Copyright (c) 2014 Gabi Melman. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#pragma once - -// A blocking multi-consumer/multi-producer thread safe queue. -// Has max capacity and supports timeout on push or pop operations. - -#include -#include -#include -#include -#include - -namespace spdlog -{ -namespace details -{ - -template -class blocking_queue -{ -public: - using queue_type = std::queue; - using item_type = T; - using size_type = typename queue_type::size_type; - using clock = std::chrono::system_clock; - - explicit blocking_queue(size_type max_size) : - _max_size(max_size), - _q(), - _mutex() - { - } - blocking_queue(const blocking_queue&) = delete; - blocking_queue& operator=(const blocking_queue&) = delete; - ~blocking_queue() = default; - - size_type size() - { - std::lock_guard lock(_mutex); - return _q.size(); - } - - // Push copy of item into the back of the queue. - // If the queue is full, block the calling thread util there is room or timeout have passed. - // Return: false on timeout, true on successful push. - template - bool push(TT&& item, const std::chrono::duration& timeout) - { - { - std::unique_lock ul(_mutex); - if (is_full()) - { - if (!_item_popped_cond.wait_until(ul, clock::now() + timeout, [this]() - { - return !this->is_full(); - })) - return false; - } - _q.push(std::forward(item)); - } - _item_pushed_cond.notify_one(); - return true; - } - -// Push copy of item into the back of the queue. -// If the queue is full, block the calling thread until there is room. - template - void push(TT&& item) - { - while (!push(std::forward(item), std::chrono::seconds(60))); - } - -// Pop a copy of the front item in the queue into the given item ref. -// If the queue is empty, block the calling thread util there is item to pop or timeout have passed. -// Return: false on timeout , true on successful pop/ - template - bool pop(T& item, const std::chrono::duration& timeout) - { - { - std::unique_lock ul(_mutex); - if (is_empty()) - { - if (!_item_pushed_cond.wait_until(ul, clock::now() + timeout, [this]() - { - return !this->is_empty(); - })) - return false; - } - item = std::move(_q.front()); - _q.pop(); - } - _item_popped_cond.notify_one(); - return true; - } - -// Pop a copy of the front item in the queue into the given item ref. -// If the queue is empty, block the calling thread util there is item to pop. - void pop(T& item) - { - while (!pop(item, std::chrono::hours(1))); - } - -// Clear the queue - void clear() - { - { - std::unique_lock ul(_mutex); - queue_type().swap(_q); - } - _item_popped_cond.notify_all(); - } - -private: - size_type _max_size; - std::queue _q; - std::mutex _mutex; - std::condition_variable _item_pushed_cond; - std::condition_variable _item_popped_cond; - - inline bool is_full() - { - return _q.size() >= _max_size; - } - - inline bool is_empty() - { - return _q.size() == 0; - } -}; - -} -} diff --git a/include/spdlog/details/fast_istostr.h b/include/spdlog/details/fast_istostr.h deleted file mode 100644 index 55500955..00000000 --- a/include/spdlog/details/fast_istostr.h +++ /dev/null @@ -1,130 +0,0 @@ -/*************************************************************************/ -/* spdlog - an extremely fast and easy to use c++11 logging library. */ -/* Copyright (c) 2014 Gabi Melman. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#pragma once -#include - -//Fast to int to string -//Base on :http://stackoverflow.com/a/4351484/192001 -//Modified version to pad zeros according to padding arg - -namespace spdlog -{ -namespace details -{ - -const char digit_pairs[201] = -{ - "00010203040506070809" - "10111213141516171819" - "20212223242526272829" - "30313233343536373839" - "40414243444546474849" - "50515253545556575859" - "60616263646566676869" - "70717273747576777879" - "80818283848586878889" - "90919293949596979899" -}; - - -inline std::string& fast_itostr(int n, std::string& s, size_t padding) -{ - if (n == 0) - { - s = std::string(padding, '0'); - return s; - } - - int sign = -(n < 0); - unsigned int val = static_cast((n^sign) - sign); - - size_t size; - if (val >= 10000) - { - if (val >= 10000000) - { - if (val >= 1000000000) - size = 10; - else if (val >= 100000000) - size = 9; - else - size = 8; - } - else - { - if (val >= 1000000) - size = 7; - else if (val >= 100000) - size = 6; - else - size = 5; - } - } - else - { - if (val >= 100) - { - if (val >= 1000) - size = 4; - else - size = 3; - } - else - { - if (val >= 10) - size = 2; - else - size = 1; - } - } - size -= sign; - if (size < padding) - size = padding; - - s.resize(size); - char* c = &s[0]; - if (sign) - *c = '-'; - - c += size - 1; - while (val >= 100) - { - size_t pos = val % 100; - val /= 100; - *(short*)(c - 1) = *(short*)(digit_pairs + 2 * pos); - c -= 2; - } - while (val > 0) - { - *c-- = static_cast('0' + (val % 10)); - val /= 10; - } - - while (c >= s.data()) - *c-- = '0'; - return s; -} -} -} diff --git a/include/spdlog/details/fast_oss.h b/include/spdlog/details/fast_oss.h deleted file mode 100644 index 447c1783..00000000 --- a/include/spdlog/details/fast_oss.h +++ /dev/null @@ -1,189 +0,0 @@ -/*************************************************************************/ -/* spdlog - an extremely fast and easy to use c++11 logging library. */ -/* Copyright (c) 2014 Gabi Melman. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#pragma once - -// A faster-than-ostringstream class -// uses stack_buf as the underlying buffer (upto 256 bytes before using the heap) - -#include -#include -#include "fast_istostr.h" -#include "stack_buf.h" -#include - -namespace spdlog -{ -namespace details -{ - -class stack_devicebuf :public std::streambuf -{ -public: - static const unsigned short stack_size = 256; - using stackbuf_t = stack_buf; - - stack_devicebuf() = default; - ~stack_devicebuf() = default; - - stack_devicebuf(const stack_devicebuf& other) :std::basic_streambuf(), _stackbuf(other._stackbuf) - {} - - stack_devicebuf(stack_devicebuf&& other): - std::basic_streambuf(), - _stackbuf(std::move(other._stackbuf)) - { - other.clear(); - } - - stack_devicebuf& operator=(stack_devicebuf other) - { - std::swap(_stackbuf, other._stackbuf); - return *this; - } - - const stackbuf_t& buf() const - { - return _stackbuf; - } - std::size_t size() const - { - return _stackbuf.size(); - } - - void clear() - { - _stackbuf.clear(); - } - -protected: - // copy the give buffer into the accumulated fast buffer - std::streamsize xsputn(const char_type* s, std::streamsize count) override - { - _stackbuf.append(s, static_cast(count)); - return count; - } - - int_type overflow(int_type ch) override - { - if (traits_type::not_eof(ch)) - { - char c = traits_type::to_char_type(ch); - xsputn(&c, 1); - } - return ch; - } -private: - stackbuf_t _stackbuf; -}; - - -class fast_oss :public std::ostream -{ -public: - fast_oss() :std::ostream(&_dev) {} - ~fast_oss() = default; - - fast_oss(const fast_oss& other) :std::basic_ios(), std::ostream(&_dev), _dev(other._dev) - {} - - fast_oss(fast_oss&& other) :std::basic_ios(), std::ostream(&_dev), _dev(std::move(other._dev)) - { - other.clear(); - } - - - fast_oss& operator=(fast_oss other) - { - swap(*this, other); - return *this; - } - - void swap(fast_oss& first, fast_oss& second) // nothrow - { - using std::swap; - swap(first._dev, second._dev); - } - - std::string str() const - { - auto& buffer = _dev.buf(); - const char*data = buffer.data(); - return std::string(data, data+buffer.size()); - } - - const stack_devicebuf::stackbuf_t& buf() const - { - return _dev.buf(); - } - - - std::size_t size() const - { - return _dev.size(); - } - - void clear() - { - _dev.clear(); - } - - // - // The following were added because they significantly boost to perfromance - // - void putc(char c) - { - _dev.sputc(c); - } - - // put int and pad with zeroes if smalled than min_width - void put_int(int n, size_t padding) - { - std::string s; - details::fast_itostr(n, s, padding); - _dev.sputn(s.data(), s.size()); - } - - void put_data(const char* p, std::size_t data_size) - { - _dev.sputn(p, data_size); - } - - void put_str(const std::string& s) - { - _dev.sputn(s.data(), s.size()); - } - - void put_fast_oss(const fast_oss& oss) - { - auto& buffer = oss.buf(); - _dev.sputn(buffer.data(), buffer.size()); - } - - -private: - stack_devicebuf _dev; -}; -} -} diff --git a/include/spdlog/details/file_helper.h b/include/spdlog/details/file_helper.h index db2c9da2..ac0ef948 100644 --- a/include/spdlog/details/file_helper.h +++ b/include/spdlog/details/file_helper.h @@ -52,7 +52,7 @@ public: explicit file_helper(bool auto_flush): _fd(nullptr), _auto_flush(auto_flush) - {}; + {}; file_helper(const file_helper&) = delete; file_helper& operator=(const file_helper&) = delete; @@ -99,14 +99,15 @@ public: void write(const log_msg& msg) { - auto& buf = msg.formatted.buf(); - size_t size = buf.size(); - if(std::fwrite(buf.data(), sizeof(char), size, _fd) != size) + + size_t size = msg.formatted.size(); + auto data = msg.formatted.data(); + if(std::fwrite(data, 1, size, _fd) != size) throw spdlog_ex("Failed writing to file " + _filename); if(_auto_flush) std::fflush(_fd); - + } const std::string& filename() const diff --git a/include/spdlog/details/format.cc b/include/spdlog/details/format.cc new file mode 100644 index 00000000..76ef84eb --- /dev/null +++ b/include/spdlog/details/format.cc @@ -0,0 +1,1171 @@ +/* +Formatting library for C++ + +Copyright (c) 2012 - 2014, Victor Zverovich +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// Disable useless MSVC warnings. +#undef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#undef _SCL_SECURE_NO_WARNINGS +#define _SCL_SECURE_NO_WARNINGS + + +#include + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# define WIN32_LEAN_AND_MEAN +# ifdef __MINGW32__ +# include +# endif +# include +# undef ERROR +#endif + +using fmt::LongLong; +using fmt::ULongLong; +using fmt::internal::Arg; + +// Check if exceptions are disabled. +#if __GNUC__ && !__EXCEPTIONS +# define FMT_EXCEPTIONS 0 +#endif +#if _MSC_VER && !_HAS_EXCEPTIONS +# define FMT_EXCEPTIONS 0 +#endif +#ifndef FMT_EXCEPTIONS +# define FMT_EXCEPTIONS 1 +#endif + +#if FMT_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + +#ifndef FMT_THROW +# if FMT_EXCEPTIONS +# define FMT_THROW(x) throw x +# else +# define FMT_THROW(x) assert(false) +# endif +#endif + +#ifdef FMT_HEADER_ONLY +# define FMT_FUNC inline +#else +# define FMT_FUNC +#endif + +#if _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4127) // conditional expression is constant +#endif + +namespace { + +#ifndef _MSC_VER +# define FMT_SNPRINTF snprintf +#else // _MSC_VER +inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) { + va_list args; + va_start(args, format); + int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); + va_end(args); + return result; +} +# define FMT_SNPRINTF fmt_snprintf +#endif // _MSC_VER + +// Checks if a value fits in int - used to avoid warnings about comparing +// signed and unsigned integers. +template +struct IntChecker { + template + static bool fits_in_int(T value) { + unsigned max = INT_MAX; + return value <= max; + } +}; + +template <> +struct IntChecker { + template + static bool fits_in_int(T value) { + return value >= INT_MIN && value <= INT_MAX; + } +}; + +const char RESET_COLOR[] = "\x1b[0m"; + +typedef void(*FormatFunc)(fmt::Writer &, int, fmt::StringRef); + +// Portable thread-safe version of strerror. +// Sets buffer to point to a string describing the error code. +// This can be either a pointer to a string stored in buffer, +// or a pointer to some static immutable string. +// Returns one of the following values: +// 0 - success +// ERANGE - buffer is not large enough to store the error message +// other - failure +// Buffer should be at least of size 1. +int safe_strerror( + int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT(true) { + assert(buffer != 0 && buffer_size != 0); + int result = 0; +#ifdef _GNU_SOURCE + char *message = strerror_r(error_code, buffer, buffer_size); + // If the buffer is full then the message is probably truncated. + if (message == buffer && strlen(buffer) == buffer_size - 1) + result = ERANGE; + buffer = message; +#elif __MINGW32__ + errno = 0; + (void)buffer_size; + buffer = strerror(error_code); + result = errno; +#elif _WIN32 + result = strerror_s(buffer, buffer_size, error_code); + // If the buffer is full then the message is probably truncated. + if (result == 0 && std::strlen(buffer) == buffer_size - 1) + result = ERANGE; +#else + result = strerror_r(error_code, buffer, buffer_size); + if (result == -1) + result = errno; // glibc versions before 2.13 return result in errno. +#endif + return result; +} + +void format_error_code(fmt::Writer &out, int error_code, + fmt::StringRef message) FMT_NOEXCEPT(true) { + // Report error code making sure that the output fits into + // INLINE_BUFFER_SIZE to avoid dynamic memory allocation and potential + // bad_alloc. + out.clear(); + static const char SEP[] = ": "; + static const char ERROR[] = "error "; + fmt::internal::IntTraits::MainType ec_value = error_code; + // Subtract 2 to account for terminating null characters in SEP and ERROR. + std::size_t error_code_size = + sizeof(SEP) + sizeof(ERROR) + fmt::internal::count_digits(ec_value) - 2; + if (message.size() <= fmt::internal::INLINE_BUFFER_SIZE - error_code_size) + out << message << SEP; + out << ERROR << error_code; + assert(out.size() <= fmt::internal::INLINE_BUFFER_SIZE); +} + +void report_error(FormatFunc func, + int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) { + fmt::MemoryWriter full_message; + func(full_message, error_code, message); + // Use Writer::data instead of Writer::c_str to avoid potential memory + // allocation. + std::fwrite(full_message.data(), full_message.size(), 1, stderr); + std::fputc('\n', stderr); +} + +// IsZeroInt::visit(arg) returns true iff arg is a zero integer. +class IsZeroInt : public fmt::internal::ArgVisitor { +public: + template + bool visit_any_int(T value) { + return value == 0; + } +}; + +// Parses an unsigned integer advancing s to the end of the parsed input. +// This function assumes that the first character of s is a digit. +template +int parse_nonnegative_int(const Char *&s) { + assert('0' <= *s && *s <= '9'); + unsigned value = 0; + do { + unsigned new_value = value * 10 + (*s++ - '0'); + // Check if value wrapped around. + if (new_value < value) { + value = UINT_MAX; + break; + } + value = new_value; + } while ('0' <= *s && *s <= '9'); + if (value > INT_MAX) + FMT_THROW(fmt::FormatError("number is too big")); + return value; +} + +inline void require_numeric_argument(const Arg &arg, char spec) { + if (arg.type > Arg::LAST_NUMERIC_TYPE) { + std::string message = + fmt::format("format specifier '{}' requires numeric argument", spec); + FMT_THROW(fmt::FormatError(message)); + } +} + +template +void check_sign(const Char *&s, const Arg &arg) { + char sign = static_cast(*s); + require_numeric_argument(arg, sign); + if (arg.type == Arg::UINT || arg.type == Arg::ULONG_LONG) { + FMT_THROW(fmt::FormatError(fmt::format( + "format specifier '{}' requires signed argument", sign))); + } + ++s; +} + +// Checks if an argument is a valid printf width specifier and sets +// left alignment if it is negative. +class WidthHandler : public fmt::internal::ArgVisitor { +private: + fmt::FormatSpec &spec_; + +public: + explicit WidthHandler(fmt::FormatSpec &spec) : spec_(spec) {} + + unsigned visit_unhandled_arg() { + FMT_THROW(fmt::FormatError("width is not integer")); + return 0; + } + + template + unsigned visit_any_int(T value) { + typedef typename fmt::internal::IntTraits::MainType UnsignedType; + UnsignedType width = value; + if (fmt::internal::is_negative(value)) { + spec_.align_ = fmt::ALIGN_LEFT; + width = 0 - width; + } + if (width > INT_MAX) + FMT_THROW(fmt::FormatError("number is too big")); + return static_cast(width); + } +}; + +class PrecisionHandler : + public fmt::internal::ArgVisitor { +public: + unsigned visit_unhandled_arg() { + FMT_THROW(fmt::FormatError("precision is not integer")); + return 0; + } + + template + int visit_any_int(T value) { + if (!IntChecker::is_signed>::fits_in_int(value)) + FMT_THROW(fmt::FormatError("number is too big")); + return static_cast(value); + } +}; + +// Converts an integer argument to an integral type T for printf. +template +class ArgConverter : public fmt::internal::ArgVisitor, void> { +private: + fmt::internal::Arg &arg_; + wchar_t type_; + +public: + ArgConverter(fmt::internal::Arg &arg, wchar_t type) + : arg_(arg), type_(type) {} + + template + void visit_any_int(U value) { + bool is_signed = type_ == 'd' || type_ == 'i'; + using fmt::internal::Arg; + if (sizeof(T) <= sizeof(int)) { + // Extra casts are used to silence warnings. + if (is_signed) { + arg_.type = Arg::INT; + arg_.int_value = static_cast(static_cast(value)); + } + else { + arg_.type = Arg::UINT; + arg_.uint_value = static_cast( + static_cast::Type>(value)); + } + } + else { + if (is_signed) { + arg_.type = Arg::LONG_LONG; + arg_.long_long_value = + static_cast::Type>(value); + } + else { + arg_.type = Arg::ULONG_LONG; + arg_.ulong_long_value = + static_cast::Type>(value); + } + } + } +}; + +// Converts an integer argument to char for printf. +class CharConverter : public fmt::internal::ArgVisitor { +private: + fmt::internal::Arg &arg_; + +public: + explicit CharConverter(fmt::internal::Arg &arg) : arg_(arg) {} + + template + void visit_any_int(T value) { + arg_.type = Arg::CHAR; + arg_.int_value = static_cast(value); + } +}; + +// This function template is used to prevent compile errors when handling +// incompatible string arguments, e.g. handling a wide string in a narrow +// string formatter. +template +Arg::StringValue ignore_incompatible_str(Arg::StringValue); + +template <> +inline Arg::StringValue ignore_incompatible_str( + Arg::StringValue) { + return Arg::StringValue(); +} + +template <> +inline Arg::StringValue ignore_incompatible_str( + Arg::StringValue s) { + return s; +} +} // namespace + +FMT_FUNC void fmt::SystemError::init( + int error_code, StringRef format_str, ArgList args) { + error_code_ = error_code; + MemoryWriter w; + internal::format_system_error(w, error_code, format(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(w.str()); +} + +template +int fmt::internal::CharTraits::format_float( + char *buffer, std::size_t size, const char *format, + unsigned width, int precision, T value) { + if (width == 0) { + return precision < 0 ? + FMT_SNPRINTF(buffer, size, format, value) : + FMT_SNPRINTF(buffer, size, format, precision, value); + } + return precision < 0 ? + FMT_SNPRINTF(buffer, size, format, width, value) : + FMT_SNPRINTF(buffer, size, format, width, precision, value); +} + +template +int fmt::internal::CharTraits::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, + unsigned width, int precision, T value) { + if (width == 0) { + return precision < 0 ? + swprintf(buffer, size, format, value) : + swprintf(buffer, size, format, precision, value); + } + return precision < 0 ? + swprintf(buffer, size, format, width, value) : + swprintf(buffer, size, format, width, precision, value); +} + +template +const char fmt::internal::BasicData::DIGITS[] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + +#define FMT_POWERS_OF_10(factor) \ + factor * 10, \ + factor * 100, \ + factor * 1000, \ + factor * 10000, \ + factor * 100000, \ + factor * 1000000, \ + factor * 10000000, \ + factor * 100000000, \ + factor * 1000000000 + +template +const uint32_t fmt::internal::BasicData::POWERS_OF_10_32[] = { + 0, FMT_POWERS_OF_10(1) +}; + +template +const uint64_t fmt::internal::BasicData::POWERS_OF_10_64[] = { + 0, + FMT_POWERS_OF_10(1), + FMT_POWERS_OF_10(ULongLong(1000000000)), + // Multiply several constants instead of using a single long long constant + // to avoid warnings about C++98 not supporting long long. + ULongLong(1000000000) * ULongLong(1000000000) * 10 +}; + +FMT_FUNC void fmt::internal::report_unknown_type(char code, const char *type) { + if (std::isprint(static_cast(code))) { + FMT_THROW(fmt::FormatError( + fmt::format("unknown format code '{}' for {}", code, type))); + } + FMT_THROW(fmt::FormatError( + fmt::format("unknown format code '\\x{:02x}' for {}", + static_cast(code), type))); +} + +#ifdef _WIN32 + +fmt::internal::UTF8ToUTF16::UTF8ToUTF16(fmt::StringRef s) { + int length = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, s.c_str(), -1, 0, 0); + static const char ERROR[] = "cannot convert string from UTF-8 to UTF-16"; + if (length == 0) + FMT_THROW(WindowsError(GetLastError(), ERROR)); + buffer_.resize(length); + length = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, s.c_str(), -1, &buffer_[0], length); + if (length == 0) + FMT_THROW(WindowsError(GetLastError(), ERROR)); +} + +fmt::internal::UTF16ToUTF8::UTF16ToUTF8(fmt::WStringRef s) { + if (int error_code = convert(s)) { + FMT_THROW(WindowsError(error_code, + "cannot convert string from UTF-16 to UTF-8")); + } +} + +int fmt::internal::UTF16ToUTF8::convert(fmt::WStringRef s) { + int length = WideCharToMultiByte(CP_UTF8, 0, s.c_str(), -1, 0, 0, 0, 0); + if (length == 0) + return GetLastError(); + buffer_.resize(length); + length = WideCharToMultiByte( + CP_UTF8, 0, s.c_str(), -1, &buffer_[0], length, 0, 0); + if (length == 0) + return GetLastError(); + return 0; +} + +void fmt::WindowsError::init( + int error_code, StringRef format_str, ArgList args) { + error_code_ = error_code; + MemoryWriter w; + internal::format_windows_error(w, error_code, format(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(w.str()); +} + +#endif + +FMT_FUNC void fmt::internal::format_system_error( + fmt::Writer &out, int error_code, + fmt::StringRef message) FMT_NOEXCEPT(true) { + FMT_TRY { + MemoryBuffer buffer; + buffer.resize(INLINE_BUFFER_SIZE); + for (;;) { + char *system_message = &buffer[0]; + int result = safe_strerror(error_code, system_message, buffer.size()); + if (result == 0) { + out << message << ": " << system_message; + return; + } + if (result != ERANGE) + break; // Can't get error message, report error code instead. + buffer.resize(buffer.size() * 2); + } + } FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +#ifdef _WIN32 +void fmt::internal::format_windows_error( + fmt::Writer &out, int error_code, + fmt::StringRef message) FMT_NOEXCEPT(true) { + class String { + private: + LPWSTR str_; + + public: + String() : str_() {} + ~String() { + LocalFree(str_); + } + LPWSTR *ptr() { + return &str_; + } + LPCWSTR c_str() const { + return str_; + } + }; + FMT_TRY { + String system_message; + if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, + error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(system_message.ptr()), 0, 0)) { + UTF16ToUTF8 utf8_message; + if (utf8_message.convert(system_message.c_str()) == ERROR_SUCCESS) { + out << message << ": " << utf8_message; + return; + } + } + } FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} +#endif + +// An argument formatter. +template +class fmt::internal::ArgFormatter : + public fmt::internal::ArgVisitor, void> { +private: + fmt::BasicFormatter &formatter_; + fmt::BasicWriter &writer_; + fmt::FormatSpec &spec_; + const Char *format_; + +public: + ArgFormatter( + fmt::BasicFormatter &f, fmt::FormatSpec &s, const Char *fmt) + : formatter_(f), writer_(f.writer()), spec_(s), format_(fmt) {} + + template + void visit_any_int(T value) { + writer_.write_int(value, spec_); + } + + template + void visit_any_double(T value) { + writer_.write_double(value, spec_); + } + + void visit_char(int value) { + if (spec_.type_ && spec_.type_ != 'c') { + spec_.flags_ |= CHAR_FLAG; + writer_.write_int(value, spec_); + return; + } + if (spec_.align_ == ALIGN_NUMERIC || spec_.flags_ != 0) + FMT_THROW(FormatError("invalid format specifier for char")); + typedef typename fmt::BasicWriter::CharPtr CharPtr; + CharPtr out = CharPtr(); + if (spec_.width_ > 1) { + Char fill = static_cast(spec_.fill()); + out = writer_.grow_buffer(spec_.width_); + if (spec_.align_ == fmt::ALIGN_RIGHT) { + std::fill_n(out, spec_.width_ - 1, fill); + out += spec_.width_ - 1; + } + else if (spec_.align_ == fmt::ALIGN_CENTER) { + out = writer_.fill_padding(out, spec_.width_, 1, fill); + } + else { + std::fill_n(out + 1, spec_.width_ - 1, fill); + } + } + else { + out = writer_.grow_buffer(1); + } + *out = static_cast(value); + } + + void visit_string(Arg::StringValue value) { + writer_.write_str(value, spec_); + } + void visit_wstring(Arg::StringValue value) { + writer_.write_str(ignore_incompatible_str(value), spec_); + } + + void visit_pointer(const void *value) { + if (spec_.type_ && spec_.type_ != 'p') + fmt::internal::report_unknown_type(spec_.type_, "pointer"); + spec_.flags_ = fmt::HASH_FLAG; + spec_.type_ = 'x'; + writer_.write_int(reinterpret_cast(value), spec_); + } + + void visit_custom(Arg::CustomValue c) { + c.format(&formatter_, c.value, &format_); + } +}; + +template +template +void fmt::BasicWriter::write_str( + const Arg::StringValue &str, const FormatSpec &spec) { + // Check if StrChar is convertible to Char. + internal::CharTraits::convert(StrChar()); + if (spec.type_ && spec.type_ != 's') + internal::report_unknown_type(spec.type_, "string"); + const StrChar *s = str.value; + std::size_t size = str.size; + if (size == 0) { + if (!s) + FMT_THROW(FormatError("string pointer is null")); + if (*s) + size = std::char_traits::length(s); + } + write_str(s, size, spec); +} + +template +inline Arg fmt::BasicFormatter::parse_arg_index(const Char *&s) { + const char *error = 0; + Arg arg = *s < '0' || *s > '9' ? + next_arg(error) : get_arg(parse_nonnegative_int(s), error); + if (error) { + FMT_THROW(FormatError( + *s != '}' && *s != ':' ? "invalid format string" : error)); + } + return arg; +} + +FMT_FUNC Arg fmt::internal::FormatterBase::do_get_arg( + unsigned arg_index, const char *&error) { + Arg arg = args_[arg_index]; + if (arg.type == Arg::NONE) + error = "argument index out of range"; + return arg; +} + +inline Arg fmt::internal::FormatterBase::next_arg(const char *&error) { + if (next_arg_index_ >= 0) + return do_get_arg(next_arg_index_++, error); + error = "cannot switch from manual to automatic argument indexing"; + return Arg(); +} + +inline Arg fmt::internal::FormatterBase::get_arg( + unsigned arg_index, const char *&error) { + if (next_arg_index_ <= 0) { + next_arg_index_ = -1; + return do_get_arg(arg_index, error); + } + error = "cannot switch from automatic to manual argument indexing"; + return Arg(); +} + +template +void fmt::internal::PrintfFormatter::parse_flags( + FormatSpec &spec, const Char *&s) { + for (;;) { + switch (*s++) { + case '-': + spec.align_ = ALIGN_LEFT; + break; + case '+': + spec.flags_ |= SIGN_FLAG | PLUS_FLAG; + break; + case '0': + spec.fill_ = '0'; + break; + case ' ': + spec.flags_ |= SIGN_FLAG; + break; + case '#': + spec.flags_ |= HASH_FLAG; + break; + default: + --s; + return; + } + } +} + +template +Arg fmt::internal::PrintfFormatter::get_arg( + const Char *s, unsigned arg_index) { + const char *error = 0; + Arg arg = arg_index == UINT_MAX ? + next_arg(error) : FormatterBase::get_arg(arg_index - 1, error); + if (error) + FMT_THROW(FormatError(!*s ? "invalid format string" : error)); + return arg; +} + +template +unsigned fmt::internal::PrintfFormatter::parse_header( + const Char *&s, FormatSpec &spec) { + unsigned arg_index = UINT_MAX; + Char c = *s; + if (c >= '0' && c <= '9') { + // Parse an argument index (if followed by '$') or a width possibly + // preceded with '0' flag(s). + unsigned value = parse_nonnegative_int(s); + if (*s == '$') { // value is an argument index + ++s; + arg_index = value; + } + else { + if (c == '0') + spec.fill_ = '0'; + if (value != 0) { + // Nonzero value means that we parsed width and don't need to + // parse it or flags again, so return now. + spec.width_ = value; + return arg_index; + } + } + } + parse_flags(spec, s); + // Parse width. + if (*s >= '0' && *s <= '9') { + spec.width_ = parse_nonnegative_int(s); + } + else if (*s == '*') { + ++s; + spec.width_ = WidthHandler(spec).visit(get_arg(s)); + } + return arg_index; +} + +template +void fmt::internal::PrintfFormatter::format( + BasicWriter &writer, BasicStringRef format, + const ArgList &args) { + const Char *start = format.c_str(); + set_args(args); + const Char *s = start; + while (*s) { + Char c = *s++; + if (c != '%') continue; + if (*s == c) { + write(writer, start, s); + start = ++s; + continue; + } + write(writer, start, s - 1); + + FormatSpec spec; + spec.align_ = ALIGN_RIGHT; + + // Parse argument index, flags and width. + unsigned arg_index = parse_header(s, spec); + + // Parse precision. + if (*s == '.') { + ++s; + if ('0' <= *s && *s <= '9') { + spec.precision_ = parse_nonnegative_int(s); + } + else if (*s == '*') { + ++s; + spec.precision_ = PrecisionHandler().visit(get_arg(s)); + } + } + + Arg arg = get_arg(s, arg_index); + if (spec.flag(HASH_FLAG) && IsZeroInt().visit(arg)) + spec.flags_ &= ~HASH_FLAG; + if (spec.fill_ == '0') { + if (arg.type <= Arg::LAST_NUMERIC_TYPE) + spec.align_ = ALIGN_NUMERIC; + else + spec.fill_ = ' '; // Ignore '0' flag for non-numeric types. + } + + // Parse length and convert the argument to the required type. + switch (*s++) { + case 'h': + if (*s == 'h') + ArgConverter(arg, *++s).visit(arg); + else + ArgConverter(arg, *s).visit(arg); + break; + case 'l': + if (*s == 'l') + ArgConverter(arg, *++s).visit(arg); + else + ArgConverter(arg, *s).visit(arg); + break; + case 'j': + ArgConverter(arg, *s).visit(arg); + break; + case 'z': + ArgConverter(arg, *s).visit(arg); + break; + case 't': + ArgConverter(arg, *s).visit(arg); + break; + case 'L': + // printf produces garbage when 'L' is omitted for long double, no + // need to do the same. + break; + default: + --s; + ArgConverter(arg, *s).visit(arg); + } + + // Parse type. + if (!*s) + FMT_THROW(FormatError("invalid format string")); + spec.type_ = static_cast(*s++); + if (arg.type <= Arg::LAST_INTEGER_TYPE) { + // Normalize type. + switch (spec.type_) { + case 'i': + case 'u': + spec.type_ = 'd'; + break; + case 'c': + // TODO: handle wchar_t + CharConverter(arg).visit(arg); + break; + } + } + + start = s; + + // Format argument. + switch (arg.type) { + case Arg::INT: + writer.write_int(arg.int_value, spec); + break; + case Arg::UINT: + writer.write_int(arg.uint_value, spec); + break; + case Arg::LONG_LONG: + writer.write_int(arg.long_long_value, spec); + break; + case Arg::ULONG_LONG: + writer.write_int(arg.ulong_long_value, spec); + break; + case Arg::CHAR: { + if (spec.type_ && spec.type_ != 'c') + writer.write_int(arg.int_value, spec); + typedef typename BasicWriter::CharPtr CharPtr; + CharPtr out = CharPtr(); + if (spec.width_ > 1) { + Char fill = ' '; + out = writer.grow_buffer(spec.width_); + if (spec.align_ != ALIGN_LEFT) { + std::fill_n(out, spec.width_ - 1, fill); + out += spec.width_ - 1; + } + else { + std::fill_n(out + 1, spec.width_ - 1, fill); + } + } + else { + out = writer.grow_buffer(1); + } + *out = static_cast(arg.int_value); + break; + } + case Arg::DOUBLE: + writer.write_double(arg.double_value, spec); + break; + case Arg::LONG_DOUBLE: + writer.write_double(arg.long_double_value, spec); + break; + case Arg::CSTRING: + arg.string.size = 0; + writer.write_str(arg.string, spec); + break; + case Arg::STRING: + writer.write_str(arg.string, spec); + break; + case Arg::WSTRING: + writer.write_str(ignore_incompatible_str(arg.wstring), spec); + break; + case Arg::POINTER: + if (spec.type_ && spec.type_ != 'p') + internal::report_unknown_type(spec.type_, "pointer"); + spec.flags_ = HASH_FLAG; + spec.type_ = 'x'; + writer.write_int(reinterpret_cast(arg.pointer), spec); + break; + case Arg::CUSTOM: { + if (spec.type_) + internal::report_unknown_type(spec.type_, "object"); + const void *s = "s"; + arg.custom.format(&writer, arg.custom.value, &s); + break; + } + default: + assert(false); + break; + } + } + write(writer, start, s); +} + +template +const Char *fmt::BasicFormatter::format( + const Char *&format_str, const Arg &arg) { + const Char *s = format_str; + FormatSpec spec; + if (*s == ':') { + if (arg.type == Arg::CUSTOM) { + arg.custom.format(this, arg.custom.value, &s); + return s; + } + ++s; + // Parse fill and alignment. + if (Char c = *s) { + const Char *p = s + 1; + spec.align_ = ALIGN_DEFAULT; + do { + switch (*p) { + case '<': + spec.align_ = ALIGN_LEFT; + break; + case '>': + spec.align_ = ALIGN_RIGHT; + break; + case '=': + spec.align_ = ALIGN_NUMERIC; + break; + case '^': + spec.align_ = ALIGN_CENTER; + break; + } + if (spec.align_ != ALIGN_DEFAULT) { + if (p != s) { + if (c == '}') break; + if (c == '{') + FMT_THROW(FormatError("invalid fill character '{'")); + s += 2; + spec.fill_ = c; + } + else ++s; + if (spec.align_ == ALIGN_NUMERIC) + require_numeric_argument(arg, '='); + break; + } + } while (--p >= s); + } + + // Parse sign. + switch (*s) { + case '+': + check_sign(s, arg); + spec.flags_ |= SIGN_FLAG | PLUS_FLAG; + break; + case '-': + check_sign(s, arg); + spec.flags_ |= MINUS_FLAG; + break; + case ' ': + check_sign(s, arg); + spec.flags_ |= SIGN_FLAG; + break; + } + + if (*s == '#') { + require_numeric_argument(arg, '#'); + spec.flags_ |= HASH_FLAG; + ++s; + } + + // Parse width and zero flag. + if ('0' <= *s && *s <= '9') { + if (*s == '0') { + require_numeric_argument(arg, '0'); + spec.align_ = ALIGN_NUMERIC; + spec.fill_ = '0'; + } + // Zero may be parsed again as a part of the width, but it is simpler + // and more efficient than checking if the next char is a digit. + spec.width_ = parse_nonnegative_int(s); + } + + // Parse precision. + if (*s == '.') { + ++s; + spec.precision_ = 0; + if ('0' <= *s && *s <= '9') { + spec.precision_ = parse_nonnegative_int(s); + } + else if (*s == '{') { + ++s; + const Arg &precision_arg = parse_arg_index(s); + if (*s++ != '}') + FMT_THROW(FormatError("invalid format string")); + ULongLong value = 0; + switch (precision_arg.type) { + case Arg::INT: + if (precision_arg.int_value < 0) + FMT_THROW(FormatError("negative precision")); + value = precision_arg.int_value; + break; + case Arg::UINT: + value = precision_arg.uint_value; + break; + case Arg::LONG_LONG: + if (precision_arg.long_long_value < 0) + FMT_THROW(FormatError("negative precision")); + value = precision_arg.long_long_value; + break; + case Arg::ULONG_LONG: + value = precision_arg.ulong_long_value; + break; + default: + FMT_THROW(FormatError("precision is not integer")); + } + if (value > INT_MAX) + FMT_THROW(FormatError("number is too big")); + spec.precision_ = static_cast(value); + } + else { + FMT_THROW(FormatError("missing precision specifier")); + } + if (arg.type != Arg::DOUBLE && arg.type != Arg::LONG_DOUBLE) { + FMT_THROW(FormatError( + "precision specifier requires floating-point argument")); + } + } + + // Parse type. + if (*s != '}' && *s) + spec.type_ = static_cast(*s++); + } + + if (*s++ != '}') + FMT_THROW(FormatError("missing '}' in format string")); + start_ = s; + + // Format argument. + internal::ArgFormatter(*this, spec, s - 1).visit(arg); + return s; +} + +template +void fmt::BasicFormatter::format( + BasicStringRef format_str, const ArgList &args) { + const Char *s = start_ = format_str.c_str(); + set_args(args); + while (*s) { + Char c = *s++; + if (c != '{' && c != '}') continue; + if (*s == c) { + write(writer_, start_, s); + start_ = ++s; + continue; + } + if (c == '}') + FMT_THROW(FormatError("unmatched '}' in format string")); + write(writer_, start_, s - 1); + Arg arg = parse_arg_index(s); + s = format(s, arg); + } + write(writer_, start_, s); +} + +FMT_FUNC void fmt::report_system_error( + int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) { + report_error(internal::format_system_error, error_code, message); +} + +#ifdef _WIN32 +void fmt::report_windows_error( + int error_code, fmt::StringRef message) FMT_NOEXCEPT(true) { + report_error(internal::format_windows_error, error_code, message); +} +#endif + +FMT_FUNC void fmt::print(std::FILE *f, StringRef format_str, ArgList args) { + MemoryWriter w; + w.write(format_str, args); + std::fwrite(w.data(), 1, w.size(), f); +} + +FMT_FUNC void fmt::print(StringRef format_str, ArgList args) { + print(stdout, format_str, args); +} + +FMT_FUNC void fmt::print(std::ostream &os, StringRef format_str, ArgList args) { + MemoryWriter w; + w.write(format_str, args); + os.write(w.data(), w.size()); +} + +FMT_FUNC void fmt::print_colored(Color c, StringRef format, ArgList args) { + char escape[] = "\x1b[30m"; + escape[3] = '0' + static_cast(c); + std::fputs(escape, stdout); + print(format, args); + std::fputs(RESET_COLOR, stdout); +} + +FMT_FUNC int fmt::fprintf(std::FILE *f, StringRef format, ArgList args) { + MemoryWriter w; + printf(w, format, args); + std::size_t size = w.size(); + return std::fwrite(w.data(), 1, size, f) < size ? -1 : static_cast(size); +} + +// Explicit instantiations for char. + +template const char *fmt::BasicFormatter::format( + const char *&format_str, const fmt::internal::Arg &arg); + +template void fmt::BasicFormatter::format( + BasicStringRef format, const ArgList &args); + +template void fmt::internal::PrintfFormatter::format( + BasicWriter &writer, BasicStringRef format, const ArgList &args); + +template int fmt::internal::CharTraits::format_float( + char *buffer, std::size_t size, const char *format, + unsigned width, int precision, double value); + +template int fmt::internal::CharTraits::format_float( + char *buffer, std::size_t size, const char *format, + unsigned width, int precision, long double value); + +// Explicit instantiations for wchar_t. + +template const wchar_t *fmt::BasicFormatter::format( + const wchar_t *&format_str, const fmt::internal::Arg &arg); + +template void fmt::BasicFormatter::format( + BasicStringRef format, const ArgList &args); + +template void fmt::internal::PrintfFormatter::format( + BasicWriter &writer, BasicStringRef format, + const ArgList &args); + +template int fmt::internal::CharTraits::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, + unsigned width, int precision, double value); + +template int fmt::internal::CharTraits::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, + unsigned width, int precision, long double value); + +#if _MSC_VER +# pragma warning(pop) +#endif \ No newline at end of file diff --git a/include/spdlog/details/format.h b/include/spdlog/details/format.h new file mode 100644 index 00000000..82f65058 --- /dev/null +++ b/include/spdlog/details/format.h @@ -0,0 +1,2875 @@ +/* +Formatting library for C++ + +Copyright (c) 2012 - 2014, Victor Zverovich +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FMT_FORMAT_H_ +#define FMT_FORMAT_H_ + +#include + +#include +#include +#include // for std::ptrdiff_t +#include +#include +#include +#include +#include +#include + +#if _SECURE_SCL +# include +#endif + +#ifdef __GNUC__ +// Ignore shadow warnings +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wshadow" + +# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +# define FMT_GCC_EXTENSION __extension__ +// Disable warning about "long long" which is sometimes reported even +// when using __extension__. +# if FMT_GCC_VERSION >= 406 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wlong-long" +# endif +#else +# define FMT_GCC_EXTENSION +#endif + +#ifdef __GNUC_LIBSTD__ +# define FMT_GNUC_LIBSTD_VERSION (__GNUC_LIBSTD__ * 100 + __GNUC_LIBSTD_MINOR__) +#endif + +#ifdef __has_feature +# define FMT_HAS_FEATURE(x) __has_feature(x) +#else +# define FMT_HAS_FEATURE(x) 0 +#endif + +#ifdef __has_builtin +# define FMT_HAS_BUILTIN(x) __has_builtin(x) +#else +# define FMT_HAS_BUILTIN(x) 0 +#endif + +#ifndef FMT_USE_VARIADIC_TEMPLATES +// Variadic templates are available in GCC since version 4.4 +// (http://gcc.gnu.org/projects/cxx0x.html) and in Visual C++ +// since version 2013. +# define FMT_USE_VARIADIC_TEMPLATES \ + (FMT_HAS_FEATURE(cxx_variadic_templates) || \ + (FMT_GCC_VERSION >= 404 && __cplusplus >= 201103) || _MSC_VER >= 1800) +#endif + +#ifndef FMT_USE_RVALUE_REFERENCES +// Don't use rvalue references when compiling with clang and an old libstdc++ +// as the latter doesn't provide std::move. +# if defined(FMT_GNUC_LIBSTD_VERSION) && FMT_GNUC_LIBSTD_VERSION <= 402 +# define FMT_USE_RVALUE_REFERENCES 0 +# else +# define FMT_USE_RVALUE_REFERENCES \ + (FMT_HAS_FEATURE(cxx_rvalue_references) || \ + (FMT_GCC_VERSION >= 403 && __cplusplus >= 201103) || _MSC_VER >= 1600) +# endif +#endif + +#if FMT_USE_RVALUE_REFERENCES +# include // for std::move +#endif + +// Define FMT_USE_NOEXCEPT to make C++ Format use noexcept (C++11 feature). +#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ + (FMT_GCC_VERSION >= 408 && __cplusplus >= 201103) +# define FMT_NOEXCEPT(expr) noexcept(expr) +#else +# define FMT_NOEXCEPT(expr) +#endif + +// A macro to disallow the copy constructor and operator= functions +// This should be used in the private: declarations for a class +#define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +namespace fmt +{ + +// Fix the warning about long long on older versions of GCC +// that don't support the diagnostic pragma. +FMT_GCC_EXTENSION typedef long long LongLong; +FMT_GCC_EXTENSION typedef unsigned long long ULongLong; + +#if FMT_USE_RVALUE_REFERENCES +using std::move; +#endif + +template +class BasicWriter; + +typedef BasicWriter Writer; +typedef BasicWriter WWriter; + +template +class BasicFormatter; + +template +void format(BasicFormatter &f, const Char *&format_str, const T &value); + +/** +\rst +A string reference. It can be constructed from a C string or +``std::string``. + +You can use one of the following typedefs for common character types: + ++------------+-------------------------+ +| Type | Definition | ++============+=========================+ +| StringRef | BasicStringRef | ++------------+-------------------------+ +| WStringRef | BasicStringRef | ++------------+-------------------------+ + +This class is most useful as a parameter type to allow passing +different types of strings to a function, for example:: + +template +std::string format(StringRef format, const Args & ... args); + +format("{}", 42); +format(std::string("{}"), 42); +\endrst +*/ +template +class BasicStringRef +{ +private: + const Char *data_; + std::size_t size_; + +public: + /** + Constructs a string reference object from a C string and a size. + */ + BasicStringRef(const Char *s, std::size_t size) : data_(s), size_(size) {} + + /** + Constructs a string reference object from a C string computing + the size with ``std::char_traits::length``. + */ + BasicStringRef(const Char *s) + : data_(s), size_(std::char_traits::length(s)) {} + + /** + Constructs a string reference from an `std::string` object. + */ + BasicStringRef(const std::basic_string &s) + : data_(s.c_str()), size_(s.size()) {} + + /** + Converts a string reference to an `std::string` object. + */ + operator std::basic_string() const + { + return std::basic_string(data_, size()); + } + + /** + Returns the pointer to a C string. + */ + const Char *c_str() const + { + return data_; + } + + /** + Returns the string size. + */ + std::size_t size() const + { + return size_; + } + + friend bool operator==(BasicStringRef lhs, BasicStringRef rhs) + { + return lhs.data_ == rhs.data_; + } + friend bool operator!=(BasicStringRef lhs, BasicStringRef rhs) + { + return lhs.data_ != rhs.data_; + } +}; + +typedef BasicStringRef StringRef; +typedef BasicStringRef WStringRef; + +/** +A formatting error such as invalid format string. +*/ +class FormatError : public std::runtime_error +{ +public: + explicit FormatError(StringRef message) + : std::runtime_error(message.c_str()) {} +}; + +namespace internal +{ + +// The number of characters to store in the MemoryBuffer object itself +// to avoid dynamic memory allocation. +enum { INLINE_BUFFER_SIZE = 500 }; + +#if _SECURE_SCL +// Use checked iterator to avoid warnings on MSVC. +template +inline stdext::checked_array_iterator make_ptr(T *ptr, std::size_t size) +{ + return stdext::checked_array_iterator(ptr, size); +} +#else +template +inline T *make_ptr(T *ptr, std::size_t) +{ + return ptr; +} +#endif + +// A buffer for POD types. It supports a subset of std::vector's operations. +template +class Buffer +{ +private: + FMT_DISALLOW_COPY_AND_ASSIGN(Buffer); + +protected: + T *ptr_; + std::size_t size_; + std::size_t capacity_; + + Buffer(T *ptr = 0, std::size_t capacity = 0) + : ptr_(ptr), size_(0), capacity_(capacity) {} + + virtual void grow(std::size_t size) = 0; + +public: + virtual ~Buffer() {} + + // Returns the size of this buffer. + std::size_t size() const + { + return size_; + } + + // Returns the capacity of this buffer. + std::size_t capacity() const + { + return capacity_; + } + + // Resizes the buffer. If T is a POD type new elements are not initialized. + void resize(std::size_t new_size) + { + if (new_size > capacity_) + grow(new_size); + size_ = new_size; + } + + // Reserves space to store at least capacity elements. + void reserve(std::size_t capacity) + { + if (capacity > capacity_) + grow(capacity); + } + + void clear() FMT_NOEXCEPT(true) + { + size_ = 0; + } + + void push_back(const T &value) + { + if (size_ == capacity_) + grow(size_ + 1); + ptr_[size_++] = value; + } + + // Appends data to the end of the buffer. + void append(const T *begin, const T *end); + + T &operator[](std::size_t index) + { + return ptr_[index]; + } + const T &operator[](std::size_t index) const + { + return ptr_[index]; + } +}; + +template +void Buffer::append(const T *begin, const T *end) +{ + std::ptrdiff_t num_elements = end - begin; + if (size_ + num_elements > capacity_) + grow(size_ + num_elements); + std::copy(begin, end, make_ptr(ptr_, capacity_) + size_); + size_ += num_elements; +} + +// A memory buffer for POD types with the first SIZE elements stored in +// the object itself. +template > +class MemoryBuffer : private Allocator, public Buffer +{ +private: + T data_[SIZE]; + + // Free memory allocated by the buffer. + void free() + { + if (this->ptr_ != data_) this->deallocate(this->ptr_, this->capacity_); + } + +protected: + void grow(std::size_t size); + +public: + explicit MemoryBuffer(const Allocator &alloc = Allocator()) + : Allocator(alloc), Buffer(data_, SIZE) {} + ~MemoryBuffer() + { + free(); + } + +#if FMT_USE_RVALUE_REFERENCES +private: + // Move data from other to this buffer. + void move(MemoryBuffer &other) + { + Allocator &this_alloc = *this, &other_alloc = other; + this_alloc = std::move(other_alloc); + this->size_ = other.size_; + this->capacity_ = other.capacity_; + if (other.ptr_ == other.data_) + { + this->ptr_ = data_; + std::copy(other.data_, + other.data_ + this->size_, make_ptr(data_, this->capacity_)); + } + else + { + this->ptr_ = other.ptr_; + // Set pointer to the inline array so that delete is not called + // when freeing. + other.ptr_ = other.data_; + } + } + +public: + MemoryBuffer(MemoryBuffer &&other) + { + move(other); + } + + MemoryBuffer &operator=(MemoryBuffer &&other) + { + assert(this != &other); + free(); + move(other); + return *this; + } +#endif + + // Returns a copy of the allocator associated with this buffer. + Allocator get_allocator() const + { + return *this; + } +}; + +template +void MemoryBuffer::grow(std::size_t size) +{ + std::size_t new_capacity = + (std::max)(size, this->capacity_ + this->capacity_ / 2); + T *new_ptr = this->allocate(new_capacity); + // The following code doesn't throw, so the raw pointer above doesn't leak. + std::copy(this->ptr_, + this->ptr_ + this->size_, make_ptr(new_ptr, new_capacity)); + std::size_t old_capacity = this->capacity_; + T *old_ptr = this->ptr_; + this->capacity_ = new_capacity; + this->ptr_ = new_ptr; + // deallocate may throw (at least in principle), but it doesn't matter since + // the buffer already uses the new storage and will deallocate it in case + // of exception. + if (old_ptr != data_) + this->deallocate(old_ptr, old_capacity); +} + +#ifndef _MSC_VER +// Portable version of signbit. +inline int getsign(double x) +{ + // When compiled in C++11 mode signbit is no longer a macro but a function + // defined in namespace std and the macro is undefined. +# ifdef signbit + return signbit(x); +# else + return std::signbit(x); +# endif +} + +// Portable version of isinf. +# ifdef isinf +inline int isinfinity(double x) +{ + return isinf(x); +} +inline int isinfinity(long double x) +{ + return isinf(x); +} +# else +inline int isinfinity(double x) +{ + return std::isinf(x); +} +inline int isinfinity(long double x) +{ + return std::isinf(x); +} +# endif +#else +inline int getsign(double value) +{ + if (value < 0) return 1; + if (value == value) return 0; + int dec = 0, sign = 0; + char buffer[2]; // The buffer size must be >= 2 or _ecvt_s will fail. + _ecvt_s(buffer, sizeof(buffer), value, 0, &dec, &sign); + return sign; +} +inline int isinfinity(double x) +{ + return !_finite(x); +} +#endif + +template +struct IsLongDouble +{ + enum { VALUE = 0 }; +}; + +template <> +struct IsLongDouble +{ + enum { VALUE = 1 }; +}; + +template +class BasicCharTraits +{ +public: +#if _SECURE_SCL + typedef stdext::checked_array_iterator CharPtr; +#else + typedef Char *CharPtr; +#endif +}; + +template +class CharTraits; + +template <> +class CharTraits : public BasicCharTraits +{ +private: + // Conversion from wchar_t to char is not allowed. + static char convert(wchar_t); + +public: + typedef const wchar_t *UnsupportedStrType; + + static char convert(char value) + { + return value; + } + + // Formats a floating-point number. + template + static int format_float(char *buffer, std::size_t size, + const char *format, unsigned width, int precision, T value); +}; + +template <> +class CharTraits : public BasicCharTraits +{ +public: + typedef const char *UnsupportedStrType; + + static wchar_t convert(char value) + { + return value; + } + static wchar_t convert(wchar_t value) + { + return value; + } + + template + static int format_float(wchar_t *buffer, std::size_t size, + const wchar_t *format, unsigned width, int precision, T value); +}; + +// Checks if a number is negative - used to avoid warnings. +template +struct SignChecker +{ + template + static bool is_negative(T value) + { + return value < 0; + } +}; + +template <> +struct SignChecker +{ + template + static bool is_negative(T) + { + return false; + } +}; + +// Returns true if value is negative, false otherwise. +// Same as (value < 0) but doesn't produce warnings if T is an unsigned type. +template +inline bool is_negative(T value) +{ + return SignChecker::is_signed>::is_negative(value); +} + +// Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise. +template +struct TypeSelector +{ + typedef uint32_t Type; +}; + +template <> +struct TypeSelector +{ + typedef uint64_t Type; +}; + +template +struct IntTraits +{ + // Smallest of uint32_t and uint64_t that is large enough to represent + // all values of T. + typedef typename + TypeSelector::digits <= 32>::Type MainType; +}; + +// MakeUnsigned::Type gives an unsigned type corresponding to integer type T. +template +struct MakeUnsigned +{ + typedef T Type; +}; + +#define FMT_SPECIALIZE_MAKE_UNSIGNED(T, U) \ + template <> \ + struct MakeUnsigned { typedef U Type; } + +FMT_SPECIALIZE_MAKE_UNSIGNED(char, unsigned char); +FMT_SPECIALIZE_MAKE_UNSIGNED(signed char, unsigned char); +FMT_SPECIALIZE_MAKE_UNSIGNED(short, unsigned short); +FMT_SPECIALIZE_MAKE_UNSIGNED(int, unsigned); +FMT_SPECIALIZE_MAKE_UNSIGNED(long, unsigned long); +FMT_SPECIALIZE_MAKE_UNSIGNED(LongLong, ULongLong); + +void report_unknown_type(char code, const char *type); + +// Static data is placed in this class template to allow header-only +// configuration. +template +struct BasicData +{ + static const uint32_t POWERS_OF_10_32[]; + static const uint64_t POWERS_OF_10_64[]; + static const char DIGITS[]; +}; + +typedef BasicData<> Data; + +#if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clzll) +// Returns the number of decimal digits in n. Leading zeros are not counted +// except for n == 0 in which case count_digits returns 1. +inline unsigned count_digits(uint64_t n) +{ + // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 + // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits. + unsigned t = (64 - __builtin_clzll(n | 1)) * 1233 >> 12; + return t - (n < Data::POWERS_OF_10_64[t]) + 1; +} +# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clz) +// Optional version of count_digits for better performance on 32-bit platforms. +inline unsigned count_digits(uint32_t n) +{ + uint32_t t = (32 - __builtin_clz(n | 1)) * 1233 >> 12; + return t - (n < Data::POWERS_OF_10_32[t]) + 1; +} +# endif +#else +// Fallback version of count_digits used when __builtin_clz is not available. +inline unsigned count_digits(uint64_t n) +{ + unsigned count = 1; + for (;;) + { + // Integer division is slow so do it for a group of four digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + if (n < 10) return count; + if (n < 100) return count + 1; + if (n < 1000) return count + 2; + if (n < 10000) return count + 3; + n /= 10000u; + count += 4; + } +} +#endif + +// Formats a decimal unsigned integer value writing into buffer. +template +inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) +{ + --num_digits; + while (value >= 100) + { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + unsigned index = (value % 100) * 2; + value /= 100; + buffer[num_digits] = Data::DIGITS[index + 1]; + buffer[num_digits - 1] = Data::DIGITS[index]; + num_digits -= 2; + } + if (value < 10) + { + *buffer = static_cast('0' + value); + return; + } + unsigned index = static_cast(value * 2); + buffer[1] = Data::DIGITS[index + 1]; + buffer[0] = Data::DIGITS[index]; +} + +#ifdef _WIN32 +// A converter from UTF-8 to UTF-16. +// It is only provided for Windows since other systems support UTF-8 natively. +class UTF8ToUTF16 +{ +private: + MemoryBuffer buffer_; + +public: + explicit UTF8ToUTF16(StringRef s); + operator WStringRef() const + { + return WStringRef(&buffer_[0], size()); + } + size_t size() const + { + return buffer_.size() - 1; + } + const wchar_t *c_str() const + { + return &buffer_[0]; + } + std::wstring str() const + { + return std::wstring(&buffer_[0], size()); + } +}; + +// A converter from UTF-16 to UTF-8. +// It is only provided for Windows since other systems support UTF-8 natively. +class UTF16ToUTF8 +{ +private: + MemoryBuffer buffer_; + +public: + UTF16ToUTF8() {} + explicit UTF16ToUTF8(WStringRef s); + operator StringRef() const + { + return StringRef(&buffer_[0], size()); + } + size_t size() const + { + return buffer_.size() - 1; + } + const char *c_str() const + { + return &buffer_[0]; + } + std::string str() const + { + return std::string(&buffer_[0], size()); + } + + // Performs conversion returning a system error code instead of + // throwing exception on conversion error. This method may still throw + // in case of memory allocation error. + int convert(WStringRef s); +}; +#endif + +void format_system_error(fmt::Writer &out, int error_code, + fmt::StringRef message) FMT_NOEXCEPT(true); + +#ifdef _WIN32 +void format_windows_error(fmt::Writer &out, int error_code, + fmt::StringRef message) FMT_NOEXCEPT(true); +#endif + +// Computes max(Arg, 1) at compile time. It is used to avoid errors about +// allocating an array of 0 size. +template +struct NonZero +{ + enum { VALUE = Arg }; +}; + +template <> +struct NonZero<0> +{ + enum { VALUE = 1 }; +}; + +// The value of a formatting argument. It is a POD type to allow storage in +// internal::MemoryBuffer. +struct Value +{ + template + struct StringValue + { + const Char *value; + std::size_t size; + }; + + typedef void(*FormatFunc)( + void *formatter, const void *arg, void *format_str_ptr); + + struct CustomValue + { + const void *value; + FormatFunc format; + }; + + union + { + int int_value; + unsigned uint_value; + LongLong long_long_value; + ULongLong ulong_long_value; + double double_value; + long double long_double_value; + const void *pointer; + StringValue string; + StringValue sstring; + StringValue ustring; + StringValue wstring; + CustomValue custom; + }; +}; + +struct Arg : Value +{ + enum Type + { + NONE, + // Integer types should go first, + INT, UINT, LONG_LONG, ULONG_LONG, CHAR, LAST_INTEGER_TYPE = CHAR, + // followed by floating-point types. + DOUBLE, LONG_DOUBLE, LAST_NUMERIC_TYPE = LONG_DOUBLE, + CSTRING, STRING, WSTRING, POINTER, CUSTOM + }; + Type type; +}; + +// Makes a Value object from any type. +template +class MakeValue : public Value +{ +private: + // The following two methods are private to disallow formatting of + // arbitrary pointers. If you want to output a pointer cast it to + // "void *" or "const void *". In particular, this forbids formatting + // of "[const] volatile char *" which is printed as bool by iostreams. + // Do not implement! + template + MakeValue(const T *value); + template + MakeValue(T *value); + + void set_string(StringRef str) + { + string.value = str.c_str(); + string.size = str.size(); + } + + void set_string(WStringRef str) + { + CharTraits::convert(wchar_t()); + wstring.value = str.c_str(); + wstring.size = str.size(); + } + + // Formats an argument of a custom type, such as a user-defined class. + template + static void format_custom_arg( + void *formatter, const void *arg, void *format_str_ptr) + { + format(*static_cast*>(formatter), + *static_cast(format_str_ptr), + *static_cast(arg)); + } + +public: + MakeValue() {} + +#define FMT_MAKE_VALUE(Type, field, TYPE) \ + MakeValue(Type value) { field = value; } \ + static uint64_t type(Type) { return Arg::TYPE; } + + FMT_MAKE_VALUE(bool, int_value, INT) + FMT_MAKE_VALUE(short, int_value, INT) + FMT_MAKE_VALUE(unsigned short, uint_value, UINT) + FMT_MAKE_VALUE(int, int_value, INT) + FMT_MAKE_VALUE(unsigned, uint_value, UINT) + + MakeValue(long value) + { + // To minimize the number of types we need to deal with, long is + // translated either to int or to long long depending on its size. + if (sizeof(long) == sizeof(int)) + int_value = static_cast(value); + else + long_long_value = value; + } + static uint64_t type(long) + { + return sizeof(long) == sizeof(int) ? Arg::INT : Arg::LONG_LONG; + } + + MakeValue(unsigned long value) + { + if (sizeof(unsigned long) == sizeof(unsigned)) + uint_value = static_cast(value); + else + ulong_long_value = value; + } + static uint64_t type(unsigned long) + { + return sizeof(unsigned long) == sizeof(unsigned) ? + Arg::UINT : Arg::ULONG_LONG; + } + + FMT_MAKE_VALUE(LongLong, long_long_value, LONG_LONG) + FMT_MAKE_VALUE(ULongLong, ulong_long_value, ULONG_LONG) + FMT_MAKE_VALUE(float, double_value, DOUBLE) + FMT_MAKE_VALUE(double, double_value, DOUBLE) + FMT_MAKE_VALUE(long double, long_double_value, LONG_DOUBLE) + FMT_MAKE_VALUE(signed char, int_value, CHAR) + FMT_MAKE_VALUE(unsigned char, int_value, CHAR) + FMT_MAKE_VALUE(char, int_value, CHAR) + + MakeValue(wchar_t value) + { + int_value = internal::CharTraits::convert(value); + } + static uint64_t type(wchar_t) + { + return Arg::CHAR; + } + +#define FMT_MAKE_STR_VALUE(Type, TYPE) \ + MakeValue(Type value) { set_string(value); } \ + static uint64_t type(Type) { return Arg::TYPE; } + + FMT_MAKE_VALUE(char *, string.value, CSTRING) + FMT_MAKE_VALUE(const char *, string.value, CSTRING) + FMT_MAKE_VALUE(const signed char *, sstring.value, CSTRING) + FMT_MAKE_VALUE(const unsigned char *, ustring.value, CSTRING) + FMT_MAKE_STR_VALUE(const std::string &, STRING) + FMT_MAKE_STR_VALUE(StringRef, STRING) + + FMT_MAKE_STR_VALUE(wchar_t *, WSTRING) + FMT_MAKE_STR_VALUE(const wchar_t *, WSTRING) + FMT_MAKE_STR_VALUE(const std::wstring &, WSTRING) + FMT_MAKE_STR_VALUE(WStringRef, WSTRING) + + FMT_MAKE_VALUE(void *, pointer, POINTER) + FMT_MAKE_VALUE(const void *, pointer, POINTER) + + template + MakeValue(const T &value) + { + custom.value = &value; + custom.format = &format_custom_arg; + } + template + static uint64_t type(const T &) + { + return Arg::CUSTOM; + } +}; + +#define FMT_DISPATCH(call) static_cast(this)->call + +// An argument visitor. +// To use ArgVisitor define a subclass that implements some or all of the +// visit methods with the same signatures as the methods in ArgVisitor, +// for example, visit_int(int). +// Specify the subclass name as the Impl template parameter. Then calling +// ArgVisitor::visit for some argument will dispatch to a visit method +// specific to the argument type. For example, if the argument type is +// double then visit_double(double) method of a subclass will be called. +// If the subclass doesn't contain a method with this signature, then +// a corresponding method of ArgVisitor will be called. +// +// Example: +// class MyArgVisitor : public ArgVisitor { +// public: +// void visit_int(int value) { print("{}", value); } +// void visit_double(double value) { print("{}", value ); } +// }; +// +// ArgVisitor uses the curiously recurring template pattern: +// http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern +template +class ArgVisitor +{ +public: + Result visit_unhandled_arg() + { + return Result(); + } + + Result visit_int(int value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + Result visit_long_long(LongLong value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + Result visit_uint(unsigned value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + Result visit_ulong_long(ULongLong value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + Result visit_char(int value) + { + return FMT_DISPATCH(visit_any_int(value)); + } + template + Result visit_any_int(T) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + Result visit_double(double value) + { + return FMT_DISPATCH(visit_any_double(value)); + } + Result visit_long_double(long double value) + { + return FMT_DISPATCH(visit_any_double(value)); + } + template + Result visit_any_double(T) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + Result visit_string(Arg::StringValue) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + Result visit_wstring(Arg::StringValue) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + Result visit_pointer(const void *) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + Result visit_custom(Arg::CustomValue) + { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + Result visit(const Arg &arg) + { + switch (arg.type) + { + default: + assert(false); + // Fall through. + case Arg::INT: + return FMT_DISPATCH(visit_int(arg.int_value)); + case Arg::UINT: + return FMT_DISPATCH(visit_uint(arg.uint_value)); + case Arg::LONG_LONG: + return FMT_DISPATCH(visit_long_long(arg.long_long_value)); + case Arg::ULONG_LONG: + return FMT_DISPATCH(visit_ulong_long(arg.ulong_long_value)); + case Arg::DOUBLE: + return FMT_DISPATCH(visit_double(arg.double_value)); + case Arg::LONG_DOUBLE: + return FMT_DISPATCH(visit_long_double(arg.long_double_value)); + case Arg::CHAR: + return FMT_DISPATCH(visit_char(arg.int_value)); + case Arg::CSTRING: + { + Value::StringValue str = arg.string; + str.size = 0; + return FMT_DISPATCH(visit_string(str)); + } + case Arg::STRING: + return FMT_DISPATCH(visit_string(arg.string)); + case Arg::WSTRING: + return FMT_DISPATCH(visit_wstring(arg.wstring)); + case Arg::POINTER: + return FMT_DISPATCH(visit_pointer(arg.pointer)); + case Arg::CUSTOM: + return FMT_DISPATCH(visit_custom(arg.custom)); + } + } +}; + +class RuntimeError : public std::runtime_error +{ +protected: + RuntimeError() : std::runtime_error("") {} +}; + +template +class ArgFormatter; +} // namespace internal + +/** +An argument list. +*/ +class ArgList +{ +private: + uint64_t types_; + const internal::Value *values_; + +public: + // Maximum number of arguments that can be passed in ArgList. + enum { MAX_ARGS = 16 }; + + ArgList() : types_(0) {} + ArgList(ULongLong types, const internal::Value *values) + : types_(types), values_(values) {} + + /** + Returns the argument at specified index. + */ + internal::Arg operator[](unsigned index) const + { + using internal::Arg; + Arg arg; + if (index >= MAX_ARGS) + { + arg.type = Arg::NONE; + return arg; + } + unsigned shift = index * 4; + uint64_t mask = 0xf; + Arg::Type type = + static_cast((types_ & (mask << shift)) >> shift); + arg.type = type; + if (type != Arg::NONE) + { + internal::Value &value = arg; + value = values_[index]; + } + return arg; + } +}; + +struct FormatSpec; + +namespace internal +{ + +class FormatterBase +{ +private: + ArgList args_; + int next_arg_index_; + + // Returns the argument with specified index. + Arg do_get_arg(unsigned arg_index, const char *&error); + +protected: + void set_args(const ArgList &args) + { + args_ = args; + next_arg_index_ = 0; + } + + // Returns the next argument. + Arg next_arg(const char *&error); + + // Checks if manual indexing is used and returns the argument with + // specified index. + Arg get_arg(unsigned arg_index, const char *&error); + + template + void write(BasicWriter &w, const Char *start, const Char *end) + { + if (start != end) + w << BasicStringRef(start, end - start); + } +}; + +// A printf formatter. +template +class PrintfFormatter : private FormatterBase +{ +private: + void parse_flags(FormatSpec &spec, const Char *&s); + + // Returns the argument with specified index or, if arg_index is equal + // to the maximum unsigned value, the next argument. + Arg get_arg(const Char *s, + unsigned arg_index = (std::numeric_limits::max)()); + + // Parses argument index, flags and width and returns the argument index. + unsigned parse_header(const Char *&s, FormatSpec &spec); + +public: + void format(BasicWriter &writer, + BasicStringRef format, const ArgList &args); +}; +} // namespace internal + +// A formatter. +template +class BasicFormatter : private internal::FormatterBase +{ +private: + BasicWriter &writer_; + const Char *start_; + + // Parses argument index and returns corresponding argument. + internal::Arg parse_arg_index(const Char *&s); + +public: + explicit BasicFormatter(BasicWriter &w) : writer_(w) {} + + BasicWriter &writer() + { + return writer_; + } + + void format(BasicStringRef format_str, const ArgList &args); + + const Char *format(const Char *&format_str, const internal::Arg &arg); +}; + +enum Alignment +{ + ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC +}; + +// Flags. +enum +{ + SIGN_FLAG = 1, PLUS_FLAG = 2, MINUS_FLAG = 4, HASH_FLAG = 8, + CHAR_FLAG = 0x10 // Argument has char type - used in error reporting. +}; + +// An empty format specifier. +struct EmptySpec {}; + +// A type specifier. +template +struct TypeSpec : EmptySpec +{ + Alignment align() const + { + return ALIGN_DEFAULT; + } + unsigned width() const + { + return 0; + } + int precision() const + { + return -1; + } + bool flag(unsigned) const + { + return false; + } + char type() const + { + return TYPE; + } + char fill() const + { + return ' '; + } +}; + +// A width specifier. +struct WidthSpec +{ + unsigned width_; + // Fill is always wchar_t and cast to char if necessary to avoid having + // two specialization of WidthSpec and its subclasses. + wchar_t fill_; + + WidthSpec(unsigned width, wchar_t fill) : width_(width), fill_(fill) {} + + unsigned width() const + { + return width_; + } + wchar_t fill() const + { + return fill_; + } +}; + +// An alignment specifier. +struct AlignSpec : WidthSpec +{ + Alignment align_; + + AlignSpec(unsigned width, wchar_t fill, Alignment align = ALIGN_DEFAULT) + : WidthSpec(width, fill), align_(align) {} + + Alignment align() const + { + return align_; + } + + int precision() const + { + return -1; + } +}; + +// An alignment and type specifier. +template +struct AlignTypeSpec : AlignSpec +{ + AlignTypeSpec(unsigned width, wchar_t fill) : AlignSpec(width, fill) {} + + bool flag(unsigned) const + { + return false; + } + char type() const + { + return TYPE; + } +}; + +// A full format specifier. +struct FormatSpec : AlignSpec +{ + unsigned flags_; + int precision_; + char type_; + + FormatSpec( + unsigned width = 0, char type = 0, wchar_t fill = ' ') + : AlignSpec(width, fill), flags_(0), precision_(-1), type_(type) {} + + bool flag(unsigned f) const + { + return (flags_ & f) != 0; + } + int precision() const + { + return precision_; + } + char type() const + { + return type_; + } +}; + +// An integer format specifier. +template , typename Char = char> +class IntFormatSpec : public SpecT +{ +private: + T value_; + +public: + IntFormatSpec(T value, const SpecT &spec = SpecT()) + : SpecT(spec), value_(value) {} + + T value() const + { + return value_; + } +}; + +// A string format specifier. +template +class StrFormatSpec : public AlignSpec +{ +private: + const T *str_; + +public: + StrFormatSpec(const T *str, unsigned width, wchar_t fill) + : AlignSpec(width, fill), str_(str) {} + + const T *str() const + { + return str_; + } +}; + +/** +Returns an integer format specifier to format the value in base 2. +*/ +IntFormatSpec > bin(int value); + +/** +Returns an integer format specifier to format the value in base 8. +*/ +IntFormatSpec > oct(int value); + +/** +Returns an integer format specifier to format the value in base 16 using +lower-case letters for the digits above 9. +*/ +IntFormatSpec > hex(int value); + +/** +Returns an integer formatter format specifier to format in base 16 using +upper-case letters for the digits above 9. +*/ +IntFormatSpec > hexu(int value); + +/** +\rst +Returns an integer format specifier to pad the formatted argument with the +fill character to the specified width using the default (right) numeric +alignment. + +**Example**:: + +MemoryWriter out; +out << pad(hex(0xcafe), 8, '0'); +// out.str() == "0000cafe" + +\endrst +*/ +template +IntFormatSpec, Char> pad( + int value, unsigned width, Char fill = ' '); + +#define FMT_DEFINE_INT_FORMATTERS(TYPE) \ +inline IntFormatSpec > bin(TYPE value) { \ + return IntFormatSpec >(value, TypeSpec<'b'>()); \ + } \ + \ +inline IntFormatSpec > oct(TYPE value) { \ + return IntFormatSpec >(value, TypeSpec<'o'>()); \ + } \ + \ +inline IntFormatSpec > hex(TYPE value) { \ + return IntFormatSpec >(value, TypeSpec<'x'>()); \ + } \ + \ +inline IntFormatSpec > hexu(TYPE value) { \ + return IntFormatSpec >(value, TypeSpec<'X'>()); \ + } \ + \ +template \ +inline IntFormatSpec > pad( \ + IntFormatSpec > f, unsigned width) { \ + return IntFormatSpec >( \ + f.value(), AlignTypeSpec(width, ' ')); \ + } \ + \ +/* For compatibility with older compilers we provide two overloads for pad, */ \ +/* one that takes a fill character and one that doesn't. In the future this */ \ +/* can be replaced with one overload making the template argument Char */ \ +/* default to char (C++11). */ \ +template \ +inline IntFormatSpec, Char> pad( \ + IntFormatSpec, Char> f, \ + unsigned width, Char fill) { \ + return IntFormatSpec, Char>( \ + f.value(), AlignTypeSpec(width, fill)); \ + } \ + \ +inline IntFormatSpec > pad( \ + TYPE value, unsigned width) { \ + return IntFormatSpec >( \ + value, AlignTypeSpec<0>(width, ' ')); \ + } \ + \ +template \ +inline IntFormatSpec, Char> pad( \ + TYPE value, unsigned width, Char fill) { \ + return IntFormatSpec, Char>( \ + value, AlignTypeSpec<0>(width, fill)); \ + } + +FMT_DEFINE_INT_FORMATTERS(int) +FMT_DEFINE_INT_FORMATTERS(long) +FMT_DEFINE_INT_FORMATTERS(unsigned) +FMT_DEFINE_INT_FORMATTERS(unsigned long) +FMT_DEFINE_INT_FORMATTERS(LongLong) +FMT_DEFINE_INT_FORMATTERS(ULongLong) + +/** +\rst +Returns a string formatter that pads the formatted argument with the fill +character to the specified width using the default (left) string alignment. + +**Example**:: + +std::string s = str(MemoryWriter() << pad("abc", 8)); +// s == "abc " + +\endrst +*/ +template +inline StrFormatSpec pad( + const Char *str, unsigned width, Char fill = ' ') +{ + return StrFormatSpec(str, width, fill); +} + +inline StrFormatSpec pad( + const wchar_t *str, unsigned width, char fill = ' ') +{ + return StrFormatSpec(str, width, fill); +} + +// Generates a comma-separated list with results of applying f to +// numbers 0..n-1. +# define FMT_GEN(n, f) FMT_GEN##n(f) +# define FMT_GEN1(f) f(0) +# define FMT_GEN2(f) FMT_GEN1(f), f(1) +# define FMT_GEN3(f) FMT_GEN2(f), f(2) +# define FMT_GEN4(f) FMT_GEN3(f), f(3) +# define FMT_GEN5(f) FMT_GEN4(f), f(4) +# define FMT_GEN6(f) FMT_GEN5(f), f(5) +# define FMT_GEN7(f) FMT_GEN6(f), f(6) +# define FMT_GEN8(f) FMT_GEN7(f), f(7) +# define FMT_GEN9(f) FMT_GEN8(f), f(8) +# define FMT_GEN10(f) FMT_GEN9(f), f(9) +# define FMT_GEN11(f) FMT_GEN10(f), f(10) +# define FMT_GEN12(f) FMT_GEN11(f), f(11) +# define FMT_GEN13(f) FMT_GEN12(f), f(12) +# define FMT_GEN14(f) FMT_GEN13(f), f(13) +# define FMT_GEN15(f) FMT_GEN14(f), f(14) + +namespace internal +{ +inline uint64_t make_type() +{ + return 0; +} + +template +inline uint64_t make_type(const T &arg) +{ + return MakeValue::type(arg); +} + +#if FMT_USE_VARIADIC_TEMPLATES +template +inline uint64_t make_type(const Arg &first, const Args & ... tail) +{ + return make_type(first) | (make_type(tail...) << 4); +} +#else + +struct ArgType +{ + uint64_t type; + + ArgType() : type(0) {} + + template + ArgType(const T &arg) : type(make_type(arg)) {} +}; + +# define FMT_ARG_TYPE_DEFAULT(n) ArgType t##n = ArgType() + +inline uint64_t make_type(FMT_GEN15(FMT_ARG_TYPE_DEFAULT)) +{ + return t0.type | (t1.type << 4) | (t2.type << 8) | (t3.type << 12) | + (t4.type << 16) | (t5.type << 20) | (t6.type << 24) | (t7.type << 28) | + (t8.type << 32) | (t9.type << 36) | (t10.type << 40) | (t11.type << 44) | + (t12.type << 48) | (t13.type << 52) | (t14.type << 56); +} +#endif +} // namespace internal + +# define FMT_MAKE_TEMPLATE_ARG(n) typename T##n +# define FMT_MAKE_ARG_TYPE(n) T##n +# define FMT_MAKE_ARG(n) const T##n &v##n +# define FMT_MAKE_REF_char(n) fmt::internal::MakeValue(v##n) +# define FMT_MAKE_REF_wchar_t(n) fmt::internal::MakeValue(v##n) + +#if FMT_USE_VARIADIC_TEMPLATES +// Defines a variadic function returning void. +# define FMT_VARIADIC_VOID(func, arg_type) \ + template \ + void func(arg_type arg1, const Args & ... args) { \ + const fmt::internal::Value values[ \ + fmt::internal::NonZero::VALUE] = { \ + fmt::internal::MakeValue(args)... \ + }; \ + func(arg1, ArgList(fmt::internal::make_type(args...), values)); \ + } + +// Defines a variadic constructor. +# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \ + template \ + ctor(arg0_type arg0, arg1_type arg1, const Args & ... args) { \ + using fmt::internal::MakeValue; \ + const fmt::internal::Value values[ \ + fmt::internal::NonZero::VALUE] = { \ + MakeValue(args)... \ + }; \ + func(arg0, arg1, ArgList(fmt::internal::make_type(args...), values)); \ + } + +#else + +# define FMT_MAKE_REF(n) fmt::internal::MakeValue(v##n) +# define FMT_MAKE_REF2(n) v##n + +// Defines a wrapper for a function taking one argument of type arg_type +// and n additional arguments of arbitrary types. +# define FMT_WRAP1(func, arg_type, n) \ + template \ + inline void func(arg_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \ + const fmt::internal::Value vals[] = {FMT_GEN(n, FMT_MAKE_REF)}; \ + func(arg1, fmt::ArgList( \ + fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), vals)); \ + } + +// Emulates a variadic function returning void on a pre-C++11 compiler. +# define FMT_VARIADIC_VOID(func, arg_type) \ + FMT_WRAP1(func, arg_type, 1) FMT_WRAP1(func, arg_type, 2) \ + FMT_WRAP1(func, arg_type, 3) FMT_WRAP1(func, arg_type, 4) \ + FMT_WRAP1(func, arg_type, 5) FMT_WRAP1(func, arg_type, 6) \ + FMT_WRAP1(func, arg_type, 7) FMT_WRAP1(func, arg_type, 8) \ + FMT_WRAP1(func, arg_type, 9) FMT_WRAP1(func, arg_type, 10) + +# define FMT_CTOR(ctor, func, arg0_type, arg1_type, n) \ + template \ + ctor(arg0_type arg0, arg1_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \ + const fmt::internal::Value vals[] = {FMT_GEN(n, FMT_MAKE_REF)}; \ + func(arg0, arg1, fmt::ArgList( \ + fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), vals)); \ + } + +// Emulates a variadic constructor on a pre-C++11 compiler. +# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 1) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 2) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 3) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 4) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 5) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 6) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 7) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 8) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 9) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 10) +#endif + +// Generates a comma-separated list with results of applying f to pairs +// (argument, index). +#define FMT_FOR_EACH1(f, x0) f(x0, 0) +#define FMT_FOR_EACH2(f, x0, x1) \ + FMT_FOR_EACH1(f, x0), f(x1, 1) +#define FMT_FOR_EACH3(f, x0, x1, x2) \ + FMT_FOR_EACH2(f, x0 ,x1), f(x2, 2) +#define FMT_FOR_EACH4(f, x0, x1, x2, x3) \ + FMT_FOR_EACH3(f, x0, x1, x2), f(x3, 3) +#define FMT_FOR_EACH5(f, x0, x1, x2, x3, x4) \ + FMT_FOR_EACH4(f, x0, x1, x2, x3), f(x4, 4) +#define FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5) \ + FMT_FOR_EACH5(f, x0, x1, x2, x3, x4), f(x5, 5) +#define FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6) \ + FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5), f(x6, 6) +#define FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7) \ + FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6), f(x7, 7) +#define FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8) \ + FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7), f(x8, 8) +#define FMT_FOR_EACH10(f, x0, x1, x2, x3, x4, x5, x6, x7, x8, x9) \ + FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8), f(x9, 9) + +/** +An error returned by an operating system or a language runtime, +for example a file opening error. +*/ +class SystemError : public internal::RuntimeError +{ +private: + void init(int error_code, StringRef format_str, ArgList args); + +protected: + int error_code_; + + typedef char Char; // For FMT_VARIADIC_CTOR. + + SystemError() {} + +public: + /** + \rst + Constructs a :cpp:class:`fmt::SystemError` object with the description + of the form "**: **", where ** is the + formatted message and ** is the system message corresponding + to the error code. + *error_code* is a system error code as given by ``errno``. + \endrst + */ + SystemError(int error_code, StringRef message) + { + init(error_code, message, ArgList()); + } + FMT_VARIADIC_CTOR(SystemError, init, int, StringRef) + + int error_code() const + { + return error_code_; + } +}; + +/** +\rst +This template provides operations for formatting and writing data into +a character stream. The output is stored in a buffer provided by a subclass +such as :cpp:class:`fmt::BasicMemoryWriter`. + +You can use one of the following typedefs for common character types: + ++---------+----------------------+ +| Type | Definition | ++=========+======================+ +| Writer | BasicWriter | ++---------+----------------------+ +| WWriter | BasicWriter | ++---------+----------------------+ + +\endrst +*/ +template +class BasicWriter +{ +private: + // Output buffer. + internal::Buffer &buffer_; + + FMT_DISALLOW_COPY_AND_ASSIGN(BasicWriter); + + typedef typename internal::CharTraits::CharPtr CharPtr; + +#if _SECURE_SCL + // Returns pointer value. + static Char *get(CharPtr p) + { + return p.base(); + } +#else + static Char *get(Char *p) + { + return p; + } +#endif + + // Fills the padding around the content and returns the pointer to the + // content area. + static CharPtr fill_padding(CharPtr buffer, + unsigned total_size, std::size_t content_size, wchar_t fill); + + // Grows the buffer by n characters and returns a pointer to the newly + // allocated area. + CharPtr grow_buffer(std::size_t n) + { + std::size_t size = buffer_.size(); + buffer_.resize(size + n); + return internal::make_ptr(&buffer_[size], n); + } + + // Prepare a buffer for integer formatting. + CharPtr prepare_int_buffer(unsigned num_digits, + const EmptySpec &, const char *prefix, unsigned prefix_size) + { + unsigned size = prefix_size + num_digits; + CharPtr p = grow_buffer(size); + std::copy(prefix, prefix + prefix_size, p); + return p + size - 1; + } + + template + CharPtr prepare_int_buffer(unsigned num_digits, + const Spec &spec, const char *prefix, unsigned prefix_size); + + // Formats an integer. + template + void write_int(T value, Spec spec); + + // Formats a floating-point number (double or long double). + template + void write_double(T value, const FormatSpec &spec); + + // Writes a formatted string. + template + CharPtr write_str( + const StrChar *s, std::size_t size, const AlignSpec &spec); + + template + void write_str( + const internal::Arg::StringValue &str, const FormatSpec &spec); + + // This method is private to disallow writing a wide string to a + // char stream and vice versa. If you want to print a wide string + // as a pointer as std::ostream does, cast it to const void*. + // Do not implement! + void operator<<(typename internal::CharTraits::UnsupportedStrType); + + friend class internal::ArgFormatter; + friend class internal::PrintfFormatter; + +protected: + /** + Constructs a ``BasicWriter`` object. + */ + explicit BasicWriter(internal::Buffer &b) : buffer_(b) {} + +public: + /** + Destroys a ``BasicWriter`` object. + */ + virtual ~BasicWriter() {} + + /** + Returns the total number of characters written. + */ + std::size_t size() const + { + return buffer_.size(); + } + + /** + Returns a pointer to the output buffer content. No terminating null + character is appended. + */ + const Char *data() const FMT_NOEXCEPT(true) + { + return &buffer_[0]; + } + + /** + Returns a pointer to the output buffer content with terminating null + character appended. + */ + const Char *c_str() const + { + std::size_t size = buffer_.size(); + buffer_.reserve(size + 1); + buffer_[size] = '\0'; + return &buffer_[0]; + } + + /** + Returns the content of the output buffer as an `std::string`. + */ + std::basic_string str() const + { + return std::basic_string(&buffer_[0], buffer_.size()); + } + + /** + \rst + Writes formatted data. + + *args* is an argument list representing arbitrary arguments. + + **Example**:: + + MemoryWriter out; + out.write("Current point:\n"); + out.write("({:+f}, {:+f})", -3.14, 3.14); + + This will write the following output to the ``out`` object: + + .. code-block:: none + + Current point: + (-3.140000, +3.140000) + + The output can be accessed using :meth:`data`, :meth:`c_str` or :meth:`str` + methods. + + See also :ref:`syntax`. + \endrst + */ + void write(BasicStringRef format, ArgList args) + { + BasicFormatter(*this).format(format, args); + } + FMT_VARIADIC_VOID(write, BasicStringRef) + + BasicWriter &operator<<(int value) + { + return *this << IntFormatSpec(value); + } + BasicWriter &operator<<(unsigned value) + { + return *this << IntFormatSpec(value); + } + BasicWriter &operator<<(long value) + { + return *this << IntFormatSpec(value); + } + BasicWriter &operator<<(unsigned long value) + { + return *this << IntFormatSpec(value); + } + BasicWriter &operator<<(LongLong value) + { + return *this << IntFormatSpec(value); + } + + /** + Formats *value* and writes it to the stream. + */ + BasicWriter &operator<<(ULongLong value) + { + return *this << IntFormatSpec(value); + } + + BasicWriter &operator<<(double value) + { + write_double(value, FormatSpec()); + return *this; + } + + /** + Formats *value* using the general format for floating-point numbers + (``'g'``) and writes it to the stream. + */ + BasicWriter &operator<<(long double value) + { + write_double(value, FormatSpec()); + return *this; + } + + /** + Writes a character to the stream. + */ + BasicWriter &operator<<(char value) + { + buffer_.push_back(value); + return *this; + } + + BasicWriter &operator<<(wchar_t value) + { + buffer_.push_back(internal::CharTraits::convert(value)); + return *this; + } + + /** + Writes *value* to the stream. + */ + BasicWriter &operator<<(fmt::BasicStringRef value) + { + const Char *str = value.c_str(); + buffer_.append(str, str + value.size()); + return *this; + } + + template + BasicWriter &operator<<(IntFormatSpec spec) + { + internal::CharTraits::convert(FillChar()); + write_int(spec.value(), spec); + return *this; + } + + template + BasicWriter &operator<<(const StrFormatSpec &spec) + { + const StrChar *s = spec.str(); + // TODO: error if fill is not convertible to Char + write_str(s, std::char_traits::length(s), spec); + return *this; + } + + void clear() FMT_NOEXCEPT(true) + { + buffer_.clear(); + } +}; + +template +template +typename BasicWriter::CharPtr BasicWriter::write_str( + const StrChar *s, std::size_t size, const AlignSpec &spec) +{ + CharPtr out = CharPtr(); + if (spec.width() > size) + { + out = grow_buffer(spec.width()); + Char fill = static_cast(spec.fill()); + if (spec.align() == ALIGN_RIGHT) + { + std::fill_n(out, spec.width() - size, fill); + out += spec.width() - size; + } + else if (spec.align() == ALIGN_CENTER) + { + out = fill_padding(out, spec.width(), size, fill); + } + else + { + std::fill_n(out + size, spec.width() - size, fill); + } + } + else + { + out = grow_buffer(size); + } + std::copy(s, s + size, out); + return out; +} + +template +typename BasicWriter::CharPtr +BasicWriter::fill_padding( + CharPtr buffer, unsigned total_size, + std::size_t content_size, wchar_t fill) +{ + std::size_t padding = total_size - content_size; + std::size_t left_padding = padding / 2; + Char fill_char = static_cast(fill); + std::fill_n(buffer, left_padding, fill_char); + buffer += left_padding; + CharPtr content = buffer; + std::fill_n(buffer + content_size, padding - left_padding, fill_char); + return content; +} + +template +template +typename BasicWriter::CharPtr +BasicWriter::prepare_int_buffer( + unsigned num_digits, const Spec &spec, + const char *prefix, unsigned prefix_size) +{ + unsigned width = spec.width(); + Alignment align = spec.align(); + Char fill = static_cast(spec.fill()); + if (spec.precision() > static_cast(num_digits)) + { + // Octal prefix '0' is counted as a digit, so ignore it if precision + // is specified. + if (prefix_size > 0 && prefix[prefix_size - 1] == '0') + --prefix_size; + unsigned number_size = prefix_size + spec.precision(); + AlignSpec subspec(number_size, '0', ALIGN_NUMERIC); + if (number_size >= width) + return prepare_int_buffer(num_digits, subspec, prefix, prefix_size); + buffer_.reserve(width); + unsigned fill_size = width - number_size; + if (align != ALIGN_LEFT) + { + CharPtr p = grow_buffer(fill_size); + std::fill(p, p + fill_size, fill); + } + CharPtr result = prepare_int_buffer( + num_digits, subspec, prefix, prefix_size); + if (align == ALIGN_LEFT) + { + CharPtr p = grow_buffer(fill_size); + std::fill(p, p + fill_size, fill); + } + return result; + } + unsigned size = prefix_size + num_digits; + if (width <= size) + { + CharPtr p = grow_buffer(size); + std::copy(prefix, prefix + prefix_size, p); + return p + size - 1; + } + CharPtr p = grow_buffer(width); + CharPtr end = p + width; + if (align == ALIGN_LEFT) + { + std::copy(prefix, prefix + prefix_size, p); + p += size; + std::fill(p, end, fill); + } + else if (align == ALIGN_CENTER) + { + p = fill_padding(p, width, size, fill); + std::copy(prefix, prefix + prefix_size, p); + p += size; + } + else + { + if (align == ALIGN_NUMERIC) + { + if (prefix_size != 0) + { + p = std::copy(prefix, prefix + prefix_size, p); + size -= prefix_size; + } + } + else + { + std::copy(prefix, prefix + prefix_size, end - size); + } + std::fill(p, end - size, fill); + p = end; + } + return p - 1; +} + +template +template +void BasicWriter::write_int(T value, Spec spec) +{ + unsigned prefix_size = 0; + typedef typename internal::IntTraits::MainType UnsignedType; + UnsignedType abs_value = value; + char prefix[4] = ""; + if (internal::is_negative(value)) + { + prefix[0] = '-'; + ++prefix_size; + abs_value = 0 - abs_value; + } + else if (spec.flag(SIGN_FLAG)) + { + prefix[0] = spec.flag(PLUS_FLAG) ? '+' : ' '; + ++prefix_size; + } + switch (spec.type()) + { + case 0: + case 'd': + { + unsigned num_digits = internal::count_digits(abs_value); + CharPtr p = prepare_int_buffer( + num_digits, spec, prefix, prefix_size) + 1 - num_digits; + internal::format_decimal(get(p), abs_value, num_digits); + break; + } + case 'x': + case 'X': + { + UnsignedType n = abs_value; + if (spec.flag(HASH_FLAG)) + { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = spec.type(); + } + unsigned num_digits = 0; + do + { + ++num_digits; + } + while ((n >>= 4) != 0); + Char *p = get(prepare_int_buffer( + num_digits, spec, prefix, prefix_size)); + n = abs_value; + const char *digits = spec.type() == 'x' ? + "0123456789abcdef" : "0123456789ABCDEF"; + do + { + *p-- = digits[n & 0xf]; + } + while ((n >>= 4) != 0); + break; + } + case 'b': + case 'B': + { + UnsignedType n = abs_value; + if (spec.flag(HASH_FLAG)) + { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = spec.type(); + } + unsigned num_digits = 0; + do + { + ++num_digits; + } + while ((n >>= 1) != 0); + Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size)); + n = abs_value; + do + { + *p-- = '0' + (n & 1); + } + while ((n >>= 1) != 0); + break; + } + case 'o': + { + UnsignedType n = abs_value; + if (spec.flag(HASH_FLAG)) + prefix[prefix_size++] = '0'; + unsigned num_digits = 0; + do + { + ++num_digits; + } + while ((n >>= 3) != 0); + Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size)); + n = abs_value; + do + { + *p-- = '0' + (n & 7); + } + while ((n >>= 3) != 0); + break; + } + default: + internal::report_unknown_type( + spec.type(), spec.flag(CHAR_FLAG) ? "char" : "integer"); + break; + } +} + +template +template +void BasicWriter::write_double( + T value, const FormatSpec &spec) +{ + // Check type. + char type = spec.type(); + bool upper = false; + switch (type) + { + case 0: + type = 'g'; + break; + case 'e': + case 'f': + case 'g': + case 'a': + break; + case 'F': +#ifdef _MSC_VER + // MSVC's printf doesn't support 'F'. + type = 'f'; +#endif + // Fall through. + case 'E': + case 'G': + case 'A': + upper = true; + break; + default: + internal::report_unknown_type(type, "double"); + break; + } + + char sign = 0; + // Use getsign instead of value < 0 because the latter is always + // false for NaN. + if (internal::getsign(static_cast(value))) + { + sign = '-'; + value = -value; + } + else if (spec.flag(SIGN_FLAG)) + { + sign = spec.flag(PLUS_FLAG) ? '+' : ' '; + } + + if (value != value) + { + // Format NaN ourselves because sprintf's output is not consistent + // across platforms. + std::size_t size = 4; + const char *nan = upper ? " NAN" : " nan"; + if (!sign) + { + --size; + ++nan; + } + CharPtr out = write_str(nan, size, spec); + if (sign) + *out = sign; + return; + } + + if (internal::isinfinity(value)) + { + // Format infinity ourselves because sprintf's output is not consistent + // across platforms. + std::size_t size = 4; + const char *inf = upper ? " INF" : " inf"; + if (!sign) + { + --size; + ++inf; + } + CharPtr out = write_str(inf, size, spec); + if (sign) + *out = sign; + return; + } + + std::size_t offset = buffer_.size(); + unsigned width = spec.width(); + if (sign) + { + buffer_.reserve(buffer_.size() + (std::max)(width, 1u)); + if (width > 0) + --width; + ++offset; + } + + // Build format string. + enum { MAX_FORMAT_SIZE = 10 }; // longest format: %#-*.*Lg + Char format[MAX_FORMAT_SIZE]; + Char *format_ptr = format; + *format_ptr++ = '%'; + unsigned width_for_sprintf = width; + if (spec.flag(HASH_FLAG)) + *format_ptr++ = '#'; + if (spec.align() == ALIGN_CENTER) + { + width_for_sprintf = 0; + } + else + { + if (spec.align() == ALIGN_LEFT) + *format_ptr++ = '-'; + if (width != 0) + *format_ptr++ = '*'; + } + if (spec.precision() >= 0) + { + *format_ptr++ = '.'; + *format_ptr++ = '*'; + } + if (internal::IsLongDouble::VALUE) + *format_ptr++ = 'L'; + *format_ptr++ = type; + *format_ptr = '\0'; + + // Format using snprintf. + Char fill = static_cast(spec.fill()); + for (;;) + { + std::size_t size = buffer_.capacity() - offset; +#if _MSC_VER + // MSVC's vsnprintf_s doesn't work with zero size, so reserve + // space for at least one extra character to make the size non-zero. + // Note that the buffer's capacity will increase by more than 1. + if (size == 0) + { + buffer_.reserve(offset + 1); + size = buffer_.capacity() - offset; + } +#endif + Char *start = &buffer_[offset]; + int n = internal::CharTraits::format_float( + start, size, format, width_for_sprintf, spec.precision(), value); + if (n >= 0 && offset + n < buffer_.capacity()) + { + if (sign) + { + if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) || + *start != ' ') + { + *(start - 1) = sign; + sign = 0; + } + else + { + *(start - 1) = fill; + } + ++n; + } + if (spec.align() == ALIGN_CENTER && + spec.width() > static_cast(n)) + { + unsigned width = spec.width(); + CharPtr p = grow_buffer(width); + std::copy(p, p + n, p + (width - n) / 2); + fill_padding(p, spec.width(), n, fill); + return; + } + if (spec.fill() != ' ' || sign) + { + while (*start == ' ') + *start++ = fill; + if (sign) + *(start - 1) = sign; + } + grow_buffer(n); + return; + } + // If n is negative we ask to increase the capacity by at least 1, + // but as std::vector, the buffer grows exponentially. + buffer_.reserve(n >= 0 ? offset + n + 1 : buffer_.capacity() + 1); + } +} + +/** +\rst +This template provides operations for formatting and writing data into +a character stream. The output is stored in a memory buffer that grows +dynamically. + +You can use one of the following typedefs for common character types +and the standard allocator: + ++---------------+-----------------------------------------------+ +| Type | Definition | ++===============+===============================================+ +| MemoryWriter | BasicWriter> | ++---------------+-----------------------------------------------+ +| WMemoryWriter | BasicWriter> | ++---------------+-----------------------------------------------+ + +**Example**:: + +MemoryWriter out; +out << "The answer is " << 42 << "\n"; +out.write("({:+f}, {:+f})", -3.14, 3.14); + +This will write the following output to the ``out`` object: + +.. code-block:: none + +The answer is 42 +(-3.140000, +3.140000) + +The output can be converted to an ``std::string`` with ``out.str()`` or +accessed as a C string with ``out.c_str()``. +\endrst +*/ +template > +class BasicMemoryWriter : public BasicWriter +{ +private: + internal::MemoryBuffer buffer_; + +public: + explicit BasicMemoryWriter(const Allocator& alloc = Allocator()) + : BasicWriter(buffer_), buffer_(alloc) {} + +#if FMT_USE_RVALUE_REFERENCES + /** + Constructs a ``BasicMemoryWriter`` object moving the content of the other + object to it. + */ + BasicMemoryWriter(BasicMemoryWriter &&other) + : BasicWriter(buffer_), buffer_(std::move(other.buffer_)) + { + } + + /** + Moves the content of the other ``BasicMemoryWriter`` object to this one. + */ + BasicMemoryWriter &operator=(BasicMemoryWriter &&other) + { + buffer_ = std::move(other.buffer_); + return *this; + } +#endif +}; + +typedef BasicMemoryWriter MemoryWriter; +typedef BasicMemoryWriter WMemoryWriter; + +// Formats a value. +template +void format(BasicFormatter &f, const Char *&format_str, const T &value) +{ + std::basic_ostringstream os; + os << value; + internal::Arg arg; + internal::Value &arg_value = arg; + std::basic_string str = os.str(); + arg_value = internal::MakeValue(str); + arg.type = internal::Arg::STRING; + format_str = f.format(format_str, arg); +} + +// Reports a system error without throwing an exception. +// Can be used to report errors from destructors. +void report_system_error(int error_code, StringRef message) FMT_NOEXCEPT(true); + +#ifdef _WIN32 + +/** +A Windows error. +*/ +class WindowsError : public SystemError +{ +private: + void init(int error_code, StringRef format_str, ArgList args); + +public: + /** + \rst + Constructs a :cpp:class:`fmt::WindowsError` object with the description + of the form "**: **", where ** is the + formatted message and ** is the system message corresponding + to the error code. + *error_code* is a Windows error code as given by ``GetLastError``. + \endrst + */ + WindowsError(int error_code, StringRef message) + { + init(error_code, message, ArgList()); + } + FMT_VARIADIC_CTOR(WindowsError, init, int, StringRef) +}; + +// Reports a Windows error without throwing an exception. +// Can be used to report errors from destructors. +void report_windows_error(int error_code, StringRef message) FMT_NOEXCEPT(true); + +#endif + +enum Color { BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE }; + +/** +Formats a string and prints it to stdout using ANSI escape sequences +to specify color (experimental). +Example: +PrintColored(fmt::RED, "Elapsed time: {0:.2f} seconds") << 1.23; +*/ +void print_colored(Color c, StringRef format, ArgList args); + +/** +\rst +Formats arguments and returns the result as a string. + +**Example**:: + +std::string message = format("The answer is {}", 42); +\endrst +*/ +inline std::string format(StringRef format_str, ArgList args) +{ + MemoryWriter w; + w.write(format_str, args); + return w.str(); +} + +inline std::wstring format(WStringRef format_str, ArgList args) +{ + WMemoryWriter w; + w.write(format_str, args); + return w.str(); +} + +/** +\rst +Prints formatted data to the file *f*. + +**Example**:: + +print(stderr, "Don't {}!", "panic"); +\endrst +*/ +void print(std::FILE *f, StringRef format_str, ArgList args); + +/** +\rst +Prints formatted data to ``stdout``. + +**Example**:: + +print("Elapsed time: {0:.2f} seconds", 1.23); +\endrst +*/ +void print(StringRef format_str, ArgList args); + +/** +\rst +Prints formatted data to the stream *os*. + +**Example**:: + +print(cerr, "Don't {}!", "panic"); +\endrst +*/ +void print(std::ostream &os, StringRef format_str, ArgList args); + +template +void printf(BasicWriter &w, BasicStringRef format, ArgList args) +{ + internal::PrintfFormatter().format(w, format, args); +} + +/** +\rst +Formats arguments and returns the result as a string. + +**Example**:: + +std::string message = fmt::sprintf("The answer is %d", 42); +\endrst +*/ +inline std::string sprintf(StringRef format, ArgList args) +{ + MemoryWriter w; + printf(w, format, args); + return w.str(); +} + +/** +\rst +Prints formatted data to the file *f*. + +**Example**:: + +fmt::fprintf(stderr, "Don't %s!", "panic"); +\endrst +*/ +int fprintf(std::FILE *f, StringRef format, ArgList args); + +/** +\rst +Prints formatted data to ``stdout``. + +**Example**:: + +fmt::printf("Elapsed time: %.2f seconds", 1.23); +\endrst +*/ +inline int printf(StringRef format, ArgList args) +{ + return fprintf(stdout, format, args); +} + +/** +Fast integer formatter. +*/ +class FormatInt +{ +private: + // Buffer should be large enough to hold all digits (digits10 + 1), + // a sign and a null character. + enum { BUFFER_SIZE = std::numeric_limits::digits10 + 3 }; + mutable char buffer_[BUFFER_SIZE]; + char *str_; + + // Formats value in reverse and returns the number of digits. + char *format_decimal(ULongLong value) + { + char *buffer_end = buffer_ + BUFFER_SIZE - 1; + while (value >= 100) + { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + unsigned index = (value % 100) * 2; + value /= 100; + *--buffer_end = internal::Data::DIGITS[index + 1]; + *--buffer_end = internal::Data::DIGITS[index]; + } + if (value < 10) + { + *--buffer_end = static_cast('0' + value); + return buffer_end; + } + unsigned index = static_cast(value * 2); + *--buffer_end = internal::Data::DIGITS[index + 1]; + *--buffer_end = internal::Data::DIGITS[index]; + return buffer_end; + } + + void FormatSigned(LongLong value) + { + ULongLong abs_value = static_cast(value); + bool negative = value < 0; + if (negative) + abs_value = 0 - abs_value; + str_ = format_decimal(abs_value); + if (negative) + *--str_ = '-'; + } + +public: + explicit FormatInt(int value) + { + FormatSigned(value); + } + explicit FormatInt(long value) + { + FormatSigned(value); + } + explicit FormatInt(LongLong value) + { + FormatSigned(value); + } + explicit FormatInt(unsigned value) : str_(format_decimal(value)) {} + explicit FormatInt(unsigned long value) : str_(format_decimal(value)) {} + explicit FormatInt(ULongLong value) : str_(format_decimal(value)) {} + + /** + Returns the number of characters written to the output buffer. + */ + std::size_t size() const + { + return buffer_ - str_ + BUFFER_SIZE - 1; + } + + /** + Returns a pointer to the output buffer content. No terminating null + character is appended. + */ + const char *data() const + { + return str_; + } + + /** + Returns a pointer to the output buffer content with terminating null + character appended. + */ + const char *c_str() const + { + buffer_[BUFFER_SIZE - 1] = '\0'; + return str_; + } + + /** + Returns the content of the output buffer as an `std::string`. + */ + std::string str() const + { + return std::string(str_, size()); + } +}; + +// Formats a decimal integer value writing into buffer and returns +// a pointer to the end of the formatted string. This function doesn't +// write a terminating null character. +template +inline void format_decimal(char *&buffer, T value) +{ + typename internal::IntTraits::MainType abs_value = value; + if (internal::is_negative(value)) + { + *buffer++ = '-'; + abs_value = 0 - abs_value; + } + if (abs_value < 100) + { + if (abs_value < 10) + { + *buffer++ = static_cast('0' + abs_value); + return; + } + unsigned index = static_cast(abs_value * 2); + *buffer++ = internal::Data::DIGITS[index]; + *buffer++ = internal::Data::DIGITS[index + 1]; + return; + } + unsigned num_digits = internal::count_digits(abs_value); + internal::format_decimal(buffer, abs_value, num_digits); + buffer += num_digits; +} +} + +#if FMT_GCC_VERSION +// Use the system_header pragma to suppress warnings about variadic macros +// because suppressing -Wvariadic-macros with the diagnostic pragma doesn't +// work. It is used at the end because we want to suppress as little warnings +// as possible. +# pragma GCC system_header +#endif + +// This is used to work around VC++ bugs in handling variadic macros. +#define FMT_EXPAND(args) args + +// Returns the number of arguments. +// Based on https://groups.google.com/forum/#!topic/comp.std.c/d-6Mj5Lko_s. +#define FMT_NARG(...) FMT_NARG_(__VA_ARGS__, FMT_RSEQ_N()) +#define FMT_NARG_(...) FMT_EXPAND(FMT_ARG_N(__VA_ARGS__)) +#define FMT_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N +#define FMT_RSEQ_N() 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + +#define FMT_CONCAT(a, b) a##b +#define FMT_FOR_EACH_(N, f, ...) \ + FMT_EXPAND(FMT_CONCAT(FMT_FOR_EACH, N)(f, __VA_ARGS__)) +#define FMT_FOR_EACH(f, ...) \ + FMT_EXPAND(FMT_FOR_EACH_(FMT_NARG(__VA_ARGS__), f, __VA_ARGS__)) + +#define FMT_ADD_ARG_NAME(type, index) type arg##index +#define FMT_GET_ARG_NAME(type, index) arg##index + +#if FMT_USE_VARIADIC_TEMPLATES +# define FMT_VARIADIC_(Char, ReturnType, func, call, ...) \ + template \ + ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ + const Args & ... args) { \ + using fmt::internal::Value; \ + const Value values[fmt::internal::NonZero::VALUE] = { \ + fmt::internal::MakeValue(args)... \ + }; \ + call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList( \ + fmt::internal::make_type(args...), values)); \ + } +#else +// Defines a wrapper for a function taking __VA_ARGS__ arguments +// and n additional arguments of arbitrary types. +# define FMT_WRAP(Char, ReturnType, func, call, n, ...) \ + template \ + inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ + FMT_GEN(n, FMT_MAKE_ARG)) { \ + const fmt::internal::Value vals[] = {FMT_GEN(n, FMT_MAKE_REF_##Char)}; \ + call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList( \ + fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), vals)); \ + } + +# define FMT_VARIADIC_(Char, ReturnType, func, call, ...) \ + inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__)) { \ + call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList()); \ + } \ + FMT_WRAP(Char, ReturnType, func, call, 1, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 2, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 3, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 4, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 5, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 6, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 7, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 8, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 9, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 10, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 11, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 12, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 13, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 14, __VA_ARGS__) \ + FMT_WRAP(Char, ReturnType, func, call, 15, __VA_ARGS__) +#endif // FMT_USE_VARIADIC_TEMPLATES + +/** +\rst +Defines a variadic function with the specified return type, function name +and argument types passed as variable arguments to this macro. + +**Example**:: + +void print_error(const char *file, int line, const char *format, +fmt::ArgList args) { +fmt::print("{}: {}: ", file, line); +fmt::print(format, args); +} +FMT_VARIADIC(void, print_error, const char *, int, const char *) + +``FMT_VARIADIC`` is used for compatibility with legacy C++ compilers that +don't implement variadic templates. You don't have to use this macro if +you don't need legacy compiler support and can use variadic templates +directly:: + +template +void print_error(const char *file, int line, const char *format, +const Args & ... args) { +fmt::print("{}: {}: ", file, line); +fmt::print(format, args...); +} +\endrst +*/ +#define FMT_VARIADIC(ReturnType, func, ...) \ + FMT_VARIADIC_(char, ReturnType, func, return func, __VA_ARGS__) + +#define FMT_VARIADIC_W(ReturnType, func, ...) \ + FMT_VARIADIC_(wchar_t, ReturnType, func, return func, __VA_ARGS__) + +namespace fmt +{ +FMT_VARIADIC(std::string, format, StringRef) +FMT_VARIADIC_W(std::wstring, format, WStringRef) +FMT_VARIADIC(void, print, StringRef) +FMT_VARIADIC(void, print, std::FILE *, StringRef) +FMT_VARIADIC(void, print, std::ostream &, StringRef) +FMT_VARIADIC(void, print_colored, Color, StringRef) +FMT_VARIADIC(std::string, sprintf, StringRef) +FMT_VARIADIC(int, printf, StringRef) +FMT_VARIADIC(int, fprintf, std::FILE *, StringRef) +} + +// Restore warnings. +#if FMT_GCC_VERSION >= 406 +# pragma GCC diagnostic pop +#endif + + +#define FMT_HEADER_ONLY +# include "format.cc" + +#ifdef __GNUC__ +# pragma GCC diagnostic pop //pop -Wshadow warnings ignore +#endif + +#endif // FMT_FORMAT_H_ diff --git a/include/spdlog/details/line_logger.h b/include/spdlog/details/line_logger.h index c16a81ad..2a2bd2af 100644 --- a/include/spdlog/details/line_logger.h +++ b/include/spdlog/details/line_logger.h @@ -26,7 +26,6 @@ #include "../common.h" #include "../logger.h" -#include "fast_oss.h" // Line logger class - aggregates operator<< calls to fast ostream @@ -66,7 +65,6 @@ public: { _log_msg.logger_name = _callback_logger->name(); _log_msg.time = log_clock::now(); - _log_msg.tm_time = details::os::localtime(log_clock::to_time_t(_log_msg.time)); _callback_logger->_log_msg(_log_msg); } } @@ -80,6 +78,12 @@ public: } } + template + void write(const std::string& fmt, const Args&... args) + { + _log_msg.raw.write(fmt, args...); + } + template line_logger& operator<<(const T& what) { @@ -87,6 +91,7 @@ public: return *this; } + void disable() { _enabled = false; diff --git a/include/spdlog/details/log_msg.h b/include/spdlog/details/log_msg.h index 19abcfda..abd9bd41 100644 --- a/include/spdlog/details/log_msg.h +++ b/include/spdlog/details/log_msg.h @@ -25,7 +25,7 @@ #pragma once #include "../common.h" -#include "./fast_oss.h" +#include "./format.h" namespace spdlog { @@ -38,25 +38,31 @@ struct log_msg logger_name(), level(l), time(), - tm_time(), raw(), formatted() {} - log_msg(const log_msg& other): + + log_msg(const log_msg& other) : logger_name(other.logger_name), level(other.level), - time(other.time), - tm_time(other.tm_time), - raw(other.raw), - formatted(other.formatted) {} + time(other.time) + { + if (other.raw.size()) + raw << fmt::BasicStringRef(other.raw.data(), other.raw.size()); + if (other.formatted.size()) + formatted << fmt::BasicStringRef(other.formatted.data(), other.formatted.size()); + + } log_msg(log_msg&& other) : logger_name(std::move(other.logger_name)), level(other.level), time(std::move(other.time)), - tm_time(other.tm_time), raw(std::move(other.raw)), - formatted(std::move(other.formatted)) {} + formatted(std::move(other.formatted)) + { + other.clear(); + } log_msg& operator=(log_msg&& other) { @@ -66,16 +72,15 @@ struct log_msg logger_name = std::move(other.logger_name); level = other.level; time = std::move(other.time); - tm_time = other.tm_time; raw = std::move(other.raw); formatted = std::move(other.formatted); + other.clear(); return *this; } - - void clear() { + level = level::OFF; raw.clear(); formatted.clear(); } @@ -83,9 +88,8 @@ struct log_msg std::string logger_name; level::level_enum level; log_clock::time_point time; - std::tm tm_time; - fast_oss raw; - fast_oss formatted; + fmt::MemoryWriter raw; + fmt::MemoryWriter formatted; }; } } diff --git a/include/spdlog/details/logger_impl.h b/include/spdlog/details/logger_impl.h index ccf1886e..62e8a6e5 100644 --- a/include/spdlog/details/logger_impl.h +++ b/include/spdlog/details/logger_impl.h @@ -63,78 +63,157 @@ inline void spdlog::logger::set_pattern(const std::string& pattern) _set_pattern(pattern); } - - +// +// cppformat API of the form logger.info("hello {} {}", "world", 1); +// template -inline spdlog::details::line_logger spdlog::logger::log(level::level_enum lvl, const Args&... args) +inline spdlog::details::line_logger spdlog::logger::_log(level::level_enum lvl, const std::string& fmt, const Args&... args) { bool msg_enabled = should_log(lvl); details::line_logger l(this, lvl, msg_enabled); if (msg_enabled) - _variadic_log(l, args...); + { + try + { + l.write(fmt, args...); + } + catch(const fmt::FormatError& e) + { + throw spdlog_ex(fmt::format("formatting error while processing format string '{}': {}", fmt, e.what())); + } + + } return l; } template -inline spdlog::details::line_logger spdlog::logger::log(const Args&... args) +inline spdlog::details::line_logger spdlog::logger::log(const std::string& fmt, const Args&... args) { - return log(level::ALWAYS, args...); + return _log(level::ALWAYS, fmt, args...); } template -inline spdlog::details::line_logger spdlog::logger::trace(const Args&... args) +inline spdlog::details::line_logger spdlog::logger::trace(const std::string& fmt, const Args&... args) { - return log(level::TRACE, args...); + return _log(level::TRACE, fmt, args...); } template -inline spdlog::details::line_logger spdlog::logger::debug(const Args&... args) +inline spdlog::details::line_logger spdlog::logger::debug(const std::string& fmt, const Args&... args) { - return log(level::DEBUG, args...); + return _log(level::DEBUG, fmt, args...); } template -inline spdlog::details::line_logger spdlog::logger::info(const Args&... args) +inline spdlog::details::line_logger spdlog::logger::info(const std::string& fmt, const Args&... args) { - return log(level::INFO, args...); + return _log(level::INFO, fmt, args...); } template -inline spdlog::details::line_logger spdlog::logger::notice(const Args&... args) +inline spdlog::details::line_logger spdlog::logger::notice(const std::string& fmt, const Args&... args) { - return log(level::NOTICE, args...); + return _log(level::NOTICE, fmt, args...); } template -inline spdlog::details::line_logger spdlog::logger::warn(const Args&... args) +inline spdlog::details::line_logger spdlog::logger::warn(const std::string& fmt, const Args&... args) { - return log(level::WARN, args...); + return _log(level::WARN, fmt, args...); } template -inline spdlog::details::line_logger spdlog::logger::error(const Args&... args) +inline spdlog::details::line_logger spdlog::logger::error(const std::string& fmt, const Args&... args) { - return log(level::ERR, args...); + return _log(level::ERR, fmt, args...); } template -inline spdlog::details::line_logger spdlog::logger::critical(const Args&... args) +inline spdlog::details::line_logger spdlog::logger::critical(const std::string& fmt, const Args&... args) { - return log(level::CRITICAL, args...); + return _log(level::CRITICAL, fmt, args...); } template -inline spdlog::details::line_logger spdlog::logger::alert(const Args&... args) +inline spdlog::details::line_logger spdlog::logger::alert(const std::string& fmt, const Args&... args) { - return log(level::ALERT, args...); + return _log(level::ALERT, fmt, args...); } template -inline spdlog::details::line_logger spdlog::logger::emerg(const Args&... args) +inline spdlog::details::line_logger spdlog::logger::emerg(const std::string& fmt, const Args&... args) { - return log(level::EMERG, args...); + return _log(level::EMERG, fmt, args...); } + +// +// //API to support logger.info() << ".." calls +// + + +inline spdlog::details::line_logger spdlog::logger::_log(level::level_enum lvl) +{ + bool msg_enabled = should_log(lvl); + details::line_logger l(this, lvl, msg_enabled); + return l; +} + +inline spdlog::details::line_logger spdlog::logger::log() +{ + return _log(level::ALWAYS); +} + +inline spdlog::details::line_logger spdlog::logger::trace() +{ + return _log(level::TRACE); +} + + +inline spdlog::details::line_logger spdlog::logger::debug() +{ + return _log(level::DEBUG); +} + +inline spdlog::details::line_logger spdlog::logger::info() +{ + return _log(level::INFO); +} + +inline spdlog::details::line_logger spdlog::logger::notice() +{ + return _log(level::NOTICE); +} + +inline spdlog::details::line_logger spdlog::logger::warn() +{ + return _log(level::WARN); +} + +inline spdlog::details::line_logger spdlog::logger::error() +{ + return _log(level::ERR); +} + +inline spdlog::details::line_logger spdlog::logger::critical() +{ + return _log(level::CRITICAL); +} + +inline spdlog::details::line_logger spdlog::logger::alert() +{ + return _log(level::ALERT); +} + +inline spdlog::details::line_logger spdlog::logger::emerg() +{ + return _log(level::EMERG); +} + + + +// + inline const std::string& spdlog::logger::name() const { return _name; @@ -182,24 +261,7 @@ inline void spdlog::logger::_stop() set_level(level::OFF); } -/* private functions */ -inline void spdlog::logger::_variadic_log(spdlog::details::line_logger&) {} - -template -inline void spdlog::logger::_variadic_log(spdlog::details::line_logger& l, const Last& last) -{ - l.write(last); -} -template -inline void spdlog::logger::_variadic_log(spdlog::details::line_logger& l, const First& first, const Rest&... rest) -{ - l.write(first); - l.write(' '); - _variadic_log(l, rest...); -} - - diff --git a/include/spdlog/details/mpmc_bounded_q.h b/include/spdlog/details/mpmc_bounded_q.h new file mode 100644 index 00000000..53dfd108 --- /dev/null +++ b/include/spdlog/details/mpmc_bounded_q.h @@ -0,0 +1,175 @@ +/* +A modified version of Bounded MPMC queue by Dmitry Vyukov. + +Original code from: +http://www.1024cores.net/home/lock-free-algorithms/queues/bounded-mpmc-queue + +licensed by Dmitry Vyukov under the terms below: + +Simplified BSD license + +Copyright (c) 2010-2011 Dmitry Vyukov. All rights reserved. +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of +conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list +of conditions and the following disclaimer in the documentation and/or other materials +provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY DMITRY VYUKOV "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL DMITRY VYUKOV OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the authors and +should not be interpreted as representing official policies, either expressed or implied, of Dmitry Vyukov. +*/ + +/* +The code in its current form adds the license below: + +spdlog - an extremely fast and easy to use c++11 logging library. +Copyright (c) 2014 Gabi Melman. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#pragma once + +#include +#include "../common.h" + +namespace spdlog +{ +namespace details +{ + +template +class mpmc_bounded_queue +{ +public: + + using item_type = T; + mpmc_bounded_queue(size_t buffer_size) + : buffer_(new cell_t [buffer_size]), + buffer_mask_(buffer_size - 1) + { + //queue size must be power of two + if(!((buffer_size >= 2) && ((buffer_size & (buffer_size - 1)) == 0))) + throw spdlog_ex("async logger queue size must be power of two"); + + for (size_t i = 0; i != buffer_size; i += 1) + buffer_[i].sequence_.store(i, std::memory_order_relaxed); + enqueue_pos_.store(0, std::memory_order_relaxed); + dequeue_pos_.store(0, std::memory_order_relaxed); + } + + ~mpmc_bounded_queue() + { + delete [] buffer_; + } + + + bool enqueue(T&& data) + { + cell_t* cell; + size_t pos = enqueue_pos_.load(std::memory_order_relaxed); + for (;;) + { + cell = &buffer_[pos & buffer_mask_]; + size_t seq = cell->sequence_.load(std::memory_order_acquire); + intptr_t dif = (intptr_t)seq - (intptr_t)pos; + if (dif == 0) + { + if (enqueue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed)) + break; + } + else if (dif < 0) + { + return false; + } + else + { + pos = enqueue_pos_.load(std::memory_order_relaxed); + } + } + cell->data_ = std::move(data); + cell->sequence_.store(pos + 1, std::memory_order_release); + return true; + } + + bool dequeue(T& data) + { + cell_t* cell; + size_t pos = dequeue_pos_.load(std::memory_order_relaxed); + for (;;) + { + cell = &buffer_[pos & buffer_mask_]; + size_t seq = + cell->sequence_.load(std::memory_order_acquire); + intptr_t dif = (intptr_t)seq - (intptr_t)(pos + 1); + if (dif == 0) + { + if (dequeue_pos_.compare_exchange_weak(pos, pos + 1, std::memory_order_relaxed)) + break; + } + else if (dif < 0) + return false; + else + pos = dequeue_pos_.load(std::memory_order_relaxed); + } + data = std::move(cell->data_); + cell->sequence_.store(pos + buffer_mask_ + 1, std::memory_order_release); + return true; + } + +private: + struct cell_t + { + std::atomic sequence_; + T data_; + }; + + static size_t const cacheline_size = 64; + typedef char cacheline_pad_t [cacheline_size]; + + cacheline_pad_t pad0_; + cell_t* const buffer_; + size_t const buffer_mask_; + cacheline_pad_t pad1_; + std::atomic enqueue_pos_; + cacheline_pad_t pad2_; + std::atomic dequeue_pos_; + cacheline_pad_t pad3_; + + mpmc_bounded_queue(mpmc_bounded_queue const&); + void operator = (mpmc_bounded_queue const&); +}; + +} // ns details +} // ns spdlog diff --git a/include/spdlog/details/pattern_formatter_impl.h b/include/spdlog/details/pattern_formatter_impl.h index aaf4b137..0d36da0d 100644 --- a/include/spdlog/details/pattern_formatter_impl.h +++ b/include/spdlog/details/pattern_formatter_impl.h @@ -30,9 +30,9 @@ #include #include + #include "../formatter.h" #include "./log_msg.h" -#include "./fast_oss.h" #include "./os.h" namespace spdlog @@ -43,7 +43,7 @@ class flag_formatter { public: virtual ~flag_formatter() {} - virtual void format(details::log_msg& msg) = 0; + virtual void format(details::log_msg& msg, const std::tm& tm_time) = 0; }; /////////////////////////////////////////////////////////////////////// @@ -53,7 +53,7 @@ namespace { class name_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm&) override { msg.formatted << msg.logger_name; } @@ -63,7 +63,7 @@ class name_formatter :public flag_formatter // log level appender class level_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm&) override { msg.formatted << level::to_str(msg.level); } @@ -87,9 +87,9 @@ static int to12h(const tm& t) static const std::string days[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; class a_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_str(days[msg.tm_time.tm_wday]); + msg.formatted << days[tm_time.tm_wday]; } }; @@ -97,9 +97,9 @@ class a_formatter :public flag_formatter static const std::string full_days[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; class A_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_str(full_days[msg.tm_time.tm_wday]); + msg.formatted << full_days[tm_time.tm_wday]; } }; @@ -107,9 +107,9 @@ class A_formatter :public flag_formatter static const std::string months[] { "Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec" }; class b_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_str(months[msg.tm_time.tm_mon]); + msg.formatted<< months[tm_time.tm_mon]; } }; @@ -117,28 +117,35 @@ class b_formatter :public flag_formatter static const std::string full_months[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; class B_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_str(full_months[msg.tm_time.tm_mon]); + msg.formatted << full_months[tm_time.tm_mon]; } }; + +//write 2 ints seperated by sep with padding of 2 +static fmt::MemoryWriter& pad_n_join(fmt::MemoryWriter& w, int v1, int v2, char sep) +{ + w << fmt::pad(v1, 2, '0') << sep << fmt::pad(v2, 2, '0'); + return w; +} + +//write 3 ints seperated by sep with padding of 2 +static fmt::MemoryWriter& pad_n_join(fmt::MemoryWriter& w, int v1, int v2, int v3, char sep) +{ + w << fmt::pad(v1, 2, '0') << sep << fmt::pad(v2, 2, '0') << sep << fmt::pad(v3, 2, '0'); + return w; +} + + //Date and time representation (Thu Aug 23 15:35:46 2014) class c_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_str(days[msg.tm_time.tm_wday]); - msg.formatted.putc(' '); - msg.formatted.put_str(months[msg.tm_time.tm_mon]); - msg.formatted.putc(' '); - msg.formatted.put_int(msg.tm_time.tm_mday, 2); - msg.formatted.putc(' '); - msg.formatted.put_int(msg.tm_time.tm_hour, 2); - msg.formatted.putc(':'); - msg.formatted.put_int(msg.tm_time.tm_min, 2); - msg.formatted.putc(':'); - msg.formatted.put_int(msg.tm_time.tm_sec, 2); + msg.formatted << days[tm_time.tm_wday] << ' ' << months[tm_time.tm_mon] << ' ' << tm_time.tm_mday << ' '; + pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, ':') << ' ' << tm_time.tm_year + 1900; } }; @@ -146,9 +153,9 @@ class c_formatter :public flag_formatter // year - 2 digit class C_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_int(msg.tm_time.tm_year % 100, 2); + msg.formatted << fmt::pad(tm_time.tm_year % 100, 2, '0'); } }; @@ -157,13 +164,9 @@ class C_formatter :public flag_formatter // Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01 class D_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_int(msg.tm_time.tm_mon + 1, 2); - msg.formatted.putc('/'); - msg.formatted.put_int(msg.tm_time.tm_mday, 2); - msg.formatted.putc('/'); - msg.formatted.put_int(msg.tm_time.tm_year % 100, 2); + pad_n_join(msg.formatted, tm_time.tm_mon + 1, tm_time.tm_mday, tm_time.tm_year % 100, '/'); } }; @@ -171,83 +174,83 @@ class D_formatter :public flag_formatter // year - 4 digit class Y_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_int(msg.tm_time.tm_year + 1900, 4); + msg.formatted << tm_time.tm_year + 1900; } }; // month 1-12 class m_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_int(msg.tm_time.tm_mon + 1, 2); + msg.formatted << fmt::pad(tm_time.tm_mon + 1, 2, '0'); } }; // day of month 1-31 class d_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_int(msg.tm_time.tm_mday, 2); + msg.formatted << fmt::pad(tm_time.tm_mday, 2, '0'); } }; // hours in 24 format 0-23 class H_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_int(msg.tm_time.tm_hour, 2); + msg.formatted << fmt::pad(tm_time.tm_hour, 2, '0'); } }; // hours in 12 format 1-12 class I_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_int(to12h(msg.tm_time), 2); + msg.formatted << fmt::pad(to12h(tm_time), 2, '0'); } }; // ninutes 0-59 class M_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_int(msg.tm_time.tm_min, 2); + msg.formatted << fmt::pad(tm_time.tm_min, 2, '0'); } }; // seconds 0-59 class S_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_int(msg.tm_time.tm_sec, 2); + msg.formatted << fmt::pad(tm_time.tm_sec, 2, '0'); } }; // milliseconds class e_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm&) override { auto duration = msg.time.time_since_epoch(); auto millis = std::chrono::duration_cast(duration).count() % 1000; - msg.formatted.put_int(static_cast(millis), 3); + msg.formatted << fmt::pad(static_cast(millis), 3, '0'); } }; // AM/PM class p_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_data(ampm(msg.tm_time), 2); + msg.formatted << ampm(tm_time); } }; @@ -255,100 +258,92 @@ class p_formatter :public flag_formatter // 12 hour clock 02:55:02 pm class r_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_int(to12h(msg.tm_time), 2); - msg.formatted.putc(':'); - msg.formatted.put_int(msg.tm_time.tm_min, 2); - msg.formatted.putc(':'); - msg.formatted.put_int(msg.tm_time.tm_sec, 2); - msg.formatted.putc(' '); - msg.formatted.put_data(ampm(msg.tm_time), 2); + pad_n_join(msg.formatted, to12h(tm_time), tm_time.tm_min, tm_time.tm_sec, ':') << ' ' << ampm(tm_time); } }; // 24-hour HH:MM time, equivalent to %H:%M class R_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_int(msg.tm_time.tm_hour, 2); - msg.formatted.putc(':'); - msg.formatted.put_int(msg.tm_time.tm_min, 2); - + pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, ':'); } }; // ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S class T_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.put_int(msg.tm_time.tm_hour, 2); - msg.formatted.putc(':'); - msg.formatted.put_int(msg.tm_time.tm_min, 2); - msg.formatted.putc(':'); - msg.formatted.put_int(msg.tm_time.tm_sec, 2); + pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, ':'); } }; -// ISO 8601 offset from UTC in timezone (HH:MM) + +// ISO 8601 offset from UTC in timezone (+-HH:MM) class z_formatter :public flag_formatter { public: - z_formatter() {} + const std::chrono::seconds cache_refresh = std::chrono::seconds(5); + + z_formatter() :_last_update(std::chrono::seconds(0)) {} z_formatter(const z_formatter&) = delete; z_formatter& operator=(const z_formatter&) = delete; - void format(log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - std::lock_guard l(_mutex); - using namespace std::chrono; - auto diff = msg.time - _last_update; - auto secs_diff = std::abs((duration_cast(diff)).count()); - if (secs_diff >= 2) - { - _value = get_value(msg); - _last_update = msg.time; - } - msg.formatted.put_str(_value); +#ifdef _WIN32 + int total_minutes = get_cached_offset(msg, tm_time); +#else + // No need to chache under gcc, + // it is very fast (already stored in tm.tm_gmtoff) + int total_minutes = os::utc_minutes_offset(tm_time); +#endif + + int h = total_minutes / 60; + int m = total_minutes % 60; + char sign = h >= 0 ? '+' : '-'; + msg.formatted << sign; + pad_n_join(msg.formatted, h, m, ':'); } private: log_clock::time_point _last_update; - std::string _value; + int _offset_minutes; std::mutex _mutex; - std::string get_value(const log_msg& msg) + int get_cached_offset(const log_msg& msg, const std::tm& tm_time) { - int total_minutes = os::utc_minutes_offset(msg.tm_time); - int h = total_minutes / 60; - int m = total_minutes % 60; - fast_oss oss; - oss.putc(h < 0 ? '-' : '+'); - oss.put_int(h, 2); - oss.putc(':'); - oss.put_int(m, 2); - return oss.str(); + using namespace std::chrono; + std::lock_guard l(_mutex); + if (msg.time - _last_update >= cache_refresh) + { + _offset_minutes = os::utc_minutes_offset(tm_time); + _last_update = msg.time; + } + return _offset_minutes; } - }; + //Thread id class t_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm&) override { - msg.formatted << std::this_thread::get_id(); + msg.formatted << std::hash()(std::this_thread::get_id()); } }; class v_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm&) override { - msg.formatted.put_fast_oss(msg.raw); + msg.formatted << fmt::StringRef(msg.raw.data(), msg.raw.size()); } }; @@ -357,9 +352,9 @@ class ch_formatter :public flag_formatter public: explicit ch_formatter(char ch) : _ch(ch) {} - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm&) override { - msg.formatted.putc(_ch); + msg.formatted << _ch; } private: char _ch; @@ -376,9 +371,9 @@ public: { _str += ch; } - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm&) override { - msg.formatted.put_str(_str); + msg.formatted << _str; } private: std::string _str; @@ -388,29 +383,35 @@ private: // pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v class full_formatter :public flag_formatter { - void format(details::log_msg& msg) override + void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted.putc('['); - msg.formatted.put_int(msg.tm_time.tm_year+1900, 4); - msg.formatted.putc('-'); - msg.formatted.put_int(msg.tm_time.tm_mon+ 1, 2); - msg.formatted.putc('-'); - msg.formatted.put_int(msg.tm_time.tm_mday, 2); - msg.formatted.putc(' '); - msg.formatted.put_int(msg.tm_time.tm_hour, 2); - msg.formatted.putc(':'); - msg.formatted.put_int(msg.tm_time.tm_min, 2); - msg.formatted.putc(':'); - msg.formatted.put_int(msg.tm_time.tm_sec, 2); - //millis - msg.formatted.putc('.'); auto duration = msg.time.time_since_epoch(); auto millis = std::chrono::duration_cast(duration).count() % 1000; - msg.formatted.put_int(static_cast(millis), 3); - msg.formatted.putc(']'); - msg.formatted << " [" << msg.logger_name << "] [" << level::to_str(msg.level) << "] "; - msg.formatted.put_fast_oss(msg.raw); + /* Slower version(while still very fast - about 3.2 million lines/sec under 10 threads), + msg.formatted.write("[{:d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}.{:03d}] [{}] [{}] {} ", + tm_time.tm_year + 1900, + tm_time.tm_mon + 1, + tm_time.tm_mday, + tm_time.tm_hour, + tm_time.tm_min, + tm_time.tm_sec, + static_cast(millis), + msg.logger_name, + level::to_str(msg.level), + msg.raw.str());*/ + + // Faster (albeit uglier) way to format the line (5.6 million lines/sec under 10 threads) + msg.formatted << '[' << static_cast(tm_time.tm_year + 1900) << '-' + << fmt::pad(static_cast(tm_time.tm_mon + 1), 2, '0') << '-' + << fmt::pad(static_cast(tm_time.tm_mday), 2, '0') << ' ' + << fmt::pad(static_cast(tm_time.tm_hour), 2, '0') << ':' + << fmt::pad(static_cast(tm_time.tm_min), 2, '0') << ':' + << fmt::pad(static_cast(tm_time.tm_sec), 2, '0') << '.' + << fmt::pad(static_cast(millis), 3, '0') << "] "; + + msg.formatted << '[' << msg.logger_name << "] [" << level::to_str(msg.level) << "] "; + msg.formatted << fmt::StringRef(msg.raw.data(), msg.raw.size()); } }; @@ -571,10 +572,18 @@ inline void spdlog::pattern_formatter::handle_flag(char flag) inline void spdlog::pattern_formatter::format(details::log_msg& msg) { - for (auto &f : _formatters) + try { - f->format(msg); + auto tm_time = details::os::localtime(log_clock::to_time_t(msg.time)); + for (auto &f : _formatters) + { + f->format(msg, tm_time); + } + //write eol + msg.formatted << details::os::eol(); + } + catch(const fmt::FormatError& e) + { + throw spdlog_ex(fmt::format("formatting error while processing format string: {}", e.what())); } - //write eol - msg.formatted.write(details::os::eol(), details::os::eol_size()); } diff --git a/include/spdlog/details/registry.h b/include/spdlog/details/registry.h index ee6c7d95..0e24ba26 100644 --- a/include/spdlog/details/registry.h +++ b/include/spdlog/details/registry.h @@ -61,7 +61,7 @@ public: return found->second; std::shared_ptr new_logger; if (_async_mode) - new_logger = std::make_shared(logger_name, sinks_begin, sinks_end, _async_q_size, _async_shutdown_duration); + new_logger = std::make_shared(logger_name, sinks_begin, sinks_end, _async_q_size); else new_logger = std::make_shared(logger_name, sinks_begin, sinks_end); @@ -114,12 +114,11 @@ public: l.second->set_level(log_level); } - void set_async_mode(size_t q_size, const log_clock::duration& shutdown_duration) + void set_async_mode(size_t q_size) { std::lock_guard lock(_mutex); _async_mode = true; _async_q_size = q_size; - _async_shutdown_duration = shutdown_duration; } void set_sync_mode() @@ -153,7 +152,6 @@ private: level::level_enum _level = level::INFO; bool _async_mode = false; size_t _async_q_size = 0; - log_clock::duration _async_shutdown_duration; }; } } diff --git a/include/spdlog/details/spdlog_impl.h b/include/spdlog/details/spdlog_impl.h index a3a76111..6b559064 100644 --- a/include/spdlog/details/spdlog_impl.h +++ b/include/spdlog/details/spdlog_impl.h @@ -133,9 +133,9 @@ inline void spdlog::set_level(level::level_enum log_level) } -inline void spdlog::set_async_mode(size_t queue_size, const log_clock::duration& shutdown_duration) +inline void spdlog::set_async_mode(size_t queue_size) { - details::registry::instance().set_async_mode(queue_size, shutdown_duration); + details::registry::instance().set_async_mode(queue_size); } inline void spdlog::set_sync_mode() diff --git a/include/spdlog/details/stack_buf.h b/include/spdlog/details/stack_buf.h deleted file mode 100644 index 7b120967..00000000 --- a/include/spdlog/details/stack_buf.h +++ /dev/null @@ -1,134 +0,0 @@ -/*************************************************************************/ -/* spdlog - an extremely fast and easy to use c++11 logging library. */ -/* Copyright (c) 2014 Gabi Melman. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#pragma once - -#include -#include -#include -#include - -namespace spdlog -{ -namespace details -{ - -// Fast memory storage on the stack when possible or in std::vector -template -class stack_buf -{ -public: - static const unsigned short stack_size = STACK_SIZE; - stack_buf() :_v(), _stack_size(0) {} - ~stack_buf() = default; - stack_buf(const stack_buf& other) :stack_buf(other, delegate_copy_or_move {}) - {} - - stack_buf(stack_buf&& other) :stack_buf(other, delegate_copy_or_move {}) - { - other.clear(); - } - template - stack_buf& operator=(T1&& other) - { - _stack_size = other._stack_size; - if (other.vector_used()) - _v = std::forward(other)._v; - else - std::copy_n(other._stack_array.begin(), other._stack_size, _stack_array.begin()); - return *this; - } - - void append(const char* buf, std::size_t buf_size) - { - //If we are aleady using _v, forget about the stack - if (vector_used()) - { - _v.insert(_v.end(), buf, buf + buf_size); - } - //Try use the stack - else - { - if (_stack_size + buf_size <= STACK_SIZE) - { - std::memcpy(&_stack_array[_stack_size], buf, buf_size); - _stack_size += buf_size; - } - //Not enough stack space. Copy all to _v - else - { - _v.reserve(_stack_size + buf_size); - _v.insert(_v.end(), _stack_array.begin(), _stack_array.begin() + _stack_size); - _v.insert(_v.end(), buf, buf + buf_size); - } - } - } - - - void clear() - { - _stack_size = 0; - _v.clear(); - } - - const char* data() const - { - if (vector_used()) - return _v.data(); - else - return _stack_array.data(); - } - - std::size_t size() const - { - if (vector_used()) - return _v.size(); - else - return _stack_size; - } - -private: - struct delegate_copy_or_move {}; - template - stack_buf(T1&& other, delegate_copy_or_move) - { - _stack_size = other._stack_size; - if (other.vector_used()) - _v = std::forward(other)._v; - else - std::copy_n(other._stack_array.begin(), other._stack_size, _stack_array.begin()); - } - - inline bool vector_used() const - { - return !(_v.empty()); - } - - std::vector _v; - std::array _stack_array; - std::size_t _stack_size; -}; - -} -} //namespace spdlog { namespace details { diff --git a/include/spdlog/logger.h b/include/spdlog/logger.h index d52f1665..cc7fb58b 100644 --- a/include/spdlog/logger.h +++ b/include/spdlog/logger.h @@ -34,7 +34,6 @@ #include #include #include "sinks/base_sink.h" -#include "sinks/async_sink.h" #include "common.h" namespace spdlog @@ -66,17 +65,29 @@ public: //Stop logging void stop(); - template details::line_logger log(level::level_enum lvl, const Args&... args); - template details::line_logger log(const Args&... args); - template details::line_logger trace(const Args&... args); - template details::line_logger debug(const Args&... args); - template details::line_logger info(const Args&... args); - template details::line_logger notice(const Args&... args); - template details::line_logger warn(const Args&... args); - template details::line_logger error(const Args&... args); - template details::line_logger critical(const Args&... args); - template details::line_logger alert(const Args&... args); - template details::line_logger emerg(const Args&... args); + template details::line_logger log(const std::string& fmt, const Args&... args); + template details::line_logger trace(const std::string& fmt, const Args&... args); + template details::line_logger debug(const std::string& fmt, const Args&... args); + template details::line_logger info(const std::string& fmt, const Args&... args); + template details::line_logger notice(const std::string& fmt, const Args&... args); + template details::line_logger warn(const std::string& fmt, const Args&... args); + template details::line_logger error(const std::string& fmt, const Args&... args); + template details::line_logger critical(const std::string& fmt, const Args&... args); + template details::line_logger alert(const std::string& fmt, const Args&... args); + template details::line_logger emerg(const std::string& fmt, const Args&... args); + + //API to support logger.info() << ".." calls + + details::line_logger log(); + details::line_logger trace(); + details::line_logger debug(); + details::line_logger info(); + details::line_logger notice(); + details::line_logger warn(); + details::line_logger error(); + details::line_logger critical(); + details::line_logger alert(); + details::line_logger emerg(); void set_pattern(const std::string&); @@ -84,10 +95,12 @@ public: protected: - virtual void _log_msg(details::log_msg& msg); + virtual void _log_msg(details::log_msg&); virtual void _set_pattern(const std::string&); virtual void _set_formatter(formatter_ptr); virtual void _stop(); + details::line_logger _log(level::level_enum lvl); + template details::line_logger _log(level::level_enum lvl, const std::string& fmt, const Args&... args); friend details::line_logger; @@ -96,13 +109,6 @@ protected: formatter_ptr _formatter; std::atomic_int _level; -private: - void _variadic_log(details::line_logger& l); - template - inline void _variadic_log(spdlog::details::line_logger& l, const Last& last); - template - void _variadic_log(details::line_logger&l, const First& first, const Rest&... rest); - }; } diff --git a/include/spdlog/sinks/async_sink.h b/include/spdlog/sinks/async_sink.h deleted file mode 100644 index fe87c10b..00000000 --- a/include/spdlog/sinks/async_sink.h +++ /dev/null @@ -1,209 +0,0 @@ -/*************************************************************************/ -/* spdlog - an extremely fast and easy to use c++11 logging library. */ -/* Copyright (c) 2014 Gabi Melman. */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -// async sink: -// Process logs asynchronously using a back thread. -// -// If the internal queue of log messages reaches its max size, -// then the client call will block until there is more room. -// -// If the back thread throws during logging, a spdlog::spdlog_ex exception -// will be thrown in client's thread when tries to log the next message -#pragma once - -#include -#include -#include - -#include "./base_sink.h" -#include "../logger.h" -#include "../details/blocking_queue.h" -#include "../details/null_mutex.h" -#include "../details/log_msg.h" - - -namespace spdlog -{ -namespace sinks -{ - - -class async_sink : public base_sink < details::null_mutex > //single worker thread so null_mutex -{ -public: - using q_type = details::blocking_queue < std::unique_ptr > ; - - explicit async_sink(const q_type::size_type max_queue_size); - - //Stop logging and join the back thread - ~async_sink(); - void add_sink(sink_ptr sink); - void remove_sink(sink_ptr sink_ptr); - void set_formatter(formatter_ptr); - //Wait to remaining items (if any) in the queue to be written and shutdown - void shutdown(const log_clock::duration& timeout); - - - -protected: - void _sink_it(const details::log_msg& msg) override; - void _thread_loop(); - -private: - std::vector> _sinks; - std::atomic _active; - q_type _q; - std::thread _back_thread; - std::mutex _mutex; - //Last exception thrown from the back thread - std::shared_ptr _last_backthread_ex; - - formatter_ptr _formatter; - - //will throw last back thread exception or if backthread no active - void _push_sentry(); - - //Clear all remaining messages(if any), stop the _back_thread and join it - void _join(); - -}; -} -} - -/////////////////////////////////////////////////////////////////////////////// -// async_sink class implementation -/////////////////////////////////////////////////////////////////////////////// -inline spdlog::sinks::async_sink::async_sink(const q_type::size_type max_queue_size) - :_sinks(), - _active(true), - _q(max_queue_size), - _back_thread(&async_sink::_thread_loop, this) -{} - -inline spdlog::sinks::async_sink::~async_sink() -{ - _join(); -} - -inline void spdlog::sinks::async_sink::_sink_it(const details::log_msg& msg) -{ - _push_sentry(); - _q.push(std::unique_ptr(new details::log_msg(msg))); -} - - -inline void spdlog::sinks::async_sink::_thread_loop() -{ - std::chrono::seconds pop_timeout { 1 }; - - while (_active) - { - q_type::item_type msg; - if (_q.pop(msg, pop_timeout)) - { - if (!_active) - return; - _formatter->format(*msg); - for (auto &s : _sinks) - { - try - { - s->log(*msg); - } - catch (const std::exception& ex) - { - _last_backthread_ex = std::make_shared(ex.what()); - } - catch (...) - { - _last_backthread_ex = std::make_shared("Unknown exception"); - } - } - } - } -} - -inline void spdlog::sinks::async_sink::add_sink(spdlog::sink_ptr s) -{ - std::lock_guard guard(_mutex); - _sinks.push_back(s); -} - - -inline void spdlog::sinks::async_sink::remove_sink(spdlog::sink_ptr s) -{ - std::lock_guard guard(_mutex); - _sinks.erase(std::remove(_sinks.begin(), _sinks.end(), s), _sinks.end()); -} - - -inline void spdlog::sinks::async_sink::set_formatter(formatter_ptr msg_formatter) -{ - _formatter = msg_formatter; -} - - - -inline void spdlog::sinks::async_sink::shutdown(const log_clock::duration& timeout) -{ - if (timeout > std::chrono::milliseconds::zero()) - { - auto until = log_clock::now() + timeout; - while (_q.size() > 0 && log_clock::now() < until) - { - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - } - } - _join(); -} - - -inline void spdlog::sinks::async_sink::_push_sentry() -{ - if (_last_backthread_ex) - { - auto ex = std::move(_last_backthread_ex); - _last_backthread_ex.reset(); - throw *ex; - } - if (!_active) - throw(spdlog_ex("async_sink not active")); -} - - -inline void spdlog::sinks::async_sink::_join() -{ - _active = false; - if (_back_thread.joinable()) - { - try - { - _back_thread.join(); - } - catch (const std::system_error&) //Dont crash if thread not joinable - {} - } - -} - diff --git a/include/spdlog/sinks/file_sinks.h b/include/spdlog/sinks/file_sinks.h index 25325b1b..26d5d0e1 100644 --- a/include/spdlog/sinks/file_sinks.h +++ b/include/spdlog/sinks/file_sinks.h @@ -28,8 +28,7 @@ #include "base_sink.h" #include "../details/null_mutex.h" #include "../details/file_helper.h" -#include "../details/fast_oss.h" - +#include "../details/format.h" namespace spdlog @@ -100,12 +99,12 @@ protected: private: static std::string calc_filename(const std::string& filename, std::size_t index, const std::string& extension) { - details::fast_oss oss; + fmt::MemoryWriter w; if (index) - oss << filename << "." << index << "." << extension; + w.write("{}.{}.{}", filename, index, extension); else - oss << filename << "." << extension; - return oss.str(); + w.write("{}.{}", filename, extension); + return w.str(); } @@ -197,11 +196,9 @@ private: static std::string calc_filename(const std::string& basename, const std::string& extension) { std::tm tm = spdlog::details::os::localtime(); - details::fast_oss oss; - oss << basename << '.'; - oss << tm.tm_year + 1900 << '-' << std::setw(2) << std::setfill('0') << tm.tm_mon + 1 << '-' << tm.tm_mday; - oss << '.' << extension; - return oss.str(); + fmt::MemoryWriter w; + w.write("{}.{:04d}-{:02d}-{:02d}.{}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, extension); + return w.str(); } std::string _base_filename; diff --git a/include/spdlog/sinks/ostream_sink.h b/include/spdlog/sinks/ostream_sink.h index 5f33743b..68ecec87 100644 --- a/include/spdlog/sinks/ostream_sink.h +++ b/include/spdlog/sinks/ostream_sink.h @@ -24,7 +24,7 @@ #pragma once -#include +#include #include #include @@ -47,8 +47,7 @@ public: protected: virtual void _sink_it(const details::log_msg& msg) override { - auto& buf = msg.formatted.buf(); - _ostream.write(buf.data(), buf.size()); + _ostream.write(msg.formatted.data(), msg.formatted.size()); } std::ostream& _ostream; }; diff --git a/include/spdlog/spdlog.h b/include/spdlog/spdlog.h index 6db7ca3d..4841854e 100644 --- a/include/spdlog/spdlog.h +++ b/include/spdlog/spdlog.h @@ -65,9 +65,8 @@ void set_level(level::level_enum log_level); // // Turn on async mode and set the queue size for each async_logger -// shutdown_duration indicates max time to wait for the worker thread to log its messages before terminating. -void set_async_mode(size_t queue_size, const log_clock::duration& shutdown_duration = std::chrono::seconds(5)); +void set_async_mode(size_t queue_size); // Turn off async mode void set_sync_mode();