MetaImGUI 1.0.0
ImGui Application Template for C++20
Loading...
Searching...
No Matches
Logger.cpp
Go to the documentation of this file.
1/*
2 MetaImGUI
3 Copyright (C) 2026 A P Nicholson
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>.
17*/
18
19#include "Logger.h"
20
21#include <chrono>
22#include <ctime>
23#include <filesystem>
24#include <fstream>
25#include <iomanip>
26#include <iostream>
27#include <mutex>
28#include <sstream>
29
30namespace MetaImGUI {
31
33 bool consoleOutput = true;
34 bool fileOutput = false;
35 std::filesystem::path logFilePath;
36 std::ofstream logFile;
37 mutable std::mutex mutex;
38};
39
40namespace {
41
42std::string CurrentTimestamp() {
43 const auto now = std::chrono::system_clock::now();
44 const auto time = std::chrono::system_clock::to_time_t(now);
45 const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
46
47 std::tm tm_buf{};
48#ifdef _WIN32
49 localtime_s(&tm_buf, &time);
50#else
51 localtime_r(&time, &tm_buf);
52#endif
53
54 std::ostringstream oss;
55 oss << std::put_time(&tm_buf, "%Y-%m-%d %H:%M:%S");
56 oss << '.' << std::setfill('0') << std::setw(3) << ms.count();
57 return oss.str();
58}
59
60std::string_view LevelToString(LogLevel level) {
61 switch (level) {
62 case LogLevel::Debug:
63 return "DEBUG";
64 case LogLevel::Info:
65 return "INFO ";
67 return "WARN ";
68 case LogLevel::Error:
69 return "ERROR";
70 case LogLevel::Fatal:
71 return "FATAL";
72 }
73 return "?????";
74}
75
76const char* LevelToColor(LogLevel level) {
77 switch (level) {
78 case LogLevel::Debug:
79 return "\033[36m";
80 case LogLevel::Info:
81 return "\033[32m";
83 return "\033[33m";
84 case LogLevel::Error:
85 return "\033[31m";
86 case LogLevel::Fatal:
87 return "\033[35m";
88 }
89 return "\033[0m";
90}
91
92} // namespace
93
94Logger::Logger() : m_impl(std::make_unique<Impl>()) {}
95
96Logger::~Logger() {
97 Shutdown();
98}
99
101 static Logger instance;
102 return instance;
103}
104
105void Logger::Initialize(const std::filesystem::path& logFilePath, LogLevel minLevel) {
106 const std::lock_guard<std::mutex> lock(m_impl->mutex);
107
108 m_minLevel.store(minLevel, std::memory_order_release);
109 m_impl->logFilePath = logFilePath;
110
111 if (!logFilePath.empty()) {
112 const auto parentPath = logFilePath.parent_path();
113 if (!parentPath.empty() && !std::filesystem::exists(parentPath)) {
114 std::error_code ec;
115 std::filesystem::create_directories(parentPath, ec);
116 if (ec) {
117 std::cerr << "Warning: Could not create log directory: " << ec.message() << '\n';
118 }
119 }
120
121 m_impl->logFile.open(logFilePath, std::ios::out | std::ios::app);
122 if (m_impl->logFile.is_open()) {
123 m_impl->fileOutput = true;
124 m_impl->logFile << "\n========== Log Session Started: " << CurrentTimestamp() << " ==========\n";
125 m_impl->logFile.flush();
126 } else {
127 std::cerr << "Failed to open log file: " << logFilePath << '\n';
128 m_impl->fileOutput = false;
129 }
130 }
131
132 if (m_impl->consoleOutput) {
133 std::cout << "Logger initialized (Level: " << LevelToString(minLevel) << ")" << '\n';
134 }
135}
136
138 const std::lock_guard<std::mutex> lock(m_impl->mutex);
139
140 if (m_impl->logFile.is_open()) {
141 m_impl->logFile << "========== Log Session Ended: " << CurrentTimestamp() << " ==========\n\n";
142 m_impl->logFile.close();
143 }
144}
145
146void Logger::SetConsoleOutput(bool enable) {
147 const std::lock_guard<std::mutex> lock(m_impl->mutex);
148 m_impl->consoleOutput = enable;
149}
150
151void Logger::SetFileOutput(bool enable) {
152 const std::lock_guard<std::mutex> lock(m_impl->mutex);
153 m_impl->fileOutput = enable && m_impl->logFile.is_open();
154}
155
157 const std::lock_guard<std::mutex> lock(m_impl->mutex);
158 if (m_impl->logFile.is_open()) {
159 m_impl->logFile.flush();
160 }
161 std::cout.flush();
162 std::cerr.flush();
163}
164
165std::filesystem::path Logger::GetLogFilePath() const {
166 const std::lock_guard<std::mutex> lock(m_impl->mutex);
167 return m_impl->logFilePath;
168}
169
170void Logger::LogVFormat(LogLevel level, std::string_view fmt, std::format_args args) {
171 std::string message;
172 try {
173 message = std::vformat(fmt, args);
174 } catch (const std::format_error& e) {
175 // Fall back to the raw format string so a bad spec never silently drops
176 // the message — the surrounding text still helps locate the call site.
177 message = std::string(fmt) + " [format_error: " + e.what() + "]";
178 }
179 LogPlain(level, message);
180}
181
182void Logger::LogPlain(LogLevel level, std::string_view message) {
183 // Format the line *outside* the mutex so a logging call from inside an
184 // ostream/streambuf during the write step can never re-enter and deadlock.
185 const std::string timestamp = CurrentTimestamp();
186 const std::string_view levelStr = LevelToString(level);
187
188 std::string formatted;
189 formatted.reserve(timestamp.size() + levelStr.size() + message.size() + 8);
190 formatted.append("[").append(timestamp).append("] [").append(levelStr).append("] ").append(message);
191
192 bool consoleOutput = false;
193 bool fileOutput = false;
194 {
195 const std::lock_guard<std::mutex> lock(m_impl->mutex);
196 consoleOutput = m_impl->consoleOutput;
197 fileOutput = m_impl->fileOutput && m_impl->logFile.is_open();
198
199 if (fileOutput) {
200 m_impl->logFile << formatted << '\n';
201 if (level >= LogLevel::Error) {
202 m_impl->logFile.flush();
203 }
204 }
205 }
206
207 // Console writes happen unlocked: cout/cerr have their own internal sync.
208 if (consoleOutput) {
209 const char* color = LevelToColor(level);
210 const char* reset = "\033[0m";
211 std::ostream& out = (level >= LogLevel::Error) ? std::cerr : std::cout;
212 out << color << formatted << reset << '\n';
213 }
214}
215
216} // namespace MetaImGUI
Thread-safe logger with file and console output.
Definition Logger.h:57
void SetFileOutput(bool enable)
Definition Logger.cpp:151
void SetConsoleOutput(bool enable)
Definition Logger.cpp:146
void Initialize(const std::filesystem::path &logFilePath, LogLevel minLevel=LogLevel::Info)
Definition Logger.cpp:105
static Logger & Instance()
Definition Logger.cpp:100
std::filesystem::path GetLogFilePath() const
Definition Logger.cpp:165
LogLevel
Log severity levels.
Definition Logger.h:37
@ Warning
Warning messages.
@ Info
Informational messages.
@ Fatal
Fatal error messages.
@ Error
Error messages.
@ Debug
Detailed debugging information.
std::ofstream logFile
Definition Logger.cpp:36
std::filesystem::path logFilePath
Definition Logger.cpp:35