This commit is contained in:
gabime 2017-03-28 02:08:18 +03:00
parent 5a8cecdfb6
commit 0c276beaaf
10 changed files with 3414 additions and 2704 deletions

View File

@ -64,9 +64,9 @@ public:
//Warning: this can potentialy last forever as we wait it to complete //Warning: this can potentialy last forever as we wait it to complete
void flush() override; void flush() override;
// Error handler // Error handler
virtual void set_error_handler(log_err_handler) override; virtual void set_error_handler(log_err_handler) override;
virtual log_err_handler error_handler() override; virtual log_err_handler error_handler() override;
protected: protected:
void _sink_it(details::log_msg& msg) override; void _sink_it(details::log_msg& msg) override;

View File

@ -32,244 +32,252 @@
namespace spdlog namespace spdlog
{ {
namespace details namespace details
{ {
class async_log_helper class async_log_helper
{ {
// Async msg to move to/from the queue // Async msg to move to/from the queue
// Movable only. should never be copied // Movable only. should never be copied
enum class async_msg_type enum class async_msg_type
{ {
log, log,
flush, flush,
terminate terminate
}; };
struct async_msg struct async_msg
{ {
std::string logger_name; std::string logger_name;
level::level_enum level; level::level_enum level;
log_clock::time_point time; log_clock::time_point time;
size_t thread_id; size_t thread_id;
std::string txt; std::string txt;
async_msg_type msg_type; async_msg_type msg_type;
async_msg() = default; async_msg() = default;
~async_msg() = default; ~async_msg() = default;
async_msg(async_msg&& other) SPDLOG_NOEXCEPT: async_msg(async_msg&& other) SPDLOG_NOEXCEPT:
logger_name(std::move(other.logger_name)), logger_name(std::move(other.logger_name)),
level(std::move(other.level)), level(std::move(other.level)),
time(std::move(other.time)), time(std::move(other.time)),
txt(std::move(other.txt)), txt(std::move(other.txt)),
msg_type(std::move(other.msg_type)) 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 async_msg& operator=(async_msg&& other) SPDLOG_NOEXCEPT
{ {
logger_name = std::move(other.logger_name); logger_name = std::move(other.logger_name);
level = other.level; level = other.level;
time = std::move(other.time); time = std::move(other.time);
thread_id = other.thread_id; thread_id = other.thread_id;
txt = std::move(other.txt); txt = std::move(other.txt);
msg_type = other.msg_type; msg_type = other.msg_type;
return *this; return *this;
} }
// never copy or assign. should only be moved.. // never copy or assign. should only be moved..
async_msg(const async_msg&) = delete; async_msg(const async_msg&) = delete;
async_msg& operator=(const async_msg& other) = delete; async_msg& operator=(const async_msg& other) = delete;
// construct from log_msg // construct from log_msg
async_msg(const details::log_msg& m): async_msg(const details::log_msg& m):
level(m.level), level(m.level),
time(m.time), time(m.time),
thread_id(m.thread_id), thread_id(m.thread_id),
txt(m.raw.data(), m.raw.size()), txt(m.raw.data(), m.raw.size()),
msg_type(async_msg_type::log) msg_type(async_msg_type::log)
{ {
#ifndef SPDLOG_NO_NAME #ifndef SPDLOG_NO_NAME
logger_name = *m.logger_name; logger_name = *m.logger_name;
#endif #endif
} }
// copy into log_msg // copy into log_msg
void fill_log_msg(log_msg &msg) void fill_log_msg(log_msg &msg)
{ {
msg.logger_name = &logger_name; msg.logger_name = &logger_name;
msg.level = level; msg.level = level;
msg.time = time; msg.time = time;
msg.thread_id = thread_id; msg.thread_id = thread_id;
msg.raw << txt; msg.raw << txt;
} }
}; };
public: public:
using item_type = async_msg; using item_type = async_msg;
using q_type = details::mpmc_bounded_queue<item_type>; using q_type = details::mpmc_bounded_queue<item_type>;
using clock = std::chrono::steady_clock; using clock = std::chrono::steady_clock;
async_log_helper(formatter_ptr formatter, async_log_helper(formatter_ptr formatter,
const std::vector<sink_ptr>& sinks, const std::vector<sink_ptr>& sinks,
size_t queue_size, size_t queue_size,
const log_err_handler err_handler, const log_err_handler err_handler,
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(), const std::chrono::milliseconds& flush_interval_ms = std::chrono::milliseconds::zero(),
const std::function<void()>& worker_teardown_cb = nullptr); const std::function<void()>& worker_teardown_cb = nullptr);
void log(const details::log_msg& msg); void log(const details::log_msg& msg);
// stop logging and join the back thread // stop logging and join the back thread
~async_log_helper(); ~async_log_helper();
void set_formatter(formatter_ptr); void set_formatter(formatter_ptr);
void flush(bool wait_for_q); void flush(bool wait_for_q);
void set_error_handler(spdlog::log_err_handler err_handler); void set_error_handler(spdlog::log_err_handler err_handler);
private: private:
formatter_ptr _formatter; formatter_ptr _formatter;
std::vector<std::shared_ptr<sinks::sink>> _sinks; std::vector<std::shared_ptr<sinks::sink>> _sinks;
// queue of messages to log // queue of messages to log
q_type _q; q_type _q;
log_err_handler _err_handler; log_err_handler _err_handler;
bool _flush_requested; bool _flush_requested;
bool _terminate_requested; bool _terminate_requested;
// overflow policy // overflow policy
const async_overflow_policy _overflow_policy; const async_overflow_policy _overflow_policy;
// 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 // auto periodic sink flush parameter
const std::chrono::milliseconds _flush_interval_ms; const std::chrono::milliseconds _flush_interval_ms;
// worker thread teardown callback // worker thread teardown callback
const std::function<void()> _worker_teardown_cb; const std::function<void()> _worker_teardown_cb;
// worker thread // worker thread
std::thread _worker_thread; std::thread _worker_thread;
void push_msg(async_msg&& new_msg); void push_msg(async_msg&& new_msg);
// worker thread main loop // worker thread main loop
void worker_loop(); void worker_loop();
// pop next message from the queue and process it. will set the last_pop to the pop time // pop next message from the queue and process it. will set the last_pop to the pop time
// return false if termination of the queue is required // return false if termination of the queue is required
bool process_next_msg(log_clock::time_point& last_pop, log_clock::time_point& last_flush); 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); 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 spdlog::log_clock::time_point& now, const log_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);
// wait until the queue is empty // wait until the queue is empty
void wait_empty_q(); void wait_empty_q();
}; };
} }
} }
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// async_sink class implementation // async_sink class implementation
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
inline spdlog::details::async_log_helper::async_log_helper( inline spdlog::details::async_log_helper::async_log_helper(
formatter_ptr formatter, formatter_ptr formatter,
const std::vector<sink_ptr>& sinks, const std::vector<sink_ptr>& sinks,
size_t queue_size, size_t queue_size,
log_err_handler err_handler, log_err_handler err_handler,
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, const std::chrono::milliseconds& flush_interval_ms,
const std::function<void()>& worker_teardown_cb): const std::function<void()>& worker_teardown_cb):
_formatter(formatter), _formatter(formatter),
_sinks(sinks), _sinks(sinks),
_q(queue_size), _q(queue_size),
_err_handler(err_handler), _err_handler(err_handler),
_flush_requested(false), _flush_requested(false),
_terminate_requested(false), _terminate_requested(false),
_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), _flush_interval_ms(flush_interval_ms),
_worker_teardown_cb(worker_teardown_cb), _worker_teardown_cb(worker_teardown_cb),
_worker_thread(&async_log_helper::worker_loop, this) _worker_thread(&async_log_helper::worker_loop, this)
{} {}
// Send to the worker thread termination message(level=off) // Send to the worker thread termination message(level=off)
// and wait for it to finish gracefully // and wait for it to finish gracefully
inline spdlog::details::async_log_helper::~async_log_helper() inline spdlog::details::async_log_helper::~async_log_helper()
{ {
try { try
push_msg(async_msg(async_msg_type::terminate)); {
_worker_thread.join(); push_msg(async_msg(async_msg_type::terminate));
} _worker_thread.join();
catch (...) // don't crash in destructor }
{ catch (...) // don't crash in destructor
} {
}
} }
//Try to push and block until succeeded (if the policy is not to discard when the queue is full) //Try to push and block until succeeded (if the policy is not to discard when the queue is full)
inline void spdlog::details::async_log_helper::log(const details::log_msg& msg) inline void spdlog::details::async_log_helper::log(const details::log_msg& msg)
{ {
push_msg(async_msg(msg)); push_msg(async_msg(msg));
} }
inline void spdlog::details::async_log_helper::push_msg(details::async_log_helper::async_msg&& new_msg) inline void spdlog::details::async_log_helper::push_msg(details::async_log_helper::async_msg&& new_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 = details::os::now(); {
auto now = last_op_time; auto last_op_time = details::os::now();
do { auto now = last_op_time;
now = details::os::now(); do
sleep_or_yield(now, last_op_time); {
} while (!_q.enqueue(std::move(new_msg))); now = details::os::now();
} sleep_or_yield(now, last_op_time);
}
while (!_q.enqueue(std::move(new_msg)));
}
} }
// optionally wait for the queue be empty and request flush from the sinks // 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) inline void spdlog::details::async_log_helper::flush(bool wait_for_q)
{ {
push_msg(async_msg(async_msg_type::flush)); 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 wait_empty_q(); //return only make after the above flush message was processed
} }
inline void spdlog::details::async_log_helper::worker_loop() inline void spdlog::details::async_log_helper::worker_loop()
{ {
if (_worker_warmup_cb) _worker_warmup_cb(); if (_worker_warmup_cb) _worker_warmup_cb();
auto last_pop = details::os::now(); auto last_pop = details::os::now();
auto last_flush = last_pop; auto last_flush = last_pop;
auto active = true; auto active = true;
while (active) { while (active)
try { {
active = process_next_msg(last_pop, last_flush); try
} {
catch (const std::exception &ex) { active = process_next_msg(last_pop, last_flush);
_err_handler(ex.what()); }
} catch (const std::exception &ex)
catch (...) { {
_err_handler("Unknown exception"); _err_handler(ex.what());
} }
} catch (...)
if (_worker_teardown_cb) _worker_teardown_cb(); {
_err_handler("Unknown exception");
}
}
if (_worker_teardown_cb) _worker_teardown_cb();
} }
@ -278,98 +286,105 @@ inline void spdlog::details::async_log_helper::worker_loop()
// return true if this thread should still be active (while no terminate msg was received) // 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) 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;
if (_q.dequeue(incoming_async_msg)) { if (_q.dequeue(incoming_async_msg))
last_pop = details::os::now(); {
switch (incoming_async_msg.msg_type) { last_pop = details::os::now();
case async_msg_type::flush: switch (incoming_async_msg.msg_type)
_flush_requested = true; {
break; case async_msg_type::flush:
_flush_requested = true;
break;
case async_msg_type::terminate: case async_msg_type::terminate:
_flush_requested = true; _flush_requested = true;
_terminate_requested = true; _terminate_requested = true;
break; break;
default: default:
log_msg incoming_log_msg; log_msg incoming_log_msg;
incoming_async_msg.fill_log_msg(incoming_log_msg); incoming_async_msg.fill_log_msg(incoming_log_msg);
_formatter->format(incoming_log_msg); _formatter->format(incoming_log_msg);
for (auto &s : _sinks) { for (auto &s : _sinks)
if (s->should_log(incoming_log_msg.level)) { {
s->log(incoming_log_msg); if (s->should_log(incoming_log_msg.level))
} {
} s->log(incoming_log_msg);
} }
return true; }
} }
return true;
}
// Handle empty queue.. // Handle empty queue..
// This is the only place where the queue can terminate or flush to avoid losing messages already in the queue // This is the only place where the queue can terminate or flush to avoid losing messages already in the queue
else { else
auto now = details::os::now(); {
handle_flush_interval(now, last_flush); auto now = details::os::now();
sleep_or_yield(now, last_pop); handle_flush_interval(now, last_flush);
return !_terminate_requested; sleep_or_yield(now, last_pop);
} return !_terminate_requested;
}
} }
// flush all sinks if _flush_interval_ms has expired // flush all sinks if _flush_interval_ms has expired
inline void spdlog::details::async_log_helper::handle_flush_interval(log_clock::time_point& now, log_clock::time_point& last_flush) inline void spdlog::details::async_log_helper::handle_flush_interval(log_clock::time_point& now, log_clock::time_point& last_flush)
{ {
auto should_flush = _flush_requested || (_flush_interval_ms != std::chrono::milliseconds::zero() && now - last_flush >= _flush_interval_ms); auto should_flush = _flush_requested || (_flush_interval_ms != std::chrono::milliseconds::zero() && now - last_flush >= _flush_interval_ms);
if (should_flush) { if (should_flush)
for (auto &s : _sinks) {
s->flush(); for (auto &s : _sinks)
now = last_flush = details::os::now(); s->flush();
_flush_requested = false; now = last_flush = details::os::now();
} _flush_requested = false;
}
} }
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;
} }
// spin, yield or sleep. use the time passed since last message as a hint // spin, yield or sleep. use the time passed since last message as a hint
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) 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 namespace std::this_thread; using namespace std::this_thread;
using std::chrono::milliseconds; using std::chrono::milliseconds;
using std::chrono::microseconds; using std::chrono::microseconds;
auto time_since_op = now - last_op_time; auto time_since_op = now - last_op_time;
// spin upto 50 micros // spin upto 50 micros
if (time_since_op <= microseconds(50)) if (time_since_op <= microseconds(50))
return; return;
// yield upto 150 micros // yield upto 150 micros
if (time_since_op <= microseconds(100)) if (time_since_op <= microseconds(100))
return std::this_thread::yield(); return std::this_thread::yield();
// sleep for 20 ms upto 200 ms // sleep for 20 ms upto 200 ms
if (time_since_op <= milliseconds(200)) if (time_since_op <= milliseconds(200))
return sleep_for(milliseconds(20)); return sleep_for(milliseconds(20));
// sleep for 200 ms // sleep for 200 ms
return sleep_for(milliseconds(200)); return sleep_for(milliseconds(200));
} }
// wait for the queue to be empty // wait for the queue to be empty
inline void spdlog::details::async_log_helper::wait_empty_q() inline void spdlog::details::async_log_helper::wait_empty_q()
{ {
auto last_op = details::os::now(); auto last_op = details::os::now();
while (_q.approx_size() > 0) { while (_q.approx_size() > 0)
sleep_or_yield(details::os::now(), last_op); {
} sleep_or_yield(details::os::now(), last_op);
}
} }
inline void spdlog::details::async_log_helper::set_error_handler(spdlog::log_err_handler err_handler) inline void spdlog::details::async_log_helper::set_error_handler(spdlog::log_err_handler err_handler)
{ {
_err_handler = err_handler; _err_handler = err_handler;
} }

