#include "Log.h" #include // date/time #include // ofstream (logging to file) #include // std::put_time #include // std::ostream, std::cout, std::cin #include // list of outputs #if LOG_USEMUTEX == 1 #include #endif namespace Log { /* * abstract class Output * default implementations */ // abstract base class for a log sink class Output { public: Output() {} Output(Level lvl_max) : lvl_max(lvl_max) {} virtual ~Output() {} 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 << '\n'; } }; 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() : Output() {} 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; virtual void log(Level lvl, std::stringbuf* sbuf) { // off fatal error warn note info debug trace static const char* color_codes[] = {"", "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 << '\n'; } } virtual std::ostream* getOs(Level lvl) { // 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 virtual std::ostream* getOs(Level lvl) { if (lvl_min <= lvl && lvl <= lvl_max) return &ofs; return nullptr; } }; static std::vector outputs; void log(Level lvl, std::stringbuf* strb) { for (Output* out : outputs) { out->log(lvl, strb); // reset stringbuffer read pointer to the beginning strb->pubseekpos(0); } } LeveledSink fatal{Level::fatal}; LeveledSink error{Level::error}; LeveledSink warn{Level::warn}; LeveledSink note{Level::note}; LeveledSink info{Level::info}; LeveledSink debug{Level::debug}; LeveledSink trace{Level::trace}; /* * class Entry */ Entry::Entry(Level lvl) : lvl{lvl} { for(auto metafunc : entryMetaFunctions) { metafunc(ss, *this); } } Entry::~Entry() { log(lvl, ss.rdbuf()); } std::vector entryMetaFunctions; std::ostream& defaultEntryMetaTime(std::ostream& os, const Entry& e) { (void) e; // unused using namespace std::chrono; auto now = system_clock::to_time_t(system_clock::now()); auto tm = *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 return os << "[" << std::put_time(&tm, "%Y-%m-%d %H:%M:%S") << "]"; } std::ostream& defaultEntryMetaLevel(std::ostream& os, const Entry& e) { static const char* LevelTag[] = { "", "[FATAL]", "[ERROR]", "[WARN ]", "[NOTE ]", "[INFO ]", "[DEBUG]", "[TRACE]" }; return os << LevelTag[static_cast(e.getLevel())]; } void init() { // add default console logger if (outputs.empty()) outputs.push_back(new ConsoleOutput()); // set default entry metadata printing functions auto space = [](std::ostream& os, const Entry& e) -> std::ostream& { (void) e; return os << ' '; }; entryMetaFunctions = { defaultEntryMetaTime, defaultEntryMetaLevel, space }; } void stop() { for (auto output : outputs) delete output; outputs.clear(); } void addLogfile(const std::string& filename, Level max, bool truncate) { outputs.push_back(new FileOutput(filename, max, truncate)); } void addLogfile(const std::string& filename, Level min, Level max, bool truncate) { outputs.push_back(new FileOutput(filename, min, max, truncate)); } void setConsoleLogLevel(Level lvl) { outputs.at(0)->setLogLevel(lvl); // has to exist } void setColoredOutput(bool enabled) { ((ConsoleOutput*) outputs.at(0))->setColoredOutput(enabled); // has to exist } } // namespace Log