Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Rob Patro 2015-08-28 00:19:42 -04:00
commit eade79f798
36 changed files with 11059 additions and 530 deletions

108
.travis.yml Normal file
View File

@ -0,0 +1,108 @@
# Adapted from various sources, including:
# - Louis Dionne's Hana: https://github.com/ldionne/hana
# - Paul Fultz II's FIT: https://github.com/pfultz2/Fit
# - Eric Niebler's range-v3: https://github.com/ericniebler/range-v3
language: cpp
# Test matrix:
# - Build matrix per compiler: C++11/C++14 + Debug/Release
# - Optionally: AddressSanitizer (ASAN)
# - Valgrind: all release builds are also tested with valgrind
# - clang 3.4, 3.5, 3.6, trunk
# - Note: 3.4 and trunk are tested with/without ASAN,
# the rest is only tested with ASAN=On.
# - gcc 4.9, 5.0
#
matrix:
include:
# Test clang-3.5: C++11/C++14, Buidd=Debug/Release, ASAN=On/Off
- env: CLANG_VERSION=3.5 BUILD_TYPE=Debug CPP=11 ASAN=On LIBCXX=On
os: linux
addons: &clang35
apt:
packages:
- clang-3.5
- valgrind
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-precise-3.5
- env: CLANG_VERSION=3.5 BUILD_TYPE=Release CPP=11 ASAN=Off LIBCXX=On
os: linux
addons: *clang35
# Test gcc-4.8: C++11, Build=Debug/Release, ASAN=Off
- env: GCC_VERSION=4.8 BUILD_TYPE=Debug CPP=11 ASAN=Off LIBCXX=Off
os: linux
addons: &gcc48
apt:
packages:
- g++-4.8
- valgrind
sources:
- ubuntu-toolchain-r-test
- env: GCC_VERSION=4.8 BUILD_TYPE=Release CPP=11 ASAN=Off LIBCXX=Off
os: linux
addons: *gcc48
# Test gcc-4.9: C++11, Build=Debug/Release, ASAN=Off
- env: GCC_VERSION=4.9 BUILD_TYPE=Debug CPP=11 ASAN=Off LIBCXX=Off
os: linux
addons: &gcc49
apt:
packages:
- g++-4.9
- valgrind
sources:
- ubuntu-toolchain-r-test
- env: GCC_VERSION=4.9 BUILD_TYPE=Release CPP=11 ASAN=Off LIBCXX=Off
os: linux
addons: *gcc49
# Install dependencies
before_install:
- export CHECKOUT_PATH=`pwd`;
- if [ -n "$GCC_VERSION" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi
- if [ -n "$CLANG_VERSION" ]; then export CXX="clang++-${CLANG_VERSION}" CC="clang-${CLANG_VERSION}"; fi
- if [ "$CLANG_VERSION" == "3.4" ]; then export CXX="/usr/local/clang-3.4/bin/clang++" CC="/usr/local/clang-3.4/bin/clang"; fi
- which $CXX
- which $CC
- which valgrind
- if [ -n "$CLANG_VERSION" ]; then sudo CXX=$CXX CC=$CC ./tests/install_libcxx.sh; fi
install:
- cd $CHECKOUT_PATH
# Workaround for valgrind bug: https://bugs.kde.org/show_bug.cgi?id=326469.
# It is fixed in valgrind 3.10 so this won't be necessary if someone
# replaces the current valgrind (3.7) with valgrind-3.10
- sed -i 's/march=native/msse4.2/' example/Makefile
- if [ ! -d build ]; then mkdir build; fi
- export CXX_FLAGS=""
- export CXX_LINKER_FLAGS=""
- if [ -z "$BUILD_TYPE" ]; then export BUILD_TYPE=Release; fi
- if [ "$ASAN" == "On"]; then export CXX_FLAGS="${CXX_FLAGS} -fsanitize=address,undefined,integer -fno-omit-frame-pointer -fno-sanitize=unsigned-integer-overflow"; fi
- if [ -n "$CLANG_VERSION" ]; then CXX_FLAGS="${CXX_FLAGS} -D__extern_always_inline=inline"; fi
- if [ "$LIBCXX" == "On" ]; then CXX_FLAGS="${CXX_FLAGS} -stdlib=libc++ -I/usr/include/c++/v1/"; fi
- if [ "$LIBCXX" == "On" ]; then CXX_LINKER_FLAGS="${CXX_FLAGS} -L/usr/lib/ -lc++"; fi
- CXX_FLAGS="${CXX_FLAGS} -std=c++${CPP}"
# Build examples
- cd example
- if [ "$BUILD_TYPE" == "Release" ]; then make rebuild CXXFLAGS="${CXX_FLAGS} ${CXX_LINKER_FLAGS}" VERBOSE=1; export BIN=example; fi
- if [ "$BUILD_TYPE" == "Debug" ]; then make rebuild debug CXXFLAGS="${CXX_FLAGS} ${CXX_LINKER_FLAGS}" VERBOSE=1; export BIN=example-debug; fi
script:
- ./"${BIN}"
- valgrind --trace-children=yes --leak-check=full ./"${BIN}"
- cd $CHECKOUT_PATH/tests; make rebuild; ./tests
notifications:
email: false

View File

@ -1,6 +1,6 @@
# spdlog # spdlog
Very fast, header only, C++ logging library. Very fast, header only, C++ logging library. [![Build Status](https://travis-ci.org/gabime/spdlog.svg?branch=master)](https://travis-ci.org/gabime/spdlog)
## Install ## Install
Just copy the files to your build tree and use a C++11 compiler Just copy the files to your build tree and use a C++11 compiler
@ -17,7 +17,7 @@ Just copy the files to your build tree and use a C++11 compiler
* Feature rich [call style](#usage-example) using the excellent [cppformat](http://cppformat.github.io/) library. * Feature rich [call style](#usage-example) using the excellent [cppformat](http://cppformat.github.io/) library.
* ostream call style is supported too. * ostream call style is supported too.
* Extremely fast asynchronous mode (optional) - using lockfree queues and other tricks to reach millions of calls/sec. * Extremely fast asynchronous mode (optional) - using lockfree queues and other tricks to reach millions of calls/sec.
* [Custom](https://github.com/gabime/spdlog/wiki/Custom-formatting) formatting. * [Custom](https://github.com/gabime/spdlog/wiki/3.-Custom-formatting) formatting.
* Multi/Single threaded loggers. * Multi/Single threaded loggers.
* Various log targets: * Various log targets:
* Rotating log files. * Rotating log files.
@ -155,4 +155,3 @@ void custom_class_example()
## Documentation ## Documentation
Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki/1.-QuickStart) pages. Documentation can be found in the [wiki](https://github.com/gabime/spdlog/wiki/1.-QuickStart) pages.

View File

@ -1,6 +1,7 @@
CXX ?= g++ CXX ?= g++
CXXFLAGS = -march=native -Wall -Wshadow -Wextra -pedantic -std=c++11 -pthread -Wl,--no-as-needed -I../include CXXFLAGS =
CXX_RELEASE_FLAGS = -O3 -flto CXX_FLAGS = -Wall -Wshadow -Wextra -pedantic -std=c++11 -pthread -I../include
CXX_RELEASE_FLAGS = -O3 -march=native
CXX_DEBUG_FLAGS= -g CXX_DEBUG_FLAGS= -g
@ -8,19 +9,17 @@ all: example bench
debug: example-debug bench-debug debug: example-debug bench-debug
example: example.cpp example: example.cpp
$(CXX) example.cpp -o example $(CXXFLAGS) $(CXX_RELEASE_FLAGS) $(CXX) example.cpp -o example $(CXX_FLAGS) $(CXX_RELEASE_FLAGS) $(CXXFLAGS)
bench: bench.cpp bench: bench.cpp
$(CXX) bench.cpp -o bench $(CXXFLAGS) $(CXX_RELEASE_FLAGS) $(CXX) bench.cpp -o bench $(CXX_FLAGS) $(CXX_RELEASE_FLAGS) $(CXXFLAGS)
example-debug: example.cpp example-debug: example.cpp
$(CXX) example.cpp -o example-debug $(CXXFLAGS) $(CXX_DEBUG_FLAGS) $(CXX) example.cpp -o example-debug $(CXX_FLAGS) $(CXX_DEBUG_FLAGS) $(CXXFLAGS)
bench-debug: bench.cpp bench-debug: bench.cpp
$(CXX) bench.cpp -o bench-debug $(CXXFLAGS) $(CXX_DEBUG_FLAGS) $(CXX) bench.cpp -o bench-debug $(CXX_FLAGS) $(CXX_DEBUG_FLAGS) $(CXXFLAGS)
clean: clean:
rm -f *.o logs/*.txt example example-debug bench bench-debug rm -f *.o logs/*.txt example example-debug bench bench-debug
@ -28,5 +27,3 @@ clean:
rebuild: clean all rebuild: clean all
rebuild-debug: clean debug rebuild-debug: clean debug

View File

@ -58,19 +58,22 @@ public:
const It& end, const It& end,
size_t queue_size, size_t queue_size,
const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, const async_overflow_policy overflow_policy = async_overflow_policy::block_retry,
const std::function<void()>& worker_warmup_cb = nullptr); const std::function<void()>& worker_warmup_cb = nullptr,
const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero());
async_logger(const std::string& logger_name, async_logger(const std::string& logger_name,
sinks_init_list sinks, sinks_init_list sinks,
size_t queue_size, size_t queue_size,
const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, const async_overflow_policy overflow_policy = async_overflow_policy::block_retry,
const std::function<void()>& worker_warmup_cb = nullptr); const std::function<void()>& worker_warmup_cb = nullptr,
const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero());
async_logger(const std::string& logger_name, async_logger(const std::string& logger_name,
sink_ptr single_sink, sink_ptr single_sink,
size_t queue_size, size_t queue_size,
const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, const async_overflow_policy overflow_policy = async_overflow_policy::block_retry,
const std::function<void()>& worker_warmup_cb = nullptr); const std::function<void()>& worker_warmup_cb = nullptr,
const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero());
protected: protected:

View File

@ -49,8 +49,8 @@ class sink;
// Common types across the lib // Common types across the lib
using log_clock = std::chrono::system_clock; using log_clock = std::chrono::system_clock;
using sink_ptr = std::shared_ptr < sinks::sink > ; using sink_ptr = std::shared_ptr < sinks::sink >;
using sinks_init_list = std::initializer_list < sink_ptr > ; using sinks_init_list = std::initializer_list < sink_ptr >;
using formatter_ptr = std::shared_ptr<spdlog::formatter>; using formatter_ptr = std::shared_ptr<spdlog::formatter>;
@ -59,16 +59,16 @@ namespace level
{ {
typedef enum typedef enum
{ {
trace = 0, trace = 0,
debug = 1, debug = 1,
info = 2, info = 2,
notice = 3, notice = 3,
warn = 4, warn = 4,
err = 5, err = 5,
critical = 6, critical = 6,
alert = 7, alert = 7,
emerg = 8, emerg = 8,
off = 9 off = 9
} level_enum; } level_enum;
static const char* level_names[] { "trace", "debug", "info", "notice", "warning", "error", "critical", "alert", "emerg", "off"}; static const char* level_names[] { "trace", "debug", "info", "notice", "warning", "error", "critical", "alert", "emerg", "off"};

View File

@ -43,6 +43,7 @@
#include "./mpmc_bounded_q.h" #include "./mpmc_bounded_q.h"
#include "./log_msg.h" #include "./log_msg.h"
#include "./format.h" #include "./format.h"
#include "os.h"
namespace spdlog namespace spdlog
@ -119,7 +120,8 @@ public:
const std::vector<sink_ptr>& sinks, const std::vector<sink_ptr>& sinks,
size_t queue_size, size_t queue_size,
const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, const async_overflow_policy overflow_policy = async_overflow_policy::block_retry,
const std::function<void()>& worker_warmup_cb = nullptr); const std::function<void()>& worker_warmup_cb = nullptr,
const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero());
void log(const details::log_msg& msg); void log(const details::log_msg& msg);
@ -145,6 +147,9 @@ private:
// worker thread warmup callback - one can set thread priority, affinity, etc // worker thread warmup callback - one can set thread priority, affinity, etc
const std::function<void()> _worker_warmup_cb; const std::function<void()> _worker_warmup_cb;
// auto periodic sink flush parameter
const std::chrono::milliseconds _flush_interval_ms;
// worker thread // worker thread
std::thread _worker_thread; std::thread _worker_thread;
@ -156,10 +161,14 @@ private:
// pop next message from the queue and process it // pop next message from the queue and process it
// return true if a message was available (queue was not empty), will set the last_pop to the pop time // return true if a message was available (queue was not empty), will set the last_pop to the pop time
bool process_next_msg(clock::time_point& last_pop); bool process_next_msg(log_clock::time_point& last_pop, log_clock::time_point& last_flush);
void handle_flush_interval(log_clock::time_point& now, log_clock::time_point& last_flush);
// sleep,yield or return immediatly using the time passed since last message as a hint // sleep,yield or return immediatly using the time passed since last message as a hint
static void sleep_or_yield(const clock::time_point& last_op_time); static void sleep_or_yield(const spdlog::log_clock::time_point& now, const log_clock::time_point& last_op_time);
}; };
} }
@ -168,12 +177,13 @@ private:
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// async_sink class implementation // async_sink class implementation
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
inline spdlog::details::async_log_helper::async_log_helper(formatter_ptr formatter, const std::vector<sink_ptr>& sinks, size_t queue_size, const async_overflow_policy overflow_policy, const std::function<void()>& worker_warmup_cb): inline spdlog::details::async_log_helper::async_log_helper(formatter_ptr formatter, const std::vector<sink_ptr>& sinks, size_t queue_size, const async_overflow_policy overflow_policy, const std::function<void()>& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms):
_formatter(formatter), _formatter(formatter),
_sinks(sinks), _sinks(sinks),
_q(queue_size), _q(queue_size),
_overflow_policy(overflow_policy), _overflow_policy(overflow_policy),
_worker_warmup_cb(worker_warmup_cb), _worker_warmup_cb(worker_warmup_cb),
_flush_interval_ms(flush_interval_ms),
_worker_thread(&async_log_helper::worker_loop, this) _worker_thread(&async_log_helper::worker_loop, this)
{} {}
@ -199,10 +209,12 @@ inline void spdlog::details::async_log_helper::log(const details::log_msg& msg)
async_msg new_msg(msg); async_msg new_msg(msg);
if (!_q.enqueue(std::move(new_msg)) && _overflow_policy != async_overflow_policy::discard_log_msg) if (!_q.enqueue(std::move(new_msg)) && _overflow_policy != async_overflow_policy::discard_log_msg)
{ {
auto last_op_time = clock::now(); auto last_op_time = details::os::now();
auto now = last_op_time;
do do
{ {
sleep_or_yield(last_op_time); now = details::os::now();
sleep_or_yield(now, last_op_time);
} }
while (!_q.enqueue(std::move(new_msg))); while (!_q.enqueue(std::move(new_msg)));
} }
@ -214,8 +226,9 @@ inline void spdlog::details::async_log_helper::worker_loop()
try try
{ {
if (_worker_warmup_cb) _worker_warmup_cb(); if (_worker_warmup_cb) _worker_warmup_cb();
clock::time_point last_pop = clock::now(); auto last_pop = details::os::now();
while(process_next_msg(last_pop)); auto last_flush = last_pop;
while(process_next_msg(last_pop, last_flush));
} }
catch (const std::exception& ex) catch (const std::exception& ex)
{ {
@ -229,7 +242,7 @@ inline void spdlog::details::async_log_helper::worker_loop()
// process next message in the queue // process next message in the queue
// return true if this thread should still be active (no msg with level::off was received) // return true if this thread should still be active (no msg with level::off was received)
inline bool spdlog::details::async_log_helper::process_next_msg(clock::time_point& last_pop) 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; async_msg incoming_async_msg;
@ -237,7 +250,7 @@ inline bool spdlog::details::async_log_helper::process_next_msg(clock::time_poin
if (_q.dequeue(incoming_async_msg)) if (_q.dequeue(incoming_async_msg))
{ {
last_pop = clock::now(); last_pop = details::os::now();
if(incoming_async_msg.level == level::off) if(incoming_async_msg.level == level::off)
return false; return false;
@ -249,11 +262,22 @@ inline bool spdlog::details::async_log_helper::process_next_msg(clock::time_poin
} }
else //empty queue else //empty queue
{ {
sleep_or_yield(last_pop); auto now = details::os::now();
handle_flush_interval(now, last_flush);
sleep_or_yield(now, last_pop);
} }
return true; return true;
} }
inline void spdlog::details::async_log_helper::handle_flush_interval(log_clock::time_point& now, log_clock::time_point& last_flush)
{
if (_flush_interval_ms != std::chrono::milliseconds::zero() && now - last_flush >= _flush_interval_ms)
{
for (auto &s : _sinks)
s->flush();
now = last_flush = details::os::now();
}
}
inline void spdlog::details::async_log_helper::set_formatter(formatter_ptr msg_formatter) inline void spdlog::details::async_log_helper::set_formatter(formatter_ptr msg_formatter)
{ {
_formatter = msg_formatter; _formatter = msg_formatter;
@ -261,12 +285,12 @@ inline void spdlog::details::async_log_helper::set_formatter(formatter_ptr msg_f
// sleep,yield or return immediatly using the time passed since last message as a hint // sleep,yield or return immediatly using the time passed since last message as a hint
inline void spdlog::details::async_log_helper::sleep_or_yield(const clock::time_point& last_op_time) inline void spdlog::details::async_log_helper::sleep_or_yield(const spdlog::log_clock::time_point& now, const spdlog::log_clock::time_point& last_op_time)
{ {
using std::chrono::milliseconds; using std::chrono::milliseconds;
using namespace std::this_thread; using namespace std::this_thread;
auto time_since_op = clock::now() - last_op_time; auto time_since_op = now - last_op_time;
// spin upto 1 ms // spin upto 1 ms
if (time_since_op <= milliseconds(1)) if (time_since_op <= milliseconds(1))

View File

@ -39,9 +39,10 @@ inline spdlog::async_logger::async_logger(const std::string& logger_name,
const It& end, const It& end,
size_t queue_size, size_t queue_size,
const async_overflow_policy overflow_policy, const async_overflow_policy overflow_policy,
const std::function<void()>& worker_warmup_cb) : const std::function<void()>& worker_warmup_cb,
const std::chrono::milliseconds& flush_interval_ms) :
logger(logger_name, begin, end), logger(logger_name, begin, end),
_async_log_helper(new details::async_log_helper(_formatter, _sinks, queue_size, overflow_policy, worker_warmup_cb)) _async_log_helper(new details::async_log_helper(_formatter, _sinks, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms))
{ {
} }
@ -49,15 +50,17 @@ inline spdlog::async_logger::async_logger(const std::string& logger_name,
sinks_init_list sinks, sinks_init_list sinks,
size_t queue_size, size_t queue_size,
const async_overflow_policy overflow_policy, const async_overflow_policy overflow_policy,
const std::function<void()>& worker_warmup_cb) : const std::function<void()>& worker_warmup_cb,
async_logger(logger_name, sinks.begin(), sinks.end(), queue_size, overflow_policy, worker_warmup_cb) {} const std::chrono::milliseconds& flush_interval_ms) :
async_logger(logger_name, sinks.begin(), sinks.end(), queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms) {}
inline spdlog::async_logger::async_logger(const std::string& logger_name, inline spdlog::async_logger::async_logger(const std::string& logger_name,
sink_ptr single_sink, sink_ptr single_sink,
size_t queue_size, size_t queue_size,
const async_overflow_policy overflow_policy, const async_overflow_policy overflow_policy,
const std::function<void()>& worker_warmup_cb) : const std::function<void()>& worker_warmup_cb,
async_logger(logger_name, { single_sink }, queue_size, overflow_policy, worker_warmup_cb) {} const std::chrono::milliseconds& flush_interval_ms) :
async_logger(logger_name, { single_sink }, queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms) {}
inline void spdlog::async_logger::_set_formatter(spdlog::formatter_ptr msg_formatter) inline void spdlog::async_logger::_set_formatter(spdlog::formatter_ptr msg_formatter)

View File

@ -48,7 +48,7 @@ public:
const int open_tries = 5; const int open_tries = 5;
const int open_interval = 10; const int open_interval = 10;
explicit file_helper(bool force_flush): explicit file_helper(bool force_flush) :
_fd(nullptr), _fd(nullptr),
_force_flush(force_flush) _force_flush(force_flush)
{} {}
@ -62,7 +62,7 @@ public:
} }
void open(const std::string& fname, bool truncate=false) void open(const std::string& fname, bool truncate = false)
{ {
close(); close();
@ -70,7 +70,7 @@ public:
_filename = fname; _filename = fname;
for (int tries = 0; tries < open_tries; ++tries) for (int tries = 0; tries < open_tries; ++tries)
{ {
if(!os::fopen_s(&_fd, fname, mode)) if (!os::fopen_s(&_fd, fname, mode))
return; return;
std::this_thread::sleep_for(std::chrono::milliseconds(open_interval)); std::this_thread::sleep_for(std::chrono::milliseconds(open_interval));
@ -81,12 +81,16 @@ public:
void reopen(bool truncate) void reopen(bool truncate)
{ {
if(_filename.empty()) if (_filename.empty())
throw spdlog_ex("Failed re opening file - was not opened before"); throw spdlog_ex("Failed re opening file - was not opened before");
open(_filename, truncate); open(_filename, truncate);
} }
void flush() {
std::fflush(_fd);
}
void close() void close()
{ {
if (_fd) if (_fd)
@ -101,14 +105,37 @@ public:
size_t size = msg.formatted.size(); size_t size = msg.formatted.size();
auto data = msg.formatted.data(); auto data = msg.formatted.data();
if(std::fwrite(data, 1, size, _fd) != size) if (std::fwrite(data, 1, size, _fd) != size)
throw spdlog_ex("Failed writing to file " + _filename); throw spdlog_ex("Failed writing to file " + _filename);
if(_force_flush) if (_force_flush)
std::fflush(_fd); std::fflush(_fd);
} }
long size()
{
if (!_fd)
throw spdlog_ex("Cannot use size() on closed file " + _filename);
auto pos = ftell(_fd);
if (fseek(_fd, 0, SEEK_END) != 0)
throw spdlog_ex("fseek failed on file " + _filename);
auto size = ftell(_fd);
if(fseek(_fd, pos, SEEK_SET) !=0)
throw spdlog_ex("fseek failed on file " + _filename);
if (size == -1)
throw spdlog_ex("ftell failed on file " + _filename);
return size;
}
const std::string& filename() const const std::string& filename() const
{ {
return _filename; return _filename;
@ -128,6 +155,8 @@ public:
} }
} }
private: private:
FILE* _fd; FILE* _fd;
std::string _filename; std::string _filename;
@ -137,4 +166,3 @@ private:
}; };
} }
} }

View File

@ -35,11 +35,18 @@
#include <cmath> #include <cmath>
#include <cstdarg> #include <cstdarg>
#ifdef _WIN32 #if defined(_WIN32) && defined(__MINGW32__)
# ifdef __MINGW32__ # include <cstring>
# include <cstring> #endif
#if FMT_USE_WINDOWS_H
# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX)
# include <windows.h>
# else
# define NOMINMAX
# include <windows.h>
# undef NOMINMAX
# endif # endif
# include <windows.h>
#endif #endif
using fmt::internal::Arg; using fmt::internal::Arg;
@ -88,13 +95,14 @@ using fmt::internal::Arg;
// Dummy implementations of strerror_r and strerror_s called if corresponding // Dummy implementations of strerror_r and strerror_s called if corresponding
// system functions are not available. // system functions are not available.
static inline fmt::internal::None<> strerror_r(int, char *, ...) { static inline fmt::internal::Null<> strerror_r(int, char *, ...) {
return fmt::internal::None<>(); return fmt::internal::Null<>();
} }
static inline fmt::internal::None<> strerror_s(char *, std::size_t, ...) { static inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) {
return fmt::internal::None<>(); return fmt::internal::Null<>();
} }
namespace fmt {
namespace { namespace {
#ifndef _MSC_VER #ifndef _MSC_VER
@ -125,6 +133,7 @@ struct IntChecker {
unsigned max = INT_MAX; unsigned max = INT_MAX;
return value <= max; return value <= max;
} }
static bool fits_in_int(bool) { return true; }
}; };
template <> template <>
@ -150,7 +159,7 @@ typedef void (*FormatFunc)(fmt::Writer &, int, fmt::StringRef);
// Buffer should be at least of size 1. // Buffer should be at least of size 1.
int safe_strerror( int safe_strerror(
int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT { int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT {
assert(buffer != 0 && buffer_size != 0); FMT_ASSERT(buffer != 0 && buffer_size != 0, "invalid buffer");
class StrError { class StrError {
private: private:
@ -177,7 +186,7 @@ int safe_strerror(
} }
// Handle the case when strerror_r is not available. // Handle the case when strerror_r is not available.
int handle(fmt::internal::None<>) { int handle(fmt::internal::Null<>) {
return fallback(strerror_s(buffer_, buffer_size_, error_code_)); return fallback(strerror_s(buffer_, buffer_size_, error_code_));
} }
@ -189,17 +198,20 @@ int safe_strerror(
} }
// Fallback to strerror if strerror_r and strerror_s are not available. // Fallback to strerror if strerror_r and strerror_s are not available.
int fallback(fmt::internal::None<>) { int fallback(fmt::internal::Null<>) {
errno = 0; errno = 0;
buffer_ = strerror(error_code_); buffer_ = strerror(error_code_);
return errno; return errno;
} }
public: public:
StrError(int error_code, char *&buffer, std::size_t buffer_size) StrError(int err_code, char *&buf, std::size_t buf_size)
: error_code_(error_code), buffer_(buffer), buffer_size_(buffer_size) {} : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {}
int run() { return handle(strerror_r(error_code_, buffer_, buffer_size_)); } int run() {
strerror_r(0, 0, ""); // Suppress a warning about unused strerror_r.
return handle(strerror_r(error_code_, buffer_, buffer_size_));
}
}; };
return StrError(error_code, buffer, buffer_size).run(); return StrError(error_code, buffer, buffer_size).run();
} }
@ -259,6 +271,11 @@ int parse_nonnegative_int(const Char *&s) {
return value; return value;
} }
template <typename Char>
inline bool is_name_start(Char c) {
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c;
}
inline void require_numeric_argument(const Arg &arg, char spec) { inline void require_numeric_argument(const Arg &arg, char spec) {
if (arg.type > Arg::LAST_NUMERIC_TYPE) { if (arg.type > Arg::LAST_NUMERIC_TYPE) {
std::string message = std::string message =
@ -379,24 +396,139 @@ class CharConverter : public fmt::internal::ArgVisitor<CharConverter, void> {
arg_.int_value = static_cast<char>(value); arg_.int_value = static_cast<char>(value);
} }
}; };
// This function template is used to prevent compile errors when handling
// incompatible string arguments, e.g. handling a wide string in a narrow
// string formatter.
template <typename Char>
Arg::StringValue<Char> ignore_incompatible_str(Arg::StringValue<wchar_t>);
template <>
inline Arg::StringValue<char> ignore_incompatible_str(
Arg::StringValue<wchar_t>) { return Arg::StringValue<char>(); }
template <>
inline Arg::StringValue<wchar_t> ignore_incompatible_str(
Arg::StringValue<wchar_t> s) { return s; }
} // namespace } // namespace
namespace internal {
template <typename Impl, typename Char>
class BasicArgFormatter : public ArgVisitor<Impl, void> {
private:
BasicWriter<Char> &writer_;
FormatSpec &spec_;
FMT_DISALLOW_COPY_AND_ASSIGN(BasicArgFormatter);
protected:
BasicWriter<Char> &writer() { return writer_; }
const FormatSpec &spec() const { return spec_; }
public:
BasicArgFormatter(BasicWriter<Char> &w, FormatSpec &s)
: writer_(w), spec_(s) {}
template <typename T>
void visit_any_int(T value) { writer_.write_int(value, spec_); }
template <typename T>
void visit_any_double(T value) { writer_.write_double(value, spec_); }
void visit_bool(bool value) {
if (spec_.type_) {
writer_.write_int(value, spec_);
return;
}
const char *str_value = value ? "true" : "false";
Arg::StringValue<char> str = { str_value, strlen(str_value) };
writer_.write_str(str, spec_);
}
void visit_char(int value) {
if (spec_.type_ && spec_.type_ != 'c') {
spec_.flags_ |= CHAR_FLAG;
writer_.write_int(value, spec_);
return;
}
if (spec_.align_ == ALIGN_NUMERIC || spec_.flags_ != 0)
FMT_THROW(FormatError("invalid format specifier for char"));
typedef typename BasicWriter<Char>::CharPtr CharPtr;
Char fill = internal::CharTraits<Char>::cast(spec_.fill());
CharPtr out = CharPtr();
if (spec_.width_ > 1) {
out = writer_.grow_buffer(spec_.width_);
if (spec_.align_ == ALIGN_RIGHT) {
std::fill_n(out, spec_.width_ - 1, fill);
out += spec_.width_ - 1;
} else if (spec_.align_ == ALIGN_CENTER) {
out = writer_.fill_padding(out, spec_.width_, 1, fill);
} else {
std::fill_n(out + 1, spec_.width_ - 1, fill);
}
} else {
out = writer_.grow_buffer(1);
}
*out = internal::CharTraits<Char>::cast(value);
}
void visit_string(Arg::StringValue<char> value) {
writer_.write_str(value, spec_);
}
using ArgVisitor<Impl, void>::visit_wstring;
void visit_wstring(Arg::StringValue<Char> value) {
writer_.write_str(value, spec_);
}
void visit_pointer(const void *value) {
if (spec_.type_ && spec_.type_ != 'p')
report_unknown_type(spec_.type_, "pointer");
spec_.flags_ = HASH_FLAG;
spec_.type_ = 'x';
writer_.write_int(reinterpret_cast<uintptr_t>(value), spec_);
}
};
// An argument formatter.
template <typename Char>
class ArgFormatter : public BasicArgFormatter<ArgFormatter<Char>, Char> {
private:
BasicFormatter<Char> &formatter_;
const Char *format_;
public:
ArgFormatter(BasicFormatter<Char> &f, FormatSpec &s, const Char *fmt)
: BasicArgFormatter<ArgFormatter<Char>, Char>(f.writer(), s),
formatter_(f), format_(fmt) {}
void visit_custom(Arg::CustomValue c) {
c.format(&formatter_, c.value, &format_);
}
};
template <typename Char>
class PrintfArgFormatter :
public BasicArgFormatter<PrintfArgFormatter<Char>, Char> {
public:
PrintfArgFormatter(BasicWriter<Char> &w, FormatSpec &s)
: BasicArgFormatter<PrintfArgFormatter<Char>, Char>(w, s) {}
void visit_char(int value) {
const FormatSpec &fmt_spec = this->spec();
BasicWriter<Char> &w = this->writer();
if (fmt_spec.type_ && fmt_spec.type_ != 'c')
w.write_int(value, fmt_spec);
typedef typename BasicWriter<Char>::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<Char>(value);
}
};
} // namespace internal
} // namespace fmt
FMT_FUNC void fmt::SystemError::init( FMT_FUNC void fmt::SystemError::init(
int err_code, StringRef format_str, ArgList args) { int err_code, CStringRef format_str, ArgList args) {
error_code_ = err_code; error_code_ = err_code;
MemoryWriter w; MemoryWriter w;
internal::format_system_error(w, err_code, format(format_str, args)); internal::format_system_error(w, err_code, format(format_str, args));
@ -477,19 +609,23 @@ FMT_FUNC void fmt::internal::report_unknown_type(char code, const char *type) {
static_cast<unsigned>(code), type))); static_cast<unsigned>(code), type)));
} }
#ifdef _WIN32 #if FMT_USE_WINDOWS_H
FMT_FUNC fmt::internal::UTF8ToUTF16::UTF8ToUTF16(fmt::StringRef s) { FMT_FUNC fmt::internal::UTF8ToUTF16::UTF8ToUTF16(fmt::StringRef s) {
int length = MultiByteToWideChar(
CP_UTF8, MB_ERR_INVALID_CHARS, s.c_str(), -1, 0, 0);
static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16"; 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));
int s_size = static_cast<int>(s.size());
int length = MultiByteToWideChar(
CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, 0, 0);
if (length == 0) if (length == 0)
FMT_THROW(WindowsError(GetLastError(), ERROR_MSG)); FMT_THROW(WindowsError(GetLastError(), ERROR_MSG));
buffer_.resize(length); buffer_.resize(length + 1);
length = MultiByteToWideChar( length = MultiByteToWideChar(
CP_UTF8, MB_ERR_INVALID_CHARS, s.c_str(), -1, &buffer_[0], length); CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length);
if (length == 0) if (length == 0)
FMT_THROW(WindowsError(GetLastError(), ERROR_MSG)); FMT_THROW(WindowsError(GetLastError(), ERROR_MSG));
buffer_[length] = 0;
} }
FMT_FUNC fmt::internal::UTF16ToUTF8::UTF16ToUTF8(fmt::WStringRef s) { FMT_FUNC fmt::internal::UTF16ToUTF8::UTF16ToUTF8(fmt::WStringRef s) {
@ -500,19 +636,23 @@ FMT_FUNC fmt::internal::UTF16ToUTF8::UTF16ToUTF8(fmt::WStringRef s) {
} }
FMT_FUNC int fmt::internal::UTF16ToUTF8::convert(fmt::WStringRef s) { FMT_FUNC int fmt::internal::UTF16ToUTF8::convert(fmt::WStringRef s) {
int length = WideCharToMultiByte(CP_UTF8, 0, s.c_str(), -1, 0, 0, 0, 0); if (s.size() > INT_MAX)
return ERROR_INVALID_PARAMETER;
int s_size = static_cast<int>(s.size());
int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, 0, 0, 0, 0);
if (length == 0) if (length == 0)
return GetLastError(); return GetLastError();
buffer_.resize(length); buffer_.resize(length + 1);
length = WideCharToMultiByte( length = WideCharToMultiByte(
CP_UTF8, 0, s.c_str(), -1, &buffer_[0], length, 0, 0); CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, 0, 0);
if (length == 0) if (length == 0)
return GetLastError(); return GetLastError();
buffer_[length] = 0;
return 0; return 0;
} }
FMT_FUNC void fmt::WindowsError::init( FMT_FUNC void fmt::WindowsError::init(
int err_code, StringRef format_str, ArgList args) { int err_code, CStringRef format_str, ArgList args) {
error_code_ = err_code; error_code_ = err_code;
MemoryWriter w; MemoryWriter w;
internal::format_windows_error(w, err_code, format(format_str, args)); internal::format_windows_error(w, err_code, format(format_str, args));
@ -520,30 +660,6 @@ FMT_FUNC void fmt::WindowsError::init(
base = std::runtime_error(w.str()); base = std::runtime_error(w.str());
} }
#endif
FMT_FUNC void fmt::internal::format_system_error(
fmt::Writer &out, int error_code,
fmt::StringRef message) FMT_NOEXCEPT {
FMT_TRY {
MemoryBuffer<char, INLINE_BUFFER_SIZE> buffer;
buffer.resize(INLINE_BUFFER_SIZE);
for (;;) {
char *system_message = &buffer[0];
int result = safe_strerror(error_code, system_message, buffer.size());
if (result == 0) {
out << message << ": " << system_message;
return;
}
if (result != ERANGE)
break; // Can't get error message, report error code instead.
buffer.resize(buffer.size() * 2);
}
} FMT_CATCH(...) {}
format_error_code(out, error_code, message);
}
#ifdef _WIN32
FMT_FUNC void fmt::internal::format_windows_error( FMT_FUNC void fmt::internal::format_windows_error(
fmt::Writer &out, int error_code, fmt::Writer &out, int error_code,
fmt::StringRef message) FMT_NOEXCEPT { fmt::StringRef message) FMT_NOEXCEPT {
@ -572,81 +688,74 @@ FMT_FUNC void fmt::internal::format_windows_error(
} FMT_CATCH(...) {} } FMT_CATCH(...) {}
format_error_code(out, error_code, message); format_error_code(out, error_code, message);
} }
#endif
// An argument formatter. #endif // FMT_USE_WINDOWS_H
template <typename Char>
class fmt::internal::ArgFormatter :
public fmt::internal::ArgVisitor<fmt::internal::ArgFormatter<Char>, void> {
private:
fmt::BasicFormatter<Char> &formatter_;
fmt::BasicWriter<Char> &writer_;
fmt::FormatSpec &spec_;
const Char *format_;
FMT_DISALLOW_COPY_AND_ASSIGN(ArgFormatter); FMT_FUNC void fmt::internal::format_system_error(
fmt::Writer &out, int error_code,
public: fmt::StringRef message) FMT_NOEXCEPT {
ArgFormatter( FMT_TRY {
fmt::BasicFormatter<Char> &f,fmt::FormatSpec &s, const Char *fmt) MemoryBuffer<char, INLINE_BUFFER_SIZE> buffer;
: formatter_(f), writer_(f.writer()), spec_(s), format_(fmt) {} buffer.resize(INLINE_BUFFER_SIZE);
for (;;) {
template <typename T> char *system_message = &buffer[0];
void visit_any_int(T value) { writer_.write_int(value, spec_); } int result = safe_strerror(error_code, system_message, buffer.size());
if (result == 0) {
template <typename T> out << message << ": " << system_message;
void visit_any_double(T value) { writer_.write_double(value, spec_); } return;
void visit_char(int value) {
if (spec_.type_ && spec_.type_ != 'c') {
spec_.flags_ |= CHAR_FLAG;
writer_.write_int(value, spec_);
return;
}
if (spec_.align_ == ALIGN_NUMERIC || spec_.flags_ != 0)
FMT_THROW(FormatError("invalid format specifier for char"));
typedef typename fmt::BasicWriter<Char>::CharPtr CharPtr;
Char fill = static_cast<Char>(spec_.fill());
if (spec_.precision_ == 0) {
std::fill_n(writer_.grow_buffer(spec_.width_), spec_.width_, fill);
return;
}
CharPtr out = CharPtr();
if (spec_.width_ > 1) {
out = writer_.grow_buffer(spec_.width_);
if (spec_.align_ == fmt::ALIGN_RIGHT) {
std::fill_n(out, spec_.width_ - 1, fill);
out += spec_.width_ - 1;
} else if (spec_.align_ == fmt::ALIGN_CENTER) {
out = writer_.fill_padding(out, spec_.width_, 1, fill);
} else {
std::fill_n(out + 1, spec_.width_ - 1, fill);
} }
} else { if (result != ERANGE)
out = writer_.grow_buffer(1); break; // Can't get error message, report error code instead.
buffer.resize(buffer.size() * 2);
} }
*out = static_cast<Char>(value); } FMT_CATCH(...) {}
} format_error_code(out, error_code, message);
}
void visit_string(Arg::StringValue<char> value) { template <typename Char>
writer_.write_str(value, spec_); void fmt::internal::ArgMap<Char>::init(const ArgList &args) {
if (!map_.empty())
return;
typedef internal::NamedArg<Char> NamedArg;
const NamedArg *named_arg = 0;
bool use_values =
args.type(ArgList::MAX_PACKED_ARGS - 1) == internal::Arg::NONE;
if (use_values) {
for (unsigned i = 0;/*nothing*/; ++i) {
internal::Arg::Type arg_type = args.type(i);
switch (arg_type) {
case internal::Arg::NONE:
return;
case internal::Arg::NAMED_ARG:
named_arg = static_cast<const NamedArg*>(args.values_[i].pointer);
map_.insert(Pair(named_arg->name, *named_arg));
break;
default:
/*nothing*/;
}
}
return;
} }
void visit_wstring(Arg::StringValue<wchar_t> value) { for (unsigned i = 0; i != ArgList::MAX_PACKED_ARGS; ++i) {
writer_.write_str(ignore_incompatible_str<Char>(value), spec_); internal::Arg::Type arg_type = args.type(i);
if (arg_type == internal::Arg::NAMED_ARG) {
named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);
map_.insert(Pair(named_arg->name, *named_arg));
}
} }
for (unsigned i = ArgList::MAX_PACKED_ARGS;/*nothing*/; ++i) {
void visit_pointer(const void *value) { switch (args.args_[i].type) {
if (spec_.type_ && spec_.type_ != 'p') case internal::Arg::NONE:
fmt::internal::report_unknown_type(spec_.type_, "pointer"); return;
spec_.flags_ = fmt::HASH_FLAG; case internal::Arg::NAMED_ARG:
spec_.type_ = 'x'; named_arg = static_cast<const NamedArg*>(args.args_[i].pointer);
writer_.write_int(reinterpret_cast<uintptr_t>(value), spec_); map_.insert(Pair(named_arg->name, *named_arg));
break;
default:
/*nothing*/;
}
} }
}
void visit_custom(Arg::CustomValue c) {
c.format(&formatter_, c.value, &format_);
}
};
template <typename Char> template <typename Char>
void fmt::internal::FixedBuffer<Char>::grow(std::size_t) { void fmt::internal::FixedBuffer<Char>::grow(std::size_t) {
@ -675,6 +784,19 @@ void fmt::BasicWriter<Char>::write_str(
write_str(str_value, str_size, spec); write_str(str_value, str_size, spec);
} }
template <typename Char>
inline Arg fmt::BasicFormatter<Char>::get_arg(
BasicStringRef<Char> arg_name, const char *&error) {
if (check_no_auto_index(error)) {
map_.init(args());
const Arg *arg = map_.find(arg_name);
if (arg)
return *arg;
error = "argument not found";
}
return Arg();
}
template <typename Char> template <typename Char>
inline Arg fmt::BasicFormatter<Char>::parse_arg_index(const Char *&s) { inline Arg fmt::BasicFormatter<Char>::parse_arg_index(const Char *&s) {
const char *error = 0; const char *error = 0;
@ -687,11 +809,33 @@ inline Arg fmt::BasicFormatter<Char>::parse_arg_index(const Char *&s) {
return arg; return arg;
} }
template <typename Char>
inline Arg fmt::BasicFormatter<Char>::parse_arg_name(const Char *&s) {
assert(is_name_start(*s));
const Char *start = s;
Char c;
do {
c = *++s;
} while (is_name_start(c) || ('0' <= c && c <= '9'));
const char *error = 0;
Arg arg = get_arg(fmt::BasicStringRef<Char>(start, s - start), error);
if (error)
FMT_THROW(fmt::FormatError(error));
return arg;
}
FMT_FUNC Arg fmt::internal::FormatterBase::do_get_arg( FMT_FUNC Arg fmt::internal::FormatterBase::do_get_arg(
unsigned arg_index, const char *&error) { unsigned arg_index, const char *&error) {
Arg arg = args_[arg_index]; Arg arg = args_[arg_index];
if (arg.type == Arg::NONE) switch (arg.type) {
case Arg::NONE:
error = "argument index out of range"; error = "argument index out of range";
break;
case Arg::NAMED_ARG:
arg = *static_cast<const internal::Arg*>(arg.pointer);
default:
/*nothing*/;
}
return arg; return arg;
} }
@ -702,14 +846,19 @@ inline Arg fmt::internal::FormatterBase::next_arg(const char *&error) {
return Arg(); return Arg();
} }
inline bool fmt::internal::FormatterBase::check_no_auto_index(
const char *&error) {
if (next_arg_index_ > 0) {
error = "cannot switch from automatic to manual argument indexing";
return false;
}
next_arg_index_ = -1;
return true;
}
inline Arg fmt::internal::FormatterBase::get_arg( inline Arg fmt::internal::FormatterBase::get_arg(
unsigned arg_index, const char *&error) { unsigned arg_index, const char *&error) {
if (next_arg_index_ <= 0) { return check_no_auto_index(error) ? do_get_arg(arg_index, error) : Arg();
next_arg_index_ = -1;
return do_get_arg(arg_index, error);
}
error = "cannot switch from automatic to manual argument indexing";
return Arg();
} }
template <typename Char> template <typename Char>
@ -787,10 +936,8 @@ unsigned fmt::internal::PrintfFormatter<Char>::parse_header(
template <typename Char> template <typename Char>
void fmt::internal::PrintfFormatter<Char>::format( void fmt::internal::PrintfFormatter<Char>::format(
BasicWriter<Char> &writer, BasicStringRef<Char> format_str, BasicWriter<Char> &writer, BasicCStringRef<Char> format_str) {
const ArgList &args) {
const Char *start = format_str.c_str(); const Char *start = format_str.c_str();
set_args(args);
const Char *s = start; const Char *s = start;
while (*s) { while (*s) {
Char c = *s++; Char c = *s++;
@ -881,73 +1028,7 @@ void fmt::internal::PrintfFormatter<Char>::format(
start = s; start = s;
// Format argument. // Format argument.
switch (arg.type) { internal::PrintfArgFormatter<Char>(writer, spec).visit(arg);
case Arg::INT:
writer.write_int(arg.int_value, spec);
break;
case Arg::UINT:
writer.write_int(arg.uint_value, spec);
break;
case Arg::LONG_LONG:
writer.write_int(arg.long_long_value, spec);
break;
case Arg::ULONG_LONG:
writer.write_int(arg.ulong_long_value, spec);
break;
case Arg::CHAR: {
if (spec.type_ && spec.type_ != 'c')
writer.write_int(arg.int_value, spec);
typedef typename BasicWriter<Char>::CharPtr CharPtr;
CharPtr out = CharPtr();
if (spec.width_ > 1) {
Char fill = ' ';
out = writer.grow_buffer(spec.width_);
if (spec.align_ != ALIGN_LEFT) {
std::fill_n(out, spec.width_ - 1, fill);
out += spec.width_ - 1;
} else {
std::fill_n(out + 1, spec.width_ - 1, fill);
}
} else {
out = writer.grow_buffer(1);
}
*out = static_cast<Char>(arg.int_value);
break;
}
case Arg::DOUBLE:
writer.write_double(arg.double_value, spec);
break;
case Arg::LONG_DOUBLE:
writer.write_double(arg.long_double_value, spec);
break;
case Arg::CSTRING:
arg.string.size = 0;
writer.write_str(arg.string, spec);
break;
case Arg::STRING:
writer.write_str(arg.string, spec);
break;
case Arg::WSTRING:
writer.write_str(ignore_incompatible_str<Char>(arg.wstring), spec);
break;
case Arg::POINTER:
if (spec.type_ && spec.type_ != 'p')
internal::report_unknown_type(spec.type_, "pointer");
spec.flags_= HASH_FLAG;
spec.type_ = 'x';
writer.write_int(reinterpret_cast<uintptr_t>(arg.pointer), spec);
break;
case Arg::CUSTOM: {
if (spec.type_)
internal::report_unknown_type(spec.type_, "object");
const void *str_format = "s";
arg.custom.format(&writer, arg.custom.value, &str_format);
break;
}
default:
assert(false);
break;
}
} }
write(writer, start, s); write(writer, start, s);
} }
@ -1019,16 +1100,47 @@ const Char *fmt::BasicFormatter<Char>::format(
++s; ++s;
} }
// Parse width and zero flag. // Parse zero flag.
if (*s == '0') {
require_numeric_argument(arg, '0');
spec.align_ = ALIGN_NUMERIC;
spec.fill_ = '0';
++s;
}
// Parse width.
if ('0' <= *s && *s <= '9') { if ('0' <= *s && *s <= '9') {
if (*s == '0') {
require_numeric_argument(arg, '0');
spec.align_ = ALIGN_NUMERIC;
spec.fill_ = '0';
}
// Zero may be parsed again as a part of the width, but it is simpler
// and more efficient than checking if the next char is a digit.
spec.width_ = parse_nonnegative_int(s); spec.width_ = parse_nonnegative_int(s);
} else if (*s == '{') {
++s;
Arg width_arg = is_name_start(*s) ?
parse_arg_name(s) : parse_arg_index(s);
if (*s++ != '}')
FMT_THROW(FormatError("invalid format string"));
ULongLong value = 0;
switch (width_arg.type) {
case Arg::INT:
if (width_arg.int_value < 0)
FMT_THROW(FormatError("negative width"));
value = width_arg.int_value;
break;
case Arg::UINT:
value = width_arg.uint_value;
break;
case Arg::LONG_LONG:
if (width_arg.long_long_value < 0)
FMT_THROW(FormatError("negative width"));
value = width_arg.long_long_value;
break;
case Arg::ULONG_LONG:
value = width_arg.ulong_long_value;
break;
default:
FMT_THROW(FormatError("width is not integer"));
}
if (value > INT_MAX)
FMT_THROW(FormatError("number is too big"));
spec.width_ = static_cast<int>(value);
} }
// Parse precision. // Parse precision.
@ -1039,7 +1151,8 @@ const Char *fmt::BasicFormatter<Char>::format(
spec.precision_ = parse_nonnegative_int(s); spec.precision_ = parse_nonnegative_int(s);
} else if (*s == '{') { } else if (*s == '{') {
++s; ++s;
const Arg &precision_arg = parse_arg_index(s); Arg precision_arg =
is_name_start(*s) ? parse_arg_name(s) : parse_arg_index(s);
if (*s++ != '}') if (*s++ != '}')
FMT_THROW(FormatError("invalid format string")); FMT_THROW(FormatError("invalid format string"));
ULongLong value = 0; ULongLong value = 0;
@ -1069,7 +1182,7 @@ const Char *fmt::BasicFormatter<Char>::format(
} else { } else {
FMT_THROW(FormatError("missing precision specifier")); FMT_THROW(FormatError("missing precision specifier"));
} }
if (arg.type < Arg::LAST_INTEGER_TYPE || arg.type == Arg::POINTER) { if (arg.type <= Arg::LAST_INTEGER_TYPE || arg.type == Arg::POINTER) {
FMT_THROW(FormatError( FMT_THROW(FormatError(
fmt::format("precision not allowed in {} format specifier", fmt::format("precision not allowed in {} format specifier",
arg.type == Arg::POINTER ? "pointer" : "integer"))); arg.type == Arg::POINTER ? "pointer" : "integer")));
@ -1083,7 +1196,6 @@ const Char *fmt::BasicFormatter<Char>::format(
if (*s++ != '}') if (*s++ != '}')
FMT_THROW(FormatError("missing '}' in format string")); FMT_THROW(FormatError("missing '}' in format string"));
start_ = s;
// Format argument. // Format argument.
internal::ArgFormatter<Char>(*this, spec, s - 1).visit(arg); internal::ArgFormatter<Char>(*this, spec, s - 1).visit(arg);
@ -1091,25 +1203,24 @@ const Char *fmt::BasicFormatter<Char>::format(
} }
template <typename Char> template <typename Char>
void fmt::BasicFormatter<Char>::format( void fmt::BasicFormatter<Char>::format(BasicCStringRef<Char> format_str) {
BasicStringRef<Char> format_str, const ArgList &args) { const Char *s = format_str.c_str();
const Char *s = start_ = format_str.c_str(); const Char *start = s;
set_args(args);
while (*s) { while (*s) {
Char c = *s++; Char c = *s++;
if (c != '{' && c != '}') continue; if (c != '{' && c != '}') continue;
if (*s == c) { if (*s == c) {
write(writer_, start_, s); write(writer_, start, s);
start_ = ++s; start = ++s;
continue; continue;
} }
if (c == '}') if (c == '}')
FMT_THROW(FormatError("unmatched '}' in format string")); FMT_THROW(FormatError("unmatched '}' in format string"));
write(writer_, start_, s - 1); write(writer_, start, s - 1);
Arg arg = parse_arg_index(s); Arg arg = is_name_start(*s) ? parse_arg_name(s) : parse_arg_index(s);
s = format(s, arg); start = s = format(s, arg);
} }
write(writer_, start_, s); write(writer_, start, s);
} }
FMT_FUNC void fmt::report_system_error( FMT_FUNC void fmt::report_system_error(
@ -1117,30 +1228,30 @@ FMT_FUNC void fmt::report_system_error(
report_error(internal::format_system_error, error_code, message); report_error(internal::format_system_error, error_code, message);
} }
#ifdef _WIN32 #if FMT_USE_WINDOWS_H
FMT_FUNC void fmt::report_windows_error( FMT_FUNC void fmt::report_windows_error(
int error_code, fmt::StringRef message) FMT_NOEXCEPT { int error_code, fmt::StringRef message) FMT_NOEXCEPT {
report_error(internal::format_windows_error, error_code, message); report_error(internal::format_windows_error, error_code, message);
} }
#endif #endif
FMT_FUNC void fmt::print(std::FILE *f, StringRef format_str, ArgList args) { FMT_FUNC void fmt::print(std::FILE *f, CStringRef format_str, ArgList args) {
MemoryWriter w; MemoryWriter w;
w.write(format_str, args); w.write(format_str, args);
std::fwrite(w.data(), 1, w.size(), f); std::fwrite(w.data(), 1, w.size(), f);
} }
FMT_FUNC void fmt::print(StringRef format_str, ArgList args) { FMT_FUNC void fmt::print(CStringRef format_str, ArgList args) {
print(stdout, format_str, args); print(stdout, format_str, args);
} }
FMT_FUNC void fmt::print(std::ostream &os, StringRef format_str, ArgList args) { FMT_FUNC void fmt::print(std::ostream &os, CStringRef format_str, ArgList args) {
MemoryWriter w; MemoryWriter w;
w.write(format_str, args); w.write(format_str, args);
os.write(w.data(), w.size()); os.write(w.data(), w.size());
} }
FMT_FUNC void fmt::print_colored(Color c, StringRef format, ArgList args) { FMT_FUNC void fmt::print_colored(Color c, CStringRef format, ArgList args) {
char escape[] = "\x1b[30m"; char escape[] = "\x1b[30m";
escape[3] = '0' + static_cast<char>(c); escape[3] = '0' + static_cast<char>(c);
std::fputs(escape, stdout); std::fputs(escape, stdout);
@ -1148,7 +1259,7 @@ FMT_FUNC void fmt::print_colored(Color c, StringRef format, ArgList args) {
std::fputs(RESET_COLOR, stdout); std::fputs(RESET_COLOR, stdout);
} }
FMT_FUNC int fmt::fprintf(std::FILE *f, StringRef format, ArgList args) { FMT_FUNC int fmt::fprintf(std::FILE *f, CStringRef format, ArgList args) {
MemoryWriter w; MemoryWriter w;
printf(w, format, args); printf(w, format, args);
std::size_t size = w.size(); std::size_t size = w.size();
@ -1157,6 +1268,8 @@ FMT_FUNC int fmt::fprintf(std::FILE *f, StringRef format, ArgList args) {
#ifndef FMT_HEADER_ONLY #ifndef FMT_HEADER_ONLY
template struct fmt::internal::BasicData<void>;
// Explicit instantiations for char. // Explicit instantiations for char.
template void fmt::internal::FixedBuffer<char>::grow(std::size_t); template void fmt::internal::FixedBuffer<char>::grow(std::size_t);
@ -1164,11 +1277,10 @@ template void fmt::internal::FixedBuffer<char>::grow(std::size_t);
template const char *fmt::BasicFormatter<char>::format( template const char *fmt::BasicFormatter<char>::format(
const char *&format_str, const fmt::internal::Arg &arg); const char *&format_str, const fmt::internal::Arg &arg);
template void fmt::BasicFormatter<char>::format( template void fmt::BasicFormatter<char>::format(CStringRef format);
BasicStringRef<char> format, const ArgList &args);
template void fmt::internal::PrintfFormatter<char>::format( template void fmt::internal::PrintfFormatter<char>::format(
BasicWriter<char> &writer, BasicStringRef<char> format, const ArgList &args); BasicWriter<char> &writer, CStringRef format);
template int fmt::internal::CharTraits<char>::format_float( template int fmt::internal::CharTraits<char>::format_float(
char *buffer, std::size_t size, const char *format, char *buffer, std::size_t size, const char *format,
@ -1186,11 +1298,10 @@ template const wchar_t *fmt::BasicFormatter<wchar_t>::format(
const wchar_t *&format_str, const fmt::internal::Arg &arg); const wchar_t *&format_str, const fmt::internal::Arg &arg);
template void fmt::BasicFormatter<wchar_t>::format( template void fmt::BasicFormatter<wchar_t>::format(
BasicStringRef<wchar_t> format, const ArgList &args); BasicCStringRef<wchar_t> format);
template void fmt::internal::PrintfFormatter<wchar_t>::format( template void fmt::internal::PrintfFormatter<wchar_t>::format(
BasicWriter<wchar_t> &writer, BasicStringRef<wchar_t> format, BasicWriter<wchar_t> &writer, WCStringRef format);
const ArgList &args);
template int fmt::internal::CharTraits<wchar_t>::format_float( template int fmt::internal::CharTraits<wchar_t>::format_float(
wchar_t *buffer, std::size_t size, const wchar_t *format, wchar_t *buffer, std::size_t size, const wchar_t *format,

File diff suppressed because it is too large Load Diff

View File

@ -206,6 +206,11 @@ public:
_enabled = false; _enabled = false;
} }
bool is_enabled() const
{
return _enabled;
}
private: private:
logger* _callback_logger; logger* _callback_logger;

View File

@ -50,7 +50,9 @@ inline spdlog::logger::logger(const std::string& logger_name, sinks_init_list si
// ctor with single sink // ctor with single sink
inline spdlog::logger::logger(const std::string& logger_name, spdlog::sink_ptr single_sink) : inline spdlog::logger::logger(const std::string& logger_name, spdlog::sink_ptr single_sink) :
logger(logger_name, { single_sink }) {} logger(logger_name, {
single_sink
}) {}
inline spdlog::logger::~logger() = default; inline spdlog::logger::~logger() = default;
@ -312,4 +314,7 @@ inline void spdlog::logger::_set_formatter(formatter_ptr msg_formatter)
_formatter = msg_formatter; _formatter = msg_formatter;
} }
inline void spdlog::logger::flush() {
for (auto& sink : _sinks)
sink->flush();
}

View File

@ -195,4 +195,3 @@ inline size_t thread_id()
} //spdlog } //spdlog

View File

@ -265,6 +265,17 @@ class f_formatter :public flag_formatter
} }
}; };
// nanoseconds
class F_formatter :public flag_formatter
{
void format(details::log_msg& msg, const std::tm&) override
{
auto duration = msg.time.time_since_epoch();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() % 1000000000;
msg.formatted << fmt::pad(static_cast<int>(ns), 9, '0');
}
};
// AM/PM // AM/PM
class p_formatter :public flag_formatter class p_formatter :public flag_formatter
{ {
@ -575,6 +586,9 @@ inline void spdlog::pattern_formatter::handle_flag(char flag)
case('f') : case('f') :
_formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::f_formatter())); _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::f_formatter()));
break; break;
case('F') :
_formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::F_formatter()));
break;
case('p') : case('p') :
_formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::p_formatter())); _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::p_formatter()));

