Ember
Loading...
Searching...
No Matches
SpdlogManager.h
Go to the documentation of this file.
1#pragma once
2
3#ifdef HAVE_SPDLOG
4
5#include <memory>
6#include <spdlog/sinks/basic_file_sink.h>
7#include <spdlog/sinks/rotating_file_sink.h>
8#include <spdlog/sinks/stdout_color_sinks.h>
9#include <spdlog/sinks/stdout_sinks.h>
10#include <spdlog/spdlog.h>
11#include <string>
12
13namespace EmberCore {
14
21class SpdlogManager {
22 public:
23 static SpdlogManager &GetInstance() {
24 static SpdlogManager instance;
25 return instance;
26 }
27
36 void Initialize(const std::string &app_name = "Ember", const std::string &log_file_path = "logs/Ember.log",
37 size_t max_file_size = 1024 * 1024 * 5, // 5MB
38 size_t max_files = 3, bool enable_console_colors = true) {
39 try {
40 // Check if logger with this name already exists
41 auto existing_logger = spdlog::get(app_name);
42 if (existing_logger) {
43 // Use existing logger instead of creating a new one
44 m_logger = existing_logger;
45 m_initialized = true;
46 m_logger->info("Using existing spdlog logger: {}", app_name);
47 return;
48 }
49
50 // Create sinks
51 std::shared_ptr<spdlog::sinks::sink> console_sink;
52 if (enable_console_colors) {
53 auto color_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
54 // Force color mode to always enable (ignore TTY detection)
55 color_sink->set_color_mode(spdlog::color_mode::always);
56 console_sink = color_sink;
57 // Pattern with color markers (%^ and %$ for colored level)
58 console_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [%n] %v");
59 } else {
60 console_sink = std::make_shared<spdlog::sinks::stdout_sink_mt>();
61 // Pattern without color markers
62 console_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%n] %v");
63 }
64
65 auto file_sink =
66 std::make_shared<spdlog::sinks::rotating_file_sink_mt>(log_file_path, max_file_size, max_files);
67
68 // Set pattern for file sink (no colors in file)
69 file_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%n] %v");
70
71 // Create main logger
72 std::vector<spdlog::sink_ptr> sinks{console_sink, file_sink};
73 m_logger = std::make_shared<spdlog::logger>(app_name, sinks.begin(), sinks.end());
74
75 // Set log level to info by default
76 m_logger->set_level(spdlog::level::info);
77 m_logger->flush_on(spdlog::level::warn); // Auto-flush on warnings and above
78
79 // Register with spdlog
80 spdlog::register_logger(m_logger);
81 spdlog::set_default_logger(m_logger);
82
83 m_initialized = true;
84
85 // Log initialization success
86 m_logger->info("spdlog initialized successfully");
87
88 } catch (const spdlog::spdlog_ex &ex) {
89 // Fallback to console only if file logging fails
90 try {
91 // Try to get existing logger first
92 auto existing_logger = spdlog::get(app_name);
93 if (existing_logger) {
94 m_logger = existing_logger;
95 } else {
96 // Create console-only logger based on color preference
97 if (enable_console_colors) {
98 auto color_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
99 color_sink->set_color_mode(spdlog::color_mode::always);
100 m_logger = std::make_shared<spdlog::logger>(app_name, color_sink);
101 spdlog::register_logger(m_logger);
102 } else {
103 auto plain_sink = std::make_shared<spdlog::sinks::stdout_sink_mt>();
104 m_logger = std::make_shared<spdlog::logger>(app_name, plain_sink);
105 spdlog::register_logger(m_logger);
106 }
107 }
108 m_logger->error("Failed to initialize file logging: {}", ex.what());
109 m_initialized = true;
110 } catch (...) {
111 // If all else fails, create a basic logger without registration
112 m_logger = std::make_shared<spdlog::logger>(app_name);
113 m_initialized = true;
114 }
115 }
116 }
117
121 std::shared_ptr<spdlog::logger> GetLogger() {
122 if (!m_initialized) {
123 Initialize();
124 }
125 return m_logger;
126 }
127
132 std::shared_ptr<spdlog::logger> GetLogger(const std::string &name) {
133 if (!m_initialized) {
134 Initialize();
135 }
136
137 auto existing = spdlog::get(name);
138 if (existing) {
139 return existing;
140 }
141
142 // Create new logger with same sinks as main logger
143 auto new_logger = std::make_shared<spdlog::logger>(name, m_logger->sinks().begin(), m_logger->sinks().end());
144 new_logger->set_level(m_logger->level());
145 spdlog::register_logger(new_logger);
146
147 return new_logger;
148 }
149
153 void SetLogLevel(spdlog::level::level_enum level) {
154 if (m_logger) {
155 m_logger->set_level(level);
156 spdlog::set_level(level);
157 }
158 }
159
164 void SetConsoleColorsEnabled(bool enabled) {
165 if (!m_initialized || !m_logger) {
166 return;
167 }
168
169 try {
170 // Get current sinks
171 auto sinks = m_logger->sinks();
172
173 // Find and replace the console sink
174 for (size_t i = 0; i < sinks.size(); ++i) {
175 // Check if this is a console sink (first sink is typically console)
176 if (i == 0) {
177 // Create new console sink with desired color setting
178 std::shared_ptr<spdlog::sinks::sink> new_console_sink;
179 if (enabled) {
180 auto color_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
181 // Force color mode to always enable (ignore TTY detection)
182 color_sink->set_color_mode(spdlog::color_mode::always);
183 new_console_sink = color_sink;
184 // Pattern with color markers (%^ and %$ for colored level)
185 new_console_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%^%l%$] [%n] %v");
186 } else {
187 new_console_sink = std::make_shared<spdlog::sinks::stdout_sink_mt>();
188 // Pattern without color markers
189 new_console_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] [%n] %v");
190 }
191
192 new_console_sink->set_level(sinks[i]->level());
193
194 // Replace the sink
195 sinks[i] = new_console_sink;
196 break;
197 }
198 }
199
200 // Create new logger with updated sinks
201 std::string logger_name = m_logger->name();
202 auto new_logger = std::make_shared<spdlog::logger>(logger_name, sinks.begin(), sinks.end());
203 new_logger->set_level(m_logger->level());
204 new_logger->flush_on(spdlog::level::warn);
205
206 // Replace in registry
207 spdlog::drop(logger_name);
208 spdlog::register_logger(new_logger);
209 spdlog::set_default_logger(new_logger);
210
211 m_logger = new_logger;
212
213 } catch (const std::exception &ex) {
214 // Failed to update console colors
215 }
216 }
217
221 void Shutdown() {
222 if (m_initialized) {
223 spdlog::shutdown();
224 m_initialized = false;
225 }
226 }
227
228 private:
229 SpdlogManager() = default;
230 // Do NOT call Shutdown() in destructor - causes use-after-free when spdlog's
231 // static registry is destroyed before this (static destruction order is undefined).
232 // Application must call Shutdown() explicitly in OnExit() before process exit.
233 ~SpdlogManager() = default;
234
235 std::shared_ptr<spdlog::logger> m_logger;
236 bool m_initialized = false;
237};
238
239} // namespace EmberCore
240
241// Convenience macros for spdlog (use EMBER_ prefix to avoid conflicts with spdlog's built-in macros)
242#define EMBER_SPDLOG_TRACE(...) \
243 if (auto logger = EmberCore::SpdlogManager::GetInstance().GetLogger()) \
244 logger->trace(__VA_ARGS__)
245#define EMBER_SPDLOG_DEBUG(...) \
246 if (auto logger = EmberCore::SpdlogManager::GetInstance().GetLogger()) \
247 logger->debug(__VA_ARGS__)
248#define EMBER_SPDLOG_INFO(...) \
249 if (auto logger = EmberCore::SpdlogManager::GetInstance().GetLogger()) \
250 logger->info(__VA_ARGS__)
251#define EMBER_SPDLOG_WARN(...) \
252 if (auto logger = EmberCore::SpdlogManager::GetInstance().GetLogger()) \
253 logger->warn(__VA_ARGS__)
254#define EMBER_SPDLOG_ERROR(...) \
255 if (auto logger = EmberCore::SpdlogManager::GetInstance().GetLogger()) \
256 logger->error(__VA_ARGS__)
257#define EMBER_SPDLOG_CRITICAL(...) \
258 if (auto logger = EmberCore::SpdlogManager::GetInstance().GetLogger()) \
259 logger->critical(__VA_ARGS__)
260
261// Alternative: You can also use spdlog's built-in macros directly after setting the default logger:
262// SPDLOG_INFO, SPDLOG_WARN, etc. will work automatically with our configured logger
263
264// Component-specific logging macros
265#define EMBER_SPDLOG_UI_INFO(...) \
266 if (auto logger = EmberCore::SpdlogManager::GetInstance().GetLogger("UI")) \
267 logger->info(__VA_ARGS__)
268#define EMBER_SPDLOG_UI_WARN(...) \
269 if (auto logger = EmberCore::SpdlogManager::GetInstance().GetLogger("UI")) \
270 logger->warn(__VA_ARGS__)
271#define EMBER_SPDLOG_UI_ERROR(...) \
272 if (auto logger = EmberCore::SpdlogManager::GetInstance().GetLogger("UI")) \
273 logger->error(__VA_ARGS__)
274
275#define EMBER_SPDLOG_CORE_INFO(...) \
276 if (auto logger = EmberCore::SpdlogManager::GetInstance().GetLogger("Core")) \
277 logger->info(__VA_ARGS__)
278#define EMBER_SPDLOG_CORE_WARN(...) \
279 if (auto logger = EmberCore::SpdlogManager::GetInstance().GetLogger("Core")) \
280 logger->warn(__VA_ARGS__)
281#define EMBER_SPDLOG_CORE_ERROR(...) \
282 if (auto logger = EmberCore::SpdlogManager::GetInstance().GetLogger("Core")) \
283 logger->error(__VA_ARGS__)
284
285#define EMBER_SPDLOG_NETWORK_INFO(...) \
286 if (auto logger = EmberCore::SpdlogManager::GetInstance().GetLogger("Network")) \
287 logger->info(__VA_ARGS__)
288#define EMBER_SPDLOG_NETWORK_WARN(...) \
289 if (auto logger = EmberCore::SpdlogManager::GetInstance().GetLogger("Network")) \
290 logger->warn(__VA_ARGS__)
291#define EMBER_SPDLOG_NETWORK_ERROR(...) \
292 if (auto logger = EmberCore::SpdlogManager::GetInstance().GetLogger("Network")) \
293 logger->error(__VA_ARGS__)
294
295#else
296
297// If spdlog is not available, define empty macros
298#define EMBER_SPDLOG_TRACE(...)
299#define EMBER_SPDLOG_DEBUG(...)
300#define EMBER_SPDLOG_INFO(...)
301#define EMBER_SPDLOG_WARN(...)
302#define EMBER_SPDLOG_ERROR(...)
303#define EMBER_SPDLOG_CRITICAL(...)
304
305#define EMBER_SPDLOG_UI_INFO(...)
306#define EMBER_SPDLOG_UI_WARN(...)
307#define EMBER_SPDLOG_UI_ERROR(...)
308
309#define EMBER_SPDLOG_CORE_INFO(...)
310#define EMBER_SPDLOG_CORE_WARN(...)
311#define EMBER_SPDLOG_CORE_ERROR(...)
312
313#define EMBER_SPDLOG_NETWORK_INFO(...)
314#define EMBER_SPDLOG_NETWORK_WARN(...)
315#define EMBER_SPDLOG_NETWORK_ERROR(...)
316
317#endif // HAVE_SPDLOG
Main types header for EmberCore.