From a5e52072fe690cf179b11c6989af70c8844def8f Mon Sep 17 00:00:00 2001 From: okaestne Date: Sat, 1 Jun 2019 19:25:24 +0200 Subject: [PATCH] init --- Log.h | 267 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 Log.h diff --git a/Log.h b/Log.h new file mode 100644 index 0000000..e430597 --- /dev/null +++ b/Log.h @@ -0,0 +1,267 @@ +#pragma once +#include // date/time +#include // ofstream (logging to file) +#include // std::put_time +#include // std::ostream, std::cout, std::cin +#include // std::stringstream (buffer for log entries) +#include +#include // list of outputs + +class Log { + public: + enum Level { OFF = 0, FATAL, ERROR, WARN, NOTE, INFO, DEBUG, TRACE }; + + // delete ctors as this class is used via static methods only + Log() = delete; + Log(const Log&) = delete; + Log& operator=(const Log&) = delete; + ~Log() = delete; + + // set up the logger with a ConsoleOutput + static void init(); + // close all output streams + static void stop(); + + private: + // abstract base class for a log sink + class Output { + public: + Output() {} + Output(Log::Level lvl_max) : lvl_max(lvl_max) {} + virtual ~Output() {} + virtual void log(Log::Level lvl, std::stringbuf* sbuf); + virtual void setLogLevel(Log::Level lvl) { lvl_max = lvl; } + + protected: + Log::Level lvl_max = 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(Log::Level lvl) = 0; // abstract + virtual void printLogHeader(std::ostream* os, Log::Level lvl); + }; + + // logging to stdout/stderr + class ConsoleOutout : public Output { + public: + ConsoleOutout(); + virtual bool setColoredOutput(bool enabled); + + private: + std::ostream* osStd = &std::cout; + std::ostream* osErr = &std::cerr; + bool coloredOutput; + virtual void log(Log::Level lvl, std::stringbuf* sbuf); + virtual std::ostream* getOs(Log::Level lvl); + }; + + class FileOutput : public Output { + public: + FileOutput(const std::string& filename, Log::Level lvl_max); + FileOutput(const std::string& filename, Log::Level lvl_min, Log::Level lvl_max); + + private: + std::string filename; + std::ofstream ofs; + Log::Level lvl_min = FATAL; + virtual std::ostream* getOs(Log::Level lvl); + }; + + static std::vector outputs; + + public: + static void addLogfile(const std::string& filename, Level max); + static void addLogfile(const std::string& filename, Level min, Level max); + + static void setConsoleLogLevel(Level lvl); + static void setColoredOutput(bool enabled); + + // Log entry that can be formed with various mixed data types + // by concatenation with the << operator + // Inspired from https://stackoverflow.com/a/8337882 + class Entry { + private: + std::stringstream ss; + Log::Level lvl; + + public: + Entry(Log::Level lvl) : lvl(lvl) {} + Entry(Entry&&) = default; + ~Entry() { Log::log(lvl, ss.rdbuf()); } + + template + Entry& operator<<(const T& msg) { + ss << msg; + return *this; + } + }; + + template + static void fatal(const T& msg) { + Entry(FATAL) << msg; + } + static Entry fatal() { return Entry(FATAL); } + + template + static void error(const T& msg) { + Entry(ERROR) << msg; + } + static Entry error() { return Entry(ERROR); } + + template + static void warn(const T& msg) { + Entry(WARN) << msg; + } + static Entry warn() { return Entry(WARN); } + + template + static void note(const T& msg) { + Entry(NOTE) << msg; + } + static Entry note() { return Entry(NOTE); } + + template + static void info(const T& msg) { + Entry(INFO) << msg; + } + static Entry info() { return Entry(INFO); } + + template + static void debug(const T& msg) { + Entry(DEBUG) << msg; + } + static Entry debug() { return Entry(DEBUG); } + + template + static void trace(const T& msg) { + Entry(TRACE) << msg; + } + static Entry trace() { return Entry(TRACE); } + + template + static void log(const T& msg, Level lvl) { + Entry(lvl) << msg; + } + static void log(Level lvl, std::stringbuf* strb); +}; + +// static member +std::vector Log::outputs; + +void Log::init() { + // add default console logger + if (outputs.empty()) + outputs.push_back(new Log::ConsoleOutout()); +} + +void Log::stop() { + for (auto output : outputs) + delete output; +} + +void Log::addLogfile(const std::string& filename, Level max) { + outputs.push_back(new Log::FileOutput(filename, max)); +} + +void Log::addLogfile(const std::string& filename, Level min, Level max) { + outputs.push_back(new Log::FileOutput(filename, min, max)); +} + +void Log::setConsoleLogLevel(Level lvl) { + outputs.at(0)->setLogLevel(lvl); // has to exist +} + +void Log::setColoredOutput(bool enabled) { + ((Log::ConsoleOutout*) outputs.at(0))->setColoredOutput(enabled); // has to exist +} + +void Log::log(Level lvl, std::stringbuf* strb) { + for (Output* out : outputs) { + out->log(lvl, strb); + // reset stringbuffer read pointer to the beginning + strb->pubseekpos(0); + } +} + +/* + * abstract class Ouput + * default implementations + */ + +void Log::Output::log(Log::Level lvl, std::stringbuf* sbuf) { + std::ostream* os = getOs(lvl); + if (os) { + printLogHeader(os, lvl); + *os << sbuf << std::endl; + } +} + +void Log::Output::printLogHeader(std::ostream* os, Level lvl) { + static const char* LevelTag[] = {"", "[FATAL] ", "[ERROR] ", "[WARN ] ", + "[NOTE ] ", "[INFO ] ", "[DEBUG] ", "[TRACE] "}; + + // get current date/time + auto now = std::chrono::system_clock::now(); + std::time_t now_c = std::chrono::system_clock::to_time_t(now); + + *os << "[" << std::put_time(std::localtime(&now_c), "%F %T") << "]" << LevelTag[lvl]; +} + +/* ConsoleOutout */ + +Log::ConsoleOutout::ConsoleOutout() : Output() {} + +bool Log::ConsoleOutout::setColoredOutput(bool enabled) { + // TODO: check the terminals compatibility for colors + coloredOutput = enabled; + return true; +} + +void Log::ConsoleOutout::log(Log::Level lvl, std::stringbuf* sbuf) { + static const char* esc_seq_start = "\033["; + static const char* esc_seq_reset = "\033[0m"; + // OFF FATAL ERROR WARN NOTE INFO DEBUG TRACE + static const char* color_codes[] = {"", "1;31;40m", "31m", "33m", "96m", "32m", "0m", "0m"}; + + std::ostream* os = getOs(lvl); + + if (os) { + // print colors if enabled + if (coloredOutput) + *os << esc_seq_start << color_codes[lvl]; + + Output::printLogHeader(os, lvl); + *os << sbuf; + + // reset color at end of the line + if (coloredOutput) + *os << esc_seq_reset; + + *os << std::endl; + } +} + +std::ostream* Log::ConsoleOutout::getOs(Log::Level lvl) { + // out of scope? + if (!lvl || lvl > lvl_max) + return nullptr; + + // stderr for FATAL, ERROR, WARN + if (lvl <= Log::Level::WARN) + return osErr; + else + return osStd; +} + +/* FileOutput ( logging to file ) */ + +Log::FileOutput::FileOutput(const std::string& filename, Log::Level lvl_max) + : Output(lvl_max), filename(filename), ofs(filename, std::ofstream::out | std::ofstream::app) {} + +Log::FileOutput::FileOutput(const std::string& filename, Log::Level lvl_min, Log::Level lvl_max) + : Output(lvl_max), filename(filename), ofs(filename, std::ofstream::out | std::ofstream::app), lvl_min(lvl_min) {} + +std::ostream* Log::FileOutput::getOs(Log::Level lvl) { + if (lvl_min <= lvl && lvl <= lvl_max) + return &ofs; + return nullptr; +} \ No newline at end of file