LCOV - code coverage report
Current view: top level - wirestead/diagnostics - logger.cc (source / functions) Coverage Total Hit
Test: Wirestead Coverage Report Lines: 94.3 % 407 384
Test Date: 2026-10-29 11:36:05 Functions: 92.2 % 64 59
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 74.5 % 196 146

             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
        

Generated by: LCOV version 2.0-1