implemented a new logging infrastructure

This commit is contained in:
Dominik Tomičević 2016-05-09 23:30:13 +02:00
parent 4bf5636d24
commit d6840d670a
20 changed files with 657 additions and 1 deletions

32
examples/exceptions.cpp Normal file
View File

@ -0,0 +1,32 @@
#include <iostream>
#include "utils/exceptions/basic_exception.hpp"
void i_will_throw()
{
throw BasicException("this is not {}", "ok!");
}
void bar()
{
i_will_throw();
}
void foo()
{
bar();
}
int main(void)
{
try
{
foo();
}
catch(std::exception& e)
{
std::cout << e.what() << std::endl;
}
return 0;
}

26
examples/log.cpp Normal file
View File

@ -0,0 +1,26 @@
#include "logging/logger.hpp"
#include "logging/logs/sync_log.hpp"
#include "logging/logs/async_log.hpp"
#include "logging/streams/stdout.hpp"
int main(void)
{
//Log::uptr log = std::make_unique<SyncLog>();
Log::uptr log = std::make_unique<AsyncLog>();
log->pipe(std::make_unique<Stdout>());
auto logger = log->logger("main");
logger.info("This is very {}!", "awesome");
logger.warn("This is very {}!", "awesome");
logger.error("This is very {}!", "awesome");
logger.trace("This is very {}!", "awesome");
logger.debug("This is very {}!", "awesome");
using namespace std::chrono;
/* std::this_thread::sleep_for(1s); */
return 0;
}

23
examples/timestamp.cpp Executable file
View File

@ -0,0 +1,23 @@
#include <iostream>
#include <chrono>
#include <thread>
#include "utils/datetime/timestamp.hpp"
int main(void)
{
auto timestamp = Timestamp::now();
std::cout << timestamp << std::endl;
std::cout << Timestamp::now() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(250));
std::cout << Timestamp::now().to_iso8601() << std::endl;
std::cout << std::boolalpha;
std::cout << (timestamp == Timestamp::now()) << std::endl;
return 0;
}

7
logging/levels.cpp Normal file
View File

@ -0,0 +1,7 @@
#include "levels.hpp"
std::string Trace::text = "TRACE";
std::string Debug::text = "DEBUG";
std::string Info::text = "INFO";
std::string Warn::text = "WARN";
std::string Error::text = "ERROR";

33
logging/levels.hpp Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <string>
struct Trace
{
static std::string text;
static constexpr unsigned level = 0;
};
struct Debug
{
static std::string text;
static constexpr unsigned level = 10;
};
struct Info
{
static std::string text;
static constexpr unsigned level = 20;
};
struct Warn
{
static std::string text;
static constexpr unsigned level = 30;
};
struct Error
{
static std::string text;
static constexpr unsigned level = 40;
};

9
logging/log.cpp Normal file
View File

@ -0,0 +1,9 @@
#include <iostream>
#include "log.hpp"
#include "logger.hpp"
Logger Log::logger(const std::string& name)
{
return Logger(*this, name);
}

64
logging/log.hpp Normal file
View File

@ -0,0 +1,64 @@
#pragma once
#include <vector>
#include <string>
#include "utils/datetime/timestamp.hpp"
class Logger;
class Log
{
public:
using uptr = std::unique_ptr<Log>;
class Record
{
public:
using uptr = std::unique_ptr<Record>;
Record() = default;
virtual ~Record() = default;
virtual const Timestamp& when() const = 0;
virtual const std::string& where() const = 0;
virtual unsigned level() const = 0;
virtual const std::string& level_str() const = 0;
virtual const std::string& text() const = 0;
};
class Stream
{
public:
using uptr = std::unique_ptr<Stream>;
Stream() = default;
virtual ~Stream() = default;
virtual void emit(const Record&) = 0;
};
virtual ~Log() = default;
Logger logger(const std::string& name);
void pipe(Stream::uptr&& stream)
{
streams.emplace_back(std::forward<Stream::uptr>(stream));
}
protected:
friend class Logger;
virtual void emit(Record::uptr record) = 0;
void dispatch(const Record& record)
{
for(auto& stream : streams)
stream->emit(record);
}
std::vector<Stream::uptr> streams;
};

93
logging/logger.hpp Normal file
View File

