325 lines
7.5 KiB
C++
325 lines
7.5 KiB
C++
#include "Log.h"
|
|
|
|
#include <array>
|
|
#include <chrono> // date/time
|
|
#include <cstring>
|
|
#include <fstream> // ofstream (logging to file)
|
|
#include <iostream> // std::ostream, std::cout, std::cin
|
|
#include <memory> // std::unique_ptr
|
|
|
|
#if LOG_USEMUTEX == 1
|
|
#include <mutex>
|
|
#endif
|
|
|
|
#if LOG_ENABLEQT == 1
|
|
#include <QString>
|
|
#endif
|
|
|
|
#if LOG_ENABLEFLUSH == 1
|
|
#define FLUSH_COMMAND std::endl
|
|
#else
|
|
// dont flush
|
|
#define FLUSH_COMMAND '\n'
|
|
#endif
|
|
|
|
namespace Log {
|
|
|
|
#if LOG_ENABLEQT == 1
|
|
void qtMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
|
|
|
|
// convert type to Log::Level
|
|
Log::Level lvl = Log::Level::off;
|
|
switch(type) {
|
|
case QtDebugMsg: lvl = Log::Level::debug; break;
|
|
case QtInfoMsg: lvl = Log::Level::info; break;
|
|
case QtWarningMsg: lvl = Log::Level::warn; break;
|
|
case QtCriticalMsg: lvl = Log::Level::error; break;
|
|
case QtFatalMsg: lvl = Log::Level::fatal; break;
|
|
}
|
|
|
|
Entry(lvl, context) << message.toStdString();
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* abstract class Output
|
|
* default implementations
|
|
*/
|
|
|
|
// abstract base class for a log sink
|
|
class Output {
|
|
public:
|
|
Output() = default;
|
|
explicit Output(Level lvl_max) : lvl_max{lvl_max} {}
|
|
virtual ~Output() = default;
|
|
|
|
virtual void log(Level lvl, std::stringbuf* sbuf) {
|
|
//aquire lock
|
|
#if LOG_USEMUTEX == 1
|
|
std::unique_lock<std::mutex> lock(ostreamLock);
|
|
#endif
|
|
|
|
std::ostream* os = getOs(lvl);
|
|
if (os) {
|
|
*os << sbuf << FLUSH_COMMAND;
|
|
}
|
|
};
|
|
|
|
virtual void setLogLevel(Level lvl) { lvl_max = lvl; }
|
|
|
|
protected:
|
|
Level lvl_max = Level::info;
|
|
// returns the correct ostream for the given log-level
|
|
// or returns nullptr if no ostream is set/enabled for this level
|
|
virtual std::ostream* getOs(Level lvl) = 0; // abstract
|
|
|
|
#if LOG_USEMUTEX == 1
|
|
std::mutex ostreamLock; //used for both streams
|
|
#endif
|
|
};
|
|
|
|
// logging to stdout/stderr
|
|
class ConsoleOutput : public Output {
|
|
public:
|
|
ConsoleOutput() = default;
|
|
|
|
virtual bool setColoredOutput(bool enabled) {
|
|
// TODO: check the terminal's compatibility for colors
|
|
coloredOutput = enabled;
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
std::ostream* osStd = &std::cout;
|
|
std::ostream* osErr = &std::cerr;
|
|
bool coloredOutput = false;
|
|
|
|
void log(Level lvl, std::stringbuf* sbuf) override {
|
|
static constexpr std::array<const char*, 8> color_codes = {
|
|
// off fatal error warn note info debug trace
|
|
"", "1;31;40m", "31m", "33m", "96m", "32m", "0m", "0m"
|
|
};
|
|
|
|
//aquire lock
|
|
#if LOG_USEMUTEX == 1
|
|
std::unique_lock<std::mutex> lock(ostreamLock);
|
|
#endif
|
|
|
|
std::ostream* os = getOs(lvl);
|
|
|
|
if (os) {
|
|
// print colors if enabled
|
|
if (coloredOutput) {
|
|
*os << "\x1B[" << color_codes[static_cast<int>(lvl)] << sbuf << "\x1B[0m";
|
|
} else {
|
|
*os << sbuf;
|
|
}
|
|
|
|
os->put('\n');
|
|
}
|
|
}
|
|
|
|
std::ostream* getOs(Level lvl) override {
|
|
// out of scope?
|
|
if (lvl == Level::off || lvl > lvl_max) {
|
|
return nullptr;
|
|
}
|
|
|
|
// stderr for fatal, error, warn
|
|
if (lvl <= Level::warn) {
|
|
return osErr;
|
|
} else {
|
|
return osStd;
|
|
}
|
|
}
|
|
};
|
|
|
|
class FileOutput : public Output {
|
|
public:
|
|
FileOutput(const std::string& filename, Level lvl_max, bool truncate)
|
|
: Output(lvl_max), filename(filename), ofs(filename, truncate ? std::ostream::trunc : std::ostream::app) {}
|
|
|
|
FileOutput(const std::string& filename, Level lvl_min, Level lvl_max, bool truncate)
|
|
: Output(lvl_max),
|
|
filename(filename),
|
|
ofs(filename, truncate ? std::ostream::trunc : std::ostream::app),
|
|
lvl_min(lvl_min) {}
|
|
|
|
private:
|
|
std::string filename;
|
|
std::ofstream ofs;
|
|
Level lvl_min = Level::fatal;
|
|
|
|
#if LOG_USEMUTEX == 1
|
|
std::mutex ostreamLock; //used for both streams
|
|
#endif
|
|
|
|
std::ostream* getOs(Level lvl) override {
|
|
if (lvl_min <= lvl && lvl <= lvl_max) {
|
|
return &ofs;
|
|
}
|
|
return nullptr;
|
|
}
|
|
};
|
|
|
|
static std::vector<std::unique_ptr<Output>> outputs;
|
|
|
|
void log(Level lvl, std::stringbuf* strb) {
|
|
for (auto&& out : outputs) {
|
|
out->log(lvl, strb);
|
|
// reset stringbuffer read pointer to the beginning
|
|
strb->pubseekpos(0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* class Entry
|
|
*/
|
|
#if LOG_ENABLEQT == 1
|
|
Entry::Entry(Level lvl, const QMessageLogContext& context) : lvl{lvl}, context{&context} {
|
|
for(const auto& metafunc : entryMetaFunctions) {
|
|
metafunc(ss, *this);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
Entry::Entry(Level lvl) : lvl{lvl} {
|
|
for(const auto& metafunc : entryMetaFunctions) {
|
|
metafunc(ss, *this);
|
|
}
|
|
}
|
|
|
|
Entry::~Entry() {
|
|
log(lvl, ss.rdbuf());
|
|
}
|
|
|
|
Deleter::Deleter() {
|
|
if(refCount.fetch_add(1) == 0) {
|
|
init();
|
|
}
|
|
}
|
|
Deleter::~Deleter() {
|
|
if(refCount.fetch_sub(1) == 1) {
|
|
stop();
|
|
}
|
|
}
|
|
|
|
std::atomic<uint32_t> Deleter::refCount{0};
|
|
|
|
std::vector<Entry::MetaFunction> entryMetaFunctions;
|
|
|
|
std::ostream& defaultEntryMetaTime(std::ostream& os, const Entry& e) {
|
|
(void) e; // unused
|
|
|
|
using std::chrono::system_clock;
|
|
auto now = system_clock::to_time_t(system_clock::now());
|
|
auto time = *std::localtime(&now);
|
|
|
|
// MinGW doesn't support the ISO8601 formatting characters like "%F" and "%T"
|
|
// ref: https://sourceforge.net/p/mingw-w64/bugs/793/
|
|
// Therefore, use a more verbose time string
|
|
std::array<char, 24> buf;
|
|
(void) std::strftime(buf.data(), buf.size(), "[%Y-%m-%d %H:%M:%S]", &time);
|
|
return os << buf.data();
|
|
}
|
|
|
|
std::ostream& defaultEntryMetaLevel(std::ostream& os, const Entry& e) {
|
|
static constexpr std::array<const char*, 8> LevelTag = {
|
|
"", "[FATAL]", "[ERROR]", "[WARN ]", "[NOTE ]", "[INFO ]", "[DEBUG]", "[TRACE]"
|
|
};
|
|
return os << LevelTag[static_cast<int>(e.getLevel())];
|
|
}
|
|
|
|
#if LOG_ENABLEQT == 1
|
|
std::ostream& defaultEntryMetaQtContext(std::ostream& os, const Entry& e) {
|
|
if (e.getContext() && e.getContext()->file) {
|
|
const std::string file = e.getContext()->file;
|
|
return os << file << ':' << e.getContext()->line;
|
|
}
|
|
return os << "-:-";
|
|
}
|
|
#endif
|
|
|
|
void init() {
|
|
// add default console logger
|
|
if (outputs.empty()) {
|
|
outputs.push_back(std::unique_ptr<Output>(new ConsoleOutput()));
|
|
}
|
|
|
|
// set default entry metadata printing functions
|
|
auto space = [](std::ostream& os, const Entry& e) -> std::ostream& {
|
|
(void) e;
|
|
return os.put(' ');
|
|
};
|
|
entryMetaFunctions = {
|
|
defaultEntryMetaTime,
|
|
defaultEntryMetaLevel,
|
|
|
|
#if LOG_ENABLEQT == 1
|
|
space,
|
|
defaultEntryMetaQtContext,
|
|
#endif
|
|
|
|
space
|
|
};
|
|
|
|
#if LOG_ENABLEQT == 1
|
|
qInstallMessageHandler( &Log::qtMessageHandler );
|
|
#endif
|
|
}
|
|
|
|
void stop() {
|
|
#if LOG_ENABLEQT == 1
|
|
qInstallMessageHandler( nullptr );
|
|
#endif
|
|
outputs.clear();
|
|
}
|
|
|
|
void addLogfile(const std::string& filename, Level max, bool truncate) {
|
|
outputs.push_back(std::unique_ptr<Output>(new FileOutput(filename, max, truncate)));
|
|
}
|
|
|
|
void addLogfile(const std::string& filename, Level min, Level max, bool truncate) {
|
|
outputs.push_back(std::unique_ptr<Output>(new FileOutput(filename, min, max, truncate)));
|
|
}
|
|
|
|
void setConsoleLogLevel(Level lvl) {
|
|
outputs.at(0)->setLogLevel(lvl); // has to exist
|
|
}
|
|
|
|
void setColoredOutput(bool enabled) {
|
|
dynamic_cast<ConsoleOutput&>(*outputs.at(0)).setColoredOutput(enabled); // has to exist
|
|
}
|
|
|
|
const PrintErrno err;
|
|
|
|
std::ostream& operator<<(std::ostream& str, const PrintErrno&) {
|
|
return str << strerror(errno) << " (" << errno << ')';
|
|
}
|
|
|
|
FileSize::operator std::string() const {
|
|
std::ostringstream str;
|
|
str << *this;
|
|
return str.str();
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& str, const FileSize& fs) {
|
|
static const char PREFIX[] {' ', 'K', 'M', 'G', 'T', 'P', 'E'};
|
|
static const uint_fast8_t PREFIXCOUNT = 7;
|
|
static const uint_fast32_t FACTOR = 1000;
|
|
static const uint_fast32_t COMMA = 100; // nach komma stellen
|
|
|
|
uint64_t cpy = fs.fs * COMMA;
|
|
uint_fast8_t prefix = 0;
|
|
while(cpy > (FACTOR * 3 * COMMA) && prefix < PREFIXCOUNT) {
|
|
cpy /= FACTOR;
|
|
++prefix;
|
|
}
|
|
|
|
str << (cpy / (float) COMMA);
|
|
if(prefix > 0)
|
|
str << PREFIX[prefix];
|
|
return str << "B";
|
|
}
|
|
|
|
} // namespace Log
|