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(