View File

@ -33,6 +33,7 @@
#include <unordered_map> #include <unordered_map>
#include <functional> #include <functional>
#include "./null_mutex.h"
#include "../logger.h" #include "../logger.h"
#include "../async_logger.h" #include "../async_logger.h"
#include "../common.h" #include "../common.h"
@ -41,20 +42,20 @@ namespace spdlog
{ {
namespace details namespace details
{ {
class registry template <class Mutex> class registry_t
{ {
public: public:
void register_logger(std::shared_ptr<logger> logger) void register_logger(std::shared_ptr<logger> logger)
{ {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<Mutex> lock(_mutex);
register_logger_impl(logger); register_logger_impl(logger);
} }
std::shared_ptr<logger> get(const std::string& logger_name) std::shared_ptr<logger> get(const std::string& logger_name)
{ {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<Mutex> lock(_mutex);
auto found = _loggers.find(logger_name); auto found = _loggers.find(logger_name);
return found == _loggers.end() ? nullptr : found->second; return found == _loggers.end() ? nullptr : found->second;
} }
@ -65,11 +66,11 @@ public:
std::shared_ptr<logger> new_logger; std::shared_ptr<logger> new_logger;
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<Mutex> lock(_mutex);
if (_async_mode) if (_async_mode)
new_logger = std::make_shared<async_logger>(logger_name, sinks_begin, sinks_end, _async_q_size, _overflow_policy, _worker_warmup_cb); new_logger = std::make_shared<async_logger>(logger_name, sinks_begin, sinks_end, _async_q_size, _overflow_policy, _worker_warmup_cb, _flush_interval_ms);
else else
new_logger = std::make_shared<logger>(logger_name, sinks_begin, sinks_end); new_logger = std::make_shared<logger>(logger_name, sinks_begin, sinks_end);
@ -83,13 +84,13 @@ public:
void drop(const std::string& logger_name) void drop(const std::string& logger_name)
{ {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<Mutex> lock(_mutex);
_loggers.erase(logger_name); _loggers.erase(logger_name);
} }
void drop_all() void drop_all()
{ {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<Mutex> lock(_mutex);
_loggers.clear(); _loggers.clear();
} }
std::shared_ptr<logger> create(const std::string& logger_name, sinks_init_list sinks) std::shared_ptr<logger> create(const std::string& logger_name, sinks_init_list sinks)
@ -105,7 +106,7 @@ public:
void formatter(formatter_ptr f) void formatter(formatter_ptr f)
{ {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<Mutex> lock(_mutex);
_formatter = f; _formatter = f;
for (auto& l : _loggers) for (auto& l : _loggers)
l.second->set_formatter(_formatter); l.second->set_formatter(_formatter);
@ -113,7 +114,7 @@ public:
void set_pattern(const std::string& pattern) void set_pattern(const std::string& pattern)
{ {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<Mutex> lock(_mutex);
_formatter = std::make_shared<pattern_formatter>(pattern); _formatter = std::make_shared<pattern_formatter>(pattern);
for (auto& l : _loggers) for (auto& l : _loggers)
l.second->set_formatter(_formatter); l.second->set_formatter(_formatter);
@ -121,30 +122,31 @@ public:
void set_level(level::level_enum log_level) void set_level(level::level_enum log_level)
{ {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<Mutex> lock(_mutex);
for (auto& l : _loggers) for (auto& l : _loggers)
l.second->set_level(log_level); l.second->set_level(log_level);
_level = log_level; _level = log_level;
} }
void set_async_mode(size_t q_size, const async_overflow_policy overflow_policy, const std::function<void()>& worker_warmup_cb) void set_async_mode(size_t q_size, const async_overflow_policy overflow_policy, const std::function<void()>& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms)
{ {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<Mutex> lock(_mutex);
_async_mode = true; _async_mode = true;
_async_q_size = q_size; _async_q_size = q_size;
_overflow_policy = overflow_policy; _overflow_policy = overflow_policy;
_worker_warmup_cb = worker_warmup_cb; _worker_warmup_cb = worker_warmup_cb;
_flush_interval_ms = flush_interval_ms;
} }
void set_sync_mode() void set_sync_mode()
{ {
std::lock_guard<std::mutex> lock(_mutex); std::lock_guard<Mutex> lock(_mutex);
_async_mode = false; _async_mode = false;
} }
static registry& instance() static registry_t<Mutex>& instance()
{ {
static registry s_instance; static registry_t<Mutex> s_instance;
return s_instance; return s_instance;
} }
@ -156,10 +158,10 @@ private:
throw spdlog_ex("logger with name " + logger_name + " already exists"); throw spdlog_ex("logger with name " + logger_name + " already exists");
_loggers[logger->name()] = logger; _loggers[logger->name()] = logger;
} }
registry() = default; registry_t<Mutex>(){}
registry(const registry&) = delete; registry_t<Mutex>(const registry_t<Mutex>&) = delete;
registry& operator=(const registry&) = delete; registry_t<Mutex>& operator=(const registry_t<Mutex>&) = delete;
std::mutex _mutex; Mutex _mutex;
std::unordered_map <std::string, std::shared_ptr<logger>> _loggers; std::unordered_map <std::string, std::shared_ptr<logger>> _loggers;
formatter_ptr _formatter; formatter_ptr _formatter;
level::level_enum _level = level::info; level::level_enum _level = level::info;
@ -167,6 +169,12 @@ private:
size_t _async_q_size = 0; size_t _async_q_size = 0;
async_overflow_policy _overflow_policy = async_overflow_policy::block_retry; async_overflow_policy _overflow_policy = async_overflow_policy::block_retry;
std::function<void()> _worker_warmup_cb = nullptr; std::function<void()> _worker_warmup_cb = nullptr;
std::chrono::milliseconds _flush_interval_ms;
}; };
#ifdef SPDLOG_NO_REGISTRY_MUTEX
typedef registry_t<spdlog::details::null_mutex> registry;
#else
typedef registry_t<std::mutex> registry;
#endif
} }
} }

View File

@ -137,9 +137,9 @@ inline void spdlog::set_level(level::level_enum log_level)
} }
inline void spdlog::set_async_mode(size_t queue_size, const async_overflow_policy overflow_policy, const std::function<void()>& worker_warmup_cb) inline void spdlog::set_async_mode(size_t queue_size, const async_overflow_policy overflow_policy, const std::function<void()>& worker_warmup_cb, const std::chrono::milliseconds& flush_interval_ms)
{ {
details::registry::instance().set_async_mode(queue_size, overflow_policy, worker_warmup_cb); details::registry::instance().set_async_mode(queue_size, overflow_policy, worker_warmup_cb, flush_interval_ms);
} }
inline void spdlog::set_sync_mode() inline void spdlog::set_sync_mode()

