Skip to content

Commit da942e1

Browse files
committed
Log/Logger: Asynchronous and configurable
1 parent 34b9b67 commit da942e1

90 files changed

Lines changed: 1163 additions & 690 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
### 🚀 Execution
4141
- [`Event`](modules/Execution/Event.mpp) - An event for thread synchronization
4242
- [`EventDispatcher`](modules/Execution/EventDispatcher.mpp) - Event system to subscribe functions and trigger actions by event name
43+
- [`EventQueue`](modules/Execution/EventQueue.mpp) - Thread-safe event queue running on a dedicated thread for asynchronous event processing (preserves event order)
4344
- [`ScopeGuard`](modules/Execution/ScopeGuard.mpp) - RAII utility to execute a function when leaving a scope, ensuring resource cleanup
4445

4546
### 📁 File System
@@ -57,7 +58,8 @@
5758
- [`VirtualMachine`](modules/Language/VirtualMachine.mpp) - Generic virtual machine
5859

5960
### 📝 Logging & Benchmarking
60-
- [`Logger`](modules/Log/Logger.mpp) - Formattable logger with customizable log types and appearances
61+
- [`Logger`](modules/Log/Logger.mpp) - Asynchronous, configurable and formattable logger with support for colors and customizable log types
62+
- [`FileSink`](modules/Log/FileSink.mpp) - Pluggable sink for automated file logging with integrated rotation and file size management
6163
- [`LogRotate`](modules/Log/LogRotate.mpp) - Log file rotation based on maximum file size
6264
- [`ChronoLogger`](modules/Log/ChronoLogger.mpp) - RAII timer that logs elapsed time at scope exit
6365

@@ -74,6 +76,10 @@
7476
- [`Client`](modules/Network/Client.mpp) - TCP client with synchronous and asynchronous modes
7577
- [`Server`](modules/Network/Server.mpp) - TCP server with multi-client support
7678

79+
### 🧩 Patterns
80+
- [`Singleton`](modules/Pattern/Singleton.mpp) - Generic Meyers Singleton implementation (thread-safe and lazy)
81+
- [`Multiton`](modules/Pattern/Multiton.mpp) - Generic Multiton implementation based on the Meyers Singleton
82+
7783
### 💻 Terminal
7884
- [`Canvas`](modules/Terminal/Canvas.mpp) - Terminal-based 2D drawing surface
7985
- [`Cursor`](modules/Terminal/Cursor.mpp) - Terminal cursor manipulation
@@ -98,7 +104,6 @@
98104
- [`ReadOnlyAccessor`](modules/Thread/SharedLocker.mpp) - RAII accessor for shared (non-exclusive) reading of a `SharedLocker`, allowing parallel access
99105
- [`MultipleAccessor`](modules/Thread/UniqueLocker.mpp) - RAII accessor for multiple lockers, safely acquiring them to avoid deadlocks and data races
100106

101-
102107
### 🏷️ Type
103108
- [`Concept`](modules/Type/Concept.mpp) - Extensions to `<type_traits>` and `<concepts>` providing additional compile-time checks and utilities
104109
- [`Enum`](modules/Type/Enum.mpp) - Generic enum-to-string conversion

modules/Container/BidirectionalMap.mpp

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,40 +33,20 @@ export namespace CppUtils::Container
3333
values{values}
3434
{}
3535

36-
#if defined(COMPILER_CLANG)
3736
[[nodiscard]] constexpr inline auto begin(this auto&& self) -> decltype(auto)
3837
{
3938
return std::begin(self.values);
4039
}
41-
#else
42-
[[nodiscard]] constexpr inline auto begin() const -> decltype(auto)
43-
{
44-
return std::begin(values);
45-
}
46-
[[nodiscard]] inline constexpr auto begin() -> decltype(auto)
47-
{
48-
return std::begin(values);
49-
}
50-
#endif
40+
5141
[[nodiscard]] constexpr inline auto cbegin() const -> decltype(auto)
5242
{
5343
return std::cbegin(values);
5444
}
55-
#if defined(COMPILER_CLANG)
45+
5646
[[nodiscard]] constexpr inline auto end(this auto&& self) -> decltype(auto)
5747
{
5848
return std::end(self.values);
5949
}
60-
#else
61-
[[nodiscard]] constexpr inline auto end() const -> decltype(auto)
62-
{
63-
return std::end(values);
64-
}
65-
[[nodiscard]] inline constexpr auto end() -> decltype(auto)
66-
{
67-
return std::end(values);
68-
}
69-
#endif
7050
[[nodiscard]] constexpr inline auto cend() const -> decltype(auto)
7151
{
7252
return std::cend(values);

modules/Container/DependencyGraph.mpp

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,12 @@ export namespace CppUtils::Container
4343
std::erase(node.dependencies, key);
4444
}
4545

