diff options
author | gabi <galim120@bezeqint.net> | 2014-10-31 02:13:27 +0300 |
---|---|---|
committer | gabi <galim120@bezeqint.net> | 2014-10-31 02:13:27 +0300 |
commit | c7b8c762fbeb7db011aea264254066c01eab5ab3 (patch) | |
tree | c7ba62b8403995f10ee9e9a0eb4b0df64dc6d728 /include/spdlog | |
parent | cbddc8796a7ff5ceb903d27fe61ccd95d1166fcc (diff) |
spdlog
Diffstat (limited to 'include/spdlog')
24 files changed, 2473 insertions, 0 deletions
diff --git a/include/spdlog/common.h b/include/spdlog/common.h new file mode 100644 index 00000000..dd0dbe64 --- /dev/null +++ b/include/spdlog/common.h @@ -0,0 +1,56 @@ +#pragma once + +#include<initializer_list> +#include<chrono> + +namespace spdlog +{ +class formatter; +namespace sinks { +class sink; +} + +// Common types across the lib +using log_clock = std::chrono::system_clock; +using sink_ptr = std::shared_ptr < sinks::sink > ; +using sinks_init_list = std::initializer_list < sink_ptr > ; +using formatter_ptr = std::shared_ptr<spdlog::formatter>; + +//Log level enum +namespace level +{ +typedef enum +{ + TRACE, + DEBUG, + INFO, + WARN, + ERR, + CRITICAL, + ALWAYS, + OFF +} level_enum; + +static const char* level_names[] { "trace", "debug", "info", "warning", "error", "critical", "", ""}; +inline const char* to_str(spdlog::level::level_enum l) +{ + return level_names[l]; +} +} //level + +// +// Log exception +// +class fflog_exception : public std::exception +{ +public: + fflog_exception(const std::string& msg) :_msg(msg) {}; + const char* what() const throw() override { + return _msg.c_str(); + } +private: + std::string _msg; + +}; + +} //spdlog diff --git a/include/spdlog/details/blocking_queue.h b/include/spdlog/details/blocking_queue.h new file mode 100644 index 00000000..574a5530 --- /dev/null +++ b/include/spdlog/details/blocking_queue.h @@ -0,0 +1,126 @@ +#pragma once + +// blocking_queue: +// A blocking multi-consumer/multi-producer thread safe queue. +// Has max capacity and supports timeout on push or pop operations. + +#include <chrono> +#include <memory> +#include <queue> +#include <mutex> +#include <condition_variable> + +namespace spdlog +{ +namespace details +{ + +template<typename T> +class blocking_queue +{ +public: + using queue_type = std::queue<T>; + using item_type = T; + using size_type = typename queue_type::size_type; + using clock = std::chrono::system_clock; + + explicit blocking_queue(size_type max_size) : + _max_size(max_size), + _q(), + _mutex() + { + } + blocking_queue(const blocking_queue&) = delete; + blocking_queue& operator=(const blocking_queue&) = delete; + ~blocking_queue() = default; + + size_type size() + { + std::lock_guard<std::mutex> lock(_mutex); + return _q.size(); + } + + // Push copy of item into the back of the queue. + // If the queue is full, block the calling thread util there is room or timeout have passed. + // Return: false on timeout, true on successful push. + template<typename Duration_Rep, typename Duration_Period, typename TT> + bool push(TT&& item, const std::chrono::duration<Duration_Rep, Duration_Period>& timeout) + { + std::unique_lock<std::mutex> ul(_mutex); + if (_q.size() >= _max_size) + { + if (!_item_popped_cond.wait_until(ul, clock::now() + timeout, [this]() + { + return this->_q.size() < this->_max_size; + })) + return false; + } + _q.push(std::forward<TT>(item)); + if (_q.size() <= 1) + { + ul.unlock(); //So the notified thread will have better chance to accuire the lock immediatly.. + _item_pushed_cond.notify_one(); + } + return true; + } + + // Push copy of item into the back of the queue. + // If the queue is full, block the calling thread until there is room. + template<typename TT> + void push(TT&& item) + { + while (!push(std::forward<TT>(item), std::chrono::hours(1))); + } + + // Pop a copy of the front item in the queue into the given item ref. + // If the queue is empty, block the calling thread util there is item to pop or timeout have passed. + // Return: false on timeout , true on successful pop/ + template<class Duration_Rep, class Duration_Period> + bool pop(T& item, const std::chrono::duration<Duration_Rep, Duration_Period>& timeout) + { + std::unique_lock<std::mutex> ul(_mutex); + if (_q.empty()) + { + if (!_item_pushed_cond.wait_until(ul, clock::now() + timeout, [this]() + { + return !this->_q.empty(); + })) + return false; + } + item = std::move(_q.front()); + _q.pop(); + if (_q.size() >= _max_size - 1) + { + ul.unlock(); //So the notified thread will have better chance to accuire the lock immediatly.. + _item_popped_cond.notify_one(); + } + return true; + } + + // Pop a copy of the front item in the queue into the given item ref. + // If the queue is empty, block the calling thread util there is item to pop. + void pop(T& item) + { + while (!pop(item, std::chrono::hours(1))); + } + + // Clear the queue + void clear() + { + { + std::unique_lock<std::mutex> ul(_mutex); + queue_type().swap(_q); + } + _item_popped_cond.notify_all(); + } + +private: + size_type _max_size; + std::queue<T> _q; + std::mutex _mutex; + std::condition_variable _item_pushed_cond; + std::condition_variable _item_popped_cond; +}; + +} +} diff --git a/include/spdlog/details/fast_istostr.h b/include/spdlog/details/fast_istostr.h new file mode 100644 index 00000000..3855d57a --- /dev/null +++ b/include/spdlog/details/fast_istostr.h @@ -0,0 +1,103 @@ +#pragma once +#include <string> + +//Fast to int to string +//Source: http://stackoverflow.com/a/4351484/192001 +//Modified version to pad zeros according to padding arg + +namespace spdlog { +namespace details { + +const char digit_pairs[201] = { + "00010203040506070809" + "10111213141516171819" + "20212223242526272829" + "30313233343536373839" + "40414243444546474849" + "50515253545556575859" + "60616263646566676869" + "70717273747576777879" + "80818283848586878889" + "90919293949596979899" +}; + + +inline std::string& fast_itostr(int n, std::string& s, int padding) +{ + if (n == 0) + { + s = std::string(padding, '0'); + return s; + } + + int sign = -(n < 0); + unsigned int val = (n^sign) - sign; + + int size; + if (val >= 10000) + { + if (val >= 10000000) + { + if (val >= 1000000000) + size = 10; + else if (val >= 100000000) + size = 9; + else + size = 8; + } + else + { + if (val >= 1000000) + size = 7; + else if (val >= 100000) + size = 6; + else + size = 5; + } + } + else + { + if (val >= 100) + { + if (val >= 1000) + size = 4; + else + size = 3; + } + else + { + if (val >= 10) + size = 2; + else + size = 1; + } + } + size -= sign; + if (size < padding) + size = padding; + + s.resize(size); + char* c = &s[0]; + if (sign) + *c = '-'; + + c += size - 1; + while (val >= 100) + { + int pos = val % 100; + val /= 100; + *(short*)(c - 1) = *(short*)(digit_pairs + 2 * pos); + c -= 2; + } + while (val > 0) + { + *c-- = '0' + (val % 10); + val /= 10; + } + + while (c >= s.data()) + *c-- = '0'; + return s; +} +} +}
\ No newline at end of file diff --git a/include/spdlog/details/fast_oss.h b/include/spdlog/details/fast_oss.h new file mode 100644 index 00000000..3a9a07ed --- /dev/null +++ b/include/spdlog/details/fast_oss.h @@ -0,0 +1,165 @@ +#pragma once + +// A faster-than-ostringstream class +// uses stack_buf as the underlying buffer (upto 192 bytes before using the heap) + +#include <ostream> +#include <iomanip> +#include "fast_istostr.h" +#include "stack_buf.h" +#include<iostream> + +namespace spdlog +{ +namespace details +{ + +class stack_devicebuf :public std::streambuf +{ +public: + static const unsigned short stack_size = 256; + using stackbuf_t = stack_buf<stack_size>; + + stack_devicebuf() = default; + ~stack_devicebuf() = default; + + stack_devicebuf(const stack_devicebuf& other) :std::basic_streambuf<char>(), _stackbuf(other._stackbuf) + {} + + stack_devicebuf(stack_devicebuf&& other): + std::basic_streambuf<char>(), + _stackbuf(std::move(other._stackbuf)) + { + other.clear(); + } + + stack_devicebuf& operator=(stack_devicebuf other) + { + std::swap(_stackbuf, other._stackbuf); + return *this; + } + + const stackbuf_t& buf() const + { + return _stackbuf; + } + std::size_t size() const + { + return _stackbuf.size(); + } + + void clear() + { + _stackbuf.clear(); + } + +protected: + // copy the give buffer into the accumulated fast buffer + std::streamsize xsputn(const char_type* s, std::streamsize count) override + { + _stackbuf.append(s, static_cast<unsigned int>(count)); + return count; + } + + int_type overflow(int_type ch) override + { + if (traits_type::not_eof(ch)) + { + char c = traits_type::to_char_type(ch); + xsputn(&c, 1); + } + return ch; + } +private: + stackbuf_t _stackbuf; +}; + + +class fast_oss :public std::ostream +{ +public: + fast_oss() :std::ostream(&_dev) {} + ~fast_oss() = default; + + fast_oss(const fast_oss& other) :std::basic_ios<char>(), std::ostream(&_dev), _dev(other._dev) + {} + + fast_oss(fast_oss&& other) :std::basic_ios<char>(), std::ostream(&_dev), _dev(std::move(other._dev)) + { + other.clear(); + } + + + fast_oss& operator=(fast_oss other) + { + swap(*this, other); + return *this; + } + + void swap(fast_oss& first, fast_oss& second) // nothrow + { + using std::swap; + swap(first._dev, second._dev); + } + + std::string str() + { + auto& buffer = _dev.buf(); + const char*data = buffer.data(); + return std::string(data, data+buffer.size()); + } + + const stack_devicebuf::stackbuf_t& buf() const + { + return _dev.buf(); + } + + + std::size_t size() const + { + return _dev.size(); + } + + void clear() + { + _dev.clear(); + } + + // + // The following were added because they significantly boost to perfromance + // + void putc(char c) + { + _dev.sputc(c); + } + + // put int and pad with zeroes if smalled than min_width + void put_int(int n, int padding) + { + std::string s; + details::fast_itostr(n, s, padding); + _dev.sputn(s.data(), s.size()); + } + + void put_data(const char* p, std::size_t data_size) + { + _dev.sputn(p, data_size); + } + + void put_str(const std::string& s) + { + _dev.sputn(s.data(), s.size()); + } + + void put_fast_oss(const fast_oss& oss) + { + auto& buffer = oss.buf(); + _dev.sputn(buffer.data(), buffer.size()); + } + + +private: + stack_devicebuf _dev; +}; +} +} diff --git a/include/spdlog/details/file_helper.h b/include/spdlog/details/file_helper.h new file mode 100644 index 00000000..fd5ee4a0 --- /dev/null +++ b/include/spdlog/details/file_helper.h @@ -0,0 +1,111 @@ +#pragma once + + + +// Helper class for file sink +// When failing to open a file, retry several times(5) with small delay between the tries(10 ms) +// Flush to file every X writes (or never if X==0) +// Throw fflog_ exception on errors + + +#include <cstdio> +#include <string> +#include <thread> +#include <chrono> +#include "../common.h" + + + +namespace spdlog +{ +namespace details +{ + +class file_helper +{ +public: + static const int open_max_tries = 5; + static const int sleep_ms_bewteen_tries = 10; + + explicit file_helper(const std::size_t flush_inverval): + _fd(nullptr), + _flush_inverval(flush_inverval), + _flush_countdown(flush_inverval) {}; + + file_helper(const file_helper&) = delete; + + ~file_helper() + { + close(); + } + + + void open(const std::string& filename) + { + + close(); + + _filename = filename; + for (int tries = 0; tries < open_max_tries; ++tries) + { + if(!os::fopen_s(&_fd, filename, "wb")) + return; + + std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms_bewteen_tries)); + } + + throw fflog_exception("Failed opening file " + filename + " for writing"); + } + + void close() + { + if (_fd) + { + std::fclose(_fd); + _fd = nullptr; + } + } + + void write(const log_msg& msg) + { + auto& buf = msg.formatted.buf(); + size_t size = buf.size(); + if(std::fwrite(buf.data(), sizeof(char), size, _fd) != size) + throw fflog_exception("Failed writing to file " + _filename); + + if(--_flush_countdown == 0) + { + std::fflush(_fd); + _flush_countdown = _flush_inverval; + } + } + + const std::string& filename() const + { + return _filename; + } + + static bool file_exists(const std::string& name) + { + FILE* file; + if (!os::fopen_s(&file, name.c_str(), "r")) + { + fclose(file); + return true; + } + else + { + return false; + } + } + +private: + FILE* _fd; + std::string _filename; + const std::size_t _flush_inverval; + std::size_t _flush_countdown; + +}; +} +} + diff --git a/include/spdlog/details/line_logger.h b/include/spdlog/details/line_logger.h new file mode 100644 index 00000000..23b3b0cc --- /dev/null +++ b/include/spdlog/details/line_logger.h @@ -0,0 +1,79 @@ +#pragma once + +#include "../common.h" +#include "../logger.h" +#include "./fast_oss.h" + + +// Line logger class - aggregates operator<< calls to fast ostream +// and logs upon destruction + +namespace spdlog +{ +namespace details +{ +class line_logger +{ +public: + line_logger(logger* callback_logger, level::level_enum msg_level, bool enabled): + _callback_logger(callback_logger), + _log_msg(msg_level), + _enabled(enabled) + {} + + // No copy intended. Only move + line_logger(const line_logger& other) = delete; + line_logger& operator=(const line_logger&) = delete; + line_logger& operator=(line_logger&&) = delete; + + + line_logger(line_logger&& other) : + _callback_logger(other._callback_logger), + _log_msg(std::move(other._log_msg)), + _enabled(other._enabled) + { + other.disable(); + } + + //Log the log message using the callback logger + ~line_logger() + { + if (_enabled) + { + _log_msg.logger_name = _callback_logger->name(); + _log_msg.time = log_clock::now(); + _log_msg.tm_time = details::os::localtime(log_clock::to_time_t(_log_msg.time)); + _callback_logger->_log_msg(_log_msg); + } + } + + template<typename T> + void write(const T& what) + { + if (_enabled) + { + _log_msg.raw << what; + } + } + + template<typename T> + line_logger& operator<<(const T& what) + { + write(what); + return *this; + } + + void disable() + { + _enabled = false; + } + + + +private: + logger* _callback_logger; + log_msg _log_msg; + bool _enabled; +}; +} //Namespace details +} // Namespace spdlog diff --git a/include/spdlog/details/log_msg.h b/include/spdlog/details/log_msg.h new file mode 100644 index 00000000..2ee38dc5 --- /dev/null +++ b/include/spdlog/details/log_msg.h @@ -0,0 +1,69 @@ +#pragma once + +#include "../common.h" +#include "./fast_oss.h" + +namespace spdlog +{ +namespace details +{ +struct log_msg +{ + log_msg() = default; + log_msg(level::level_enum l): + logger_name(), + level(l), + time(), + tm_time(), + raw(), + formatted() {} + + log_msg(const log_msg& other): + logger_name(other.logger_name), + level(other.level), + time(other.time), + tm_time(other.tm_time), + raw(other.raw), + formatted(other.formatted) {} + + log_msg(log_msg&& other) + { + swap(*this, other); + } + + void swap(log_msg& l, log_msg& r) + { + using std::swap; + swap(l.logger_name, r.logger_name); + swap(l.level, r.level); + swap(l.time, r.time); + swap(l.tm_time, r.tm_time); + swap(l.raw, r.raw); + swap(l.formatted, r.formatted); + } + + + log_msg& operator=(log_msg other) + { + swap(*this, other); + return *this; + } + + + void clear() + { + raw.clear(); + formatted.clear(); + } + + std::string logger_name; + level::level_enum level; + log_clock::time_point time; + std::tm tm_time; + fast_oss raw; + fast_oss formatted; + + +}; +} +} diff --git a/include/spdlog/details/logger_impl.h b/include/spdlog/details/logger_impl.h new file mode 100644 index 00000000..32900093 --- /dev/null +++ b/include/spdlog/details/logger_impl.h @@ -0,0 +1,136 @@ +#pragma once +// +// Logger implementation +// + + +#include "./line_logger.h" + + +inline spdlog::logger::logger(const std::string& logger_name, sinks_init_list sinks_list) : + _name(logger_name), + _sinks(sinks_list) +{ + // no support under vs2013 for member initialization for std::atomic + _level = level::INFO; +} + +template<class It> +inline spdlog::logger::logger(const std::string& logger_name, const It& begin, const It& end) : + _name(logger_name), + _sinks(begin, end) +{} + + +inline void spdlog::logger::set_formatter(spdlog::formatter_ptr msg_formatter) +{ + _formatter = msg_formatter; +} + +inline void spdlog::logger::set_pattern(const std::string& pattern) +{ + _formatter = std::make_shared<pattern_formatter>(pattern); +} + +inline spdlog::formatter_ptr spdlog::logger::get_formatter() const +{ + return _formatter; +} + + +template <typename... Args> +inline spdlog::details::line_logger spdlog::logger::log(level::level_enum lvl, const Args&... args) { + bool msg_enabled = should_log(lvl); + details::line_logger l(this, lvl, msg_enabled); + if (msg_enabled) + _variadic_log(l, args...); + return l; +} + +template <typename... Args> +inline spdlog::details::line_logger spdlog::logger::log(const Args&... args) { + return log(level::ALWAYS, args...); +} + +template <typename... Args> +inline spdlog::details::line_logger spdlog::logger::trace(const Args&... args) +{ + return log(level::TRACE, args...); +} + +template <typename... Args> +inline spdlog::details::line_logger spdlog::logger::debug(const Args&... args) +{ + return log(level::DEBUG, args...); +} + +template <typename... Args> +inline spdlog::details::line_logger spdlog::logger::info(const Args&... args) +{ + return log(level::INFO, args...); +} + +template <typename... Args> +inline spdlog::details::line_logger spdlog::logger::warn(const Args&... args) +{ + return log(level::WARN, args...); +} + +template <typename... Args> +inline spdlog::details::line_logger spdlog::logger::error(const Args&... args) +{ + return log(level::ERR, args...); +} + +template <typename... Args> +inline spdlog::details::line_logger spdlog::logger::critical(const Args&... args) +{ + return log(level::CRITICAL, args...); +} + +inline const std::string& spdlog::logger::name() const +{ + return _name; +} + +inline void spdlog::logger::set_level(spdlog::level::level_enum log_level) +{ + _level.store(log_level); +} + +inline spdlog::level::level_enum spdlog::logger::level() const +{ + return static_cast<spdlog::level::level_enum>(_level.load()); +} + +inline bool spdlog::logger::should_log(spdlog::level::level_enum msg_level) const +{ + return msg_level >= _level.load(); +} + +inline void spdlog::logger::stop_logging() +{ + set_level(level::OFF); +} + + +inline void spdlog::logger::_variadic_log(spdlog::details::line_logger&) {} + +template <typename First, typename... Rest> +void spdlog::logger::_variadic_log(spdlog::details::line_logger& l, const First& first, const Rest&... rest) +{ + l.write(first); + l.write(' '); + _variadic_log(l, rest...); +} + +inline void spdlog::logger::_log_msg(details::log_msg& msg) +{ + //Use default formatter if not set + if (!_formatter) + _formatter = std::make_shared<pattern_formatter>("%+"); + _formatter->format(msg); + for (auto &sink : _sinks) + sink->log(msg); +} + diff --git a/include/spdlog/details/null_mutex.h b/include/spdlog/details/null_mutex.h new file mode 100644 index 00000000..54c354c5 --- /dev/null +++ b/include/spdlog/details/null_mutex.h @@ -0,0 +1,17 @@ +#pragma once + +// null, no cost mutex + +namespace spdlog { +namespace details { +struct null_mutex +{ + void lock() {} + void unlock() {} + bool try_lock() + { + return true; + } +}; +} +} diff --git a/include/spdlog/details/os.h b/include/spdlog/details/os.h new file mode 100644 index 00000000..ebea0547 --- /dev/null +++ b/include/spdlog/details/os.h @@ -0,0 +1,129 @@ +#pragma once + +#include<string> +#include<cstdio> +#include<ctime> +#ifdef _WIN32 +#include <Windows.h> +#endif + +namespace spdlog +{ +namespace details +{ +namespace os +{ + +inline std::tm localtime(const std::time_t &time_tt) +{ + +#ifdef _WIN32 + std::tm tm; + localtime_s(&tm, &time_tt); +#else + std::tm tm; + localtime_r(&time_tt, &tm); +#endif + return tm; +} + +inline std::tm localtime() +{ + std::time_t now_t = time(0); + return localtime(now_t); +} + + +inline std::tm gmtime(const std::time_t &time_tt) +{ + +#ifdef _WIN32 + std::tm tm; + gmtime_s(&tm, &time_tt); +#else + std::tm tm; + gmtime_r(&time_tt, &tm); +#endif + return tm; +} + +inline std::tm gmtime() +{ + std::time_t now_t = time(0); + return gmtime(now_t); +} +inline bool operator==(const std::tm& tm1, const std::tm& tm2) +{ + return (tm1.tm_sec == tm2.tm_sec && + tm1.tm_min == tm2.tm_min && + tm1.tm_hour == tm2.tm_hour && + tm1.tm_mday == tm2.tm_mday && + tm1.tm_mon == tm2.tm_mon && + tm1.tm_year == tm2.tm_year && + tm1.tm_isdst == tm2.tm_isdst); +} + +inline bool operator!=(const std::tm& tm1, const std::tm& tm2) +{ + return !(tm1 == tm2); +} + +#ifdef _WIN32 +inline const char* eol() +{ + return "\r\n"; +} +#else +constexpr inline const char* eol() +{ + return "\n"; +} +#endif + +#ifdef _WIN32 +inline unsigned short eol_size() +{ + return 2; +} +#else +constexpr inline unsigned short eol_size() +{ + return 1; +} +#endif + +//fopen_s on non windows for writing +inline int fopen_s(FILE** fp, const std::string& filename, const char* mode) +{ +#ifdef _WIN32 + return ::fopen_s(fp, filename.c_str(), mode); +#else + *fp = fopen((filename.c_str()), mode); + return *fp == nullptr; +#endif + + +} + +//Return utc offset in minutes or -1 on failure +inline int utc_minutes_offset(const std::tm& tm = localtime()) +{ + +#ifdef _WIN32 + DYNAMIC_TIME_ZONE_INFORMATION tzinfo; + auto rv = GetDynamicTimeZoneInformation(&tzinfo); + if (!rv) + return -1; + return -1 * (tzinfo.Bias + tzinfo.DaylightBias); +#else + return tm.tm_gmtoff / 60; +#endif +} + + +} //os +} //details +} //spdlog + + + diff --git a/include/spdlog/details/pattern_formatter_impl.h b/include/spdlog/details/pattern_formatter_impl.h new file mode 100644 index 00000000..949ecbfe --- /dev/null +++ b/include/spdlog/details/pattern_formatter_impl.h @@ -0,0 +1,534 @@ +#pragma once + +#include <string> +#include <chrono> +#include <memory> +#include <vector> + +#include "../formatter.h" +#include "./log_msg.h" +#include "./fast_oss.h" +#include "./os.h" + +namespace spdlog +{ +namespace details { +class flag_formatter +{ +public: + virtual void format(details::log_msg& msg) = 0; +}; + +/////////////////////////////////////////////////////////////////////// +// name & level pattern appenders +/////////////////////////////////////////////////////////////////////// +namespace { +class name_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted << msg.logger_name; + } +}; +} + +// log level appender +class level_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted << level::to_str(msg.level); + } +}; + +/////////////////////////////////////////////////////////////////////// +// Date time pattern appenders +/////////////////////////////////////////////////////////////////////// + +static const char* ampm(const tm& t) +{ + return t.tm_hour >= 12 ? "PM" : "AM"; +} + +static int to12h(const tm& t) +{ + return t.tm_hour > 12 ? t.tm_hour - 12 : t.tm_hour; +} + +//Abbreviated weekday name +static const std::string days[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; +class a_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_str(days[msg.tm_time.tm_wday]); + } +}; + +//Full weekday name +static const std::string full_days[] { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; +class A_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_str(full_days[msg.tm_time.tm_wday]); + } +}; + +//Abbreviated month +static const std::string months[] { "Jan", "Feb", "Mar", "Apr", "May", "June", "July", "Aug", "Sept", "Oct", "Nov", "Dec" }; +class b_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_str(months[msg.tm_time.tm_mon]); + } +}; + +//Full month name +static const std::string full_months[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; +class B_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_str(full_months[msg.tm_time.tm_mon]); + } +}; + +//Date and time representation (Thu Aug 23 15:35:46 2014) +class c_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_str(days[msg.tm_time.tm_wday]); + msg.formatted.putc(' '); + msg.formatted.put_str(months[msg.tm_time.tm_mon]); + msg.formatted.putc(' '); + msg.formatted.put_int(msg.tm_time.tm_mday, 2); + msg.formatted.putc(' '); + msg.formatted.put_int(msg.tm_time.tm_hour, 2); + msg.formatted.putc(':'); + msg.formatted.put_int(msg.tm_time.tm_min, 2); + msg.formatted.putc(':'); + msg.formatted.put_int(msg.tm_time.tm_sec, 2); + } +}; + + +// year - 2 digit +class C_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_int(msg.tm_time.tm_year % 100, 2); + } +}; + + + +// Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01 +class D_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_int(msg.tm_time.tm_mon + 1, 2); + msg.formatted.putc('/'); + msg.formatted.put_int(msg.tm_time.tm_mday, 2); + msg.formatted.putc('/'); + msg.formatted.put_int(msg.tm_time.tm_year % 100, 2); + } +}; + + +// year - 4 digit +class Y_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_int(msg.tm_time.tm_year + 1900, 4); + } +}; + +// month 1-12 +class m_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_int(msg.tm_time.tm_mon + 1, 2); + } +}; + +// day of month 1-31 +class d_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_int(msg.tm_time.tm_mday, 2); + } +}; + +// hours in 24 format 0-23 +class H_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_int(msg.tm_time.tm_hour, 2); + } +}; + +// hours in 12 format 1-12 +class I_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_int(to12h(msg.tm_time), 2); + } +}; + +// ninutes 0-59 +class M_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_int(msg.tm_time.tm_min, 2); + } +}; + +// seconds 0-59 +class S_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_int(msg.tm_time.tm_sec, 2); + } +}; + +// milliseconds +class e_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + auto duration = msg.time.time_since_epoch(); + auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() % 1000; + msg.formatted.put_int(static_cast<int>(millis), 3); + } +}; + +// AM/PM +class p_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_data(ampm(msg.tm_time), 2); + } +}; + + +// 12 hour clock 02:55:02 pm +class r_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_int(to12h(msg.tm_time), 2); + msg.formatted.putc(':'); + msg.formatted.put_int(msg.tm_time.tm_min, 2); + msg.formatted.putc(':'); + msg.formatted.put_int(msg.tm_time.tm_sec, 2); + msg.formatted.putc(' '); + msg.formatted.put_data(ampm(msg.tm_time), 2); + } +}; + +// 24-hour HH:MM time, equivalent to %H:%M +class R_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_int(msg.tm_time.tm_hour, 2); + msg.formatted.putc(':'); + msg.formatted.put_int(msg.tm_time.tm_min, 2); + + } +}; + +// ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S +class T_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_int(msg.tm_time.tm_hour, 2); + msg.formatted.putc(':'); + msg.formatted.put_int(msg.tm_time.tm_min, 2); + msg.formatted.putc(':'); + msg.formatted.put_int(msg.tm_time.tm_sec, 2); + } +}; + +// ISO 8601 offset from UTC in timezone (HH:MM) +class z_formatter :public flag_formatter +{ +public: + + void format(log_msg& msg) override + { + std::lock_guard<std::mutex> l(_mutex); + using namespace std::chrono; + auto diff = msg.time - _last_update; + auto secs_diff = abs((duration_cast<seconds>(diff)).count()); + if (secs_diff >= 2) + { + _value = get_value(msg); + _last_update = msg.time; + } + msg.formatted.put_str(_value); + } +private: + log_clock::time_point _last_update; + std::string _value; + std::string get_value(const log_msg& msg) + { + int total_minutes = os::utc_minutes_offset(msg.tm_time); + int h = total_minutes / 60; + int m = total_minutes % 60; + fast_oss oss; + oss.putc(h < 0 ? '-' : '+'); + oss.put_int(h, 2); + oss.putc(':'); + oss.put_int(m, 2); + return oss.str(); + } + std::mutex _mutex; +}; + + + +class t_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.put_fast_oss(msg.raw); + } +}; + +class ch_formatter :public flag_formatter +{ +public: + explicit ch_formatter(char ch) : _ch(ch) + {} + void format(details::log_msg& msg) override + { + msg.formatted.putc(_ch); + } +private: + char _ch; +}; + + +//aggregate user chars to display as is +class aggregate_formatter :public flag_formatter +{ +public: + aggregate_formatter() + {} + void add_ch(char ch) + { + _str += ch; + } + void format(details::log_msg& msg) override + { + msg.formatted.put_str(_str); + } +private: + std::string _str; +}; + +// Full info formatter +// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %t +class full_formatter :public flag_formatter +{ + void format(details::log_msg& msg) override + { + msg.formatted.putc('['); + msg.formatted.put_int(msg.tm_time.tm_year+1900, 4); + msg.formatted.putc('-'); + msg.formatted.put_int(msg.tm_time.tm_mon+ 1, 2); + msg.formatted.putc('-'); + msg.formatted.put_int(msg.tm_time.tm_mday, 2); + msg.formatted.putc(' '); + msg.formatted.put_int(msg.tm_time.tm_hour, 2); + msg.formatted.putc(':'); + msg.formatted.put_int(msg.tm_time.tm_min, 2); + msg.formatted.putc(':'); + msg.formatted.put_int(msg.tm_time.tm_sec, 2); + //millis + msg.formatted.putc('.'); + auto duration = msg.time.time_since_epoch(); + auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(duration).count() % 1000; + msg.formatted.put_int(static_cast<int>(millis), 3); + msg.formatted.putc(']'); + msg.formatted << " [" << msg.logger_name << "] [" << level::to_str(msg.level) << "] "; + msg.formatted.put_fast_oss(msg.raw); + + } +}; + +} +} +/////////////////////////////////////////////////////////////////////////////// +// pattern_formatter inline impl +/////////////////////////////////////////////////////////////////////////////// +inline spdlog::pattern_formatter::pattern_formatter(const std::string& pattern) +{ + compile_pattern(pattern); +} + +inline void spdlog::pattern_formatter::compile_pattern(const std::string& pattern) +{ + auto end = pattern.end(); + std::unique_ptr<details::aggregate_formatter> user_chars; + for (auto it = pattern.begin(); it != end; ++it) + { + if (*it == '%') + { + if (user_chars) //append user chars found so far + _formatters.push_back(std::move(user_chars)); + + if (++it != end) + handle_flag(*it); + else + break; + } + else // chars not following the % sign should be displayed as is + { + if (!user_chars) + user_chars = std::unique_ptr<details::aggregate_formatter>(new details::aggregate_formatter()); + user_chars->add_ch(*it); + } + } + if (user_chars) //append raw chars found so far + { + _formatters.push_back(std::move(user_chars)); + } + +} +inline void spdlog::pattern_formatter::handle_flag(char flag) +{ + switch (flag) + { + // logger name + case 'n': + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::name_formatter())); + break; + + case 'l': + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::level_formatter())); + break; + + case('t') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::t_formatter())); + break; + + case('a') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::a_formatter())); + break; + + case('A') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::A_formatter())); + break; + + case('b') : + case('h') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::b_formatter())); + break; + + case('B') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::B_formatter())); + break; + case('c') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::c_formatter())); + break; + + case('C') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::C_formatter())); + break; + + case('Y') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::Y_formatter())); + break; + + case('D') : + case('x') : + + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::D_formatter())); + break; + + case('m') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::m_formatter())); + break; + + case('d') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::d_formatter())); + break; + + case('H') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::H_formatter())); + break; + + case('I') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::I_formatter())); + break; + + case('M') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::M_formatter())); + break; + + case('S') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::S_formatter())); + break; + + case('e') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::e_formatter())); + break; + + case('p') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::p_formatter())); + break; + + case('r') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::r_formatter())); + break; + + case('R') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::R_formatter())); + break; + + case('T') : + case('X') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::T_formatter())); + break; + + case('z') : + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::z_formatter())); + break; + + case ('+'): + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::full_formatter())); + break; + + default: //Unkown flag appears as is + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::ch_formatter('%'))); + _formatters.push_back(std::unique_ptr<details::flag_formatter>(new details::ch_formatter(flag))); + break; + } +} + + +inline void spdlog::pattern_formatter::format(details::log_msg& msg) +{ + for (auto &f : _formatters) + { + f->format(msg); + } + //write eol + msg.formatted.write(details::os::eol(), details::os::eol_size()); +} diff --git a/include/spdlog/details/registry.h b/include/spdlog/details/registry.h new file mode 100644 index 00000000..e75a17ec --- /dev/null +++ b/include/spdlog/details/registry.h @@ -0,0 +1,99 @@ +#pragma once +// Loggers registy of unique name->logger pointer +// If 2 loggers with same name are added, the second will be overrun the first +// If user requests a non existing logger, nullptr will be returned +// This class is thread safe + +#include <string> +#include <mutex> +#include <unordered_map> + +#include "../logger.h" +#include "../common.h" + +namespace spdlog { +namespace details { + +class registry { +public: + std::shared_ptr<logger> get(const std::string& name) + { + std::lock_guard<std::mutex> lock(_mutex); + auto found = _loggers.find(name); + return found == _loggers.end() ? nullptr : found->second; + } + + template<class It> + std::shared_ptr<logger> create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end) + { + std::lock_guard<std::mutex> lock(_mutex); + auto new_logger = std::make_shared<logger>(logger_name, sinks_begin, sinks_end); + new_logger->set_formatter(_formatter); + new_logger->set_level(_level); + _loggers[logger_name] = new_logger; + return new_logger; + } + + + std::shared_ptr<logger> create(const std::string& logger_name, sinks_init_list sinks) + { + return create(logger_name, sinks.begin(), sinks.end()); + } + + std::shared_ptr<logger> create(const std::string& logger_name, sink_ptr sink) + { + return create(logger_name, { sink }); + } + + + void formatter(formatter_ptr f) + { + std::lock_guard<std::mutex> lock(_mutex); + _formatter = f; + for (auto& l : _loggers) + l.second->set_formatter(_formatter); + } + + + void set_pattern(const std::string& pattern) + { + std::lock_guard<std::mutex> lock(_mutex); + _formatter = std::make_shared<pattern_formatter>(pattern); + for (auto& l : _loggers) + l.second->set_formatter(_formatter); + + } + + void set_level(level::level_enum log_level) + { + std::lock_guard<std::mutex> lock(_mutex); + for (auto& l : _loggers) + l.second->set_level(log_level); + + } + + void stop_all() + { + std::lock_guard<std::mutex> lock(_mutex); + _level = level::OFF; + for (auto& l : _loggers) + l.second->stop_logging(); + } + + + static registry& instance() + { + static registry s_instance; + return s_instance; + } + +private: + registry() = default; + registry(const registry&) = delete; + std::mutex _mutex; + std::unordered_map <std::string, std::shared_ptr<logger>> _loggers; + formatter_ptr _formatter; + level::level_enum _level = level::INFO; +}; +} +} diff --git a/include/spdlog/details/spdlog_impl.h b/include/spdlog/details/spdlog_impl.h new file mode 100644 index 00000000..05c1149a --- /dev/null +++ b/include/spdlog/details/spdlog_impl.h @@ -0,0 +1,51 @@ +#pragma once + +// +// Global registry functions +// +#include "registry.h" + +inline std::shared_ptr<spdlog::logger> spdlog::get(const std::string& name) +{ + return details::registry::instance().get(name); +} + +inline std::shared_ptr<spdlog::logger> spdlog::create(const std::string& logger_name, spdlog::sinks_init_list sinks) +{ + return details::registry::instance().create(logger_name, sinks); +} + + +template <typename Sink, typename... Args> +inline std::shared_ptr<spdlog::logger> spdlog::create(const std::string& logger_name, const Args&... args) +{ + sink_ptr sink = std::make_shared<Sink>(args...); + return details::registry::instance().create(logger_name, { sink }); +} + + +template<class It> +inline std::shared_ptr<spdlog::logger> spdlog::create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end) +{ + return details::registry::instance().create(logger_name, sinks_begin, sinks_end); +} + +inline void spdlog::set_formatter(spdlog::formatter_ptr f) +{ + details::registry::instance().formatter(f); +} + +inline void spdlog::set_pattern(const std::string& format_string) +{ + return details::registry::instance().set_pattern(format_string); +} + +inline void spdlog::set_level(level::level_enum log_level) +{ + return details::registry::instance().set_level(log_level); +} + +inline void spdlog::stop() +{ + return details::registry::instance().stop_all(); +} diff --git a/include/spdlog/details/stack_buf.h b/include/spdlog/details/stack_buf.h new file mode 100644 index 00000000..d8d2a2aa --- /dev/null +++ b/include/spdlog/details/stack_buf.h @@ -0,0 +1,111 @@ +#pragma once + +#include <algorithm> +#include <array> +#include <vector> +#include <cstring> + + +// Fast memory storage on the stack when possible or in std::vector +namespace spdlog +{ +namespace details +{ + +template<unsigned short STACK_SIZE> +class stack_buf +{ +public: + static const unsigned short stack_size = STACK_SIZE; + stack_buf() :_v(), _stack_size(0) {} + ~stack_buf() = default; + stack_buf(const stack_buf& other):stack_buf(other, delegate_copy_move {}) + {} + + stack_buf(stack_buf&& other):stack_buf(other, delegate_copy_move {}) + { + other.clear(); + } + template<class T1> + stack_buf& operator=(T1&& other) + { + _stack_size = other._stack_size; + if (other.vector_used()) + _v = std::forward<T1>(other)._v; + else + std::copy_n(other._stack_array.begin(), other._stack_size, _stack_array.begin()); + return *this; + } + + void append(const char* buf, std::size_t buf_size) + { + //If we are aleady using _v, forget about the stack + if (vector_used()) + { + _v.insert(_v.end(), buf, buf + buf_size); + } + //Try use the stack + else + { + if (_stack_size + buf_size <= STACK_SIZE) + { + std::memcpy(&_stack_array[_stack_size], buf, buf_size); + _stack_size += buf_size; + } + //Not enough stack space. Copy all to _v + else + { + _v.reserve(_stack_size + buf_size); + _v.insert(_v.end(), _stack_array.begin(), _stack_array.begin() + _stack_size); + _v.insert(_v.end(), buf, buf + buf_size); + } + } + } + + + void clear() + { + _stack_size = 0; + _v.clear(); + } + + const char* data() const + { + if (vector_used()) + return _v.data(); + else + return _stack_array.data(); + } + + std::size_t size() const + { + if (vector_used()) + return _v.size(); + else + return _stack_size; + } + +private: + struct delegate_copy_move {}; + template<class T1> + stack_buf(T1&& other, delegate_copy_move) + { + _stack_size = other._stack_size; + if (other.vector_used()) + _v = std::forward<T1>(other)._v; + else + std::copy_n(other._stack_array.begin(), other._stack_size, _stack_array.begin()); + } + + inline bool vector_used() const + { + return !(_v.empty()); + } + + std::vector<char> _v; + std::array<char, STACK_SIZE> _stack_array; + std::size_t _stack_size; +}; + +} +} //namespace spdlog { namespace details { diff --git a/include/spdlog/formatter.h b/include/spdlog/formatter.h new file mode 100644 index 00000000..8575c073 --- /dev/null +++ b/include/spdlog/formatter.h @@ -0,0 +1,33 @@ +#pragma once + +#include "details/log_msg.h" +namespace spdlog +{ +namespace details { +class flag_formatter; +} + +class formatter +{ +public: + virtual ~formatter() {} + virtual void format(details::log_msg& msg) = 0; +}; + +class pattern_formatter : public formatter +{ + +public: + explicit pattern_formatter(const std::string& pattern); + pattern_formatter(const pattern_formatter&) = delete; + void format(details::log_msg& msg) override; +private: + const std::string _pattern; + std::vector<std::unique_ptr<details::flag_formatter>> _formatters; + void handle_flag(char flag); + void compile_pattern(const std::string& pattern); +}; +} + +#include "details/pattern_formatter_impl.h" + diff --git a/include/spdlog/logger.h b/include/spdlog/logger.h new file mode 100644 index 00000000..a4008dc1 --- /dev/null +++ b/include/spdlog/logger.h @@ -0,0 +1,91 @@ +#pragma once + +// Thread safe logger +// Has name, log level, vector of std::shared sink pointers and formatter +// Upon each log write the logger: +// 1. Checks if its log level is enough to log the message +// 2. Format the message using the formatter function +// 3. Pass the formatted message to its sinks to performa the actual logging + +#include<vector> +#include<memory> +#include<atomic> +#include <sstream> +#include <exception> +#include "sinks/base_sink.h" +#include "common.h" + +namespace spdlog +{ + +namespace details +{ +class line_logger; +} + +class logger +{ +public: + + logger(const std::string& name, sinks_init_list); + template<class It> + logger(const std::string& name, const It& begin, const It& end); + + void set_pattern(const std::string&); + void set_formatter(formatter_ptr); + formatter_ptr get_formatter() const; + + + logger(const logger&) = delete; + logger& operator=(const logger&) = delete; + + void set_level(level::level_enum); + level::level_enum level() const; + + const std::string& name() const; + bool should_log(level::level_enum) const; + + void stop_logging(); + + template <typename... Args> details::line_logger log(level::level_enum lvl, const Args&... args); + template <typename... Args> details::line_logger log(const Args&... args); + template <typename... Args> details::line_logger trace(const Args&... args); + template <typename... Args> details::line_logger debug(const Args&... args); + template <typename... Args> details::line_logger info(const Args&... args); + template <typename... Args> details::line_logger warn(const Args&... args); + template <typename... Args> details::line_logger error(const Args&... args); + template <typename... Args> details::line_logger critical(const Args&... args); + + +private: + friend details::line_logger; + std::string _name; + formatter_ptr _formatter; + std::vector<sink_ptr> _sinks; + std::atomic_int _level; + void _variadic_log(details::line_logger& l); + template <typename First, typename... Rest> + void _variadic_log(details::line_logger&l, const First& first, const Rest&... rest); + void _log_msg(details::log_msg& msg); +}; + + + +} + +// +// Trace & debug macros +// +#ifdef FFLOG_ENABLE_TRACE +#define FFLOG_TRACE(logger, ...) logger->log(spdlog::level::TRACE, __FILE__, " #", __LINE__,": " __VA_ARGS__) +#else +#define FFLOG_TRACE(logger, ...) {} +#endif + +#ifdef FFLOG_ENABLE_DEBUG +#define FFLOG_DEBUG(logger, ...) logger->log(spdlog::level::DEBUG, __VA_ARGS__) +#else +#define FFLOG_DEBUG(logger, ...) {} +#endif + +#include "./details/logger_impl.h" diff --git a/include/spdlog/sinks/async_sink.h b/include/spdlog/sinks/async_sink.h new file mode 100644 index 00000000..35a8b6e0 --- /dev/null +++ b/include/spdlog/sinks/async_sink.h @@ -0,0 +1,137 @@ +#pragma once + +#include <thread> +#include <chrono> +#include <atomic> +#include <algorithm> + +#include "./base_sink.h" +#include "../logger.h" +#include "../details/blocking_queue.h" +#include "../details/null_mutex.h" +#include "../details/log_msg.h" + +#include<iostream> + +namespace spdlog +{ +namespace sinks +{ + +class async_sink : public base_sink<details::null_mutex> +{ +public: + using q_type = details::blocking_queue<details::log_msg>; + + explicit async_sink(const q_type::size_type max_queue_size); + + //Stop logging and join the back thread + ~async_sink(); + void add_sink(sink_ptr sink); + void remove_sink(sink_ptr sink_ptr); + q_type& q(); + //Wait to remaining items (if any) in the queue to be written and shutdown + void shutdown(const std::chrono::milliseconds& timeout); + + +protected: + void _sink_it(const details::log_msg& msg) override; + void _thread_loop(); + +private: + std::vector<std::shared_ptr<sink>> _sinks; + std::atomic<bool> _active; + q_type _q; + std::thread _back_thread; + //Clear all remaining messages(if any), stop the _back_thread and join it + void _shutdown(); + std::mutex _mutex; +}; +} +} + +/////////////////////////////////////////////////////////////////////////////// +// async_sink class implementation +/////////////////////////////////////////////////////////////////////////////// +inline spdlog::sinks::async_sink::async_sink(const q_type::size_type max_queue_size) + :_sinks(), + _active(true), + _q(max_queue_size), + _back_thread(&async_sink::_thread_loop, this) +{} + +inline spdlog::sinks::async_sink::~async_sink() +{ + _shutdown(); +} + +inline void spdlog::sinks::async_sink::_sink_it(const details::log_msg& msg) +{ + if(!_active) + return; + _q.push(msg); +} + +inline void spdlog::sinks::async_sink::_thread_loop() +{ + static std::chrono::seconds pop_timeout { 1 }; + while (_active) + { + q_type::item_type msg; + if (_q.pop(msg, pop_timeout)) + { + for (auto &s : _sinks) + { + s->log(msg); + if(!_active) + break; + } + } + } +} + +inline void spdlog::sinks::async_sink::add_sink(spdlog::sink_ptr s) +{ + std::lock_guard<std::mutex> guard(_mutex); + _sinks.push_back(s); +} + + +inline void spdlog::sinks::async_sink::remove_sink(spdlog::sink_ptr s) +{ + std::lock_guard<std::mutex> guard(_mutex); + _sinks.erase(std::remove(_sinks.begin(), _sinks.end(), s), _sinks.end()); +} + + +inline spdlog::sinks::async_sink::q_type& spdlog::sinks::async_sink::q() +{ + return _q; +} + + +inline void spdlog::sinks::async_sink::shutdown(const std::chrono::milliseconds& timeout) +{ + if(timeout > std::chrono::milliseconds::zero()) + { + auto until = log_clock::now() + timeout; + while (_q.size() > 0 && log_clock::now() < until) + { + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + } + } + _shutdown(); +} + + +inline void spdlog::sinks::async_sink::_shutdown() +{ + std::lock_guard<std::mutex> guard(_mutex); + if(_active) + { + _active = false; + if (_back_thread.joinable()) + _back_thread.join(); + } +} + diff --git a/include/spdlog/sinks/base_sink.h b/include/spdlog/sinks/base_sink.h new file mode 100644 index 00000000..5e91e4f5 --- /dev/null +++ b/include/spdlog/sinks/base_sink.h @@ -0,0 +1,43 @@ +#pragma once +// +// base sink templated over a mutex (either dummy or realy) +// concrete implementation should only overrid the _sink_it method. +// all locking is taken care of here so no locking needed by the implementors.. +// + +#include<string> +#include<mutex> +#include<atomic> +#include "./sink.h" +#include "../formatter.h" +#include "../common.h" +#include "../details/log_msg.h" + + +namespace spdlog +{ +namespace sinks +{ +template<class Mutex> +class base_sink:public sink +{ +public: + base_sink():_mutex() {} + virtual ~base_sink() = default; + + base_sink(const base_sink&) = delete; + base_sink& operator=(const base_sink&) = delete; + + void log(const details::log_msg& msg) override + { + std::lock_guard<Mutex> lock(_mutex); + _sink_it(msg); + }; + + +protected: + virtual void _sink_it(const details::log_msg& msg) = 0; + Mutex _mutex; +}; +} +} diff --git a/include/spdlog/sinks/file_sinks.h b/include/spdlog/sinks/file_sinks.h new file mode 100644 index 00000000..ae35d834 --- /dev/null +++ b/include/spdlog/sinks/file_sinks.h @@ -0,0 +1,189 @@ +#pragma once + + +#include <mutex> +#include "./base_sink.h" + +#include "../details/null_mutex.h" +#include "../details/file_helper.h" +#include "../details/fast_oss.h" + + + +namespace spdlog +{ +namespace sinks +{ + +/* +* Trivial file sink with single file as target +*/ +template<class Mutex> +class simple_file_sink : public base_sink<Mutex> +{ +public: + explicit simple_file_sink(const std::string &filename, + const std::size_t flush_inverval=0): + _file_helper(flush_inverval) + { + _file_helper.open(filename); + } +protected: + void _sink_it(const details::log_msg& msg) override + { + _file_helper.write(msg); + } +private: + details::file_helper _file_helper; +}; + +typedef simple_file_sink<std::mutex> simple_file_sink_mt; +typedef simple_file_sink<details::null_mutex> simple_file_sink_st; + +/* + * Rotating file sink based on size +*/ +template<class Mutex> +class rotating_file_sink : public base_sink<Mutex> +{ +public: + rotating_file_sink(const std::string &base_filename, const std::string &extension, + const std::size_t max_size, const std::size_t max_files, + const std::size_t flush_inverval=0): + _base_filename(base_filename), + _extension(extension), + _max_size(max_size), + _max_files(max_files), + _current_size(0), + _file_helper(flush_inverval) + { + _file_helper.open(calc_filename(_base_filename, 0, _extension)); + } + +protected: + void _sink_it(const details::log_msg& msg) override + { + _current_size += msg.formatted.size(); + if (_current_size > _max_size) + { + _rotate(); + _current_size = msg.formatted.size(); + } + _file_helper.write(msg); + } + + +private: + static std::string calc_filename(const std::string& filename, std::size_t index, const std::string& extension) + { + details::fast_oss oss; + if (index) + oss << filename << "." << index << "." << extension; + else + oss << filename << "." << extension; + return oss.str(); + } + + + // Rotate files: + // log.txt -> log.1.txt + // log.1.txt -> log2.txt + // log.2.txt -> log3.txt + // log.3.txt -> delete + + + void _rotate() + { + _file_helper.close(); + for (auto i = _max_files; i > 0; --i) + { + std::string src = calc_filename(_base_filename, i - 1, _extension); + std::string target = calc_filename(_base_filename, i, _extension); + + if (details::file_helper::file_exists(target)) + std::remove(target.c_str()); + if (details::file_helper::file_exists(src) && std::rename(src.c_str(), target.c_str())) + { + throw fflog_exception("rotating_file_sink: failed renaming " + src + " to " + target); + } + } + auto cur_name = _file_helper.filename(); + std::remove(cur_name.c_str()); + _file_helper.open(cur_name); + } + std::string _base_filename; + std::string _extension; + std::size_t _max_size; + std::size_t _max_files; + std::size_t _current_size; + details::file_helper _file_helper; +}; + +typedef rotating_file_sink<std::mutex> rotating_file_sink_mt; +typedef rotating_file_sink<details::null_mutex>rotating_file_sink_st; + +/* + * Rotating file sink based on date. rotates at midnight + */ +template<class Mutex> +class daily_file_sink:public base_sink<Mutex> +{ +public: + explicit daily_file_sink(const std::string& base_filename, + const std::string& extension, + const std::size_t flush_inverval=0): + _base_filename(base_filename), + _extension(extension), + _midnight_tp (_calc_midnight_tp() ), + _file_helper(flush_inverval) + { + _file_helper.open(calc_filename(_base_filename, _extension)); + } + +protected: + void _sink_it(const details::log_msg& msg) override + { + if (std::chrono::system_clock::now() >= _midnight_tp) + { + _file_helper.close(); + _file_helper.open(calc_filename(_base_filename, _extension)); + _midnight_tp = _calc_midnight_tp(); + } + _file_helper.write(msg); + } + +private: + // Return next midnight's time_point + static std::chrono::system_clock::time_point _calc_midnight_tp() + { + using namespace std::chrono; + auto now = system_clock::now(); + time_t tnow = std::chrono::system_clock::to_time_t(now); + tm date = spdlog::details::os::localtime(tnow); + date.tm_hour = date.tm_min = date.tm_sec = 0; + auto midnight = std::chrono::system_clock::from_time_t(std::mktime(&date)); + return system_clock::time_point(midnight + hours(24)); + } + + //Create filename for the form basename.YYYY-MM-DD.extension + static std::string calc_filename(const std::string& basename, const std::string& extension) + { + std::tm tm = spdlog::details::os::localtime(); + details::fast_oss oss; + oss << basename << '.'; + oss << tm.tm_year + 1900 << '-' << std::setw(2) << std::setfill('0') << tm.tm_mon + 1 << '-' << tm.tm_mday; + oss << '.' << extension; + return oss.str(); + } + + std::string _base_filename; + std::string _extension; + std::chrono::system_clock::time_point _midnight_tp; + details::file_helper _file_helper; + +}; + +typedef daily_file_sink<std::mutex> daily_file_sink_mt; +typedef daily_file_sink<details::null_mutex> daily_file_sink_st; +} +} diff --git a/include/spdlog/sinks/null_sink.h b/include/spdlog/sinks/null_sink.h new file mode 100644 index 00000000..776425e6 --- /dev/null +++ b/include/spdlog/sinks/null_sink.h @@ -0,0 +1,23 @@ +#pragma once +#include <mutex> +#include "./base_sink.h" +#include "../details/null_mutex.h" + + +namespace spdlog { +namespace sinks { + +template <class Mutex> +class null_sink : public base_sink<Mutex> +{ +protected: + void _sink_it(const details::log_msg&) override + {} +}; + +typedef null_sink<details::null_mutex> null_sink_st; +typedef null_sink<std::mutex> null_sink_mt; + +} +} + diff --git a/include/spdlog/sinks/ostream_sink.h b/include/spdlog/sinks/ostream_sink.h new file mode 100644 index 00000000..5fa62683 --- /dev/null +++ b/include/spdlog/sinks/ostream_sink.h @@ -0,0 +1,36 @@ +#pragma once + +#include <iostream> +#include <mutex> +#include <memory> + +#include "../details/null_mutex.h" +#include "./base_sink.h" + +namespace spdlog +{ +namespace sinks +{ +template<class Mutex> +class ostream_sink: public base_sink<Mutex> +{ +public: + explicit ostream_sink(std::ostream& os) :_ostream(os) {} + ostream_sink(const ostream_sink&) = delete; + ostream_sink& operator=(const ostream_sink&) = delete; + virtual ~ostream_sink() = default; + + +protected: + virtual void _sink_it(const details::log_msg& msg) override + { + auto& buf = msg.formatted.buf(); + _ostream.write(buf.data(), buf.size()); + } + std::ostream& _ostream; +}; + +typedef ostream_sink<std::mutex> ostream_sink_mt; +typedef ostream_sink<details::null_mutex> ostream_sink_st; +} +} diff --git a/include/spdlog/sinks/sink.h b/include/spdlog/sinks/sink.h new file mode 100644 index 00000000..9d4a2cc5 --- /dev/null +++ b/include/spdlog/sinks/sink.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../details/log_msg.h" + +namespace spdlog +{ +namespace sinks +{ +class sink +{ +public: + virtual ~sink() {} + virtual void log(const details::log_msg& msg) = 0; +}; +} +} + diff --git a/include/spdlog/sinks/stdout_sinks.h b/include/spdlog/sinks/stdout_sinks.h new file mode 100644 index 00000000..19b0f908 --- /dev/null +++ b/include/spdlog/sinks/stdout_sinks.h @@ -0,0 +1,34 @@ +#pragma once + +#include <iostream> +#include <mutex> +#include "./ostream_sink.h" +#include "../details/null_mutex.h" + +namespace spdlog +{ +namespace sinks +{ + +template <class Mutex> +class stdout_sink : public ostream_sink<Mutex> +{ +public: + stdout_sink() : ostream_sink<Mutex>(std::cout) {} +}; + +typedef stdout_sink<details::null_mutex> stdout_sink_st; +typedef stdout_sink<std::mutex> stdout_sink_mt; + + +template <class Mutex> +class stderr_sink : public ostream_sink<Mutex> +{ +public: + stderr_sink() : ostream_sink<Mutex>(std::cerr) {} +}; + +typedef stderr_sink<std::mutex> stderr_sink_mt; +typedef stderr_sink<details::null_mutex> stderr_sink_st; +} +}
\ No newline at end of file diff --git a/include/spdlog/spdlog.h b/include/spdlog/spdlog.h new file mode 100644 index 00000000..44d13485 --- /dev/null +++ b/include/spdlog/spdlog.h @@ -0,0 +1,84 @@ +// +// This is spdlog - an extremely fast and easy to use c++11 logging library +// + +// example code (create multi threaded daily logger): +// auto my_logger = spdlog::create<daily_file_sink_st>("mylog", "dailylog_filename", "txt"); +// .. +// auto mylog = spdlog::get("mylog"); +// mylog->info("Hello logger.", "This is message number", 1, "!!") ; +// mylog->info() << "std streams are also supprted: " << std::hex << 255; + +// see example.cpp for more examples + +#pragma once + +#include "logger.h" +#include "details/registry.h" + +namespace spdlog +{ + +// Return an existing logger or nullptr if a logger with such name doesn't exist. +// Examples: +// +// spdlog::get("mylog")->info("Hello"); +// auto logger = spdlog::get("mylog"); +// logger.info("This is another message" , x, y, z); +// logger.info() << "This is another message" << x << y << z; +std::shared_ptr<logger> get(const std::string& name); + + +// Example: +// auto logger = spdlog::create("mylog", {sink1, sink2}); +std::shared_ptr<logger> create(const std::string& logger_name, sinks_init_list sinks); + + +// Example (create a logger with daily rotating file): +// using namespace spdlog::sinks; +// spdlog::create<daily_file_sink_st>("mylog", "dailylog_filename", "txt"); +template <typename Sink, typename... Args> +std::shared_ptr<spdlog::logger> create(const std::string& logger_name, const Args&... args); + +// Example: +// using namespace spdlog::sinks; +// std::vector<spdlog::sink_ptr> mySinks; +// mySinks.push_back(std::make_shared<rotating_file_sink_mt>("filename", "txt", 1024 * 1024 * 5, 10)); +// mySinks.push_back(std::make_shared<stdout_sink_mt>()); +// spdlog::create("mylog", mySinks.begin(), mySinks.end()); +template<class It> +std::shared_ptr<logger> create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end); + + +// Set global formatting +// Example: +// spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e %l : %t"); +void set_pattern(const std::string& format_string); + + +// Set global formatter object +void set_formatter(formatter_ptr f); + + +//Set global active logging level +void set_level(level::level_enum log_level); + + +//Stop all loggers +void stop(); + + +// +// Trace macro to turn on/off at compile time +// Example: SPDLOG_TRACE(my_logger, "Some trace message"); +// +#ifdef _DEBUG +#define SPDLOG_TRACE(logger, ...) logger->log(__FILE__, " #", __LINE__,": " __VA_ARGS__) +#else +#define SPDLOG_TRACE(logger, ...) {} +#endif + + +} + +#include "details/spdlog_impl.h"
\ No newline at end of file |