@ -0,0 +1,93 @@
#pragma once
#include "log.hpp"
#include "levels.hpp"
class Logger
{
template <class Level>
class Message : public Log::Record
{
public:
Message(Timestamp timestamp, std::string location, std::string message)
: timestamp(timestamp), location(location), message(message) {}
const Timestamp& when() const override
{
return timestamp;
}
const std::string& where() const override
{
return location;
}
unsigned level() const override
{
return Level::level;
}
const std::string& level_str() const override
{
return Level::text;
}
const std::string& text() const override
{
return message;
}
private:
Timestamp timestamp;
std::string location;
std::string message;
};
public:
Logger(Log& log, const std::string& name) : log(log), name(name) {}
template <class Level, class... Args>
void emit(Args&&... args)
{
auto message = std::make_unique<Message<Level>>(
Timestamp::now(), name, fmt::format(std::forward<Args>(args)...)
);
log.get().emit(std::move(message));
}
template <class... Args>
void trace(Args&&... args)
{
emit<Trace>(std::forward<Args>(args)...);
}
template <class... Args>
void debug(Args&&... args)
{
emit<Debug>(std::forward<Args>(args)...);
}
template <class... Args>
void info(Args&&... args)
{
emit<Info>(std::forward<Args>(args)...);
}
template <class... Args>
void warn(Args&&... args)
{
emit<Warn>(std::forward<Args>(args)...);
}
template <class... Args>
void error(Args&&... args)
{
emit<Error>(std::forward<Args>(args)...);
}
private:
std::reference_wrapper<Log> log;
std::string name;
};

View File

@ -0,0 +1,33 @@
#include "async_log.hpp"
AsyncLog::~AsyncLog()
{
alive.store(false);
worker.join();
}
void AsyncLog::emit(Record::uptr record)
{
records.push(std::move(record));
}
void AsyncLog::work()
{
using namespace std::chrono_literals;
while(true)
{
auto record = records.pop();
if(record != nullptr)
{
dispatch(*record);
continue;
}
if(!alive)
return;
std::this_thread::sleep_for(10ms);
}
}

View File

@ -0,0 +1,22 @@
#pragma once
#include <thread>
#include "logging/log.hpp"
#include "data_structures/queue/mpsc_queue.hpp"
class AsyncLog : public Log
{
public:
~AsyncLog();
protected:
void emit(Record::uptr) override;
private:
lockfree::MpscQueue<Record> records;
std::atomic<bool> alive {true};
std::thread worker {[this]() { work(); }};
void work();
};

View File

@ -0,0 +1,7 @@
#include "sync_log.hpp"
void SyncLog::emit(Record::uptr record)
{
auto guard = this->acquire_unique();
dispatch(*record);
}

11
logging/logs/sync_log.hpp Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include "logging/log.hpp"
#include "threading/sync/lockable.hpp"
#include "threading/sync/futex.hpp"
class SyncLog : public Log, Lockable<Futex>
{
protected:
void emit(Record::uptr) override;
};

View File

@ -0,0 +1,10 @@
#include "stdout.hpp"
#include <cppformat/format.h>
void Stdout::emit(const Log::Record& record)
{
fmt::print("{} {:<5} [{}] {}\n", record.when(), record.level_str(),
record.where(), record.text());
}

View File

@ -0,0 +1,9 @@
#pragma once
#include "logging/log.hpp"
class Stdout : public Log::Stream
{
public:
void emit(const Log::Record&) override;
};

View File

