diff --git a/CMakeLists.txt b/CMakeLists.txt index 99975812f..0a1044db6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,10 +30,16 @@ HunterGate( ) project(spdlog VERSION 1.0.0) +include(CTest) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR + "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + set(CMAKE_CXX_FLAGS "-Wall ${CMAKE_CXX_FLAGS}") +endif() + add_library(spdlog INTERFACE) option(SPDLOG_BUILD_EXAMPLES "Build examples" OFF) @@ -48,7 +54,6 @@ target_include_directories( set(HEADER_BASE "${CMAKE_CURRENT_SOURCE_DIR}/include") -include(CTest) if(SPDLOG_BUILD_EXAMPLES) enable_testing() add_subdirectory(example) @@ -104,3 +109,6 @@ install( NAMESPACE "${namespace}" DESTINATION "${config_install_dir}" ) + +file(GLOB_RECURSE spdlog_include_SRCS "${HEADER_BASE}/*.h") +add_custom_target(spdlog_headers_for_ide SOURCES ${spdlog_include_SRCS}) diff --git a/README.md b/README.md index e023c56b0..f5e5fe346 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,27 @@ Very fast, header only, C++ logging library. [![Build Status](https://travis-ci. ## Install -Just copy the source [folder](https://github.com/gabime/spdlog/tree/master/include/spdlog) to your build tree and use a C++11 compiler +#### Just copy the headers: + +* Copy the source [folder](https://github.com/gabime/spdlog/tree/master/include/spdlog) to your build tree and use a C++11 compiler. + +#### Or use your favourite package manager: + +* Ubuntu: `apt-get install libspdlog-dev` +* Homebrew: `brew install spdlog` +* FreeBSD: `cd /usr/ports/devel/spdlog/ && make install clean` +* Fedora: `yum install spdlog` +* Arch Linux: `pacman -S spdlog-git` +* vcpkg: `vcpkg install spdlog` + ## Platforms - * Linux (gcc 4.8.1+, clang 3.5+) - * Windows (visual studio 2013+, cygwin/mingw with g++ 4.9.1+) + * Linux, FreeBSD, Solaris + * Windows (vc 2013+, cygwin/mingw) * Mac OSX (clang 3.5+) - * Solaris (gcc 5.2.0+) * Android -##Features +## Features * Very fast - performance is the primary goal (see [benchmarks](#benchmarks) below). * Headers only, just copy and use. * Feature rich [call style](#usage-example) using the excellent [fmt](https://github.com/fmtlib/fmt) library. @@ -25,6 +36,7 @@ Just copy the source [folder](https://github.com/gabime/spdlog/tree/master/inclu * Daily log files. * Console logging (colors supported). * syslog. + * Windows debugger (```OutputDebugString(..)```) * Easily extendable with custom log targets (just implement a single function in the [sink](include/spdlog/sinks/sink.h) interface). * Severity based filtering - threshold levels can be modified in runtime as well as in compile time. @@ -74,34 +86,25 @@ int main(int, char*[]) { try { - // Multithreaded color console - auto console = spd::stdout_logger_mt("console", true); + // Console logger with color + auto console = spd::stdout_color_mt("console"); console->info("Welcome to spdlog!"); - console->info("An info message example {}..", 1); + console->error("Some error message with arg{}..", 1); // Formatting examples console->warn("Easy padding in numbers like {:08d}", 12); console->critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); console->info("Support for floats {:03.2f}", 1.23456); console->info("Positional args are {1} {0}..", "too", "supported"); - console->info("{:<30}", "left aligned"); - console->info("{:>30}", "right aligned"); - console->info("{:^30}", "centered"); + spd::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name) function"); - - // Runtime log levels - spd::set_level(spd::level::info); //Set global log level to info - console->debug("This message shold not be displayed!"); - console->set_level(spd::level::debug); // Set specific logger's log level - console->debug("This message shold be displayed.."); - + // Create basic file logger (not rotated) auto my_logger = spd::basic_logger_mt("basic_logger", "logs/basic.txt"); my_logger->info("Some log message"); - // Create a file rotating logger with 5mb size max and 3 rotated files auto rotating_logger = spd::rotating_logger_mt("some_logger_name", "logs/mylogfile", 1048576 * 5, 3); for (int i = 0; i < 10; ++i) @@ -109,42 +112,57 @@ int main(int, char*[]) // Create a daily logger - a new file is created every day on 2:30am auto daily_logger = spd::daily_logger_mt("daily_logger", "logs/daily", 2, 30); + // trigger flush if the log severity is error or higher + daily_logger->flush_on(spd::level::err); daily_logger->info(123.44); // Customize msg format for all messages spd::set_pattern("*** [%H:%M:%S %z] [thread %t] %v ***"); rotating_logger->info("This is another message with custom format"); - // Compile time debug or trace macros. - // Enabled #ifdef SPDLOG_DEBUG_ON or #ifdef SPDLOG_TRACE_ON + + // Runtime log levels + spd::set_level(spd::level::info); //Set global log level to info + console->debug("This message shold not be displayed!"); + console->set_level(spd::level::debug); // Set specific logger's log level + console->debug("This message shold be displayed.."); + + // Compile time log levels + // define SPDLOG_DEBUG_ON or SPDLOG_TRACE_ON SPDLOG_TRACE(console, "Enabled only #ifdef SPDLOG_TRACE_ON..{} ,{}", 1, 3.23); SPDLOG_DEBUG(console, "Enabled only #ifdef SPDLOG_DEBUG_ON.. {} ,{}", 1, 3.23); - + // Asynchronous logging is very fast.. // Just call spdlog::set_async_mode(q_size) and all created loggers from now on will be asynchronous.. async_example(); - // syslog example. linux/osx only.. + // syslog example. linux/osx only syslog_example(); - // Log user-defined types example.. + // android example. compile with NDK + android_example(); + + // Log user-defined types example user_defined_example(); // Change default log error handler err_handler_example(); // Apply a function on all registered loggers - spd::apply_all([&](std::shared_ptr l) {l->info("End of example."); }); - + spd::apply_all([&](std::shared_ptr l) + { + l->info("End of example."); + }); + // Release and close all loggers spdlog::drop_all(); } - // Exceptions will only be thrown upon failed logger or sink construction (not during logging) + // Exceptions will only be thrown upon failed logger or sink construction (not during logging) catch (const spd::spdlog_ex& ex) { std::cout << "Log init failed: " << ex.what() << std::endl; return 1; - } + } } void async_example() diff --git a/bench/boost-bench.cpp b/bench/boost-bench.cpp index 76685442d..32c5b692a 100644 --- a/bench/boost-bench.cpp +++ b/bench/boost-bench.cpp @@ -2,7 +2,6 @@ // Copyright(c) 2015 Gabi Melman. // Distributed under the MIT License (http://opensource.org/licenses/MIT) // - #include #include #include diff --git a/bench/latency/Makefile b/bench/latency/Makefile new file mode 100644 index 000000000..99b479a4a --- /dev/null +++ b/bench/latency/Makefile @@ -0,0 +1,32 @@ +CXX ?= g++ +CXXFLAGS = -march=native -Wall -std=c++11 -pthread +CXX_RELEASE_FLAGS = -O2 -DNDEBUG + + +binaries=spdlog-latency g3log-latency g3log-crush + +all: $(binaries) + +spdlog-latency: spdlog-latency.cpp + $(CXX) spdlog-latency.cpp -o spdlog-latency $(CXXFLAGS) $(CXX_RELEASE_FLAGS) -I../../include + + + +g3log-latency: g3log-latency.cpp + $(CXX) g3log-latency.cpp -o g3log-latency $(CXXFLAGS) $(CXX_RELEASE_FLAGS) -I../../../g3log/src -L. -lg3logger + + +g3log-crush: g3log-crush.cpp + $(CXX) g3log-crush.cpp -o g3log-crush $(CXXFLAGS) $(CXX_RELEASE_FLAGS) -I../../../g3log/src -L. -lg3logger + + +.PHONY: clean + +clean: + rm -f *.o *.log $(binaries) + + +rebuild: clean all + + + diff --git a/bench/latency/compare.sh b/bench/latency/compare.sh new file mode 100755 index 000000000..0f0e4c97f --- /dev/null +++ b/bench/latency/compare.sh @@ -0,0 +1,13 @@ +#!/bin/bash +echo "running spdlog and g3log tests 10 time with ${1:-10} threads each (total 1,000,000 entries).." +rm -f *.log +for i in {1..10} + +do + echo + sleep 0.5 + ./spdlog-latency ${1:-10} 2>/dev/null || exit + sleep 0.5 + ./g3log-latency ${1:-10} 2>/dev/null || exit + +done diff --git a/bench/latency/g3log-crush.cpp b/bench/latency/g3log-crush.cpp new file mode 100644 index 000000000..417b014ca --- /dev/null +++ b/bench/latency/g3log-crush.cpp @@ -0,0 +1,37 @@ +#include + +#include +#include + +void CrusherLoop() +{ + size_t counter = 0; + while (true) + { + LOGF(INFO, "Some text to crush you machine. thread:"); + if(++counter % 1000000 == 0) + { + std::cout << "Wrote " << counter << " entries" << std::endl; + } + } +} + + +int main(int argc, char** argv) +{ + std::cout << "WARNING: This test will exaust all your machine memory and will crush it!" << std::endl; + std::cout << "Are you sure you want to continue ? " << std::endl; + char c; + std::cin >> c; + if (toupper( c ) != 'Y') + return 0; + + auto worker = g3::LogWorker::createLogWorker(); + auto handle= worker->addDefaultLogger(argv[0], "g3log.txt"); + g3::initializeLogging(worker.get()); + CrusherLoop(); + + return 0; +} + + diff --git a/bench/latency/g3log-latency.cpp b/bench/latency/g3log-latency.cpp new file mode 100644 index 000000000..e96e421b9 --- /dev/null +++ b/bench/latency/g3log-latency.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" +#include +#include + + +namespace +{ +const uint64_t g_iterations = 1000000; + + +std::atomic g_counter = {0}; + + +void MeasurePeakDuringLogWrites(const size_t id, std::vector& result) +{ + + while (true) + { + const size_t value_now = ++g_counter; + if (value_now > g_iterations) + { + return; + } + + auto start_time = std::chrono::high_resolution_clock::now(); + LOGF(INFO, "Some text to log for thread: %ld", id); + auto stop_time = std::chrono::high_resolution_clock::now(); + uint64_t time_us = std::chrono::duration_cast(stop_time - start_time).count(); + result.push_back(time_us); + } +} + + + +void PrintResults(const std::map>& threads_result, size_t total_us) +{ + + std::vector all_measurements; + all_measurements.reserve(g_iterations); + for (auto& t_result : threads_result) + { + all_measurements.insert(all_measurements.end(), t_result.second.begin(), t_result.second.end()); + } + + // calc worst latenct + auto worst = *std::max_element(all_measurements.begin(), all_measurements.end()); + + // calc avg + auto total = accumulate(begin(all_measurements), end(all_measurements), 0, std::plus()); + auto avg = double(total)/all_measurements.size(); + + std::cout << "[g3log] worst: " << std::setw(10) << std::right << worst << "\tAvg: " << avg << "\tTotal: " << utils::format(total_us) << " us" << std::endl; + +} +}// anonymous + + +// The purpose of this test is NOT to see how fast +// each thread can possibly write. It is to see what +// the worst latency is for writing a log entry +// +// In the test 1 million log entries will be written +// an atomic counter is used to give each thread what +// it is to write next. The overhead of atomic +// synchronization between the threads are not counted in the worst case latency +int main(int argc, char** argv) +{ + size_t number_of_threads {0}; + if (argc == 2) + { + number_of_threads = atoi(argv[1]); + } + if (argc != 2 || number_of_threads == 0) + { + std::cerr << "USAGE is: " << argv[0] << " number_threads" << std::endl; + return 1; + } + + + std::vector threads(number_of_threads); + std::map> threads_result; + + for (size_t idx = 0; idx < number_of_threads; ++idx) + { + // reserve to 1 million for all the result + // it's a test so let's not care about the wasted space + threads_result[idx].reserve(g_iterations); + } + + const std::string g_path = "./" ; + const std::string g_prefix_log_name = "g3log-performance-"; + const std::string g_measurement_dump = g_path + g_prefix_log_name + "_RESULT.txt"; + + auto worker = g3::LogWorker::createLogWorker(); + auto handle= worker->addDefaultLogger(argv[0], "g3log.txt"); + g3::initializeLogging(worker.get()); + + auto start_time_application_total = std::chrono::high_resolution_clock::now(); + for (uint64_t idx = 0; idx < number_of_threads; ++idx) + { + threads[idx] = std::thread(MeasurePeakDuringLogWrites, idx, std::ref(threads_result[idx])); + } + for (size_t idx = 0; idx < number_of_threads; ++idx) + { + threads[idx].join(); + } + auto stop_time_application_total = std::chrono::high_resolution_clock::now(); + + uint64_t total_time_in_us = std::chrono::duration_cast(stop_time_application_total - start_time_application_total).count(); + PrintResults(threads_result, total_time_in_us); + return 0; +} + + diff --git a/bench/latency/spdlog-latency.cpp b/bench/latency/spdlog-latency.cpp new file mode 100644 index 000000000..ed4966cc4 --- /dev/null +++ b/bench/latency/spdlog-latency.cpp @@ -0,0 +1,128 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils.h" +#include + +#include "spdlog/spdlog.h" + +namespace spd = spdlog; + +namespace +{ +const uint64_t g_iterations = 1000000; + + +std::atomic g_counter = {0}; + + +void MeasurePeakDuringLogWrites(const size_t id, std::vector& result) +{ + auto logger = spd::get("file_logger"); + while (true) + { + const size_t value_now = ++g_counter; + if (value_now > g_iterations) + { + return; + } + + auto start_time = std::chrono::high_resolution_clock::now(); + logger->info("Some text to log for thread: [somemore text...............................] {}", id); + auto stop_time = std::chrono::high_resolution_clock::now(); + uint64_t time_us = std::chrono::duration_cast(stop_time - start_time).count(); + result.push_back(time_us); + } +} + + +void PrintResults(const std::map>& threads_result, size_t total_us) +{ + + std::vector all_measurements; + all_measurements.reserve(g_iterations); + for (auto& t_result : threads_result) + { + all_measurements.insert(all_measurements.end(), t_result.second.begin(), t_result.second.end()); + } + + // calc worst latenct + auto worst = *std::max_element(all_measurements.begin(), all_measurements.end()); + + // calc avg + auto total = accumulate(begin(all_measurements), end(all_measurements), 0, std::plus()); + auto avg = double(total)/all_measurements.size(); + + std::cout << "[spdlog] worst: " << std::setw(10) << std::right << worst << "\tAvg: " << avg << "\tTotal: " << utils::format(total_us) << " us" << std::endl; + +} +}// anonymous + + +// The purpose of this test is NOT to see how fast +// each thread can possibly write. It is to see what +// the worst latency is for writing a log entry +// +// In the test 1 million log entries will be written +// an atomic counter is used to give each thread what +// it is to write next. The overhead of atomic +// synchronization between the threads are not counted in the worst case latency +int main(int argc, char** argv) +{ + size_t number_of_threads {0}; + if (argc == 2) + { + number_of_threads = atoi(argv[1]); + } + if (argc != 2 || number_of_threads == 0) + { + std::cerr << "usage: " << argv[0] << " number_threads" << std::endl; + return 1; + } + + + std::vector threads(number_of_threads); + std::map> threads_result; + + for (size_t idx = 0; idx < number_of_threads; ++idx) + { + // reserve to 1 million for all the result + // it's a test so let's not care about the wasted space + threads_result[idx].reserve(g_iterations); + } + + int queue_size = 1048576; // 2 ^ 20 + spdlog::set_async_mode(queue_size); + auto logger = spdlog::create("file_logger", "spdlog.log", true); + + //force flush on every call to compare with g3log + auto s = (spd::sinks::simple_file_sink_mt*)logger->sinks()[0].get(); + s->set_force_flush(true); + + auto start_time_application_total = std::chrono::high_resolution_clock::now(); + for (uint64_t idx = 0; idx < number_of_threads; ++idx) + { + threads[idx] = std::thread(MeasurePeakDuringLogWrites, idx, std::ref(threads_result[idx])); + } + for (size_t idx = 0; idx < number_of_threads; ++idx) + { + threads[idx].join(); + } + auto stop_time_application_total = std::chrono::high_resolution_clock::now(); + + uint64_t total_time_in_us = std::chrono::duration_cast(stop_time_application_total - start_time_application_total).count(); + + PrintResults(threads_result, total_time_in_us); + return 0; +} + + diff --git a/bench/latency/utils.h b/bench/latency/utils.h new file mode 100644 index 000000000..b260f7249 --- /dev/null +++ b/bench/latency/utils.h @@ -0,0 +1,35 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include +#include +#include + +namespace utils +{ + +template +inline std::string format(const T& value) +{ + static std::locale loc(""); + std::stringstream ss; + ss.imbue(loc); + ss << value; + return ss.str(); +} + +template<> +inline std::string format(const double & value) +{ + static std::locale loc(""); + std::stringstream ss; + ss.imbue(loc); + ss << std::fixed << std::setprecision(1) << value; + return ss.str(); +} + +} diff --git a/bench/spdlog-null-async.cpp b/bench/spdlog-null-async.cpp index 435d7eb58..3874371a7 100644 --- a/bench/spdlog-null-async.cpp +++ b/bench/spdlog-null-async.cpp @@ -46,7 +46,7 @@ int main(int argc, char* argv[]) if (argc > 3) queue_size = atoi(argv[3]); - + cout << "\n*******************************************************************************\n"; cout << "async logging.. " << threads << " threads sharing same logger, " << format(howmany) << " messages " << endl; cout << "*******************************************************************************\n"; @@ -64,7 +64,7 @@ int main(int argc, char* argv[]) } std::cout << endl; std::cout << "Avg rate: " << format(total_rate/iters) << "/sec" <info("Welcome to spdlog!"); - console->error("An error message example {}..", 1); + console->error("Some error message with arg{}..", 1); // Formatting examples console->warn("Easy padding in numbers like {:08d}", 12); console->critical("Support for int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}", 42); console->info("Support for floats {:03.2f}", 1.23456); console->info("Positional args are {1} {0}..", "too", "supported"); - console->info("{:<30}", "left aligned"); - console->info("{:>30}", "right aligned"); - console->info("{:^30}", "centered"); + spd::get("console")->info("loggers can be retrieved from a global registry using the spdlog::get(logger_name) function"); - // Runtime log levels - spd::set_level(spd::level::info); //Set global log level to info - console->debug("This message shold not be displayed!"); - console->set_level(spd::level::debug); // Set specific logger's log level - console->debug("This message shold be displayed.."); // Create basic file logger (not rotated) - auto my_logger = spd::basic_logger_mt("basic_logger", "logs/basic.txt"); + auto my_logger = spd::basic_logger_mt("basic_logger", "logs/basic"); my_logger->info("Some log message"); #if defined(__ANDROID__) @@ -73,8 +66,15 @@ int main(int, char*[]) spd::set_pattern("*** [%H:%M:%S %z] [thread %t] %v ***"); rotating_logger->info("This is another message with custom format"); - // Compile time debug or trace macros. - // Enabled #ifdef SPDLOG_DEBUG_ON or #ifdef SPDLOG_TRACE_ON + + // Runtime log levels + spd::set_level(spd::level::info); //Set global log level to info + console->debug("This message shold not be displayed!"); + console->set_level(spd::level::debug); // Set specific logger's log level + console->debug("This message shold be displayed.."); + + // Compile time log levels + // define SPDLOG_DEBUG_ON or SPDLOG_TRACE_ON SPDLOG_TRACE(console, "Enabled only #ifdef SPDLOG_TRACE_ON..{} ,{}", 1, 3.23); SPDLOG_DEBUG(console, "Enabled only #ifdef SPDLOG_DEBUG_ON.. {} ,{}", 1, 3.23); @@ -121,7 +121,7 @@ void async_example() size_t q_size = 4096; //queue size must be power of 2 spdlog::set_async_mode(q_size); - auto async_file = spd::daily_logger_st("async_file_logger", log_dir + "async_log.txt"); + auto async_file = spd::daily_logger_st("async_file_logger", log_dir + "async_log"); for (int i = 0; i < 100; ++i) async_file->info("Async message #{}", i); diff --git a/example/example.vcxproj b/example/example.vcxproj index e0c58aeea..63db2b5dd 100644 --- a/example/example.vcxproj +++ b/example/example.vcxproj @@ -42,6 +42,7 @@ + @@ -55,13 +56,13 @@ Application true - v140 + v120 Unicode Application false - v140 + v120 true Unicode diff --git a/include/spdlog/async_logger.h b/include/spdlog/async_logger.h index 1c42fd9ce..76d70a3d5 100644 --- a/include/spdlog/async_logger.h +++ b/include/spdlog/async_logger.h @@ -63,6 +63,11 @@ class async_logger :public logger //Wait for the queue to be empty, and flush synchronously //Warning: this can potentialy last forever as we wait it to complete void flush() override; + + // Error handler + virtual void set_error_handler(log_err_handler) override; + virtual log_err_handler error_handler() override; + protected: void _sink_it(details::log_msg& msg) override; void _set_formatter(spdlog::formatter_ptr msg_formatter) override; diff --git a/include/spdlog/common.h b/include/spdlog/common.h index 490deec76..a0a227ef6 100644 --- a/include/spdlog/common.h +++ b/include/spdlog/common.h @@ -30,11 +30,11 @@ #endif #if defined(__GNUC__) || defined(__clang__) -#define DEPRECATED __attribute__((deprecated)) +#define SPDLOG_DEPRECATED __attribute__((deprecated)) #elif defined(_MSC_VER) -#define DEPRECATED __declspec(deprecated) +#define SPDLOG_DEPRECATED __declspec(deprecated) #else -#define DEPRECATED +#define SPDLOG_DEPRECATED #endif @@ -57,7 +57,7 @@ using formatter_ptr = std::shared_ptr; #if defined(SPDLOG_NO_ATOMIC_LEVELS) using level_t = details::null_atomic_int; #else -using level_t = std::atomic_int; +using level_t = std::atomic; #endif using log_err_handler = std::function; diff --git a/include/spdlog/details/async_log_helper.h b/include/spdlog/details/async_log_helper.h index 7e9b3eb17..92a0f4cdc 100644 --- a/include/spdlog/details/async_log_helper.h +++ b/include/spdlog/details/async_log_helper.h @@ -66,8 +66,8 @@ async_msg(async_msg&& other) SPDLOG_NOEXCEPT: msg_type(std::move(other.msg_type)) {} - async_msg(async_msg_type m_type) :msg_type(m_type) - {}; + async_msg(async_msg_type m_type):msg_type(m_type) + {} async_msg& operator=(async_msg&& other) SPDLOG_NOEXCEPT { @@ -82,10 +82,10 @@ async_msg(async_msg&& other) SPDLOG_NOEXCEPT: // never copy or assign. should only be moved.. async_msg(const async_msg&) = delete; - async_msg& operator=(async_msg& other) = delete; + async_msg& operator=(const async_msg& other) = delete; // construct from log_msg - async_msg(const details::log_msg& m) : + async_msg(const details::log_msg& m): level(m.level), time(m.time), thread_id(m.thread_id), @@ -135,6 +135,7 @@ async_msg(async_msg&& other) SPDLOG_NOEXCEPT: void flush(bool wait_for_q); + void set_error_handler(spdlog::log_err_handler err_handler); private: formatter_ptr _formatter; @@ -221,7 +222,8 @@ inline spdlog::details::async_log_helper::~async_log_helper() _worker_thread.join(); } catch (...) // don't crash in destructor - {} + { + } } @@ -229,8 +231,6 @@ inline spdlog::details::async_log_helper::~async_log_helper() inline void spdlog::details::async_log_helper::log(const details::log_msg& msg) { push_msg(async_msg(msg)); - - } inline void spdlog::details::async_log_helper::push_msg(details::async_log_helper::async_msg&& new_msg) @@ -246,45 +246,48 @@ inline void spdlog::details::async_log_helper::push_msg(details::async_log_helpe } while (!_q.enqueue(std::move(new_msg))); } - } // optionally wait for the queue be empty and request flush from the sinks inline void spdlog::details::async_log_helper::flush(bool wait_for_q) { push_msg(async_msg(async_msg_type::flush)); - if(wait_for_q) + if (wait_for_q) wait_empty_q(); //return only make after the above flush message was processed } inline void spdlog::details::async_log_helper::worker_loop() { - try + if (_worker_warmup_cb) _worker_warmup_cb(); + auto last_pop = details::os::now(); + auto last_flush = last_pop; + auto active = true; + while (active) { - if (_worker_warmup_cb) _worker_warmup_cb(); - auto last_pop = details::os::now(); - auto last_flush = last_pop; - while(process_next_msg(last_pop, last_flush)); - if (_worker_teardown_cb) _worker_teardown_cb(); - } - catch (const std::exception &ex) - { - _err_handler(ex.what()); - } - catch (...) - { - _err_handler("Unknown exception"); + try + { + active = process_next_msg(last_pop, last_flush); + } + catch (const std::exception &ex) + { + _err_handler(ex.what()); + } + catch (...) + { + _err_handler("Unknown exception"); + } } + if (_worker_teardown_cb) _worker_teardown_cb(); + + } // process next message in the queue // return true if this thread should still be active (while no terminate msg was received) inline bool spdlog::details::async_log_helper::process_next_msg(log_clock::time_point& last_pop, log_clock::time_point& last_flush) { - async_msg incoming_async_msg; - if (_q.dequeue(incoming_async_msg)) { last_pop = details::os::now(); @@ -305,7 +308,7 @@ inline bool spdlog::details::async_log_helper::process_next_msg(log_clock::time_ _formatter->format(incoming_log_msg); for (auto &s : _sinks) { - if(s->should_log( incoming_log_msg.level)) + if (s->should_log(incoming_log_msg.level)) { s->log(incoming_log_msg); } @@ -359,8 +362,7 @@ inline void spdlog::details::async_log_helper::sleep_or_yield(const spdlog::log_ // yield upto 150 micros if (time_since_op <= microseconds(100)) - return yield(); - + return std::this_thread::yield(); // sleep for 20 ms upto 200 ms if (time_since_op <= milliseconds(200)) @@ -378,13 +380,12 @@ inline void spdlog::details::async_log_helper::wait_empty_q() { sleep_or_yield(details::os::now(), last_op); } - } - - - - +inline void spdlog::details::async_log_helper::set_error_handler(spdlog::log_err_handler err_handler) +{ + _err_handler = err_handler; +} diff --git a/include/spdlog/details/async_logger_impl.h b/include/spdlog/details/async_logger_impl.h index 736d2e317..487c628a2 100644 --- a/include/spdlog/details/async_logger_impl.h +++ b/include/spdlog/details/async_logger_impl.h @@ -31,13 +31,13 @@ inline spdlog::async_logger::async_logger(const std::string& logger_name, } inline spdlog::async_logger::async_logger(const std::string& logger_name, - sinks_init_list sinks, + sinks_init_list sinks_list, size_t queue_size, const async_overflow_policy overflow_policy, const std::function& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms, const std::function& worker_teardown_cb) : - async_logger(logger_name, sinks.begin(), sinks.end(), queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb) {} + async_logger(logger_name, sinks_list.begin(), sinks_list.end(), queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms, worker_teardown_cb) {} inline spdlog::async_logger::async_logger(const std::string& logger_name, sink_ptr single_sink, @@ -57,6 +57,19 @@ inline void spdlog::async_logger::flush() _async_log_helper->flush(true); } +// Error handler +inline void spdlog::async_logger::set_error_handler(spdlog::log_err_handler err_handler) +{ + _err_handler = err_handler; + _async_log_helper->set_error_handler(err_handler); + +} +inline spdlog::log_err_handler spdlog::async_logger::error_handler() +{ + return _err_handler; +} + + inline void spdlog::async_logger::_set_formatter(spdlog::formatter_ptr msg_formatter) { _formatter = msg_formatter; diff --git a/include/spdlog/details/file_helper.h b/include/spdlog/details/file_helper.h index 2e6ce9d22..074d9b835 100644 --- a/include/spdlog/details/file_helper.h +++ b/include/spdlog/details/file_helper.h @@ -32,7 +32,7 @@ class file_helper const int open_interval = 10; explicit file_helper() : - _fd(nullptr) + _fd(nullptr) {} file_helper(const file_helper&) = delete; @@ -89,7 +89,7 @@ class file_helper size_t msg_size = msg.formatted.size(); auto data = msg.formatted.data(); if (std::fwrite(data, 1, msg_size, _fd) != msg_size) - throw spdlog_ex("Failed writing to file " + os::filename_to_str(_filename), errno); + throw spdlog_ex("Failed writing to file " + os::filename_to_str(_filename), errno); } size_t size() @@ -112,7 +112,7 @@ class file_helper private: FILE* _fd; - filename_t _filename; + filename_t _filename; }; } } diff --git a/include/spdlog/details/logger_impl.h b/include/spdlog/details/logger_impl.h index a337b3596..5bb85f679 100644 --- a/include/spdlog/details/logger_impl.h +++ b/include/spdlog/details/logger_impl.h @@ -121,44 +121,42 @@ inline void spdlog::logger::log(level::level_enum lvl, const T& msg) } -template -inline void spdlog::logger::trace(const char* fmt, const Args&... args) +template +inline void spdlog::logger::trace(const char* fmt, const Arg1 &arg1, const Args&... args) { - log(level::trace, fmt, args...); + log(level::trace, fmt, arg1, args...); } -template -inline void spdlog::logger::debug(const char* fmt, const Args&... args) +template +inline void spdlog::logger::debug(const char* fmt, const Arg1 &arg1, const Args&... args) { - log(level::debug, fmt, args...); + log(level::debug, fmt, arg1, args...); } -template -inline void spdlog::logger::info(const char* fmt, const Args&... args) +template +inline void spdlog::logger::info(const char* fmt, const Arg1 &arg1, const Args&... args) { - log(level::info, fmt, args...); + log(level::info, fmt, arg1, args...); } - -template -inline void spdlog::logger::warn(const char* fmt, const Args&... args) +template +inline void spdlog::logger::warn(const char* fmt, const Arg1 &arg1, const Args&... args) { - log(level::warn, fmt, args...); + log(level::warn, fmt, arg1, args...); } -template -inline void spdlog::logger::error(const char* fmt, const Args&... args) +template +inline void spdlog::logger::error(const char* fmt, const Arg1 &arg1, const Args&... args) { - log(level::err, fmt, args...); + log(level::err, fmt, arg1, args...); } -template -inline void spdlog::logger::critical(const char* fmt, const Args&... args) +template +inline void spdlog::logger::critical(const char* fmt, const Arg1 &arg1, const Args&... args) { - log(level::critical, fmt, args...); + log(level::critical, fmt, arg1, args...); } - template inline void spdlog::logger::trace(const T& msg) { @@ -291,3 +289,8 @@ inline bool spdlog::logger::_should_flush_on(const details::log_msg &msg) const auto flush_level = _flush_level.load(std::memory_order_relaxed); return (msg.level >= flush_level) && (msg.level != level::off); } + +inline const std::vector& spdlog::logger::sinks() const +{ + return _sinks; +} diff --git a/include/spdlog/details/os.h b/include/spdlog/details/os.h index 6d27bc39e..b63ce667f 100644 --- a/include/spdlog/details/os.h +++ b/include/spdlog/details/os.h @@ -10,10 +10,12 @@ #include #include #include +#include +#include #include #include #include - +#include #ifdef _WIN32 @@ -25,28 +27,32 @@ #define WIN32_LEAN_AND_MEAN #endif #include +#include // _get_pid support +#include // _get_osfhandle support #ifdef __MINGW32__ #include #endif -#include -#include +#else // unix -#elif __linux__ +#include +#include +#ifdef __linux__ #include //Use gettid() syscall under linux to get thread id -#include -#include #elif __FreeBSD__ #include //Use thr_self() syscall under FreeBSD to get thread id +#endif -#else -#include +#endif //unix +#ifndef __has_feature // Clang - feature checking macros. +#define __has_feature(x) 0 // Compatibility with non-clang compilers. #endif + namespace spdlog { namespace details @@ -136,6 +142,18 @@ inline bool operator!=(const std::tm& tm1, const std::tm& tm2) SPDLOG_CONSTEXPR static const char* eol = SPDLOG_EOL; SPDLOG_CONSTEXPR static int eol_size = sizeof(SPDLOG_EOL) - 1; +inline void prevent_child_fd(FILE *f) +{ +#ifdef _WIN32 + auto file_handle = (HANDLE)_get_osfhandle(_fileno(f)); + if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0)) + throw spdlog_ex("SetHandleInformation failed", errno); +#else + auto fd = fileno(f); + if(fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + throw spdlog_ex("fcntl with FD_CLOEXEC failed", errno); +#endif +} //fopen_s on non windows for writing @@ -147,13 +165,18 @@ inline int fopen_s(FILE** fp, const filename_t& filename, const filename_t& mode #else *fp = _fsopen((filename.c_str()), mode.c_str(), _SH_DENYWR); #endif - return *fp == nullptr; -#else +#else //unix *fp = fopen((filename.c_str()), mode.c_str()); - return *fp == nullptr; #endif + +#ifdef SPDLOG_PREVENT_CHILD_FD + if(*fp != nullptr) + prevent_child_fd(*fp); +#endif + return *fp == nullptr; } + inline int remove(const filename_t &filename) { #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) @@ -216,11 +239,11 @@ inline size_t filesize(FILE *f) #if !defined(__FreeBSD__) && !defined(__APPLE__) && (defined(__x86_64__) || defined(__ppc64__)) struct stat64 st; if (fstat64(fd, &st) == 0) - return st.st_size; + return static_cast(st.st_size); #else // unix 32 bits or osx struct stat st; if (fstat(fd, &st) == 0) - return st.st_size; + return static_cast(st.st_size); #endif #endif throw spdlog_ex("Failed getting file size from fd", errno); @@ -293,7 +316,7 @@ inline int utc_minutes_offset(const std::tm& tm = details::os::localtime()) //Return current thread id as size_t //It exists because the std::this_thread::get_id() is much slower(espcially under VS 2013) -inline size_t thread_id() +inline size_t _thread_id() { #ifdef _WIN32 return static_cast(::GetCurrentThreadId()); @@ -309,11 +332,22 @@ inline size_t thread_id() #else //Default to standard C++11 (OSX and other Unix) return static_cast(std::hash()(std::this_thread::get_id())); #endif +} - +//Return current thread id as size_t (from thread local storage) +inline size_t thread_id() +{ +#if defined(_MSC_VER) && (_MSC_VER < 1900) || defined(__clang__) && !__has_feature(cxx_thread_local) + return _thread_id(); +#else + static thread_local const size_t tid = _thread_id(); + return tid; +#endif } + + // wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) #if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) #define SPDLOG_FILENAME_T(s) L ## s @@ -343,8 +377,8 @@ inline std::string errno_str(int err_num) else return "Unkown error"; -#elif defined(__FreeBSD__) || defined(__APPLE__) || defined(ANDROID) || \ - ((_POSIX_C_SOURCE >= 200112L) && ! _GNU_SOURCE) // posix version +#elif defined(__FreeBSD__) || defined(__APPLE__) || defined(ANDROID) || defined(__SUNPRO_CC) || \ + ((_POSIX_C_SOURCE >= 200112L) && ! defined(_GNU_SOURCE)) // posix version if (strerror_r(err_num, buf, buf_size) == 0) return std::string(buf); @@ -356,6 +390,17 @@ inline std::string errno_str(int err_num) #endif } +inline int pid() +{ + +#ifdef _WIN32 + return ::_getpid(); +#else + return static_cast(::getpid()); +#endif + +} + } //os } //details } //spdlog diff --git a/include/spdlog/details/pattern_formatter_impl.h b/include/spdlog/details/pattern_formatter_impl.h index 73c0db3b9..70b9dc807 100644 --- a/include/spdlog/details/pattern_formatter_impl.h +++ b/include/spdlog/details/pattern_formatter_impl.h @@ -18,6 +18,7 @@ #include #include #include +#include namespace spdlog { @@ -78,42 +79,60 @@ static int to12h(const tm& t) } //Abbreviated weekday name -static const std::string days[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; +using days_array = std::array; +static const days_array& days() +{ + static const days_array arr{ { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" } }; + return arr; +} class a_formatter:public flag_formatter { void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted << days[tm_time.tm_wday]; + msg.formatted << days()[tm_time.tm_wday]; } }; //Full weekday name -static const std::string full_days[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; +static const days_array& full_days() +{ + static const days_array arr{ { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" } }; + return arr; +} class A_formatter:public flag_formatter { void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted << full_days[tm_time.tm_wday]; + msg.formatted << full_days()[tm_time.tm_wday]; } }; //Abbreviated month -static const std::string months[] { "Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec" }; +using months_array = std::array; +static const months_array& months() +{ + static const months_array arr{ { "Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec" } }; + return arr; +} class b_formatter:public flag_formatter { void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted << months[tm_time.tm_mon]; + msg.formatted << months()[tm_time.tm_mon]; } }; //Full month name -static const std::string full_months[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; +static const months_array& full_months() +{ + static const months_array arr{ { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" } }; + return arr; +} class B_formatter:public flag_formatter { void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted << full_months[tm_time.tm_mon]; + msg.formatted << full_months()[tm_time.tm_mon]; } }; @@ -138,7 +157,7 @@ class c_formatter:public flag_formatter { void format(details::log_msg& msg, const std::tm& tm_time) override { - msg.formatted << days[tm_time.tm_wday] << ' ' << months[tm_time.tm_mon] << ' ' << tm_time.tm_mday << ' '; + msg.formatted << days()[tm_time.tm_wday] << ' ' << months()[tm_time.tm_mon] << ' ' << tm_time.tm_mday << ' '; pad_n_join(msg.formatted, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, ':') << ' ' << tm_time.tm_year + 1900; } }; @@ -356,7 +375,7 @@ class z_formatter:public flag_formatter -//Thread id +// Thread id class t_formatter:public flag_formatter { void format(details::log_msg& msg, const std::tm&) override @@ -365,6 +384,15 @@ class t_formatter:public flag_formatter } }; +// Current pid +class pid_formatter:public flag_formatter +{ + void format(details::log_msg& msg, const std::tm&) override + { + msg.formatted << details::os::pid(); + } +}; + class v_formatter:public flag_formatter { @@ -453,6 +481,8 @@ class full_formatter:public flag_formatter } }; + + } } /////////////////////////////////////////////////////////////////////////////// @@ -611,6 +641,10 @@ inline void spdlog::pattern_formatter::handle_flag(char flag) _formatters.push_back(std::unique_ptr(new details::full_formatter())); break; + case ('P'): + _formatters.push_back(std::unique_ptr(new details::pid_formatter())); + break; + default: //Unkown flag appears as is _formatters.push_back(std::unique_ptr(new details::ch_formatter('%'))); _formatters.push_back(std::unique_ptr(new details::ch_formatter(flag))); diff --git a/include/spdlog/details/spdlog_impl.h b/include/spdlog/details/spdlog_impl.h index bc283c835..79d3ac450 100644 --- a/include/spdlog/details/spdlog_impl.h +++ b/include/spdlog/details/spdlog_impl.h @@ -12,9 +12,20 @@ #include #include #include +#ifdef SPDLOG_ENABLE_SYSLOG #include +#endif + +#ifdef _WIN32 +#include +#else #include +#endif + + +#ifdef __ANDROID__ #include +#endif #include #include @@ -50,53 +61,105 @@ inline std::shared_ptr spdlog::basic_logger_st(const std::string // Create multi/single threaded rotating file logger inline std::shared_ptr spdlog::rotating_logger_mt(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files) { - return create(logger_name, filename, SPDLOG_FILENAME_T("txt"), max_file_size, max_files); + return create(logger_name, filename, max_file_size, max_files); } inline std::shared_ptr spdlog::rotating_logger_st(const std::string& logger_name, const filename_t& filename, size_t max_file_size, size_t max_files) { - return create(logger_name, filename, SPDLOG_FILENAME_T("txt"), max_file_size, max_files); + return create(logger_name, filename, max_file_size, max_files); } // Create file logger which creates new file at midnight): inline std::shared_ptr spdlog::daily_logger_mt(const std::string& logger_name, const filename_t& filename, int hour, int minute) { - return create(logger_name, filename, SPDLOG_FILENAME_T("txt"), hour, minute); + return create(logger_name, filename, hour, minute); } inline std::shared_ptr spdlog::daily_logger_st(const std::string& logger_name, const filename_t& filename, int hour, int minute) { - return create(logger_name, filename, SPDLOG_FILENAME_T("txt"), hour, minute); + return create(logger_name, filename, hour, minute); +} + + +// +// stdout/stderr loggers +// +inline std::shared_ptr spdlog::stdout_logger_mt(const std::string& logger_name) +{ + return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stdout_sink_mt::instance()); +} + +inline std::shared_ptr spdlog::stdout_logger_st(const std::string& logger_name) +{ + return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stdout_sink_st::instance()); } -// Create stdout/stderr loggers (with optinal color support) -inline std::shared_ptr create_console_logger(const std::string& logger_name, spdlog::sink_ptr sink, bool color) +inline std::shared_ptr spdlog::stderr_logger_mt(const std::string& logger_name) { - if (color) //use color wrapper sink - sink = std::make_shared(sink); + return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stderr_sink_mt::instance()); +} + +inline std::shared_ptr spdlog::stderr_logger_st(const std::string& logger_name) +{ + return spdlog::details::registry::instance().create(logger_name, spdlog::sinks::stderr_sink_st::instance()); +} + +// +// stdout/stderr color loggers +// +#ifdef _WIN32 +inline std::shared_ptr spdlog::stdout_color_mt(const std::string& logger_name) +{ + auto sink = std::make_shared(); return spdlog::details::registry::instance().create(logger_name, sink); } -inline std::shared_ptr spdlog::stdout_logger_mt(const std::string& logger_name, bool color) +inline std::shared_ptr spdlog::stdout_color_st(const std::string& logger_name) { - return create_console_logger(logger_name, sinks::stdout_sink_mt::instance(), color); + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); } -inline std::shared_ptr spdlog::stdout_logger_st(const std::string& logger_name, bool color) +inline std::shared_ptr spdlog::stderr_color_mt(const std::string& logger_name) { - return create_console_logger(logger_name, sinks::stdout_sink_st::instance(), color); + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); } -inline std::shared_ptr spdlog::stderr_logger_mt(const std::string& logger_name, bool color) + +inline std::shared_ptr spdlog::stderr_color_st(const std::string& logger_name) { - return create_console_logger(logger_name, sinks::stderr_sink_mt::instance(), color); + auto sink = std::make_shared(); + return spdlog::details::registry::instance().create(logger_name, sink); } -inline std::shared_ptr spdlog::stderr_logger_st(const std::string& logger_name, bool color) +#else //ansi terminal colors + +inline std::shared_ptr spdlog::stdout_color_mt(const std::string& logger_name) { - return create_console_logger(logger_name, sinks::stderr_sink_st::instance(), color); + auto sink = std::make_shared(spdlog::sinks::stdout_sink_mt::instance()); + return spdlog::details::registry::instance().create(logger_name, sink); } +inline std::shared_ptr spdlog::stdout_color_st(const std::string& logger_name) +{ + auto sink = std::make_shared(spdlog::sinks::stdout_sink_st::instance()); + return spdlog::details::registry::instance().create(logger_name, sink); +} + +inline std::shared_ptr spdlog::stderr_color_mt(const std::string& logger_name) +{ + auto sink = std::make_shared(spdlog::sinks::stderr_sink_mt::instance()); + return spdlog::details::registry::instance().create(logger_name, sink); +} + +inline std::shared_ptr spdlog::stderr_color_st(const std::string& logger_name) +{ + auto sink = std::make_shared(spdlog::sinks::stderr_sink_st::instance()); + return spdlog::details::registry::instance().create(logger_name, sink); +} +#endif + #ifdef SPDLOG_ENABLE_SYSLOG // Create syslog logger inline std::shared_ptr spdlog::syslog_logger(const std::string& logger_name, const std::string& syslog_ident, int syslog_option) @@ -105,7 +168,7 @@ inline std::shared_ptr spdlog::syslog_logger(const std::string& } #endif -#if defined(__ANDROID__) +#ifdef __ANDROID__ inline std::shared_ptr spdlog::android_logger(const std::string& logger_name, const std::string& tag) { return create(logger_name, tag); diff --git a/include/spdlog/fmt/bundled/format.cc b/include/spdlog/fmt/bundled/format.cc index 0f7e0aa24..2bd774e44 100644 --- a/include/spdlog/fmt/bundled/format.cc +++ b/include/spdlog/fmt/bundled/format.cc @@ -25,9 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -// Commented out by spdlog to use header only -// #include "fmt/format.h" -// #include "fmt/printf.h" +#include "format.h" #include @@ -107,6 +105,27 @@ inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) { # define FMT_SWPRINTF swprintf #endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) +// Checks if a value fits in int - used to avoid warnings about comparing +// signed and unsigned integers. +template +struct IntChecker { + template + static bool fits_in_int(T value) { + unsigned max = INT_MAX; + return value <= max; + } + static bool fits_in_int(bool) { return true; } +}; + +template <> +struct IntChecker { + template + static bool fits_in_int(T value) { + return value >= INT_MIN && value <= INT_MAX; + } + static bool fits_in_int(int) { return true; } +}; + const char RESET_COLOR[] = "\x1b[0m"; typedef void (*FormatFunc)(Writer &, int, StringRef); @@ -211,29 +230,222 @@ void report_error(FormatFunc func, int error_code, 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 ArgVisitor { + public: + template + bool visit_any_int(T value) { return value == 0; } +}; + +// Checks if an argument is a valid printf width specifier and sets +// left alignment if it is negative. +class WidthHandler : public ArgVisitor { + private: + FormatSpec &spec_; + + FMT_DISALLOW_COPY_AND_ASSIGN(WidthHandler); + + public: + explicit WidthHandler(FormatSpec &spec) : spec_(spec) {} + + void report_unhandled_arg() { + FMT_THROW(FormatError("width is not integer")); + } + + template + unsigned visit_any_int(T value) { + typedef typename internal::IntTraits::MainType UnsignedType; + UnsignedType width = static_cast(value); + if (internal::is_negative(value)) { + spec_.align_ = ALIGN_LEFT; + width = 0 - width; + } + if (width > INT_MAX) + FMT_THROW(FormatError("number is too big")); + return static_cast(width); + } +}; + +class PrecisionHandler : public ArgVisitor { + public: + void report_unhandled_arg() { + FMT_THROW(FormatError("precision is not integer")); + } + + template + int visit_any_int(T value) { + if (!IntChecker::is_signed>::fits_in_int(value)) + FMT_THROW(FormatError("number is too big")); + return static_cast(value); + } +}; + +template +struct is_same { + enum { value = 0 }; +}; + +template +struct is_same { + enum { value = 1 }; +}; + +// An argument visitor that converts an integer argument to T for printf, +// if T is an integral type. If T is void, the argument is converted to +// corresponding signed or unsigned type depending on the type specifier: +// 'd' and 'i' - signed, other - unsigned) +template +class ArgConverter : public ArgVisitor, void> { + private: + internal::Arg &arg_; + wchar_t type_; + + FMT_DISALLOW_COPY_AND_ASSIGN(ArgConverter); + + public: + ArgConverter(internal::Arg &arg, wchar_t type) + : arg_(arg), type_(type) {} + + void visit_bool(bool value) { + if (type_ != 's') + visit_any_int(value); + } + + template + void visit_any_int(U value) { + bool is_signed = type_ == 'd' || type_ == 'i'; + using internal::Arg; + typedef typename internal::Conditional< + is_same::value, U, T>::type TargetType; + if (sizeof(TargetType) <= sizeof(int)) { + // Extra casts are used to silence warnings. + if (is_signed) { + arg_.type = Arg::INT; + arg_.int_value = static_cast(static_cast(value)); + } else { + arg_.type = Arg::UINT; + typedef typename internal::MakeUnsigned::Type Unsigned; + arg_.uint_value = static_cast(static_cast(value)); + } + } else { + if (is_signed) { + arg_.type = Arg::LONG_LONG; + // glibc's printf doesn't sign extend arguments of smaller types: + // std::printf("%lld", -42); // prints "4294967254" + // but we don't have to do the same because it's a UB. + arg_.long_long_value = static_cast(value); + } else { + arg_.type = Arg::ULONG_LONG; + arg_.ulong_long_value = + static_cast::Type>(value); + } + } + } +}; + +// Converts an integer argument to char for printf. +class CharConverter : public ArgVisitor { + private: + internal::Arg &arg_; + + FMT_DISALLOW_COPY_AND_ASSIGN(CharConverter); + + public: + explicit CharConverter(internal::Arg &arg) : arg_(arg) {} + + template + void visit_any_int(T value) { + arg_.type = internal::Arg::CHAR; + arg_.int_value = static_cast(value); + } +}; } // namespace namespace internal { -// This method is used to preserve binary compatibility with fmt 3.0. -// It can be removed in 4.0. -FMT_FUNC void format_system_error( - Writer &out, int error_code, StringRef message) FMT_NOEXCEPT { - fmt::format_system_error(out, error_code, message); -} +template +class PrintfArgFormatter : + public ArgFormatterBase, Char> { + + void write_null_pointer() { + this->spec().type_ = 0; + this->write("(nil)"); + } + + typedef ArgFormatterBase, Char> Base; + + public: + PrintfArgFormatter(BasicWriter &w, FormatSpec &s) + : ArgFormatterBase, Char>(w, s) {} + + void visit_bool(bool value) { + FormatSpec &fmt_spec = this->spec(); + if (fmt_spec.type_ != 's') + return this->visit_any_int(value); + fmt_spec.type_ = 0; + this->write(value); + } + + void visit_char(int value) { + const FormatSpec &fmt_spec = this->spec(); + BasicWriter &w = this->writer(); + if (fmt_spec.type_ && fmt_spec.type_ != 'c') + w.write_int(value, fmt_spec); + typedef typename BasicWriter::CharPtr CharPtr; + CharPtr out = CharPtr(); + if (fmt_spec.width_ > 1) { + Char fill = ' '; + out = w.grow_buffer(fmt_spec.width_); + if (fmt_spec.align_ != ALIGN_LEFT) { + std::fill_n(out, fmt_spec.width_ - 1, fill); + out += fmt_spec.width_ - 1; + } else { + std::fill_n(out + 1, fmt_spec.width_ - 1, fill); + } + } else { + out = w.grow_buffer(1); + } + *out = static_cast(value); + } + + void visit_cstring(const char *value) { + if (value) + Base::visit_cstring(value); + else if (this->spec().type_ == 'p') + write_null_pointer(); + else + this->write("(null)"); + } + + void visit_pointer(const void *value) { + if (value) + return Base::visit_pointer(value); + this->spec().type_ = 0; + write_null_pointer(); + } + + void visit_custom(Arg::CustomValue c) { + BasicFormatter formatter(ArgList(), this->writer()); + const Char format_str[] = {'}', 0}; + const Char *format = format_str; + c.format(&formatter, c.value, &format); + } +}; } // namespace internal +} // namespace fmt -FMT_FUNC void SystemError::init( +FMT_FUNC void fmt::SystemError::init( int err_code, CStringRef format_str, ArgList args) { error_code_ = err_code; MemoryWriter w; - format_system_error(w, err_code, format(format_str, args)); + internal::format_system_error(w, err_code, format(format_str, args)); std::runtime_error &base = *this; base = std::runtime_error(w.str()); } template -int internal::CharTraits::format_float( +int fmt::internal::CharTraits::format_float( char *buffer, std::size_t size, const char *format, unsigned width, int precision, T value) { if (width == 0) { @@ -247,7 +459,7 @@ int internal::CharTraits::format_float( } template -int internal::CharTraits::format_float( +int fmt::internal::CharTraits::format_float( wchar_t *buffer, std::size_t size, const wchar_t *format, unsigned width, int precision, T value) { if (width == 0) { @@ -261,7 +473,7 @@ int internal::CharTraits::format_float( } template -const char internal::BasicData::DIGITS[] = +const char fmt::internal::BasicData::DIGITS[] = "0001020304050607080910111213141516171819" "2021222324252627282930313233343536373839" "4041424344454647484950515253545556575859" @@ -280,34 +492,34 @@ const char internal::BasicData::DIGITS[] = factor * 1000000000 template -const uint32_t internal::BasicData::POWERS_OF_10_32[] = { +const uint32_t fmt::internal::BasicData::POWERS_OF_10_32[] = { 0, FMT_POWERS_OF_10(1) }; template -const uint64_t internal::BasicData::POWERS_OF_10_64[] = { +const uint64_t fmt::internal::BasicData::POWERS_OF_10_64[] = { 0, FMT_POWERS_OF_10(1), - FMT_POWERS_OF_10(ULongLong(1000000000)), + FMT_POWERS_OF_10(fmt::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::ULongLong(1000000000) * fmt::ULongLong(1000000000) * 10 }; -FMT_FUNC void internal::report_unknown_type(char code, const char *type) { +FMT_FUNC void fmt::internal::report_unknown_type(char code, const char *type) { (void)type; if (std::isprint(static_cast(code))) { - FMT_THROW(FormatError( - format("unknown format code '{}' for {}", code, type))); + FMT_THROW(fmt::FormatError( + fmt::format("unknown format code '{}' for {}", code, type))); } - FMT_THROW(FormatError( - format("unknown format code '\\x{:02x}' for {}", + FMT_THROW(fmt::FormatError( + fmt::format("unknown format code '\\x{:02x}' for {}", static_cast(code), type))); } #if FMT_USE_WINDOWS_H -FMT_FUNC internal::UTF8ToUTF16::UTF8ToUTF16(StringRef s) { +FMT_FUNC fmt::internal::UTF8ToUTF16::UTF8ToUTF16(fmt::StringRef s) { static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16"; if (s.size() > INT_MAX) FMT_THROW(WindowsError(ERROR_INVALID_PARAMETER, ERROR_MSG)); @@ -324,14 +536,14 @@ FMT_FUNC internal::UTF8ToUTF16::UTF8ToUTF16(StringRef s) { buffer_[length] = 0; } -FMT_FUNC internal::UTF16ToUTF8::UTF16ToUTF8(WStringRef s) { +FMT_FUNC fmt::internal::UTF16ToUTF8::UTF16ToUTF8(fmt::WStringRef s) { if (int error_code = convert(s)) { FMT_THROW(WindowsError(error_code, "cannot convert string from UTF-16 to UTF-8")); } } -FMT_FUNC int internal::UTF16ToUTF8::convert(WStringRef s) { +FMT_FUNC int fmt::internal::UTF16ToUTF8::convert(fmt::WStringRef s) { if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER; int s_size = static_cast(s.size()); @@ -347,7 +559,7 @@ FMT_FUNC int internal::UTF16ToUTF8::convert(WStringRef s) { return 0; } -FMT_FUNC void WindowsError::init( +FMT_FUNC void fmt::WindowsError::init( int err_code, CStringRef format_str, ArgList args) { error_code_ = err_code; MemoryWriter w; @@ -356,8 +568,9 @@ FMT_FUNC void WindowsError::init( base = std::runtime_error(w.str()); } -FMT_FUNC void internal::format_windows_error( - Writer &out, int error_code, StringRef message) FMT_NOEXCEPT { +FMT_FUNC void fmt::internal::format_windows_error( + fmt::Writer &out, int error_code, + fmt::StringRef message) FMT_NOEXCEPT { FMT_TRY { MemoryBuffer buffer; buffer.resize(INLINE_BUFFER_SIZE); @@ -384,11 +597,12 @@ FMT_FUNC void internal::format_windows_error( #endif // FMT_USE_WINDOWS_H -FMT_FUNC void format_system_error( - Writer &out, int error_code, StringRef message) FMT_NOEXCEPT { +FMT_FUNC void fmt::internal::format_system_error( + fmt::Writer &out, int error_code, + fmt::StringRef message) FMT_NOEXCEPT { FMT_TRY { - internal::MemoryBuffer buffer; - buffer.resize(internal::INLINE_BUFFER_SIZE); + MemoryBuffer buffer; + buffer.resize(INLINE_BUFFER_SIZE); for (;;) { char *system_message = &buffer[0]; int result = safe_strerror(error_code, system_message, buffer.size()); @@ -405,7 +619,7 @@ FMT_FUNC void format_system_error( } template -void internal::ArgMap::init(const ArgList &args) { +void fmt::internal::ArgMap::init(const ArgList &args) { if (!map_.empty()) return; typedef internal::NamedArg NamedArg; @@ -450,11 +664,11 @@ void internal::ArgMap::init(const ArgList &args) { } template -void internal::FixedBuffer::grow(std::size_t) { +void fmt::internal::FixedBuffer::grow(std::size_t) { FMT_THROW(std::runtime_error("buffer overflow")); } -FMT_FUNC Arg internal::FormatterBase::do_get_arg( +FMT_FUNC Arg fmt::internal::FormatterBase::do_get_arg( unsigned arg_index, const char *&error) { Arg arg = args_[arg_index]; switch (arg.type) { @@ -470,31 +684,203 @@ FMT_FUNC Arg internal::FormatterBase::do_get_arg( return arg; } -FMT_FUNC void report_system_error( +template +void fmt::internal::PrintfFormatter::parse_flags( + FormatSpec &spec, const Char *&s) { + for (;;) { + switch (*s++) { + case '-': + spec.align_ = ALIGN_LEFT; + break; + case '+': + spec.flags_ |= SIGN_FLAG | PLUS_FLAG; + break; + case '0': + spec.fill_ = '0'; + break; + case ' ': + spec.flags_ |= SIGN_FLAG; + break; + case '#': + spec.flags_ |= HASH_FLAG; + break; + default: + --s; + return; + } + } +} + +template +Arg fmt::internal::PrintfFormatter::get_arg( + const Char *s, unsigned arg_index) { + (void)s; + const char *error = 0; + Arg arg = arg_index == UINT_MAX ? + next_arg(error) : FormatterBase::get_arg(arg_index - 1, error); + if (error) + FMT_THROW(FormatError(!*s ? "invalid format string" : error)); + return arg; +} + +template +unsigned fmt::internal::PrintfFormatter::parse_header( + const Char *&s, FormatSpec &spec) { + unsigned arg_index = UINT_MAX; + Char c = *s; + if (c >= '0' && c <= '9') { + // Parse an argument index (if followed by '$') or a width possibly + // preceded with '0' flag(s). + unsigned value = parse_nonnegative_int(s); + if (*s == '$') { // value is an argument index + ++s; + arg_index = value; + } else { + if (c == '0') + spec.fill_ = '0'; + if (value != 0) { + // Nonzero value means that we parsed width and don't need to + // parse it or flags again, so return now. + spec.width_ = value; + return arg_index; + } + } + } + parse_flags(spec, s); + // Parse width. + if (*s >= '0' && *s <= '9') { + spec.width_ = parse_nonnegative_int(s); + } else if (*s == '*') { + ++s; + spec.width_ = WidthHandler(spec).visit(get_arg(s)); + } + return arg_index; +} + +template +void fmt::internal::PrintfFormatter::format( + BasicWriter &writer, BasicCStringRef format_str) { + const Char *start = format_str.c_str(); + 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_ = static_cast(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_ &= ~to_unsigned(HASH_FLAG); + if (spec.fill_ == '0') { + if (arg.type <= Arg::LAST_NUMERIC_TYPE) + spec.align_ = ALIGN_NUMERIC; + else + spec.fill_ = ' '; // Ignore '0' flag for non-numeric types. + } + + // Parse length and convert the argument to the required type. + switch (*s++) { + case 'h': + if (*s == 'h') + ArgConverter(arg, *++s).visit(arg); + else + ArgConverter(arg, *s).visit(arg); + break; + case 'l': + if (*s == 'l') + ArgConverter(arg, *++s).visit(arg); + else + ArgConverter(arg, *s).visit(arg); + break; + case 'j': + ArgConverter(arg, *s).visit(arg); + break; + case 'z': + ArgConverter(arg, *s).visit(arg); + break; + case 't': + ArgConverter(arg, *s).visit(arg); + break; + case 'L': + // printf produces garbage when 'L' is omitted for long double, no + // need to do the same. + break; + default: + --s; + ArgConverter(arg, *s).visit(arg); + } + + // Parse type. + if (!*s) + FMT_THROW(FormatError("invalid format string")); + spec.type_ = static_cast(*s++); + if (arg.type <= Arg::LAST_INTEGER_TYPE) { + // Normalize type. + switch (spec.type_) { + case 'i': case 'u': + spec.type_ = 'd'; + break; + case 'c': + // TODO: handle wchar_t + CharConverter(arg).visit(arg); + break; + } + } + + start = s; + + // Format argument. + internal::PrintfArgFormatter(writer, spec).visit(arg); + } + write(writer, start, s); +} + +FMT_FUNC void fmt::report_system_error( int error_code, fmt::StringRef message) FMT_NOEXCEPT { // 'fmt::' is for bcc32. - report_error(format_system_error, error_code, message); + fmt::report_error(internal::format_system_error, error_code, message); } #if FMT_USE_WINDOWS_H -FMT_FUNC void report_windows_error( +FMT_FUNC void fmt::report_windows_error( int error_code, fmt::StringRef message) FMT_NOEXCEPT { // 'fmt::' is for bcc32. - report_error(internal::format_windows_error, error_code, message); + fmt::report_error(internal::format_windows_error, error_code, message); } #endif -FMT_FUNC void print(std::FILE *f, CStringRef format_str, ArgList args) { +FMT_FUNC void fmt::print(std::FILE *f, CStringRef format_str, ArgList args) { MemoryWriter w; w.write(format_str, args); std::fwrite(w.data(), 1, w.size(), f); } -FMT_FUNC void print(CStringRef format_str, ArgList args) { +FMT_FUNC void fmt::print(CStringRef format_str, ArgList args) { print(stdout, format_str, args); } -FMT_FUNC void print_colored(Color c, CStringRef format, ArgList args) { +FMT_FUNC void fmt::print_colored(Color c, CStringRef format, ArgList args) { char escape[] = "\x1b[30m"; escape[3] = static_cast('0' + c); std::fputs(escape, stdout); @@ -502,10 +888,7 @@ FMT_FUNC void print_colored(Color c, CStringRef format, ArgList args) { std::fputs(RESET_COLOR, stdout); } -template -void printf(BasicWriter &w, BasicCStringRef format, ArgList args); - -FMT_FUNC int fprintf(std::FILE *f, CStringRef format, ArgList args) { +FMT_FUNC int fmt::fprintf(std::FILE *f, CStringRef format, ArgList args) { MemoryWriter w; printf(w, format, args); std::size_t size = w.size(); @@ -514,44 +897,44 @@ FMT_FUNC int fprintf(std::FILE *f, CStringRef format, ArgList args) { #ifndef FMT_HEADER_ONLY -template struct internal::BasicData; +template struct fmt::internal::BasicData; // Explicit instantiations for char. -template void internal::FixedBuffer::grow(std::size_t); +template void fmt::internal::FixedBuffer::grow(std::size_t); -template void internal::ArgMap::init(const ArgList &args); +template void fmt::internal::ArgMap::init(const fmt::ArgList &args); -template void PrintfFormatter::format(CStringRef format); +template void fmt::internal::PrintfFormatter::format( + BasicWriter &writer, CStringRef format); -template int internal::CharTraits::format_float( +template int fmt::internal::CharTraits::format_float( char *buffer, std::size_t size, const char *format, unsigned width, int precision, double value); -template int internal::CharTraits::format_float( +template int fmt::internal::CharTraits::format_float( char *buffer, std::size_t size, const char *format, unsigned width, int precision, long double value); // Explicit instantiations for wchar_t. -template void internal::FixedBuffer::grow(std::size_t); +template void fmt::internal::FixedBuffer::grow(std::size_t); -template void internal::ArgMap::init(const ArgList &args); +template void fmt::internal::ArgMap::init(const fmt::ArgList &args); -template void PrintfFormatter::format(WCStringRef format); +template void fmt::internal::PrintfFormatter::format( + BasicWriter &writer, WCStringRef format); -template int internal::CharTraits::format_float( +template int fmt::internal::CharTraits::format_float( wchar_t *buffer, std::size_t size, const wchar_t *format, unsigned width, int precision, double value); -template int internal::CharTraits::format_float( +template int fmt::internal::CharTraits::format_float( wchar_t *buffer, std::size_t size, const wchar_t *format, unsigned width, int precision, long double value); #endif // FMT_HEADER_ONLY -} // namespace fmt - #ifdef _MSC_VER # pragma warning(pop) #endif diff --git a/include/spdlog/fmt/bundled/format.h b/include/spdlog/fmt/bundled/format.h index 294a686fd..64c949bea 100644 --- a/include/spdlog/fmt/bundled/format.h +++ b/include/spdlog/fmt/bundled/format.h @@ -194,6 +194,17 @@ typedef __int64 intmax_t; # endif #endif +#ifndef FMT_OVERRIDE +# if FMT_USE_OVERRIDE || FMT_HAS_FEATURE(cxx_override) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ + FMT_MSC_VER >= 1900 +# define FMT_OVERRIDE override +# else +# define FMT_OVERRIDE +# endif +#endif + + // A macro to disallow the copy constructor and operator= functions // This should be used in the private: declarations for a class #ifndef FMT_USE_DELETED_FUNCTIONS @@ -420,9 +431,6 @@ typedef BasicWriter WWriter; template class ArgFormatter; -template -class BasicPrintfArgFormatter; - template > class BasicFormatter; @@ -766,7 +774,7 @@ class MemoryBuffer : private Allocator, public Buffer } protected: - void grow(std::size_t size); + void grow(std::size_t size) FMT_OVERRIDE; public: explicit MemoryBuffer(const Allocator &alloc = Allocator()) @@ -1163,6 +1171,9 @@ FMT_API void format_windows_error(fmt::Writer &out, int error_code, fmt::StringRef message) FMT_NOEXCEPT; #endif +FMT_API void format_system_error(fmt::Writer &out, int error_code, + fmt::StringRef message) FMT_NOEXCEPT; + // A formatting argument value. struct Value { @@ -1308,25 +1319,19 @@ struct Conditional }; // For bcc32 which doesn't understand ! in template arguments. -template +template struct Not { enum { value = 0 }; }; -template <> +template<> struct Not { enum { value = 1 }; }; -template -struct False -{ - enum { value = 0 }; -}; - -template struct LConvCheck +template struct LConvCheck { LConvCheck(int) {} }; @@ -1346,36 +1351,6 @@ inline fmt::StringRef thousands_sep(...) return ""; } -#define FMT_CONCAT(a, b) a##b - -#if FMT_GCC_VERSION >= 407 -# define FMT_UNUSED __attribute__((unused)) -#else -# define FMT_UNUSED -#endif - -#ifndef FMT_USE_STATIC_ASSERT -# define FMT_USE_STATIC_ASSERT 0 -#endif - -#if FMT_USE_STATIC_ASSERT || FMT_HAS_FEATURE(cxx_static_assert) || \ - (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || _MSC_VER >= 1600 -# define FMT_STATIC_ASSERT(cond, message) static_assert(cond, message) -#else -# define FMT_CONCAT_(a, b) FMT_CONCAT(a, b) -# define FMT_STATIC_ASSERT(cond, message) \ - typedef int FMT_CONCAT_(Assert, __LINE__)[(cond) ? 1 : -1] FMT_UNUSED -#endif - -template -void format_arg(Formatter &, const Char *, const T &) -{ - FMT_STATIC_ASSERT(False::value, - "Cannot format argument. To enable the use of ostream " - "operator<< include fmt/ostream.h. Otherwise provide " - "an overload of format_arg."); -} - // Makes an Arg object from any type. template class MakeValue : public Arg @@ -1423,9 +1398,9 @@ class MakeValue : public Arg static void format_custom_arg( void *formatter, const void *arg, void *format_str_ptr) { - format_arg(*static_cast(formatter), - *static_cast(format_str_ptr), - *static_cast(arg)); + format(*static_cast(formatter), + *static_cast(format_str_ptr), + *static_cast(arg)); } public: @@ -1590,6 +1565,9 @@ class RuntimeError : public std::runtime_error ~RuntimeError() throw(); }; +template +class PrintfArgFormatter; + template class ArgMap; } // namespace internal @@ -2233,29 +2211,29 @@ class ArgFormatterBase : public ArgVisitor typedef typename BasicWriter::CharPtr CharPtr; Char fill = internal::CharTraits::cast(spec_.fill()); CharPtr out = CharPtr(); - const unsigned CHAR_WIDTH = 1; - if (spec_.width_ > CHAR_WIDTH) + const unsigned CHAR_SIZE = 1; + if (spec_.width_ > CHAR_SIZE) { out = writer_.grow_buffer(spec_.width_); if (spec_.align_ == ALIGN_RIGHT) { - std::uninitialized_fill_n(out, spec_.width_ - CHAR_WIDTH, fill); - out += spec_.width_ - CHAR_WIDTH; + std::uninitialized_fill_n(out, spec_.width_ - CHAR_SIZE, fill); + out += spec_.width_ - CHAR_SIZE; } else if (spec_.align_ == ALIGN_CENTER) { out = writer_.fill_padding(out, spec_.width_, - internal::const_check(CHAR_WIDTH), fill); + internal::const_check(CHAR_SIZE), fill); } else { - std::uninitialized_fill_n(out + CHAR_WIDTH, - spec_.width_ - CHAR_WIDTH, fill); + std::uninitialized_fill_n(out + CHAR_SIZE, + spec_.width_ - CHAR_SIZE, fill); } } else { - out = writer_.grow_buffer(CHAR_WIDTH); + out = writer_.grow_buffer(CHAR_SIZE); } *out = internal::CharTraits::cast(value); } @@ -2342,6 +2320,27 @@ class FormatterBase w << BasicStringRef(start, internal::to_unsigned(end - start)); } }; + +// A printf formatter. +template +class PrintfFormatter : private FormatterBase +{ +private: + void parse_flags(FormatSpec &spec, const Char *&s); + + // Returns the argument with specified index or, if arg_index is equal + // to the maximum unsigned value, the next argument. + Arg get_arg(const Char *s, + unsigned arg_index = (std::numeric_limits::max)()); + + // Parses argument index, flags and width and returns the argument index. + unsigned parse_header(const Char *&s, FormatSpec &spec); + +public: + explicit PrintfFormatter(const ArgList &args) : FormatterBase(args) {} + FMT_API void format(BasicWriter &writer, + BasicCStringRef format_str); +}; } // namespace internal /** @@ -2382,7 +2381,7 @@ class BasicArgFormatter : public internal::ArgFormatterBase : internal::ArgFormatterBase(formatter.writer(), spec), formatter_(formatter), format_(fmt) {} - /** Formats an argument of a custom (user-defined) type. */ + /** Formats argument of a custom (user-defined) type. */ void visit_custom(internal::Arg::CustomValue c) { c.format(&formatter_, c.value, &format_); @@ -2665,10 +2664,17 @@ class SystemError : public internal::RuntimeError public: /** \rst - Constructs a :class:`fmt::SystemError` object with a description - formatted with `fmt::format_system_error`. *message* and additional - arguments passed into the constructor are formatted similarly to - `fmt::format`. + Constructs a :class:`fmt::SystemError` object with the description + of the form + + .. parsed-literal:: + **: ** + + where ** is the formatted message and ** is + the system message corresponding to the error code. + *error_code* is a system error code as given by ``errno``. + If *error_code* is not a valid error code such as -1, the system message + may look like "Unknown error -1" and is platform-dependent. **Example**:: @@ -2695,25 +2701,6 @@ class SystemError : public internal::RuntimeError } }; -/** - \rst - Formats an error returned by an operating system or a language runtime, - for example a file opening error, and writes it to *out* in the following - form: - - .. parsed-literal:: - **: ** - - where ** is the passed message and ** is - the system message corresponding to the error code. - *error_code* is a system error code as given by ``errno``. - If *error_code* is not a valid error code such as -1, the system message - may look like "Unknown error -1" and is platform-dependent. - \endrst - */ -FMT_API void format_system_error(fmt::Writer &out, int error_code, - fmt::StringRef message) FMT_NOEXCEPT; - /** \rst This template provides operations for formatting and writing data into @@ -2848,8 +2835,7 @@ class BasicWriter template friend class internal::ArgFormatterBase; - template - friend class BasicPrintfArgFormatter; + friend class internal::PrintfArgFormatter; protected: /** @@ -3733,6 +3719,60 @@ FMT_API void print(std::FILE *f, CStringRef format_str, ArgList args); */ FMT_API void print(CStringRef format_str, ArgList args); +template +void printf(BasicWriter &w, BasicCStringRef format, ArgList args) +{ + internal::PrintfFormatter(args).format(w, format); +} + +/** + \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(CStringRef format, ArgList args) +{ + MemoryWriter w; + printf(w, format, args); + return w.str(); +} + +inline std::wstring sprintf(WCStringRef format, ArgList args) +{ + WMemoryWriter 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 + */ +FMT_API int fprintf(std::FILE *f, CStringRef format, ArgList args); + +/** + \rst + Prints formatted data to ``stdout``. + + **Example**:: + + fmt::printf("Elapsed time: %.2f seconds", 1.23); + \endrst + */ +inline int printf(CStringRef format, ArgList args) +{ + return fprintf(stdout, format, args); +} + /** Fast integer formatter. */ @@ -3912,6 +3952,7 @@ void arg(WStringRef, const internal::NamedArg&) FMT_DELETED_OR_UNDEFINED; #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, ...) \ @@ -4026,7 +4067,12 @@ FMT_VARIADIC(std::string, format, CStringRef) FMT_VARIADIC_W(std::wstring, format, WCStringRef) FMT_VARIADIC(void, print, CStringRef) FMT_VARIADIC(void, print, std::FILE *, CStringRef) + FMT_VARIADIC(void, print_colored, Color, CStringRef) +FMT_VARIADIC(std::string, sprintf, CStringRef) +FMT_VARIADIC_W(std::wstring, sprintf, WCStringRef) +FMT_VARIADIC(int, printf, CStringRef) +FMT_VARIADIC(int, fprintf, std::FILE *, CStringRef) namespace internal { diff --git a/include/spdlog/fmt/bundled/ostream.cc b/include/spdlog/fmt/bundled/ostream.cc index 4d4a9a4d1..bcb67fe15 100644 --- a/include/spdlog/fmt/bundled/ostream.cc +++ b/include/spdlog/fmt/bundled/ostream.cc @@ -7,13 +7,13 @@ For the license information refer to format.h. */ -// Commented out by spdlog to use header only -// #include "fmt/ostream.h" +#include "ostream.h" namespace fmt { -namespace internal { -FMT_FUNC void write(std::ostream &os, Writer &w) { +namespace { +// Write the content of w to os. +void write(std::ostream &os, Writer &w) { const char *data = w.data(); typedef internal::MakeUnsigned::Type UnsignedStreamSize; UnsignedStreamSize size = w.size(); @@ -31,6 +31,13 @@ FMT_FUNC void write(std::ostream &os, Writer &w) { FMT_FUNC void print(std::ostream &os, CStringRef format_str, ArgList args) { MemoryWriter w; w.write(format_str, args); - internal::write(os, w); + write(os, w); +} + +FMT_FUNC int fprintf(std::ostream &os, CStringRef format, ArgList args) { + MemoryWriter w; + printf(w, format, args); + write(os, w); + return static_cast(w.size()); } } // namespace fmt diff --git a/include/spdlog/fmt/bundled/ostream.h b/include/spdlog/fmt/bundled/ostream.h index 37e08f3b2..c52646d0b 100644 --- a/include/spdlog/fmt/bundled/ostream.h +++ b/include/spdlog/fmt/bundled/ostream.h @@ -10,8 +10,7 @@ #ifndef FMT_OSTREAM_H_ #define FMT_OSTREAM_H_ -// Commented out by spdlog to use header only -// #include "fmt/format.h" +#include "format.h" #include namespace fmt @@ -77,15 +76,12 @@ struct ConvertToIntImpl value = sizeof(convert(get() << get())) == sizeof(No) }; }; - -// Write the content of w to os. -void write(std::ostream &os, Writer &w); } // namespace internal // Formats a value. template -void format_arg(BasicFormatter &f, - const Char *&format_str, const T &value) +void format(BasicFormatter &f, + const Char *&format_str, const T &value) { internal::MemoryBuffer buffer; @@ -109,6 +105,18 @@ void format_arg(BasicFormatter &f, */ FMT_API void print(std::ostream &os, CStringRef format_str, ArgList args); FMT_VARIADIC(void, print, std::ostream &, CStringRef) + +/** + \rst + Prints formatted data to the stream *os*. + + **Example**:: + + fprintf(cerr, "Don't %s!", "panic"); + \endrst + */ +FMT_API int fprintf(std::ostream &os, CStringRef format_str, ArgList args); +FMT_VARIADIC(int, fprintf, std::ostream &, CStringRef) } // namespace fmt #ifdef FMT_HEADER_ONLY diff --git a/include/spdlog/fmt/bundled/posix.cc b/include/spdlog/fmt/bundled/posix.cc new file mode 100644 index 000000000..76eb7f05e --- /dev/null +++ b/include/spdlog/fmt/bundled/posix.cc @@ -0,0 +1,238 @@ +/* + A C++ interface to POSIX functions. + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +// Disable bogus MSVC warnings. +#ifndef _CRT_SECURE_NO_WARNINGS +# define _CRT_SECURE_NO_WARNINGS +#endif + +#include "posix.h" + +#include +#include +#include + +#ifndef _WIN32 +# include +#else +# include +# include + +# define O_CREAT _O_CREAT +# define O_TRUNC _O_TRUNC + +# ifndef S_IRUSR +# define S_IRUSR _S_IREAD +# endif + +# ifndef S_IWUSR +# define S_IWUSR _S_IWRITE +# endif + +# ifdef __MINGW32__ +# define _SH_DENYNO 0x40 +# endif + +#endif // _WIN32 + +#ifdef fileno +# undef fileno +#endif + +namespace { +#ifdef _WIN32 +// Return type of read and write functions. +typedef int RWResult; + +// On Windows the count argument to read and write is unsigned, so convert +// it from size_t preventing integer overflow. +inline unsigned convert_rwcount(std::size_t count) { + return count <= UINT_MAX ? static_cast(count) : UINT_MAX; +} +#else +// Return type of read and write functions. +typedef ssize_t RWResult; + +inline std::size_t convert_rwcount(std::size_t count) { return count; } +#endif +} + +fmt::BufferedFile::~BufferedFile() FMT_NOEXCEPT { + if (file_ && FMT_SYSTEM(fclose(file_)) != 0) + fmt::report_system_error(errno, "cannot close file"); +} + +fmt::BufferedFile::BufferedFile( + fmt::CStringRef filename, fmt::CStringRef mode) { + FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())), 0); + if (!file_) + FMT_THROW(SystemError(errno, "cannot open file {}", filename)); +} + +void fmt::BufferedFile::close() { + if (!file_) + return; + int result = FMT_SYSTEM(fclose(file_)); + file_ = 0; + if (result != 0) + FMT_THROW(SystemError(errno, "cannot close file")); +} + +// A macro used to prevent expansion of fileno on broken versions of MinGW. +#define FMT_ARGS + +int fmt::BufferedFile::fileno() const { + int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_)); + if (fd == -1) + FMT_THROW(SystemError(errno, "cannot get file descriptor")); + return fd; +} + +fmt::File::File(fmt::CStringRef path, int oflag) { + int mode = S_IRUSR | S_IWUSR; +#if defined(_WIN32) && !defined(__MINGW32__) + fd_ = -1; + FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode)); +#else + FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode))); +#endif + if (fd_ == -1) + FMT_THROW(SystemError(errno, "cannot open file {}", path)); +} + +fmt::File::~File() FMT_NOEXCEPT { + // Don't retry close in case of EINTR! + // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html + if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0) + fmt::report_system_error(errno, "cannot close file"); +} + +void fmt::File::close() { + if (fd_ == -1) + return; + // Don't retry close in case of EINTR! + // See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html + int result = FMT_POSIX_CALL(close(fd_)); + fd_ = -1; + if (result != 0) + FMT_THROW(SystemError(errno, "cannot close file")); +} + +fmt::LongLong fmt::File::size() const { +#ifdef _WIN32 + // Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT + // is less than 0x0500 as is the case with some default MinGW builds. + // Both functions support large file sizes. + DWORD size_upper = 0; + HANDLE handle = reinterpret_cast(_get_osfhandle(fd_)); + DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper)); + if (size_lower == INVALID_FILE_SIZE) { + DWORD error = GetLastError(); + if (error != NO_ERROR) + FMT_THROW(WindowsError(GetLastError(), "cannot get file size")); + } + fmt::ULongLong long_size = size_upper; + return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower; +#else + typedef struct stat Stat; + Stat file_stat = Stat(); + if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1) + FMT_THROW(SystemError(errno, "cannot get file attributes")); + FMT_STATIC_ASSERT(sizeof(fmt::LongLong) >= sizeof(file_stat.st_size), + "return type of File::size is not large enough"); + return file_stat.st_size; +#endif +} + +std::size_t fmt::File::read(void *buffer, std::size_t count) { + RWResult result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count)))); + if (result < 0) + FMT_THROW(SystemError(errno, "cannot read from file")); + return internal::to_unsigned(result); +} + +std::size_t fmt::File::write(const void *buffer, std::size_t count) { + RWResult result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count)))); + if (result < 0) + FMT_THROW(SystemError(errno, "cannot write to file")); + return internal::to_unsigned(result); +} + +fmt::File fmt::File::dup(int fd) { + // Don't retry as dup doesn't return EINTR. + // http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html + int new_fd = FMT_POSIX_CALL(dup(fd)); + if (new_fd == -1) + FMT_THROW(SystemError(errno, "cannot duplicate file descriptor {}", fd)); + return File(new_fd); +} + +void fmt::File::dup2(int fd) { + int result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); + if (result == -1) { + FMT_THROW(SystemError(errno, + "cannot duplicate file descriptor {} to {}", fd_, fd)); + } +} + +void fmt::File::dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT { + int result = 0; + FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd))); + if (result == -1) + ec = ErrorCode(errno); +} + +void fmt::File::pipe(File &read_end, File &write_end) { + // Close the descriptors first to make sure that assignments don't throw + // and there are no leaks. + read_end.close(); + write_end.close(); + int fds[2] = {}; +#ifdef _WIN32 + // Make the default pipe capacity same as on Linux 2.6.11+. + enum { DEFAULT_CAPACITY = 65536 }; + int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY)); +#else + // Don't retry as the pipe function doesn't return EINTR. + // http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html + int result = FMT_POSIX_CALL(pipe(fds)); +#endif + if (result != 0) + FMT_THROW(SystemError(errno, "cannot create pipe")); + // The following assignments don't throw because read_fd and write_fd + // are closed. + read_end = File(fds[0]); + write_end = File(fds[1]); +} + +fmt::BufferedFile fmt::File::fdopen(const char *mode) { + // Don't retry as fdopen doesn't return EINTR. + FILE *f = FMT_POSIX_CALL(fdopen(fd_, mode)); + if (!f) + FMT_THROW(SystemError(errno, "cannot associate stream with file descriptor")); + BufferedFile file(f); + fd_ = -1; + return file; +} + +long fmt::getpagesize() { +#ifdef _WIN32 + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwPageSize; +#else + long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE)); + if (size < 0) + FMT_THROW(SystemError(errno, "cannot get memory page size")); + return size; +#endif +} diff --git a/include/spdlog/fmt/bundled/posix.h b/include/spdlog/fmt/bundled/posix.h new file mode 100644 index 000000000..859fcaa75 --- /dev/null +++ b/include/spdlog/fmt/bundled/posix.h @@ -0,0 +1,443 @@ +/* + A C++ interface to POSIX functions. + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +#ifndef FMT_POSIX_H_ +#define FMT_POSIX_H_ + +#if defined(__MINGW32__) || defined(__CYGWIN__) +// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/. +# undef __STRICT_ANSI__ +#endif + +#include +#include // for O_RDONLY +#include // for locale_t +#include +#include // for strtod_l + +#include + +#if defined __APPLE__ || defined(__FreeBSD__) +# include // for LC_NUMERIC_MASK on OS X +#endif + +#include "format.h" + +#ifndef FMT_POSIX +# if defined(_WIN32) && !defined(__MINGW32__) +// Fix warnings about deprecated symbols. +# define FMT_POSIX(call) _##call +# else +# define FMT_POSIX(call) call +# endif +#endif + +// Calls to system functions are wrapped in FMT_SYSTEM for testability. +#ifdef FMT_SYSTEM +# define FMT_POSIX_CALL(call) FMT_SYSTEM(call) +#else +# define FMT_SYSTEM(call) call +# ifdef _WIN32 +// Fix warnings about deprecated symbols. +# define FMT_POSIX_CALL(call) ::_##call +# else +# define FMT_POSIX_CALL(call) ::call +# endif +#endif + +#if FMT_GCC_VERSION >= 407 +# define FMT_UNUSED __attribute__((unused)) +#else +# define FMT_UNUSED +#endif + +#ifndef FMT_USE_STATIC_ASSERT +# define FMT_USE_STATIC_ASSERT 0 +#endif + +#if FMT_USE_STATIC_ASSERT || FMT_HAS_FEATURE(cxx_static_assert) || \ + (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || _MSC_VER >= 1600 +# define FMT_STATIC_ASSERT(cond, message) static_assert(cond, message) +#else +# define FMT_CONCAT_(a, b) FMT_CONCAT(a, b) +# define FMT_STATIC_ASSERT(cond, message) \ + typedef int FMT_CONCAT_(Assert, __LINE__)[(cond) ? 1 : -1] FMT_UNUSED +#endif + +// Retries the expression while it evaluates to error_result and errno +// equals to EINTR. +#ifndef _WIN32 +# define FMT_RETRY_VAL(result, expression, error_result) \ + do { \ + result = (expression); \ + } while (result == error_result && errno == EINTR) +#else +# define FMT_RETRY_VAL(result, expression, error_result) result = (expression) +#endif + +#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) + +namespace fmt +{ + +// An error code. +class ErrorCode +{ +private: + int value_; + +public: +explicit ErrorCode(int value = 0) FMT_NOEXCEPT : + value_(value) {} + + int get() const FMT_NOEXCEPT + { + return value_; + } +}; + +// A buffered file. +class BufferedFile +{ +private: + FILE *file_; + + friend class File; + + explicit BufferedFile(FILE *f) : file_(f) {} + +public: + // Constructs a BufferedFile object which doesn't represent any file. +BufferedFile() FMT_NOEXCEPT : + file_(0) {} + + // Destroys the object closing the file it represents if any. + ~BufferedFile() FMT_NOEXCEPT; + +#if !FMT_USE_RVALUE_REFERENCES + // Emulate a move constructor and a move assignment operator if rvalue + // references are not supported. + +private: + // A proxy object to emulate a move constructor. + // It is private to make it impossible call operator Proxy directly. + struct Proxy + { + FILE *file; + }; + +public: + // A "move constructor" for moving from a temporary. +BufferedFile(Proxy p) FMT_NOEXCEPT : + file_(p.file) {} + + // A "move constructor" for moving from an lvalue. +BufferedFile(BufferedFile &f) FMT_NOEXCEPT : + file_(f.file_) + { + f.file_ = 0; + } + + // A "move assignment operator" for moving from a temporary. + BufferedFile &operator=(Proxy p) + { + close(); + file_ = p.file; + return *this; + } + + // A "move assignment operator" for moving from an lvalue. + BufferedFile &operator=(BufferedFile &other) + { + close(); + file_ = other.file_; + other.file_ = 0; + return *this; + } + + // Returns a proxy object for moving from a temporary: + // BufferedFile file = BufferedFile(...); + operator Proxy() FMT_NOEXCEPT + { + Proxy p = {file_}; + file_ = 0; + return p; + } + +#else +private: + FMT_DISALLOW_COPY_AND_ASSIGN(BufferedFile); + +public: +BufferedFile(BufferedFile &&other) FMT_NOEXCEPT : + file_(other.file_) + { + other.file_ = 0; + } + + BufferedFile& operator=(BufferedFile &&other) + { + close(); + file_ = other.file_; + other.file_ = 0; + return *this; + } +#endif + + // Opens a file. + BufferedFile(CStringRef filename, CStringRef mode); + + // Closes the file. + void close(); + + // Returns the pointer to a FILE object representing this file. + FILE *get() const FMT_NOEXCEPT + { + return file_; + } + + // We place parentheses around fileno to workaround a bug in some versions + // of MinGW that define fileno as a macro. + int (fileno)() const; + + void print(CStringRef format_str, const ArgList &args) + { + fmt::print(file_, format_str, args); + } + FMT_VARIADIC(void, print, CStringRef) +}; + +// A file. Closed file is represented by a File object with descriptor -1. +// Methods that are not declared with FMT_NOEXCEPT may throw +// fmt::SystemError in case of failure. Note that some errors such as +// closing the file multiple times will cause a crash on Windows rather +// than an exception. You can get standard behavior by overriding the +// invalid parameter handler with _set_invalid_parameter_handler. +class File +{ +private: + int fd_; // File descriptor. + + // Constructs a File object with a given descriptor. + explicit File(int fd) : fd_(fd) {} + +public: + // Possible values for the oflag argument to the constructor. + enum + { + RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. + WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. + RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. + }; + + // Constructs a File object which doesn't represent any file. +File() FMT_NOEXCEPT : + fd_(-1) {} + + // Opens a file and constructs a File object representing this file. + File(CStringRef path, int oflag); + +#if !FMT_USE_RVALUE_REFERENCES + // Emulate a move constructor and a move assignment operator if rvalue + // references are not supported. + +private: + // A proxy object to emulate a move constructor. + // It is private to make it impossible call operator Proxy directly. + struct Proxy + { + int fd; + }; + +public: + // A "move constructor" for moving from a temporary. +File(Proxy p) FMT_NOEXCEPT : + fd_(p.fd) {} + + // A "move constructor" for moving from an lvalue. +File(File &other) FMT_NOEXCEPT : + fd_(other.fd_) + { + other.fd_ = -1; + } + + // A "move assignment operator" for moving from a temporary. + File &operator=(Proxy p) + { + close(); + fd_ = p.fd; + return *this; + } + + // A "move assignment operator" for moving from an lvalue. + File &operator=(File &other) + { + close(); + fd_ = other.fd_; + other.fd_ = -1; + return *this; + } + + // Returns a proxy object for moving from a temporary: + // File file = File(...); + operator Proxy() FMT_NOEXCEPT + { + Proxy p = {fd_}; + fd_ = -1; + return p; + } + +#else +private: + FMT_DISALLOW_COPY_AND_ASSIGN(File); + +public: +File(File &&other) FMT_NOEXCEPT : + fd_(other.fd_) + { + other.fd_ = -1; + } + + File& operator=(File &&other) + { + close(); + fd_ = other.fd_; + other.fd_ = -1; + return *this; + } +#endif + + // Destroys the object closing the file it represents if any. + ~File() FMT_NOEXCEPT; + + // Returns the file descriptor. + int descriptor() const FMT_NOEXCEPT + { + return fd_; + } + + // Closes the file. + void close(); + + // Returns the file size. The size has signed type for consistency with + // stat::st_size. + LongLong size() const; + + // Attempts to read count bytes from the file into the specified buffer. + std::size_t read(void *buffer, std::size_t count); + + // Attempts to write count bytes from the specified buffer to the file. + std::size_t write(const void *buffer, std::size_t count); + + // Duplicates a file descriptor with the dup function and returns + // the duplicate as a file object. + static File dup(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + void dup2(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + void dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT; + + // Creates a pipe setting up read_end and write_end file objects for reading + // and writing respectively. + static void pipe(File &read_end, File &write_end); + + // Creates a BufferedFile object associated with this file and detaches + // this File object from the file. + BufferedFile fdopen(const char *mode); +}; + +// Returns the memory page size. +long getpagesize(); + +#if (defined(LC_NUMERIC_MASK) || defined(_MSC_VER)) && \ + !defined(__ANDROID__) && !defined(__CYGWIN__) +# define FMT_LOCALE +#endif + +#ifdef FMT_LOCALE +// A "C" numeric locale. +class Locale +{ +private: +# ifdef _MSC_VER + typedef _locale_t locale_t; + + enum { LC_NUMERIC_MASK = LC_NUMERIC }; + + static locale_t newlocale(int category_mask, const char *locale, locale_t) + { + return _create_locale(category_mask, locale); + } + + static void freelocale(locale_t locale) + { + _free_locale(locale); + } + + static double strtod_l(const char *nptr, char **endptr, _locale_t locale) + { + return _strtod_l(nptr, endptr, locale); + } +# endif + + locale_t locale_; + + FMT_DISALLOW_COPY_AND_ASSIGN(Locale); + +public: + typedef locale_t Type; + + Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", NULL)) + { + if (!locale_) + FMT_THROW(fmt::SystemError(errno, "cannot create locale")); + } + ~Locale() + { + freelocale(locale_); + } + + Type get() const + { + return locale_; + } + + // Converts string to floating-point number and advances str past the end + // of the parsed input. + double strtod(const char *&str) const + { + char *end = 0; + double result = strtod_l(str, &end, locale_); + str = end; + return result; + } +}; +#endif // FMT_LOCALE +} // namespace fmt + +#if !FMT_USE_RVALUE_REFERENCES +namespace std +{ +// For compatibility with C++98. +inline fmt::BufferedFile &move(fmt::BufferedFile &f) +{ + return f; +} +inline fmt::File &move(fmt::File &f) +{ + return f; +} +} +#endif + +#endif // FMT_POSIX_H_ diff --git a/include/spdlog/fmt/bundled/printf.h b/include/spdlog/fmt/bundled/printf.h deleted file mode 100644 index 77e68403f..000000000 --- a/include/spdlog/fmt/bundled/printf.h +++ /dev/null @@ -1,642 +0,0 @@ -/* - Formatting library for C++ - - Copyright (c) 2012 - 2016, Victor Zverovich - All rights reserved. - - For the license information refer to format.h. - */ - -#ifndef FMT_PRINTF_H_ -#define FMT_PRINTF_H_ - -#include // std::fill_n -#include // std::numeric_limits - -#include "fmt/ostream.h" - -namespace fmt -{ -namespace internal -{ - -// Checks if a value fits in int - used to avoid warnings about comparing -// signed and unsigned integers. -template -struct IntChecker -{ - template - static bool fits_in_int(T value) - { - unsigned max = std::numeric_limits::max(); - return value <= max; - } - static bool fits_in_int(bool) - { - return true; - } -}; - -template <> -struct IntChecker -{ - template - static bool fits_in_int(T value) - { - return value >= std::numeric_limits::min() && - value <= std::numeric_limits::max(); - } - static bool fits_in_int(int) - { - return true; - } -}; - -class PrecisionHandler : public ArgVisitor -{ -public: - void report_unhandled_arg() - { - FMT_THROW(FormatError("precision is not integer")); - } - - template - int visit_any_int(T value) - { - if (!IntChecker::is_signed>::fits_in_int(value)) - FMT_THROW(FormatError("number is too big")); - return static_cast(value); - } -}; - -// IsZeroInt::visit(arg) returns true iff arg is a zero integer. -class IsZeroInt : public ArgVisitor -{ -public: - template - bool visit_any_int(T value) - { - return value == 0; - } -}; - -template -struct is_same -{ - enum { value = 0 }; -}; - -template -struct is_same -{ - enum { value = 1 }; -}; - -// An argument visitor that converts an integer argument to T for printf, -// if T is an integral type. If T is void, the argument is converted to -// corresponding signed or unsigned type depending on the type specifier: -// 'd' and 'i' - signed, other - unsigned) -template -class ArgConverter : public ArgVisitor, void> -{ -private: - internal::Arg &arg_; - wchar_t type_; - - FMT_DISALLOW_COPY_AND_ASSIGN(ArgConverter); - -public: - ArgConverter(internal::Arg &arg, wchar_t type) - : arg_(arg), type_(type) {} - - void visit_bool(bool value) - { - if (type_ != 's') - visit_any_int(value); - } - - template - void visit_any_int(U value) - { - bool is_signed = type_ == 'd' || type_ == 'i'; - using internal::Arg; - typedef typename internal::Conditional< - is_same::value, U, T>::type TargetType; - if (sizeof(TargetType) <= sizeof(int)) - { - // Extra casts are used to silence warnings. - if (is_signed) - { - arg_.type = Arg::INT; - arg_.int_value = static_cast(static_cast(value)); - } - else - { - arg_.type = Arg::UINT; - typedef typename internal::MakeUnsigned::Type Unsigned; - arg_.uint_value = static_cast(static_cast(value)); - } - } - else - { - if (is_signed) - { - arg_.type = Arg::LONG_LONG; - // glibc's printf doesn't sign extend arguments of smaller types: - // std::printf("%lld", -42); // prints "4294967254" - // but we don't have to do the same because it's a UB. - arg_.long_long_value = static_cast(value); - } - else - { - arg_.type = Arg::ULONG_LONG; - arg_.ulong_long_value = - static_cast::Type>(value); - } - } - } -}; - -// Converts an integer argument to char for printf. -class CharConverter : public ArgVisitor -{ -private: - internal::Arg &arg_; - - FMT_DISALLOW_COPY_AND_ASSIGN(CharConverter); - -public: - explicit CharConverter(internal::Arg &arg) : arg_(arg) {} - - template - void visit_any_int(T value) - { - arg_.type = internal::Arg::CHAR; - arg_.int_value = static_cast(value); - } -}; - -// Checks if an argument is a valid printf width specifier and sets -// left alignment if it is negative. -class WidthHandler : public ArgVisitor -{ -private: - FormatSpec &spec_; - - FMT_DISALLOW_COPY_AND_ASSIGN(WidthHandler); - -public: - explicit WidthHandler(FormatSpec &spec) : spec_(spec) {} - - void report_unhandled_arg() - { - FMT_THROW(FormatError("width is not integer")); - } - - template - unsigned visit_any_int(T value) - { - typedef typename internal::IntTraits::MainType UnsignedType; - UnsignedType width = static_cast(value); - if (internal::is_negative(value)) - { - spec_.align_ = ALIGN_LEFT; - width = 0 - width; - } - unsigned int_max = std::numeric_limits::max(); - if (width > int_max) - FMT_THROW(FormatError("number is too big")); - return static_cast(width); - } -}; -} // namespace internal - -/** - \rst - A ``printf`` argument formatter based on the `curiously recurring template - pattern `_. - - To use `~fmt::BasicPrintfArgFormatter` define a subclass that implements some - or all of the visit methods with the same signatures as the methods in - `~fmt::ArgVisitor`, for example, `~fmt::ArgVisitor::visit_int()`. - Pass the subclass as the *Impl* template parameter. When a formatting - function processes an argument, it will dispatch to a visit method - specific to the argument type. For example, if the argument type is - ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass - will be called. If the subclass doesn't contain a method with this signature, - then a corresponding method of `~fmt::BasicPrintfArgFormatter` or its - superclass will be called. - \endrst - */ -template -class BasicPrintfArgFormatter : public internal::ArgFormatterBase -{ -private: - void write_null_pointer() - { - this->spec().type_ = 0; - this->write("(nil)"); - } - - typedef internal::ArgFormatterBase Base; - -public: - /** - \rst - Constructs an argument formatter object. - *writer* is a reference to the output writer and *spec* contains format - specifier information for standard argument types. - \endrst - */ - BasicPrintfArgFormatter(BasicWriter &writer, FormatSpec &spec) - : internal::ArgFormatterBase(writer, spec) {} - - /** Formats an argument of type ``bool``. */ - void visit_bool(bool value) - { - FormatSpec &fmt_spec = this->spec(); - if (fmt_spec.type_ != 's') - return this->visit_any_int(value); - fmt_spec.type_ = 0; - this->write(value); - } - - /** Formats a character. */ - void visit_char(int value) - { - const FormatSpec &fmt_spec = this->spec(); - BasicWriter &w = this->writer(); - if (fmt_spec.type_ && fmt_spec.type_ != 'c') - w.write_int(value, fmt_spec); - typedef typename BasicWriter::CharPtr CharPtr; - CharPtr out = CharPtr(); - if (fmt_spec.width_ > 1) - { - Char fill = ' '; - out = w.grow_buffer(fmt_spec.width_); - if (fmt_spec.align_ != ALIGN_LEFT) - { - std::fill_n(out, fmt_spec.width_ - 1, fill); - out += fmt_spec.width_ - 1; - } - else - { - std::fill_n(out + 1, fmt_spec.width_ - 1, fill); - } - } - else - { - out = w.grow_buffer(1); - } - *out = static_cast(value); - } - - /** Formats a null-terminated C string. */ - void visit_cstring(const char *value) - { - if (value) - Base::visit_cstring(value); - else if (this->spec().type_ == 'p') - write_null_pointer(); - else - this->write("(null)"); - } - - /** Formats a pointer. */ - void visit_pointer(const void *value) - { - if (value) - return Base::visit_pointer(value); - this->spec().type_ = 0; - write_null_pointer(); - } - - /** Formats an argument of a custom (user-defined) type. */ - void visit_custom(internal::Arg::CustomValue c) - { - BasicFormatter formatter(ArgList(), this->writer()); - const Char format_str[] = {'}', 0}; - const Char *format = format_str; - c.format(&formatter, c.value, &format); - } -}; - -/** The default printf argument formatter. */ -template -class PrintfArgFormatter - : public BasicPrintfArgFormatter, Char> -{ -public: - /** Constructs an argument formatter object. */ - PrintfArgFormatter(BasicWriter &w, FormatSpec &s) - : BasicPrintfArgFormatter, Char>(w, s) {} -}; - -/** This template formats data and writes the output to a writer. */ -template > -class PrintfFormatter : private internal::FormatterBase -{ -private: - BasicWriter &writer_; - - 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. - internal::Arg get_arg( - const Char *s, - unsigned arg_index = (std::numeric_limits::max)()); - - // Parses argument index, flags and width and returns the argument index. - unsigned parse_header(const Char *&s, FormatSpec &spec); - -public: - /** - \rst - Constructs a ``PrintfFormatter`` object. References to the arguments and - the writer are stored in the formatter object so make sure they have - appropriate lifetimes. - \endrst - */ - explicit PrintfFormatter(const ArgList &args, BasicWriter &w) - : FormatterBase(args), writer_(w) {} - - /** Formats stored arguments and writes the output to the writer. */ - FMT_API void format(BasicCStringRef format_str); -}; - -template -void PrintfFormatter::parse_flags(FormatSpec &spec, const Char *&s) -{ - for (;;) - { - switch (*s++) - { - case '-': - spec.align_ = ALIGN_LEFT; - break; - case '+': - spec.flags_ |= SIGN_FLAG | PLUS_FLAG; - break; - case '0': - spec.fill_ = '0'; - break; - case ' ': - spec.flags_ |= SIGN_FLAG; - break; - case '#': - spec.flags_ |= HASH_FLAG; - break; - default: - --s; - return; - } - } -} - -template -internal::Arg PrintfFormatter::get_arg(const Char *s, - unsigned arg_index) -{ - (void)s; - const char *error = 0; - internal::Arg arg = arg_index == std::numeric_limits::max() ? - next_arg(error) : FormatterBase::get_arg(arg_index - 1, error); - if (error) - FMT_THROW(FormatError(!*s ? "invalid format string" : error)); - return arg; -} - -template -unsigned PrintfFormatter::parse_header( - const Char *&s, FormatSpec &spec) -{ - unsigned arg_index = std::numeric_limits::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 = internal::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_ = internal::parse_nonnegative_int(s); - } - else if (*s == '*') - { - ++s; - spec.width_ = internal::WidthHandler(spec).visit(get_arg(s)); - } - return arg_index; -} - -template -void PrintfFormatter::format(BasicCStringRef format_str) -{ - const Char *start = format_str.c_str(); - 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_ = static_cast(internal::parse_nonnegative_int(s)); - } - else if (*s == '*') - { - ++s; - spec.precision_ = internal::PrecisionHandler().visit(get_arg(s)); - } - } - - using internal::Arg; - Arg arg = get_arg(s, arg_index); - if (spec.flag(HASH_FLAG) && internal::IsZeroInt().visit(arg)) - spec.flags_ &= ~internal::to_unsigned(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. - using internal::ArgConverter; - switch (*s++) - { - case 'h': - if (*s == 'h') - ArgConverter(arg, *++s).visit(arg); - else - ArgConverter(arg, *s).visit(arg); - break; - case 'l': - if (*s == 'l') - ArgConverter(arg, *++s).visit(arg); - else - ArgConverter(arg, *s).visit(arg); - break; - case 'j': - ArgConverter(arg, *s).visit(arg); - break; - case 'z': - ArgConverter(arg, *s).visit(arg); - break; - case 't': - ArgConverter(arg, *s).visit(arg); - break; - case 'L': - // printf produces garbage when 'L' is omitted for long double, no - // need to do the same. - break; - default: - --s; - ArgConverter(arg, *s).visit(arg); - } - - // Parse type. - if (!*s) - FMT_THROW(FormatError("invalid format string")); - spec.type_ = static_cast(*s++); - if (arg.type <= Arg::LAST_INTEGER_TYPE) - { - // Normalize type. - switch (spec.type_) - { - case 'i': - case 'u': - spec.type_ = 'd'; - break; - case 'c': - // TODO: handle wchar_t - internal::CharConverter(arg).visit(arg); - break; - } - } - - start = s; - - // Format argument. - AF(writer_, spec).visit(arg); - } - write(writer_, start, s); -} - -template -void printf(BasicWriter &w, BasicCStringRef format, ArgList args) -{ - PrintfFormatter(args, w).format(format); -} - -/** - \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(CStringRef format, ArgList args) -{ - MemoryWriter w; - printf(w, format, args); - return w.str(); -} -FMT_VARIADIC(std::string, sprintf, CStringRef) - -inline std::wstring sprintf(WCStringRef format, ArgList args) -{ - WMemoryWriter w; - printf(w, format, args); - return w.str(); -} -FMT_VARIADIC_W(std::wstring, sprintf, WCStringRef) - -/** - \rst - Prints formatted data to the file *f*. - - **Example**:: - - fmt::fprintf(stderr, "Don't %s!", "panic"); - \endrst - */ -FMT_API int fprintf(std::FILE *f, CStringRef format, ArgList args); -FMT_VARIADIC(int, fprintf, std::FILE *, CStringRef) - -/** - \rst - Prints formatted data to ``stdout``. - - **Example**:: - - fmt::printf("Elapsed time: %.2f seconds", 1.23); - \endrst - */ -inline int printf(CStringRef format, ArgList args) -{ - return fprintf(stdout, format, args); -} -FMT_VARIADIC(int, printf, CStringRef) - -/** - \rst - Prints formatted data to the stream *os*. - - **Example**:: - - fprintf(cerr, "Don't %s!", "panic"); - \endrst - */ -inline int fprintf(std::ostream &os, CStringRef format_str, ArgList args) -{ - MemoryWriter w; - printf(w, format_str, args); - internal::write(os, w); - return static_cast(w.size()); -} -FMT_VARIADIC(int, fprintf, std::ostream &, CStringRef) -} // namespace fmt - -#endif // FMT_PRINTF_H_ diff --git a/include/spdlog/fmt/bundled/time.h b/include/spdlog/fmt/bundled/time.h new file mode 100644 index 000000000..10c6cfc90 --- /dev/null +++ b/include/spdlog/fmt/bundled/time.h @@ -0,0 +1,58 @@ +/* + Formatting library for C++ - time formatting + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + For the license information refer to format.h. + */ + +#ifndef FMT_TIME_H_ +#define FMT_TIME_H_ + +#include "format.h" +#include + +namespace fmt +{ +template +void format(BasicFormatter &f, + const char *&format_str, const std::tm &tm) +{ + if (*format_str == ':') + ++format_str; + const char *end = format_str; + while (*end && *end != '}') + ++end; + if (*end != '}') + FMT_THROW(FormatError("missing '}' in format string")); + internal::MemoryBuffer format; + format.append(format_str, end + 1); + format[format.size() - 1] = '\0'; + Buffer &buffer = f.writer().buffer(); + std::size_t start = buffer.size(); + for (;;) + { + std::size_t size = buffer.capacity() - start; + std::size_t count = std::strftime(&buffer[start], size, &format[0], &tm); + if (count != 0) + { + buffer.resize(start + count); + break; + } + if (size >= format.size() * 256) + { + // If the buffer is 256 times larger than the format string, assume + // that `strftime` gives an empty result. There doesn't seem to be a + // better way to distinguish the two cases: + // https://github.com/fmtlib/fmt/issues/367 + break; + } + const std::size_t MIN_GROWTH = 10; + buffer.reserve(buffer.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); + } + format_str = end + 1; +} +} + +#endif // FMT_TIME_H_ diff --git a/include/spdlog/logger.h b/include/spdlog/logger.h index e998999ef..58b4841a8 100644 --- a/include/spdlog/logger.h +++ b/include/spdlog/logger.h @@ -5,7 +5,7 @@ #pragma once -// Thread safe logger +// Thread safe logger (except for set_pattern(..), set_formatter(..) and set_error_handler()) // Has name, log level, vector of std::shared sink pointers and formatter // Upon each log write the logger: // 1. Checks if its log level is enough to log the message @@ -37,12 +37,12 @@ class logger template void log(level::level_enum lvl, const char* fmt, const Args&... args); template void log(level::level_enum lvl, const char* msg); - template void trace(const char* fmt, const Args&... args); - template void debug(const char* fmt, const Args&... args); - template void info(const char* fmt, const Args&... args); - template void warn(const char* fmt, const Args&... args); - template void error(const char* fmt, const Args&... args); - template void critical(const char* fmt, const Args&... args); + template void trace(const char* fmt, const Arg1&, const Args&... args); + template void debug(const char* fmt, const Arg1&, const Args&... args); + template void info(const char* fmt, const Arg1&, const Args&... args); + template void warn(const char* fmt, const Arg1&, const Args&... args); + template void error(const char* fmt, const Arg1&, const Args&... args); + template void critical(const char* fmt, const Arg1&, const Args&... args); template void log(level::level_enum lvl, const T&); template void trace(const T&); @@ -59,15 +59,17 @@ class logger void set_pattern(const std::string&); void set_formatter(formatter_ptr); - // error handler - void set_error_handler(log_err_handler); - log_err_handler error_handler(); - // automatically call flush() if message level >= log_level void flush_on(level::level_enum log_level); virtual void flush(); + const std::vector& sinks() const; + + // error handler + virtual void set_error_handler(log_err_handler); + virtual log_err_handler error_handler(); + protected: virtual void _sink_it(details::log_msg&); virtual void _set_pattern(const std::string&); @@ -90,5 +92,3 @@ class logger } #include - - diff --git a/include/spdlog/sinks/ansicolor_sink.h b/include/spdlog/sinks/ansicolor_sink.h index a3b4292dd..96e10148e 100644 --- a/include/spdlog/sinks/ansicolor_sink.h +++ b/include/spdlog/sinks/ansicolor_sink.h @@ -33,7 +33,7 @@ class ansicolor_sink : public sink virtual void log(const details::log_msg& msg) override; virtual void flush() override; - void set_color(level::level_enum level, const std::string& color); + void set_color(level::level_enum color_level, const std::string& color); /// Formatting codes const std::string reset = "\033[00m"; @@ -101,9 +101,9 @@ inline void ansicolor_sink::flush() sink_->flush(); } -inline void ansicolor_sink::set_color(level::level_enum level, const std::string& color) +inline void ansicolor_sink::set_color(level::level_enum color_level, const std::string& color) { - colors_[level] = color; + colors_[color_level] = color; } inline ansicolor_sink::~ansicolor_sink() diff --git a/include/spdlog/sinks/file_sinks.h b/include/spdlog/sinks/file_sinks.h index fad21fe15..721a96df0 100644 --- a/include/spdlog/sinks/file_sinks.h +++ b/include/spdlog/sinks/file_sinks.h @@ -29,7 +29,7 @@ template class simple_file_sink : public base_sink < Mutex > { public: - explicit simple_file_sink(const filename_t &filename, bool truncate = false) + explicit simple_file_sink(const filename_t &filename, bool truncate = false):_force_flush(false) { _file_helper.open(filename, truncate); } @@ -37,14 +37,21 @@ class simple_file_sink : public base_sink < Mutex > { _file_helper.flush(); } + void set_force_flush(bool force_flush) + { + _force_flush = force_flush; + } protected: void _sink_it(const details::log_msg& msg) override { _file_helper.write(msg); + if(_force_flush) + _file_helper.flush(); } private: details::file_helper _file_helper; + bool _force_flush; }; typedef simple_file_sink simple_file_sink_mt; @@ -57,16 +64,15 @@ template class rotating_file_sink : public base_sink < Mutex > { public: - rotating_file_sink(const filename_t &base_filename, const filename_t &extension, - std::size_t max_size, std::size_t max_files ) : + rotating_file_sink(const filename_t &base_filename, + std::size_t max_size, std::size_t max_files) : _base_filename(base_filename), - _extension(extension), _max_size(max_size), _max_files(max_files), _current_size(0), _file_helper() { - _file_helper.open(calc_filename(_base_filename, 0, _extension)); + _file_helper.open(calc_filename(_base_filename, 0)); _current_size = _file_helper.size(); //expensive. called only once } @@ -88,21 +94,21 @@ class rotating_file_sink : public base_sink < Mutex > } private: - static filename_t calc_filename(const filename_t& filename, std::size_t index, const filename_t& extension) + static filename_t calc_filename(const filename_t& filename, std::size_t index) { std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; if (index) - w.write(SPDLOG_FILENAME_T("{}.{}.{}"), filename, index, extension); + w.write(SPDLOG_FILENAME_T("{}.{}"), filename, index); else - w.write(SPDLOG_FILENAME_T("{}.{}"), filename, extension); + w.write(SPDLOG_FILENAME_T("{}"), filename); return w.str(); } // Rotate files: - // log.txt -> log.1.txt - // log.1.txt -> log2.txt - // log.2.txt -> log3.txt - // log.3.txt -> delete + // log.txt -> log.txt.1 + // log.txt.1 -> log.txt.2 + // log.txt.2 -> log.txt.3 + // lo3.txt.3 -> delete void _rotate() { @@ -110,8 +116,8 @@ class rotating_file_sink : public base_sink < Mutex > _file_helper.close(); for (auto i = _max_files; i > 0; --i) { - filename_t src = calc_filename(_base_filename, i - 1, _extension); - filename_t target = calc_filename(_base_filename, i, _extension); + filename_t src = calc_filename(_base_filename, i - 1); + filename_t target = calc_filename(_base_filename, i); if (details::file_helper::file_exists(target)) { @@ -128,7 +134,6 @@ class rotating_file_sink : public base_sink < Mutex > _file_helper.reopen(true); } filename_t _base_filename; - filename_t _extension; std::size_t _max_size; std::size_t _max_files; std::size_t _current_size; @@ -143,27 +148,27 @@ typedef rotating_file_sinkrotating_file_sink_st; */ struct default_daily_file_name_calculator { - // Create filename for the form basename.YYYY-MM-DD_hh-mm.extension - static filename_t calc_filename(const filename_t& basename, const filename_t& extension) + // Create filename for the form basename.YYYY-MM-DD_hh-mm + static filename_t calc_filename(const filename_t& basename) { std::tm tm = spdlog::details::os::localtime(); std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; - w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}_{:02d}-{:02d}.{}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, extension); + w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}_{:02d}-{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min); return w.str(); } }; /* - * Generator of daily log file names in format basename.YYYY-MM-DD.extension + * Generator of daily log file names in format basename.YYYY-MM-DD */ struct dateonly_daily_file_name_calculator { - // Create filename for the form basename.YYYY-MM-DD.extension - static filename_t calc_filename(const filename_t& basename, const filename_t& extension) + // Create filename for the form basename.YYYY-MM-DD + static filename_t calc_filename(const filename_t& basename) { std::tm tm = spdlog::details::os::localtime(); std::conditional::value, fmt::MemoryWriter, fmt::WMemoryWriter>::type w; - w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}.{}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, extension); + w.write(SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}"), basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); return w.str(); } }; @@ -178,17 +183,15 @@ class daily_file_sink :public base_sink < Mutex > //create daily file sink which rotates on given time daily_file_sink( const filename_t& base_filename, - const filename_t& extension, int rotation_hour, int rotation_minute) : _base_filename(base_filename), - _extension(extension), _rotation_h(rotation_hour), - _rotation_m(rotation_minute) + _rotation_m(rotation_minute) { if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59) throw spdlog_ex("daily_file_sink: Invalid rotation time in ctor"); _rotation_tp = _next_rotation_tp(); - _file_helper.open(FileNameCalc::calc_filename(_base_filename, _extension)); + _file_helper.open(FileNameCalc::calc_filename(_base_filename)); } void flush() override @@ -201,7 +204,7 @@ class daily_file_sink :public base_sink < Mutex > { if (std::chrono::system_clock::now() >= _rotation_tp) { - _file_helper.open(FileNameCalc::calc_filename(_base_filename, _extension)); + _file_helper.open(FileNameCalc::calc_filename(_base_filename)); _rotation_tp = _next_rotation_tp(); } _file_helper.write(msg); @@ -224,7 +227,6 @@ class daily_file_sink :public base_sink < Mutex > } filename_t _base_filename; - filename_t _extension; int _rotation_h; int _rotation_m; std::chrono::system_clock::time_point _rotation_tp; diff --git a/include/spdlog/sinks/null_sink.h b/include/spdlog/sinks/null_sink.h index 68bd9c94d..1d427aa2a 100644 --- a/include/spdlog/sinks/null_sink.h +++ b/include/spdlog/sinks/null_sink.h @@ -27,7 +27,7 @@ class null_sink : public base_sink < Mutex > }; typedef null_sink null_sink_st; -typedef null_sink null_sink_mt; +typedef null_sink null_sink_mt; } } diff --git a/include/spdlog/sinks/sink.h b/include/spdlog/sinks/sink.h index d27fdbe0e..b48dd8b9d 100644 --- a/include/spdlog/sinks/sink.h +++ b/include/spdlog/sinks/sink.h @@ -15,7 +15,10 @@ namespace sinks class sink { public: - sink(): _level( level::trace ) {} + sink() + { + _level = level::trace; + } virtual ~sink() {} virtual void log(const details::log_msg& msg) = 0; diff --git a/include/spdlog/sinks/stdout_sinks.h b/include/spdlog/sinks/stdout_sinks.h index 30c19a548..c05f80dd1 100644 --- a/include/spdlog/sinks/stdout_sinks.h +++ b/include/spdlog/sinks/stdout_sinks.h @@ -18,11 +18,12 @@ namespace sinks { template -class stdout_sink : public base_sink +class stdout_sink: public base_sink { using MyType = stdout_sink; public: - stdout_sink() {} + stdout_sink() + {} static std::shared_ptr instance() { static std::shared_ptr instance = std::make_shared(); @@ -46,11 +47,12 @@ typedef stdout_sink stdout_sink_mt; template -class stderr_sink : public base_sink +class stderr_sink: public base_sink { using MyType = stderr_sink; public: - stderr_sink() {} + stderr_sink() + {} static std::shared_ptr instance() { static std::shared_ptr instance = std::make_shared(); diff --git a/include/spdlog/sinks/wincolor_sink.h b/include/spdlog/sinks/wincolor_sink.h new file mode 100644 index 000000000..63ecbe217 --- /dev/null +++ b/include/spdlog/sinks/wincolor_sink.h @@ -0,0 +1,116 @@ +// +// Copyright(c) 2016 spdlog +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +namespace spdlog +{ +namespace sinks +{ +/* + * Windows color console sink. Uses WriteConsoleA to write to the console with colors + */ +template +class wincolor_sink: public base_sink +{ +public: + const WORD BOLD = FOREGROUND_INTENSITY; + const WORD RED = FOREGROUND_RED; + const WORD CYAN = FOREGROUND_GREEN | FOREGROUND_BLUE; + const WORD WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + const WORD YELLOW = FOREGROUND_RED | FOREGROUND_GREEN; + + wincolor_sink(HANDLE std_handle): out_handle_(std_handle) + { + colors_[level::trace] = CYAN; + colors_[level::debug] = CYAN; + colors_[level::info] = WHITE | BOLD; + colors_[level::warn] = YELLOW | BOLD; + colors_[level::err] = RED | BOLD; // red bold + colors_[level::critical] = BACKGROUND_RED | WHITE | BOLD; // white bold on red background + colors_[level::off] = 0; + } + + virtual ~wincolor_sink() + { + flush(); + } + + wincolor_sink(const wincolor_sink& other) = delete; + wincolor_sink& operator=(const wincolor_sink& other) = delete; + + virtual void _sink_it(const details::log_msg& msg) override + { + auto color = colors_[msg.level]; + auto orig_attribs = set_console_attribs(color); + WriteConsoleA(out_handle_, msg.formatted.data(), static_cast(msg.formatted.size()), nullptr, nullptr); + SetConsoleTextAttribute(out_handle_, orig_attribs); //reset to orig colors + } + + virtual void flush() override + { + // windows console always flushed? + } + + // change the color for the given level + void set_color(level::level_enum level, WORD color) + { + std::lock_guard lock(base_sink::_mutex); + colors_[level] = color; + } + +private: + HANDLE out_handle_; + std::map colors_; + + // set color and return the orig console attributes (for resetting later) + WORD set_console_attribs(WORD attribs) + { + CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info; + GetConsoleScreenBufferInfo(out_handle_, &orig_buffer_info); + SetConsoleTextAttribute(out_handle_, attribs); + return orig_buffer_info.wAttributes; //return orig attribs + } +}; + +// +// windows color console to stdout +// +template +class wincolor_stdout_sink: public wincolor_sink +{ +public: + wincolor_stdout_sink() : wincolor_sink(GetStdHandle(STD_OUTPUT_HANDLE)) + {} +}; + +typedef wincolor_stdout_sink wincolor_stdout_sink_mt; +typedef wincolor_stdout_sink wincolor_stdout_sink_st; + +// +// windows color console to stderr +// +template +class wincolor_stderr_sink: public wincolor_sink +{ +public: + wincolor_stderr_sink() : wincolor_sink(GetStdHandle(STD_ERROR_HANDLE)) + {} +}; + +typedef wincolor_stderr_sink wincolor_stderr_sink_mt; +typedef wincolor_stderr_sink wincolor_stderr_sink_st; + +} +} diff --git a/include/spdlog/spdlog.h b/include/spdlog/spdlog.h index d4f4203ec..f6edd6471 100644 --- a/include/spdlog/spdlog.h +++ b/include/spdlog/spdlog.h @@ -2,12 +2,13 @@ // Copyright(c) 2015 Gabi Melman. // Distributed under the MIT License (http://opensource.org/licenses/MIT) // - // spdlog main header file. // see example.cpp for usage example #pragma once +#define SPDLOG_VERSION "0.13.0" + #include #include #include @@ -88,10 +89,17 @@ std::shared_ptr daily_logger_st(const std::string& logger_name, const fi // // Create and register stdout/stderr loggers // -std::shared_ptr stdout_logger_mt(const std::string& logger_name, bool color = false); -std::shared_ptr stdout_logger_st(const std::string& logger_name, bool color = false); -std::shared_ptr stderr_logger_mt(const std::string& logger_name, bool color = false); -std::shared_ptr stderr_logger_st(const std::string& logger_name, bool color = false); +std::shared_ptr stdout_logger_mt(const std::string& logger_name); +std::shared_ptr stdout_logger_st(const std::string& logger_name); +std::shared_ptr stderr_logger_mt(const std::string& logger_name); +std::shared_ptr stderr_logger_st(const std::string& logger_name); +// +// Create and register colored stdout/stderr loggers +// +std::shared_ptr stdout_color_mt(const std::string& logger_name); +std::shared_ptr stdout_color_st(const std::string& logger_name); +std::shared_ptr stderr_color_mt(const std::string& logger_name); +std::shared_ptr stderr_color_st(const std::string& logger_name); // @@ -116,7 +124,7 @@ std::shared_ptr create(const std::string& logger_name, const It& sinks_b // Create and register a logger with templated sink type // Example: -// spdlog::create("mylog", "dailylog_filename", "txt"); +// spdlog::create("mylog", "dailylog_filename"); template std::shared_ptr create(const std::string& logger_name, Args...); diff --git a/include/spdlog/tweakme.h b/include/spdlog/tweakme.h index 1af539bf6..86f66b9e0 100644 --- a/include/spdlog/tweakme.h +++ b/include/spdlog/tweakme.h @@ -101,3 +101,8 @@ /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to prevent child processes from inheriting log file descriptors +// +// #define SPDLOG_PREVENT_CHILD_FD +/////////////////////////////////////////////////////////////////////////////// diff --git a/tests/errors.cpp b/tests/errors.cpp index 04cd97c24..75de900a2 100644 --- a/tests/errors.cpp +++ b/tests/errors.cpp @@ -6,6 +6,19 @@ #include + + +class failing_sink: public spdlog::sinks::sink +{ + void log(const spdlog::details::log_msg& msg) override + { + throw std::runtime_error("some error happened during log"); + } + + void flush() + {} +}; + TEST_CASE("default_error_handler", "[errors]]") { prepare_logdir(); @@ -22,13 +35,16 @@ TEST_CASE("default_error_handler", "[errors]]") } -struct custom_ex {}; + + +struct custom_ex +{}; TEST_CASE("custom_error_handler", "[errors]]") { prepare_logdir(); std::string filename = "logs/simple_log.txt"; auto logger = spdlog::create("logger", filename, true); - logger->flush_on(spdlog::level::info); + logger->flush_on(spdlog::level::info); logger->set_error_handler([=](const std::string& msg) { throw custom_ex(); @@ -39,6 +55,17 @@ TEST_CASE("custom_error_handler", "[errors]]") REQUIRE(count_lines(filename) == 2); } +TEST_CASE("default_error_handler2", "[errors]]") +{ + + auto logger = spdlog::create("failed_logger"); + logger->set_error_handler([=](const std::string& msg) + { + throw custom_ex(); + }); + REQUIRE_THROWS_AS(logger->info("Some message"), custom_ex); +} + TEST_CASE("async_error_handler", "[errors]]") { prepare_logdir(); @@ -62,3 +89,25 @@ TEST_CASE("async_error_handler", "[errors]]") REQUIRE(count_lines(filename) == 2); REQUIRE(file_contents("logs/custom_err.txt") == err_msg); } + +// Make sure async error handler is executed +TEST_CASE("async_error_handler2", "[errors]]") +{ + prepare_logdir(); + std::string err_msg("This is async handler error message"); + spdlog::set_async_mode(128); + { + auto logger = spdlog::create("failed_logger"); + logger->set_error_handler([=](const std::string& msg) + { + std::ofstream ofs("logs/custom_err2.txt"); + if (!ofs) throw std::runtime_error("Failed open logs/custom_err2.txt"); + ofs << err_msg; + }); + logger->info("Hello failure"); + spdlog::drop("failed_logger"); //force logger to drain the queue and shutdown + spdlog::set_sync_mode(); + } + + REQUIRE(file_contents("logs/custom_err2.txt") == err_msg); +} diff --git a/tests/file_helper.cpp b/tests/file_helper.cpp index 599c233ff..9a4ad603a 100644 --- a/tests/file_helper.cpp +++ b/tests/file_helper.cpp @@ -12,7 +12,7 @@ static void write_with_helper(file_helper &helper, size_t howmany) log_msg msg; msg.formatted << std::string(howmany, '1'); helper.write(msg); - helper.flush(); + helper.flush(); } @@ -45,7 +45,7 @@ TEST_CASE("file_helper_exists", "[file_helper::file_exists()]]") { prepare_logdir(); REQUIRE(!file_helper::file_exists(target_filename)); - file_helper helper; + file_helper helper; helper.open(target_filename); REQUIRE(file_helper::file_exists(target_filename)); } @@ -65,7 +65,7 @@ TEST_CASE("file_helper_reopen2", "[file_helper::reopen(false)]]") { prepare_logdir(); size_t expected_size = 14; - file_helper helper; + file_helper helper; helper.open(target_filename); write_with_helper(helper, expected_size); REQUIRE(helper.size() == expected_size); diff --git a/tests/file_log.cpp b/tests/file_log.cpp index ab1d9432a..45f6e8c17 100644 --- a/tests/file_log.cpp +++ b/tests/file_log.cpp @@ -7,7 +7,7 @@ TEST_CASE("simple_file_logger", "[simple_logger]]") { prepare_logdir(); - std::string filename = "logs/simple_log.txt"; + std::string filename = "logs/simple_log"; auto logger = spdlog::create("logger", filename); logger->set_pattern("%v"); @@ -23,21 +23,21 @@ TEST_CASE("simple_file_logger", "[simple_logger]]") TEST_CASE("flush_on", "[flush_on]]") { - prepare_logdir(); - std::string filename = "logs/simple_log.txt"; - - auto logger = spdlog::create("logger", filename); - logger->set_pattern("%v"); - logger->set_level(spdlog::level::trace); - logger->flush_on(spdlog::level::info); - logger->trace("Should not be flushed"); - REQUIRE(count_lines(filename) == 0); - - logger->info("Test message {}", 1); - logger->info("Test message {}", 2); - logger->flush(); - REQUIRE(file_contents(filename) == std::string("Should not be flushed\nTest message 1\nTest message 2\n")); - REQUIRE(count_lines(filename) == 3); + prepare_logdir(); + std::string filename = "logs/simple_log"; + + auto logger = spdlog::create("logger", filename); + logger->set_pattern("%v"); + logger->set_level(spdlog::level::trace); + logger->flush_on(spdlog::level::info); + logger->trace("Should not be flushed"); + REQUIRE(count_lines(filename) == 0); + + logger->info("Test message {}", 1); + logger->info("Test message {}", 2); + logger->flush(); + REQUIRE(file_contents(filename) == std::string("Should not be flushed\nTest message 1\nTest message 2\n")); + REQUIRE(count_lines(filename) == 3); } TEST_CASE("rotating_file_logger1", "[rotating_logger]]") @@ -45,12 +45,12 @@ TEST_CASE("rotating_file_logger1", "[rotating_logger]]") prepare_logdir(); std::string basename = "logs/rotating_log"; auto logger = spdlog::rotating_logger_mt("logger", basename, 1024, 0); - + for (int i = 0; i < 10; ++i) logger->info("Test message {}", i); - logger->flush(); - auto filename = basename + ".txt"; + logger->flush(); + auto filename = basename; REQUIRE(count_lines(filename) == 10); } @@ -64,14 +64,14 @@ TEST_CASE("rotating_file_logger2", "[rotating_logger]]") logger->info("Test message {}", i); logger->flush(); - auto filename = basename + ".txt"; + auto filename = basename; REQUIRE(count_lines(filename) == 10); for (int i = 0; i < 1000; i++) logger->info("Test message {}", i); logger->flush(); REQUIRE(get_filesize(filename) <= 1024); - auto filename1 = basename + ".1.txt"; + auto filename1 = basename + ".1"; REQUIRE(get_filesize(filename1) <= 1024); } @@ -83,7 +83,7 @@ TEST_CASE("daily_logger", "[daily_logger]]") std::string basename = "logs/daily_log"; std::tm tm = spdlog::details::os::localtime(); fmt::MemoryWriter w; - w.write("{}_{:04d}-{:02d}-{:02d}_{:02d}-{:02d}.txt", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min); + w.write("{}_{:04d}-{:02d}-{:02d}_{:02d}-{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min); auto logger = spdlog::daily_logger_mt("logger", basename, 0, 0); logger->flush_on(spdlog::level::info); @@ -106,23 +106,23 @@ TEST_CASE("daily_logger with dateonly calculator", "[daily_logger_dateonly]]") std::string basename = "logs/daily_dateonly"; std::tm tm = spdlog::details::os::localtime(); fmt::MemoryWriter w; - w.write("{}_{:04d}-{:02d}-{:02d}.txt", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + w.write("{}_{:04d}-{:02d}-{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); - auto logger = spdlog::create("logger", basename, "txt", 0, 0); + auto logger = spdlog::create("logger", basename, 0, 0); for (int i = 0; i < 10; ++i) logger->info("Test message {}", i); - logger->flush(); + logger->flush(); auto filename = w.str(); REQUIRE(count_lines(filename) == 10); } struct custom_daily_file_name_calculator { - static spdlog::filename_t calc_filename(const spdlog::filename_t& basename, const spdlog::filename_t& extension) + static spdlog::filename_t calc_filename(const spdlog::filename_t& basename) { std::tm tm = spdlog::details::os::localtime(); fmt::MemoryWriter w; - w.write("{}{:04d}{:02d}{:02d}.{}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, extension); + w.write("{}{:04d}{:02d}{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); return w.str(); } }; @@ -138,14 +138,14 @@ TEST_CASE("daily_logger with custom calculator", "[daily_logger_custom]]") std::string basename = "logs/daily_dateonly"; std::tm tm = spdlog::details::os::localtime(); fmt::MemoryWriter w; - w.write("{}{:04d}{:02d}{:02d}.txt", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + w.write("{}{:04d}{:02d}{:02d}", basename, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); - auto logger = spdlog::create("logger", basename, "txt", 0, 0); + auto logger = spdlog::create("logger", basename, 0, 0); for (int i = 0; i < 10; ++i) logger->info("Test message {}", i); - logger->flush(); - auto filename = w.str(); + logger->flush(); + auto filename = w.str(); REQUIRE(count_lines(filename) == 10); } diff --git a/tests/utils.cpp b/tests/utils.cpp index 9fe8b1621..e07853525 100644 --- a/tests/utils.cpp +++ b/tests/utils.cpp @@ -1,14 +1,17 @@ #include "includes.h" + void prepare_logdir() { spdlog::drop_all(); #ifdef _WIN32 - auto rv = system("del /F /Q logs\\*"); + system("if not exist logs mkdir logs"); + system("del /F /Q logs\\*"); #else - auto rv = system("rm -f logs/*"); -#endif + auto rv = system("mkdir -p logs"); + rv = system("rm -f logs/*"); (void)rv; +#endif }