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 : : #pragma once
18 : :
19 : : #include <atomic>
20 : : #include <chrono>
21 : : #include <functional>
22 : : #include <memory>
23 : : #include <source_location>
24 : : #include <string>
25 : : #include <string_view>
26 : : #include <vector>
27 : :
28 : : #include "wirestead/base/visibility.hpp"
29 : :
30 : : #ifdef DEBUG
31 : : #undef DEBUG
32 : : #endif
33 : : #ifdef INFO
34 : : #undef INFO
35 : : #endif
36 : : #ifdef WARNING
37 : : #undef WARNING
38 : : #endif
39 : : #ifdef ERROR
40 : : #undef ERROR
41 : : #endif
42 : : #ifdef CRITICAL
43 : : #undef CRITICAL
44 : : #endif
45 : : #ifdef CALLBACK
46 : : #undef CALLBACK
47 : : #endif
48 : :
49 : : namespace wirestead {
50 : : namespace diagnostics {
51 : :
52 : : /**
53 : : * @brief Log severity levels
54 : : */
55 : : enum class LogLevel { DEBUG = 0, INFO = 1, WARNING = 2, ERROR = 3, CRITICAL = 4 };
56 : :
57 : : /**
58 : : * @brief Log output destinations
59 : : */
60 : : enum class LogOutput { CONSOLE = 0x01, FILE = 0x02, CALLBACK = 0x04 };
61 : :
62 : : /**
63 : : * @brief Log rotation configuration
64 : : */
65 : : struct LogRotationConfig {
66 : : size_t max_file_size_bytes = 10 * 1024 * 1024; // 10MB default
67 : : size_t max_files = 10; // Keep 10 files max
68 : : bool enable_compression = false; // Reserved for future use
69 : : std::string file_pattern = "{name}.{index}.log"; // Reserved for future use
70 : :
71 : 1239 : LogRotationConfig() = default;
72 : :
73 : : LogRotationConfig(size_t max_size, size_t max_count) : max_file_size_bytes(max_size), max_files(max_count) {}
74 : : };
75 : :
76 : : /**
77 : : * @brief Async logging configuration
78 : : */
79 : : struct AsyncLogConfig {
80 : : size_t max_queue_size = 10000; // Maximum queue size
81 : : size_t batch_size = 100; // Reserved; spdlog manages batching internally
82 : 350 : std::chrono::milliseconds flush_interval{100}; // Flush interval
83 : 350 : std::chrono::milliseconds shutdown_timeout{5000}; // Shutdown timeout
84 : : bool enable_backpressure = true; // Enable backpressure handling (maps to spdlog block policy)
85 : : bool enable_batch_processing = true; // Reserved; spdlog manages batching internally
86 : :
87 : 350 : AsyncLogConfig() = default;
88 : :
89 : : AsyncLogConfig(size_t max_q, size_t batch, std::chrono::milliseconds interval)
90 : : : max_queue_size(max_q), batch_size(batch), flush_interval(interval) {}
91 : : };
92 : :
93 : : /**
94 : : * @brief Centralized logging system with async support
95 : : *
96 : : * Provides thread-safe, configurable logging with multiple output destinations,
97 : : * async processing, batch operations, and performance optimizations for production use.
98 : : */
99 : : class WIRESTEAD_API Logger {
100 : : public:
101 : : using LogCallback = std::function<void(LogLevel level, const std::string& formatted_message)>;
102 : :
103 : : /**
104 : : * @brief Get singleton instance
105 : : */
106 : : static Logger& instance();
107 : : [[deprecated("Use Logger::instance() instead")]]
108 : : static Logger& default_logger();
109 : :
110 : : Logger();
111 : : ~Logger();
112 : :
113 : : // Move semantics
114 : : Logger(Logger&&) noexcept;
115 : : Logger& operator=(Logger&&) noexcept;
116 : :
117 : : // Non-copyable
118 : : Logger(const Logger&) = delete;
119 : : Logger& operator=(const Logger&) = delete;
120 : :
121 : : /**
122 : : * @brief Set minimum log level
123 : : * @param level Messages below this level will be ignored
124 : : */
125 : : void set_level(LogLevel level);
126 : :
127 : : /**
128 : : * @brief Get current log level
129 : : */
130 : : LogLevel level() const;
131 : :
132 : : /**
133 : : * @brief Check whether a message at the given level would be logged.
134 : : */
135 : : bool should_log(LogLevel level) const;
136 : :
137 : : /**
138 : : * @brief Enable/disable console output
139 : : * @param enable True to enable console output
140 : : */
141 : : void set_console_output(bool enable);
142 : :
143 : : /**
144 : : * @brief Set file output
145 : : * @param filename Log file path (empty string to disable file output)
146 : : */
147 : : void set_file_output(const std::string& filename);
148 : :
149 : : /**
150 : : * @brief Set file output and report whether it succeeded.
151 : : * @param filename Log file path (empty string to disable file output)
152 : : * @return True when the requested output state was applied
153 : : */
154 : : bool try_set_file_output(const std::string& filename);
155 : :
156 : : /**
157 : : * @brief Set file output with rotation
158 : : * @param filename Log file path
159 : : * @param config Rotation configuration
160 : : */
161 : : void set_file_output_with_rotation(const std::string& filename,
162 : : const LogRotationConfig& config = LogRotationConfig{});
163 : :
164 : : /**
165 : : * @brief Set file output with rotation and report whether it succeeded.
166 : : * @param filename Log file path
167 : : * @param config Rotation configuration
168 : : * @return True when the requested output state was applied
169 : : */
170 : : bool try_set_file_output_with_rotation(const std::string& filename,
171 : : const LogRotationConfig& config = LogRotationConfig{});
172 : :
173 : : /**
174 : : * @brief Enable/disable async logging
175 : : * @param enable True to enable async logging
176 : : * @param config Async logging configuration
177 : : */
178 : : void set_async_logging(bool enable, const AsyncLogConfig& config = AsyncLogConfig{});
179 : :
180 : : /**
181 : : * @brief Check if async logging is enabled
182 : : */
183 : : bool async_logging_enabled() const;
184 : :
185 : : /**
186 : : * @brief Re-apply supported logger settings from environment variables.
187 : : *
188 : : * Currently supports WIRESTEAD_LOG_LEVEL values DEBUG, INFO, WARNING, ERROR, CRITICAL, and OFF.
189 : : */
190 : : void reload_from_environment();
191 : :
192 : : /**
193 : : * @brief Set log callback
194 : : * @param callback Function to call for each log message
195 : : */
196 : : void set_callback(LogCallback callback);
197 : :
198 : : /**
199 : : * @brief Set output destinations
200 : : * @param outputs Bitwise OR of LogOutput flags
201 : : */
202 : : void set_outputs(int outputs);
203 : :
204 : : /**
205 : : * @brief Enable/disable logging
206 : : * @param enabled True to enable logging
207 : : */
208 : : void set_enabled(bool enabled);
209 : :
210 : : /**
211 : : * @brief Check if logging is enabled
212 : : */
213 : : bool enabled() const;
214 : :
215 : : /**
216 : : * @brief Return true when at least one output destination is active.
217 : : */
218 : : bool has_outputs() const;
219 : :
220 : : /**
221 : : * @brief Last logger configuration error, empty when the last operation succeeded.
222 : : */
223 : : std::string last_error() const;
224 : :
225 : : /**
226 : : * @brief Set log format
227 : : * @param format Format string with placeholders: {timestamp}, {level}, {component}, {operation}, {source}, {file},
228 : : * {line}, {function}, {message}
229 : : */
230 : : void set_format(const std::string& format);
231 : :
232 : : /**
233 : : * @brief Flush all outputs
234 : : */
235 : : void flush();
236 : :
237 : : // Main logging functions
238 : : void log(LogLevel level, std::string_view component, std::string_view operation, std::string_view message,
239 : 1826 : const std::source_location& loc = std::source_location::current());
240 : :
241 : : void debug(std::string_view component, std::string_view operation, std::string_view message,
242 : 1 : const std::source_location& loc = std::source_location::current());
243 : : void info(std::string_view component, std::string_view operation, std::string_view message,
244 : 1 : const std::source_location& loc = std::source_location::current());
245 : : void warning(std::string_view component, std::string_view operation, std::string_view message,
246 : 1 : const std::source_location& loc = std::source_location::current());
247 : : void error(std::string_view component, std::string_view operation, std::string_view message,
248 : 1 : const std::source_location& loc = std::source_location::current());
249 : : void critical(std::string_view component, std::string_view operation, std::string_view message,
250 : 2 : const std::source_location& loc = std::source_location::current());
251 : :
252 : : private:
253 : : struct Impl;
254 : 2917 : const Impl* get_impl() const { return impl_.get(); }
255 : : Impl* get_impl() { return impl_.get(); }
256 : : std::unique_ptr<Impl> impl_;
257 : : };
258 : :
259 : : /**
260 : : * @brief Convenience macros for logging
261 : : */
262 : : #define WIRESTEAD_LOG(level, component, operation, message) \
263 : : do { \
264 : : const auto wirestead_log_level = (level); \
265 : : if (wirestead::diagnostics::Logger::instance().should_log(wirestead_log_level)) { \
266 : : wirestead::diagnostics::Logger::instance().log(wirestead_log_level, component, operation, message); \
267 : : } \
268 : : } while (0)
269 : :
270 : : #define WIRESTEAD_LOG_DEBUG(component, operation, message) \
271 : : WIRESTEAD_LOG(wirestead::diagnostics::LogLevel::DEBUG, component, operation, message)
272 : :
273 : : #define WIRESTEAD_LOG_INFO(component, operation, message) \
274 : : WIRESTEAD_LOG(wirestead::diagnostics::LogLevel::INFO, component, operation, message)
275 : :
276 : : #define WIRESTEAD_LOG_WARNING(component, operation, message) \
277 : : WIRESTEAD_LOG(wirestead::diagnostics::LogLevel::WARNING, component, operation, message)
278 : :
279 : : #define WIRESTEAD_LOG_ERROR(component, operation, message) \
280 : : WIRESTEAD_LOG(wirestead::diagnostics::LogLevel::ERROR, component, operation, message)
281 : :
282 : : #define WIRESTEAD_LOG_CRITICAL(component, operation, message) \
283 : : WIRESTEAD_LOG(wirestead::diagnostics::LogLevel::CRITICAL, component, operation, message)
284 : :
285 : : /**
286 : : * @brief Performance logging macros for expensive operations.
287 : : *
288 : : * Usage: WIRESTEAD_LOG_PERF_START and WIRESTEAD_LOG_PERF_END must use the same
289 : : * `component` and `operation` tokens, and each `operation` token must be
290 : : * unique within its enclosing scope (the token becomes part of a variable name).
291 : : */
292 : : #define WIRESTEAD_LOG_PERF_START(component, operation) \
293 : : auto _perf_start_##operation = \
294 : : (wirestead::diagnostics::Logger::instance().should_log(wirestead::diagnostics::LogLevel::DEBUG)) \
295 : : ? std::chrono::high_resolution_clock::now() \
296 : : : std::chrono::high_resolution_clock::time_point()
297 : :
298 : : #define WIRESTEAD_LOG_PERF_END(component, operation) \
299 : : do { \
300 : : if (wirestead::diagnostics::Logger::instance().should_log(wirestead::diagnostics::LogLevel::DEBUG)) { \
301 : : auto _perf_end_##operation = std::chrono::high_resolution_clock::now(); \
302 : : using _us_t = std::chrono::microseconds; \
303 : : auto _diff_##operation = _perf_end_##operation - _perf_start_##operation; \
304 : : auto _perf_duration_##operation = std::chrono::duration_cast<_us_t>(_diff_##operation).count(); \
305 : : WIRESTEAD_LOG(wirestead::diagnostics::LogLevel::DEBUG, component, operation, \
306 : : "Duration: " + std::to_string(_perf_duration_##operation) + " μs"); \
307 : : } \
308 : : } while (0)
309 : :
310 : : #define UNILINK_LOG(level, component, operation, message) WIRESTEAD_LOG(level, component, operation, message)
311 : :
312 : : #define UNILINK_LOG_DEBUG(component, operation, message) WIRESTEAD_LOG_DEBUG(component, operation, message)
313 : :
314 : : #define UNILINK_LOG_INFO(component, operation, message) WIRESTEAD_LOG_INFO(component, operation, message)
315 : :
316 : : #define UNILINK_LOG_WARNING(component, operation, message) WIRESTEAD_LOG_WARNING(component, operation, message)
317 : :
318 : : #define UNILINK_LOG_ERROR(component, operation, message) WIRESTEAD_LOG_ERROR(component, operation, message)
319 : :
320 : : #define UNILINK_LOG_CRITICAL(component, operation, message) WIRESTEAD_LOG_CRITICAL(component, operation, message)
321 : :
322 : : #define UNILINK_LOG_PERF_START(component, operation) WIRESTEAD_LOG_PERF_START(component, operation)
323 : :
324 : : #define UNILINK_LOG_PERF_END(component, operation) WIRESTEAD_LOG_PERF_END(component, operation)
325 : :
326 : : } // namespace diagnostics
327 : : } // namespace wirestead
|