10#include <ascii-chat/log/mmap.h>
11#include <ascii-chat/log/logging.h>
12#include <ascii-chat/platform/mmap.h>
13#include <ascii-chat/platform/system.h>
14#include <ascii-chat/video/ansi.h>
15#include <ascii-chat/util/time.h>
53static void format_timestamp(
char *buf,
size_t buf_size) {
55 time_t seconds = (time_t)(current_time_ns / NS_PER_SEC_INT);
56 uint64_t microseconds = time_ns_to_us(current_time_ns % NS_PER_SEC_INT);
61 size_t len = strftime(buf, buf_size,
"%H:%M:%S", &tm_info);
62 if (len > 0 && len < buf_size - 10) {
63 safe_snprintf(buf + len, buf_size - len,
".%06llu", (
unsigned long long)microseconds);
71static volatile sig_atomic_t g_crash_in_progress = 0;
74static void crash_signal_handler(
int sig) {
76 if (g_crash_in_progress) {
79 g_crash_in_progress = 1;
82 if (g_mmap_log.initialized && g_mmap_log.text_region) {
83 const char *crash_msg =
"\n=== CRASH DETECTED (signal %d) ===\n";
85 int len =
safe_snprintf(msg_buf,
sizeof(msg_buf), crash_msg, sig);
87 uint64_t pos = atomic_fetch_add(&g_mmap_log.write_pos, (uint64_t)len);
88 if (pos + (uint64_t)len <= g_mmap_log.text_capacity) {
89 memcpy(g_mmap_log.text_region + pos, msg_buf, (
size_t)len);
95 if (g_mmap_log.initialized) {
96 platform_mmap_sync(&g_mmap_log.mmap,
true);
100 signal(sig, SIG_DFL);
111static LONG WINAPI windows_crash_handler(EXCEPTION_POINTERS *exception_info) {
112 (void)exception_info;
115 if (g_crash_in_progress) {
116 return EXCEPTION_CONTINUE_SEARCH;
118 g_crash_in_progress = 1;
121 if (g_mmap_log.initialized && g_mmap_log.text_region) {
122 DWORD exception_code = exception_info ? exception_info->ExceptionRecord->ExceptionCode : 0;
123 const char *crash_msg =
"\n=== CRASH DETECTED (exception 0x%08lX) ===\n";
125 int len =
safe_snprintf(msg_buf,
sizeof(msg_buf), crash_msg, (
unsigned long)exception_code);
127 uint64_t pos = atomic_fetch_add(&g_mmap_log.write_pos, (uint64_t)len);
128 if (pos + (uint64_t)len <= g_mmap_log.text_capacity) {
129 memcpy(g_mmap_log.text_region + pos, msg_buf, (
size_t)len);
135 if (g_mmap_log.initialized) {
136 platform_mmap_sync(&g_mmap_log.mmap,
true);
140 return EXCEPTION_CONTINUE_SEARCH;
146 struct sigaction sa = {0};
147 sa.sa_handler = crash_signal_handler;
148 sigemptyset(&sa.sa_mask);
149 sa.sa_flags = (int)SA_RESETHAND;
151 sigaction(SIGSEGV, &sa, NULL);
152 sigaction(SIGABRT, &sa, NULL);
153 sigaction(SIGBUS, &sa, NULL);
154 sigaction(SIGFPE, &sa, NULL);
155 sigaction(SIGILL, &sa, NULL);
157 SetUnhandledExceptionFilter(windows_crash_handler);
171static size_t find_content_end(
const char *text,
size_t capacity) {
173 size_t pos = capacity;
174 while (pos > 0 && (text[pos - 1] ==
' ' || text[pos - 1] ==
'\0' || text[pos - 1] ==
'\n')) {
179 while (pos < capacity && text[pos] !=
'\n' && text[pos] !=
' ' && text[pos] !=
'\0') {
184 if (pos < capacity && text[pos] ==
'\n') {
192 if (!config || !config->log_path) {
193 return SET_ERRNO(ERROR_INVALID_PARAM,
"mmap log: config or log_path is NULL");
196 if (g_mmap_log.initialized) {
197 log_warn(
"mmap log: already initialized, destroying first");
202 size_t file_size = config->max_size > 0 ? config->max_size : LOG_MMAP_DEFAULT_SIZE;
203 if (file_size < 1024) {
208 SAFE_STRNCPY(g_mmap_log.file_path, config->log_path,
sizeof(g_mmap_log.file_path) - 1);
211 platform_mmap_init(&g_mmap_log.mmap);
212 asciichat_error_t result = platform_mmap_open(config->log_path, file_size, &g_mmap_log.mmap);
213 if (result != ASCIICHAT_OK) {
218 g_mmap_log.text_region = (
char *)g_mmap_log.mmap.addr;
219 g_mmap_log.text_capacity = file_size;
222 size_t existing_pos = find_content_end(g_mmap_log.text_region, file_size);
223 atomic_store(&g_mmap_log.write_pos, existing_pos);
227 if (existing_pos < file_size) {
228 memset(g_mmap_log.text_region + existing_pos,
'\n', file_size - existing_pos);
231 if (existing_pos > 0) {
232 log_info(
"mmap log: resumed existing log at position %zu", existing_pos);
234 log_info(
"mmap log: created new log file %s (%zu bytes)", config->log_path, file_size);
238 atomic_store(&g_mmap_log.bytes_written, 0);
239 atomic_store(&g_mmap_log.wrap_count, 0);
244 g_mmap_log.initialized =
true;
247 log_mmap_write(1 , NULL, 0, NULL,
"=== Log started (mmap text mode, %zu bytes) ===", file_size);
253 log_mmap_config_t config = {
254 .log_path = log_path,
255 .max_size = max_size,
261 if (!g_mmap_log.initialized) {
269 platform_mmap_sync(&g_mmap_log.mmap,
true);
273 uint64_t final_pos = atomic_load(&g_mmap_log.write_pos);
274 if (final_pos < g_mmap_log.text_capacity && strlen(g_mmap_log.file_path) > 0) {
277 platform_mmap_close(&g_mmap_log.mmap);
279 CreateFileA(g_mmap_log.file_path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
280 if (hFile != INVALID_HANDLE_VALUE) {
282 size.QuadPart = (LONGLONG)final_pos;
283 if (SetFilePointerEx(hFile, size, NULL, FILE_BEGIN)) {
287 log_debug(
"mmap log: truncated %s to %zu bytes (was %zu MB)", g_mmap_log.file_path, (
size_t)final_pos,
288 g_mmap_log.text_capacity / 1024 / 1024);
292 if (g_mmap_log.mmap.fd >= 0) {
293 if (ftruncate(g_mmap_log.mmap.fd, (off_t)final_pos) == 0) {
294 log_debug(
"mmap log: truncated %s to %zu bytes (was %zu MB)", g_mmap_log.file_path, (
size_t)final_pos,
295 g_mmap_log.text_capacity / 1024 / 1024);
298 platform_mmap_close(&g_mmap_log.mmap);
302 platform_mmap_close(&g_mmap_log.mmap);
305 g_mmap_log.text_region = NULL;
306 g_mmap_log.file_path[0] =
'\0';
308 g_mmap_log.initialized =
false;
309 log_debug(
"mmap log: destroyed");
312void log_mmap_write(
int level,
const char *file,
int line,
const char *func,
const char *fmt, ...) {
313 if (!g_mmap_log.initialized || !g_mmap_log.text_region) {
317 static const char *level_names[] = {
"DEV",
"DEBUG",
"INFO",
"WARN",
"ERROR",
"FATAL"};
318 const char *level_name = (level >= 0 && level < 6) ? level_names[level] :
"???";
321 char line_buf[LOG_MMAP_MSG_BUFFER_SIZE];
322 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
323 format_timestamp(time_buf,
sizeof(time_buf));
328 safe_snprintf(line_buf,
sizeof(line_buf),
"[%s] [%s] %s:%d in %s(): ", time_buf, level_name, file, line, func);
330 prefix_len =
safe_snprintf(line_buf,
sizeof(line_buf),
"[%s] [%s] ", time_buf, level_name);
333 if (prefix_len < 0) {
340 int msg_len =
safe_vsnprintf(line_buf + prefix_len,
sizeof(line_buf) - (
size_t)prefix_len - 1, fmt,
args);
348 size_t total_len = (size_t)prefix_len + (
size_t)msg_len;
349 if (total_len >=
sizeof(line_buf) - 1) {
350 total_len =
sizeof(line_buf) - 2;
352 line_buf[total_len] =
'\n';
354 line_buf[total_len] =
'\0';
358 const char *write_buf = stripped ? stripped : line_buf;
359 size_t write_len = stripped ? strlen(stripped) : total_len;
362 uint64_t pos = atomic_fetch_add(&g_mmap_log.write_pos, write_len);
366 if (pos + write_len > g_mmap_log.text_capacity) {
368 atomic_fetch_sub(&g_mmap_log.write_pos, write_len);
376 memcpy(g_mmap_log.text_region + pos, write_buf, write_len);
378 atomic_fetch_add(&g_mmap_log.bytes_written, write_len);
386 platform_mmap_sync(&g_mmap_log.mmap,
false);
391 return g_mmap_log.initialized;
395 if (g_mmap_log.initialized) {
396 platform_mmap_sync(&g_mmap_log.mmap,
true);
405 *
wrap_count = atomic_load(&g_mmap_log.wrap_count);
410 if (!g_mmap_log.initialized) {
415 *used = (size_t)atomic_load(&g_mmap_log.write_pos);
418 *capacity = g_mmap_log.text_capacity;
424 if (!g_mmap_log.initialized || !g_mmap_log.text_region) {
430 uint64_t current_pos = atomic_load(&g_mmap_log.write_pos);
431 size_t capacity = g_mmap_log.text_capacity;
434 size_t keep_size = capacity * 2 / 3;
435 if (current_pos <= keep_size) {
440 size_t skip_bytes = (size_t)current_pos - keep_size;
441 char *keep_start = g_mmap_log.text_region + skip_bytes;
445 while (skipped < keep_size && *keep_start !=
'\n') {
449 if (skipped < keep_size && *keep_start ==
'\n') {
454 size_t actual_keep = keep_size - skipped;
455 if (actual_keep == 0) {
457 atomic_store(&g_mmap_log.write_pos, 0);
458 memset(g_mmap_log.text_region,
'\n', capacity);
463 memmove(g_mmap_log.text_region, keep_start, actual_keep);
466 memset(g_mmap_log.text_region + actual_keep,
'\n', capacity - actual_keep);
469 atomic_store(&g_mmap_log.write_pos, actual_keep);
472 const char *rotate_msg =
"\n=== LOG ROTATED ===\n";
473 size_t rotate_len = strlen(rotate_msg);
474 if (actual_keep + rotate_len < capacity) {
475 memcpy(g_mmap_log.text_region + actual_keep, rotate_msg, rotate_len);
476 atomic_store(&g_mmap_log.write_pos, actual_keep + rotate_len);
479 atomic_fetch_add(&g_mmap_log.wrap_count, 1);
482 platform_mmap_sync(&g_mmap_log.mmap,
true);
char * ansi_strip_escapes(const char *input, size_t input_len)
char file_path[PLATFORM_MAX_PATH_LENGTH]
asciichat_error_t log_mmap_init(const log_mmap_config_t *config)
void log_mmap_rotate(void)
void log_mmap_install_crash_handlers(void)
bool log_mmap_is_active(void)
void log_mmap_write(int level, const char *file, int line, const char *func, const char *fmt,...)
bool log_mmap_get_usage(size_t *used, size_t *capacity)
_Atomic uint64_t bytes_written
_Atomic uint64_t wrap_count
void log_mmap_destroy(void)
void log_mmap_get_stats(uint64_t *bytes_written, uint64_t *wrap_count)
_Atomic uint64_t write_pos
asciichat_error_t log_mmap_init_simple(const char *log_path, size_t max_size)
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
int safe_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list ap)
Safe formatted string printing with va_list.
#define PLATFORM_MAX_PATH_LENGTH
uint64_t time_get_realtime_ns(void)
asciichat_error_t platform_localtime(const time_t *timer, struct tm *result)