CppTrail
Loading...
Searching...
No Matches
async_logger.h
Go to the documentation of this file.
1
11#ifndef CPPTRAIL_ASYNC_LOGGER_H
12#define CPPTRAIL_ASYNC_LOGGER_H
13
14#include <atomic>
15#include <condition_variable>
16#include <mutex>
17#include <queue>
18#include <stdexcept>
19#include <thread>
20#include <vector>
21
23
24namespace CppTrail {
32 class LoggerOverflowError : public std::runtime_error {
33 public:
38 : std::runtime_error("LoggerOverflowError") {
39 }
40 };
41
52 template<typename char_t>
53 class BasicAsyncLoggerImpl : public BasicLoggerImpl<char_t> {
54 public:
57
60
63
64 protected:
70 std::numeric_limits<std::size_t>::max(),
71 true, 1
72 ) {
73 }
74
82 std::size_t nMaxEntryCount,
83 bool bThrowOnOverflow,
84 std::size_t nWorkerCount
85 );
86
87 protected:
92 ~BasicAsyncLoggerImpl() override = default;
93
94 protected:
100 virtual void work(message_type oMessage) = 0;
101
102 public:
117 void log(message_type oMessage) final;
118
127 void start() final;
128
137 void stop() final;
138
146 void signalStop() final;
147
155 void join() final;
156
172 Status status() final;
173
174 protected:
185 virtual Status serviceStatus() = 0;
186
195 virtual void serviceStart() = 0;
196
204 virtual void serviceStop() = 0;
205
208 private:
213 std::mutex m_oQueueMutex;
215 std::deque<message_type> m_qEntryQueue;
217 std::size_t m_nMaxEntryCount;
219 bool m_bThrowOnOverflow;
226 std::mutex m_oWorkerMutex;
228 std::condition_variable m_oWorkerCv;
230 std::vector<std::thread> m_vWorkers;
232 std::size_t m_nWorkerCount;
234 std::atomic_bool m_bWorkerEngineOn{false};
241 std::mutex m_oKillerMutex;
243 std::vector<std::thread> m_oKillerThread;
245 std::condition_variable m_oKillerCv;
248 private:
255 void work();
256
262 void startWorkers();
263
269 void stopWorkers();
270
276 void waitForKiller();
277 };
278
285
288
289#if __cplusplus >= 202002L
292#endif
293
296
299
302 template<typename char_t>
304 const std::size_t nMaxEntryCount,
305 const bool bThrowOnOverflow,
306 const std::size_t nWorkerCount
307 ) : m_nMaxEntryCount(nMaxEntryCount),
308 m_bThrowOnOverflow(bThrowOnOverflow),
309 m_nWorkerCount(nWorkerCount) {
310 // Reserve workers
311 this->m_vWorkers.reserve(nWorkerCount);
312 this->m_oKillerThread.reserve(1);
313 }
314
315 template<typename char_t>
317 message_type oMessage
318 ) {
319 // Check if we accept log requests
320 if (!m_bWorkerEngineOn.load())return;
321
322 // Queue lock
323 std::unique_lock<std::mutex> oQueueLock(this->m_oQueueMutex);
324
325 // Overflow check
326 if (this->m_qEntryQueue.size() + 1 > this->m_nMaxEntryCount) {
327 // Throw when overflow
328 if (this->m_bThrowOnOverflow)
329 throw LoggerOverflowError{};
330 // Ignore when overflow
331 return;
332 }
333
334 // Push back message
335 this->m_qEntryQueue.push_back(std::move(oMessage));
336
337 // Wake up call
338 this->m_oWorkerCv.notify_one();
339 }
340
341
342 template<typename char_t>
344 if (!this->m_bWorkerEngineOn.load()) {
345 // Create workers
346 this->m_vWorkers.clear();
347 for (std::size_t i = 0; i < this->m_nWorkerCount; i++) {
348 this->m_vWorkers.emplace_back([this] {
349 this->work();
350 });
351 }
352 // Update status
353 this->m_bWorkerEngineOn.store(true);
354 }
355 }
356
357 template<typename char_t>
358 void BasicAsyncLoggerImpl<char_t>::stopWorkers() {
359 if (this->m_bWorkerEngineOn.load()) {
360 // Ask for worker termination
361 this->m_bWorkerEngineOn.store(false);
362 this->m_oWorkerCv.notify_all();
363 // Kill workers
364 for (auto &oWorker: this->m_vWorkers) {
365 if (oWorker.joinable())
366 oWorker.join();
367 }
368 this->m_vWorkers.clear();
369 }
370 }
371
372 template<typename char_t>
373 void BasicAsyncLoggerImpl<char_t>::waitForKiller() {
374 // Ensure killer thread is off
375 if (!this->m_oKillerThread.empty()) {
376 auto &oKiller = this->m_oKillerThread.front();
377 if (oKiller.joinable())
378 oKiller.join();
379 this->m_oKillerThread.clear();
380 }
381 }
382
383 template<typename char_t>
385 // Ensure killer thread is OFF
386 this->m_oKillerMutex.lock();
387 this->waitForKiller();
388
389 // Worker lock
390 std::unique_lock<std::mutex> oWorkerLock(this->m_oWorkerMutex);
391
392 // Release the killer lock
393 // We no longer need it
394 this->m_oKillerMutex.unlock();
395
396 // Ensure killer thread is OFF
397 this->waitForKiller();
398
399 // Get service status
400 const auto nStatus = this->serviceStatus();
401
402 // Start service
403 if (nStatus != Status::TRASCENDENT && nStatus != Status::RUNNING) {
404 // External service if OFF
405 this->serviceStart();
406 }
407
408 // Start the working engine
409 this->startWorkers();
410 }
411
412 template<typename char_t>
414 // Ensure killer thread is OFF
415 this->m_oKillerMutex.lock();
416 this->waitForKiller();
417
418 // Worker lock
419 std::unique_lock<std::mutex> oWorkerLock(this->m_oWorkerMutex);
420
421 // Release the killer lock
422 // We no longer need it
423 this->m_oKillerMutex.unlock();
424
425 // Stop the working engine
426 this->stopWorkers();
427
428 // Get service status
429 const auto nStatus = this->serviceStatus();
430
431 // Stop service
432 if (nStatus == Status::TRASCENDENT || nStatus == Status::RUNNING) {
433 // External service if ON
434 this->serviceStop();
435 }
436 }
437
438 template<typename char_t>
440 // Killer lock
441 std::unique_lock<std::mutex> oKillerLock(this->m_oKillerMutex);
442
443 // Ensure killer thread is OFF
444 this->waitForKiller();
445
446 // Worker lock
447 std::unique_lock<std::mutex> oWorkerLock(this->m_oWorkerMutex);
448
449 // Killer thread
450 this->m_oKillerThread.emplace_back(
451 [this] {
452 // Worker lock
453 std::unique_lock<std::mutex> oKillerWorkerLock(this->m_oWorkerMutex);
454
455 // Stop the working engine
456 this->stopWorkers();
457
458 // Get service status
459 const auto nStatus = this->serviceStatus();
460
461 // Stop service
462 if (nStatus == Status::TRASCENDENT || nStatus == Status::RUNNING) {
463 // External service if ON
464 this->serviceStop();
465 }
466 }
467 );
468 }
469
470 template<typename char_t>
472 // Killer lock
473 std::unique_lock<std::mutex> oKillerLock(this->m_oKillerMutex);
474 // Ensure killer thread is OFF
475 this->waitForKiller();
476 }
477
478 template<typename char_t>
480 // Get service status
481 auto nStatus = this->serviceStatus();
482
483 // Calculate logger status
484 switch (nStatus) {
486 case Status::RUNNING:
487 return m_bWorkerEngineOn.load() ? nStatus : Status::BROKEN;
488 case Status::STOPPED:
489 return !m_bWorkerEngineOn.load() ? nStatus : Status::BROKEN;
490 case Status::BROKEN:
491 default:
492 return Status::BROKEN;
493 }
494 }
495
496 template<typename char_t>
498 bool bContinue;
499 do {
500 // Queue lock
501 std::unique_lock<std::mutex> oQueueLock(this->m_oQueueMutex);
502
503 // Continue while there is something to do
504 // Continue to end if stop signal
505 this->m_oWorkerCv.wait(oQueueLock, [this] {
506 // Checkif there are entries queued
507 if (!this->m_qEntryQueue.empty())return true;
508 // Continue to stop
509 return !this->m_bWorkerEngineOn.load();
510 });
511
512 // Check entry
513 if (!this->m_qEntryQueue.empty()) {
514 // Get entry
515 message_type oEntry = std::move(this->m_qEntryQueue.front());
516 this->m_qEntryQueue.pop_front();
517
518 // Do work (heavy lifting)
519 this->m_oQueueMutex.unlock();
520 this->work(std::move(oEntry));
521 this->m_oQueueMutex.lock();
522 }
523
524 // Determine wherther continue
525 const bool bEmpty = this->m_qEntryQueue.empty();
526 const bool bEngine = this->m_bWorkerEngineOn.load();
527 bContinue = bEngine || !bEmpty;
528 } while (bContinue);
529 }
530}
531
532#endif
Core logging interfaces and high-level handle abstractions.
An asynchronous logger template utilizing background worker threads and object recycling....
Definition async_logger.h:53
void start() final
Starts the background worker pool and the underlying service.
Definition async_logger.h:384
void join() final
Blocks until all background activity (workers and killer threads) has ceased.
Definition async_logger.h:471
void log(message_type oMessage) final
Enqueues a log message for asynchronous processing.
Definition async_logger.h:316
typename BasicLoggerImpl< char_t >::string_type string_type
Alias for the basic_string type associated with this logger's encoding.
Definition async_logger.h:59
virtual void serviceStart()=0
Performs the initialization sequence for the sink.
~BasicAsyncLoggerImpl() override=default
Destructor.
virtual void work(message_type oMessage)=0
Abstract method for the actual I/O work.
virtual void serviceStop()=0
Performs the shutdown sequence for the sink.
typename BasicLoggerImpl< char_t >::message_type message_type
Alias for the BasicMessage type associated to the logger.
Definition async_logger.h:62
BasicAsyncLoggerImpl()
Default constructor.
Definition async_logger.h:69
void stop() final
Synchronously shuts down the worker pool and the service.
Definition async_logger.h:413
Status status() final
Retrieves the aggregated operational status of the asynchronous logger.
Definition async_logger.h:479
virtual Status serviceStatus()=0
Reports the current operational status of the underlying sink.
void signalStop() final
Initiates a shutdown sequence on a separate background "killer" thread.
Definition async_logger.h:439
typename BasicLoggerImpl< char_t >::char_type char_type
Alias for the underlying character type.
Definition async_logger.h:56
Abstract template interface defining the mandatory contract for all loggers.
Definition base_logger.h:28
char_t char_type
Alias for the underlying character type used by this logger.
Definition base_logger.h:31
std::basic_string< char_type > string_type
Alias for the basic_string type associated with this logger's encoding.
Definition base_logger.h:34
Container for log message implementations.
Definition message.h:76
Exception thrown when the logger queue exceeds its maximum allowed size. This exception is specifical...
Definition async_logger.h:32
LoggerOverflowError()
Constructs a new LoggerOverflowError object.
Definition async_logger.h:37
Root namespace for the CppTrail logging library.
Definition async_logger.h:24
Status
Represents the current operational state of a Logger implementation.
Definition def.h:31
@ TRASCENDENT
The logger operates outside standard lifecycle management.
@ STOPPED
The logger has been gracefully shut down.
@ BROKEN
The logger encountered a fatal error (e.g., IO failure).
@ RUNNING
The logger is active and processing entries.