Branch data Line data Source code
1 : : /*
2 : : * Copyright 2025 Jinwoo Sung
3 : : *
4 : : * Licensed under the Apache License, Version 2.0 (the "License");
5 : : * you may not use this file except in compliance with the License.
6 : : * You may obtain a copy of the License at
7 : : *
8 : : * http://www.apache.org/licenses/LICENSE-2.0
9 : : *
10 : : * Unless required by applicable law or agreed to in writing, software
11 : : * distributed under the License is distributed on an "AS IS" BASIS,
12 : : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : : * See the License for the specific language governing permissions and
14 : : * limitations under the License.
15 : : */
16 : :
17 : : #include "logger.hpp"
18 : :
19 : : #include <spdlog/async.h>
20 : : #include <spdlog/details/thread_pool.h>
21 : : #include <spdlog/sinks/base_sink.h>
22 : : #include <spdlog/sinks/dist_sink.h>
23 : : #include <spdlog/sinks/rotating_file_sink.h>
24 : : #include <spdlog/sinks/sink.h>
25 : : #include <spdlog/sinks/stdout_color_sinks.h>
26 : : #include <spdlog/spdlog.h>
27 : :
28 : : #include <algorithm>
29 : : #include <cctype>
30 : : #include <cstdlib>
31 : : #include <ctime>
32 : : #include <future>
33 : : #include <iomanip>
34 : : #include <iostream>
35 : : #include <mutex>
36 : : #include <sstream>
37 : : #include <string_view>
38 : :
39 : : namespace wirestead {
40 : : namespace diagnostics {
41 : :
42 : : /**
43 : : * @brief Custom spdlog sink for LogCallback
44 : : */
45 : : template <typename Mutex>
46 : : class callback_sink : public spdlog::sinks::base_sink<Mutex> {
47 : : public:
48 : 8 : explicit callback_sink(Logger::LogCallback callback) : callback_(std::move(callback)) {}
49 : :
50 : 1 : void set_callback(Logger::LogCallback callback) {
51 : 1 : std::lock_guard<Mutex> lock(spdlog::sinks::base_sink<Mutex>::mutex_);
52 : 1 : callback_ = std::move(callback);
53 : 1 : }
54 : :
55 : : protected:
56 : 16 : void sink_it_(const spdlog::details::log_msg& msg) override {
57 [ - + ]: 16 : if (!callback_) return;
58 : :
59 : 16 : spdlog::memory_buf_t formatted;
60 : 16 : spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted);
61 : : try {
62 : 17 : callback_(from_spdlog_level(msg.level), fmt::to_string(formatted));
63 : 1 : } catch (...) {
64 : : }
65 : 16 : }
66 : :
67 : 14 : void flush_() override {}
68 : :
69 : : private:
70 : 16 : static LogLevel from_spdlog_level(spdlog::level::level_enum level) {
71 [ + + + + : 16 : switch (level) {
+ - ]
72 : 2 : case spdlog::level::debug:
73 : 2 : return LogLevel::DEBUG;
74 : 10 : case spdlog::level::info:
75 : 10 : return LogLevel::INFO;
76 : 2 : case spdlog::level::warn:
77 : 2 : return LogLevel::WARNING;
78 : 1 : case spdlog::level::err:
79 : 1 : return LogLevel::ERROR;
80 : 1 : case spdlog::level::critical:
81 : 1 : return LogLevel::CRITICAL;
82 : 0 : default:
83 : 0 : return LogLevel::INFO;
84 : : }
85 : : }
86 : :
87 : : Logger::LogCallback callback_;
88 : : };
89 : :
90 : : using callback_sink_mt = callback_sink<std::mutex>;
91 : :
92 : : class level_range_sink final : public spdlog::sinks::sink {
93 : : public:
94 : 2676 : level_range_sink(std::shared_ptr<spdlog::sinks::sink> sink, spdlog::level::level_enum min_level,
95 : : spdlog::level::level_enum max_level)
96 : 2676 : : sink_(std::move(sink)), min_level_(min_level), max_level_(max_level) {}
97 : :
98 : 5474 : void log(const spdlog::details::log_msg& msg) override {
99 [ + + + + ]: 5474 : if (msg.level >= min_level_ && msg.level <= max_level_) {
100 : 2737 : sink_->log(msg);
101 : : }
102 : 5474 : }
103 : :
104 : 54 : void flush() override { sink_->flush(); }
105 : :
106 : 0 : void set_pattern(const std::string& pattern) override { sink_->set_pattern(pattern); }
107 : :
108 : 5404 : void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override {
109 : 5404 : sink_->set_formatter(std::move(sink_formatter));
110 : 5404 : }
111 : :
112 : : private:
113 : : std::shared_ptr<spdlog::sinks::sink> sink_;
114 : : spdlog::level::level_enum min_level_;
115 : : spdlog::level::level_enum max_level_;
116 : : };
117 : :
118 : : struct Logger::Impl {
119 : : mutable std::mutex mutex_;
120 : : std::atomic<LogLevel> current_level_{LogLevel::INFO};
121 : : std::atomic<bool> enabled_{true};
122 : : std::atomic<int> outputs_{static_cast<int>(LogOutput::CONSOLE)};
123 : :
124 : : std::shared_ptr<spdlog::logger> spd_logger_;
125 : : std::shared_ptr<spdlog::sinks::dist_sink_mt> dist_sink_;
126 : : std::shared_ptr<spdlog::sinks::stdout_color_sink_mt> console_stdout_sink_;
127 : : std::shared_ptr<spdlog::sinks::stderr_color_sink_mt> console_stderr_sink_;
128 : : std::shared_ptr<level_range_sink> console_stdout_filter_;
129 : : std::shared_ptr<level_range_sink> console_stderr_filter_;
130 : : std::shared_ptr<spdlog::sinks::rotating_file_sink_mt> file_sink_;
131 : : std::shared_ptr<callback_sink_mt> callback_sink_;
132 : :
133 : : std::string current_log_file_;
134 : : LogRotationConfig rotation_config_;
135 : : std::string format_ = "{timestamp} [{level}] [{component}] [{operation}] [{source}] {message}";
136 : : std::string last_error_;
137 : :
138 : : // Async logging support
139 : : std::atomic<bool> async_enabled_{false};
140 : : AsyncLogConfig async_config_;
141 : : std::future<void> drop_future_;
142 : : std::shared_ptr<spdlog::details::thread_pool> async_thread_pool_;
143 : :
144 : 3984 : Impl() {
145 : 1328 : dist_sink_ = std::make_shared<spdlog::sinks::dist_sink_mt>();
146 : :
147 : : // Default to sync logger initially, can be changed to async via set_async_logging
148 : 1328 : spd_logger_ = std::make_shared<spdlog::logger>("wirestead", dist_sink_);
149 : 1328 : spd_logger_->set_level(to_spdlog_level(LogLevel::DEBUG)); // Allow all, filter via Logger::log or Logger::set_level
150 : :
151 : 1328 : add_console_sinks();
152 : :
153 : 1328 : apply_spdlog_pattern();
154 : 1328 : apply_environment_settings();
155 : 1328 : }
156 : :
157 : 0 : ~Impl() {
158 [ # # ]: 0 : if (drop_future_.valid()) {
159 : 0 : drop_future_.wait();
160 : : }
161 : 0 : spdlog::drop("wirestead");
162 : 0 : }
163 : :
164 : 2717 : void apply_spdlog_pattern() {
165 [ + - ]: 2717 : if (dist_sink_) {
166 : 8151 : dist_sink_->set_pattern("%v");
167 : : }
168 : 2717 : }
169 : :
170 : 4 : void set_error(std::string message) { last_error_ = std::move(message); }
171 : :
172 : 224 : void clear_error() { last_error_.clear(); }
173 : :
174 : 13818 : bool has_outputs_unlocked() const { return outputs_.load() != 0; }
175 : :
176 : 1354 : void add_console_sinks() {
177 [ + + ]: 1354 : if (!console_stdout_sink_) {
178 : 1338 : console_stdout_sink_ = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
179 : : console_stdout_filter_ =
180 : 1338 : std::make_shared<level_range_sink>(console_stdout_sink_, spdlog::level::trace, spdlog::level::warn);
181 : : }
182 [ + + ]: 1354 : if (!console_stderr_sink_) {
183 : 1338 : console_stderr_sink_ = std::make_shared<spdlog::sinks::stderr_color_sink_mt>();
184 : : console_stderr_filter_ =
185 : 1338 : std::make_shared<level_range_sink>(console_stderr_sink_, spdlog::level::err, spdlog::level::critical);
186 : : }
187 : :
188 : 1354 : dist_sink_->remove_sink(console_stdout_filter_);
189 : 1354 : dist_sink_->remove_sink(console_stderr_filter_);
190 : 1354 : dist_sink_->add_sink(console_stdout_filter_);
191 : 1354 : dist_sink_->add_sink(console_stderr_filter_);
192 : 1354 : apply_spdlog_pattern();
193 : 1354 : }
194 : :
195 : 49 : void remove_console_sinks() {
196 [ + + ]: 49 : if (console_stdout_filter_) {
197 : 44 : dist_sink_->remove_sink(console_stdout_filter_);
198 : : }
199 [ + + ]: 49 : if (console_stderr_filter_) {
200 : 44 : dist_sink_->remove_sink(console_stderr_filter_);
201 : : }
202 : 49 : console_stdout_filter_.reset();
203 : 49 : console_stderr_filter_.reset();
204 : 49 : console_stdout_sink_.reset();
205 : 49 : console_stderr_sink_.reset();
206 : 49 : }
207 : :
208 : 36 : void set_format(const std::string& format) { format_ = format; }
209 : :
210 : 25416 : static void replace_all(std::string& str, const std::string& from, const std::string& to) {
211 [ - + ]: 25416 : if (from.empty()) {
212 : 0 : return;
213 : : }
214 : :
215 : 25416 : size_t pos = 0;
216 [ + + ]: 42358 : while ((pos = str.find(from, pos)) != std::string::npos) {
217 : 16942 : str.replace(pos, from.length(), to);
218 : 16942 : pos += to.length();
219 : : }
220 : : }
221 : :
222 : 2824 : static std::string level_name(LogLevel level) {
223 [ + + + + : 2824 : switch (level) {
+ - ]
224 : 117 : case LogLevel::DEBUG:
225 : 234 : return "debug";
226 : 1824 : case LogLevel::INFO:
227 : 3648 : return "info";
228 : 525 : case LogLevel::WARNING:
229 : 1050 : return "warning";
230 : 355 : case LogLevel::ERROR:
231 : 710 : return "error";
232 : 3 : case LogLevel::CRITICAL:
233 : 6 : return "critical";
234 : 0 : default:
235 : 0 : return "info";
236 : : }
237 : : }
238 : :
239 : 10 : static std::string normalize_env_value(std::string_view value) {
240 : 10 : std::string normalized(value);
241 [ + - + - ]: 10 : normalized.erase(normalized.begin(), std::find_if(normalized.begin(), normalized.end(),
242 : 11 : [](unsigned char c) { return !std::isspace(c); }));
243 [ + - ]: 20 : normalized.erase(
244 [ + - ]: 21 : std::find_if(normalized.rbegin(), normalized.rend(), [](unsigned char c) { return !std::isspace(c); }).base(),
245 : 10 : normalized.end());
246 : 10 : std::transform(normalized.begin(), normalized.end(), normalized.begin(),
247 : 49 : [](unsigned char c) { return static_cast<char>(std::toupper(c)); });
248 : 10 : return normalized;
249 : 0 : }
250 : :
251 : 10 : static bool parse_log_level(std::string_view value, LogLevel& level, bool& disable_logging) {
252 : 10 : const auto normalized = normalize_env_value(value);
253 : 10 : disable_logging = false;
254 [ + - + - : 10 : if (normalized == "DEBUG" || normalized == "TRACE") {
+ - + + +
+ ]
255 : 1 : level = LogLevel::DEBUG;
256 : 1 : return true;
257 : : }
258 [ + - + + ]: 9 : if (normalized == "INFO") {
259 : 1 : level = LogLevel::INFO;
260 : 1 : return true;
261 : : }
262 [ + - + + : 8 : if (normalized == "WARNING" || normalized == "WARN") {
+ - + + +
+ ]
263 : 3 : level = LogLevel::WARNING;
264 : 3 : return true;
265 : : }
266 [ + - + - : 5 : if (normalized == "ERROR" || normalized == "ERR") {
+ - + + +
+ ]
267 : 1 : level = LogLevel::ERROR;
268 : 1 : return true;
269 : : }
270 [ + - + - : 4 : if (normalized == "CRITICAL" || normalized == "FATAL") {
+ - + + +
+ ]
271 : 1 : level = LogLevel::CRITICAL;
272 : 1 : return true;
273 : : }
274 [ + - + + : 3 : if (normalized == "OFF" || normalized == "NONE" || normalized == "DISABLED") {
+ - + + +
- - + +
+ ]
275 : 2 : disable_logging = true;
276 : 2 : return true;
277 : : }
278 : 1 : return false;
279 : 10 : }
280 : :
281 : 1337 : void apply_environment_settings() {
282 : 1337 : const char* env_level = std::getenv("WIRESTEAD_LOG_LEVEL");
283 : 1337 : const char* env_name = "WIRESTEAD_LOG_LEVEL";
284 [ + + - + ]: 1337 : if (!env_level || *env_level == '\0') {
285 : 1328 : return;
286 : : }
287 : :
288 : 10 : LogLevel parsed_level = current_level_.load();
289 : 10 : bool disable_logging = false;
290 [ + - + + ]: 10 : if (!parse_log_level(env_level, parsed_level, disable_logging)) {
291 : 3 : set_error("Invalid " + std::string(env_name) + ": " + std::string(env_level));
292 : 1 : return;
293 : : }
294 : :
295 : 9 : enabled_.store(!disable_logging);
296 [ + + ]: 9 : if (!disable_logging) {
297 : 7 : current_level_.store(parsed_level);
298 [ + - ]: 7 : if (spd_logger_) {
299 : 7 : spd_logger_->set_level(to_spdlog_level(parsed_level));
300 : : }
301 : : }
302 : 9 : clear_error();
303 : : }
304 : :
305 : 2824 : static std::string timestamp_now() {
306 : : using namespace std::chrono;
307 : 2824 : const auto now = system_clock::now();
308 : 2824 : const auto tt = system_clock::to_time_t(now);
309 : 2824 : std::tm tm{};
310 : : #if defined(_WIN32)
311 : : localtime_s(&tm, &tt);
312 : : #else
313 : 2824 : localtime_r(&tt, &tm);
314 : : #endif
315 : 2824 : const auto ms = duration_cast<milliseconds>(now.time_since_epoch()) % 1000;
316 : :
317 : 2824 : std::ostringstream oss;
318 : 2824 : oss << std::put_time(&tm, "%F %T") << '.' << std::setw(3) << std::setfill('0') << ms.count();
319 : 5648 : return oss.str();
320 : 2824 : }
321 : :
322 : 2824 : static std::string format_message(const std::string& format, LogLevel level, std::string_view component,
323 : : std::string_view operation, std::string_view message,
324 : : const std::source_location& loc) {
325 : 2824 : const auto file = std::string(loc.file_name());
326 : 2824 : const auto line = std::to_string(loc.line());
327 : 2824 : const auto function = std::string(loc.function_name());
328 : 2824 : const auto source = fmt::format("{}:{}:{}", file, line, function);
329 : :
330 : 2824 : std::string formatted = format;
331 : 8472 : replace_all(formatted, "{timestamp}", timestamp_now());
332 : 8472 : replace_all(formatted, "{level}", level_name(level));
333 : 11296 : replace_all(formatted, "{component}", std::string(component));
334 : 11296 : replace_all(formatted, "{operation}", std::string(operation));
335 : 5648 : replace_all(formatted, "{source}", source);
336 : 5648 : replace_all(formatted, "{file}", file);
337 : 5648 : replace_all(formatted, "{line}", line);
338 : 5648 : replace_all(formatted, "{function}", function);
339 : 8472 : replace_all(formatted, "{message}", std::string(message));
340 : 5648 : return formatted;
341 : 2824 : }
342 : :
343 : : void flush() {
344 : : auto logger = spd_logger_;
345 : : auto sink = dist_sink_;
346 : : if (logger) logger->flush();
347 : : if (sink) sink->flush();
348 : : }
349 : :
350 : 7 : void setup_async_logging(const AsyncLogConfig& config) {
351 : 7 : async_config_ = config;
352 : :
353 : 7 : spdlog::drop("wirestead"); // Drop existing logger
354 : :
355 [ + - ]: 7 : auto overflow_policy = config.enable_backpressure ? spdlog::async_overflow_policy::block
356 : : : spdlog::async_overflow_policy::overrun_oldest;
357 : :
358 : 7 : async_thread_pool_ = std::make_shared<spdlog::details::thread_pool>(std::max<size_t>(1, config.max_queue_size), 1);
359 : :
360 : 7 : spd_logger_ = std::make_shared<spdlog::async_logger>("wirestead", dist_sink_, async_thread_pool_, overflow_policy);
361 : 7 : spd_logger_->set_level(to_spdlog_level(current_level_.load()));
362 : 7 : apply_spdlog_pattern();
363 : :
364 : 7 : spdlog::register_logger(spd_logger_);
365 : :
366 [ + + ]: 7 : if (config.flush_interval.count() > 0) {
367 : : // spdlog::flush_every takes std::chrono::seconds.
368 : : // Ensure at least 1 second if a positive interval is requested.
369 : : auto secs =
370 : 5 : std::max(std::chrono::seconds(1), std::chrono::duration_cast<std::chrono::seconds>(config.flush_interval));
371 : 5 : spdlog::flush_every(secs);
372 : : }
373 : :
374 : 7 : async_enabled_.store(true);
375 : 7 : }
376 : :
377 : 13 : void teardown_async_logging() {
378 [ + + ]: 13 : if (!async_enabled_.load()) return;
379 : :
380 : 7 : async_enabled_.store(false);
381 : :
382 : : // If there's a previous drop still pending, wait for it now to avoid multiple drop tasks
383 [ + + ]: 7 : if (drop_future_.valid()) {
384 : 1 : drop_future_.wait();
385 : : }
386 : :
387 : : // Use std::async to drop the logger with a timeout
388 : 28 : drop_future_ = std::async(std::launch::async, []() { spdlog::drop("wirestead"); });
389 : :
390 : 7 : bool drop_success = true;
391 [ - + ]: 7 : if (drop_future_.wait_for(async_config_.shutdown_timeout) == std::future_status::timeout) {
392 : 0 : drop_success = false;
393 : : }
394 : :
395 : 7 : spd_logger_.reset();
396 [ + - ]: 7 : if (drop_success) {
397 : 7 : async_thread_pool_.reset();
398 : : }
399 : 7 : dist_sink_->flush();
400 : :
401 : : // Create sync logger
402 : 7 : spd_logger_ = std::make_shared<spdlog::logger>("wirestead", dist_sink_);
403 : 7 : spd_logger_->set_level(to_spdlog_level(current_level_.load()));
404 : 7 : apply_spdlog_pattern();
405 : :
406 : : // Only register globally if the previous drop finished, to avoid race conditions.
407 : : // If it didn't finish, we still have spd_logger_ internally so log() works.
408 [ + - ]: 7 : if (drop_success) {
409 : 7 : spdlog::register_logger(spd_logger_);
410 : : }
411 : : }
412 : :
413 : 4258 : static spdlog::level::level_enum to_spdlog_level(LogLevel level) {
414 [ + + + + : 4258 : switch (level) {
+ - ]
415 : 1527 : case LogLevel::DEBUG:
416 : 1527 : return spdlog::level::debug;
417 : 1837 : case LogLevel::INFO:
418 : 1837 : return spdlog::level::info;
419 : 530 : case LogLevel::WARNING:
420 : 530 : return spdlog::level::warn;
421 : 359 : case LogLevel::ERROR:
422 : 359 : return spdlog::level::err;
423 : 5 : case LogLevel::CRITICAL:
424 : 5 : return spdlog::level::critical;
425 : 0 : default:
426 : 0 : return spdlog::level::info;
427 : : }
428 : : }
429 : : };
430 : :
431 : 1328 : Logger::Logger() : impl_(std::make_unique<Impl>()) {}
432 : :
433 : 0 : Logger::~Logger() = default;
434 : :
435 : 0 : Logger::Logger(Logger&&) noexcept = default;
436 : 0 : Logger& Logger::operator=(Logger&&) noexcept = default;
437 : :
438 : 7495 : Logger& Logger::instance() {
439 : 7495 : static Logger* inst = new Logger();
440 : 7495 : return *inst;
441 : : }
442 : :
443 : 1 : Logger& Logger::default_logger() { return instance(); }
444 : :
445 : 85 : void Logger::set_level(LogLevel level) {
446 : 85 : std::lock_guard<std::mutex> lock(impl_->mutex_);
447 : 85 : impl_->current_level_.store(level);
448 [ + - ]: 85 : if (impl_->spd_logger_) {
449 : 85 : impl_->spd_logger_->set_level(Impl::to_spdlog_level(level));
450 : : }
451 : 85 : }
452 : :
453 : 8 : LogLevel Logger::level() const { return get_impl()->current_level_.load(); }
454 : :
455 : 6909 : bool Logger::should_log(LogLevel level) const {
456 : 6909 : const auto* impl = get_impl();
457 [ + + + + : 6909 : return impl->enabled_.load() && impl->has_outputs_unlocked() && level >= impl->current_level_.load();
+ + ]
458 : : }
459 : :
460 : 71 : void Logger::set_console_output(bool enable) {
461 : 71 : std::lock_guard<std::mutex> lock(impl_->mutex_);
462 [ + + ]: 71 : if (enable) {
463 : 26 : impl_->add_console_sinks();
464 : 26 : impl_->outputs_.fetch_or(static_cast<int>(LogOutput::CONSOLE));
465 : : } else {
466 : 45 : impl_->remove_console_sinks();
467 : 45 : impl_->outputs_.fetch_and(~static_cast<int>(LogOutput::CONSOLE));
468 : : }
469 : 71 : impl_->clear_error();
470 : 71 : }
471 : :
472 : 50 : void Logger::set_file_output(const std::string& filename) { (void)try_set_file_output_with_rotation(filename); }
473 : :
474 : 1 : bool Logger::try_set_file_output(const std::string& filename) { return try_set_file_output_with_rotation(filename); }
475 : :
476 : 5 : void Logger::set_file_output_with_rotation(const std::string& filename, const LogRotationConfig& config) {
477 : 5 : (void)try_set_file_output_with_rotation(filename, config);
478 : 5 : }
479 : :
480 : 58 : bool Logger::try_set_file_output_with_rotation(const std::string& filename, const LogRotationConfig& config) {
481 : 58 : std::lock_guard<std::mutex> lock(impl_->mutex_);
482 : :
483 [ + + ]: 58 : if (filename.empty()) {
484 [ + + ]: 45 : if (impl_->file_sink_) {
485 : 10 : impl_->dist_sink_->remove_sink(impl_->file_sink_);
486 : 10 : impl_->file_sink_.reset();
487 : : }
488 : 45 : impl_->current_log_file_.clear();
489 : 45 : impl_->outputs_.fetch_and(~static_cast<int>(LogOutput::FILE));
490 : 45 : impl_->clear_error();
491 : 45 : return true;
492 : : } else {
493 : : try {
494 [ + + ]: 13 : if (config.enable_compression) {
495 : 2 : impl_->set_error("Log compression is not supported by the current rotating file sink");
496 : 1 : return false;
497 : : }
498 [ + - + + ]: 12 : if (config.file_pattern != LogRotationConfig{}.file_pattern) {
499 : 2 : impl_->set_error("Custom log rotation file_pattern is not supported by the current rotating file sink");
500 : 1 : return false;
501 : : }
502 [ - + ]: 11 : if (impl_->file_sink_) {
503 : 0 : impl_->dist_sink_->remove_sink(impl_->file_sink_);
504 : : }
505 : :
506 : 11 : impl_->rotation_config_ = config;
507 : 11 : impl_->current_log_file_ = filename;
508 : :
509 : 21 : impl_->file_sink_ = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(filename, config.max_file_size_bytes,
510 : 21 : config.max_files);
511 : :
512 : 10 : impl_->dist_sink_->add_sink(impl_->file_sink_);
513 : 10 : impl_->apply_spdlog_pattern();
514 : 10 : impl_->outputs_.fetch_or(static_cast<int>(LogOutput::FILE));
515 : 10 : impl_->clear_error();
516 : 10 : return true;
517 : 1 : } catch (const spdlog::spdlog_ex& e) {
518 : 1 : impl_->set_error("Failed to open log file: " + filename + " (" + e.what() + ")");
519 : 1 : std::cerr << impl_->last_error_ << std::endl;
520 : 1 : impl_->file_sink_.reset();
521 : 1 : impl_->current_log_file_.clear();
522 : 1 : impl_->outputs_.fetch_and(~static_cast<int>(LogOutput::FILE));
523 : 1 : return false;
524 : 1 : }
525 : : }
526 : 58 : }
527 : :
528 : 19 : void Logger::set_async_logging(bool enable, const AsyncLogConfig& config) {
529 : 19 : std::lock_guard<std::mutex> lock(impl_->mutex_);
530 [ + + ]: 19 : if (enable) {
531 [ + + ]: 7 : if (impl_->async_enabled_.load()) {
532 : 1 : impl_->teardown_async_logging();
533 : : }
534 : 7 : impl_->setup_async_logging(config);
535 : : } else {
536 : 12 : impl_->teardown_async_logging();
537 : : }
538 : 19 : }
539 : :
540 : 9 : bool Logger::async_logging_enabled() const { return get_impl()->async_enabled_.load(); }
541 : :
542 : 9 : void Logger::reload_from_environment() {
543 : 9 : std::lock_guard<std::mutex> lock(impl_->mutex_);
544 : 9 : impl_->apply_environment_settings();
545 : 9 : }
546 : :
547 : 49 : void Logger::set_callback(LogCallback callback) {
548 : 49 : std::lock_guard<std::mutex> lock(impl_->mutex_);
549 : :
550 [ + + ]: 49 : if (!callback) {
551 [ + + ]: 40 : if (impl_->callback_sink_) {
552 : 8 : impl_->dist_sink_->remove_sink(impl_->callback_sink_);
553 : 8 : impl_->callback_sink_.reset();
554 : : }
555 : 40 : impl_->outputs_.fetch_and(~static_cast<int>(LogOutput::CALLBACK));
556 : 40 : impl_->clear_error();
557 : 40 : return;
558 : : }
559 : :
560 [ + + ]: 9 : if (impl_->callback_sink_) {
561 : 1 : impl_->callback_sink_->set_callback(std::move(callback));
562 : 1 : impl_->dist_sink_->remove_sink(impl_->callback_sink_); // Avoid duplicates
563 : 1 : impl_->dist_sink_->add_sink(impl_->callback_sink_);
564 : : } else {
565 : 8 : impl_->callback_sink_ = std::make_shared<callback_sink_mt>(std::move(callback));
566 : 8 : impl_->dist_sink_->add_sink(impl_->callback_sink_);
567 : : }
568 : 9 : impl_->apply_spdlog_pattern();
569 : 9 : impl_->outputs_.fetch_or(static_cast<int>(LogOutput::CALLBACK));
570 : 9 : impl_->clear_error();
571 : 49 : }
572 : :
573 : 4 : void Logger::set_outputs(int outputs) {
574 : 4 : std::lock_guard<std::mutex> lock(impl_->mutex_);
575 : 4 : int effective_outputs = 0;
576 : :
577 : : // Reconcile sinks in dist_sink_ based on the new bitmask
578 : :
579 : : // Console Sink
580 [ - + ]: 4 : if (outputs & static_cast<int>(LogOutput::CONSOLE)) {
581 : 0 : impl_->add_console_sinks();
582 : 0 : effective_outputs |= static_cast<int>(LogOutput::CONSOLE);
583 : : } else {
584 : 4 : impl_->remove_console_sinks();
585 : : }
586 : :
587 : : // File Sink
588 [ + + ]: 4 : if (outputs & static_cast<int>(LogOutput::FILE)) {
589 [ + - + - : 1 : if (!impl_->file_sink_ && !impl_->current_log_file_.empty()) {
+ - ]
590 : 2 : impl_->file_sink_ = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
591 : 2 : impl_->current_log_file_, impl_->rotation_config_.max_file_size_bytes, impl_->rotation_config_.max_files);
592 : : }
593 [ + - ]: 1 : if (impl_->file_sink_) {
594 : 1 : impl_->dist_sink_->remove_sink(impl_->file_sink_); // Ensure no duplicate
595 : 1 : impl_->dist_sink_->add_sink(impl_->file_sink_);
596 : 1 : impl_->apply_spdlog_pattern();
597 : 1 : effective_outputs |= static_cast<int>(LogOutput::FILE);
598 : : }
599 : : } else {
600 [ + + ]: 3 : if (impl_->file_sink_) {
601 : 1 : impl_->dist_sink_->remove_sink(impl_->file_sink_);
602 : 1 : impl_->file_sink_.reset();
603 : : }
604 : : }
605 : :
606 : : // Callback Sink
607 [ + + ]: 4 : if (outputs & static_cast<int>(LogOutput::CALLBACK)) {
608 [ + - ]: 1 : if (impl_->callback_sink_) {
609 : 1 : impl_->dist_sink_->remove_sink(impl_->callback_sink_); // Ensure no duplicate
610 : 1 : impl_->dist_sink_->add_sink(impl_->callback_sink_);
611 : 1 : impl_->apply_spdlog_pattern();
612 : 1 : effective_outputs |= static_cast<int>(LogOutput::CALLBACK);
613 : : }
614 : : } else {
615 [ + + ]: 3 : if (impl_->callback_sink_) {
616 : 2 : impl_->dist_sink_->remove_sink(impl_->callback_sink_);
617 : : }
618 : : }
619 : :
620 : 4 : impl_->outputs_.store(effective_outputs);
621 : 4 : impl_->clear_error();
622 : 4 : }
623 : :
624 : 43 : void Logger::set_enabled(bool enabled) { impl_->enabled_.store(enabled); }
625 : :
626 : 8 : bool Logger::enabled() const { return get_impl()->enabled_.load(); }
627 : :
628 : 2 : bool Logger::has_outputs() const { return get_impl()->has_outputs_unlocked(); }
629 : :
630 : 10 : std::string Logger::last_error() const {
631 : 10 : std::lock_guard<std::mutex> lock(impl_->mutex_);
632 : 20 : return impl_->last_error_;
633 : 10 : }
634 : :
635 : 36 : void Logger::set_format(const std::string& format) {
636 : 36 : std::lock_guard<std::mutex> lock(impl_->mutex_);
637 : 36 : impl_->set_format(format);
638 : 36 : impl_->clear_error();
639 : 36 : }
640 : :
641 : 26 : void Logger::flush() {
642 : 26 : std::shared_ptr<spdlog::logger> logger;
643 : 26 : std::shared_ptr<spdlog::sinks::dist_sink_mt> sink;
644 : : {
645 : 26 : std::lock_guard<std::mutex> lock(impl_->mutex_);
646 : 26 : logger = impl_->spd_logger_;
647 : 26 : sink = impl_->dist_sink_;
648 : 26 : }
649 [ + - + - ]: 26 : if (logger) logger->flush();
650 [ + - + - ]: 26 : if (sink) sink->flush();
651 : 26 : }
652 : :
653 : 2824 : void Logger::log(LogLevel level, std::string_view component, std::string_view operation, std::string_view message,
654 : : const std::source_location& loc) {
655 [ - + ]: 2824 : if (!should_log(level)) {
656 : 0 : return;
657 : : }
658 : :
659 : 2824 : std::shared_ptr<spdlog::logger> logger;
660 : 2824 : std::string format;
661 : : {
662 : 2824 : std::lock_guard<std::mutex> lock(impl_->mutex_);
663 : 2824 : logger = impl_->spd_logger_;
664 : 2824 : format = impl_->format_;
665 : 2824 : }
666 : :
667 [ - + ]: 2824 : if (!logger) {
668 : 0 : return;
669 : : }
670 : :
671 : 2824 : const auto payload = Impl::format_message(format, level, component, operation, message, loc);
672 : 2824 : logger->log(Impl::to_spdlog_level(level), payload);
673 : 2824 : }
674 : :
675 : 1 : void Logger::debug(std::string_view component, std::string_view operation, std::string_view message,
676 : : const std::source_location& loc) {
677 : 1 : log(LogLevel::DEBUG, component, operation, message, loc);
678 : 1 : }
679 : :
680 : 1 : void Logger::info(std::string_view component, std::string_view operation, std::string_view message,
681 : : const std::source_location& loc) {
682 : 1 : log(LogLevel::INFO, component, operation, message, loc);
683 : 1 : }
684 : :
685 : 1 : void Logger::warning(std::string_view component, std::string_view operation, std::string_view message,
686 : : const std::source_location& loc) {
687 : 1 : log(LogLevel::WARNING, component, operation, message, loc);
688 : 1 : }
689 : :
690 : 1 : void Logger::error(std::string_view component, std::string_view operation, std::string_view message,
691 : : const std::source_location& loc) {
692 : 1 : log(LogLevel::ERROR, component, operation, message, loc);
693 : 1 : }
694 : :
695 : 1 : void Logger::critical(std::string_view component, std::string_view operation, std::string_view message,
696 : : const std::source_location& loc) {
697 : 1 : log(LogLevel::CRITICAL, component, operation, message, loc);
698 : 1 : }
699 : :
700 : : } // namespace diagnostics
701 : : } // namespace wirestead
|