Line data Source code
1 : // 2 : // Copyright 2024 OpenModelViewer Authors 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 : #pragma once 18 : 19 : #include "openmodelviewer/core/lifecycle/irunnable.hpp" 20 : 21 : #include "openmodelviewer/core/log/log_level.hpp" 22 : #include "openmodelviewer/core/log/log_entry.hpp" 23 : #include "openmodelviewer/core/log/ilog_sink.hpp" 24 : 25 : #include <initializer_list> 26 : #include <string> 27 : #include <fmt/format.h> 28 : #include <utility> 29 : 30 : #include <thread> 31 : #include <atomic> 32 : #include <condition_variable> 33 : #include <mutex> 34 : 35 : #include <vector> 36 : #include <deque> 37 : #include <memory> 38 : 39 : namespace openmodelviewer::core::log 40 : { 41 : /** 42 : * @brief Asynchronous, thread-safe logger. 43 : * Buffers log entries and dispatches them to attached sinks in a background thread. 44 : * Supports configurable minimum log level and synchronous or asynchronous message submission. 45 : */ 46 : class Logger : public lifecycle::IRunnable 47 : { 48 : public: 49 : /** 50 : * @brief Constructs a logger instance with a given minimum log level. 51 : * @param level Minimum log level to accept (inclusive). 52 : */ 53 : Logger(LogLevel level = LogLevel::Trace); 54 : 55 : /** 56 : * @brief Constructs a logger instance with an initial list of sinks. 57 : * @param sinks A list of sinks to which log entries will be dispatched. 58 : * @param level Minimum log level to accept (inclusive). 59 : */ 60 : Logger(std::initializer_list<std::shared_ptr<ILogSink>> sinks, LogLevel level = LogLevel::Trace); 61 : 62 : /** 63 : * @brief Destructor. 64 : * Automatically stops the logging thread and flushes pending log entries. 65 : */ 66 : ~Logger(); 67 : 68 : /** 69 : * @brief Starts the background processing thread. Has no effect if already running. 70 : * 71 : * @return True if the thread was successfully started, false if it was already running. 72 : */ 73 : bool start() override; 74 : 75 : /** 76 : * @brief Stops the background thread and flushes all remaining log entries. 77 : * Safe to call multiple times. 78 : */ 79 : void stop() override; 80 : 81 : /** 82 : * @brief Returns whether the logger is currently running. 83 : * 84 : * @return true if the logger has been started and is still active. 85 : */ 86 : bool isRunning() const noexcept override; 87 : 88 : /** 89 : * @brief Immediately flushes all pending log entries in the queue. 90 : * This call is blocking and processes all entries synchronously. 91 : */ 92 : void flush(); 93 : 94 : /** 95 : * @brief Adds a new log sink to the logger. The sink will receive all future log entries above the configured level. 96 : * 97 : * @param sinkPtr Shared pointer to a valid ILogSink implementation. Null pointers are ignored. 98 : */ 99 : void addSink(std::shared_ptr<ILogSink> sinkPtr); 100 : 101 : /** 102 : * @brief Removes a previously added sink. 103 : * All matching pointers will be removed. 104 : * 105 : * @param sink Shared pointer to the sink to remove. 106 : */ 107 : void removeSink(const std::shared_ptr<ILogSink>& sink); 108 : 109 : /** 110 : * @brief Sets the minimum log level. Messages below this level will be ignored. 111 : * 112 : * @param level New minimum log level. 113 : */ 114 : void setMinLevel(LogLevel level) noexcept; 115 : 116 : /** 117 : * @brief Gets the current minimum log level. 118 : * 119 : * @return The configured minimum log level. 120 : */ 121 : LogLevel getMinLevel() const noexcept; 122 : 123 : /** 124 : * @brief Formats and enqueues a log entry for asynchronous processing. 125 : * The entry is only logged if its level is equal to or higher than the minimum. 126 : * 127 : * @tparam Args Format arguments types. 128 : * @param level Severity level of the message. 129 : * @param format Format string compatible with fmt::format. 130 : * @param args Arguments to format into the message. 131 : */ 132 : template <typename... Args> 133 237620 : void log(LogLevel level, fmt::format_string<Args...> format, Args&&... args) 134 : { 135 474359 : logv(level, fmt::format(format, std::forward<Args>(args)...)); 136 237700 : } 137 : 138 : /** 139 : * @brief Formats and enqueues a log entry with Trace level for asynchronous processing. 140 : * The entry is only logged if Trace level is equal to or higher than the minimum. 141 : * 142 : * @tparam Args Format arguments types. 143 : * @param format Format string compatible with fmt::format. 144 : * @param args Arguments to format into the message. 145 : */ 146 : template <typename... Args> 147 : void logTrace(fmt::format_string<Args...> format, Args&&... args) 148 : { 149 : logv(LogLevel::Trace, fmt::format(format, std::forward<Args>(args)...)); 150 : } 151 : 152 : /** 153 : * @brief Formats and enqueues a log entry with Debug level for asynchronous processing. 154 : * The entry is only logged if Debug level is equal to or higher than the minimum. 155 : * 156 : * @tparam Args Format arguments types. 157 : * @param format Format string compatible with fmt::format. 158 : * @param args Arguments to format into the message. 159 : */ 160 : template <typename... Args> 161 : void logDebug(fmt::format_string<Args...> format, Args&&... args) 162 : { 163 : logv(LogLevel::Debug, fmt::format(format, std::forward<Args>(args)...)); 164 : } 165 : 166 : /** 167 : * @brief Formats and enqueues a log entry with Info level for asynchronous processing. 168 : * The entry is only logged if Info level is equal to or higher than the minimum. 169 : * 170 : * @tparam Args Format arguments types. 171 : * @param format Format string compatible with fmt::format. 172 : * @param args Arguments to format into the message. 173 : */ 174 : template <typename... Args> 175 2 : void logInfo(fmt::format_string<Args...> format, Args&&... args) 176 : { 177 4 : logv(LogLevel::Info, fmt::format(format, std::forward<Args>(args)...)); 178 2 : } 179 : 180 : /** 181 : * @brief Formats and enqueues a log entry with Warning level for asynchronous processing. 182 : * The entry is only logged if Warning level is equal to or higher than the minimum. 183 : * 184 : * @tparam Args Format arguments types. 185 : * @param format Format string compatible with fmt::format. 186 : * @param args Arguments to format into the message. 187 : */ 188 : template <typename... Args> 189 1 : void logWarning(fmt::format_string<Args...> format, Args&&... args) 190 : { 191 2 : logv(LogLevel::Warning, fmt::format(format, std::forward<Args>(args)...)); 192 1 : } 193 : 194 : /** 195 : * @brief Formats and enqueues a log entry with Error level for asynchronous processing. 196 : * The entry is only logged if Error level is equal to or higher than the minimum. 197 : * 198 : * @tparam Args Format arguments types. 199 : * @param format Format string compatible with fmt::format. 200 : * @param args Arguments to format into the message. 201 : */ 202 : template <typename... Args> 203 : void logError(fmt::format_string<Args...> format, Args&&... args) 204 : { 205 : logv(LogLevel::Error, fmt::format(format, std::forward<Args>(args)...)); 206 : } 207 : 208 : /** 209 : * @brief Formats and enqueues a log entry with Critical level for asynchronous processing. 210 : * The entry is only logged if Critical level is equal to or higher than the minimum. 211 : * 212 : * @tparam Args Format arguments types. 213 : * @param format Format string compatible with fmt::format. 214 : * @param args Arguments to format into the message. 215 : */ 216 : template <typename... Args> 217 : void logCritical(fmt::format_string<Args...> format, Args&&... args) 218 : { 219 : logv(LogLevel::Critical, fmt::format(format, std::forward<Args>(args)...)); 220 : } 221 : 222 : /** 223 : * @brief Immediately formats and dispatches a log entry synchronously, bypassing the queue. 224 : * 225 : * @tparam Args Format arguments types. 226 : * @param level Severity level of the message. 227 : * @param format Format string compatible with fmt::format. 228 : * @param args Arguments to format into the message. 229 : */ 230 : template<typename... Args> 231 2 : void logImmediate(LogLevel level, fmt::format_string<Args...> format, Args&&... args) 232 : { 233 2 : logvi(level, fmt::format(format, std::forward<Args>(args)...)); 234 2 : } 235 : 236 : private: 237 : LogLevel m_minLogLevel; 238 : 239 : std::thread m_worker; 240 : std::atomic<bool> m_working; 241 : std::condition_variable m_workCondition; 242 : 243 : mutable std::mutex m_sinkMutex; 244 : std::vector<std::shared_ptr<ILogSink>> m_sinks; 245 : 246 : mutable std::mutex m_logMutex; 247 : std::deque<std::unique_ptr<LogEntry>> m_logQueue; 248 : 249 : void process(); 250 : void logv(LogLevel level, std::string message); 251 : void logvi(LogLevel level, std::string message); 252 : void dispatch(const std::vector<std::unique_ptr<LogEntry>>& buffer) const; 253 : void dumpQueue(std::vector<std::unique_ptr<LogEntry>>& buffer); 254 : }; 255 : } // namespace openmodelviewer::core::log