diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f6589b..960e281 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -347,8 +347,18 @@ target_link_libraries(rhubarb-lib # ... rhubarb-logging add_library(rhubarb-logging + src/logging/Entry.cpp + src/logging/Entry.h + src/logging/Formatter.h + src/logging/formatters.cpp + src/logging/formatters.h + src/logging/Level.cpp + src/logging/Level.h src/logging/logging.cpp src/logging/logging.h + src/logging/Sink.h + src/logging/sinks.cpp + src/logging/sinks.h ) target_include_directories(rhubarb-logging PUBLIC "src/logging") target_link_libraries(rhubarb-logging diff --git a/src/logging/Entry.cpp b/src/logging/Entry.cpp new file mode 100644 index 0000000..8772c7b --- /dev/null +++ b/src/logging/Entry.cpp @@ -0,0 +1,38 @@ +#include "Entry.h" + +#include +#include +#include + +using std::lock_guard; +using std::unordered_map; +using std::string; + +namespace logging { + + // Returns an int representing the current thread. + // This used to be a simple thread_local variable, but Xcode doesn't support that yet + int getThreadCounter() { + using thread_id = std::thread::id; + + static std::mutex counterMutex; + lock_guard lock(counterMutex); + + static unordered_map threadCounters; + static int lastThreadId = 0; + thread_id threadId = std::this_thread::get_id(); + if (threadCounters.find(threadId) == threadCounters.end()) { + threadCounters.insert({threadId, ++lastThreadId}); + } + return threadCounters.find(threadId)->second; + } + + Entry::Entry(Level level, const string& message) : + level(level), + message(message) + { + time(×tamp); + this->threadCounter = getThreadCounter(); + } + +} diff --git a/src/logging/Entry.h b/src/logging/Entry.h new file mode 100644 index 0000000..ff4961b --- /dev/null +++ b/src/logging/Entry.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Level.h" + +namespace logging { + + struct Entry { + Entry(Level level, const std::string& message); + + time_t timestamp; + int threadCounter; + Level level; + std::string message; + }; + +} diff --git a/src/logging/Formatter.h b/src/logging/Formatter.h new file mode 100644 index 0000000..9393b66 --- /dev/null +++ b/src/logging/Formatter.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "Entry.h" + +namespace logging { + + class Formatter { + public: + virtual ~Formatter() = default; + virtual std::string format(const Entry& entry) = 0; + }; + +} diff --git a/src/logging/Level.cpp b/src/logging/Level.cpp new file mode 100644 index 0000000..ce4dfff --- /dev/null +++ b/src/logging/Level.cpp @@ -0,0 +1,35 @@ +#include "Level.h" + +using std::string; + +namespace logging { + + LevelConverter& LevelConverter::get() { + static LevelConverter converter; + return converter; + } + + string LevelConverter::getTypeName() { + return "Level"; + } + + EnumConverter::member_data LevelConverter::getMemberData() { + return member_data{ + {Level::Trace, "Trace"}, + {Level::Debug, "Debug"}, + {Level::Info, "Info"}, + {Level::Warn, "Warn"}, + {Level::Error, "Error"}, + {Level::Fatal, "Fatal"} + }; + } + + std::ostream& operator<<(std::ostream& stream, Level value) { + return LevelConverter::get().write(stream, value); + } + + std::istream& operator >> (std::istream& stream, Level& value) { + return LevelConverter::get().read(stream, value); + } + +} diff --git a/src/logging/Level.h b/src/logging/Level.h new file mode 100644 index 0000000..a487284 --- /dev/null +++ b/src/logging/Level.h @@ -0,0 +1,29 @@ +#pragma once + +#include "EnumConverter.h" + +namespace logging { + + enum class Level { + Trace, + Debug, + Info, + Warn, + Error, + Fatal, + EndSentinel + }; + + class LevelConverter : public EnumConverter { + public: + static LevelConverter& get(); + protected: + std::string getTypeName() override; + member_data getMemberData() override; + }; + + std::ostream& operator<<(std::ostream& stream, Level value); + + std::istream& operator >> (std::istream& stream, Level& value); + +} diff --git a/src/logging/Sink.h b/src/logging/Sink.h new file mode 100644 index 0000000..bfeafab --- /dev/null +++ b/src/logging/Sink.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Entry.h" + +namespace logging { + + class Sink { + public: + virtual ~Sink() = default; + virtual void receive(const Entry& entry) = 0; + }; + +} diff --git a/src/logging/formatters.cpp b/src/logging/formatters.cpp new file mode 100644 index 0000000..7447b35 --- /dev/null +++ b/src/logging/formatters.cpp @@ -0,0 +1,18 @@ +#include "formatters.h" +#include +#include "Entry.h" +#include "tools.h" + +using std::string; + +namespace logging { + + string SimpleConsoleFormatter::format(const Entry& entry) { + return fmt::format("[{0}] {1}", entry.level, entry.message); + } + + string SimpleFileFormatter::format(const Entry& entry) { + return fmt::format("[{0}] {1} {2}", formatTime(entry.timestamp, "%F %H:%M:%S"), entry.threadCounter, consoleFormatter.format(entry)); + } + +} diff --git a/src/logging/formatters.h b/src/logging/formatters.h new file mode 100644 index 0000000..ca68fd2 --- /dev/null +++ b/src/logging/formatters.h @@ -0,0 +1,19 @@ +#pragma once + +#include "Formatter.h" + +namespace logging { + + class SimpleConsoleFormatter : public Formatter { + public: + std::string format(const Entry& entry) override; + }; + + class SimpleFileFormatter : public Formatter { + public: + std::string format(const Entry& entry) override; + private: + SimpleConsoleFormatter consoleFormatter; + }; + +} diff --git a/src/logging/logging.cpp b/src/logging/logging.cpp index f5b95fd..d73496c 100644 --- a/src/logging/logging.cpp +++ b/src/logging/logging.cpp @@ -1,130 +1,13 @@ #include "logging.h" #include -#include -#include -#include -#include +#include +#include "Entry.h" using namespace logging; using std::string; using std::vector; using std::shared_ptr; using std::lock_guard; -using std::unordered_map; - -LevelConverter& LevelConverter::get() { - static LevelConverter converter; - return converter; -} - -string LevelConverter::getTypeName() { - return "Level"; -} - -EnumConverter::member_data LevelConverter::getMemberData() { - return member_data { - { Level::Trace, "Trace" }, - { Level::Debug, "Debug" }, - { Level::Info, "Info" }, - { Level::Warn, "Warn" }, - { Level::Error, "Error" }, - { Level::Fatal, "Fatal" } - }; -} - -std::ostream& logging::operator<<(std::ostream& stream, Level value) { - return LevelConverter::get().write(stream, value); -} - -std::istream& logging::operator>>(std::istream& stream, Level& value) { - return LevelConverter::get().read(stream, value); -} - -// Returns an int representing the current thread. -// This used to be a simple thread_local variable, but Xcode doesn't support that yet -int getThreadCounter() { - using thread_id = std::thread::id; - - static std::mutex counterMutex; - lock_guard lock(counterMutex); - - static unordered_map threadCounters; - static int lastThreadId = 0; - thread_id threadId = std::this_thread::get_id(); - if (threadCounters.find(threadId) == threadCounters.end()) { - threadCounters.insert({threadId, ++lastThreadId}); - } - return threadCounters.find(threadId)->second; -} - -Entry::Entry(Level level, const string& message) : - level(level), - message(message) -{ - time(×tamp); - this->threadCounter = getThreadCounter(); -} - -string SimpleConsoleFormatter::format(const Entry& entry) { - return fmt::format("[{0}] {1}", entry.level, entry.message); -} - -string SimpleFileFormatter::format(const Entry& entry) { - return fmt::format("[{0}] {1} {2}", formatTime(entry.timestamp, "%F %H:%M:%S"), entry.threadCounter, consoleFormatter.format(entry)); -} - -LevelFilter::LevelFilter(shared_ptr innerSink, Level minLevel) : - innerSink(innerSink), - minLevel(minLevel) -{} - -void LevelFilter::receive(const Entry& entry) { - if (entry.level >= minLevel) { - innerSink->receive(entry); - } -} - -StreamSink::StreamSink(shared_ptr stream, shared_ptr formatter) : - stream(stream), - formatter(formatter) -{} - -void StreamSink::receive(const Entry& entry) { - string line = formatter->format(entry); - *stream << line << std::endl; -} - -StdErrSink::StdErrSink(shared_ptr formatter) : - StreamSink(std::shared_ptr(&std::cerr, [](void*) {}), formatter) -{} - -PausableSink::PausableSink(shared_ptr innerSink) : - innerSink(innerSink) -{} - -void PausableSink::receive(const Entry& entry) { - lock_guard lock(mutex); - if (isPaused) { - buffer.push_back(entry); - } else { - innerSink->receive(entry); - } -} - -void PausableSink::pause() { - lock_guard lock(mutex); - isPaused = true; - -} - -void PausableSink::resume() { - lock_guard lock(mutex); - isPaused = false; - for (const Entry& entry : buffer) { - innerSink->receive(entry); - } - buffer.clear(); -} std::mutex& getLogMutex() { static std::mutex mutex; diff --git a/src/logging/logging.h b/src/logging/logging.h index 44d6240..b06c969 100644 --- a/src/logging/logging.h +++ b/src/logging/logging.h @@ -1,103 +1,11 @@ #pragma once -#include -#include -#include "tools.h" #include "EnumConverter.h" +#include "Sink.h" +#include "Level.h" namespace logging { - enum class Level { - Trace, - Debug, - Info, - Warn, - Error, - Fatal, - EndSentinel - }; - - class LevelConverter : public EnumConverter { - public: - static LevelConverter& get(); - protected: - std::string getTypeName() override; - member_data getMemberData() override; - }; - - std::ostream& operator<<(std::ostream& stream, Level value); - - std::istream& operator>>(std::istream& stream, Level& value); - - struct Entry { - Entry(Level level, const std::string& message); - - time_t timestamp; - int threadCounter; - Level level; - std::string message; - }; - - class Formatter { - public: - virtual ~Formatter() = default; - virtual std::string format(const Entry& entry) = 0; - }; - - class SimpleConsoleFormatter : public Formatter { - public: - std::string format(const Entry& entry) override; - }; - - class SimpleFileFormatter : public Formatter { - public: - std::string format(const Entry& entry) override; - private: - SimpleConsoleFormatter consoleFormatter; - }; - - class Sink { - public: - virtual ~Sink() = default; - virtual void receive(const Entry& entry) = 0; - }; - - class LevelFilter : public Sink { - public: - LevelFilter(std::shared_ptr innerSink, Level minLevel); - void receive(const Entry& entry) override; - private: - std::shared_ptr innerSink; - Level minLevel; - }; - - class StreamSink : public Sink { - public: - StreamSink(std::shared_ptr stream, std::shared_ptr formatter); - void receive(const Entry& entry) override; - private: - std::shared_ptr stream; - std::shared_ptr formatter; - }; - - class StdErrSink : public StreamSink { - public: - explicit StdErrSink(std::shared_ptr formatter); - }; - - class PausableSink : public Sink { - public: - explicit PausableSink(std::shared_ptr innerSink); - void receive(const Entry& entry) override; - void pause(); - void resume(); - private: - std::shared_ptr innerSink; - std::vector buffer; - std::mutex mutex; - bool isPaused = false; - }; - void addSink(std::shared_ptr sink); void log(Level level, const std::string& message); diff --git a/src/logging/sinks.cpp b/src/logging/sinks.cpp new file mode 100644 index 0000000..a2be5cf --- /dev/null +++ b/src/logging/sinks.cpp @@ -0,0 +1,64 @@ +#include "sinks.h" +#include +#include "Entry.h" + +using std::string; +using std::lock_guard; +using std::shared_ptr; + +namespace logging { + + LevelFilter::LevelFilter(shared_ptr innerSink, Level minLevel) : + innerSink(innerSink), + minLevel(minLevel) + {} + + void LevelFilter::receive(const Entry& entry) { + if (entry.level >= minLevel) { + innerSink->receive(entry); + } + } + + StreamSink::StreamSink(shared_ptr stream, shared_ptr formatter) : + stream(stream), + formatter(formatter) + {} + + void StreamSink::receive(const Entry& entry) { + string line = formatter->format(entry); + *stream << line << std::endl; + } + + StdErrSink::StdErrSink(shared_ptr formatter) : + StreamSink(std::shared_ptr(&std::cerr, [](void*) {}), formatter) + {} + + PausableSink::PausableSink(shared_ptr innerSink) : + innerSink(innerSink) + {} + + void PausableSink::receive(const Entry& entry) { + lock_guard lock(mutex); + if (isPaused) { + buffer.push_back(entry); + } else { + innerSink->receive(entry); + } + } + + void PausableSink::pause() { + lock_guard lock(mutex); + isPaused = true; + + } + + void PausableSink::resume() { + lock_guard lock(mutex); + isPaused = false; + for (const Entry& entry : buffer) { + innerSink->receive(entry); + } + buffer.clear(); + } + +} diff --git a/src/logging/sinks.h b/src/logging/sinks.h new file mode 100644 index 0000000..460be40 --- /dev/null +++ b/src/logging/sinks.h @@ -0,0 +1,48 @@ +#pragma once + +#include "Sink.h" +#include +#include "Formatter.h" +#include +#include + +namespace logging { + enum class Level; + + class LevelFilter : public Sink { + public: + LevelFilter(std::shared_ptr innerSink, Level minLevel); + void receive(const Entry& entry) override; + private: + std::shared_ptr innerSink; + Level minLevel; + }; + + class StreamSink : public Sink { + public: + StreamSink(std::shared_ptr stream, std::shared_ptr formatter); + void receive(const Entry& entry) override; + private: + std::shared_ptr stream; + std::shared_ptr formatter; + }; + + class StdErrSink : public StreamSink { + public: + explicit StdErrSink(std::shared_ptr formatter); + }; + + class PausableSink : public Sink { + public: + explicit PausableSink(std::shared_ptr innerSink); + void receive(const Entry& entry) override; + void pause(); + void resume(); + private: + std::shared_ptr innerSink; + std::vector buffer; + std::mutex mutex; + bool isPaused = false; + }; + +} diff --git a/src/main.cpp b/src/main.cpp index 6bf0d6a..c4c23a2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,6 +5,8 @@ #include "NiceCmdLineOutput.h" #include "ProgressBar.h" #include "logging.h" +#include "sinks.h" +#include "formatters.h" #include #include "Exporter.h" #include "ContinuousTimeline.h"