LogIt++
Header-only, macro-first C++ logging library
Loading...
Searching...
No Matches
CrashWindowsLogger.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_CRASHWINDOWSLOGGER_HPP_INCLUDED
3#define LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_CRASHWINDOWSLOGGER_HPP_INCLUDED
4
7
8#include "ILogger.hpp"
9
10#include <array>
11#include <atomic>
12#include <cstddef>
13#include <cstring>
14#include <string>
15
16#if defined(_WIN32)
17# ifndef NOMINMAX
18# define NOMINMAX
19# endif
20# ifndef WIN32_LEAN_AND_MEAN
21# define WIN32_LEAN_AND_MEAN
22# endif
23# include <windows.h>
24# include <process.h>
25# include <cstdlib>
26#endif
27
28namespace logit {
29
30#if defined(_WIN32)
31
35 class CrashWindowsLogger : public ILogger {
36 public:
38 static constexpr std::size_t kMaxBufferSize = 64 * 1024; // 64 KiB
39
41 static constexpr std::size_t kDefaultBufferSize = kMaxBufferSize;
42
45 struct Config {
46 std::string log_path = "crash.log";
47 std::size_t buffer_size = kDefaultBufferSize;
48 };
49
52
55 explicit CrashWindowsLogger(const Config& config)
56 : m_log_path(config.log_path),
57 m_capacity(config.buffer_size > kMaxBufferSize ? kMaxBufferSize : config.buffer_size) {
58 if (m_capacity == 0) {
59 m_capacity = 1;
60 }
61 m_file = ::CreateFileA(
62 m_log_path.c_str(),
63 FILE_APPEND_DATA,
64 FILE_SHARE_READ | FILE_SHARE_WRITE,
65 nullptr,
66 OPEN_ALWAYS,
67 FILE_ATTRIBUTE_NORMAL,
68 nullptr);
69 active_logger().store(this, std::memory_order_release);
70 }
71
73 CrashWindowsLogger& operator=(const CrashWindowsLogger&) = delete;
74
76 ~CrashWindowsLogger() override {
77 CrashWindowsLogger* expected = this;
78 active_logger().compare_exchange_strong(
79 expected, nullptr, std::memory_order_release, std::memory_order_relaxed);
80 if (m_file != INVALID_HANDLE_VALUE) {
81 ::CloseHandle(m_file);
82 }
83 }
84
88 void log(const LogRecord& record, const std::string& message) override {
89 if (!record.raw_mode &&
90 static_cast<int>(record.log_level) < m_log_level.load(std::memory_order_relaxed)) {
91 return;
92 }
93
94 const std::size_t capacity = m_capacity;
95 if (capacity == 0) {
96 return;
97 }
98
99 const char* data = message.data();
100 std::size_t length = message.size();
101
102 std::size_t copy_len = length;
103 if (copy_len >= capacity) {
104 if (capacity > 1) {
105 data += length - (capacity - 1);
106 copy_len = capacity - 1;
107 } else {
108 data += length;
109 copy_len = 0;
110 }
111 }
112
113 const std::size_t total = copy_len + 1; // extra byte for newline marker
114 const std::size_t start = m_next_offset.fetch_add(total, std::memory_order_acq_rel);
115 const std::size_t index = capacity ? (start % capacity) : 0;
116
117 if (copy_len > 0) {
118 std::size_t first = capacity - index;
119 if (first > copy_len) {
120 first = copy_len;
121 }
122 std::memcpy(m_buffer.data() + index, data, first);
123 const std::size_t remaining = copy_len - first;
124 if (remaining > 0) {
125 std::memcpy(m_buffer.data(), data + first, remaining);
126 }
127 }
128
129 const std::size_t newline_index = capacity ? ((index + copy_len) % capacity) : 0;
130 m_buffer[newline_index] = '\n';
131
132 const std::size_t desired = start + total;
133 std::size_t expected = m_committed.load(std::memory_order_relaxed);
134 while (expected < desired &&
135 !m_committed.compare_exchange_weak(
136 expected, desired, std::memory_order_release, std::memory_order_relaxed)) {
137 }
138 }
139
141 std::string get_string_param(const LoggerParam& param) const override {
142 switch (param) {
144 return m_log_path;
145 default:
146 break;
147 }
148 return std::string();
149 }
150
152 int64_t get_int_param(const LoggerParam& param) const override {
153 (void)param;
154 return 0;
155 }
156
158 double get_float_param(const LoggerParam& param) const override {
159 (void)param;
160 return 0.0;
161 }
162
164 void set_log_level(LogLevel level) override {
165 m_log_level.store(static_cast<int>(level), std::memory_order_relaxed);
166 }
167
169 LogLevel get_log_level() const override {
170 return static_cast<LogLevel>(m_log_level.load(std::memory_order_relaxed));
171 }
172
174 void wait() override {}
175
177 static void install_exception_handler() {
178 ::SetUnhandledExceptionFilter(&CrashWindowsLogger::exception_filter);
179 }
180
181 private:
182 static LONG WINAPI exception_filter(EXCEPTION_POINTERS* ex_info) {
183 CrashWindowsLogger* logger = active_logger().load(std::memory_order_acquire);
184 if (logger != nullptr) {
185 DWORD code = 0;
186 if (ex_info != nullptr && ex_info->ExceptionRecord != nullptr) {
187 code = ex_info->ExceptionRecord->ExceptionCode;
188 }
189 logger->write_snapshot(code);
190 }
191 _exit(EXIT_FAILURE);
192 return EXCEPTION_EXECUTE_HANDLER;
193 }
194
195 void write_snapshot(DWORD exception_code) const noexcept {
196 if (m_file == INVALID_HANDLE_VALUE) {
197 return;
198 }
199
200 const std::size_t capacity = m_capacity;
201 const std::size_t committed = m_committed.load(std::memory_order_acquire);
202 std::size_t available = committed;
203 if (available > capacity) {
204 available = capacity;
205 }
206 const std::size_t start_offset = committed - available;
207 const std::size_t start_index = capacity ? (start_offset % capacity) : 0;
208
209 if (available > 0) {
210 std::size_t chunk = capacity - start_index;
211 if (chunk > available) {
212 chunk = available;
213 }
214 safe_write(m_buffer.data() + start_index, chunk);
215 const std::size_t remaining = available - chunk;
216 if (remaining > 0) {
217 safe_write(m_buffer.data(), remaining);
218 }
219 }
220
221 write_marker(exception_code);
222 ::FlushFileBuffers(m_file);
223 }
224
225 void safe_write(const char* data, std::size_t size) const noexcept {
226 if (m_file == INVALID_HANDLE_VALUE) {
227 return;
228 }
229
230 while (size > 0) {
231 DWORD to_write = static_cast<DWORD>(size);
232 DWORD written = 0;
233 if (!::WriteFile(m_file, data, to_write, &written, nullptr) || written == 0) {
234 break;
235 }
236 data += written;
237 size -= written;
238 }
239 }
240
241 void write_marker(DWORD exception_code) const noexcept {
242 char marker[64];
243 std::size_t idx = 0;
244 const char prefix[] = "\n== CRASH EXCEPTION 0x";
245 const char suffix[] = " ==\n";
246 for (std::size_t i = 0; i < sizeof(prefix) - 1 && idx < sizeof(marker); ++i) {
247 marker[idx++] = prefix[i];
248 }
249
250 unsigned long code = exception_code;
251 char digits[16];
252 std::size_t digit_count = 0;
253 if (code == 0) {
254 digits[digit_count++] = '0';
255 } else {
256 while (code > 0 && digit_count < sizeof(digits)) {
257 unsigned long value = code & 0xF;
258 digits[digit_count++] = static_cast<char>((value < 10) ? ('0' + value) : ('A' + (value - 10)));
259 code >>= 4;
260 }
261 }
262
263 while (digit_count > 0 && idx < sizeof(marker)) {
264 marker[idx++] = digits[--digit_count];
265 }
266
267 for (std::size_t i = 0; i < sizeof(suffix) - 1 && idx < sizeof(marker); ++i) {
268 marker[idx++] = suffix[i];
269 }
270
271 safe_write(marker, idx);
272 }
273
274 std::string m_log_path;
275 HANDLE m_file{INVALID_HANDLE_VALUE};
276 std::size_t m_capacity{0};
277 std::atomic<std::size_t> m_next_offset{0};
278 std::atomic<std::size_t> m_committed{0};
279 std::atomic<int> m_log_level{static_cast<int>(LogLevel::LOG_LVL_TRACE)};
280 std::array<char, kMaxBufferSize> m_buffer{};
281
282 static std::atomic<CrashWindowsLogger*>& active_logger() noexcept {
283 static std::atomic<CrashWindowsLogger*> instance(nullptr);
284 return instance;
285 }
286 };
287
288#else // Stub for non-Windows systems
289
293 public:
294 static constexpr std::size_t kMaxBufferSize = 0;
295 static constexpr std::size_t kDefaultBufferSize = 0;
296
297 struct Config {
298 std::string log_path{};
299 std::size_t buffer_size = 0;
300 };
301
303 explicit CrashWindowsLogger(const Config&) {}
304
305 void log(const LogRecord& record, const std::string& message) override {
306 (void)record;
307 (void)message;
308 }
309
310 std::string get_string_param(const LoggerParam& param) const override {
311 (void)param;
312 return std::string();
313 }
314
315 int64_t get_int_param(const LoggerParam& param) const override {
316 (void)param;
317 return 0;
318 }
319
320 double get_float_param(const LoggerParam& param) const override {
321 (void)param;
322 return 0.0;
323 }
324
325 void set_log_level(LogLevel level) override {
326 (void)level;
327 }
328
329 LogLevel get_log_level() const override {
331 }
332
333 void wait() override {}
334
336 };
337
338#endif
339
340} // namespace logit
341
342#endif // LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_CRASHWINDOWSLOGGER_HPP_INCLUDED
Defines the interface for loggers used in the logging system.
\class CrashWindowsLogger \brief Stub implementation on non-Windows platforms.
void set_log_level(LogLevel level) override
Sets the minimal log level for this logger.
double get_float_param(const LoggerParam &param) const override
Retrieves a floating-point parameter from the logger.
void log(const LogRecord &record, const std::string &message) override
Logs a message.
int64_t get_int_param(const LoggerParam &param) const override
Retrieves an integer parameter from the logger.
LogLevel get_log_level() const override
Gets the minimal log level for this logger.
void wait() override
Waits for all asynchronous logging operations to complete.
static constexpr std::size_t kMaxBufferSize
std::string get_string_param(const LoggerParam &param) const override
Retrieves a string parameter from the logger.
static constexpr std::size_t kDefaultBufferSize
Optional buffered snapshot access returns empty results by default.
Definition ILogger.hpp:47
The primary namespace for the LogIt++ library.
LogLevel
Logging levels.
Definition enums.hpp:15
@ LOG_LVL_TRACE
Trace level logging.
LoggerParam
Enumeration for different logger parameters that can be retrieved.
Definition enums.hpp:47
@ LastFilePath
The full path of the last file written to.
Stores log metadata and content.
Definition LogRecord.hpp:17