diff --git a/examples/exceptions.cpp b/examples/exceptions.cpp new file mode 100644 index 000000000..a8509e8e2 --- /dev/null +++ b/examples/exceptions.cpp @@ -0,0 +1,32 @@ +#include + +#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; +} diff --git a/examples/log.cpp b/examples/log.cpp new file mode 100644 index 000000000..a8c6bf01f --- /dev/null +++ b/examples/log.cpp @@ -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(); + Log::uptr log = std::make_unique(); + + log->pipe(std::make_unique()); + + 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; +} diff --git a/examples/timestamp.cpp b/examples/timestamp.cpp new file mode 100755 index 000000000..f710e71c0 --- /dev/null +++ b/examples/timestamp.cpp @@ -0,0 +1,23 @@ +#include +#include +#include + +#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; +} diff --git a/logging/levels.cpp b/logging/levels.cpp new file mode 100644 index 000000000..d5982677b --- /dev/null +++ b/logging/levels.cpp @@ -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"; diff --git a/logging/levels.hpp b/logging/levels.hpp new file mode 100644 index 000000000..c163309dd --- /dev/null +++ b/logging/levels.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +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; +}; diff --git a/logging/log.cpp b/logging/log.cpp new file mode 100644 index 000000000..d3aefc899 --- /dev/null +++ b/logging/log.cpp @@ -0,0 +1,9 @@ +#include + +#include "log.hpp" +#include "logger.hpp" + +Logger Log::logger(const std::string& name) +{ + return Logger(*this, name); +} diff --git a/logging/log.hpp b/logging/log.hpp new file mode 100644 index 000000000..b1da9c904 --- /dev/null +++ b/logging/log.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +#include "utils/datetime/timestamp.hpp" + +class Logger; + +class Log +{ +public: + using uptr = std::unique_ptr; + + class Record + { + public: + using uptr = std::unique_ptr; + + 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() = 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)); + } + +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 streams; +}; diff --git a/logging/logger.hpp b/logging/logger.hpp new file mode 100644 index 000000000..17af92809 --- /dev/null +++ b/logging/logger.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include "log.hpp" +#include "levels.hpp" + +class Logger +{ + template + 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 + void emit(Args&&... args) + { + auto message = std::make_unique>( + Timestamp::now(), name, fmt::format(std::forward(args)...) + ); + + log.get().emit(std::move(message)); + } + + template + void trace(Args&&... args) + { + emit(std::forward(args)...); + } + + template + void debug(Args&&... args) + { + emit(std::forward(args)...); + } + + template + void info(Args&&... args) + { + emit(std::forward(args)...); + } + + template + void warn(Args&&... args) + { + emit(std::forward(args)...); + } + + template + void error(Args&&... args) + { + emit(std::forward(args)...); + } + +private: + std::reference_wrapper log; + std::string name; +}; + diff --git a/logging/logs/async_log.cpp b/logging/logs/async_log.cpp new file mode 100644 index 000000000..e95ba2f29 --- /dev/null +++ b/logging/logs/async_log.cpp @@ -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); + } +} diff --git a/logging/logs/async_log.hpp b/logging/logs/async_log.hpp new file mode 100644 index 000000000..33039cebc --- /dev/null +++ b/logging/logs/async_log.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +#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 records; + std::atomic alive {true}; + std::thread worker {[this]() { work(); }}; + + void work(); +}; diff --git a/logging/logs/sync_log.cpp b/logging/logs/sync_log.cpp new file mode 100644 index 000000000..498a8828b --- /dev/null +++ b/logging/logs/sync_log.cpp @@ -0,0 +1,7 @@ +#include "sync_log.hpp" + +void SyncLog::emit(Record::uptr record) +{ + auto guard = this->acquire_unique(); + dispatch(*record); +} diff --git a/logging/logs/sync_log.hpp b/logging/logs/sync_log.hpp new file mode 100644 index 000000000..aed0379a9 --- /dev/null +++ b/logging/logs/sync_log.hpp @@ -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 +{ +protected: + void emit(Record::uptr) override; +}; diff --git a/logging/streams/stdout.cpp b/logging/streams/stdout.cpp new file mode 100644 index 000000000..7f73fb808 --- /dev/null +++ b/logging/streams/stdout.cpp @@ -0,0 +1,10 @@ +#include "stdout.hpp" + +#include + +void Stdout::emit(const Log::Record& record) +{ + fmt::print("{} {:<5} [{}] {}\n", record.when(), record.level_str(), + record.where(), record.text()); +} + diff --git a/logging/streams/stdout.hpp b/logging/streams/stdout.hpp new file mode 100644 index 000000000..74397c6c7 --- /dev/null +++ b/logging/streams/stdout.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include "logging/log.hpp" + +class Stdout : public Log::Stream +{ +public: + void emit(const Log::Record&) override; +}; diff --git a/threading/sync/futex.hpp b/threading/sync/futex.hpp index afef77450..d4739a94f 100644 --- a/threading/sync/futex.hpp +++ b/threading/sync/futex.hpp @@ -39,7 +39,7 @@ class Futex } state; }; - enum Contension : futex_t + enum Contention : futex_t { UNCONTENDED = 0x0000, CONTENDED = 0x0100 diff --git a/utils/datetime/datetime.hpp b/utils/datetime/datetime.hpp new file mode 100644 index 000000000..9b63cffd7 --- /dev/null +++ b/utils/datetime/datetime.hpp @@ -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; +}; diff --git a/utils/datetime/datetime_error.hpp b/utils/datetime/datetime_error.hpp new file mode 100644 index 000000000..8a91cca87 --- /dev/null +++ b/utils/datetime/datetime_error.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "utils/exceptions/basic_exception.hpp" + +class DatetimeError : public BasicException +{ +public: + using BasicException::BasicException; +}; + diff --git a/utils/datetime/timestamp.hpp b/utils/datetime/timestamp.hpp new file mode 100644 index 000000000..f824e97ab --- /dev/null +++ b/utils/datetime/timestamp.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include "utils/datetime/datetime_error.hpp" +#include "utils/total_ordering.hpp" + +class Timestamp : public TotalOrdering +{ +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"; +}; diff --git a/utils/exceptions/basic_exception.hpp b/utils/exceptions/basic_exception.hpp new file mode 100644 index 000000000..70418254b --- /dev/null +++ b/utils/exceptions/basic_exception.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +#include + +#include "utils/auto_scope.hpp" +#include "utils/stacktrace.hpp" + +class BasicException : public std::exception +{ +public: + template + BasicException(Args&&... args) noexcept + : message(fmt::format(std::forward(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; +}; + + diff --git a/utils/stacktrace.hpp b/utils/stacktrace.hpp new file mode 100644 index 000000000..30f851610 --- /dev/null +++ b/utils/stacktrace.hpp @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include + +#include +#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 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}; + } +};