This commit is contained in:
Oliver 2019-06-01 19:25:24 +02:00
commit a5e52072fe
1 changed files with 267 additions and 0 deletions

267
Log.h Normal file
View File

@ -0,0 +1,267 @@
#pragma once
#include <chrono> // date/time
#include <fstream> // ofstream (logging to file)
#include <iomanip> // std::put_time
#include <iostream> // std::ostream, std::cout, std::cin
#include <sstream> // std::stringstream (buffer for log entries)
#include <string>
#include <vector> // 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<Output*> 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 <typename T>
Entry& operator<<(const T& msg) {
ss << msg;
return *this;
}
};
template <typename T>
static void fatal(const T& msg) {
Entry(FATAL) << msg;
}
static Entry fatal() { return Entry(FATAL); }
template <typename T>
static void error(const T& msg) {
Entry(ERROR) << msg;
}
static Entry error() { return Entry(ERROR); }
template <typename T>
static void warn(const T& msg) {
Entry(WARN) << msg;
}
static Entry warn() { return Entry(WARN); }
template <typename T>
static void note(const T& msg) {
Entry(NOTE) << msg;
}
static Entry note() { return Entry(NOTE); }
template <typename T>
static void info(const T& msg) {
Entry(INFO) << msg;
}
static Entry info() { return Entry(INFO); }
template <typename T>
static void debug(const T& msg) {
Entry(DEBUG) << msg;
}
static Entry debug() { return Entry(DEBUG); }
template <typename T>
static void trace(const T& msg) {
Entry(TRACE) << msg;
}
static Entry trace() { return Entry(TRACE); }
template <typename T>
static void log(const T& msg, Level lvl) {
Entry(lvl) << msg;
}
static void log(Level lvl, std::stringbuf* strb);
};
// static member
std::vector<Log::Output*> 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;
}