#include "Log.h" #include #include // date/time #include #include // ofstream (logging to file) #include // std::ostream, std::cout, std::cin #include // std::unique_ptr #if LOG_USEMUTEX == 1 #include #endif #if LOG_ENABLEQT == 1 #include #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 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 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 lock(ostreamLock); #endif std::ostream* os = getOs(lvl); if (os) { // print colors if enabled if (coloredOutput) { *os << "\x1B[" << color_codes[static_cast(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> 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 Deleter::refCount{0}; std::vector 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 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 LevelTag = { "", "[FATAL]", "[ERROR]", "[WARN ]", "[NOTE ]", "[INFO ]", "[DEBUG]", "[TRACE]" }; return os << LevelTag[static_cast(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(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(new FileOutput(filename, max, truncate))); } void addLogfile(const std::string& filename, Level min, Level max, bool truncate) { outputs.push_back(std::unique_ptr(new FileOutput(filename, min, max, truncate))); } void setConsoleLogLevel(Level lvl) { outputs.at(0)->setLogLevel(lvl); // has to exist } void setColoredOutput(bool enabled) { dynamic_cast(*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