46-
[[nodiscard]] inline auto operator[](const Key& key) const noexcept -> const Value&
46+
[[nodiscard]] inline auto operator[](this auto&& self, const Key& key) -> decltype(auto)
4747
{
48-
return nodes[key].value;
49-
}
50-
51-
[[nodiscard]] inline auto operator[](const Key& key) noexcept -> Value&
52-
{
53-
return nodes[key].value;
48+
if constexpr (std::is_const_v<std::remove_reference_t<decltype(self)>>)
49+
return (self.nodes.at(key).value);
50+
else
51+
return (self.nodes[key].value);
5452
}
5553

5654
inline auto addDependency(const Key& key, const Key& dependency) -> void
@@ -63,14 +61,12 @@ export namespace CppUtils::Container
6361
std::erase(nodes[key].dependencies, dependency);
6462
}
6563

66-
[[nodiscard]] inline auto getDependencies(const Key& key) const noexcept -> decltype(auto)
67-
{
68-
return nodes.at(key).dependencies;
69-
}
70-
71-
[[nodiscard]] inline auto getDependencies(const Key& key) noexcept -> decltype(auto)
64+
[[nodiscard]] inline auto getDependencies(this auto&& self, const Key& key) -> decltype(auto)
7265
{
73-
return nodes[key].dependencies;
66+
if constexpr (std::is_const_v<std::remove_reference_t<decltype(self)>>)
67+
return (self.nodes.at(key).dependencies);
68+
else
69+
return (self.nodes[key].dependencies);
7470
}
7571

7672
[[nodiscard]] inline auto hasCycle() -> bool

modules/Container/Tree.mpp

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,9 @@ export namespace CppUtils::Container
1616
ValueType value;
1717
std::vector<Node<T>> nodes = {};
1818