View File

@ -107,6 +107,7 @@ public:
void set_pattern(const std::string&); void set_pattern(const std::string&);
void set_formatter(formatter_ptr); void set_formatter(formatter_ptr);
void flush();
protected: protected:
virtual void _log_msg(details::log_msg&); virtual void _log_msg(details::log_msg&);

View File

@ -58,7 +58,6 @@ public:
_sink_it(msg); _sink_it(msg);
} }
protected: protected:
virtual void _sink_it(const details::log_msg& msg) = 0; virtual void _sink_it(const details::log_msg& msg) = 0;
Mutex _mutex; Mutex _mutex;

View File

@ -47,6 +47,10 @@ public:
{ {
_file_helper.open(filename); _file_helper.open(filename);
} }
void flush() override
{
_file_helper.flush();
}
protected: protected:
void _sink_it(const details::log_msg& msg) override void _sink_it(const details::log_msg& msg) override
@ -61,8 +65,8 @@ typedef simple_file_sink<std::mutex> simple_file_sink_mt;
typedef simple_file_sink<details::null_mutex> simple_file_sink_st; typedef simple_file_sink<details::null_mutex> simple_file_sink_st;
/* /*
* Rotating file sink based on size * Rotating file sink based on size
*/ */
template<class Mutex> template<class Mutex>
class rotating_file_sink : public base_sink < Mutex > class rotating_file_sink : public base_sink < Mutex >
{ {
@ -78,6 +82,12 @@ public:
_file_helper(force_flush) _file_helper(force_flush)
{ {
_file_helper.open(calc_filename(_base_filename, 0, _extension)); _file_helper.open(calc_filename(_base_filename, 0, _extension));
_current_size = _file_helper.size(); //expensive. called only once
}
void flush() override
{
_file_helper.flush();
} }
protected: protected:
@ -143,8 +153,8 @@ typedef rotating_file_sink<std::mutex> rotating_file_sink_mt;
typedef rotating_file_sink<details::null_mutex>rotating_file_sink_st; typedef rotating_file_sink<details::null_mutex>rotating_file_sink_st;
/* /*
* Rotating file sink based on date. rotates at midnight * Rotating file sink based on date. rotates at midnight
*/ */
template<class Mutex> template<class Mutex>
class daily_file_sink :public base_sink < Mutex > class daily_file_sink :public base_sink < Mutex >
{ {
@ -167,6 +177,11 @@ public:
_file_helper.open(calc_filename(_base_filename, _extension)); _file_helper.open(calc_filename(_base_filename, _extension));
} }
void flush() override
{
_file_helper.flush();
}
protected: protected:
void _sink_it(const details::log_msg& msg) override void _sink_it(const details::log_msg& msg) override
{ {

View File

@ -40,6 +40,9 @@ protected:
void _sink_it(const details::log_msg&) override void _sink_it(const details::log_msg&) override
{} {}
void flush() override
{}
}; };
typedef null_sink<details::null_mutex> null_sink_st; typedef null_sink<details::null_mutex> null_sink_st;
typedef null_sink<std::mutex> null_sink_mt; typedef null_sink<std::mutex> null_sink_mt;

View File

@ -45,12 +45,18 @@ public:
virtual ~ostream_sink() = default; virtual ~ostream_sink() = default;
protected: protected:
virtual void _sink_it(const details::log_msg& msg) override void _sink_it(const details::log_msg& msg) override
{ {
_ostream.write(msg.formatted.data(), msg.formatted.size()); _ostream.write(msg.formatted.data(), msg.formatted.size());
if (_force_flush) if (_force_flush)
_ostream.flush(); _ostream.flush();
} }
void flush() override
{
_ostream.flush();
}
std::ostream& _ostream; std::ostream& _ostream;
bool _force_flush; bool _force_flush;
}; };

