Last active
April 26, 2017 20:31
-
-
Save Bajron/8ae742c58d159597567ba80b4edf072a to your computer and use it in GitHub Desktop.
Yet another logger concept
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// flags: -D EXTRACTION | |
#include <string> | |
#include <iostream> | |
#include <vector> | |
#include <type_traits> | |
#include <cassert> | |
#include <cstring> | |
#include <algorithm> | |
#include <thread> | |
#include <ctime> | |
#include <chrono> | |
#include <iomanip> | |
#include <sstream> | |
#include <utility> | |
struct KeyValue { | |
std::string key; | |
std::string value; | |
bool operator==(const KeyValue& rhs) const { | |
return key == rhs.key && value == rhs.value; | |
} | |
}; | |
using KeyValueSequence = std::vector<KeyValue>; | |
class LoggerBackend { | |
public: | |
virtual void take(const KeyValueSequence&& sequence) = 0; | |
static const KeyValue SEPARATOR; | |
}; | |
const KeyValue LoggerBackend::SEPARATOR = KeyValue{"<separator>", "<meta>"}; | |
class ClogBackend : public LoggerBackend { | |
public: | |
void take(const KeyValueSequence&& seq) override { | |
auto sepBack = std::find(seq.rbegin(), seq.rend(), SEPARATOR); | |
auto meta = sepBack.base(); | |
for (auto it = meta; it != seq.end(); ++it) | |
if (it->key == "thread id") { | |
std::clog << '<' << it->value << "> "; | |
} else { | |
std::clog << it->value << " "; | |
} | |
std::clog << "-- "; | |
// Format must be ${1:desc}, but we do not check it here | |
if (seq[0].key == "fmt") { | |
std::string fmt = seq[0].value; | |
int inGroup = -1; | |
for (size_t i=0; i<fmt.size(); ++i) { | |
if (inGroup == -1) { | |
if (fmt[i] == '$') { | |
assert((i+2) < fmt.size() && fmt[i+1] == '{'); | |
inGroup = i + 2; | |
} else { | |
std::clog << fmt[i]; | |
} | |
} else { | |
if (fmt[i] == '}') { | |
int idx = stoi(fmt.substr(inGroup, i - inGroup)); | |
std::clog << seq[idx].value; | |
inGroup = -1; | |
} | |
if (fmt[i] == ':') { | |
int idx = stoi(fmt.substr(inGroup, i - inGroup)); | |
std::clog << seq[idx].value; | |
inGroup = -1; | |
while (fmt[i] != '}') ++i; | |
--i; | |
} | |
} | |
} | |
} else { | |
for (auto it = seq.begin(); it->key != SEPARATOR.key; ++it) { | |
std::clog << it->value; | |
} | |
} | |
std::clog << std::endl; | |
} | |
}; | |
class DebugBackend : public LoggerBackend { | |
public: | |
void take(const KeyValueSequence&& seq) override { | |
for (const auto& kv : seq) { | |
std::clog << '{' << kv.key << ", " << kv.value << "} "; | |
} | |
std::clog << std::endl; | |
} | |
}; | |
class FanOutBackend : public LoggerBackend { | |
public: | |
void take(const KeyValueSequence&& seq) override { | |
for (const auto& ch : children) { | |
ch->take(KeyValueSequence(seq)); | |
} | |
} | |
void add(std::shared_ptr<LoggerBackend> lb) { | |
children.push_back(lb); | |
} | |
private: | |
std::vector<std::shared_ptr<LoggerBackend>> children; | |
}; | |
constexpr size_t tokenCount(const char* fmt) { | |
return (*fmt == '\0') ? size_t(0) : (*fmt == '$') + tokenCount(fmt+1); | |
} | |
struct FmtBase {}; | |
template <size_t C> | |
struct Fmt : public FmtBase { | |
template <size_t N> | |
Fmt(const char (&s)[N]) : string(s) { | |
assert(std::count(string, string + N, '$') == C); | |
} | |
Fmt(const char* s) : string(s) { | |
assert(std::count(string, string + strlen(s), '$') == C); | |
} | |
const char* string; | |
}; | |
template<typename T> | |
std::string toString(const T& s) { | |
return s; | |
} | |
template<size_t C> | |
std::string toString(const Fmt<C>& t) { | |
return t.string; | |
} | |
template<> | |
std::string toString(const std::chrono::system_clock::time_point& t) { | |
std::time_t time = std::chrono::system_clock::to_time_t(t); | |
std::tm tm = *std::localtime(&time); | |
std::stringstream buf; | |
buf << std::put_time(&tm, "%c"); | |
return buf.str(); | |
} | |
template<> | |
std::string toString(const std::thread::id& id) { | |
std::stringstream buf; | |
buf << std::hex << id; | |
return buf.str(); | |
} | |
template <typename T> | |
typename std::enable_if<std::is_integral<T>::value || std::is_floating_point<T>::value, std::string>::type | |
toString(const T& t) { | |
return std::to_string(t); | |
} | |
class Logger { | |
public: | |
Logger(std::shared_ptr<LoggerBackend> aBackend) : backend(aBackend) {} | |
template < | |
typename ...Args, | |
typename First> | |
void debug(const First& first, Args... args) const { | |
Kvs kvs; | |
gather(kvs, first, args...); | |
callBackend(std::move(kvs)); | |
} | |
template < | |
size_t C, | |
typename ...Args> | |
void debug(const Fmt<C>& fmt, Args... args) const { | |
static_assert(C == sizeof...(args), "Number of arguments and substitution tokens does not match."); | |
Kvs kvs; | |
kvs.emplace_back(KeyValue {"fmt", toString(fmt)}); | |
gather(kvs, args...); | |
callBackend(std::move(kvs)); | |
} | |
private: | |
using Kvs = KeyValueSequence; | |
inline void callBackend(Kvs&& kvs) const { | |
Kvs data(kvs); | |
data.emplace_back(LoggerBackend::SEPARATOR); | |
data.emplace_back(KeyValue{"timestamp", toString(std::chrono::system_clock::now())}); | |
data.emplace_back(KeyValue{"thread id", toString(std::this_thread::get_id())}); | |
backend->take(std::move(data)); | |
} | |
template <typename ...Args> | |
Kvs& gather(Kvs& kvs) const { | |
return kvs; | |
} | |
template <typename Head, typename ...Tail> | |
Kvs& gather(Kvs& kvs, Head head, Tail... tail) const { | |
static_assert(!std::is_base_of<FmtBase, Head>::value, | |
"Format can be only the very first argument"); | |
kvs.emplace_back(KeyValue{"tmp", toString(head)}); | |
return gather<Tail...>(kvs, tail...); | |
} | |
std::shared_ptr<LoggerBackend> backend; | |
}; | |
#ifdef EXTRACTION | |
#define DO_PRAGMA(x) _Pragma (#x) | |
#define MakeFmt(fmt_str) ([](){ DO_PRAGMA(message ("Fmt str " #fmt_str)); return Fmt<tokenCount(fmt_str)>(fmt_str); }()) | |
#else | |
#define MakeFmt(fmt_str) Fmt<tokenCount(fmt_str)>(fmt_str) | |
#endif | |
enum TestLogs { | |
FIRST = 0, | |
Zero, One, Two, NoImpl, | |
LAST | |
}; | |
constexpr const char* tr(TestLogs l) { | |
return (l == Zero) ? " Zero " | |
: (l == One) ? "One ${1} log" | |
: (l == Two) ? "Two ${2} ${1} log" | |
: throw std::logic_error("Translation not found"); | |
} | |
int main() { | |
auto be = std::make_shared<FanOutBackend>(); | |
be->add(std::make_shared<ClogBackend>()); | |
be->add(std::make_shared<DebugBackend>()); | |
Logger log(be); | |
log.debug("x", "x"); | |
log.debug(MakeFmt("${1}"), "test"); | |
log.debug(MakeFmt(tr(One)), "test"); | |
log.debug(MakeFmt(tr(Zero))); | |
log.debug(MakeFmt(tr(Two)), "one", "two"); | |
// log.debug(MakeFmt(tr(One))); | |
// log.debug(MakeFmt(tr(Zero)), " x "); | |
// log.debug(MakeFmt(tr(NoImpl))); | |
static_assert(tokenCount("$ $") == 2); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment