Commit e166fade authored by Rob Patro's avatar Rob Patro

Merge pull request #1 from gabime/master

Synch with gambie
parents 4e86da1b e0089dbd
...@@ -2,11 +2,9 @@ ...@@ -2,11 +2,9 @@
Very fast, header only, C++ logging library. Very fast, header only, C++ logging library.
## Install ## Install
Just copy the files to your build tree and use a C++11 compiler Just copy the files to your build tree and use a C++11 compiler
## Tested on: ## Tested on:
* gcc 4.8.1 and above * gcc 4.8.1 and above
* clang 3.5 * clang 3.5
...@@ -14,11 +12,13 @@ Just copy the files to your build tree and use a C++11 compiler ...@@ -14,11 +12,13 @@ Just copy the files to your build tree and use a C++11 compiler
* mingw with g++ 4.9.x * mingw with g++ 4.9.x
##Features ##Features
* Very fast - performance is the primary goal (see becnhmarks below). * Very fast - performance is the primary goal (see [becnhmarks](#benchmarks) below).
* Headers only. * Headers only.
* No dependencies. * No dependencies - just copy and use.
* Cross platform - Linux / Windows on 32/64 bits. * Cross platform - Linux / Windows on 32/64 bits.
* Variadic-template/stream call styles: ```logger.info("variadic", x, y) << "or stream" << z;``` * **new!** Feature rich [call style](#usage-example) using the excellent [cppformat](http://cppformat.github.io/) library.
* ostream call style is supported too.
* Extremely fast asynchronous mode (optional) - use of lockfree queues and other tricks to reach millions of calls per second from multiple threads.
* [Custom](https://github.com/gabime/spdlog/wiki/Custom-formatting) formatting. * [Custom](https://github.com/gabime/spdlog/wiki/Custom-formatting) formatting.
* Multi/Single threaded loggers. * Multi/Single threaded loggers.
* Various log targets: * Various log targets:
...@@ -27,22 +27,18 @@ Just copy the files to your build tree and use a C++11 compiler ...@@ -27,22 +27,18 @@ Just copy the files to your build tree and use a C++11 compiler
* Console logging. * Console logging.
* Linux syslog. * Linux syslog.
* Easily extendable with custom log targets (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface). * Easily extendable with custom log targets (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface).
* Optional async logging . * Severity based filtering - threshold levels can be modified in runtime.
* Log levels.
## Benchmarks ## 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: 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):
|threads|boost log|glog|g2log|spdlog|spdlog <sup>async mode</sup>| |threads|boost log|glog|g2log <sup>async mode</sup>|spdlog|spdlog <sup>async mode</sup>|
|-------|:-------:|:-----:|------:|------:|------:| |-------|:-------:|:-----:|------:|------:|------:|
|1|4.779s|1.109s|3.155s|0.947s|1.455s |1|4.779s|1.109s|3.155s|0.319s|0.212s
|10|15.151ss|3.546s|3.500s|1.549s|2.040s| |10|15.151ss|3.546s|3.500s|0.641s|0.199s|
## Usage Example ## Usage Example
...@@ -53,37 +49,61 @@ Below are some [benchmarks](bench) comparing the time needed to log 1,000,000 li ...@@ -53,37 +49,61 @@ Below are some [benchmarks](bench) comparing the time needed to log 1,000,000 li
int main(int, char* []) int main(int, char* [])
{ {
namespace spd = spdlog; namespace spd = spdlog;
try try
{ {
std::string filename = "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"); auto console = spd::stdout_logger_mt("console");
console->info("Welcome to spdlog!") ; console->info("Welcome to spdlog!") ;
console->info() << "Creating file " << filename << ".."; console->info("An info message example {}..", 1);
console->info() << "Streams are supported too " << 1;
auto file_logger = spd::rotating_logger_mt("file_logger", filename, 1024 * 1024 * 5, 3);
file_logger->info("Log file message number", 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);
for (int i = 0; i < 100; ++i) console->info("Support for floats {:03.2f}", 1.23456);
{ console->info("Positional args are {1} {0}..", "too", "supported");
auto square = i*i;
file_logger->info() << i << '*' << i << '=' << square << " (" << "0x" << std::hex << square << ")"; console->info("{:<30}", "left aligned");
} console->info("{:>30}", "right aligned");
console->info("{:^30}", "centered");
// Change log level to all loggers to warning and above
spd::set_level(spd::level::WARN); //Create a file rotating logger with 5mb size max and 3 rotated files
console->info("This should not be displayed"); auto file_logger = spd::rotating_logger_mt("file_logger", "logs/mylogfile", 1048576 * 5, 3);
console->warn("This should!"); file_logger->set_level(spd::level::INFO);
spd::set_level(spd::level::INFO); for(int i = 0; i < 10; ++i)
file_logger->info("{} * {} equals {:>10}", i, i, i*i);
// Change format pattern to all loggers
spd::set_pattern(" **** %Y-%m-%d %H:%M:%S.%e %l **** %v"); //Customize msg format for all messages
spd::get("console")->info("This is another message with different format"); spd::set_pattern("*** [%H:%M:%S %z] [thread %t] %v ***");
file_logger->info("This is another message with custom 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 very fast..
// Just call spdlog::set_async_mode(q_size) and all created loggers from now on will be asynchronous..
//
size_t q_size = 1048576; //queue size must be power of 2
spdlog::set_async_mode(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) catch (const spd::spdlog_ex& ex)
{ {
std::cout << "Log failed: " << ex.what() << std::endl; std::cout << "Log failed: " << ex.what() << std::endl;
} }
return 0;
} }
``` ```
CXX = g++ 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 CXX_RELEASE_FLAGS = -O3 -flto
......
...@@ -19,7 +19,7 @@ int main(int argc, char* argv[]) ...@@ -19,7 +19,7 @@ int main(int argc, char* argv[])
g2LogWorker g2log(argv[0], "logs"); g2LogWorker g2log(argv[0], "logs");
g2::initializeLogging(&g2log); g2::initializeLogging(&g2log);
std::atomic<int > msg_counter {0}; std::atomic<int > msg_counter {0};
vector<thread> threads; vector<thread> threads;
......
...@@ -5,10 +5,10 @@ ...@@ -5,10 +5,10 @@
int main(int, char* argv[]) int main(int, char* argv[])
{ {
int howmany = 1000000; int howmany = 1000000;
g2LogWorker g2log(argv[0], "logs"); g2LogWorker g2log(argv[0], "logs");
g2::initializeLogging(&g2log); g2::initializeLogging(&g2log);
for(int i = 0 ; i < howmany; ++i) for(int i = 0 ; i < howmany; ++i)
LOG(INFO) << "g2log message # " << i << ": This is some text for your pleasure"; LOG(INFO) << "g2log message # " << i << ": This is some text for your pleasure";
......
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
...@@ -2,29 +2,29 @@ ...@@ -2,29 +2,29 @@
echo "Running benchmakrs (all with 1000,000 writes to the logs folder)" echo "Running benchmakrs (all with 1000,000 writes to the logs folder)"
echo echo
echo "boost-bench (single thread).." echo "boost-bench (single thread).."
time ./boost-bench for i in {1..3}; do time ./boost-bench; done
rm logs/* rm -f logs/*
echo echo
echo echo
sleep 5 sleep 5
echo "glog-bench (single thread).." echo "glog-bench (single thread).."
time ./glog-bench for i in {1..3}; do time ./glog-bench; done
rm logs/* rm -f logs/*
echo echo
echo echo
sleep 5 sleep 5
echo "g2log-bench (single thread).." echo "g2log-bench (single thread).."
time ./g2log-bench for i in {1..3}; do time ./g2log-bench; done
rm logs/* rm -f logs/*
echo echo
echo echo
sleep 5 sleep 5
echo "spdlog-bench (single thread)" echo "spdlog-bench (single thread)"
time ./spdlog-bench for i in {1..3}; do time ./spdlog-bench; done
rm logs/* rm -f logs/*
echo echo
echo echo
sleep 5 sleep 5
...@@ -32,29 +32,29 @@ echo "------------------------------------" ...@@ -32,29 +32,29 @@ echo "------------------------------------"
echo "Multithreaded benchmarks.." echo "Multithreaded benchmarks.."
echo "------------------------------------" echo "------------------------------------"
echo "boost-bench-mt (10 threads, single logger)".. echo "boost-bench-mt (10 threads, single logger)"..
time ./boost-bench-mt for i in {1..3}; do ./boost-bench-mt; done
rm logs/* rm -f logs/*
echo echo
echo echo
sleep 5 sleep 5
echo "glog-bench-mt (10 threads, single logger)".. echo "glog-bench-mt (10 threads, single logger)"..
time ./glog-bench-mt for i in {1..3}; do time ./glog-bench-mt; done
rm logs/* rm -f logs/*
echo echo
echo echo
sleep 5 sleep 5
echo "g2log-bench-mt (10 threads, single logger)".. echo "g2log-bench-mt (10 threads, single logger)"..
time ./g2log-bench-mt for i in {1..3}; do time ./g2log-bench-mt; done
rm logs/* rm -f logs/*
echo echo
echo echo
sleep 5 sleep 5
echo "spdlog-bench-mt (10 threads, single logger)".. echo "spdlog-bench-mt (10 threads, single logger)"..
time ./spdlog-bench-mt for i in {1..3}; do time ./spdlog-bench-mt; done
rm logs/* rm -f logs/*
echo echo
echo echo
sleep 5 sleep 5
...@@ -64,14 +64,14 @@ echo "Async benchmarks.." ...@@ -64,14 +64,14 @@ echo "Async benchmarks.."
echo "------------------------------------" echo "------------------------------------"
echo "spdlog-bench-async (single thread)".. echo "spdlog-bench-async (single thread)"..
time ./spdlog-bench-async for i in {1..3}; do time ./spdlog-bench-async; done
rm logs/* rm -f logs/*
echo echo
echo echo
sleep 5 sleep 5
echo "spdlog-bench-mt-async (10 threads, single logger)".. 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
...@@ -4,15 +4,19 @@ ...@@ -4,15 +4,19 @@
int main(int, char* []) int main(int, char* [])
{ {
int howmany = 1000000; int howmany = 1048576;
namespace spd = spdlog; 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 ///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); auto logger = spd::rotating_logger_mt("file_logger", "logs/spd-sample", 10 *1024 * 1024 , 5);
logger->set_pattern("[%Y-%b-%d %T.%e]: %v"); logger->set_pattern("[%Y-%b-%d %T.%e]: %v");
for(int i = 0 ; i < howmany; ++i) for(int i = 0 ; i < howmany; ++i)
logger->info() << "spdlog message #" << i << ": This is some text for your pleasure"; 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; return 0;
} }
...@@ -14,10 +14,10 @@ int main(int argc, char* argv[]) ...@@ -14,10 +14,10 @@ int main(int argc, char* argv[])
if(argc > 1) if(argc > 1)
thread_count = atoi(argv[1]); thread_count = atoi(argv[1]);
int howmany = 1000000; int howmany = 1048576;
namespace spd = spdlog; 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 ///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); 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[]) ...@@ -45,6 +45,7 @@ int main(int argc, char* argv[])
t.join(); t.join();
}; };
spd::stop(); //because spdlog async logger waits for the back thread logger to finish all messages upon destrcuting,
return 0; //and we want to measure only the time it took to push those messages to the backthread..
abort();
} }
...@@ -17,8 +17,8 @@ int main(int argc, char* argv[]) ...@@ -17,8 +17,8 @@ int main(int argc, char* argv[])
int howmany = 1000000; int howmany = 1000000;
namespace spd = spdlog; namespace spd = spdlog;
///Create a file rotating logger with 5mb size max and 5 rotated files ///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); 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"); logger->set_pattern("[%Y-%b-%d %T.%e]: %v");
......
CXX = g++ 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_RELEASE_FLAGS = -O3 -flto
CXX_DEBUG_FLAGS= -g CXX_DEBUG_FLAGS= -g
......
...@@ -50,9 +50,9 @@ void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count ...@@ -50,9 +50,9 @@ void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {
int howmany = 1000000; int howmany = 1048576;
int threads = 10; int threads = 10;
bool auto_flush = true; bool auto_flush = false;
int file_size = 30 * 1024 * 1024; int file_size = 30 * 1024 * 1024;
int rotating_files = 5; int rotating_files = 5;
...@@ -64,6 +64,7 @@ int main(int argc, char* argv[]) ...@@ -64,6 +64,7 @@ int main(int argc, char* argv[])
if (argc > 2) if (argc > 2)
threads = atoi(argv[2]); threads = atoi(argv[2]);
cout << "*******************************************************************************\n"; cout << "*******************************************************************************\n";
cout << "Single thread, " << format(howmany) << " iterations, auto flush=" << auto_flush << endl; cout << "Single thread, " << format(howmany) << " iterations, auto flush=" << auto_flush << endl;
cout << "*******************************************************************************\n"; cout << "*******************************************************************************\n";
...@@ -85,18 +86,20 @@ int main(int argc, char* argv[]) ...@@ -85,18 +86,20 @@ int main(int argc, char* argv[])
auto daily_mt = spdlog::daily_logger_mt("daily_mt", "logs/daily_mt", auto_flush); auto daily_mt = spdlog::daily_logger_mt("daily_mt", "logs/daily_mt", auto_flush);
bench_mt(howmany, daily_mt, threads); bench_mt(howmany, daily_mt, threads);
bench(howmany, spdlog::create<null_sink_st>("null_mt")); bench(howmany, spdlog::create<null_sink_st>("null_mt"));
cout << "\n*******************************************************************************\n"; cout << "\n*******************************************************************************\n";
cout << "async logging.. " << threads << " threads sharing same logger, " << format(howmany) << " iterations, auto_flush=" << auto_flush << endl; 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) catch (std::exception &ex)
{ {
...@@ -113,7 +116,7 @@ void bench(int howmany, std::shared_ptr<spdlog::logger> log) ...@@ -113,7 +116,7 @@ void bench(int howmany, std::shared_ptr<spdlog::logger> log)
auto start = system_clock::now(); auto start = system_clock::now();
for (auto i = 0; i < howmany; ++i) for (auto i = 0; i < howmany; ++i)
{ {
log->info("Hello logger: msg number ", i); log->info("Hello logger: msg number {}", i);
} }
...@@ -138,7 +141,7 @@ void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count ...@@ -138,7 +141,7 @@ void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count
{ {
int counter = ++msg_counter; int counter = ++msg_counter;
if (counter > howmany) break; if (counter > howmany) break;
log->info("Hello logger: msg number ") << counter; log->info("Hello logger: msg number {}", counter);
} }
})); }));
} }
...@@ -154,4 +157,3 @@ void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count ...@@ -154,4 +157,3 @@ void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log, int thread_count
auto delta_d = duration_cast<duration<double>> (delta).count(); auto delta_d = duration_cast<duration<double>> (delta).count();
cout << format(int(howmany / delta_d)) << "/sec" << endl; cout << format(int(howmany / delta_d)) << "/sec" << endl;
} }
...@@ -44,17 +44,25 @@ int main(int, char* []) ...@@ -44,17 +44,25 @@ int main(int, char* [])
//Create console, multithreaded logger //Create console, multithreaded logger
auto console = spd::stdout_logger_mt("console"); auto console = spd::stdout_logger_mt("console");
console->info("Welcome to spdlog!") ; console->info("Welcome to spdlog!") ;
console->info("An info message example", "...", 1, 2, 3.5); console->info("An info message example {}..", 1);
console->info() << "Streams are supported too " << std::setw(5) << std::setfill('0') << 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:08x}; 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 //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); 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("Log file message number", 1);
{ for(int i = 0; i < 10; ++i)
file_logger->info(i, "in hex is", "0x") << std::hex << std::uppercase << i; file_logger->info("{} * {} equals {:>10}", i, i, i*i);
}
spd::set_pattern("*** [%H:%M:%S %z] [thread %t] %v ***"); spd::set_pattern("*** [%H:%M:%S %z] [thread %t] %v ***");
file_logger->info("This is another message with custom format"); file_logger->info("This is another message with custom format");
...@@ -65,12 +73,12 @@ int main(int, char* []) ...@@ -65,12 +73,12 @@ int main(int, char* [])
// //
// Asynchronous logging is easy.. // Asynchronous logging is easy..
// Just call spdlog::set_async_mode(max_q_size) and all created loggers from now on will be asynchronous.. // Just call spdlog::set_async_mode(q_size) and all created loggers from now on will be asynchronous..
// Note: queue size must be power of 2!
// //
size_t max_q_size = 1048576;
size_t max_q_size = 100000;
spdlog::set_async_mode(max_q_size); spdlog::set_async_mode(max_q_size);
auto async_file= spd::daily_logger_st("async_file_logger", "async_" + filename); 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!"; async_file->info() << "This is async log.." << "Should be very fast!";
// //
......
...@@ -24,7 +24,6 @@ ...@@ -24,7 +24,6 @@
#pragma once #pragma once
#include <iostream>
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
#include <locale> #include <locale>
......
...@@ -24,10 +24,15 @@ ...@@ -24,10 +24,15 @@
#pragma once #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: // Upon each log write the logger:
// 1. Checks if its log level is enough to log the message // 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) // 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 <chrono> #include <chrono>
#include "common.h" #include "common.h"
...@@ -37,18 +42,18 @@ ...@@ -37,18 +42,18 @@
namespace spdlog namespace spdlog
{ {
namespace sinks namespace details
{ {
class async_sink; class async_log_helper;
} }
class async_logger :public logger class async_logger :public logger
{ {
public: public:
template<class It> template<class It>
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& 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, const log_clock::duration& shutdown_duration); 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, const log_clock::duration& shutdown_duration); async_logger(const std::string& logger_name, sink_ptr single_sink, size_t queue_size);
protected: protected:
...@@ -58,8 +63,7 @@ protected: ...@@ -58,8 +63,7 @@ protected:
void _stop() override; void _stop() override;
private: private:
log_clock::duration _shutdown_duration; std::unique_ptr<details::async_log_helper> _async_log_helper;
std::unique_ptr<sinks::async_sink> _as;
}; };
} }
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/ /*************************************************************************/
// async sink: // async log helper :
// Process logs asynchronously using a back thread. // Process logs asynchronously using a back thread.
// //
// If the internal queue of log messages reaches its max size, // If the internal queue of log messages reaches its max size,
...@@ -30,62 +30,120 @@ ...@@ -30,62 +30,120 @@
// //
// If the back thread throws during logging, a spdlog::spdlog_ex exception // 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 // will be thrown in client's thread when tries to log the next message
#pragma once #pragma once
#include <thread>
#include <chrono> #include <chrono>
#include <thread>
#include <atomic> #include <atomic>
#include "./base_sink.h" #include "../common.h"
#include "../logger.h" #include "../sinks/sink.h"
#include "../details/blocking_queue.h" #include "./mpmc_bounded_q.h"
#include "../details/null_mutex.h" #include "./log_msg.h"
#include "../details/log_msg.h" #include "./format.h"
namespace spdlog namespace spdlog
{ {
namespace sinks namespace details
{ {
class async_sink : public base_sink < details::null_mutex > //single worker thread so null_mutex 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: public:
using q_type = details::blocking_queue < std::unique_ptr<details::log_msg> > ;
explicit async_sink(const q_type::size_type max_queue_size); using item_type = async_msg;
using q_type = details::mpmc_bounded_queue<item_type>;
//Stop logging and join the back thread using clock = std::chrono::steady_clock;
~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);
async_log_helper(formatter_ptr formatter, const std::vector<sink_ptr>& 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);
protected:
void _sink_it(const details::log_msg& msg) override;
void _thread_loop();
private: private:
std::vector<std::shared_ptr<sink>> _sinks;
std::atomic<bool> _active; std::atomic<bool> _active;
formatter_ptr _formatter;
std::vector<std::shared_ptr<sinks::sink>> _sinks;
q_type _q; q_type _q;
std::thread _back_thread; std::thread _worker_thread;
std::mutex _mutex;
//Last exception thrown from the back thread
std::shared_ptr<spdlog_ex> _last_backthread_ex;
formatter_ptr _formatter; // last exception thrown from the worker thread
std::shared_ptr<spdlog_ex> _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);
//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 // clear all remaining messages(if any), stop the _worker_thread and join it
void _join(); void join_worker();
}; };
} }
...@@ -94,116 +152,138 @@ private: ...@@ -94,116 +152,138 @@ private:
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// async_sink class implementation // async_sink class implementation
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
inline spdlog::sinks::async_sink::async_sink(const q_type::size_type max_queue_size) inline spdlog::details::async_log_helper::async_log_helper(formatter_ptr formatter, const std::vector<sink_ptr>& sinks, size_t queue_size):
:_sinks(), _active(true),
_active(true), _formatter(formatter),
_q(max_queue_size), _sinks(sinks),
_back_thread(&async_sink::_thread_loop, this) _q(queue_size),
_worker_thread(&async_log_helper::worker_loop, this)
{} {}
inline spdlog::sinks::async_sink::~async_sink() inline spdlog::details::async_log_helper::~async_log_helper()
{ {
_join(); join_worker();
} }
inline void spdlog::sinks::async_sink::_sink_it(const details::log_msg& msg)
//Try to push and block until succeeded
inline void spdlog::details::async_log_helper::log(const details::log_msg& msg)
{ {
_push_sentry(); throw_if_bad_worker();
_q.push(std::unique_ptr<details::log_msg>(new details::log_msg(msg))); 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 void spdlog::sinks::async_sink::_thread_loop()
inline bool spdlog::details::async_log_helper::process_next_msg(clock::time_point& last_pop)
{ {
std::chrono::seconds pop_timeout { 1 };
while (_active) async_msg incoming_async_msg;
log_msg incoming_log_msg;
if (_q.dequeue(incoming_async_msg))
{ {
q_type::item_type msg; last_pop = clock::now();
if (_q.pop(msg, pop_timeout)) try
{ {
if (!_active) incoming_async_msg.fill_log_msg(incoming_log_msg);
return; _formatter->format(incoming_log_msg);
_formatter->format(*msg);
for (auto &s : _sinks) for (auto &s : _sinks)
{ s->log(incoming_log_msg);
try }
{ catch (const std::exception& ex)
s->log(*msg); {
} _last_workerthread_ex = std::make_shared<spdlog_ex>(std::string("async_logger worker thread exception: ") + ex.what());
catch (const std::exception& ex)
{
_last_backthread_ex = std::make_shared<spdlog_ex>(ex.what());
}
catch (...)
{
_last_backthread_ex = std::make_shared<spdlog_ex>("Unknown exception");
}
}
} }
catch (...)
{
_last_workerthread_ex = std::make_shared<spdlog_ex>("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::sinks::async_sink::add_sink(spdlog::sink_ptr s) inline void spdlog::details::async_log_helper::set_formatter(formatter_ptr msg_formatter)
{ {
std::lock_guard<std::mutex> guard(_mutex); _formatter = msg_formatter;
_sinks.push_back(s);
} }
inline void spdlog::sinks::async_sink::remove_sink(spdlog::sink_ptr s) // 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)
{ {
std::lock_guard<std::mutex> guard(_mutex); using std::chrono::milliseconds;
_sinks.erase(std::remove(_sinks.begin(), _sinks.end(), s), _sinks.end()); using namespace std::this_thread;
}
auto time_since_op = clock::now() - last_op_time;
inline void spdlog::sinks::async_sink::set_formatter(formatter_ptr msg_formatter) //spin upto 1 ms
{ if (time_since_op <= milliseconds(1))
_formatter = msg_formatter; return;
}
// yield upto 10ms
if (time_since_op <= milliseconds(10))
return yield();
inline void spdlog::sinks::async_sink::shutdown(const log_clock::duration& timeout) // sleep for half of duration since last op
{ if (time_since_op <= milliseconds(100))
if (timeout > std::chrono::milliseconds::zero()) return sleep_for(time_since_op / 2);
{
auto until = log_clock::now() + timeout;
while (_q.size() > 0 && log_clock::now() < until)
{
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
}
_join();
}
return sleep_for(milliseconds(100));
}
inline void spdlog::sinks::async_sink::_push_sentry() //throw if the worker thread threw an exception or not active
inline void spdlog::details::async_log_helper::throw_if_bad_worker()
{ {
if (_last_backthread_ex) if (_last_workerthread_ex)
{ {
auto ex = std::move(_last_backthread_ex); auto ex = std::move(_last_workerthread_ex);
_last_backthread_ex.reset(); _last_workerthread_ex.reset();
throw *ex; throw *ex;
} }
if (!_active) if (!_active)
throw(spdlog_ex("async_sink not active")); throw(spdlog_ex("async logger is not active"));
} }
inline void spdlog::sinks::async_sink::_join() inline void spdlog::details::async_log_helper::join_worker()
{ {
_active = false; _active = false;
if (_back_thread.joinable())
try
{ {
try _worker_thread.join();
{
_back_thread.join();
}
catch (const std::system_error&) //Dont crash if thread not joinable
{}
} }
catch (const std::system_error&) //Dont crash if thread not joinable
{}
} }
...@@ -25,8 +25,7 @@ ...@@ -25,8 +25,7 @@
#pragma once #pragma once
#include <memory> #include "./async_log_helper.h"
#include "../sinks/async_sink.h"
// //
// Async Logger implementation // Async Logger implementation
...@@ -35,38 +34,29 @@ ...@@ -35,38 +34,29 @@
template<class It> template<class It>
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), logger(logger_name, begin, end),
_shutdown_duration(shutdown_duration), _async_log_helper(new details::async_log_helper(_formatter, _sinks, queue_size))
_as(std::unique_ptr<sinks::async_sink>(new sinks::async_sink(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) : 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, shutdown_duration) {} 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) : 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, shutdown_duration) {} 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) inline void spdlog::async_logger::_set_formatter(spdlog::formatter_ptr msg_formatter)
{ {
_formatter = 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) inline void spdlog::async_logger::_set_pattern(const std::string& pattern)
{ {
_formatter = std::make_shared<pattern_formatter>(pattern); _formatter = std::make_shared<pattern_formatter>(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) ...@@ -74,5 +64,9 @@ inline void spdlog::async_logger::_set_pattern(const std::string& pattern)
inline void spdlog::async_logger::_stop() inline void spdlog::async_logger::_stop()
{ {
set_level(level::OFF); set_level(level::OFF);
_as->shutdown(_shutdown_duration); }
inline void spdlog::async_logger::_log_msg(details::log_msg& msg)
{
_async_log_helper->log(msg);
} }
/*************************************************************************/
/* 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 <chrono>
#include <memory>
#include <queue>
#include <mutex>
#include <condition_variable>
namespace spdlog
{
namespace details
{
template<typename T>
class blocking_queue
{
public:
using queue_type = std::queue<T>;
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<std::mutex> 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<typename Duration_Rep, typename Duration_Period, typename TT>
bool push(TT&& item, const std::chrono::duration<Duration_Rep, Duration_Period>& timeout)
{
{
std::unique_lock<std::mutex> 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<TT>(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<typename TT>
void push(TT&& item)
{
while (!push(std::forward<TT>(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<class Duration_Rep, class Duration_Period>
bool pop(T& item, const std::chrono::duration<Duration_Rep, Duration_Period>& timeout)
{
{
std::unique_lock<std::mutex> 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<std::mutex> ul(_mutex);
queue_type().swap(_q);
}
_item_popped_cond.notify_all();
}
private:
size_type _max_size;
std::queue<T> _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;
}
};
}
}
/*************************************************************************/
/* 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 <string>
//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<unsigned int>((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<char>('0' + (val % 10));
val /= 10;
}
while (c >= s.data())
*c-- = '0';
return s;
}
}
}
/*************************************************************************/
/* 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 <ostream>
#include <iomanip>
#include "fast_istostr.h"
#include "stack_buf.h"
#include<iostream>
namespace spdlog
{
namespace details
{
class stack_devicebuf :public std::streambuf
{
public:
static const unsigned short stack_size = 256;
using stackbuf_t = stack_buf<stack_size>;
stack_devicebuf() = default;
~stack_devicebuf() = default;
stack_devicebuf(const stack_devicebuf& other) :std::basic_streambuf<char>(), _stackbuf(other._stackbuf)
{}
stack_devicebuf(stack_devicebuf&& other):
std::basic_streambuf<char>(),
_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<unsigned int>(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<char>(), std::ostream(&_dev), _dev(other._dev)
{}
fast_oss(fast_oss&& other) :std::basic_ios<char>(), 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;
};
}
}
...@@ -52,7 +52,7 @@ public: ...@@ -52,7 +52,7 @@ public:
explicit file_helper(bool auto_flush): explicit file_helper(bool auto_flush):
_fd(nullptr), _fd(nullptr),
_auto_flush(auto_flush) _auto_flush(auto_flush)
{}; {};
file_helper(const file_helper&) = delete; file_helper(const file_helper&) = delete;
file_helper& operator=(const file_helper&) = delete; file_helper& operator=(const file_helper&) = delete;
...@@ -99,14 +99,15 @@ public: ...@@ -99,14 +99,15 @@ public:
void write(const log_msg& msg) void write(const log_msg& msg)
{ {
auto& buf = msg.formatted.buf();
size_t size = buf.size(); size_t size = msg.formatted.size();
if(std::fwrite(buf.data(), sizeof(char), size, _fd) != size) auto data = msg.formatted.data();
if(std::fwrite(data, 1, size, _fd) != size)
throw spdlog_ex("Failed writing to file " + _filename); throw spdlog_ex("Failed writing to file " + _filename);
if(_auto_flush) if(_auto_flush)
std::fflush(_fd); std::fflush(_fd);
} }
const std::string& filename() const const std::string& filename() const
......
/*
Modified version of cppformat formatting library
Orginal license:
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.
*/
#include <string.h>
#include <cctype>
#include <cerrno>
#include <climits>
#include <cmath>
#include <cstdarg>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# ifdef __MINGW32__
# include <cstring>
# endif
# include <windows.h>
#endif
using spdlog::details::fmt::LongLong;
using spdlog::details::fmt::ULongLong;
using spdlog::details::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 <bool IsSigned>
struct IntChecker {
template <typename T>
static bool fits_in_int(T value) {
unsigned max = INT_MAX;
return value <= max;
}
};
template <>
struct IntChecker<true> {
template <typename T>
static bool fits_in_int(T value) {
return value >= INT_MIN && value <= INT_MAX;
}
};
const char RESET_COLOR[] = "\x1b[0m";
typedef void(*FormatFunc)(spdlog::details::fmt::Writer &, int, spdlog::details::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.
FMT_FUNC 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;
}
FMT_FUNC void format_error_code(spdlog::details::fmt::Writer &out, int error_code,
spdlog::details::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 FMT_ERROR[] = "error ";
spdlog::details::fmt::internal::IntTraits<int>::MainType ec_value = error_code;
// Subtract 2 to account for terminating null characters in SEP and FMT_ERROR.
std::size_t error_code_size =
sizeof(SEP) + sizeof(FMT_ERROR) + spdlog::details::fmt::internal::count_digits(ec_value) - 2;
if (message.size() <= spdlog::details::fmt::internal::INLINE_BUFFER_SIZE - error_code_size)
out << message << SEP;
out << FMT_ERROR << error_code;
assert(out.size() <= spdlog::details::fmt::internal::INLINE_BUFFER_SIZE);
}
FMT_FUNC void report_error(FormatFunc func,
int error_code, spdlog::details::fmt::StringRef message) FMT_NOEXCEPT(true) {
spdlog::details::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 spdlog::details::fmt::internal::ArgVisitor<IsZeroInt, bool> {
public:
template <typename T>
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 <typename Char>
FMT_FUNC 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(spdlog::details::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 =
spdlog::details::fmt::format("format specifier '{}' requires numeric argument", spec);
FMT_THROW(spdlog::details::fmt::FormatError(message));
}
}
template <typename Char>
FMT_FUNC void check_sign(const Char *&s, const Arg &arg) {
char sign = static_cast<char>(*s);
require_numeric_argument(arg, sign);
if (arg.type == Arg::UINT || arg.type == Arg::ULONG_LONG) {
FMT_THROW(spdlog::details::fmt::FormatError(spdlog::details::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 spdlog::details::fmt::internal::ArgVisitor<WidthHandler, unsigned> {
private:
spdlog::details::fmt::FormatSpec &spec_;
public:
explicit WidthHandler(spdlog::details::fmt::FormatSpec &spec) : spec_(spec) {}
unsigned visit_unhandled_arg() {
FMT_THROW(spdlog::details::fmt::FormatError("width is not integer"));
return 0;
}
template <typename T>
unsigned visit_any_int(T value) {
typedef typename spdlog::details::fmt::internal::IntTraits<T>::MainType UnsignedType;
UnsignedType width = value;
if (spdlog::details::fmt::internal::is_negative(value)) {
spec_.align_ = spdlog::details::fmt::ALIGN_LEFT;
width = 0 - width;
}
if (width > INT_MAX)
FMT_THROW(spdlog::details::fmt::FormatError("number is too big"));
return static_cast<unsigned>(width);
}
};
class PrecisionHandler :
public spdlog::details::fmt::internal::ArgVisitor<PrecisionHandler, int> {
public:
unsigned visit_unhandled_arg() {
FMT_THROW(spdlog::details::fmt::FormatError("precision is not integer"));
return 0;
}
template <typename T>
int visit_any_int(T value) {
if (!IntChecker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
FMT_THROW(spdlog::details::fmt::FormatError("number is too big"));
return static_cast<int>(value);
}
};
// Converts an integer argument to an integral type T for printf.
template <typename T>
class ArgConverter : public spdlog::details::fmt::internal::ArgVisitor<ArgConverter<T>, void> {
private:
spdlog::details::fmt::internal::Arg &arg_;
wchar_t type_;
public:
ArgConverter(spdlog::details::fmt::internal::Arg &arg, wchar_t type)
: arg_(arg), type_(type) {}
template <typename U>
void visit_any_int(U value) {
bool is_signed = type_ == 'd' || type_ == 'i';
using spdlog::details::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<int>(static_cast<T>(value));
}
else {
arg_.type = Arg::UINT;
arg_.uint_value = static_cast<unsigned>(
static_cast<typename spdlog::details::fmt::internal::MakeUnsigned<T>::Type>(value));
}
}
else {
if (is_signed) {
arg_.type = Arg::LONG_LONG;
arg_.long_long_value =
static_cast<typename spdlog::details::fmt::internal::MakeUnsigned<U>::Type>(value);
}
else {
arg_.type = Arg::ULONG_LONG;
arg_.ulong_long_value =
static_cast<typename spdlog::details::fmt::internal::MakeUnsigned<U>::Type>(value);
}
}
}
};
// Converts an integer argument to char for printf.
class CharConverter : public spdlog::details::fmt::internal::ArgVisitor<CharConverter, void> {
private:
spdlog::details::fmt::internal::Arg &arg_;
public:
explicit CharConverter(spdlog::details::fmt::internal::Arg &arg) : arg_(arg) {}
template <typename T>
void visit_any_int(T value) {
arg_.type = Arg::CHAR;
arg_.int_value = static_cast<char>(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 <typename Char>
Arg::StringValue<Char> ignore_incompatible_str(Arg::StringValue<wchar_t>);
template <>
inline Arg::StringValue<char> ignore_incompatible_str(
Arg::StringValue<wchar_t>) {
return Arg::StringValue<char>();
}
template <>
inline Arg::StringValue<wchar_t> ignore_incompatible_str(
Arg::StringValue<wchar_t> s) {
return s;
}
} // namespace
FMT_FUNC void spdlog::details::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 <typename T>
FMT_FUNC int spdlog::details::fmt::internal::CharTraits<char>::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 <typename T>
FMT_FUNC int spdlog::details::fmt::internal::CharTraits<wchar_t>::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 <typename T>
const char spdlog::details::fmt::internal::BasicData<T>::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 <typename T>
const uint32_t spdlog::details::fmt::internal::BasicData<T>::POWERS_OF_10_32[] = {
0, FMT_POWERS_OF_10(1)
};
template <typename T>
const uint64_t spdlog::details::fmt::internal::BasicData<T>::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 spdlog::details::fmt::internal::report_unknown_type(char code, const char *type) {
if (std::isprint(static_cast<unsigned char>(code))) {
FMT_THROW(spdlog::details::fmt::FormatError(
spdlog::details::fmt::format("unknown format code '{}' for {}", code, type)));
}
FMT_THROW(spdlog::details::fmt::FormatError(
spdlog::details::fmt::format("unknown format code '\\x{:02x}' for {}",
static_cast<unsigned>(code), type)));
}
#ifdef _WIN32
FMT_FUNC spdlog::details::fmt::internal::UTF8ToUTF16::UTF8ToUTF16(spdlog::details::fmt::StringRef s) {
int length = MultiByteToWideChar(
CP_UTF8, MB_ERR_INVALID_CHARS, s.c_str(), -1, 0, 0);
static const char FMT_ERROR[] = "cannot convert string from UTF-8 to UTF-16";
if (length == 0)
FMT_THROW(WindowsError(GetLastError(), FMT_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(), FMT_ERROR));
}
FMT_FUNC spdlog::details::fmt::internal::UTF16ToUTF8::UTF16ToUTF8(spdlog::details::fmt::WStringRef s) {
if (int error_code = convert(s)) {
FMT_THROW(WindowsError(error_code,
"cannot convert string from UTF-16 to UTF-8"));
}
}
FMT_FUNC int spdlog::details::fmt::internal::UTF16ToUTF8::convert(spdlog::details::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;
}
FMT_FUNC void spdlog::details::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 spdlog::details::fmt::internal::format_system_error(
spdlog::details::fmt::Writer &out, int error_code,
spdlog::details::fmt::StringRef message) FMT_NOEXCEPT(true) {
FMT_TRY {
MemoryBuffer<char, INLINE_BUFFER_SIZE> 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
FMT_FUNC void spdlog::details::fmt::internal::format_windows_error(
spdlog::details::fmt::Writer &out, int error_code,
spdlog::details::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<LPWSTR>(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 <typename Char>
class spdlog::details::fmt::internal::ArgFormatter :
public spdlog::details::fmt::internal::ArgVisitor<spdlog::details::fmt::internal::ArgFormatter<Char>, void> {
private:
spdlog::details::fmt::BasicFormatter<Char> &formatter_;
spdlog::details::fmt::BasicWriter<Char> &writer_;
spdlog::details::fmt::FormatSpec &spec_;
const Char *format_;
public:
ArgFormatter(
spdlog::details::fmt::BasicFormatter<Char> &f, spdlog::details::fmt::FormatSpec &s, const Char *fmt)
: formatter_(f), writer_(f.writer()), spec_(s), format_(fmt) {}
template <typename T>
void visit_any_int(T value) {
writer_.write_int(value, spec_);
}
template <typename T>
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 spdlog::details::fmt::BasicWriter<Char>::CharPtr CharPtr;
CharPtr out = CharPtr();
if (spec_.width_ > 1) {
Char fill = static_cast<Char>(spec_.fill());
out = writer_.grow_buffer(spec_.width_);
if (spec_.align_ == spdlog::details::fmt::ALIGN_RIGHT) {
std::fill_n(out, spec_.width_ - 1, fill);
out += spec_.width_ - 1;
}
else if (spec_.align_ == spdlog::details::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<Char>(value);
}
void visit_string(Arg::StringValue<char> value) {
writer_.write_str(value, spec_);
}
void visit_wstring(Arg::StringValue<wchar_t> value) {
writer_.write_str(ignore_incompatible_str<Char>(value), spec_);
}
void visit_pointer(const void *value) {
if (spec_.type_ && spec_.type_ != 'p')
spdlog::details::fmt::internal::report_unknown_type(spec_.type_, "pointer");
spec_.flags_ = spdlog::details::fmt::HASH_FLAG;
spec_.type_ = 'x';
writer_.write_int(reinterpret_cast<uintptr_t>(value), spec_);
}
void visit_custom(Arg::CustomValue c) {
c.format(&formatter_, c.value, &format_);
}
};
template <typename Char>
template <typename StrChar>
FMT_FUNC void spdlog::details::fmt::BasicWriter<Char>::write_str(
const Arg::StringValue<StrChar> &str, const FormatSpec &spec) {
// Check if StrChar is convertible to Char.
internal::CharTraits<Char>::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<StrChar>::length(s);
}
write_str(s, size, spec);
}
template <typename Char>
inline Arg spdlog::details::fmt::BasicFormatter<Char>::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 spdlog::details::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 spdlog::details::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 spdlog::details::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 <typename Char>
FMT_FUNC void spdlog::details::fmt::internal::PrintfFormatter<Char>::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 <typename Char>
FMT_FUNC Arg spdlog::details::fmt::internal::PrintfFormatter<Char>::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 <typename Char>
FMT_FUNC unsigned spdlog::details::fmt::internal::PrintfFormatter<Char>::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 <typename Char>
FMT_FUNC void spdlog::details::fmt::internal::PrintfFormatter<Char>::format(
BasicWriter<Char> &writer, BasicStringRef<Char> 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<signed char>(arg, *++s).visit(arg);
else
ArgConverter<short>(arg, *s).visit(arg);
break;
case 'l':
if (*s == 'l')
ArgConverter<spdlog::details::fmt::LongLong>(arg, *++s).visit(arg);
else
ArgConverter<long>(arg, *s).visit(arg);
break;
case 'j':
ArgConverter<intmax_t>(arg, *s).visit(arg);
break;
case 'z':
ArgConverter<size_t>(arg, *s).visit(arg);
break;
case 't':
ArgConverter<ptrdiff_t>(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<int>(arg, *s).visit(arg);
}
// Parse type.
if (!*s)
FMT_THROW(FormatError("invalid format string"));
spec.type_ = static_cast<char>(*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<Char>::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<Char>(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<Char>(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<uintptr_t>(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 <typename Char>
FMT_FUNC const Char *spdlog::details::fmt::BasicFormatter<Char>::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<int>(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<char>(*s++);
}
if (*s++ != '}')
FMT_THROW(FormatError("missing '}' in format string"));
start_ = s;
// Format argument.
internal::ArgFormatter<Char>(*this, spec, s - 1).visit(arg);
return s;
}
template <typename Char>
FMT_FUNC void spdlog::details::fmt::BasicFormatter<Char>::format(
BasicStringRef<Char> 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 spdlog::details::fmt::report_system_error(
int error_code, spdlog::details::fmt::StringRef message) FMT_NOEXCEPT(true) {
report_error(internal::format_system_error, error_code, message);
}
#ifdef _WIN32
FMT_FUNC void spdlog::details::fmt::report_windows_error(
int error_code, spdlog::details::fmt::StringRef message) FMT_NOEXCEPT(true) {
report_error(internal::format_windows_error, error_code, message);
}
#endif
FMT_FUNC void spdlog::details::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 spdlog::details::fmt::print(StringRef format_str, ArgList args) {
print(stdout, format_str, args);
}
FMT_FUNC void spdlog::details::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 spdlog::details::fmt::print_colored(Color c, StringRef format, ArgList args) {
char escape[] = "\x1b[30m";
escape[3] = '0' + static_cast<char>(c);
std::fputs(escape, stdout);
print(format, args);
std::fputs(RESET_COLOR, stdout);
}
FMT_FUNC int spdlog::details::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<int>(size);
}
// Explicit instantiations for char.
template const char *spdlog::details::fmt::BasicFormatter<char>::format(
const char *&format_str, const spdlog::details::fmt::internal::Arg &arg);
template void spdlog::details::fmt::BasicFormatter<char>::format(
BasicStringRef<char> format, const ArgList &args);
template void spdlog::details::fmt::internal::PrintfFormatter<char>::format(
BasicWriter<char> &writer, BasicStringRef<char> format, const ArgList &args);
template int spdlog::details::fmt::internal::CharTraits<char>::format_float(
char *buffer, std::size_t size, const char *format,
unsigned width, int precision, double value);
template int spdlog::details::fmt::internal::CharTraits<char>::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 *spdlog::details::fmt::BasicFormatter<wchar_t>::format(
const wchar_t *&format_str, const spdlog::details::fmt::internal::Arg &arg);
template void spdlog::details::fmt::BasicFormatter<wchar_t>::format(
BasicStringRef<wchar_t> format, const ArgList &args);
template void spdlog::details::fmt::internal::PrintfFormatter<wchar_t>::format(
BasicWriter<wchar_t> &writer, BasicStringRef<wchar_t> format,
const ArgList &args);
template int spdlog::details::fmt::internal::CharTraits<wchar_t>::format_float(
wchar_t *buffer, std::size_t size, const wchar_t *format,
unsigned width, int precision, double value);
template int spdlog::details::fmt::internal::CharTraits<wchar_t>::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
/*
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 <stdint.h>
#include <cassert>
#include <cmath>
#include <cstddef> // for std::ptrdiff_t
#include <cstdio>
#include <algorithm>
#include <limits>
#include <stdexcept>
#include <string>
#include <sstream>
#if _SECURE_SCL
# include <iterator>
#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 <utility> // 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 spdlog {
namespace details {
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 <typename Char>
class BasicWriter;
typedef BasicWriter<char> Writer;
typedef BasicWriter<wchar_t> WWriter;
template <typename Char>
class BasicFormatter;
template <typename Char, typename T>
void format(BasicFormatter<Char> &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<char> |
+------------+-------------------------+
| WStringRef | BasicStringRef<wchar_t> |
+------------+-------------------------+
This class is most useful as a parameter type to allow passing
different types of strings to a function, for example::
template <typename... Args>
std::string format(StringRef format, const Args & ... args);
format("{}", 42);
format(std::string("{}"), 42);
\endrst
*/
template <typename Char>
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<Char>::length``.
*/
BasicStringRef(const Char *s)
: data_(s), size_(std::char_traits<Char>::length(s)) {}
/**
Constructs a string reference from an `std::string` object.
*/
BasicStringRef(const std::basic_string<Char> &s)
: data_(s.c_str()), size_(s.size()) {}
/**
Converts a string reference to an `std::string` object.
*/
operator std::basic_string<Char>() const
{
return std::basic_string<Char>(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<char> StringRef;
typedef BasicStringRef<wchar_t> 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 <typename T>
inline stdext::checked_array_iterator<T*> make_ptr(T *ptr, std::size_t size)
{
return stdext::checked_array_iterator<T*>(ptr, size);
}
#else
template <typename T>
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 <typename T>
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 <typename T>
void Buffer<T>::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 <typename T, std::size_t SIZE, typename Allocator = std::allocator<T> >
class MemoryBuffer : private Allocator, public Buffer<T>
{
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<T>(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 <typename T, std::size_t SIZE, typename Allocator>
void MemoryBuffer<T, SIZE, Allocator>::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 <typename T>
struct IsLongDouble
{
enum { VALUE = 0 };
};
template <>
struct IsLongDouble<long double>
{
enum { VALUE = 1 };
};
template <typename Char>
class BasicCharTraits
{
public:
#if _SECURE_SCL
typedef stdext::checked_array_iterator<Char*> CharPtr;
#else
typedef Char *CharPtr;
#endif
};
template <typename Char>
class CharTraits;
template <>
class CharTraits<char> : public BasicCharTraits<char>
{
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 <typename T>
static int format_float(char *buffer, std::size_t size,
const char *format, unsigned width, int precision, T value);
};
template <>
class CharTraits<wchar_t> : public BasicCharTraits<wchar_t>
{
public:
typedef const char *UnsupportedStrType;
static wchar_t convert(char value)
{
return value;
}
static wchar_t convert(wchar_t value)
{
return value;
}
template <typename T>
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 <bool IsSigned>
struct SignChecker
{
template <typename T>
static bool is_negative(T value)
{
return value < 0;
}
};
template <>
struct SignChecker<false>
{
template <typename T>
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 <typename T>
inline bool is_negative(T value)
{
return SignChecker<std::numeric_limits<T>::is_signed>::is_negative(value);
}
// Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise.
template <bool FitsIn32Bits>
struct TypeSelector
{
typedef uint32_t Type;
};
template <>
struct TypeSelector<false>
{
typedef uint64_t Type;
};
template <typename T>
struct IntTraits
{
// Smallest of uint32_t and uint64_t that is large enough to represent
// all values of T.
typedef typename
TypeSelector<std::numeric_limits<T>::digits <= 32>::Type MainType;
};
// MakeUnsigned<T>::Type gives an unsigned type corresponding to integer type T.
template <typename T>
struct MakeUnsigned
{
typedef T Type;
};
#define FMT_SPECIALIZE_MAKE_UNSIGNED(T, U) \
template <> \
struct MakeUnsigned<T> { 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 <typename T = void>
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 <typename UInt, typename Char>
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<char>('0' + value);
return;
}
unsigned index = static_cast<unsigned>(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<wchar_t, INLINE_BUFFER_SIZE> 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<char, INLINE_BUFFER_SIZE> 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 <unsigned Arg>
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 <typename Char>
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<char> string;
StringValue<signed char> sstring;
StringValue<unsigned char> ustring;
StringValue<wchar_t> 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 <typename Char>
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 <typename T>
MakeValue(const T *value);
template <typename T>
MakeValue(T *value);
void set_string(StringRef str)
{
string.value = str.c_str();
string.size = str.size();
}
void set_string(WStringRef str)
{
CharTraits<Char>::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 <typename T>
static void format_custom_arg(
void *formatter, const void *arg, void *format_str_ptr)
{
format(*static_cast<BasicFormatter<Char>*>(formatter),
*static_cast<const Char**>(format_str_ptr),
*static_cast<const T*>(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<int>(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<unsigned>(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<Char>::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 <typename T>
MakeValue(const T &value)
{
custom.value = &value;
custom.format = &format_custom_arg<T>;
}
template <typename T>
static uint64_t type(const T &)
{
return Arg::CUSTOM;
}
};
#define FMT_DISPATCH(call) static_cast<Impl*>(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<MyArgVisitor, void> {
// 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 <typename Impl, typename Result>
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 <typename T>
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 <typename T>
Result visit_any_double(T)
{
return FMT_DISPATCH(visit_unhandled_arg());
}
Result visit_string(Arg::StringValue<char>)
{
return FMT_DISPATCH(visit_unhandled_arg());
}
Result visit_wstring(Arg::StringValue<wchar_t>)
{
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<char> 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 <typename Char>
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<Arg::Type>((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 <typename Char>
void write(BasicWriter<Char> &w, const Char *start, const Char *end)
{
if (start != end)
w << BasicStringRef<Char>(start, end - start);
}
};
// A printf formatter.
template <typename Char>
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<unsigned>::max)());
// Parses argument index, flags and width and returns the argument index.
unsigned parse_header(const Char *&s, FormatSpec &spec);
public:
void format(BasicWriter<Char> &writer,
BasicStringRef<Char> format, const ArgList &args);
};
} // namespace internal
// A formatter.
template <typename Char>
class BasicFormatter : private internal::FormatterBase
{
private:
BasicWriter<Char> &writer_;
const Char *start_;
// Parses argument index and returns corresponding argument.
internal::Arg parse_arg_index(const Char *&s);
public:
explicit BasicFormatter(BasicWriter<Char> &w) : writer_(w) {}
BasicWriter<Char> &writer()
{
return writer_;
}
void format(BasicStringRef<Char> 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 <char TYPE>
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 <char TYPE>
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 T, typename SpecT = TypeSpec<0>, 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 <typename T>
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<int, TypeSpec<'b'> > bin(int value);
/**
Returns an integer format specifier to format the value in base 8.
*/
IntFormatSpec<int, TypeSpec<'o'> > 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<int, TypeSpec<'x'> > hex(int value);
/**
Returns an integer formatter format specifier to format in base 16 using
upper-case letters for the digits above 9.
*/
IntFormatSpec<int, TypeSpec<'X'> > 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 <char TYPE_CODE, typename Char>
IntFormatSpec<int, AlignTypeSpec<TYPE_CODE>, Char> pad(
int value, unsigned width, Char fill = ' ');
#define FMT_DEFINE_INT_FORMATTERS(TYPE) \
inline IntFormatSpec<TYPE, TypeSpec<'b'> > bin(TYPE value) { \
return IntFormatSpec<TYPE, TypeSpec<'b'> >(value, TypeSpec<'b'>()); \
} \
\
inline IntFormatSpec<TYPE, TypeSpec<'o'> > oct(TYPE value) { \
return IntFormatSpec<TYPE, TypeSpec<'o'> >(value, TypeSpec<'o'>()); \
} \
\
inline IntFormatSpec<TYPE, TypeSpec<'x'> > hex(TYPE value) { \
return IntFormatSpec<TYPE, TypeSpec<'x'> >(value, TypeSpec<'x'>()); \
} \
\
inline IntFormatSpec<TYPE, TypeSpec<'X'> > hexu(TYPE value) { \
return IntFormatSpec<TYPE, TypeSpec<'X'> >(value, TypeSpec<'X'>()); \
} \
\
template <char TYPE_CODE> \
inline IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE> > pad( \
IntFormatSpec<TYPE, TypeSpec<TYPE_CODE> > f, unsigned width) { \
return IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE> >( \
f.value(), AlignTypeSpec<TYPE_CODE>(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 <char TYPE_CODE, typename Char> \
inline IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE>, Char> pad( \
IntFormatSpec<TYPE, TypeSpec<TYPE_CODE>, Char> f, \
unsigned width, Char fill) { \
return IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE>, Char>( \
f.value(), AlignTypeSpec<TYPE_CODE>(width, fill)); \
} \
\
inline IntFormatSpec<TYPE, AlignTypeSpec<0> > pad( \
TYPE value, unsigned width) { \
return IntFormatSpec<TYPE, AlignTypeSpec<0> >( \
value, AlignTypeSpec<0>(width, ' ')); \
} \
\
template <typename Char> \
inline IntFormatSpec<TYPE, AlignTypeSpec<0>, Char> pad( \
TYPE value, unsigned width, Char fill) { \
return IntFormatSpec<TYPE, AlignTypeSpec<0>, 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 <typename Char>
inline StrFormatSpec<Char> pad(
const Char *str, unsigned width, Char fill = ' ')
{
return StrFormatSpec<Char>(str, width, fill);
}
inline StrFormatSpec<wchar_t> pad(
const wchar_t *str, unsigned width, char fill = ' ')
{
return StrFormatSpec<wchar_t>(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 <typename T>
inline uint64_t make_type(const T &arg)
{
return MakeValue<char>::type(arg);
}
#if FMT_USE_VARIADIC_TEMPLATES
template <typename Arg, typename... Args>
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 <typename T>
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<char>(v##n)
# define FMT_MAKE_REF_wchar_t(n) fmt::internal::MakeValue<wchar_t>(v##n)
#if FMT_USE_VARIADIC_TEMPLATES
// Defines a variadic function returning void.
# define FMT_VARIADIC_VOID(func, arg_type) \
template <typename... Args> \
void func(arg_type arg1, const Args & ... args) { \
const fmt::internal::Value values[ \
fmt::internal::NonZero<sizeof...(Args)>::VALUE] = { \
fmt::internal::MakeValue<Char>(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 <typename... Args> \
ctor(arg0_type arg0, arg1_type arg1, const Args & ... args) { \
using fmt::internal::MakeValue; \
const fmt::internal::Value values[ \
fmt::internal::NonZero<sizeof...(Args)>::VALUE] = { \
MakeValue<Char>(args)... \
}; \
func(arg0, arg1, ArgList(fmt::internal::make_type(args...), values)); \
}
#else
# define FMT_MAKE_REF(n) fmt::internal::MakeValue<Char>(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 <FMT_GEN(n, FMT_MAKE_TEMPLATE_ARG)> \
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 <FMT_GEN(n, FMT_MAKE_TEMPLATE_ARG)> \
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 "*<message>*: *<system-message>*", where *<message>* is the
formatted message and *<system-message>* 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<char> |
+---------+----------------------+
| WWriter | BasicWriter<wchar_t> |
+---------+----------------------+
\endrst
*/
template <typename Char>
class BasicWriter
{
private:
// Output buffer.
internal::Buffer<Char> &buffer_;
FMT_DISALLOW_COPY_AND_ASSIGN(BasicWriter);
typedef typename internal::CharTraits<Char>::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 <typename Spec>
CharPtr prepare_int_buffer(unsigned num_digits,
const Spec &spec, const char *prefix, unsigned prefix_size);
// Formats an integer.
template <typename T, typename Spec>
void write_int(T value, Spec spec);
// Formats a floating-point number (double or long double).
template <typename T>
void write_double(T value, const FormatSpec &spec);
// Writes a formatted string.
template <typename StrChar>
CharPtr write_str(
const StrChar *s, std::size_t size, const AlignSpec &spec);
template <typename StrChar>
void write_str(
const internal::Arg::StringValue<StrChar> &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<Char>::UnsupportedStrType);
friend class internal::ArgFormatter<Char>;
friend class internal::PrintfFormatter<Char>;
protected:
/**
Constructs a ``BasicWriter`` object.
*/
explicit BasicWriter(internal::Buffer<Char> &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<Char> str() const
{
return std::basic_string<Char>(&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<Char> format, ArgList args)
{
BasicFormatter<Char>(*this).format(format, args);
}
FMT_VARIADIC_VOID(write, BasicStringRef<Char>)
BasicWriter &operator<<(int value)
{
return *this << IntFormatSpec<int>(value);
}
BasicWriter &operator<<(unsigned value)
{
return *this << IntFormatSpec<unsigned>(value);
}
BasicWriter &operator<<(long value)
{
return *this << IntFormatSpec<long>(value);
}
BasicWriter &operator<<(unsigned long value)
{
return *this << IntFormatSpec<unsigned long>(value);
}
BasicWriter &operator<<(LongLong value)
{
return *this << IntFormatSpec<LongLong>(value);
}
/**
Formats *value* and writes it to the stream.
*/
BasicWriter &operator<<(ULongLong value)
{
return *this << IntFormatSpec<ULongLong>(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<Char>::convert(value));
return *this;
}
/**
Writes *value* to the stream.
*/
BasicWriter &operator<<(fmt::BasicStringRef<Char> value)
{
const Char *str = value.c_str();
buffer_.append(str, str + value.size());
return *this;
}
template <typename T, typename Spec, typename FillChar>
BasicWriter &operator<<(IntFormatSpec<T, Spec, FillChar> spec)
{
internal::CharTraits<Char>::convert(FillChar());
write_int(spec.value(), spec);
return *this;
}
template <typename StrChar>
BasicWriter &operator<<(const StrFormatSpec<StrChar> &spec)
{
const StrChar *s = spec.str();
// TODO: error if fill is not convertible to Char
write_str(s, std::char_traits<Char>::length(s), spec);
return *this;
}
void clear() FMT_NOEXCEPT(true)
{
buffer_.clear();
}
};
template <typename Char>
template <typename StrChar>
typename BasicWriter<Char>::CharPtr BasicWriter<Char>::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<Char>(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 Char>
typename BasicWriter<Char>::CharPtr
BasicWriter<Char>::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<Char>(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 <typename Char>
template <typename Spec>
typename BasicWriter<Char>::CharPtr
BasicWriter<Char>::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<Char>(spec.fill());
if (spec.precision() > static_cast<int>(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 <typename Char>
template <typename T, typename Spec>
void BasicWriter<Char>::write_int(T value, Spec spec)
{
unsigned prefix_size = 0;
typedef typename internal::IntTraits<T>::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 <typename Char>
template <typename T>
void BasicWriter<Char>::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<double>(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<T>::VALUE)
*format_ptr++ = 'L';
*format_ptr++ = type;
*format_ptr = '\0';
// Format using snprintf.
Char fill = static_cast<Char>(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<Char>::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<unsigned>(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<char, std::allocator<char>> |
+---------------+-----------------------------------------------+
| WMemoryWriter | BasicWriter<wchar_t, std::allocator<wchar_t>> |
+---------------+-----------------------------------------------+
**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 <typename Char, typename Allocator = std::allocator<Char> >
class BasicMemoryWriter : public BasicWriter<Char>
{
private:
internal::MemoryBuffer<Char, internal::INLINE_BUFFER_SIZE, Allocator> buffer_;
public:
explicit BasicMemoryWriter(const Allocator& alloc = Allocator())
: BasicWriter<Char>(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<Char>(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<char> MemoryWriter;
typedef BasicMemoryWriter<wchar_t> WMemoryWriter;
// Formats a value.
template <typename Char, typename T>
void format(BasicFormatter<Char> &f, const Char *&format_str, const T &value)
{
std::basic_ostringstream<Char> os;
os << value;
internal::Arg arg;
internal::Value &arg_value = arg;
std::basic_string<Char> str = os.str();
arg_value = internal::MakeValue<Char>(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 "*<message>*: *<system-message>*", where *<message>* is the
formatted message and *<system-message>* 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 <typename Char>
void printf(BasicWriter<Char> &w, BasicStringRef<Char> format, ArgList args)
{
internal::PrintfFormatter<Char>().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<ULongLong>::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<char>('0' + value);
return buffer_end;
}
unsigned index = static_cast<unsigned>(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<ULongLong>(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 <typename T>
inline void format_decimal(char *&buffer, T value)
{
typename internal::IntTraits<T>::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<char>('0' + abs_value);
return;
}
unsigned index = static_cast<unsigned>(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;
}
} // ns fmt
} // ns deatils
} // ns spdlog
#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 <typename... Args> \
ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \
const Args & ... args) { \
using fmt::internal::Value; \
const Value values[fmt::internal::NonZero<sizeof...(Args)>::VALUE] = { \
fmt::internal::MakeValue<Char>(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 <FMT_GEN(n, FMT_MAKE_TEMPLATE_ARG)> \
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 <typename... Args>
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 spdlog {
namespace details {
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_
...@@ -26,7 +26,6 @@ ...@@ -26,7 +26,6 @@
#include "../common.h" #include "../common.h"
#include "../logger.h" #include "../logger.h"
#include "fast_oss.h"
// Line logger class - aggregates operator<< calls to fast ostream // Line logger class - aggregates operator<< calls to fast ostream
...@@ -66,27 +65,35 @@ public: ...@@ -66,27 +65,35 @@ public:
{ {
_log_msg.logger_name = _callback_logger->name(); _log_msg.logger_name = _callback_logger->name();
_log_msg.time = log_clock::now(); _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); _callback_logger->_log_msg(_log_msg);
} }
} }
template<typename T>
void write(const T& what) template <typename... Args>
void write(const char* fmt, const Args&... args)
{ {
if (_enabled) if (!_enabled)
return;
try
{ {
_log_msg.raw << what; _log_msg.raw.write(fmt, args...);
}
catch (const fmt::FormatError& e)
{
throw spdlog_ex(fmt::format("formatting error while processing format string '{}': {}", fmt, e.what()));
} }
} }
template<typename T> template<typename T>
line_logger& operator<<(const T& what) line_logger& operator<<(const T& what)
{ {
write(what); if (_enabled)
_log_msg.raw << what;
return *this; return *this;
} }
void disable() void disable()
{ {
_enabled = false; _enabled = false;
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
#pragma once #pragma once
#include "../common.h" #include "../common.h"
#include "./fast_oss.h" #include "./format.h"
namespace spdlog namespace spdlog
{ {
...@@ -38,25 +38,31 @@ struct log_msg ...@@ -38,25 +38,31 @@ struct log_msg
logger_name(), logger_name(),
level(l), level(l),
time(), time(),
tm_time(),
raw(), raw(),
formatted() {} formatted() {}
log_msg(const log_msg& other):
log_msg(const log_msg& other) :
logger_name(other.logger_name), logger_name(other.logger_name),
level(other.level), level(other.level),
time(other.time), time(other.time)
tm_time(other.tm_time), {
raw(other.raw), if (other.raw.size())
formatted(other.formatted) {} raw << fmt::BasicStringRef<char>(other.raw.data(), other.raw.size());
if (other.formatted.size())
formatted << fmt::BasicStringRef<char>(other.formatted.data(), other.formatted.size());
}
log_msg(log_msg&& other) : log_msg(log_msg&& other) :
logger_name(std::move(other.logger_name)), logger_name(std::move(other.logger_name)),
level(other.level), level(other.level),
time(std::move(other.time)), time(std::move(other.time)),
tm_time(other.tm_time),
raw(std::move(other.raw)), raw(std::move(other.raw)),
formatted(std::move(other.formatted)) {} formatted(std::move(other.formatted))
{
other.clear();
}
log_msg& operator=(log_msg&& other) log_msg& operator=(log_msg&& other)
{ {
...@@ -66,16 +72,15 @@ struct log_msg ...@@ -66,16 +72,15 @@ struct log_msg
logger_name = std::move(other.logger_name); logger_name = std::move(other.logger_name);
level = other.level; level = other.level;
time = std::move(other.time); time = std::move(other.time);
tm_time = other.tm_time;
raw = std::move(other.raw); raw = std::move(other.raw);
formatted = std::move(other.formatted); formatted = std::move(other.formatted);
other.clear();
return *this; return *this;
} }
void clear() void clear()
{ {
level = level::OFF;
raw.clear(); raw.clear();
formatted.clear(); formatted.clear();
} }
...@@ -83,9 +88,8 @@ struct log_msg ...@@ -83,9 +88,8 @@ struct log_msg
std::string logger_name; std::string logger_name;
level::level_enum level; level::level_enum level;
log_clock::time_point time; log_clock::time_point time;
std::tm tm_time; fmt::MemoryWriter raw;
fast_oss raw; fmt::MemoryWriter formatted;
fast_oss formatted;
}; };
} }
} }
...@@ -63,78 +63,146 @@ inline void spdlog::logger::set_pattern(const std::string& pattern) ...@@ -63,78 +63,146 @@ inline void spdlog::logger::set_pattern(const std::string& pattern)
_set_pattern(pattern); _set_pattern(pattern);
} }
//
// cppformat API of the form logger.info("hello {} {}", "world", 1);
//
template <typename... Args> template <typename... Args>
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 char* fmt, const Args&... args)
{ {
bool msg_enabled = should_log(lvl); bool msg_enabled = should_log(lvl);
details::line_logger l(this, lvl, msg_enabled); details::line_logger l(this, lvl, msg_enabled);
if (msg_enabled) l.write(fmt, args...);
_variadic_log(l, args...);
return l; return l;
} }
template <typename... Args> template <typename... Args>
inline spdlog::details::line_logger spdlog::logger::log(const Args&... args) inline spdlog::details::line_logger spdlog::logger::log(const char* fmt, const Args&... args)
{ {
return log(level::ALWAYS, args...); return _log(level::ALWAYS, fmt, args...);
} }
template <typename... Args> template <typename... Args>
inline spdlog::details::line_logger spdlog::logger::trace(const Args&... args) inline spdlog::details::line_logger spdlog::logger::trace(const char* fmt, const Args&... args)
{ {
return log(level::TRACE, args...); return _log(level::TRACE, fmt, args...);
} }
template <typename... Args> template <typename... Args>
inline spdlog::details::line_logger spdlog::logger::debug(const Args&... args) inline spdlog::details::line_logger spdlog::logger::debug(const char* fmt, const Args&... args)
{ {
return log(level::DEBUG, args...); return _log(level::DEBUG, fmt, args...);
} }
template <typename... Args> template <typename... Args>
inline spdlog::details::line_logger spdlog::logger::info(const Args&... args) inline spdlog::details::line_logger spdlog::logger::info(const char* fmt, const Args&... args)
{ {
return log(level::INFO, args...); return _log(level::INFO, fmt, args...);
} }
template <typename... Args> template <typename... Args>
inline spdlog::details::line_logger spdlog::logger::notice(const Args&... args) inline spdlog::details::line_logger spdlog::logger::notice(const char* fmt, const Args&... args)
{ {
return log(level::NOTICE, args...); return _log(level::NOTICE, fmt, args...);
} }
template <typename... Args> template <typename... Args>
inline spdlog::details::line_logger spdlog::logger::warn(const Args&... args) inline spdlog::details::line_logger spdlog::logger::warn(const char* fmt, const Args&... args)
{ {
return log(level::WARN, args...); return _log(level::WARN, fmt, args...);
} }
template <typename... Args> template <typename... Args>
inline spdlog::details::line_logger spdlog::logger::error(const Args&... args) inline spdlog::details::line_logger spdlog::logger::error(const char* fmt, const Args&... args)
{ {
return log(level::ERR, args...); return _log(level::ERR, fmt, args...);
} }
template <typename... Args> template <typename... Args>
inline spdlog::details::line_logger spdlog::logger::critical(const Args&... args) inline spdlog::details::line_logger spdlog::logger::critical(const char* fmt, const Args&... args)
{ {
return log(level::CRITICAL, args...); return _log(level::CRITICAL, fmt, args...);
} }
template <typename... Args> template <typename... Args>
inline spdlog::details::line_logger spdlog::logger::alert(const Args&... args) inline spdlog::details::line_logger spdlog::logger::alert(const char* fmt, const Args&... args)
{ {
return log(level::ALERT, args...); return _log(level::ALERT, fmt, args...);
} }
template <typename... Args> template <typename... Args>
inline spdlog::details::line_logger spdlog::logger::emerg(const Args&... args) inline spdlog::details::line_logger spdlog::logger::emerg(const char* fmt, const Args&... 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::EMERG, args...); 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);
}
//
// name and level
//
inline const std::string& spdlog::logger::name() const inline const std::string& spdlog::logger::name() const
{ {
return _name; return _name;
...@@ -160,7 +228,9 @@ inline void spdlog::logger::stop() ...@@ -160,7 +228,9 @@ inline void spdlog::logger::stop()
_stop(); _stop();
} }
/* protected virtual */ //
// protected virtual called at end of each user log call (if enabled) by the line_logger
//
inline void spdlog::logger::_log_msg(details::log_msg& msg) inline void spdlog::logger::_log_msg(details::log_msg& msg)
{ {
_formatter->format(msg); _formatter->format(msg);
...@@ -182,24 +252,7 @@ inline void spdlog::logger::_stop() ...@@ -182,24 +252,7 @@ inline void spdlog::logger::_stop()
set_level(level::OFF); set_level(level::OFF);
} }
/* private functions */
inline void spdlog::logger::_variadic_log(spdlog::details::line_logger&) {}
template <typename Last>
inline void spdlog::logger::_variadic_log(spdlog::details::line_logger& l, const Last& last)
{
l.write(last);
}
template <typename First, typename... Rest>
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...);
}
/*
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 <atomic>
#include "../common.h"
namespace spdlog
{
namespace details
{
template<typename T>
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<size_t> 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<size_t> enqueue_pos_;
cacheline_pad_t pad2_;
std::atomic<size_t> dequeue_pos_;
cacheline_pad_t pad3_;
mpmc_bounded_queue(mpmc_bounded_queue const&);
void operator = (mpmc_bounded_queue const&);
};
} // ns details
} // ns spdlog
...@@ -30,9 +30,9 @@ ...@@ -30,9 +30,9 @@
#include <vector> #include <vector>
#include <thread> #include <thread>
#include "../formatter.h" #include "../formatter.h"
#include "./log_msg.h" #include "./log_msg.h"
#include "./fast_oss.h"
#include "./os.h" #include "./os.h"
namespace spdlog namespace spdlog
...@@ -43,7 +43,7 @@ class flag_formatter ...@@ -43,7 +43,7 @@ class flag_formatter
{ {
public: public:
virtual ~flag_formatter() {} 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 ...@@ -53,7 +53,7 @@ namespace
{ {
class name_formatter :public flag_formatter 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; msg.formatted << msg.logger_name;
} }
...@@ -63,7 +63,7 @@ class name_formatter :public flag_formatter ...@@ -63,7 +63,7 @@ class name_formatter :public flag_formatter
// log level appender // log level appender
class level_formatter :public flag_formatter 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); msg.formatted << level::to_str(msg.level);
} }
...@@ -87,9 +87,9 @@ static int to12h(const tm& t) ...@@ -87,9 +87,9 @@ static int to12h(const tm& t)
static const std::string days[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static const std::string days[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
class a_formatter :public flag_formatter 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 ...@@ -97,9 +97,9 @@ class a_formatter :public flag_formatter
static const std::string full_days[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; static const std::string full_days[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
class A_formatter :public flag_formatter 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 ...@@ -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" }; static const std::string months[] { "Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec" };
class b_formatter :public flag_formatter 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 ...@@ -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" }; static const std::string full_months[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
class B_formatter :public flag_formatter 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) //Date and time representation (Thu Aug 23 15:35:46 2014)
class c_formatter :public flag_formatter 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 << days[tm_time.tm_wday] << ' ' << months[tm_time.tm_mon] << ' ' << tm_time.tm_mday << ' ';
msg.formatted.putc(' '); pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, ':') << ' ' << tm_time.tm_year + 1900;
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);
} }
}; };
...@@ -146,9 +153,9 @@ class c_formatter :public flag_formatter ...@@ -146,9 +153,9 @@ class c_formatter :public flag_formatter
// year - 2 digit // year - 2 digit
class C_formatter :public flag_formatter 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 ...@@ -157,13 +164,9 @@ class C_formatter :public flag_formatter
// Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01 // Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01
class D_formatter :public flag_formatter 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); pad_n_join(msg.formatted, tm_time.tm_mon + 1, tm_time.tm_mday, tm_time.tm_year % 100, '/');
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);
} }
}; };
...@@ -171,83 +174,83 @@ class D_formatter :public flag_formatter ...@@ -171,83 +174,83 @@ class D_formatter :public flag_formatter
// year - 4 digit // year - 4 digit
class Y_formatter :public flag_formatter 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 // month 1-12
class m_formatter :public flag_formatter 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 // day of month 1-31
class d_formatter :public flag_formatter 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 // hours in 24 format 0-23
class H_formatter :public flag_formatter 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 // hours in 12 format 1-12
class I_formatter :public flag_formatter 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 // ninutes 0-59
class M_formatter :public flag_formatter 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 // seconds 0-59
class S_formatter :public flag_formatter 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 // milliseconds
class e_formatter :public flag_formatter 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 duration = msg.time.time_since_epoch();
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() % 1000; auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() % 1000;
msg.formatted.put_int(static_cast<int>(millis), 3); msg.formatted << fmt::pad(static_cast<int>(millis), 3, '0');
} }
}; };
// AM/PM // AM/PM
class p_formatter :public flag_formatter 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 ...@@ -255,100 +258,92 @@ class p_formatter :public flag_formatter
// 12 hour clock 02:55:02 pm // 12 hour clock 02:55:02 pm
class r_formatter :public flag_formatter 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); pad_n_join(msg.formatted, to12h(tm_time), tm_time.tm_min, tm_time.tm_sec, ':') << ' ' << ampm(tm_time);
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);
} }
}; };
// 24-hour HH:MM time, equivalent to %H:%M // 24-hour HH:MM time, equivalent to %H:%M
class R_formatter :public flag_formatter 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); pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, ':');
msg.formatted.putc(':');
msg.formatted.put_int(msg.tm_time.tm_min, 2);
} }
}; };
// ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S // ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S
class T_formatter :public flag_formatter 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); pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, ':');
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);
} }
}; };
// ISO 8601 offset from UTC in timezone (HH:MM)
// ISO 8601 offset from UTC in timezone (+-HH:MM)
class z_formatter :public flag_formatter class z_formatter :public flag_formatter
{ {
public: 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(const z_formatter&) = delete;
z_formatter& operator=(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<std::mutex> l(_mutex); #ifdef _WIN32
using namespace std::chrono; int total_minutes = get_cached_offset(msg, tm_time);
auto diff = msg.time - _last_update; #else
auto secs_diff = std::abs((duration_cast<seconds>(diff)).count()); // No need to chache under gcc,
if (secs_diff >= 2) // it is very fast (already stored in tm.tm_gmtoff)
{ int total_minutes = os::utc_minutes_offset(tm_time);
_value = get_value(msg); #endif
_last_update = msg.time;
} int h = total_minutes / 60;
msg.formatted.put_str(_value); int m = total_minutes % 60;
char sign = h >= 0 ? '+' : '-';
msg.formatted << sign;
pad_n_join(msg.formatted, h, m, ':');
} }
private: private:
log_clock::time_point _last_update; log_clock::time_point _last_update;
std::string _value; int _offset_minutes;
std::mutex _mutex; 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); using namespace std::chrono;
int h = total_minutes / 60; std::lock_guard<std::mutex> l(_mutex);
int m = total_minutes % 60; if (msg.time - _last_update >= cache_refresh)
fast_oss oss; {
oss.putc(h < 0 ? '-' : '+'); _offset_minutes = os::utc_minutes_offset(tm_time);
oss.put_int(h, 2); _last_update = msg.time;
oss.putc(':'); }
oss.put_int(m, 2); return _offset_minutes;
return oss.str();
} }
}; };
//Thread id //Thread id
class t_formatter :public flag_formatter 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::thread::id>()(std::this_thread::get_id());
} }
}; };
class v_formatter :public flag_formatter 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 ...@@ -357,9 +352,9 @@ class ch_formatter :public flag_formatter
public: public:
explicit ch_formatter(char ch) : _ch(ch) 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: private:
char _ch; char _ch;
...@@ -376,9 +371,9 @@ public: ...@@ -376,9 +371,9 @@ public:
{ {
_str += ch; _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: private:
std::string _str; std::string _str;
...@@ -388,29 +383,35 @@ private: ...@@ -388,29 +383,35 @@ private:
// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v // pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v
class full_formatter :public flag_formatter 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 duration = msg.time.time_since_epoch();
auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() % 1000; auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() % 1000;
msg.formatted.put_int(static_cast<int>(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<int>(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<unsigned int>(tm_time.tm_year + 1900) << '-'
<< fmt::pad(static_cast<unsigned int>(tm_time.tm_mon + 1), 2, '0') << '-'
<< fmt::pad(static_cast<unsigned int>(tm_time.tm_mday), 2, '0') << ' '
<< fmt::pad(static_cast<unsigned int>(tm_time.tm_hour), 2, '0') << ':'
<< fmt::pad(static_cast<unsigned int>(tm_time.tm_min), 2, '0') << ':'
<< fmt::pad(static_cast<unsigned int>(tm_time.tm_sec), 2, '0') << '.'
<< fmt::pad(static_cast<unsigned int>(millis), 3, '0') << "] ";
msg.formatted << '[' << msg.logger_name << "] [" << level::to_str(msg.level) << "] ";
msg.formatted << fmt::StringRef(msg.raw.data(), msg.raw.size());
} }
}; };
...@@ -457,7 +458,7 @@ inline void spdlog::pattern_formatter::handle_flag(char flag) ...@@ -457,7 +458,7 @@ inline void spdlog::pattern_formatter::handle_flag(char flag)
{ {
switch (flag) switch (flag)
{ {
// logger name // logger name
case 'n': case 'n':
_formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::name_formatter())); _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::name_formatter()));
break; break;
...@@ -571,10 +572,18 @@ inline void spdlog::pattern_formatter::handle_flag(char flag) ...@@ -571,10 +572,18 @@ inline void spdlog::pattern_formatter::handle_flag(char flag)
inline void spdlog::pattern_formatter::format(details::log_msg& msg) inline void spdlog::pattern_formatter::format(details::log_msg& msg)
{ {
for (auto &f : _formatters) try
{
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 details::fmt::FormatError& e)
{ {
f->format(msg); throw spdlog_ex(details::fmt::format("formatting error while processing format string: {}", e.what()));
} }
//write eol
msg.formatted.write(details::os::eol(), details::os::eol_size());
} }
...@@ -61,7 +61,7 @@ public: ...@@ -61,7 +61,7 @@ public:
return found->second; return found->second;
std::shared_ptr<logger> new_logger; std::shared_ptr<logger> new_logger;
if (_async_mode) if (_async_mode)
new_logger = std::make_shared<async_logger>(logger_name, sinks_begin, sinks_end, _async_q_size, _async_shutdown_duration); new_logger = std::make_shared<async_logger>(logger_name, sinks_begin, sinks_end, _async_q_size);
else else
new_logger = std::make_shared<logger>(logger_name, sinks_begin, sinks_end); new_logger = std::make_shared<logger>(logger_name, sinks_begin, sinks_end);
...@@ -72,6 +72,11 @@ public: ...@@ -72,6 +72,11 @@ public:
return new_logger; return new_logger;
} }
void drop(const std::string& logger_name)
{
std::lock_guard<std::mutex> lock(_mutex);
_loggers.erase(logger_name);
}
std::shared_ptr<logger> create(const std::string& logger_name, sinks_init_list sinks) std::shared_ptr<logger> create(const std::string& logger_name, sinks_init_list sinks)
{ {
...@@ -109,12 +114,11 @@ public: ...@@ -109,12 +114,11 @@ public:
l.second->set_level(log_level); 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<std::mutex> lock(_mutex); std::lock_guard<std::mutex> lock(_mutex);
_async_mode = true; _async_mode = true;
_async_q_size = q_size; _async_q_size = q_size;
_async_shutdown_duration = shutdown_duration;
} }
void set_sync_mode() void set_sync_mode()
...@@ -148,7 +152,6 @@ private: ...@@ -148,7 +152,6 @@ private:
level::level_enum _level = level::INFO; level::level_enum _level = level::INFO;
bool _async_mode = false; bool _async_mode = false;
size_t _async_q_size = 0; size_t _async_q_size = 0;
log_clock::duration _async_shutdown_duration;
}; };
} }
} }
...@@ -37,6 +37,10 @@ inline std::shared_ptr<spdlog::logger> spdlog::get(const std::string& name) ...@@ -37,6 +37,10 @@ inline std::shared_ptr<spdlog::logger> spdlog::get(const std::string& name)
return details::registry::instance().get(name); return details::registry::instance().get(name);
} }
inline void spdlog::drop(const std::string &name)
{
details::registry::instance().drop(name);
}
// Create multi/single threaded rotating file logger // Create multi/single threaded rotating file logger
inline std::shared_ptr<spdlog::logger> spdlog::rotating_logger_mt(const std::string& logger_name, const std::string& filename, size_t max_file_size, size_t max_files, bool auto_flush) inline std::shared_ptr<spdlog::logger> spdlog::rotating_logger_mt(const std::string& logger_name, const std::string& filename, size_t max_file_size, size_t max_files, bool auto_flush)
...@@ -129,9 +133,9 @@ inline void spdlog::set_level(level::level_enum log_level) ...@@ -129,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() inline void spdlog::set_sync_mode()
......
/*************************************************************************/
/* 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 <algorithm>
#include <array>
#include <vector>
#include <cstring>
namespace spdlog
{
namespace details
{
// Fast memory storage on the stack when possible or in std::vector
template<unsigned short STACK_SIZE>
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<class T1>
stack_buf& operator=(T1&& other)
{
_stack_size = other._stack_size;
if (other.vector_used())
_v = std::forward<T1>(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<class T1>
stack_buf(T1&& other, delegate_copy_or_move)
{
_stack_size = other._stack_size;
if (other.vector_used())
_v = std::forward<T1>(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<char> _v;
std::array<char, STACK_SIZE> _stack_array;
std::size_t _stack_size;
};
}
} //namespace spdlog { namespace details {
...@@ -34,7 +34,6 @@ ...@@ -34,7 +34,6 @@
#include<vector> #include<vector>
#include<memory> #include<memory>
#include "sinks/base_sink.h" #include "sinks/base_sink.h"
#include "sinks/async_sink.h"
#include "common.h" #include "common.h"
namespace spdlog namespace spdlog
...@@ -66,17 +65,29 @@ public: ...@@ -66,17 +65,29 @@ public:
//Stop logging //Stop logging
void stop(); void stop();
template <typename... Args> details::line_logger log(level::level_enum lvl, const Args&... args); template <typename... Args> details::line_logger log(const char* fmt, const Args&... args);
template <typename... Args> details::line_logger log(const Args&... args); template <typename... Args> details::line_logger trace(const char* fmt, const Args&... args);
template <typename... Args> details::line_logger trace(const Args&... args); template <typename... Args> details::line_logger debug(const char* fmt, const Args&... args);
template <typename... Args> details::line_logger debug(const Args&... args); template <typename... Args> details::line_logger info(const char* fmt, const Args&... args);
template <typename... Args> details::line_logger info(const Args&... args); template <typename... Args> details::line_logger notice(const char* fmt, const Args&... args);
template <typename... Args> details::line_logger notice(const Args&... args); template <typename... Args> details::line_logger warn(const char* fmt, const Args&... args);
template <typename... Args> details::line_logger warn(const Args&... args); template <typename... Args> details::line_logger error(const char* fmt, const Args&... args);
template <typename... Args> details::line_logger error(const Args&... args); template <typename... Args> details::line_logger critical(const char* fmt, const Args&... args);
template <typename... Args> details::line_logger critical(const Args&... args); template <typename... Args> details::line_logger alert(const char* fmt, const Args&... args);
template <typename... Args> details::line_logger alert(const Args&... args); template <typename... Args> details::line_logger emerg(const char* fmt, const Args&... args);
template <typename... Args> details::line_logger emerg(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&); void set_pattern(const std::string&);
...@@ -84,10 +95,12 @@ public: ...@@ -84,10 +95,12 @@ public:
protected: 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_pattern(const std::string&);
virtual void _set_formatter(formatter_ptr); virtual void _set_formatter(formatter_ptr);
virtual void _stop(); virtual void _stop();
details::line_logger _log(level::level_enum lvl);
template <typename... Args> details::line_logger _log(level::level_enum lvl, const char* fmt, const Args&... args);
friend details::line_logger; friend details::line_logger;
...@@ -96,13 +109,6 @@ protected: ...@@ -96,13 +109,6 @@ protected:
formatter_ptr _formatter; formatter_ptr _formatter;
std::atomic_int _level; std::atomic_int _level;
private:
void _variadic_log(details::line_logger& l);
template <typename Last>
inline void _variadic_log(spdlog::details::line_logger& l, const Last& last);
template <typename First, typename... Rest>
void _variadic_log(details::line_logger&l, const First& first, const Rest&... rest);
}; };
} }
......
...@@ -28,8 +28,7 @@ ...@@ -28,8 +28,7 @@
#include "base_sink.h" #include "base_sink.h"
#include "../details/null_mutex.h" #include "../details/null_mutex.h"
#include "../details/file_helper.h" #include "../details/file_helper.h"
#include "../details/fast_oss.h" #include "../details/format.h"
namespace spdlog namespace spdlog
...@@ -100,12 +99,12 @@ protected: ...@@ -100,12 +99,12 @@ protected:
private: private:
static std::string calc_filename(const std::string& filename, std::size_t index, const std::string& extension) static std::string calc_filename(const std::string& filename, std::size_t index, const std::string& extension)
{ {
details::fast_oss oss; details::fmt::MemoryWriter w;
if (index) if (index)
oss << filename << "." << index << "." << extension; w.write("{}.{}.{}", filename, index, extension);
else else
oss << filename << "." << extension; w.write("{}.{}", filename, extension);
return oss.str(); return w.str();
} }
...@@ -197,11 +196,9 @@ private: ...@@ -197,11 +196,9 @@ private:
static std::string calc_filename(const std::string& basename, const std::string& extension) static std::string calc_filename(const std::string& basename, const std::string& extension)
{ {
std::tm tm = spdlog::details::os::localtime(); std::tm tm = spdlog::details::os::localtime();
details::fast_oss oss; details::fmt::MemoryWriter w;
oss << basename << '.'; w.write("{}.{:04d}-{:02d}-{:02d}.{}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, extension);
oss << tm.tm_year + 1900 << '-' << std::setw(2) << std::setfill('0') << tm.tm_mon + 1 << '-' << tm.tm_mday; return w.str();
oss << '.' << extension;
return oss.str();
} }
std::string _base_filename; std::string _base_filename;
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
#pragma once #pragma once
#include <iostream> #include <ostream>
#include <mutex> #include <mutex>
#include <memory> #include <memory>
...@@ -47,8 +47,7 @@ public: ...@@ -47,8 +47,7 @@ public:
protected: protected:
virtual void _sink_it(const details::log_msg& msg) override virtual void _sink_it(const details::log_msg& msg) override
{ {
auto& buf = msg.formatted.buf(); _ostream.write(msg.formatted.data(), msg.formatted.size());
_ostream.write(buf.data(), buf.size());
} }
std::ostream& _ostream; std::ostream& _ostream;
}; };
......
...@@ -42,6 +42,10 @@ namespace spdlog ...@@ -42,6 +42,10 @@ namespace spdlog
// logger.info() << "This is another message" << x << y << z; // logger.info() << "This is another message" << x << y << z;
std::shared_ptr<logger> get(const std::string& name); std::shared_ptr<logger> get(const std::string& name);
//
// Drop the reference to this logger.
//
void drop(const std::string &name);
// //
// Set global formatting // Set global formatting
...@@ -61,9 +65,8 @@ void set_level(level::level_enum log_level); ...@@ -61,9 +65,8 @@ void set_level(level::level_enum log_level);
// //
// Turn on async mode and set the queue size for each async_logger // 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 // Turn off async mode
void set_sync_mode(); void set_sync_mode();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment