LogIt++
Header-only, macro-first C++ logging library
Loading...
Searching...
No Matches
CrashPosixLogger.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_CRASHPOSIXLOGGER_HPP_INCLUDED
3#define LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_CRASHPOSIXLOGGER_HPP_INCLUDED
4
7
8#include "ILogger.hpp"
9
10#include <atomic>
11#include <array>
12#include <cstddef>
13#include <cstring>
14#include <string>
15
16#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
17#include <cerrno>
18#include <fcntl.h>
19#include <signal.h>
20#include <sys/stat.h>
21#include <sys/types.h>
22#include <unistd.h>
23#endif
24
25namespace logit {
26
27#if defined(__unix__) || defined(__APPLE__) || defined(__linux__)
28
32 class CrashPosixLogger : public ILogger {
33 public:
35 static constexpr std::size_t kMaxBufferSize = 64 * 1024; // 64 KiB
36
38 static constexpr std::size_t kDefaultBufferSize = kMaxBufferSize;
39
42 struct Config {
43 std::string log_path = "crash.log";
44 std::size_t buffer_size = kDefaultBufferSize;
45 };
46
48 CrashPosixLogger() : CrashPosixLogger(Config()) {}
49
52 explicit CrashPosixLogger(const Config& config) :
53 m_log_path(config.log_path),
54 m_capacity(config.buffer_size > kMaxBufferSize ? kMaxBufferSize : config.buffer_size) {
55 if (m_capacity == 0) {
56 m_capacity = 1;
57 }
58 int flags = O_WRONLY | O_CREAT | O_APPEND;
59#ifdef O_CLOEXEC
60 flags |= O_CLOEXEC;
61#endif
62 m_fd = ::open(m_log_path.c_str(), flags, static_cast<mode_t>(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
63#ifdef O_CLOEXEC
64 if (m_fd < 0 && (flags & O_CLOEXEC) != 0) {
65 flags &= ~O_CLOEXEC;
66 m_fd = ::open(m_log_path.c_str(), flags, static_cast<mode_t>(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH));
67 }
68#endif
69 s_active_logger.store(this, std::memory_order_release);
70 }
71
72 CrashPosixLogger(const CrashPosixLogger&) = delete;
73 CrashPosixLogger& operator=(const CrashPosixLogger&) = delete;
74
76 ~CrashPosixLogger() override {
77 CrashPosixLogger* expected = this;
78 s_active_logger.compare_exchange_strong(
79 expected, nullptr, std::memory_order_release, std::memory_order_relaxed);
80 if (m_fd >= 0) {
81 ::close(m_fd);
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
178 static void install_signal_handler(int signo) {
179 struct sigaction action;
180 std::memset(&action, 0, sizeof(action));
181 action.sa_sigaction = &CrashPosixLogger::signal_handler;
182 sigemptyset(&action.sa_mask);
183 action.sa_flags = SA_SIGINFO | SA_RESTART;
184 sigaction(signo, &action, nullptr);
185 }
186
187 private:
188 static void signal_handler(int signo, siginfo_t* info, void* context) {
189 (void)info;
190 (void)context;
191 CrashPosixLogger* logger = s_active_logger.load(std::memory_order_acquire);
192 if (logger != nullptr) {
193 logger->write_snapshot(signo);
194 }
195 _exit(128 + signo);
196 }
197
198 void write_snapshot(int signo) const noexcept {
199 if (m_fd < 0) {
200 return;
201 }
202
203 const std::size_t capacity = m_capacity;
204 const std::size_t committed = m_committed.load(std::memory_order_acquire);
205 std::size_t available = committed;
206 if (available > capacity) {
207 available = capacity;
208 }
209 const std::size_t start_offset = committed - available;
210 const std::size_t start_index = capacity ? (start_offset % capacity) : 0;
211
212 if (available > 0) {
213 std::size_t chunk = capacity - start_index;
214 if (chunk > available) {
215 chunk = available;
216 }
217 safe_write(m_buffer.data() + start_index, chunk);
218 const std::size_t remaining = available - chunk;
219 if (remaining > 0) {
220 safe_write(m_buffer.data(), remaining);
221 }
222 }
223
224 write_marker(signo);
225 }
226
227 void safe_write(const char* data, std::size_t size) const noexcept {
228 while (size > 0) {
229 const ssize_t written = ::write(m_fd, data, size);
230 if (written <= 0) {
231 if (written < 0 && errno == EINTR) {
232 continue;
233 }
234 break;
235 }
236 const std::size_t advance = static_cast<std::size_t>(written);
237 data += advance;
238 size -= advance;
239 }
240 }
241
242 void write_marker(int signo) const noexcept {
243 char marker[64];
244 std::size_t idx = 0;
245 const char prefix[] = "\n== CRASH SIGNAL ";
246 const char suffix[] = " ==\n";
247 for (std::size_t i = 0; i < sizeof(prefix) - 1 && idx < sizeof(marker); ++i) {
248 marker[idx++] = prefix[i];
249 }
250
251 int value = signo;
252 if (value < 0) {
253 if (idx < sizeof(marker)) {
254 marker[idx++] = '-';
255 }
256 value = -value;
257 }
258
259 char digits[32];
260 std::size_t digit_count = 0;
261 unsigned int uvalue = static_cast<unsigned int>(value);
262 if (uvalue == 0) {
263 digits[digit_count++] = '0';
264 } else {
265 while (uvalue > 0 && digit_count < sizeof(digits)) {
266 digits[digit_count++] = static_cast<char>('0' + (uvalue % 10));
267 uvalue /= 10;
268 }
269 }
270 while (digit_count > 0 && idx < sizeof(marker)) {
271 marker[idx++] = digits[--digit_count];
272 }
273
274 for (std::size_t i = 0; i < sizeof(suffix) - 1 && idx < sizeof(marker); ++i) {
275 marker[idx++] = suffix[i];
276 }
277
278 safe_write(marker, idx);
279 }
280
281 std::string m_log_path;
282 int m_fd{-1};
283 std::size_t m_capacity{0};
284 std::atomic<std::size_t> m_next_offset{0};
285 std::atomic<std::size_t> m_committed{0};
286 std::atomic<int> m_log_level{static_cast<int>(LogLevel::LOG_LVL_TRACE)};
287 std::array<char, kMaxBufferSize> m_buffer{};
288
289 inline static std::atomic<CrashPosixLogger*> s_active_logger{nullptr};
290 };
291
292#else // Stub for non-POSIX systems
293
296 class CrashPosixLogger : public ILogger {
297 public:
298 static constexpr std::size_t kMaxBufferSize = 0;
299 static constexpr std::size_t kDefaultBufferSize = 0;
300
301 struct Config {
302 std::string log_path{};
303 std::size_t buffer_size = 0;
304 };
305
306 CrashPosixLogger() = default;
307 explicit CrashPosixLogger(const Config&) {}
308
309 void log(const LogRecord& record, const std::string& message) override {
310 (void)record;
311 (void)message;
312 }
313
314 std::string get_string_param(const LoggerParam& param) const override {
315 (void)param;
316 return std::string();
317 }
318
319 int64_t get_int_param(const LoggerParam& param) const override {
320 (void)param;
321 return 0;
322 }
323
324 double get_float_param(const LoggerParam& param) const override {
325 (void)param;
326 return 0.0;
327 }
328
329 void set_log_level(LogLevel level) override {
330 (void)level;
331 }
332
333 LogLevel get_log_level() const override {
335 }
336
337 void wait() override {}
338
339 static void install_signal_handler(int signo) {
340 (void)signo;
341 }
342 };
343
344#endif
345
346} // namespace logit
347
348#endif // LOGIT_CPP_HEADER_LOGIT_CPP_LOGIT_LOGGERS_CRASHPOSIXLOGGER_HPP_INCLUDED
Defines the interface for loggers used in the logging system.
Stub implementation on non-POSIX platforms.
void set_log_level(LogLevel level) override
Sets the minimal log level for this logger.
static void install_signal_handler(int signo)
void wait() override
Waits for all asynchronous logging operations to complete.
int64_t get_int_param(const LoggerParam &param) const override
Retrieves an integer parameter from the logger.
void log(const LogRecord &record, const std::string &message) override
Logs a message.
double get_float_param(const LoggerParam &param) const override
Retrieves a floating-point parameter from the logger.
static constexpr std::size_t kDefaultBufferSize
std::string get_string_param(const LoggerParam &param) const override
Retrieves a string parameter from the logger.
static constexpr std::size_t kMaxBufferSize
LogLevel get_log_level() const override
Gets the minimal log level for this logger.
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