LCOV - code coverage report
Current view: top level - logging - Logger.h (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 94.1 % 102 96
Test Date: 2026-03-18 19:01:10 Functions: 100.0 % 12 12
Branches: 60.1 % 148 89

             Branch data     Line data    Source code
       1                 :             : #ifndef LOGGER_H
       2                 :             : #define LOGGER_H
       3                 :             : 
       4                 :             : #include <string>
       5                 :             : #include <fstream>
       6                 :             : #include <mutex>
       7                 :             : #include <memory>
       8                 :             : #include <iostream>
       9                 :             : #include <sstream>
      10                 :             : #include <iomanip>
      11                 :             : #include <chrono>
      12                 :             : #include <ctime>
      13                 :             : #include <cstdarg>
      14                 :             : #include <algorithm> // For std::max, std::min
      15                 :             : #include <filesystem>
      16                 :             : #include "StringUtils.h" // Include StringUtils.h
      17                 :             : 
      18                 :             : // Simple synchronous logger that flushes every line for crash debugging
      19                 :             : class Logger {
      20                 :             : public:
      21                 :        8696 :     static Logger& Get() {
      22   [ +  +  +  -  :        8696 :         static Logger instance;
             +  -  -  - ]
      23                 :        8696 :         return instance;
      24                 :             :     }
      25                 :             : 
      26                 :             :     /**
      27                 :             :      * Initialize the logger.
      28                 :             :      * @param base_filename The base name of the log file (e.g. "debug.log")
      29                 :             :      * @param log_path Optional directory to store the log file.
      30                 :             :      * @param use_timestamp If true (default), appends a unique timestamp to the filename to avoid overwriting.
      31                 :             :      */
      32                 :          22 :     void Init(const std::string& base_filename, const std::string& log_path = "", bool use_timestamp = true) {
      33         [ +  - ]:          22 :         std::lock_guard<std::mutex> lock(m_mutex);
      34                 :             : 
      35         [ +  - ]:          22 :         std::string final_filename = base_filename;
      36                 :          22 :         std::string timestamp_str;
      37                 :             : 
      38         [ +  + ]:          22 :         if (use_timestamp) {
      39                 :             :             // Generate timestamped filename
      40                 :          15 :             auto now = std::chrono::system_clock::now();
      41                 :          15 :             auto in_time_t = std::chrono::system_clock::to_time_t(now);
      42                 :             :             std::tm time_info;
      43                 :             :             #ifdef _WIN32
      44                 :             :                 localtime_s(&time_info, &in_time_t);
      45                 :             :             #else
      46                 :          15 :                 localtime_r(&in_time_t, &time_info);
      47                 :             :             #endif
      48                 :             : 
      49         [ +  - ]:          15 :             std::stringstream ss;
      50         [ +  - ]:          15 :             ss << std::put_time(&time_info, "%Y-%m-%d_%H-%M-%S");
      51         [ +  - ]:          15 :             timestamp_str = ss.str();
      52                 :             : 
      53                 :             :             // Extract extension if present
      54         [ +  - ]:          15 :             std::string name_part = base_filename;
      55         [ +  - ]:          15 :             std::string ext = ".log";
      56                 :          15 :             size_t dot_pos = base_filename.find_last_of('.');
      57         [ +  + ]:          15 :             if (dot_pos != std::string::npos) {
      58         [ +  - ]:          14 :                 name_part = base_filename.substr(0, dot_pos);
      59         [ +  - ]:          14 :                 ext = base_filename.substr(dot_pos);
      60                 :             :             }
      61   [ +  -  +  -  :          15 :             final_filename = name_part + "_" + timestamp_str + ext;
                   +  - ]
      62                 :          15 :         }
      63                 :             : 
      64         [ +  - ]:          22 :         std::filesystem::path path = log_path;
      65                 :          22 :         std::string new_filename;
      66         [ +  + ]:          22 :         if (!path.empty()) {
      67                 :             :             try {
      68         [ +  - ]:          13 :                 std::filesystem::create_directories(path);
      69                 :           0 :             } catch (...) {
      70                 :             :                 // Ignore, let file open fail if necessary
      71         [ -  - ]:           0 :             }
      72   [ +  -  +  -  :          13 :             new_filename = (path / final_filename).string();
                   +  - ]
      73                 :             :         } else {
      74         [ +  - ]:           9 :             new_filename = final_filename;
      75                 :             :         }
      76                 :             : 
      77                 :             :         // If we are re-initializing to the SAME file, don't close and reopen (to avoid truncation)
      78                 :             :         // Or if we must reopen, use append mode if use_timestamp is true to avoid losing startup lines
      79                 :             :         // that happened in the same second.
      80   [ +  +  +  +  :          22 :         if (m_file.is_open() && m_filename == new_filename) {
                   +  + ]
      81   [ +  -  +  - ]:           6 :             _LogNoLock("Logger re-initialized to same file. Continuing...", true);
      82                 :           6 :             return;
      83                 :             :         }
      84                 :             : 
      85         [ +  + ]:          16 :         if (m_file.is_open()) {
      86         [ +  - ]:          12 :             m_file.close();
      87                 :             :         }
      88                 :             : 
      89         [ +  - ]:          16 :         m_filename = new_filename;
      90                 :             : 
      91                 :             :         // Open mode: if timestamped, use append to be safe against multi-init in same second.
      92                 :             :         // If not timestamped (tests), use trunc to ensure clean file.
      93                 :          16 :         std::ios_base::openmode mode = std::ios::out;
      94         [ +  + ]:          16 :         if (use_timestamp) {
      95                 :           9 :             mode |= std::ios::app;
      96                 :             :         } else {
      97                 :           7 :             mode |= std::ios::trunc;
      98                 :             :         }
      99                 :             : 
     100         [ +  - ]:          16 :         m_file.open(m_filename, mode);
     101         [ +  + ]:          16 :         if (m_file.is_open()) {
     102                 :          15 :             m_initialized = true;
     103   [ +  -  +  -  :          30 :             _LogNoLock("Logger Initialized. Version: " + std::string(LMUFFB_VERSION), true);
                   +  - ]
     104                 :             :         }
     105   [ +  +  +  +  :          46 :     }
          +  +  +  +  +  
                      + ]
     106                 :             : 
     107                 :           4 :     void Close() {
     108         [ +  - ]:           4 :         std::lock_guard<std::mutex> lock(m_mutex);
     109         [ +  + ]:           4 :         if (m_file.is_open()) {
     110         [ +  - ]:           3 :             m_file << "Logger Closed Explicitly.\n";
     111         [ +  - ]:           3 :             m_file.close();
     112                 :             :         }
     113                 :           4 :         m_initialized = false;
     114                 :           4 :     }
     115                 :             : 
     116                 :        6735 :     void Log(const char* fmt, ...) {
     117                 :             :         char buffer[2048];
     118                 :             :         va_list args;
     119                 :        6735 :         va_start(args, fmt);
     120                 :        6735 :         StringUtils::vSafeFormat(buffer, sizeof(buffer), fmt, args);
     121                 :        6735 :         va_end(args);
     122                 :             : 
     123         [ +  - ]:        6735 :         std::string message(buffer);
     124                 :             : 
     125         [ +  - ]:        6735 :         std::lock_guard<std::mutex> lock(m_mutex);
     126         [ +  - ]:        6735 :         _LogNoLock(message, true);
     127                 :        6735 :     }
     128                 :             : 
     129                 :             :     // Log to file only, not to console
     130                 :        1916 :     void LogFile(const char* fmt, ...) {
     131                 :             :         char buffer[2048];
     132                 :             :         va_list args;
     133                 :        1916 :         va_start(args, fmt);
     134                 :        1916 :         StringUtils::vSafeFormat(buffer, sizeof(buffer), fmt, args);
     135                 :        1916 :         va_end(args);
     136                 :             : 
     137         [ +  - ]:        1916 :         std::string message(buffer);
     138                 :             : 
     139         [ +  - ]:        1916 :         std::lock_guard<std::mutex> lock(m_mutex);
     140         [ +  - ]:        1916 :         _LogNoLock(message, false);
     141                 :        1916 :     }
     142                 :             : 
     143                 :             :     // Helper for std::string
     144                 :           2 :     void LogStr(const std::string& msg) {
     145                 :           2 :         Log("%s", msg.c_str());
     146                 :           2 :     }
     147                 :             : 
     148                 :             :     // Helper for std::string (file only)
     149                 :             :     void LogFileStr(const std::string& msg) {
     150                 :             :         LogFile("%s", msg.c_str());
     151                 :             :     }
     152                 :             : 
     153                 :             :     // Helper for error logging with GetLastError()
     154                 :           2 :     void LogWin32Error(const char* context, unsigned long errorCode) {
     155                 :           2 :         Log("Error in %s: Code %lu", context, errorCode);
     156                 :           2 :     }
     157                 :             : 
     158                 :           5 :     const std::string& GetFilename() const { return m_filename; }
     159                 :             : 
     160                 :             :     // For testing
     161                 :          14 :     void SetTestStream(std::ostream* os) {
     162         [ +  - ]:          14 :         std::lock_guard<std::mutex> lock(m_mutex);
     163                 :          14 :         m_testStream = os;
     164                 :          14 :     }
     165                 :             : 
     166                 :             : private:
     167         [ +  - ]:           1 :     Logger() {}
     168                 :           1 :     ~Logger() noexcept {
     169                 :             :         try {
     170         [ -  + ]:           1 :             if (m_file.is_open()) {
     171         [ #  # ]:           0 :                 m_file << "Logger Shutdown.\n";
     172         [ #  # ]:           0 :                 m_file.close();
     173                 :             :             }
     174                 :           0 :         } catch (...) {
     175                 :             :             // Destructor must not throw
     176                 :           0 :         }
     177                 :           1 :     }
     178                 :             : 
     179                 :        8672 :     void _LogNoLock(const std::string& message, bool toConsole) {
     180         [ +  + ]:        8672 :         if (m_testStream) {
     181                 :          60 :             *m_testStream << message << "\n";
     182                 :             :         }
     183                 :             : 
     184         [ +  + ]:        8672 :         if (m_file.is_open()) {
     185                 :             :             // Timestamp
     186                 :        8344 :             auto now = std::chrono::system_clock::now();
     187                 :        8344 :             auto in_time_t = std::chrono::system_clock::to_time_t(now);
     188                 :             :             std::tm time_info;
     189                 :             :             #ifdef _WIN32
     190                 :             :                 localtime_s(&time_info, &in_time_t);
     191                 :             :             #else
     192                 :        8344 :                 localtime_r(&in_time_t, &time_info);
     193                 :             :             #endif
     194                 :             : 
     195   [ +  -  +  -  :        8344 :             m_file << "[" << std::put_time(&time_info, "%H:%M:%S") << "] " << message << "\n";
          +  -  +  -  +  
                      - ]
     196         [ +  - ]:        8344 :             m_file.flush(); // Critical for crash debugging
     197                 :             :         }
     198                 :             : 
     199         [ +  + ]:        8672 :         if (toConsole) {
     200                 :             :             // Also print to console for consistency
     201                 :             :             // Re-calculate timestamp if not already done (file logging also calculates it)
     202                 :        6756 :             auto now = std::chrono::system_clock::now();
     203                 :        6756 :             auto in_time_t = std::chrono::system_clock::to_time_t(now);
     204                 :             :             std::tm time_info;
     205                 :             :             #ifdef _WIN32
     206                 :             :                 localtime_s(&time_info, &in_time_t);
     207                 :             :             #else
     208                 :        6756 :                 localtime_r(&in_time_t, &time_info);
     209                 :             :             #endif
     210                 :             : 
     211   [ +  -  +  -  :        6756 :             std::cout << "[" << std::put_time(&time_info, "%H:%M:%S") << "] [Log] " << message << std::endl;
          +  -  +  -  +  
                      - ]
     212                 :             :         }
     213                 :        8672 :     }
     214                 :             : 
     215                 :             :     std::string m_filename;
     216                 :             :     std::ofstream m_file;
     217                 :             :     std::mutex m_mutex;
     218                 :             :     bool m_initialized = false;
     219                 :             :     std::ostream* m_testStream = nullptr;
     220                 :             : };
     221                 :             : 
     222                 :             : #endif // LOGGER_H
        

Generated by: LCOV version 2.0-1