implemented a new logging infrastructure
This commit is contained in:
parent
4bf5636d24
commit
d6840d670a
32
examples/exceptions.cpp
Normal file
32
examples/exceptions.cpp
Normal 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
26
examples/log.cpp
Normal 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
23
examples/timestamp.cpp
Executable 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
7
logging/levels.cpp
Normal 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
33
logging/levels.hpp
Normal 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
9
logging/log.cpp
Normal 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
64
logging/log.hpp
Normal 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
93
logging/logger.hpp
Normal 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;
|
||||
};
|
||||
|
33
logging/logs/async_log.cpp
Normal file
33
logging/logs/async_log.cpp
Normal 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);
|
||||
}
|
||||
}
|
22
logging/logs/async_log.hpp
Normal file
22
logging/logs/async_log.hpp
Normal 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();
|
||||
};
|
7
logging/logs/sync_log.cpp
Normal file
7
logging/logs/sync_log.cpp
Normal 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
11
logging/logs/sync_log.hpp
Normal 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;
|
||||
};
|
10
logging/streams/stdout.cpp
Normal file
10
logging/streams/stdout.cpp
Normal 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());
|
||||
}
|
||||
|
9
logging/streams/stdout.hpp
Normal file
9
logging/streams/stdout.hpp
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "logging/log.hpp"
|
||||
|
||||
class Stdout : public Log::Stream
|
||||
{
|
||||
public:
|
||||
void emit(const Log::Record&) override;
|
||||
};
|
@ -39,7 +39,7 @@ class Futex
|
||||
} state;
|
||||
};
|
||||
|
||||
enum Contension : futex_t
|
||||
enum Contention : futex_t
|
||||
{
|
||||
UNCONTENDED = 0x0000,
|
||||
CONTENDED = 0x0100
|
||||
|
35
utils/datetime/datetime.hpp
Normal file
35
utils/datetime/datetime.hpp
Normal 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;
|
||||
};
|
10
utils/datetime/datetime_error.hpp
Normal file
10
utils/datetime/datetime_error.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "utils/exceptions/basic_exception.hpp"
|
||||
|
||||
class DatetimeError : public BasicException
|
||||
{
|
||||
public:
|
||||
using BasicException::BasicException;
|
||||
};
|
||||
|
107
utils/datetime/timestamp.hpp
Normal file
107
utils/datetime/timestamp.hpp
Normal 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";
|
||||
};
|
37
utils/exceptions/basic_exception.hpp
Normal file
37
utils/exceptions/basic_exception.hpp
Normal 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
88
utils/stacktrace.hpp
Normal 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};
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user