neoGFX
Cross-platform C++ app/game engine
Loading...
Searching...
No Matches
logger.hpp
Go to the documentation of this file.
1// logger.hpp
2/*
3 * Copyright (c) 2020 Leigh Johnston.
4 *
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are
9 * met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 *
14 * * Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * * Neither the name of Leigh Johnston nor the names of any
19 * other contributors to this software may be used to endorse or
20 * promote products derived from this software without specific prior
21 * written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
24 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
25 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
26 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
28 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
29 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 */
35
36#pragma once
37
38#include <neolib/neolib.hpp>
39#include <map>
40#include <vector>
41#include <thread>
42#include <mutex>
43#include <condition_variable>
44#include <string>
45#include <memory>
46#include <chrono>
49
50namespace neolib
51{
52 namespace logger
53 {
54 template <std::size_t Instance = 0>
55 class logger : public i_logger, public lifetime<>
56 {
57 typedef logger<Instance> self_type;
58 public:
59 define_declared_event(NewLogMessage, new_log_message, i_string const&)
60 protected:
61 typedef std::string buffer_t;
62 private:
63 typedef std::map<category_id, std::pair<bool, std::string>> category_map_t;
64 typedef std::map<std::thread::id, std::pair<std::shared_ptr<buffer_t>, std::shared_ptr<buffer_t>>> buffer_list_t;
65 typedef std::vector<i_logger*> copy_list_t;
66 public:
68 {
69 }
71 {
73 }
74 public:
75 void copy_to(i_logger& aLogger) final
76 {
77 std::lock_guard<std::recursive_mutex> lg{ mutex() };
78 copies().push_back(&aLogger);
79 }
80 void cancel_copy_to(i_logger& aLogger) final
81 {
82 std::lock_guard<std::recursive_mutex> lg{ mutex() };
83 copies().erase(std::remove(copies().begin(), copies().end(), &aLogger), copies().end());
84 }
85 bool has_logging_thread() const final
86 {
87 std::lock_guard<std::recursive_mutex> lg{ mutex() };
88 return iLoggingThread != std::nullopt;
89 }
91 {
92 std::lock_guard<std::recursive_mutex> lg{ mutex() };
93 if (iLoggingThread)
94 throw logging_thread_already_created();
95 iLoggingThread.emplace([&]()
96 {
97 for(;;)
98 {
99 std::unique_lock<std::mutex> lk(commit_signal_mutex());
100 iCommitSignal.wait(lk, [&]() { return any_available() || is_destroying(); });
101 commit();
102 if (is_destroying())
103 break;
104 };
105 });
106 }
107 public:
109 {
110 std::lock_guard<std::recursive_mutex> lg{ mutex() };
111 return iFilterSeverity;
112 }
113 void set_filter_severity(severity aSeverity) final
114 {
115 std::lock_guard<std::recursive_mutex> lg{ mutex() };
116 iFilterSeverity = aSeverity;
117 }
122 void register_category(category_id aId, i_string const& aName) final
123 {
124 std::lock_guard<std::recursive_mutex> lg{ mutex() };
125 iCategories[aId].first = true;
126 iCategories[aId].second = aName.to_std_string_view();
127 for (auto& copy : copies())
128 copy->register_category(aId, aName);
129 }
130 bool category_enabled(category_id aId) const final
131 {
132 std::lock_guard<std::recursive_mutex> lg{ mutex() };
133 auto existing = iCategories.find(aId);
134 return existing != iCategories.end() && existing->second.first;
135 }
137 {
138 std::lock_guard<std::recursive_mutex> lg{ mutex() };
139 auto existing = iCategories.find(aId);
140 if (existing != iCategories.end())
141 existing->second.first = true;
142 for (auto& copy : copies())
143 copy->enable_category(aId);
144 }
146 {
147 std::lock_guard<std::recursive_mutex> lg{ mutex() };
148 auto existing = iCategories.find(aId);
149 if (existing != iCategories.end())
150 existing->second.first = false;
151 for (auto& copy : copies())
152 copy->disable_category(aId);
153 }
154 public:
155 bool has_formatter() const final
156 {
157 std::lock_guard<std::recursive_mutex> lg{ mutex() };
158 return iFormatter != nullptr;
159 }
160 i_formatter& formatter() const final
161 {
162 std::lock_guard<std::recursive_mutex> lg{ mutex() };
163 if (iFormatter != nullptr)
164 return *iFormatter;
165 throw no_formatter();
166 }
167 void set_formatter(i_formatter& aFormatter) final
168 {
169 std::lock_guard<std::recursive_mutex> lg{ mutex() };
170 iFormatter = std::shared_ptr<i_formatter>{ std::shared_ptr<i_formatter>{}, &aFormatter };
171 }
172 void clear_formatter() final
173 {
174 std::lock_guard<std::recursive_mutex> lg{ mutex() };
175 iFormatter = nullptr;
176 }
177 public:
178 line_id_t line_id() const final
179 {
180 return iLineId;
181 }
183 {
184 iLineId = aLineId;
185 }
186 public:
187 using i_logger::operator<<;
188 i_logger& operator<<(severity aSeverity) final
189 {
190 std::lock_guard<std::recursive_mutex> lg{ mutex() };
191 set_message_severity(aSeverity);
192 for (auto& copy : copies())
193 (*copy) << aSeverity;
194 return *this;
195 }
197 {
198 std::lock_guard<std::recursive_mutex> lg{ mutex() };
199 set_message_category(aCategory);
200 for (auto& copy : copies())
201 (*copy) << aCategory;
202 return *this;
203 }
204 protected:
205 std::recursive_mutex& mutex() const
206 {
207 return iMutex;
208 }
209 std::mutex& commit_signal_mutex() const
210 {
211 return iCommitSignalMutex;
212 }
213 std::condition_variable& commit_signal() const
214 {
215 return iCommitSignal;
216 }
218 {
219 if (iLoggingThread)
220 iLoggingThread->join();
221 }
223 {
224 return message_severity_ref();
225 }
226 void set_message_severity(severity aMessageSeverity)
227 {
228 message_severity_ref() = aMessageSeverity;
229 }
231 {
232 return message_category_ref();
233 }
235 {
236 message_category_ref() = aid;
237 }
239 {
240 std::lock_guard<std::recursive_mutex> lg{ mutex() };
241 auto existing = iCategories.find(message_category());
242 return existing == iCategories.end() || existing->second.first;
243 }
244 public:
245 void commit() override
246 {
247 if (!iLoggingThread || std::this_thread::get_id() == iLoggingThread->get_id())
248 {
249 {
250 std::lock_guard<std::recursive_mutex> lg{ mutex() };
251 for (auto& entry : buffers())
252 {
253 auto& buffers = entry.second;
254 std::swap(buffers.first, buffers.second);
255 }
256 }
257 thread_local buffer_t tempBuffer;
258 for (auto& entry : buffers())
259 {
260 auto& buffer = *entry.second.second;
261 if (!buffer.empty())
262 {
263 tempBuffer += buffer;
264 buffer.clear();
265 }
266 }
267 commit(tempBuffer);
268 tempBuffer.clear();
269 }
270 else
271 commit_signal().notify_one();
272 }
273 void wait() const final
274 {
275 if (iLoggingThread)
276 {
277 while (any_available())
278 {
279 using namespace std::chrono_literals;
280 std::this_thread::sleep_for(10ms);
281 }
282 }
283 }
284 protected:
285 void flush(i_string const& aMessage) final
286 {
287 bool notify = false;
288 {
289 std::scoped_lock lg{ mutex(), commit_signal_mutex() };
291 {
292 if (!has_formatter())
293 {
294 buffer() += aMessage.to_std_string_view();
295 NewLogMessage.trigger(aMessage);
296 }
297 else
298 {
299 thread_local string tempFormattedMessage;
300 formatter().format(*this, aMessage, tempFormattedMessage);
301 buffer() += tempFormattedMessage.to_std_string_view();
302 NewLogMessage.trigger(tempFormattedMessage);
303 tempFormattedMessage.clear();
304 }
305 ++iLineId;
306 notify = true;
307 }
308 for (auto& copy : copies())
309 copy->flush(aMessage);
310 }
311 if (notify)
312 commit_signal().notify_one();
313 }
314 protected:
315 virtual void commit(buffer_t const& aBuffer) = 0;
316 protected:
317 void finalize()
318 {
319 if (has_logging_thread())
320 {
321 wait();
322 {
323 std::unique_lock<std::mutex> lk(commit_signal_mutex());
325 }
326 commit_signal().notify_one();
328 }
329 else
330 {
332 commit();
333 }
334 }
335 private:
336 severity const& message_severity_ref() const
337 {
338 thread_local severity tMessageSeverity = severity::Info;
339 return tMessageSeverity;
340 }
341 severity& message_severity_ref()
342 {
343 return const_cast<severity&>(const_cast<self_type const&>(*this).message_severity_ref());
344 }
345 category_id const& message_category_ref() const
346 {
347 thread_local category_id tMessageCategory = {};
348 return tMessageCategory;
349 }
350 category_id& message_category_ref()
351 {
352 return const_cast<category_id&>(const_cast<self_type const&>(*this).message_category_ref());
353 }
354 bool any_available() const
355 {
356 std::lock_guard<std::recursive_mutex> lg{ mutex() };
357 for (auto& entry : buffers())
358 {
359 auto& buffer = *entry.second.first;
360 if (!buffer.empty())
361 return true;
362 }
363 return false;
364 }
365 buffer_t const& buffer() const
366 {
367 std::lock_guard<std::recursive_mutex> lg{ mutex() };
368 thread_local struct cleanup
369 {
370 logger const& parent;
371 destroyed_flag parentDestroyed;
372 ~cleanup()
373 {
374 if (!parentDestroyed)
375 {
376 parent.wait();
377 std::lock_guard<std::recursive_mutex> lg{ parent.mutex() };
378 parent.buffers().erase(std::this_thread::get_id());
379 }
380 }
381 } cleanup{ *this, *this };
382 auto existing = iBuffers.find(std::this_thread::get_id());
383 if (existing != iBuffers.end())
384 return *existing->second.first;
385 existing = iBuffers.insert(buffer_list_t::value_type{ std::this_thread::get_id(), buffer_list_t::mapped_type{ std::make_shared<buffer_t>(), std::make_shared<buffer_t>() } }).first;
386 return *existing->second.first;
387 }
388 buffer_t& buffer()
389 {
390 return const_cast<buffer_t&>(const_cast<logger const&>(*this).buffer());
391 }
392 buffer_list_t& buffers() const
393 {
394 std::lock_guard<std::recursive_mutex> lg{ mutex() };
395 return iBuffers;
396 }
397 copy_list_t const& copies() const
398 {
399 return iCopies;
400 }
401 copy_list_t& copies()
402 {
403 return iCopies;
404 }
405 private:
406 mutable std::recursive_mutex iMutex;
407 mutable std::mutex iCommitSignalMutex;
408 mutable std::condition_variable iCommitSignal;
409 std::optional<std::thread> iLoggingThread;
410 severity iFilterSeverity = severity::Info;
411 category_map_t iCategories;
412 std::shared_ptr<i_formatter> iFormatter;
414 mutable buffer_list_t iBuffers;
415 copy_list_t iCopies;
416 public:
417 static uuid const& iid() { static uuid const sIid{ Instance + 0x442ed95b, 0x215c, 0x4b6e, 0xb945, { 0xf9, 0x61, 0xc4, 0xca, 0xd8, 0x7b } }; return sIid; }
418 };
419 }
420}
void set_destroying() override
Definition lifetime.hpp:165
bool is_destroying() const final
Definition lifetime.hpp:145
virtual void format(i_logger const &aLogger, i_string const &aUnformattedMessage, i_string &aFormattedMessage)=0
virtual bool category_enabled(category_id aId) const =0
virtual void enable_category(category_id aId)=0
virtual void register_category(category_id aId, i_string const &aName)=0
virtual void disable_category(category_id aId)=0
bool has_logging_thread() const final
Definition logger.hpp:85
void commit() override
Definition logger.hpp:245
void set_formatter(i_formatter &aFormatter) final
Definition logger.hpp:167
i_logger & operator<<(category_id aCategory) final
Definition logger.hpp:196
void flush(i_string const &aMessage) final
Definition logger.hpp:285
void create_logging_thread() final
Definition logger.hpp:90
void set_message_category(category_id aid)
Definition logger.hpp:234
void wait() const final
Definition logger.hpp:273
void reset_line_id(line_id_t aLineId=DefaultInitialLineId) final
Definition logger.hpp:182
void clear_formatter() final
Definition logger.hpp:172
bool has_formatter() const final
Definition logger.hpp:155
severity message_severity() const
Definition logger.hpp:222
void cancel_copy_to(i_logger &aLogger) final
Definition logger.hpp:80
void enable_category(category_id aId) final
Definition logger.hpp:136
void set_message_severity(severity aMessageSeverity)
Definition logger.hpp:226
void disable_category(category_id aId) final
Definition logger.hpp:145
virtual void commit(buffer_t const &aBuffer)=0
std::mutex & commit_signal_mutex() const
Definition logger.hpp:209
void set_filter_severity(severity aSeverity) final
Definition logger.hpp:113
severity filter_severity() const final
Definition logger.hpp:108
std::condition_variable & commit_signal() const
Definition logger.hpp:213
void copy_to(i_logger &aLogger) final
Definition logger.hpp:75
bool category_enabled(category_id aId) const final
Definition logger.hpp:130
i_logger & operator<<(severity aSeverity) final
Definition logger.hpp:188
bool message_category_enabled() const
Definition logger.hpp:238
std::recursive_mutex & mutex() const
Definition logger.hpp:205
line_id_t line_id() const final
Definition logger.hpp:178
void register_category(category_id aId, i_string const &aName) final
Definition logger.hpp:122
static uuid const & iid()
Definition logger.hpp:417
category_id message_category() const
Definition logger.hpp:230
i_formatter & formatter() const final
Definition logger.hpp:160
std::string_view to_std_string_view() const noexcept
Definition string.hpp:86
void clear() final
Definition string.hpp:92
neolib::logger::logger< 9999 > logger
Definition neogfx.hpp:162
constexpr line_id_t DefaultInitialLineId
Definition i_logger.hpp:72
uint64_t line_id_t
Definition i_logger.hpp:71
lifetime_flag< lifetime_state::Destroyed > destroyed_flag
Definition lifetime.hpp:77
void swap(plf::hive< element_type, allocator_type > &a, plf::hive< element_type, allocator_type > &b) noexcept(std::allocator_traits< allocator_type >::propagate_on_container_swap::value||std::allocator_traits< allocator_type >::is_always_equal::value)
Definition plf_hive.h:4776
#define define_declared_event(name, declName,...)
Definition event.hpp:195