| 161 | } |
| 162 | |
| 163 | void Logging::log(LogLevel level, const std::string& message) |
| 164 | { |
| 165 | std::stringstream ss; |
| 166 | auto now = std::chrono::system_clock::now(); |
| 167 | auto now_time_t = std::chrono::system_clock::to_time_t(now); |
| 168 | auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000; |
| 169 | |
| 170 | ss << std::put_time(std::localtime(&now_time_t), "[%Y-%m-%d %H:%M:%S"); |
| 171 | ss << "." << std::setfill('0') << std::setw(3) << now_ms.count() << "]"; |
| 172 | |
| 173 | switch (level) { |
| 174 | case LogLevel::TRACE: |
| 175 | ss << " TRACE - "; |
| 176 | break; |
| 177 | case LogLevel::DEBUG: |
| 178 | ss << " DEBUG - "; |
| 179 | break; |
| 180 | case LogLevel::INFO: |
| 181 | ss << " INFO - "; |
| 182 | break; |
| 183 | case LogLevel::WARN: |
| 184 | ss << " WARN - "; |
| 185 | break; |
| 186 | case LogLevel::ERROR: |
| 187 | ss << " ERROR - "; |
| 188 | break; |
| 189 | } |
| 190 | |
| 191 | std::string logEntry = ss.str() + message + "\n"; |
| 192 | |
| 193 | std::lock_guard<std::mutex> lock(logMutex); |
| 194 | applicationLogs += logEntry; |
| 195 | if (applicationLogs.size() > APPLICATION_LOGS_CAP) { |
| 196 | // Drop the oldest data, trimming to the next line boundary so the window |
| 197 | // never starts mid-entry. |
| 198 | size_t drop = applicationLogs.size() - APPLICATION_LOGS_CAP; |
| 199 | size_t nl = applicationLogs.find('\n', drop); |
| 200 | applicationLogs.erase(0, nl == std::string::npos ? drop : nl + 1); |
| 201 | } |
| 202 | logBuffer += logEntry; |
| 203 | |
| 204 | // Flush if buffer is getting full or for important messages |
| 205 | if (logFile != nullptr) { |
| 206 | if (logBuffer.size() >= LOG_BUFFER_SIZE || level == LogLevel::ERROR || level == LogLevel::WARN) { |
| 207 | flushLogBuffer(); |
| 208 | } |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | void Logging::initFileLogging() |
| 213 | { |