View File

@ -35,6 +35,7 @@ class sink
public: public:
virtual ~sink() {} virtual ~sink() {}
virtual void log(const details::log_msg& msg) = 0; virtual void log(const details::log_msg& msg) = 0;
virtual void flush() = 0;
}; };
} }
} }

View File

@ -78,6 +78,9 @@ public:
::syslog(syslog_prio_from_level(msg), "%s", msg.formatted.str().c_str()); ::syslog(syslog_prio_from_level(msg), "%s", msg.formatted.str().c_str());
} }
void flush() override
{
}
private: private:

View File

@ -68,7 +68,7 @@ void set_level(level::level_enum log_level);
// worker_warmup_cb (optional): // worker_warmup_cb (optional):
// callback function that will be called in worker thread upon start (can be used to init stuff like thread affinity) // callback function that will be called in worker thread upon start (can be used to init stuff like thread affinity)
// //
void set_async_mode(size_t queue_size, const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, const std::function<void()>& worker_warmup_cb = nullptr); void set_async_mode(size_t queue_size, const async_overflow_policy overflow_policy = async_overflow_policy::block_retry, const std::function<void()>& worker_warmup_cb = nullptr, const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero());
// Turn off async mode // Turn off async mode
void set_sync_mode(); void set_sync_mode();
@ -76,7 +76,7 @@ void set_sync_mode();
// //
// Create and register multi/single threaded rotating file logger // Create and register multi/single threaded rotating file logger
// //
std::shared_ptr<logger> rotating_logger_mt(const std::string& logger_name, const std::string& filename, size_t max_file_size, size_t max_files, bool force_flush = false); std::shared_ptr<logger> rotating_logger_mt(const std::string& logger_name, const std::string& filenameB, size_t max_file_size, size_t max_files, bool force_flush = false);
std::shared_ptr<logger> rotating_logger_st(const std::string& logger_name, const std::string& filename, size_t max_file_size, size_t max_files, bool force_flush = false); std::shared_ptr<logger> rotating_logger_st(const std::string& logger_name, const std::string& filename, size_t max_file_size, size_t max_files, bool force_flush = false);
// //

