LCOV - code coverage report
Current view: top level - wirestead/diagnostics - logger.cc (source / functions) Coverage Total Hit
Test: Wirestead Coverage Report Lines: 94.4 % 413 390
Test Date: 2026-08-30 10:35:09 Functions: 92.2 % 64 59
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 75.9 % 216 164

             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                 :         702 :   level_range_sink(std::shared_ptr<spdlog::sinks::sink> sink, spdlog::level::level_enum min_level,
      95                 :             :                    spdlog::level::level_enum max_level)
      96                 :         702 :       : sink_(std::move(sink)), min_level_(min_level), max_level_(max_level) {}
      97                 :             : 
      98                 :        1662 :   void log(const spdlog::details::log_msg& msg) override {
      99   [ +  +  +  + ]:        1662 :     if (msg.level >= min_level_ && msg.level <= max_level_) {
     100                 :         831 :       sink_->log(msg);
     101                 :             :     }
     102                 :        1662 :   }
     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                 :        1456 :   void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override {
     109                 :        1456 :     sink_->set_formatter(std::move(sink_formatter));
     110                 :        1456 :   }
     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                 :        1023 :   Impl() {
     145                 :         341 :     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                 :         341 :     spd_logger_ = std::make_shared<spdlog::logger>("wirestead", dist_sink_);
     149                 :         341 :     spd_logger_->set_level(to_spdlog_level(LogLevel::DEBUG));  // Allow all, filter via Logger::log or Logger::set_level
     150                 :             : 
     151                 :         341 :     add_console_sinks();
     152                 :             : 
     153                 :         341 :     apply_spdlog_pattern();
     154                 :         341 :     apply_environment_settings();
     155                 :         341 :   }
     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                 :         743 :   void apply_spdlog_pattern() {
     165         [ +  - ]:         743 :     if (dist_sink_) {
     166                 :        2229 :       dist_sink_->set_pattern("%v");
     167                 :             :     }
     168                 :         743 :   }
     169                 :             : 
     170                 :           4 :   void set_error(std::string message) { last_error_ = std::move(message); }
     171                 :             : 
     172                 :         231 :   void clear_error() { last_error_.clear(); }
     173                 :             : 
     174                 :        5768 :   bool has_outputs_unlocked() const { return outputs_.load() != 0; }
     175                 :             : 
     176                 :         367 :   void add_console_sinks() {
     177         [ +  + ]:         367 :     if (!console_stdout_sink_) {
     178                 :         351 :       console_stdout_sink_ = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
     179                 :             :       console_stdout_filter_ =
     180                 :         351 :           std::make_shared<level_range_sink>(console_stdout_sink_, spdlog::level::trace, spdlog::level::warn);
     181                 :             :     }
     182         [ +  + ]:         367 :     if (!console_stderr_sink_) {
     183                 :         351 :       console_stderr_sink_ = std::make_shared<spdlog::sinks::stderr_color_sink_mt>();
     184                 :             :       console_stderr_filter_ =
     185                 :         351 :           std::make_shared<level_range_sink>(console_stderr_sink_, spdlog::level::err, spdlog::level::critical);
     186                 :             :     }
     187                 :             : 
     188                 :         367 :     dist_sink_->remove_sink(console_stdout_filter_);
     189                 :         367 :     dist_sink_->remove_sink(console_stderr_filter_);
     190                 :         367 :     dist_sink_->add_sink(console_stdout_filter_);
     191                 :         367 :     dist_sink_->add_sink(console_stderr_filter_);
     192                 :         367 :     apply_spdlog_pattern();
     193                 :         367 :   }
     194                 :             : 
     195                 :          50 :   void remove_console_sinks() {
     196         [ +  + ]:          50 :     if (console_stdout_filter_) {
     197                 :          45 :       dist_sink_->remove_sink(console_stdout_filter_);
     198                 :             :     }
     199         [ +  + ]:          50 :     if (console_stderr_filter_) {
     200                 :          45 :       dist_sink_->remove_sink(console_stderr_filter_);
     201                 :             :     }
     202                 :          50 :     console_stdout_filter_.reset();
     203                 :          50 :     console_stderr_filter_.reset();
     204                 :          50 :     console_stdout_sink_.reset();
     205                 :          50 :     console_stderr_sink_.reset();
     206                 :          50 :   }
     207                 :             : 
     208                 :          37 :   void set_format(const std::string& format) { format_ = format; }
     209                 :             : 
     210                 :        8262 :   static void replace_all(std::string& str, const std::string& from, const std::string& to) {
     211         [ -  + ]:        8262 :     if (from.empty()) {
     212                 :           0 :       return;
     213                 :             :     }
     214                 :             : 
     215                 :        8262 :     size_t pos = 0;
     216         [ +  + ]:       13768 :     while ((pos = str.find(from, pos)) != std::string::npos) {
     217                 :        5506 :       str.replace(pos, from.length(), to);
     218                 :        5506 :       pos += to.length();
     219                 :             :     }
     220                 :             :   }
     221                 :             : 
     222                 :         918 :   static std::string level_name(LogLevel level) {
     223   [ +  +  +  +  :         918 :     switch (level) {
                   +  - ]
     224                 :         117 :       case LogLevel::DEBUG:
     225                 :         234 :         return "debug";
     226                 :         599 :       case LogLevel::INFO:
     227                 :        1198 :         return "info";
     228                 :          64 :       case LogLevel::WARNING:
     229                 :         128 :         return "warning";
     230                 :         135 :       case LogLevel::ERROR:
     231                 :         270 :         return "error";
     232                 :           3 :       case LogLevel::CRITICAL:
     233                 :           6 :         return "critical";
     234                 :           0 :       default:
     235                 :           0 :         return "info";
     236                 :             :     }
     237                 :             :   }
     238                 :             : 
     239                 :          13 :   static std::string normalize_env_value(std::string_view value) {
     240                 :          13 :     std::string normalized(value);
     241   [ +  -  +  - ]:          13 :     normalized.erase(normalized.begin(), std::find_if(normalized.begin(), normalized.end(),
     242                 :          14 :                                                       [](unsigned char c) { return !std::isspace(c); }));
     243         [ +  - ]:          26 :     normalized.erase(
     244         [ +  - ]:          27 :         std::find_if(normalized.rbegin(), normalized.rend(), [](unsigned char c) { return !std::isspace(c); }).base(),
     245                 :          13 :         normalized.end());
     246                 :          13 :     std::transform(normalized.begin(), normalized.end(), normalized.begin(),
     247                 :          66 :                    [](unsigned char c) { return static_cast<char>(std::toupper(c)); });
     248                 :          13 :     return normalized;
     249                 :           0 :   }
     250                 :             : 
     251                 :          13 :   static bool parse_log_level(std::string_view value, LogLevel& level, bool& disable_logging) {
     252                 :          13 :     const auto normalized = normalize_env_value(value);
     253                 :          13 :     disable_logging = false;
     254   [ +  -  +  -  :          13 :     if (normalized == "DEBUG" || normalized == "TRACE") {
          +  -  +  +  +  
                      + ]
     255                 :           1 :       level = LogLevel::DEBUG;
     256                 :           1 :       return true;
     257                 :             :     }
     258   [ +  -  +  + ]:          12 :     if (normalized == "INFO") {
     259                 :           1 :       level = LogLevel::INFO;
     260                 :           1 :       return true;
     261                 :             :     }
     262   [ +  -  +  +  :          11 :     if (normalized == "WARNING" || normalized == "WARN") {
          +  -  +  +  +  
                      + ]
     263                 :           4 :       level = LogLevel::WARNING;
     264                 :           4 :       return true;
     265                 :             :     }
     266   [ +  -  +  +  :           7 :     if (normalized == "ERROR" || normalized == "ERR") {
          +  -  +  +  +  
                      + ]
     267                 :           3 :       level = LogLevel::ERROR;
     268                 :           3 :       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                 :          13 :   }
     280                 :             : 
     281                 :         353 :   void apply_environment_settings() {
     282                 :             :     // WIRESTEAD_LOG_LEVEL takes priority over UNILINK_LOG_LEVEL when both are
     283                 :             :     // set (docs/migration-from-unilink.md compatibility policy), with a
     284                 :             :     // startup log line so the choice is discoverable rather than silent.
     285                 :         353 :     const char* wirestead_env = std::getenv("WIRESTEAD_LOG_LEVEL");
     286                 :         353 :     const char* unilink_env = std::getenv("UNILINK_LOG_LEVEL");
     287   [ +  +  +  - ]:         353 :     const bool has_wirestead_env = wirestead_env && *wirestead_env != '\0';
     288   [ +  +  +  - ]:         353 :     const bool has_unilink_env = unilink_env && *unilink_env != '\0';
     289                 :             : 
     290         [ +  + ]:         353 :     const char* env_level = has_wirestead_env ? wirestead_env : unilink_env;
     291         [ +  + ]:         353 :     const char* env_name = has_wirestead_env ? "WIRESTEAD_LOG_LEVEL" : "UNILINK_LOG_LEVEL";
     292   [ +  +  -  + ]:         353 :     if (!env_level || *env_level == '\0') {
     293                 :         341 :       return;
     294                 :             :     }
     295                 :             : 
     296   [ +  +  +  +  :          13 :     if (has_wirestead_env && has_unilink_env && spd_logger_) {
             +  -  +  + ]
     297                 :           1 :       spd_logger_->info("WIRESTEAD_LOG_LEVEL is set alongside UNILINK_LOG_LEVEL; WIRESTEAD_LOG_LEVEL takes precedence");
     298                 :             :     }
     299                 :             : 
     300                 :          13 :     LogLevel parsed_level = current_level_.load();
     301                 :          13 :     bool disable_logging = false;
     302   [ +  -  +  + ]:          13 :     if (!parse_log_level(env_level, parsed_level, disable_logging)) {
     303                 :           3 :       set_error("Invalid " + std::string(env_name) + ": " + std::string(env_level));
     304                 :           1 :       return;
     305                 :             :     }
     306                 :             : 
     307                 :          12 :     enabled_.store(!disable_logging);
     308         [ +  + ]:          12 :     if (!disable_logging) {
     309                 :          10 :       current_level_.store(parsed_level);
     310         [ +  - ]:          10 :       if (spd_logger_) {
     311                 :          10 :         spd_logger_->set_level(to_spdlog_level(parsed_level));
     312                 :             :       }
     313                 :             :     }
     314                 :          12 :     clear_error();
     315                 :             :   }
     316                 :             : 
     317                 :         918 :   static std::string timestamp_now() {
     318                 :             :     using namespace std::chrono;
     319                 :         918 :     const auto now = system_clock::now();
     320                 :         918 :     const auto tt = system_clock::to_time_t(now);
     321                 :         918 :     std::tm tm{};
     322                 :             : #if defined(_WIN32)
     323                 :             :     localtime_s(&tm, &tt);
     324                 :             : #else
     325                 :         918 :     localtime_r(&tt, &tm);
     326                 :             : #endif
     327                 :         918 :     const auto ms = duration_cast<milliseconds>(now.time_since_epoch()) % 1000;
     328                 :             : 
     329                 :         918 :     std::ostringstream oss;
     330                 :         918 :     oss << std::put_time(&tm, "%F %T") << '.' << std::setw(3) << std::setfill('0') << ms.count();
     331                 :        1836 :     return oss.str();
     332                 :         918 :   }
     333                 :             : 
     334                 :         918 :   static std::string format_message(const std::string& format, LogLevel level, std::string_view component,
     335                 :             :                                     std::string_view operation, std::string_view message,
     336                 :             :                                     const std::source_location& loc) {
     337                 :         918 :     const auto file = std::string(loc.file_name());
     338                 :         918 :     const auto line = std::to_string(loc.line());
     339                 :         918 :     const auto function = std::string(loc.function_name());
     340                 :         918 :     const auto source = fmt::format("{}:{}:{}", file, line, function);
     341                 :             : 
     342                 :         918 :     std::string formatted = format;
     343                 :        2754 :     replace_all(formatted, "{timestamp}", timestamp_now());
     344                 :        2754 :     replace_all(formatted, "{level}", level_name(level));
     345                 :        3672 :     replace_all(formatted, "{component}", std::string(component));
     346                 :        3672 :     replace_all(formatted, "{operation}", std::string(operation));
     347                 :        1836 :     replace_all(formatted, "{source}", source);
     348                 :        1836 :     replace_all(formatted, "{file}", file);
     349                 :        1836 :     replace_all(formatted, "{line}", line);
     350                 :        1836 :     replace_all(formatted, "{function}", function);
     351                 :        2754 :     replace_all(formatted, "{message}", std::string(message));
     352                 :        1836 :     return formatted;
     353                 :         918 :   }
     354                 :             : 
     355                 :             :   void flush() {
     356                 :             :     auto logger = spd_logger_;
     357                 :             :     auto sink = dist_sink_;
     358                 :             :     if (logger) logger->flush();
     359                 :             :     if (sink) sink->flush();
     360                 :             :   }
     361                 :             : 
     362                 :           7 :   void setup_async_logging(const AsyncLogConfig& config) {
     363                 :           7 :     async_config_ = config;
     364                 :             : 
     365                 :           7 :     spdlog::drop("wirestead");  // Drop existing logger
     366                 :             : 
     367         [ +  - ]:           7 :     auto overflow_policy = config.enable_backpressure ? spdlog::async_overflow_policy::block
     368                 :             :                                                       : spdlog::async_overflow_policy::overrun_oldest;
     369                 :             : 
     370                 :           7 :     async_thread_pool_ = std::make_shared<spdlog::details::thread_pool>(std::max<size_t>(1, config.max_queue_size), 1);
     371                 :             : 
     372                 :           7 :     spd_logger_ = std::make_shared<spdlog::async_logger>("wirestead", dist_sink_, async_thread_pool_, overflow_policy);
     373                 :           7 :     spd_logger_->set_level(to_spdlog_level(current_level_.load()));
     374                 :           7 :     apply_spdlog_pattern();
     375                 :             : 
     376                 :           7 :     spdlog::register_logger(spd_logger_);
     377                 :             : 
     378         [ +  + ]:           7 :     if (config.flush_interval.count() > 0) {
     379                 :             :       // spdlog::flush_every takes std::chrono::seconds.
     380                 :             :       // Ensure at least 1 second if a positive interval is requested.
     381                 :             :       auto secs =
     382                 :           5 :           std::max(std::chrono::seconds(1), std::chrono::duration_cast<std::chrono::seconds>(config.flush_interval));
     383                 :           5 :       spdlog::flush_every(secs);
     384                 :             :     }
     385                 :             : 
     386                 :           7 :     async_enabled_.store(true);
     387                 :           7 :   }
     388                 :             : 
     389                 :          13 :   void teardown_async_logging() {
     390         [ +  + ]:          13 :     if (!async_enabled_.load()) return;
     391                 :             : 
     392                 :           7 :     async_enabled_.store(false);
     393                 :             : 
     394                 :             :     // If there's a previous drop still pending, wait for it now to avoid multiple drop tasks
     395         [ +  + ]:           7 :     if (drop_future_.valid()) {
     396                 :           1 :       drop_future_.wait();
     397                 :             :     }
     398                 :             : 
     399                 :             :     // Use std::async to drop the logger with a timeout
     400                 :          28 :     drop_future_ = std::async(std::launch::async, []() { spdlog::drop("wirestead"); });
     401                 :             : 
     402                 :           7 :     bool drop_success = true;
     403         [ -  + ]:           7 :     if (drop_future_.wait_for(async_config_.shutdown_timeout) == std::future_status::timeout) {
     404                 :           0 :       drop_success = false;
     405                 :             :     }
     406                 :             : 
     407                 :           7 :     spd_logger_.reset();
     408         [ +  - ]:           7 :     if (drop_success) {
     409                 :           7 :       async_thread_pool_.reset();
     410                 :             :     }
     411                 :           7 :     dist_sink_->flush();
     412                 :             : 
     413                 :             :     // Create sync logger
     414                 :           7 :     spd_logger_ = std::make_shared<spdlog::logger>("wirestead", dist_sink_);
     415                 :           7 :     spd_logger_->set_level(to_spdlog_level(current_level_.load()));
     416                 :           7 :     apply_spdlog_pattern();
     417                 :             : 
     418                 :             :     // Only register globally if the previous drop finished, to avoid race conditions.
     419                 :             :     // If it didn't finish, we still have spd_logger_ internally so log() works.
     420         [ +  - ]:           7 :     if (drop_success) {
     421                 :           7 :       spdlog::register_logger(spd_logger_);
     422                 :             :     }
     423                 :             :   }
     424                 :             : 
     425                 :        1369 :   static spdlog::level::level_enum to_spdlog_level(LogLevel level) {
     426   [ +  +  +  +  :        1369 :     switch (level) {
                   +  - ]
     427                 :         541 :       case LogLevel::DEBUG:
     428                 :         541 :         return spdlog::level::debug;
     429                 :         612 :       case LogLevel::INFO:
     430                 :         612 :         return spdlog::level::info;
     431                 :          70 :       case LogLevel::WARNING:
     432                 :          70 :         return spdlog::level::warn;
     433                 :         141 :       case LogLevel::ERROR:
     434                 :         141 :         return spdlog::level::err;
     435                 :           5 :       case LogLevel::CRITICAL:
     436                 :           5 :         return spdlog::level::critical;
     437                 :           0 :       default:
     438                 :           0 :         return spdlog::level::info;
     439                 :             :     }
     440                 :             :   }
     441                 :             : };
     442                 :             : 
     443                 :         341 : Logger::Logger() : impl_(std::make_unique<Impl>()) {}
     444                 :             : 
     445                 :           0 : Logger::~Logger() = default;
     446                 :             : 
     447                 :           0 : Logger::Logger(Logger&&) noexcept = default;
     448                 :           0 : Logger& Logger::operator=(Logger&&) noexcept = default;
     449                 :             : 
     450                 :        3453 : Logger& Logger::instance() {
     451                 :        3453 :   static Logger* inst = new Logger();
     452                 :        3453 :   return *inst;
     453                 :             : }
     454                 :             : 
     455                 :           1 : Logger& Logger::default_logger() { return instance(); }
     456                 :             : 
     457                 :          86 : void Logger::set_level(LogLevel level) {
     458                 :          86 :   std::lock_guard<std::mutex> lock(impl_->mutex_);
     459                 :          86 :   impl_->current_level_.store(level);
     460         [ +  - ]:          86 :   if (impl_->spd_logger_) {
     461                 :          86 :     impl_->spd_logger_->set_level(Impl::to_spdlog_level(level));
     462                 :             :   }
     463                 :          86 : }
     464                 :             : 
     465                 :          11 : LogLevel Logger::level() const { return get_impl()->current_level_.load(); }
     466                 :             : 
     467                 :        2884 : bool Logger::should_log(LogLevel level) const {
     468                 :        2884 :   const auto* impl = get_impl();
     469   [ +  +  +  +  :        2884 :   return impl->enabled_.load() && impl->has_outputs_unlocked() && level >= impl->current_level_.load();
                   +  + ]
     470                 :             : }
     471                 :             : 
     472                 :          72 : void Logger::set_console_output(bool enable) {
     473                 :          72 :   std::lock_guard<std::mutex> lock(impl_->mutex_);
     474         [ +  + ]:          72 :   if (enable) {
     475                 :          26 :     impl_->add_console_sinks();
     476                 :          26 :     impl_->outputs_.fetch_or(static_cast<int>(LogOutput::CONSOLE));
     477                 :             :   } else {
     478                 :          46 :     impl_->remove_console_sinks();
     479                 :          46 :     impl_->outputs_.fetch_and(~static_cast<int>(LogOutput::CONSOLE));
     480                 :             :   }
     481                 :          72 :   impl_->clear_error();
     482                 :          72 : }
     483                 :             : 
     484                 :          51 : void Logger::set_file_output(const std::string& filename) { (void)try_set_file_output_with_rotation(filename); }
     485                 :             : 
     486                 :           1 : bool Logger::try_set_file_output(const std::string& filename) { return try_set_file_output_with_rotation(filename); }
     487                 :             : 
     488                 :           5 : void Logger::set_file_output_with_rotation(const std::string& filename, const LogRotationConfig& config) {
     489                 :           5 :   (void)try_set_file_output_with_rotation(filename, config);
     490                 :           5 : }
     491                 :             : 
     492                 :          59 : bool Logger::try_set_file_output_with_rotation(const std::string& filename, const LogRotationConfig& config) {
     493                 :          59 :   std::lock_guard<std::mutex> lock(impl_->mutex_);
     494                 :             : 
     495         [ +  + ]:          59 :   if (filename.empty()) {
     496         [ +  + ]:          46 :     if (impl_->file_sink_) {
     497                 :          10 :       impl_->dist_sink_->remove_sink(impl_->file_sink_);
     498                 :          10 :       impl_->file_sink_.reset();
     499                 :             :     }
     500                 :          46 :     impl_->current_log_file_.clear();
     501                 :          46 :     impl_->outputs_.fetch_and(~static_cast<int>(LogOutput::FILE));
     502                 :          46 :     impl_->clear_error();
     503                 :          46 :     return true;
     504                 :             :   } else {
     505                 :             :     try {
     506         [ +  + ]:          13 :       if (config.enable_compression) {
     507                 :           2 :         impl_->set_error("Log compression is not supported by the current rotating file sink");
     508                 :           1 :         return false;
     509                 :             :       }
     510   [ +  -  +  + ]:          12 :       if (config.file_pattern != LogRotationConfig{}.file_pattern) {
     511                 :           2 :         impl_->set_error("Custom log rotation file_pattern is not supported by the current rotating file sink");
     512                 :           1 :         return false;
     513                 :             :       }
     514         [ -  + ]:          11 :       if (impl_->file_sink_) {
     515                 :           0 :         impl_->dist_sink_->remove_sink(impl_->file_sink_);
     516                 :             :       }
     517                 :             : 
     518                 :          11 :       impl_->rotation_config_ = config;
     519                 :          11 :       impl_->current_log_file_ = filename;
     520                 :             : 
     521                 :          21 :       impl_->file_sink_ = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(filename, config.max_file_size_bytes,
     522                 :          21 :                                                                                  config.max_files);
     523                 :             : 
     524                 :          10 :       impl_->dist_sink_->add_sink(impl_->file_sink_);
     525                 :          10 :       impl_->apply_spdlog_pattern();
     526                 :          10 :       impl_->outputs_.fetch_or(static_cast<int>(LogOutput::FILE));
     527                 :          10 :       impl_->clear_error();
     528                 :          10 :       return true;
     529                 :           1 :     } catch (const spdlog::spdlog_ex& e) {
     530                 :           1 :       impl_->set_error("Failed to open log file: " + filename + " (" + e.what() + ")");
     531                 :           1 :       std::cerr << impl_->last_error_ << std::endl;
     532                 :           1 :       impl_->file_sink_.reset();
     533                 :           1 :       impl_->current_log_file_.clear();
     534                 :           1 :       impl_->outputs_.fetch_and(~static_cast<int>(LogOutput::FILE));
     535                 :           1 :       return false;
     536                 :           1 :     }
     537                 :             :   }
     538                 :          59 : }
     539                 :             : 
     540                 :          19 : void Logger::set_async_logging(bool enable, const AsyncLogConfig& config) {
     541                 :          19 :   std::lock_guard<std::mutex> lock(impl_->mutex_);
     542         [ +  + ]:          19 :   if (enable) {
     543         [ +  + ]:           7 :     if (impl_->async_enabled_.load()) {
     544                 :           1 :       impl_->teardown_async_logging();
     545                 :             :     }
     546                 :           7 :     impl_->setup_async_logging(config);
     547                 :             :   } else {
     548                 :          12 :     impl_->teardown_async_logging();
     549                 :             :   }
     550                 :          19 : }
     551                 :             : 
     552                 :           9 : bool Logger::async_logging_enabled() const { return get_impl()->async_enabled_.load(); }
     553                 :             : 
     554                 :          12 : void Logger::reload_from_environment() {
     555                 :          12 :   std::lock_guard<std::mutex> lock(impl_->mutex_);
     556                 :          12 :   impl_->apply_environment_settings();
     557                 :          12 : }
     558                 :             : 
     559                 :          50 : void Logger::set_callback(LogCallback callback) {
     560                 :          50 :   std::lock_guard<std::mutex> lock(impl_->mutex_);
     561                 :             : 
     562         [ +  + ]:          50 :   if (!callback) {
     563         [ +  + ]:          41 :     if (impl_->callback_sink_) {
     564                 :           8 :       impl_->dist_sink_->remove_sink(impl_->callback_sink_);
     565                 :           8 :       impl_->callback_sink_.reset();
     566                 :             :     }
     567                 :          41 :     impl_->outputs_.fetch_and(~static_cast<int>(LogOutput::CALLBACK));
     568                 :          41 :     impl_->clear_error();
     569                 :          41 :     return;
     570                 :             :   }
     571                 :             : 
     572         [ +  + ]:           9 :   if (impl_->callback_sink_) {
     573                 :           1 :     impl_->callback_sink_->set_callback(std::move(callback));
     574                 :           1 :     impl_->dist_sink_->remove_sink(impl_->callback_sink_);  // Avoid duplicates
     575                 :           1 :     impl_->dist_sink_->add_sink(impl_->callback_sink_);
     576                 :             :   } else {
     577                 :           8 :     impl_->callback_sink_ = std::make_shared<callback_sink_mt>(std::move(callback));
     578                 :           8 :     impl_->dist_sink_->add_sink(impl_->callback_sink_);
     579                 :             :   }
     580                 :           9 :   impl_->apply_spdlog_pattern();
     581                 :           9 :   impl_->outputs_.fetch_or(static_cast<int>(LogOutput::CALLBACK));
     582                 :           9 :   impl_->clear_error();
     583                 :          50 : }
     584                 :             : 
     585                 :           4 : void Logger::set_outputs(int outputs) {
     586                 :           4 :   std::lock_guard<std::mutex> lock(impl_->mutex_);
     587                 :           4 :   int effective_outputs = 0;
     588                 :             : 
     589                 :             :   // Reconcile sinks in dist_sink_ based on the new bitmask
     590                 :             : 
     591                 :             :   // Console Sink
     592         [ -  + ]:           4 :   if (outputs & static_cast<int>(LogOutput::CONSOLE)) {
     593                 :           0 :     impl_->add_console_sinks();
     594                 :           0 :     effective_outputs |= static_cast<int>(LogOutput::CONSOLE);
     595                 :             :   } else {
     596                 :           4 :     impl_->remove_console_sinks();
     597                 :             :   }
     598                 :             : 
     599                 :             :   // File Sink
     600         [ +  + ]:           4 :   if (outputs & static_cast<int>(LogOutput::FILE)) {
     601   [ +  -  +  -  :           1 :     if (!impl_->file_sink_ && !impl_->current_log_file_.empty()) {
                   +  - ]
     602                 :           2 :       impl_->file_sink_ = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
     603                 :           2 :           impl_->current_log_file_, impl_->rotation_config_.max_file_size_bytes, impl_->rotation_config_.max_files);
     604                 :             :     }
     605         [ +  - ]:           1 :     if (impl_->file_sink_) {
     606                 :           1 :       impl_->dist_sink_->remove_sink(impl_->file_sink_);  // Ensure no duplicate
     607                 :           1 :       impl_->dist_sink_->add_sink(impl_->file_sink_);
     608                 :           1 :       impl_->apply_spdlog_pattern();
     609                 :           1 :       effective_outputs |= static_cast<int>(LogOutput::FILE);
     610                 :             :     }
     611                 :             :   } else {
     612         [ +  + ]:           3 :     if (impl_->file_sink_) {
     613                 :           1 :       impl_->dist_sink_->remove_sink(impl_->file_sink_);
     614                 :           1 :       impl_->file_sink_.reset();
     615                 :             :     }
     616                 :             :   }
     617                 :             : 
     618                 :             :   // Callback Sink
     619         [ +  + ]:           4 :   if (outputs & static_cast<int>(LogOutput::CALLBACK)) {
     620         [ +  - ]:           1 :     if (impl_->callback_sink_) {
     621                 :           1 :       impl_->dist_sink_->remove_sink(impl_->callback_sink_);  // Ensure no duplicate
     622                 :           1 :       impl_->dist_sink_->add_sink(impl_->callback_sink_);
     623                 :           1 :       impl_->apply_spdlog_pattern();
     624                 :           1 :       effective_outputs |= static_cast<int>(LogOutput::CALLBACK);
     625                 :             :     }
     626                 :             :   } else {
     627         [ +  + ]:           3 :     if (impl_->callback_sink_) {
     628                 :           2 :       impl_->dist_sink_->remove_sink(impl_->callback_sink_);
     629                 :             :     }
     630                 :             :   }
     631                 :             : 
     632                 :           4 :   impl_->outputs_.store(effective_outputs);
     633                 :           4 :   impl_->clear_error();
     634                 :           4 : }
     635                 :             : 
     636                 :          47 : void Logger::set_enabled(bool enabled) { impl_->enabled_.store(enabled); }
     637                 :             : 
     638                 :          11 : bool Logger::enabled() const { return get_impl()->enabled_.load(); }
     639                 :             : 
     640                 :           2 : bool Logger::has_outputs() const { return get_impl()->has_outputs_unlocked(); }
     641                 :             : 
     642                 :          13 : std::string Logger::last_error() const {
     643                 :          13 :   std::lock_guard<std::mutex> lock(impl_->mutex_);
     644                 :          26 :   return impl_->last_error_;
     645                 :          13 : }
     646                 :             : 
     647                 :          37 : void Logger::set_format(const std::string& format) {
     648                 :          37 :   std::lock_guard<std::mutex> lock(impl_->mutex_);
     649                 :          37 :   impl_->set_format(format);
     650                 :          37 :   impl_->clear_error();
     651                 :          37 : }
     652                 :             : 
     653                 :          26 : void Logger::flush() {
     654                 :          26 :   std::shared_ptr<spdlog::logger> logger;
     655                 :          26 :   std::shared_ptr<spdlog::sinks::dist_sink_mt> sink;
     656                 :             :   {
     657                 :          26 :     std::lock_guard<std::mutex> lock(impl_->mutex_);
     658                 :          26 :     logger = impl_->spd_logger_;
     659                 :          26 :     sink = impl_->dist_sink_;
     660                 :          26 :   }
     661   [ +  -  +  - ]:          26 :   if (logger) logger->flush();
     662   [ +  -  +  - ]:          26 :   if (sink) sink->flush();
     663                 :          26 : }
     664                 :             : 
     665                 :         918 : void Logger::log(LogLevel level, std::string_view component, std::string_view operation, std::string_view message,
     666                 :             :                  const std::source_location& loc) {
     667         [ -  + ]:         918 :   if (!should_log(level)) {
     668                 :           0 :     return;
     669                 :             :   }
     670                 :             : 
     671                 :         918 :   std::shared_ptr<spdlog::logger> logger;
     672                 :         918 :   std::string format;
     673                 :             :   {
     674                 :         918 :     std::lock_guard<std::mutex> lock(impl_->mutex_);
     675                 :         918 :     logger = impl_->spd_logger_;
     676                 :         918 :     format = impl_->format_;
     677                 :         918 :   }
     678                 :             : 
     679         [ -  + ]:         918 :   if (!logger) {
     680                 :           0 :     return;
     681                 :             :   }
     682                 :             : 
     683                 :         918 :   const auto payload = Impl::format_message(format, level, component, operation, message, loc);
     684                 :         918 :   logger->log(Impl::to_spdlog_level(level), payload);
     685                 :         918 : }
     686                 :             : 
     687                 :           1 : void Logger::debug(std::string_view component, std::string_view operation, std::string_view message,
     688                 :             :                    const std::source_location& loc) {
     689                 :           1 :   log(LogLevel::DEBUG, component, operation, message, loc);
     690                 :           1 : }
     691                 :             : 
     692                 :           1 : void Logger::info(std::string_view component, std::string_view operation, std::string_view message,
     693                 :             :                   const std::source_location& loc) {
     694                 :           1 :   log(LogLevel::INFO, component, operation, message, loc);
     695                 :           1 : }
     696                 :             : 
     697                 :           1 : void Logger::warning(std::string_view component, std::string_view operation, std::string_view message,
     698                 :             :                      const std::source_location& loc) {
     699                 :           1 :   log(LogLevel::WARNING, component, operation, message, loc);
     700                 :           1 : }
     701                 :             : 
     702                 :           1 : void Logger::error(std::string_view component, std::string_view operation, std::string_view message,
     703                 :             :                    const std::source_location& loc) {
     704                 :           1 :   log(LogLevel::ERROR, component, operation, message, loc);
     705                 :           1 : }
     706                 :             : 
     707                 :           1 : void Logger::critical(std::string_view component, std::string_view operation, std::string_view message,
     708                 :             :                       const std::source_location& loc) {
     709                 :           1 :   log(LogLevel::CRITICAL, component, operation, message, loc);
     710                 :           1 : }
     711                 :             : 
     712                 :             : }  // namespace diagnostics
     713                 :             : }  // namespace wirestead
        

Generated by: LCOV version 2.0-1