View File

@ -60,13 +60,13 @@ inline void spdlog::async_logger::flush()
// Error handler // Error handler
inline void spdlog::async_logger::set_error_handler(spdlog::log_err_handler err_handler) inline void spdlog::async_logger::set_error_handler(spdlog::log_err_handler err_handler)
{ {
_err_handler = err_handler; _err_handler = err_handler;
_async_log_helper->set_error_handler(err_handler); _async_log_helper->set_error_handler(err_handler);
} }
inline spdlog::log_err_handler spdlog::async_logger::error_handler() inline spdlog::log_err_handler spdlog::async_logger::error_handler()
{ {
return _err_handler; return _err_handler;
} }

File diff suppressed because it is too large Load Diff

View File

@ -13,74 +13,85 @@
#include "format.h" #include "format.h"
#include <ostream> #include <ostream>
namespace fmt { namespace fmt
{
namespace internal { namespace internal
{
template <class Char> template <class Char>
class FormatBuf : public std::basic_streambuf<Char> { class FormatBuf : public std::basic_streambuf<Char>
private: {
typedef typename std::basic_streambuf<Char>::int_type int_type; private:
typedef typename std::basic_streambuf<Char>::traits_type traits_type; typedef typename std::basic_streambuf<Char>::int_type int_type;
typedef typename std::basic_streambuf<Char>::traits_type traits_type;
Buffer<Char> &buffer_; Buffer<Char> &buffer_;
Char *start_; Char *start_;
public: public:
FormatBuf(Buffer<Char> &buffer) : buffer_(buffer), start_(&buffer[0]) { FormatBuf(Buffer<Char> &buffer) : buffer_(buffer), start_(&buffer[0])
this->setp(start_, start_ + buffer_.capacity()); {
} this->setp(start_, start_ + buffer_.capacity());
int_type overflow(int_type ch = traits_type::eof()) {
if (!traits_type::eq_int_type(ch, traits_type::eof())) {
size_t buf_size = size();
buffer_.resize(buf_size);
buffer_.reserve(buf_size * 2);
start_ = &buffer_[0];
start_[buf_size] = traits_type::to_char_type(ch);
this->setp(start_+ buf_size + 1, start_ + buf_size * 2);
} }
return ch;
}
size_t size() const { int_type overflow(int_type ch = traits_type::eof())
return to_unsigned(this->pptr() - start_); {
} if (!traits_type::eq_int_type(ch, traits_type::eof()))
{
size_t buf_size = size();
buffer_.resize(buf_size);
buffer_.reserve(buf_size * 2);
start_ = &buffer_[0];
start_[buf_size] = traits_type::to_char_type(ch);
this->setp(start_+ buf_size + 1, start_ + buf_size * 2);
}
return ch;
}
size_t size() const
{
return to_unsigned(this->pptr() - start_);
}
}; };
Yes &convert(std::ostream &); Yes &convert(std::ostream &);
struct DummyStream : std::ostream { struct DummyStream : std::ostream
DummyStream(); // Suppress a bogus warning in MSVC. {
// Hide all operator<< overloads from std::ostream. DummyStream(); // Suppress a bogus warning in MSVC.
void operator<<(Null<>); // Hide all operator<< overloads from std::ostream.
void operator<<(Null<>);
}; };
No &operator<<(std::ostream &, int); No &operator<<(std::ostream &, int);
template<typename T> template<typename T>
struct ConvertToIntImpl<T, true> { struct ConvertToIntImpl<T, true>
// Convert to int only if T doesn't have an overloaded operator<<. {
enum { // Convert to int only if T doesn't have an overloaded operator<<.
value = sizeof(convert(get<DummyStream>() << get<T>())) == sizeof(No) enum
}; {
value = sizeof(convert(get<DummyStream>() << get<T>())) == sizeof(No)
};
}; };
} // namespace internal } // namespace internal
// Formats a value. // Formats a value.
template <typename Char, typename ArgFormatter, typename T> template <typename Char, typename ArgFormatter, typename T>
void format(BasicFormatter<Char, ArgFormatter> &f, void format(BasicFormatter<Char, ArgFormatter> &f,
const Char *&format_str, const T &value) { const Char *&format_str, const T &value)
internal::MemoryBuffer<Char, internal::INLINE_BUFFER_SIZE> buffer; {
internal::MemoryBuffer<Char, internal::INLINE_BUFFER_SIZE> buffer;
internal::FormatBuf<Char> format_buf(buffer); internal::FormatBuf<Char> format_buf(buffer);
std::basic_ostream<Char> output(&format_buf); std::basic_ostream<Char> output(&format_buf);
output << value; output << value;
BasicStringRef<Char> str(&buffer[0], format_buf.size()); BasicStringRef<Char> str(&buffer[0], format_buf.size());
typedef internal::MakeArg< BasicFormatter<Char> > MakeArg; typedef internal::MakeArg< BasicFormatter<Char> > MakeArg;
format_str = f.format(format_str, MakeArg(str)); format_str = f.format(format_str, MakeArg(str));
} }
/** /**

View File

@ -83,112 +83,134 @@
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) #define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
namespace fmt { namespace fmt
{
// An error code. // An error code.
class ErrorCode { class ErrorCode
private: {
int value_; private:
int value_;
public: public:
explicit ErrorCode(int value = 0) FMT_NOEXCEPT : value_(value) {} explicit ErrorCode(int value = 0) FMT_NOEXCEPT :
value_(value) {}
int get() const FMT_NOEXCEPT { return value_; } int get() const FMT_NOEXCEPT
{
return value_;
}
}; };
// A buffered file. // A buffered file.
class BufferedFile { class BufferedFile
private: {
FILE *file_; private:
FILE *file_;
friend class File; friend class File;
explicit BufferedFile(FILE *f) : file_(f) {} 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: public:
// A "move constructor" for moving from a temporary. // Constructs a BufferedFile object which doesn't represent any file.
BufferedFile(Proxy p) FMT_NOEXCEPT : file_(p.file) {} BufferedFile() FMT_NOEXCEPT :
file_(0) {}
// A "move constructor" for moving from an lvalue. // Destroys the object closing the file it represents if any.
BufferedFile(BufferedFile &f) FMT_NOEXCEPT : file_(f.file_) { ~BufferedFile() FMT_NOEXCEPT;
f.file_ = 0;
}
// A "move assignment operator" for moving from a temporary. #if !FMT_USE_RVALUE_REFERENCES
BufferedFile &operator=(Proxy p) { // Emulate a move constructor and a move assignment operator if rvalue
close(); // references are not supported.
file_ = p.file;
return *this;
}
// A "move assignment operator" for moving from an lvalue. private:
BufferedFile &operator=(BufferedFile &other) { // A proxy object to emulate a move constructor.
close(); // It is private to make it impossible call operator Proxy directly.
file_ = other.file_; struct Proxy
other.file_ = 0; {
return *this; FILE *file;
} };
// Returns a proxy object for moving from a temporary: public:
// BufferedFile file = BufferedFile(...); // A "move constructor" for moving from a temporary.
operator Proxy() FMT_NOEXCEPT { BufferedFile(Proxy p) FMT_NOEXCEPT :
Proxy p = {file_}; file_(p.file) {}
file_ = 0;
return p; // 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 #else
private: private:
FMT_DISALLOW_COPY_AND_ASSIGN(BufferedFile); FMT_DISALLOW_COPY_AND_ASSIGN(BufferedFile);
public: public:
BufferedFile(BufferedFile &&other) FMT_NOEXCEPT : file_(other.file_) { BufferedFile(BufferedFile &&other) FMT_NOEXCEPT :
other.file_ = 0; file_(other.file_)
} {
other.file_ = 0;
}
BufferedFile& operator=(BufferedFile &&other) { BufferedFile& operator=(BufferedFile &&other)
close(); {
file_ = other.file_; close();
other.file_ = 0; file_ = other.file_;
return *this; other.file_ = 0;
} return *this;
}
#endif #endif
// Opens a file. // Opens a file.
BufferedFile(CStringRef filename, CStringRef mode); BufferedFile(CStringRef filename, CStringRef mode);
// Closes the file. // Closes the file.
void close(); void close();
// Returns the pointer to a FILE object representing this file. // Returns the pointer to a FILE object representing this file.
FILE *get() const FMT_NOEXCEPT { return file_; } FILE *get() const FMT_NOEXCEPT
{
return file_;
}
// We place parentheses around fileno to workaround a bug in some versions // We place parentheses around fileno to workaround a bug in some versions
// of MinGW that define fileno as a macro. // of MinGW that define fileno as a macro.
int (fileno)() const; int (fileno)() const;
void print(CStringRef format_str, const ArgList &args) { void print(CStringRef format_str, const ArgList &args)
fmt::print(file_, format_str, args); {
} fmt::print(file_, format_str, args);
FMT_VARIADIC(void, print, CStringRef) }
FMT_VARIADIC(void, print, CStringRef)
}; };
// A file. Closed file is represented by a File object with descriptor -1. // A file. Closed file is represented by a File object with descriptor -1.
@ -197,125 +219,141 @@ public:
// closing the file multiple times will cause a crash on Windows rather // closing the file multiple times will cause a crash on Windows rather
// than an exception. You can get standard behavior by overriding the // than an exception. You can get standard behavior by overriding the
// invalid parameter handler with _set_invalid_parameter_handler. // invalid parameter handler with _set_invalid_parameter_handler.
class File { class File
private: {
int fd_; // File descriptor. private:
int fd_; // File descriptor.
// Constructs a File object with a given descriptor. // Constructs a File object with a given descriptor.
explicit File(int fd) : fd_(fd) {} explicit File(int fd) : fd_(fd) {}
public: public:
// Possible values for the oflag argument to the constructor. // Possible values for the oflag argument to the constructor.
enum { enum
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. {
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. 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. // Constructs a File object which doesn't represent any file.
File() FMT_NOEXCEPT : fd_(-1) {} File() FMT_NOEXCEPT :
fd_(-1) {}
// Opens a file and constructs a File object representing this file. // Opens a file and constructs a File object representing this file.
File(CStringRef path, int oflag); File(CStringRef path, int oflag);
#if !FMT_USE_RVALUE_REFERENCES #if !FMT_USE_RVALUE_REFERENCES
// Emulate a move constructor and a move assignment operator if rvalue // Emulate a move constructor and a move assignment operator if rvalue
// references are not supported. // references are not supported.
private: private:
// A proxy object to emulate a move constructor. // A proxy object to emulate a move constructor.
// It is private to make it impossible call operator Proxy directly. // It is private to make it impossible call operator Proxy directly.
struct Proxy { struct Proxy
int fd; {
}; int fd;
};
public: public:
// A "move constructor" for moving from a temporary. // A "move constructor" for moving from a temporary.
File(Proxy p) FMT_NOEXCEPT : fd_(p.fd) {} File(Proxy p) FMT_NOEXCEPT :
fd_(p.fd) {}
// A "move constructor" for moving from an lvalue. // A "move constructor" for moving from an lvalue.
File(File &other) FMT_NOEXCEPT : fd_(other.fd_) { File(File &other) FMT_NOEXCEPT :
other.fd_ = -1; fd_(other.fd_)
} {
other.fd_ = -1;
}
// A "move assignment operator" for moving from a temporary. // A "move assignment operator" for moving from a temporary.
File &operator=(Proxy p) { File &operator=(Proxy p)
close(); {
fd_ = p.fd; close();
return *this; fd_ = p.fd;
} return *this;
}
// A "move assignment operator" for moving from an lvalue. // A "move assignment operator" for moving from an lvalue.
File &operator=(File &other) { File &operator=(File &other)
close(); {
fd_ = other.fd_; close();
other.fd_ = -1; fd_ = other.fd_;
return *this; other.fd_ = -1;
} return *this;
}
// Returns a proxy object for moving from a temporary: // Returns a proxy object for moving from a temporary:
// File file = File(...); // File file = File(...);
operator Proxy() FMT_NOEXCEPT { operator Proxy() FMT_NOEXCEPT
Proxy p = {fd_}; {
fd_ = -1; Proxy p = {fd_};
return p; fd_ = -1;
} return p;
}
#else #else
private: private:
FMT_DISALLOW_COPY_AND_ASSIGN(File); FMT_DISALLOW_COPY_AND_ASSIGN(File);
public: public:
File(File &&other) FMT_NOEXCEPT : fd_(other.fd_) { File(File &&other) FMT_NOEXCEPT :
other.fd_ = -1; fd_(other.fd_)
} {
other.fd_ = -1;
}
File& operator=(File &&other) { File& operator=(File &&other)
close(); {
fd_ = other.fd_; close();
other.fd_ = -1; fd_ = other.fd_;
return *this; other.fd_ = -1;
} return *this;
}
#endif #endif
// Destroys the object closing the file it represents if any. // Destroys the object closing the file it represents if any.
~File() FMT_NOEXCEPT; ~File() FMT_NOEXCEPT;
// Returns the file descriptor. // Returns the file descriptor.
int descriptor() const FMT_NOEXCEPT { return fd_; } int descriptor() const FMT_NOEXCEPT
{
return fd_;
}
// Closes the file. // Closes the file.
void close(); void close();
// Returns the file size. The size has signed type for consistency with // Returns the file size. The size has signed type for consistency with
// stat::st_size. // stat::st_size.
LongLong size() const; LongLong size() const;
// Attempts to read count bytes from the file into the specified buffer. // Attempts to read count bytes from the file into the specified buffer.
std::size_t read(void *buffer, std::size_t count); std::size_t read(void *buffer, std::size_t count);
// Attempts to write count bytes from the specified buffer to the file. // Attempts to write count bytes from the specified buffer to the file.
std::size_t write(const void *buffer, std::size_t count); std::size_t write(const void *buffer, std::size_t count);
// Duplicates a file descriptor with the dup function and returns // Duplicates a file descriptor with the dup function and returns
// the duplicate as a file object. // the duplicate as a file object.
static File dup(int fd); static File dup(int fd);
// Makes fd be the copy of this file descriptor, closing fd first if // Makes fd be the copy of this file descriptor, closing fd first if
// necessary. // necessary.
void dup2(int fd); void dup2(int fd);
// Makes fd be the copy of this file descriptor, closing fd first if // Makes fd be the copy of this file descriptor, closing fd first if
// necessary. // necessary.
void dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT; void dup2(int fd, ErrorCode &ec) FMT_NOEXCEPT;
// Creates a pipe setting up read_end and write_end file objects for reading // Creates a pipe setting up read_end and write_end file objects for reading
// and writing respectively. // and writing respectively.
static void pipe(File &read_end, File &write_end); static void pipe(File &read_end, File &write_end);
// Creates a BufferedFile object associated with this file and detaches // Creates a BufferedFile object associated with this file and detaches
// this File object from the file. // this File object from the file.
BufferedFile fdopen(const char *mode); BufferedFile fdopen(const char *mode);
}; };
// Returns the memory page size. // Returns the memory page size.
@ -328,58 +366,77 @@ long getpagesize();
#ifdef FMT_LOCALE #ifdef FMT_LOCALE
// A "C" numeric locale. // A "C" numeric locale.
class Locale { class Locale
private: {
private:
# ifdef _MSC_VER # ifdef _MSC_VER
typedef _locale_t locale_t; typedef _locale_t locale_t;
enum { LC_NUMERIC_MASK = LC_NUMERIC }; enum { LC_NUMERIC_MASK = LC_NUMERIC };
static locale_t newlocale(int category_mask, const char *locale, locale_t) { static locale_t newlocale(int category_mask, const char *locale, locale_t)
return _create_locale(category_mask, locale); {
} return _create_locale(category_mask, locale);
}
static void freelocale(locale_t locale) { static void freelocale(locale_t locale)
_free_locale(locale); {
} _free_locale(locale);
}
static double strtod_l(const char *nptr, char **endptr, _locale_t locale) { static double strtod_l(const char *nptr, char **endptr, _locale_t locale)
return _strtod_l(nptr, endptr, locale); {
} return _strtod_l(nptr, endptr, locale);
}
# endif # endif
locale_t locale_; locale_t locale_;
FMT_DISALLOW_COPY_AND_ASSIGN(Locale); FMT_DISALLOW_COPY_AND_ASSIGN(Locale);
public: public:
typedef locale_t Type; typedef locale_t Type;
Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", NULL)) { Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", NULL))
if (!locale_) {
FMT_THROW(fmt::SystemError(errno, "cannot create locale")); if (!locale_)
} FMT_THROW(fmt::SystemError(errno, "cannot create locale"));
~Locale() { freelocale(locale_); } }
~Locale()
{
freelocale(locale_);
}
Type get() const { return locale_; } Type get() const
{
return locale_;
}
// Converts string to floating-point number and advances str past the end // Converts string to floating-point number and advances str past the end
// of the parsed input. // of the parsed input.
double strtod(const char *&str) const { double strtod(const char *&str) const
char *end = 0; {
double result = strtod_l(str, &end, locale_); char *end = 0;
str = end; double result = strtod_l(str, &end, locale_);
return result; str = end;
} return result;
}
}; };
#endif // FMT_LOCALE #endif // FMT_LOCALE
} // namespace fmt } // namespace fmt
#if !FMT_USE_RVALUE_REFERENCES #if !FMT_USE_RVALUE_REFERENCES
namespace std { namespace std
{
// For compatibility with C++98. // For compatibility with C++98.
inline fmt::BufferedFile &move(fmt::BufferedFile &f) { return f; } inline fmt::BufferedFile &move(fmt::BufferedFile &f)
inline fmt::File &move(fmt::File &f) { return f; } {
return f;
}
inline fmt::File &move(fmt::File &f)
{
return f;
}
} }
#endif #endif

View File

@ -13,40 +13,45 @@
#include "format.h" #include "format.h"
#include <ctime> #include <ctime>
namespace fmt { namespace fmt
{
template <typename ArgFormatter> template <typename ArgFormatter>
void format(BasicFormatter<char, ArgFormatter> &f, void format(BasicFormatter<char, ArgFormatter> &f,
const char *&format_str, const std::tm &tm) { const char *&format_str, const std::tm &tm)
if (*format_str == ':') {
++format_str; if (*format_str == ':')
const char *end = format_str; ++format_str;
while (*end && *end != '}') const char *end = format_str;
++end; while (*end && *end != '}')
if (*end != '}') ++end;
FMT_THROW(FormatError("missing '}' in format string")); if (*end != '}')
internal::MemoryBuffer<char, internal::INLINE_BUFFER_SIZE> format; FMT_THROW(FormatError("missing '}' in format string"));
format.append(format_str, end + 1); internal::MemoryBuffer<char, internal::INLINE_BUFFER_SIZE> format;
format[format.size() - 1] = '\0'; format.append(format_str, end + 1);
Buffer<char> &buffer = f.writer().buffer(); format[format.size() - 1] = '\0';
std::size_t start = buffer.size(); Buffer<char> &buffer = f.writer().buffer();
for (;;) { std::size_t start = buffer.size();
std::size_t size = buffer.capacity() - start; for (;;)
std::size_t count = std::strftime(&buffer[start], size, &format[0], &tm); {
if (count != 0) { std::size_t size = buffer.capacity() - start;
buffer.resize(start + count); std::size_t count = std::strftime(&buffer[start], size, &format[0], &tm);
break; 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));
} }
if (size >= format.size() * 256) { format_str = end + 1;
// 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;
} }
} }

View File

@ -58,7 +58,7 @@ public:
const std::string& name() const; const std::string& name() const;
void set_pattern(const std::string&); void set_pattern(const std::string&);
void set_formatter(formatter_ptr); void set_formatter(formatter_ptr);
// automatically call flush() if message level >= log_level // automatically call flush() if message level >= log_level
void flush_on(level::level_enum log_level); void flush_on(level::level_enum log_level);
@ -66,9 +66,9 @@ public:
const std::vector<sink_ptr>& sinks() const; const std::vector<sink_ptr>& sinks() const;
// error handler // error handler
virtual void set_error_handler(log_err_handler); virtual void set_error_handler(log_err_handler);
virtual log_err_handler error_handler(); virtual log_err_handler error_handler();
protected: protected:
virtual void _sink_it(details::log_msg&); virtual void _sink_it(details::log_msg&);

View File

@ -8,30 +8,30 @@
class failing_sink: public spdlog::sinks::sink class failing_sink: public spdlog::sinks::sink
{ {
void log(const spdlog::details::log_msg& msg) override void log(const spdlog::details::log_msg& msg) override
{ {
throw std::runtime_error("some error happened during log"); throw std::runtime_error("some error happened during log");
} }
void flush() void flush()
{} {}
}; };
TEST_CASE("default_error_handler", "[errors]]") TEST_CASE("default_error_handler", "[errors]]")
{ {
prepare_logdir(); prepare_logdir();
std::string filename = "logs/simple_log.txt"; std::string filename = "logs/simple_log.txt";
auto logger = spdlog::create<spdlog::sinks::simple_file_sink_mt>("logger", filename, true); auto logger = spdlog::create<spdlog::sinks::simple_file_sink_mt>("logger", filename, true);
logger->set_pattern("%v"); logger->set_pattern("%v");
logger->info("Test message {} {}", 1); logger->info("Test message {} {}", 1);
logger->info("Test message {}", 2); logger->info("Test message {}", 2);
logger->flush(); logger->flush();
REQUIRE(file_contents(filename) == std::string("Test message 2\n")); REQUIRE(file_contents(filename) == std::string("Test message 2\n"));
REQUIRE(count_lines(filename) == 1); REQUIRE(count_lines(filename) == 1);
} }
@ -41,69 +41,73 @@ struct custom_ex
{}; {};
TEST_CASE("custom_error_handler", "[errors]]") TEST_CASE("custom_error_handler", "[errors]]")
{ {
prepare_logdir(); prepare_logdir();
std::string filename = "logs/simple_log.txt"; std::string filename = "logs/simple_log.txt";
auto logger = spdlog::create<spdlog::sinks::simple_file_sink_mt>("logger", filename, true); auto logger = spdlog::create<spdlog::sinks::simple_file_sink_mt>("logger", filename, true);
logger->flush_on(spdlog::level::info); logger->flush_on(spdlog::level::info);
logger->set_error_handler([=](const std::string& msg) { logger->set_error_handler([=](const std::string& msg)
throw custom_ex(); {
}); throw custom_ex();
logger->info("Good message #1"); });
REQUIRE_THROWS_AS(logger->info("Bad format msg {} {}", "xxx"), custom_ex); logger->info("Good message #1");
logger->info("Good message #2"); REQUIRE_THROWS_AS(logger->info("Bad format msg {} {}", "xxx"), custom_ex);
REQUIRE(count_lines(filename) == 2); logger->info("Good message #2");
REQUIRE(count_lines(filename) == 2);
} }
TEST_CASE("default_error_handler2", "[errors]]") TEST_CASE("default_error_handler2", "[errors]]")
{ {
auto logger = spdlog::create<failing_sink>("failed_logger"); auto logger = spdlog::create<failing_sink>("failed_logger");
logger->set_error_handler([=](const std::string& msg) { logger->set_error_handler([=](const std::string& msg)
throw custom_ex(); {
}); throw custom_ex();
REQUIRE_THROWS_AS(logger->info("Some message"), custom_ex); });
REQUIRE_THROWS_AS(logger->info("Some message"), custom_ex);
} }
TEST_CASE("async_error_handler", "[errors]]") TEST_CASE("async_error_handler", "[errors]]")
{ {
prepare_logdir(); prepare_logdir();
std::string err_msg("log failed with some msg"); std::string err_msg("log failed with some msg");
spdlog::set_async_mode(128); spdlog::set_async_mode(128);
std::string filename = "logs/simple_async_log.txt"; std::string filename = "logs/simple_async_log.txt";
{ {
auto logger = spdlog::create<spdlog::sinks::simple_file_sink_mt>("logger", filename, true); auto logger = spdlog::create<spdlog::sinks::simple_file_sink_mt>("logger", filename, true);
logger->set_error_handler([=](const std::string& msg) { logger->set_error_handler([=](const std::string& msg)
std::ofstream ofs("logs/custom_err.txt"); {
if (!ofs) throw std::runtime_error("Failed open logs/custom_err.txt"); std::ofstream ofs("logs/custom_err.txt");
ofs << err_msg; if (!ofs) throw std::runtime_error("Failed open logs/custom_err.txt");
}); ofs << err_msg;
logger->info("Good message #1"); });
logger->info("Bad format msg {} {}", "xxx"); logger->info("Good message #1");
logger->info("Good message #2"); logger->info("Bad format msg {} {}", "xxx");
spdlog::drop("logger"); //force logger to drain the queue and shutdown logger->info("Good message #2");
spdlog::set_sync_mode(); spdlog::drop("logger"); //force logger to drain the queue and shutdown
} spdlog::set_sync_mode();
REQUIRE(count_lines(filename) == 2); }
REQUIRE(file_contents("logs/custom_err.txt") == err_msg); REQUIRE(count_lines(filename) == 2);
REQUIRE(file_contents("logs/custom_err.txt") == err_msg);
} }
// Make sure async error handler is executed // Make sure async error handler is executed
TEST_CASE("async_error_handler2", "[errors]]") TEST_CASE("async_error_handler2", "[errors]]")
{ {
prepare_logdir(); prepare_logdir();
std::string err_msg("This is async handler error message"); std::string err_msg("This is async handler error message");
spdlog::set_async_mode(128); spdlog::set_async_mode(128);
{ {
auto logger = spdlog::create<failing_sink>("failed_logger"); auto logger = spdlog::create<failing_sink>("failed_logger");
logger->set_error_handler([=](const std::string& msg) { 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"); std::ofstream ofs("logs/custom_err2.txt");
ofs << err_msg; 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 logger->info("Hello failure");
spdlog::set_sync_mode(); 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); REQUIRE(file_contents("logs/custom_err2.txt") == err_msg);
} }

View File

@ -3,15 +3,15 @@
void prepare_logdir() void prepare_logdir()
{ {
spdlog::drop_all(); spdlog::drop_all();
#ifdef _WIN32 #ifdef _WIN32
system("if not exist logs mkdir logs"); system("if not exist logs mkdir logs");
system("del /F /Q logs\\*"); system("del /F /Q logs\\*");
#else #else
auto rv = system("mkdir -p logs"); auto rv = system("mkdir -p logs");
rv = system("rm -f logs/*"); rv = system("rm -f logs/*");
(void)rv; (void)rv;
#endif #endif
} }