View File

@ -32,7 +32,7 @@
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Under Linux, the much faster CLOCK_REALTIME_COARSE clock can be used. // Under Linux, the much faster CLOCK_REALTIME_COARSE clock can be used.
// This clock is less accurate - can be off by dozens of millis - depending on the kernel HZ // This clock is less accurate - can be off by dozens of millis - depending on the kernel HZ.
// Uncomment to use it instead of the regular (but slower) clock. // Uncomment to use it instead of the regular (but slower) clock.
// #define SPDLOG_CLOCK_COARSE // #define SPDLOG_CLOCK_COARSE
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -46,7 +46,7 @@
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Uncomment if thread id logging is not needed (i.e. no %t in the log pattern) // Uncomment if thread id logging is not needed (i.e. no %t in the log pattern).
// This will prevent spdlog from quering the thread id on each log call. // This will prevent spdlog from quering the thread id on each log call.
// #define SPDLOG_NO_THREAD_ID // #define SPDLOG_NO_THREAD_ID
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -60,7 +60,15 @@
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Uncomment to enable the SPDLOG_DEBUG/SPDLOG_TRACE macros // Uncomment to enable the SPDLOG_DEBUG/SPDLOG_TRACE macros.
// #define SPDLOG_DEBUG_ON // #define SPDLOG_DEBUG_ON
// #define SPDLOG_TRACE_ON // #define SPDLOG_TRACE_ON
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// Uncomment to avoid locking in the registry operations (spdlog::get(), spdlog::drop() spdlog::register()).
// Use only if your code never modifes concurrently the registry.
// Note that upon creating a logger the registry is modified by spdlog..
// #define SPDLOG_NO_REGISTRY_MUTEX
///////////////////////////////////////////////////////////////////////////////

