Implement async file logger
This commit implements asynchronous, multi-thread safe file logging.
Certain log messages in the repo, for example VPD collection logs, need
to be logged from multiple threads in an asynchronous manner.
Change-Id: I1a7c28cc37f9e88f04e6af89cf4042d032c8cf90
Signed-off-by: Souvik Roy <souvikroyofficial10@gmail.com>
diff --git a/vpd-manager/include/logger.hpp b/vpd-manager/include/logger.hpp
index 1622426..59b7afd 100644
--- a/vpd-manager/include/logger.hpp
+++ b/vpd-manager/include/logger.hpp
@@ -2,6 +2,7 @@
#include "types.hpp"
+#include <condition_variable>
#include <filesystem>
#include <fstream>
#include <iostream>
@@ -196,6 +197,18 @@
*/
class AsyncFileLogger final : public ILogFileHandler
{
+ // queue for log messages
+ std::queue<std::string> m_messageQueue;
+
+ // mutex to control access to log message queue
+ std::mutex m_mutex;
+
+ // flag which indicates log worker thread if logging is finished
+ std::atomic_bool m_stopLogging{false};
+
+ // conditional variable to signal log worker thread
+ std::condition_variable m_cv;
+
/**
* @brief Constructor
* Private so that can't be initialized by class(es) other than friends.
@@ -231,6 +244,8 @@
/**
* @brief API to log a message to file
*
+ * This API logs given message to a file. This API is multi-thread safe.
+ *
* @param[in] i_message - Message to log
*
* @throw std::runtime_error
@@ -240,15 +255,16 @@
// destructor
~AsyncFileLogger()
{
- /* TODO
- - acquire lock
- - set log stop flag to true
- - notify log worker thread
- */
+ std::unique_lock<std::mutex> l_lock(m_mutex);
+
+ m_stopLogging = true;
+
if (m_fileStream.is_open())
{
m_fileStream.close();
}
+
+ m_cv.notify_one();
}
};
diff --git a/vpd-manager/src/logger.cpp b/vpd-manager/src/logger.cpp
index f7acfb4..c57216b 100644
--- a/vpd-manager/src/logger.cpp
+++ b/vpd-manager/src/logger.cpp
@@ -57,7 +57,7 @@
- create collection logger object with collection_(n+1).log
parameter*/
m_collectionLogger.reset(
- new AsyncFileLogger("/var/lib/vpd/collection.log", 512));
+ new AsyncFileLogger("/var/lib/vpd/collection.log", 4096));
}
catch (const std::exception& l_ex)
{
@@ -84,33 +84,82 @@
}
}
-void AsyncFileLogger::logMessage(
- [[maybe_unused]] const std::string_view& i_message)
+void AsyncFileLogger::logMessage(const std::string_view& i_message)
{
try
{
- /* TODO:
- - acquire lock on queue
- - push message to queue
- - notify log worker thread
- */
+ // acquire lock on queue
+ std::unique_lock<std::mutex> l_lock(m_mutex);
+
+ // push message to queue
+ m_messageQueue.emplace(timestamp() + " : " + std::string(i_message));
+
+ // notify log worker thread
+ m_cv.notify_one();
}
catch (const std::exception& l_ex)
{
- auto l_logger = Logger::getLoggerInstance();
- l_logger->logMessage(i_message);
+ // log message to journal if we fail to push message to queue
+ Logger::getLoggerInstance()->logMessage(i_message);
}
}
void AsyncFileLogger::fileWorker() noexcept
{
- /* TODO:
- - start an infinite loop
- - check exit conditions and exit if needed
- - wait for notification from log producer
- - if notification received, flush the messages from queue to file
- - rotate file if needed
- */
+ // create lock object on mutex
+ std::unique_lock<std::mutex> l_lock(m_mutex);
+
+ // infinite loop
+ while (true)
+ {
+ // check for exit conditions
+ if (!m_fileStream.is_open() || m_stopLogging)
+ {
+ break;
+ }
+
+ // wait for notification from log producer
+ m_cv.wait(l_lock,
+ [this] { return m_stopLogging || !m_messageQueue.empty(); });
+
+ // flush the queue
+ while (!m_messageQueue.empty())
+ {
+ // read the first message in queue
+ const auto l_logMessage = m_messageQueue.front();
+ try
+ {
+ // pop the message from queue
+ m_messageQueue.pop();
+
+ // unlock mutex on queue
+ l_lock.unlock();
+
+ if (++m_currentNumEntries > m_maxEntries)
+ {
+ rotateFile();
+ }
+
+ // flush the message to file
+ m_fileStream << l_logMessage << std::endl;
+
+ // lock mutex on queue
+ l_lock.lock();
+ }
+ catch (const std::exception& l_ex)
+ {
+ // log message to journal if we fail to push message to queue
+ Logger::getLoggerInstance()->logMessage(l_logMessage);
+
+ // check if we need to reacquire lock before continuing to flush
+ // queue
+ if (!l_lock.owns_lock())
+ {
+ l_lock.lock();
+ }
+ }
+ } // queue flush loop
+ } // thread loop
}
void ILogFileHandler::rotateFile(