19-
[[nodiscard]] inline constexpr auto find(const ValueType& key) const noexcept -> auto
19+
[[nodiscard]] inline constexpr auto find(this auto&& self, const ValueType& key) noexcept -> auto
2020
{
21-
return std::ranges::find_if(nodes, [&key](const auto& node) -> bool {
22-
return node.value == key;
23-
});
24-
}
25-
26-
[[nodiscard]] inline constexpr auto find(const ValueType& key) noexcept -> auto
27-
{
28-
return std::ranges::find_if(nodes, [&key](const auto& node) -> bool {
21+
return std::ranges::find_if(self.nodes, [&key](const auto& node) -> bool {
2922
return node.value == key;
3023
});
3124
}
@@ -35,18 +28,13 @@ export namespace CppUtils::Container
3528
return find(key) != std::cend(nodes);
3629
}
3730

38-
[[nodiscard]] inline constexpr auto operator[](const ValueType& key) -> Node<T>&
39-
{
40-
for (auto& node : nodes)
41-
if (key == node.value)
42-
return node;
43-
return nodes.emplace_back(key);
44-
}
45-
46-
[[nodiscard]] inline constexpr auto operator[](const ValueType& key) const -> const Node<T>&
31+
[[nodiscard]] inline constexpr auto operator[](this auto&& self, const ValueType& key) -> decltype(auto)
4732
{
48-
if (const auto value = find(key); value == std::cend(nodes)) [[unlikely]]
49-
throw std::out_of_range{"The Node does not contain the requested child."};
33+
if (const auto value = self.find(key); value == std::end(self.nodes)) [[unlikely]]
34+
if constexpr (std::is_const_v<std::remove_reference_t<decltype(self)>>)
35+
throw std::out_of_range{"The Node does not contain the requested child."};
36+
else
37+
return self.nodes.emplace_back(key);
5038
else
5139
return *value;
5240
}
@@ -158,8 +146,7 @@ export namespace CppUtils::Container
158146
namespace std
159147
{
160148
template<class T, class CharT>
161-
requires CppUtils::Type::Specializes<T, CppUtils::Container::Tree::Node> and
162-
CppUtils::Type::Printable<typename T::ValueType>
149+
requires CppUtils::Type::Specializes<T, CppUtils::Container::Tree::Node> and CppUtils::Type::Printable<typename T::ValueType>
163150
struct formatter<T, CharT>
164151
{
165152
inline constexpr auto parse(std::format_parse_context& context) -> auto

modules/Container/TypedStack.mpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,8 @@ export namespace CppUtils::Container
173173
return std::array{+copyToDestinationImpl<SourceType, SupportedTypes>()...};
174174
};
175175

176-
static constexpr auto copyFromSource = std::array<std::array<void (*)(TypedStack<SupportedTypes...>&, std::size_t, std::size_t), sizeof...(SupportedTypes)>, sizeof...(SupportedTypes)> {
177-
copyToDestination.template operator()<SupportedTypes>()...
178-
};
176+
static constexpr auto copyFromSource = std::array<std::array<void (*)(TypedStack<SupportedTypes...>&, std::size_t, std::size_t), sizeof...(SupportedTypes)>, sizeof...(SupportedTypes)>{
177+
copyToDestination.template operator()<SupportedTypes>()...};
179178

180179
if (sourcePosition >= size())
181180
throw std::out_of_range{std::format("TypedStack::copy({}, {}) : Source out of range", sourcePosition, destinationPosition)};
@@ -207,7 +206,7 @@ export namespace CppUtils::Container
207206

208207
inline constexpr auto print() const -> void
209208
{
210-
Logger::printSeparator<"debug">();
209+
Logger::emit<"separator">();
211210
Logger::print<"debug">("Stack size: {} elements; {} bytes", size(), getByteSize());
212211
for (auto i = size(); i > 0;)
213212
print(--i);

modules/CppUtils.mpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@ export import CppUtils.Execution;
77
export import CppUtils.FileSystem;
88
export import CppUtils.Functional;
99
export import CppUtils.Language;
10-
export import CppUtils.ChronoLogger;
11-
export import CppUtils.Logger;
12-
export import CppUtils.LogRotate;
10+
export import CppUtils.Log;
1311
export import CppUtils.Math;
1412
export import CppUtils.Memory;
1513
export import CppUtils.Network;
14+
export import CppUtils.Pattern;
1615
export import CppUtils.String;
1716
export import CppUtils.System;
1817
export import CppUtils.Terminal;

modules/Execution/EventDispatcher.mpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,16 @@ export namespace CppUtils::Execution
1313
public:
1414
template<String::Hasher eventName = String::Hash{}, class... Args>
1515
inline auto emit(const Args&... args) -> void
16+
{
17+
emit(static_cast<String::Hash>(eventName), args...);
18+
}
19+
20+
template<class... Args>
21+
inline auto emit(String::Hash eventName, const Args&... args) -> void
1622
{
1723
auto lockGuard = std::shared_lock{m_mutex};
1824
using Tuple = std::tuple<std::remove_cvref_t<Args>...>;
19-
auto key = std::make_pair(static_cast<String::Hash>(eventName), std::type_index{typeid(Tuple)});
25+
auto key = std::make_pair(eventName, std::type_index{typeid(Tuple)});
2026
if (auto subscriberIt = m_subscribers.find(key); subscriberIt != std::cend(m_subscribers))
2127
{
2228
auto payload = std::make_tuple(args...);
@@ -35,7 +41,7 @@ export namespace CppUtils::Execution
3541
auto lockGuard = std::unique_lock{m_mutex};
3642
auto key = std::make_pair(static_cast<String::Hash>(eventName), std::type_index{typeid(Tuple)});
3743
m_subscribers[key].emplace_back(
38-
[function = std::forward<decltype(function)>(function)](const void* payload) -> void {
44+
[function = std::forward<decltype(function)>(function)](const void* payload) mutable -> void {
3945
const auto& args = *static_cast<const Tuple*>(payload);
4046
std::apply(function, args);
4147
});

modules/Execution/EventQueue.mpp

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
export module CppUtils.Execution.EventQueue;
2+
3+
import std;
4+
import CppUtils.String.Hash;
5+
import CppUtils.Execution.EventDispatcher;
6+
import CppUtils.Execution.ScopeGuard;
7+
import CppUtils.Thread.ThreadLoop;
8+
import CppUtils.Thread.UniqueLocker;
9+
10+
export namespace CppUtils::Execution
11+
{
12+
class EventQueue
13+
{
14+
public:
15+
inline EventQueue():
16+
m_worker{
17+
[this] { workerThread(); },
18+
[this] { m_condition.notify_all(); }}
19+
{
20+
m_worker.start();
21+
}
22+
23+
inline ~EventQueue()
24+
{
25+
waitUntilFinished();
26+
}
27+
28+
EventQueue(const EventQueue&) = delete;
29+
EventQueue& operator=(const EventQueue&) = delete;
30+
EventQueue(EventQueue&&) = delete;
31+
EventQueue& operator=(EventQueue&&) = delete;
32+
33+
template<String::Hasher eventName = String::Hash{}, class... Args>
34+
inline auto emit(Args&&... args) -> void
35+
{
36+
emit(static_cast<String::Hash>(eventName), std::forward<Args>(args)...);
37+
}
38+
39+
template<class... Args>
40+
inline auto emit(String::Hash eventName, Args&&... args) -> void
41+
{
42+
auto task = [this, eventName, payload = std::make_tuple(std::forward<Args>(args)...)]() mutable {
43+
std::apply([this, eventName](auto&&... args) {
44+
m_dispatcher.emit(eventName, std::forward<decltype(args)>(args)...);
45+
}, std::move(payload));
46+
};
47+
enqueue(std::move(task));
48+
}
49+
50+
template<String::Hasher eventName = String::Hash{}>
51+
inline auto subscribe(auto&& function) -> void
52+
{
53+
m_dispatcher.subscribe<eventName>(std::forward<decltype(function)>(function));
54+
}
55+
56+
inline auto waitUntilFinished() -> void
57+
{
58+
auto accessor = m_queue.access();
59+
m_condition.wait(accessor.getLockGuard(), [this, &accessor] { return accessor.value().empty() and not m_isTaskRunning; });
60+
}
61+
62+
private:
63+
inline auto enqueue(std::function<void()> task) -> void
64+
{
65+
{
66+
auto accessor = m_queue.access();
67+
accessor.value().push(std::move(task));
68+
}
69+
m_condition.notify_one();
70+
}
71+
72+
inline auto workerThread() -> void
73+
{
74+
auto task = std::function<void()>{};
75+
{
76+
auto accessor = m_queue.access();
77+
m_condition.wait(accessor.getLockGuard(), [this, &accessor] {
78+
return not accessor.value().empty() or m_worker.isStopRequested();
79+
});
80+
81+
if (accessor.value().empty())
82+
return;
83+
84+
task = std::move(accessor.value().front());
85+
accessor.value().pop();
86+
}
87+
if (task)
88+
{
89+
m_isTaskRunning = true;
90+
auto _ = ScopeGuard{[this] { m_isTaskRunning = false; }};
91+
task();
92+
}
93+
m_condition.notify_all();
94+
}
95+
96+
EventDispatcher m_dispatcher;
97+
Thread::UniqueLocker<std::queue<std::function<void()>>> m_queue;
98+
std::condition_variable m_condition;
99+
Thread::ThreadLoop m_worker;
100+
std::atomic<bool> m_isTaskRunning = false;
101+
};
102+
}

modules/Execution/Execution.mpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ export module CppUtils.Execution;
22

33
export import CppUtils.Execution.Event;
44
export import CppUtils.Execution.EventDispatcher;
5+
export import CppUtils.Execution.EventQueue;
56
export import CppUtils.Execution.ScopeGuard;

modules/Language/ASTParser.mpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ export namespace CppUtils::Language
1313
{
1414
ASTParser()
1515
{
16-
using namespace std::literals;
1716
using namespace String::Literals;
1817

1918
functions["abort"_token] = [](Cursor&, const ASTNode&, MetaEvaluator&) -> std::expected<void, std::string_view> {

0 commit comments

Comments
 (0)