22
tests/Makefile Normal file
View File

@ -0,0 +1,22 @@
CXX ?= g++
CXXFLAGS = -Wall -pedantic -std=c++11 -pthread -O2
LDPFALGS = -pthread
CPP_FILES := $(wildcard *.cpp)
OBJ_FILES := $(addprefix ./,$(notdir $(CPP_FILES:.cpp=.o)))
tests: $(OBJ_FILES)
$(CXX) $(CXXFLAGS) $(LDPFALGS) -o $@ $^
mkdir -p logs
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c -o $@ $<
clean:
rm -f tests *.o logs/*.txt
rebuild: clean tests

9427
tests/catch.hpp Normal file

File diff suppressed because it is too large Load Diff

132
tests/file_log.cpp Normal file
View File

@ -0,0 +1,132 @@
#include "includes.h"
static std::string file_contents(const std::string& filename)
{
std::ifstream ifs(filename);
if (!ifs)
throw std::runtime_error("Failed open file ");
return std::string((std::istreambuf_iterator<char>(ifs)),
(std::istreambuf_iterator<char>()));
}
static std::size_t count_lines(const std::string& filename)
{
std::ifstream ifs(filename);
if (!ifs)
throw std::runtime_error("Failed open file ");
std::string line;
size_t counter = 0;
while(std::getline(ifs, line))
counter++;
return counter;
}
std::ifstream::pos_type filesize(const std::string& filename)
{
std::ifstream ifs(filename, std::ifstream::ate | std::ifstream::binary);
if (!ifs)
throw std::runtime_error("Failed open file ");
return ifs.tellg();
}
static void prepare_logdir()
{
spdlog::drop_all();
#ifdef _WIN32
auto rv = system("del /F /Q logs\\*");
#else
auto rv = system("rm -f logs/*");
#endif
}
TEST_CASE("simple_file_logger", "[simple_logger]]")
{
prepare_logdir();
std::string filename = "logs/simple_log.txt";
auto logger = spdlog::create<spdlog::sinks::simple_file_sink_mt>("logger", filename);
logger->set_pattern("%v");
logger->info("Test message {}", 1);
logger->info("Test message {}", 2);
logger->flush();
REQUIRE(file_contents(filename) == std::string("Test message 1\nTest message 2\n"));
REQUIRE(count_lines(filename) == 2);
}
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, true);
for (int i = 0; i < 10; ++i)
logger->info("Test message {}", i);
auto filename = basename + ".txt";
REQUIRE(count_lines(filename) == 10);
for (int i = 0; i < 1000; i++)
logger->info("Test message {}", i);
}
TEST_CASE("rotating_file_logger2", "[rotating_logger]]")
{
prepare_logdir();
std::string basename = "logs/rotating_log";
auto logger = spdlog::rotating_logger_mt("logger", basename, 1024, 1, false);
for (int i = 0; i < 10; ++i)
logger->info("Test message {}", i);
logger->flush();
auto filename = basename + ".txt";
REQUIRE(count_lines(filename) == 10);
for (int i = 0; i < 1000; i++)
logger->info("Test message {}", i);
logger->flush();
REQUIRE(filesize(filename) <= 1024);
auto filename1 = basename + ".1.txt";
REQUIRE(filesize(filename1) <= 1024);
}
TEST_CASE("daily_logger", "[daily_logger]]")
{
prepare_logdir();
//calculate filename (time based)
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);
auto logger = spdlog::daily_logger_mt("logger", basename, 0, 0, true);
for (int i = 0; i < 10; ++i)
logger->info("Test message {}", i);
auto filename = w.str();
REQUIRE(count_lines(filename) == 10);
}

90
tests/format.cpp Normal file
View File

@ -0,0 +1,90 @@
#include "includes.h"
template<class T>
std::string log_info(const T& what, spdlog::level::level_enum logger_level = spdlog::level::info)
{
std::ostringstream oss;
auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss);
spdlog::logger oss_logger("oss", oss_sink);
oss_logger.set_level(logger_level);
oss_logger.set_pattern("%v");
oss_logger.info() << what;
//strip last eol and return the logged string
auto eol_size = strlen(spdlog::details::os::eol());
return oss.str().substr(0, oss.str().length() - eol_size);
}
//User defined class with operator<<
struct some_logged_class
{
some_logged_class(const std::string val) :value(val) {};
std::string value;
};
std::ostream& operator<<(std::ostream& os, const some_logged_class& c)
{
return os << c.value;
}
TEST_CASE("basic_logging ", "[basic_logging]")
{
//const char
REQUIRE(log_info("Hello") == "Hello");
REQUIRE(log_info("") == "");
//std::string
REQUIRE(log_info(std::string("Hello")) == "Hello");
REQUIRE(log_info(std::string()) == std::string());
//Numbers
REQUIRE(log_info(5) == "5");
REQUIRE(log_info(5.6) == "5.6");
//User defined class
REQUIRE(log_info(some_logged_class("some_val")) == "some_val");
}
TEST_CASE("log_levels", "[log_levels]")
{
REQUIRE(log_info("Hello", spdlog::level::err) == "");
REQUIRE(log_info("Hello", spdlog::level::critical) == "");
REQUIRE(log_info("Hello", spdlog::level::emerg) == "");
REQUIRE(log_info("Hello", spdlog::level::alert) == "");
REQUIRE(log_info("Hello", spdlog::level::info) == "Hello");
REQUIRE(log_info("Hello", spdlog::level::debug) == "Hello");
REQUIRE(log_info("Hello", spdlog::level::trace) == "Hello");
}
TEST_CASE("invalid_format", "[format]")
{
using namespace spdlog::sinks;
spdlog::logger null_logger("null_logger", std::make_shared<null_sink_st>());
REQUIRE_THROWS_AS(
null_logger.info("{} {}", "first"),
spdlog::spdlog_ex);
REQUIRE_THROWS_AS(
null_logger.info("{0:f}", "aads"),
spdlog::spdlog_ex);
REQUIRE_THROWS_AS(
null_logger.info("{0:kk}", 123),
spdlog::spdlog_ex);
}

12
tests/includes.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include <cstdio>
#include <fstream>
#include <string>
#include <ostream>
#include <chrono>
#include <exception>
#include "catch.hpp"
#include "../include/spdlog/spdlog.h"
#include "../include/spdlog/sinks/null_sink.h"

12
tests/install_libcxx.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
#
# Install libc++ under travis
svn --quiet co http://llvm.org/svn/llvm-project/libcxx/trunk libcxx
mkdir libcxx/build
(cd libcxx/build && cmake .. -DLIBCXX_CXX_ABI=libstdc++ -DLIBCXX_CXX_ABI_INCLUDE_PATHS="/usr/include/c++/4.6;/usr/include/c++/4.6/x86_64-linux-gnu")
make -C libcxx/build cxx -j2
sudo cp libcxx/build/lib/libc++.so.1.0 /usr/lib/
sudo cp -r libcxx/build/include/c++/v1 /usr/include/c++/v1/
sudo ln -sf /usr/lib/libc++.so.1.0 /usr/lib/libc++.so
sudo ln -sf /usr/lib/libc++.so.1.0 /usr/lib/libc++.so.1

2
tests/main.cpp Normal file
View File

@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include "catch.hpp"

53
tests/registry.cpp Normal file
View File

@ -0,0 +1,53 @@
#include "includes.h"
static const char *logger_name = "null_logger";
TEST_CASE("register_drop", "[registry]")
{
spdlog::drop_all();
spdlog::create<spdlog::sinks::null_sink_mt>(logger_name);
REQUIRE(spdlog::get(logger_name)!=nullptr);
//Throw if registring existing name
REQUIRE_THROWS_AS(spdlog::create<spdlog::sinks::null_sink_mt>(logger_name), spdlog::spdlog_ex);
}
TEST_CASE("explicit register" "[registry]")
{
spdlog::drop_all();
auto logger = std::make_shared<spdlog::logger>(logger_name, std::make_shared<spdlog::sinks::null_sink_st>());
spdlog::register_logger(logger);
REQUIRE(spdlog::get(logger_name) != nullptr);
//Throw if registring existing name
REQUIRE_THROWS_AS(spdlog::create<spdlog::sinks::null_sink_mt>(logger_name), spdlog::spdlog_ex);
}
TEST_CASE("drop" "[registry]")
{
spdlog::drop_all();
spdlog::create<spdlog::sinks::null_sink_mt>(logger_name);
spdlog::drop(logger_name);
REQUIRE_FALSE(spdlog::get(logger_name));
}
TEST_CASE("drop_all" "[registry]")
{
spdlog::drop_all();
spdlog::create<spdlog::sinks::null_sink_mt>(logger_name);
spdlog::create<spdlog::sinks::null_sink_mt>("name2");
spdlog::drop_all();
REQUIRE_FALSE(spdlog::get(logger_name));
REQUIRE_FALSE(spdlog::get("name2"));
}
TEST_CASE("drop non existing" "[registry]")
{
spdlog::drop_all();
spdlog::create<spdlog::sinks::null_sink_mt>(logger_name);
spdlog::drop("some_name");
REQUIRE_FALSE(spdlog::get("some_name"));
REQUIRE(spdlog::get(logger_name));
spdlog::drop_all();
}

28
tests/tests.sln Normal file
View File

@ -0,0 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.31101.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tests", "tests.vcxproj", "{59A07559-5F38-4DD6-A7FA-DB4153690B42}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
Debug|x64 = Debug|x64
Release|Win32 = Release|Win32
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{59A07559-5F38-4DD6-A7FA-DB4153690B42}.Debug|Win32.ActiveCfg = Debug|Win32
{59A07559-5F38-4DD6-A7FA-DB4153690B42}.Debug|Win32.Build.0 = Debug|Win32
{59A07559-5F38-4DD6-A7FA-DB4153690B42}.Debug|x64.ActiveCfg = Debug|x64
{59A07559-5F38-4DD6-A7FA-DB4153690B42}.Debug|x64.Build.0 = Debug|x64
{59A07559-5F38-4DD6-A7FA-DB4153690B42}.Release|Win32.ActiveCfg = Release|Win32
{59A07559-5F38-4DD6-A7FA-DB4153690B42}.Release|Win32.Build.0 = Release|Win32
{59A07559-5F38-4DD6-A7FA-DB4153690B42}.Release|x64.ActiveCfg = Release|x64
{59A07559-5F38-4DD6-A7FA-DB4153690B42}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

134
tests/tests.vcxproj Normal file
View File

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{59A07559-5F38-4DD6-A7FA-DB4153690B42}</ProjectGuid>
<RootNamespace>tests</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v120</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v120</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v120</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v120</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="file_log.cpp" />
<ClCompile Include="format.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="registry.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="catch.hpp" />
<ClInclude Include="includes.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="file_log.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="format.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="registry.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="includes.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="catch.hpp">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>