Log/Log.cpp

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