mirror of
https://github.com/gabime/spdlog.git
synced 2025-09-30 02:19:35 +08:00
1. renamed lib to to spitlog
2. Rotating bugfix
This commit is contained in:
55
include/spitlog/common.h
Normal file
55
include/spitlog/common.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include<initializer_list>
|
||||
#include<chrono>
|
||||
|
||||
namespace spitlog
|
||||
{
|
||||
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<spitlog::formatter>;
|
||||
|
||||
//Log level enum
|
||||
namespace level
|
||||
{
|
||||
typedef enum
|
||||
{
|
||||
TRACE,
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARN,
|
||||
ERR,
|
||||
CRITICAL,
|
||||
NONE = 99
|
||||
} level_enum;
|
||||
|
||||
static const char* level_names[] { "trace", "debug", "info", "warning", "error", "critical", "fatal" };
|
||||
inline const char* to_str(spitlog::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;
|
||||
|
||||
};
|
||||
|
||||
} //spitlog
|
126
include/spitlog/details/blocking_queue.h
Normal file
126
include/spitlog/details/blocking_queue.h
Normal file
@@ -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 spitlog
|
||||
{
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
103
include/spitlog/details/fast_istostr.h
Normal file
103
include/spitlog/details/fast_istostr.h
Normal file
@@ -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 spitlog {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
165
include/spitlog/details/fast_oss.h
Normal file
165
include/spitlog/details/fast_oss.h
Normal file
@@ -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 spitlog
|
||||
{
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
110
include/spitlog/details/file_helper.h
Normal file
110
include/spitlog/details/file_helper.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#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 spitlog
|
||||
{
|
||||
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()
|
||||
{
|
||||
if (_fd)
|
||||
std::fclose(_fd);
|
||||
}
|
||||
|
||||
|
||||
void open(const std::string& filename)
|
||||
{
|
||||
|
||||
if (_fd)
|
||||
std::fclose(_fd);
|
||||
|
||||
_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()
|
||||
{
|
||||
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;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
|
79
include/spitlog/details/line_logger.h
Normal file
79
include/spitlog/details/line_logger.h
Normal file
@@ -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 spitlog
|
||||
{
|
||||
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 spitlog
|
69
include/spitlog/details/log_msg.h
Normal file
69
include/spitlog/details/log_msg.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include "../common.h"
|
||||
#include "./fast_oss.h"
|
||||
|
||||
namespace spitlog
|
||||
{
|
||||
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;
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
}
|
163
include/spitlog/details/logger_impl.h
Normal file
163
include/spitlog/details/logger_impl.h
Normal file
@@ -0,0 +1,163 @@
|
||||
#pragma once
|
||||
//
|
||||
// Logger implementation
|
||||
//
|
||||
|
||||
|
||||
#include "./line_logger.h"
|
||||
|
||||
|
||||
inline spitlog::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 spitlog::logger::logger(const std::string& logger_name, const It& begin, const It& end) :
|
||||
_name(logger_name),
|
||||
_sinks(begin, end)
|
||||
{}
|
||||
|
||||
|
||||
inline void spitlog::logger::set_formatter(spitlog::formatter_ptr msg_formatter)
|
||||
{
|
||||
_formatter = msg_formatter;
|
||||
}
|
||||
|
||||
inline void spitlog::logger::set_format(const std::string& format)
|
||||
{
|
||||
_formatter = std::make_shared<pattern_formatter>(format);
|
||||
}
|
||||
|
||||
inline spitlog::formatter_ptr spitlog::logger::get_formatter() const
|
||||
{
|
||||
return _formatter;
|
||||
}
|
||||
|
||||
|
||||
template <typename... Args>
|
||||
inline spitlog::details::line_logger spitlog::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 spitlog::details::line_logger spitlog::logger::trace(const Args&... args)
|
||||
{
|
||||
return log(level::TRACE, args...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline spitlog::details::line_logger spitlog::logger::debug(const Args&... args)
|
||||
{
|
||||
return log(level::DEBUG, args...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline spitlog::details::line_logger spitlog::logger::info(const Args&... args)
|
||||
{
|
||||
return log(level::INFO, args...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline spitlog::details::line_logger spitlog::logger::warn(const Args&... args)
|
||||
{
|
||||
return log(level::WARN, args...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline spitlog::details::line_logger spitlog::logger::error(const Args&... args)
|
||||
{
|
||||
return log(level::ERR, args...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline spitlog::details::line_logger spitlog::logger::critical(const Args&... args)
|
||||
{
|
||||
return log(level::CRITICAL, args...);
|
||||
}
|
||||
|
||||
inline const std::string& spitlog::logger::name() const
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
inline void spitlog::logger::set_level(spitlog::level::level_enum log_level)
|
||||
{
|
||||
_level.store(log_level);
|
||||
}
|
||||
|
||||
inline spitlog::level::level_enum spitlog::logger::level() const
|
||||
{
|
||||
return static_cast<spitlog::level::level_enum>(_level.load());
|
||||
}
|
||||
|
||||
inline bool spitlog::logger::should_log(spitlog::level::level_enum msg_level) const
|
||||
{
|
||||
return msg_level >= _level.load();
|
||||
}
|
||||
|
||||
inline void spitlog::logger::_variadic_log(spitlog::details::line_logger&) {}
|
||||
|
||||
template <typename First, typename... Rest>
|
||||
void spitlog::logger::_variadic_log(spitlog::details::line_logger& l, const First& first, const Rest&... rest)
|
||||
{
|
||||
l.write(first);
|
||||
l.write(' ');
|
||||
_variadic_log(l, rest...);
|
||||
}
|
||||
|
||||
inline void spitlog::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);
|
||||
}
|
||||
|
||||
//
|
||||
// Global registry functions
|
||||
//
|
||||
#include "./registry.h"
|
||||
inline std::shared_ptr<spitlog::logger> spitlog::get(const std::string& name)
|
||||
{
|
||||
return details::registry::instance().get(name);
|
||||
}
|
||||
|
||||
inline std::shared_ptr<spitlog::logger> spitlog::create(const std::string& logger_name, spitlog::sinks_init_list sinks)
|
||||
{
|
||||
return details::registry::instance().create(logger_name, sinks);
|
||||
}
|
||||
|
||||
|
||||
template <typename Sink, typename... Args>
|
||||
inline std::shared_ptr<spitlog::logger> spitlog::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<spitlog::logger> spitlog::create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end)
|
||||
{
|
||||
return details::registry::instance().create(logger_name, std::forward(sinks_begin), std::forward(sinks_end));
|
||||
}
|
||||
|
||||
inline void spitlog::set_formatter(spitlog::formatter_ptr f)
|
||||
{
|
||||
details::registry::instance().formatter(f);
|
||||
}
|
||||
|
||||
inline void spitlog::set_format(const std::string& format_string)
|
||||
{
|
||||
return details::registry::instance().set_format(format_string);
|
||||
}
|
17
include/spitlog/details/null_mutex.h
Normal file
17
include/spitlog/details/null_mutex.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
// null, no cost mutex
|
||||
|
||||
namespace spitlog {
|
||||
namespace details {
|
||||
struct null_mutex
|
||||
{
|
||||
void lock() {}
|
||||
void unlock() {}
|
||||
bool try_lock()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
129
include/spitlog/details/os.h
Normal file
129
include/spitlog/details/os.h
Normal file
@@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
|
||||
#include<string>
|
||||
#include<cstdio>
|
||||
#include<ctime>
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
namespace spitlog
|
||||
{
|
||||
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
|
||||
} //spitlog
|
||||
|
||||
|
||||
|
534
include/spitlog/details/pattern_formatter_impl.h
Normal file
534
include/spitlog/details/pattern_formatter_impl.h
Normal file
@@ -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 spitlog
|
||||
{
|
||||
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 spitlog::pattern_formatter::pattern_formatter(const std::string& pattern)
|
||||
{
|
||||
compile_pattern(pattern);
|
||||
}
|
||||
|
||||
inline void spitlog::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 spitlog::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 spitlog::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());
|
||||
}
|
80
include/spitlog/details/registry.h
Normal file
80
include/spitlog/details/registry.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#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 spitlog {
|
||||
namespace details {
|
||||
class registry {
|
||||
public:
|
||||
std::shared_ptr<logger> get(const std::string& name)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
auto found = _loggers.find(name);
|
||||
return found == _loggers.end() ? nullptr : found->second;
|
||||
}
|
||||
std::shared_ptr<logger> create(const std::string& logger_name, sinks_init_list sinks)
|
||||
{
|
||||
std::lock_guard<std::mutex> l(_mutex);
|
||||
auto new_logger = std::make_shared<logger>(logger_name, sinks);
|
||||
new_logger->set_formatter(_formatter);
|
||||
_loggers[logger_name] = new_logger;
|
||||
return new_logger;
|
||||
}
|
||||
|
||||
std::shared_ptr<logger> create(const std::string& logger_name, sink_ptr sink)
|
||||
{
|
||||
return create(logger_name, { sink });
|
||||
}
|
||||
|
||||
|
||||
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> l(_mutex);
|
||||
auto new_logger = std::make_shared<logger>(logger_name, sinks_begin, sinks_end);
|
||||
new_logger->set_formatter(_formatter);
|
||||
_loggers[logger_name] = new_logger;
|
||||
return new_logger;
|
||||
|
||||
}
|
||||
|
||||
void formatter(formatter_ptr f)
|
||||
{
|
||||
_formatter = f;
|
||||
}
|
||||
|
||||
formatter_ptr formatter()
|
||||
{
|
||||
return _formatter;
|
||||
}
|
||||
|
||||
void set_format(const std::string& format_string)
|
||||
{
|
||||
_formatter = std::make_shared<pattern_formatter>(format_string);
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
||||
};
|
||||
}
|
||||
}
|
111
include/spitlog/details/stack_buf.h
Normal file
111
include/spitlog/details/stack_buf.h
Normal file
@@ -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 spitlog
|
||||
{
|
||||
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 spitlog { namespace details {
|
33
include/spitlog/formatter.h
Normal file
33
include/spitlog/formatter.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "details/log_msg.h"
|
||||
namespace spitlog
|
||||
{
|
||||
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"
|
||||
|
103
include/spitlog/logger.h
Normal file
103
include/spitlog/logger.h
Normal file
@@ -0,0 +1,103 @@
|
||||
#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 spitlog
|
||||
{
|
||||
|
||||
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_format(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;
|
||||
|
||||
template <typename... Args> details::line_logger log(level::level_enum lvl, 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);
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
// Registry functions for easy loggers creation and retrieval
|
||||
// example
|
||||
// auto console_logger = spitlog::create("my_logger", spitlog::sinks<stdout_sink_mt>);
|
||||
// auto same_logger = spitlog::get("my_logger");
|
||||
// auto file_logger = c11
|
||||
//
|
||||
std::shared_ptr<logger> get(const std::string& name);
|
||||
std::shared_ptr<logger> create(const std::string& logger_name, sinks_init_list sinks);
|
||||
template <typename Sink, typename... Args>
|
||||
std::shared_ptr<spitlog::logger> create(const std::string& logger_name, const Args&... args);
|
||||
template<class It>
|
||||
std::shared_ptr<logger> create(const std::string& logger_name, const It& sinks_begin, const It& sinks_end);
|
||||
|
||||
void set_formatter(formatter_ptr f);
|
||||
void set_format(const std::string& format_string);
|
||||
}
|
||||
|
||||
//
|
||||
// Trace & debug macros
|
||||
//
|
||||
#ifdef FFLOG_ENABLE_TRACE
|
||||
#define FFLOG_TRACE(logger, ...) logger->log(spitlog::level::TRACE, __FILE__, " #", __LINE__,": " __VA_ARGS__)
|
||||
#else
|
||||
#define FFLOG_TRACE(logger, ...) {}
|
||||
#endif
|
||||
|
||||
#ifdef FFLOG_ENABLE_DEBUG
|
||||
#define FFLOG_DEBUG(logger, ...) logger->log(spitlog::level::DEBUG, __VA_ARGS__)
|
||||
#else
|
||||
#define FFLOG_DEBUG(logger, ...) {}
|
||||
#endif
|
||||
|
||||
#include "./details/logger_impl.h"
|
137
include/spitlog/sinks/async_sink.h
Normal file
137
include/spitlog/sinks/async_sink.h
Normal file
@@ -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 spitlog
|
||||
{
|
||||
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 spitlog::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 spitlog::sinks::async_sink::~async_sink()
|
||||
{
|
||||
_shutdown();
|
||||
}
|
||||
|
||||
inline void spitlog::sinks::async_sink::_sink_it(const details::log_msg& msg)
|
||||
{
|
||||
if(!_active)
|
||||
return;
|
||||
_q.push(msg);
|
||||
}
|
||||
|
||||
inline void spitlog::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 spitlog::sinks::async_sink::add_sink(spitlog::sink_ptr s)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
_sinks.push_back(s);
|
||||
}
|
||||
|
||||
|
||||
inline void spitlog::sinks::async_sink::remove_sink(spitlog::sink_ptr s)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
_sinks.erase(std::remove(_sinks.begin(), _sinks.end(), s), _sinks.end());
|
||||
}
|
||||
|
||||
|
||||
inline spitlog::sinks::async_sink::q_type& spitlog::sinks::async_sink::q()
|
||||
{
|
||||
return _q;
|
||||
}
|
||||
|
||||
|
||||
inline void spitlog::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 spitlog::sinks::async_sink::_shutdown()
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(_mutex);
|
||||
if(_active)
|
||||
{
|
||||
_active = false;
|
||||
if (_back_thread.joinable())
|
||||
_back_thread.join();
|
||||
}
|
||||
}
|
||||
|
43
include/spitlog/sinks/base_sink.h
Normal file
43
include/spitlog/sinks/base_sink.h
Normal file
@@ -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 spitlog
|
||||
{
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
189
include/spitlog/sinks/file_sinks.h
Normal file
189
include/spitlog/sinks/file_sinks.h
Normal file
@@ -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 spitlog
|
||||
{
|
||||
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 = spitlog::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 = spitlog::details::os::localtime();
|
||||
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;
|
||||
}
|
||||
}
|
23
include/spitlog/sinks/null_sink.h
Normal file
23
include/spitlog/sinks/null_sink.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
#include <mutex>
|
||||
#include "./base_sink.h"
|
||||
#include "../details/null_mutex.h"
|
||||
|
||||
|
||||
namespace spitlog {
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
||||
|
36
include/spitlog/sinks/ostream_sink.h
Normal file
36
include/spitlog/sinks/ostream_sink.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
|
||||
#include "../details/null_mutex.h"
|
||||
#include "./base_sink.h"
|
||||
|
||||
namespace spitlog
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
17
include/spitlog/sinks/sink.h
Normal file
17
include/spitlog/sinks/sink.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "../details/log_msg.h"
|
||||
|
||||
namespace spitlog
|
||||
{
|
||||
namespace sinks
|
||||
{
|
||||
class sink
|
||||
{
|
||||
public:
|
||||
virtual ~sink() {}
|
||||
virtual void log(const details::log_msg& msg) = 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
34
include/spitlog/sinks/stdout_sinks.h
Normal file
34
include/spitlog/sinks/stdout_sinks.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include "./ostream_sink.h"
|
||||
#include "../details/null_mutex.h"
|
||||
|
||||
namespace spitlog
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user