Program Listing for File debug_log.h#

Return to documentation for file (debug_log.h)

#ifndef DEBUG_LOG_H
#define DEBUG_LOG_H

#include <iostream>
#include <ostream>
#include <fstream>
#include <mutex>
#include <filesystem>
#include <string>
#include <sstream>
#include <thread>
#include <queue>
#include <chrono>
#include <atomic>
#include "exec_dir_helper.h"

#ifndef ENABLE_DEBUG_LOG

#define ENABLE_DEBUG_LOG 1
#endif

#ifndef DECOUPLED_DEBUG_LOG

#define DECOUPLED_DEBUG_LOG 1
#endif

namespace debug
{
    class CLogger
    {
    public:
        CLogger()
        {
            m_pathLogFile = GetExecDirectory() / GetExecFilename().replace_extension(".log");
        }

        ~CLogger()
        {
#if DECOUPLED_DEBUG_LOG != 0
            // Shut down the logger thread
            if (m_threadLogger.joinable())
            {
                m_bShutdown = true;
                m_threadLogger.join();
            }

            // Prevent the logger mutex to be still in use.
            std::unique_lock<std::mutex> lock(m_mtxLogger);
            lock.unlock();
#endif
        }

        void Log(const std::string& rss)
        {
            // Create the message structure
            SLogMsg sMsg{std::this_thread::get_id(), DepthPerThreadOperation(EDepthOperation::report_only), rss};

#if DECOUPLED_DEBUG_LOG != 0
            std::unique_lock<std::mutex> lock(m_mtxLogger);

            // First log entry starts logging
            if (!m_threadLogger.joinable())
            {
                m_threadLogger = std::thread(&CLogger::LogToFileThreadFunc, this);
                while (!m_threadLogger.joinable())
                    std::this_thread::sleep_for(std::chrono::milliseconds(10));
            }
            std::cout << rss << std::endl;

            // Add message to the log queue
            m_queueLogger.push(sMsg);
#else
            std::ofstream fstream;
            fstream.open(m_pathLogFile, std::ios_base::out | std::ios_base::app);
            LogMsg(fstream, sMsg);
#endif
        }

        void IncrDepth()
        {
            DepthPerThreadOperation(EDepthOperation::increase);
        }

        void DecrDepth()
        {
            DepthPerThreadOperation(EDepthOperation::decrease);
        }
    private:
        void StartLog()
        {
            std::ofstream fstream;
            fstream.open(m_pathLogFile, std::ios_base::out | std::ios_base::trunc);
            if (fstream.is_open())
            {
                fstream << "Starting log of " << GetExecFilename().generic_u8string() << std::endl;
                fstream.close();
            }
            std::cout << "Starting log of " << GetExecFilename().generic_u8string() << std::endl << std::flush;
        }

        void FinishLog()
        {
            // Finish logging
            std::ofstream fstream;
            fstream.open(m_pathLogFile, std::ios_base::out | std::ios_base::app);
            if (fstream.is_open())
            {
                fstream << "End log of " << GetExecFilename().generic_u8string() << std::endl;
                fstream.close();
            }
            std::cout << "End log of " << GetExecFilename().generic_u8string() << std::endl << std::flush;
        }

        struct SLogMsg
        {
            std::thread::id id;
            size_t nDepth;
            std::string ssMsg;
        };

        void LogMsg(std::ofstream& rfstream, const SLogMsg& rsMsg)
        {
            std::string ssIndent(rsMsg.nDepth, '>');
            if (!ssIndent.empty())
                ssIndent += ' ';
            if (rfstream.is_open())
            {
                rfstream << rsMsg.id << ": " << ssIndent << rsMsg.ssMsg << std::endl;
                rfstream.close();
            }

            std::cout << rsMsg.id << ": " << ssIndent << rsMsg.ssMsg << std::endl << std::flush;
        }

#if DECOUPLED_DEBUG_LOG != 0

        void LogToFileThreadFunc()
        {
            // Start logging
            StartLog();

            // Log until shutdown
            while (!m_bShutdown)
            {
                std::unique_lock<std::mutex> lock(m_mtxLogger);

                // Are there any message. If not, pause for 100 ms
                if (m_queueLogger.empty())
                {
                    lock.unlock();
                    std::this_thread::sleep_for(std::chrono::milliseconds(100));
                    continue;
                }
                std::queue<SLogMsg> queueLocal = std::move(m_queueLogger);
                lock.unlock();

                // Open the file and log all messages that are in the queue
                std::ofstream fstream;
                fstream.open(m_pathLogFile, std::ios_base::out | std::ios_base::app);
                while (!queueLocal.empty())
                {
                    auto sMsg = std::move(queueLocal.front());
                    queueLocal.pop();
                    LogMsg(fstream, sMsg);
                }
            }

            // Finish logging
            FinishLog();
        }
#endif

        enum class EDepthOperation
        {
            increase,
            decrease,
            report_only,
        };

        size_t DepthPerThreadOperation(EDepthOperation eOperation)
        {
            thread_local static size_t nDepth = 0;
            if (eOperation == EDepthOperation::increase)
                nDepth++;
            else if (eOperation == EDepthOperation::decrease && nDepth)
                nDepth--;
            return nDepth;
        }

        std::filesystem::path m_pathLogFile;
#if DECOUPLED_DEBUG_LOG != 0
        std::mutex m_mtxLogger;
        std::thread                 m_threadLogger;
        std::atomic_bool            m_bShutdown = false;
        std::queue<SLogMsg>         m_queueLogger;
#endif
    };

    inline CLogger& GetLogger()
    {
        static CLogger logger;
        return logger;
    }

    class CFuncLogger
    {
    public:
        CFuncLogger(const std::string& rssFunc, const std::string& rssFile, size_t nLine) : m_ssFunc(rssFunc)
        {
            GetLogger().Log(std::string("Enter function:") + rssFunc + " - file " + rssFile + " - line " + std::to_string(nLine));
            GetLogger().IncrDepth();
        }

        ~CFuncLogger()
        {
            GetLogger().DecrDepth();
            GetLogger().Log(std::string("Leave function:") + m_ssFunc);
        }

        void Checkpoint(size_t nLine)
        {
            GetLogger().Log(std::string("Checkpoint #") + std::to_string(m_nCounter++) + " - line " + std::to_string(nLine));
        }

        void Log(const std::string& rss) const
        {
            GetLogger().Log(rss);
        }

    private:
        std::string     m_ssFunc;
        size_t          m_nCounter = 1;
    };

} // namespace debug

#if ENABLE_DEBUG_LOG == 1

#ifdef _MSC_VER

#define FUNC_LOGGER() debug::CFuncLogger logger(__FUNCSIG__, __FILE__, __LINE__)
#else

#define FUNC_LOGGER() debug::CFuncLogger logger(__PRETTY_FUNCTION__, __FILE__, __LINE__)
#endif

#define CHECKPOINT() logger.Checkpoint(__LINE__)

#define FUNC_LOG(msg) logger.Log(msg)

#else

#define FUNC_LOGGER()

#define CHECKPOINT()

#define FUNC_LOG(msg)

#endif


#endif // !defined DEBUG_LOG_H