@ -39,7 +39,7 @@ class Futex
} state;
};
enum Contension : futex_t
enum Contention : futex_t
{
UNCONTENDED = 0x0000,
CONTENDED = 0x0100

View File

@ -0,0 +1,35 @@
#pragma once
#include "utils/exceptions/basic_exception.hpp"
class Datetime
{
public:
Datetime()
{
}
Datetime(std::time_t time_point)
{
auto result = gmtime_r(&time_point, &time);
if(result == nullptr)
throw DatetimeError("Unable to construct from {}", time_point);
}
Datetime(const Datetime&) = default;
Datetime(Datetime&&) = default;
static Datetime now()
{
timespec
return Datetime();
}
private:
std::tm time;
};

View File

@ -0,0 +1,10 @@
#pragma once
#include "utils/exceptions/basic_exception.hpp"
class DatetimeError : public BasicException
{
public:
using BasicException::BasicException;
};

View File

@ -0,0 +1,107 @@
#pragma once
#include <chrono>
#include <ctime>
#include <iomanip>
#include <ostream>
#include <cppformat/format.h>
#include "utils/datetime/datetime_error.hpp"
#include "utils/total_ordering.hpp"
class Timestamp : public TotalOrdering<Timestamp>
{
public:
Timestamp() : Timestamp(0, 0) {}
Timestamp(std::time_t time, long nsec = 0) : unix_time(time), nsec(nsec)
{
auto result = gmtime_r(&time, &this->time);
if(result == nullptr)
throw DatetimeError("Unable to construct from {}", time);
}
Timestamp(const Timestamp&) = default;
Timestamp(Timestamp&&) = default;
static Timestamp now()
{
timespec time;
clock_gettime(CLOCK_REALTIME, &time);
return {time.tv_sec, time.tv_nsec};
}
long year() const
{
return time.tm_year + 1900;
}
long month() const
{
return time.tm_mon + 1;
}
long day() const
{
return time.tm_mday;
}
long hour() const
{
return time.tm_hour;
}
long min() const
{
return time.tm_min;
}
long sec() const
{
return time.tm_sec;
}
long subsec() const
{
return nsec;
}
const std::string to_iso8601() const
{
return fmt::format(fiso8601, year(), month(), day(), hour(),
min(), sec(), subsec());
}
friend std::ostream& operator<<(std::ostream& stream, const Timestamp& ts)
{
return stream << ts.to_iso8601();
}
operator std::string() const
{
return to_iso8601();
}
constexpr friend bool operator==(const Timestamp& a, const Timestamp& b)
{
return a.unix_time == b.unix_time && a.nsec == b.nsec;
}
constexpr friend bool operator<(const Timestamp& a, const Timestamp& b)
{
return a.unix_time < b.unix_time
|| (a.unix_time == b.unix_time && a.nsec < b.nsec);
}
private:
std::tm time;
std::time_t unix_time;
long nsec;
static constexpr auto fiso8601 =
"{:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}.{:09d}Z";
};

View File

@ -0,0 +1,37 @@
#pragma once
#include <stdexcept>
#include <cppformat/format.h>
#include "utils/auto_scope.hpp"
#include "utils/stacktrace.hpp"
class BasicException : public std::exception
{
public:
template <class... Args>
BasicException(Args&&... args) noexcept
: message(fmt::format(std::forward<Args>(args)...))
{
#ifndef NDEBUG
message += '\n';
Stacktrace stacktrace;
for(auto& line : stacktrace)
message += fmt::format(" at {} ({})\n",
line.function, line.location);
#endif
}
const char* what() const noexcept override
{
return message.c_str();
}
private:
std::string message;
};

88
utils/stacktrace.hpp Normal file
View File

@ -0,0 +1,88 @@
#pragma once
#include <cxxabi.h>
#include <stdexcept>
#include <execinfo.h>
#include <cppformat/format.h>
#include "utils/auto_scope.hpp"
class Stacktrace
{
public:
class Line
{
public:
Line(const std::string& original) : original(original) {}
Line(const std::string& original, const std::string& function,
const std::string& location)
: original(original), function(function), location(location) {}
std::string original, function, location;
};
static constexpr size_t stacktrace_depth = 128;
Stacktrace()
{
void* addresses[stacktrace_depth];
auto depth = backtrace(addresses, stacktrace_depth);
// will this leak if backtrace_symbols throws?
char** symbols = nullptr;
Auto(free(symbols));
symbols = backtrace_symbols(addresses, depth);
// skip the first one since it will be Stacktrace::Stacktrace()
for(int i = 1; i < depth; ++i)
lines.emplace_back(format(symbols[i]));
}
auto begin() { return lines.begin(); }
auto begin() const { return lines.begin(); }
auto cbegin() const { return lines.cbegin(); }
auto end() { return lines.end(); }
auto end() const { return lines.end(); }
auto cend() const { return lines.cend(); }
const Line& operator[](size_t idx) const
{
return lines[idx];
}
size_t size() const
{
return lines.size();
}
private:
std::vector<Line> lines;
Line format(const std::string& original)
{
using namespace abi;
auto line = original;
auto begin = line.find('(');
auto end = line.find('+');
if(begin == std::string::npos || end == std::string::npos)
return {original};
line[end] = '\0';
int s;
auto demangled = __cxa_demangle(line.data() + begin + 1, nullptr,
nullptr, &s);
auto location = line.substr(0, begin);
auto function = demangled ? std::string(demangled)
: fmt::format("{}()", original.substr(begin + 1, end - begin - 1));
return {original, function, location};
}
};