51static void format_timestamp(
char *buf,
size_t buf_size) {
53 clock_gettime(CLOCK_REALTIME, &ts);
58 size_t len = strftime(buf, buf_size,
"%H:%M:%S", &tm_info);
59 if (len > 0 && len < buf_size - 10) {
60 snprintf(buf + len, buf_size - len,
".%06ld", ts.tv_nsec / 1000);
68static volatile sig_atomic_t g_crash_in_progress = 0;
71static void crash_signal_handler(
int sig) {
73 if (g_crash_in_progress) {
76 g_crash_in_progress = 1;
79 if (g_mmap_log.initialized && g_mmap_log.text_region) {
80 const char *crash_msg =
"\n=== CRASH DETECTED (signal %d) ===\n";
82 int len = snprintf(msg_buf,
sizeof(msg_buf), crash_msg, sig);
85 if (pos + (
uint64_t)len <= g_mmap_log.text_capacity) {
86 memcpy(g_mmap_log.text_region + pos, msg_buf, (
size_t)len);
92 if (g_mmap_log.initialized) {
108static LONG WINAPI windows_crash_handler(EXCEPTION_POINTERS *exception_info) {
109 (void)exception_info;
112 if (g_crash_in_progress) {
113 return EXCEPTION_CONTINUE_SEARCH;
115 g_crash_in_progress = 1;
118 if (g_mmap_log.initialized && g_mmap_log.text_region) {
119 DWORD exception_code = exception_info ? exception_info->ExceptionRecord->ExceptionCode : 0;
120 const char *crash_msg =
"\n=== CRASH DETECTED (exception 0x%08lX) ===\n";
122 int len = snprintf(msg_buf,
sizeof(msg_buf), crash_msg, (
unsigned long)exception_code);
125 if (pos + (
uint64_t)len <= g_mmap_log.text_capacity) {
126 memcpy(g_mmap_log.text_region + pos, msg_buf, (
size_t)len);
132 if (g_mmap_log.initialized) {
137 return EXCEPTION_CONTINUE_SEARCH;
143 struct sigaction sa = {0};
144 sa.sa_handler = crash_signal_handler;
145 sigemptyset(&sa.sa_mask);
146 sa.sa_flags = (int)SA_RESETHAND;
148 sigaction(SIGSEGV, &sa, NULL);
149 sigaction(SIGABRT, &sa, NULL);
150 sigaction(SIGBUS, &sa, NULL);
151 sigaction(SIGFPE, &sa, NULL);
152 sigaction(SIGILL, &sa, NULL);
154 SetUnhandledExceptionFilter(windows_crash_handler);
168static size_t find_content_end(
const char *text,
size_t capacity) {
170 size_t pos = capacity;
171 while (pos > 0 && (text[pos - 1] ==
' ' || text[pos - 1] ==
'\0' || text[pos - 1] ==
'\n')) {
176 while (pos < capacity && text[pos] !=
'\n' && text[pos] !=
' ' && text[pos] !=
'\0') {
181 if (pos < capacity && text[pos] ==
'\n') {
193 if (g_mmap_log.initialized) {
194 log_warn(
"mmap log: already initialized, destroying first");
200 if (file_size < 1024) {
215 g_mmap_log.text_region = (
char *)g_mmap_log.mmap.addr;
216 g_mmap_log.text_capacity = file_size;
219 size_t existing_pos = find_content_end(g_mmap_log.text_region, file_size);
220 atomic_store(&g_mmap_log.write_pos, existing_pos);
224 if (existing_pos < file_size) {
225 memset(g_mmap_log.text_region + existing_pos,
'\n', file_size - existing_pos);
228 if (existing_pos > 0) {
229 log_info(
"mmap log: resumed existing log at position %zu", existing_pos);
231 log_info(
"mmap log: created new log file %s (%zu bytes)", config->
log_path, file_size);
235 atomic_store(&g_mmap_log.bytes_written, 0);
236 atomic_store(&g_mmap_log.wrap_count, 0);
241 g_mmap_log.initialized =
true;
244 log_mmap_write(1 , NULL, 0, NULL,
"=== Log started (mmap text mode, %zu bytes) ===", file_size);
252 .max_size = max_size,
258 if (!g_mmap_log.initialized) {
270 uint64_t final_pos = atomic_load(&g_mmap_log.write_pos);
271 if (final_pos < g_mmap_log.text_capacity && strlen(g_mmap_log.file_path) > 0) {
276 CreateFileA(g_mmap_log.file_path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
277 if (hFile != INVALID_HANDLE_VALUE) {
279 size.QuadPart = (LONGLONG)final_pos;
280 if (SetFilePointerEx(hFile, size, NULL, FILE_BEGIN)) {
284 log_info(
"mmap log: truncated %s to %zu bytes (was %zu MB)", g_mmap_log.file_path, (
size_t)final_pos,
285 g_mmap_log.text_capacity / 1024 / 1024);
289 if (g_mmap_log.mmap.fd >= 0) {
290 if (ftruncate(g_mmap_log.mmap.fd, (off_t)final_pos) == 0) {
291 log_info(
"mmap log: truncated %s to %zu bytes (was %zu MB)", g_mmap_log.file_path, (
size_t)final_pos,
292 g_mmap_log.text_capacity / 1024 / 1024);
302 g_mmap_log.text_region = NULL;
303 g_mmap_log.file_path[0] =
'\0';
305 g_mmap_log.initialized =
false;
309void log_mmap_write(
int level,
const char *file,
int line,
const char *func,
const char *fmt, ...) {
310 if (!g_mmap_log.initialized || !g_mmap_log.text_region) {
314 static const char *level_names[] = {
"DEV",
"DEBUG",
"INFO",
"WARN",
"ERROR",
"FATAL"};
315 const char *level_name = (level >= 0 && level < 6) ? level_names[level] :
"???";
320 format_timestamp(time_buf,
sizeof(time_buf));
325 snprintf(line_buf,
sizeof(line_buf),
"[%s] [%s] %s:%d in %s(): ", time_buf, level_name, file, line, func);
327 prefix_len = snprintf(line_buf,
sizeof(line_buf),
"[%s] [%s] ", time_buf, level_name);
330 if (prefix_len < 0) {
337 int msg_len = vsnprintf(line_buf + prefix_len,
sizeof(line_buf) - (
size_t)prefix_len - 1, fmt, args);
345 size_t total_len = (size_t)prefix_len + (
size_t)msg_len;
346 if (total_len >=
sizeof(line_buf) - 1) {
347 total_len =
sizeof(line_buf) - 2;
349 line_buf[total_len] =
'\n';
351 line_buf[total_len] =
'\0';
354 uint64_t pos = atomic_fetch_add(&g_mmap_log.write_pos, total_len);
358 if (pos + total_len > g_mmap_log.text_capacity) {
360 atomic_fetch_sub(&g_mmap_log.write_pos, total_len);
365 memcpy(g_mmap_log.text_region + pos, line_buf, total_len);
367 atomic_fetch_add(&g_mmap_log.bytes_written, total_len);
376 return g_mmap_log.initialized;
380 if (g_mmap_log.initialized) {
390 *
wrap_count = atomic_load(&g_mmap_log.wrap_count);
395 if (!g_mmap_log.initialized) {
400 *used = (size_t)atomic_load(&g_mmap_log.write_pos);
403 *capacity = g_mmap_log.text_capacity;
409 if (!g_mmap_log.initialized || !g_mmap_log.text_region) {
415 uint64_t current_pos = atomic_load(&g_mmap_log.write_pos);
416 size_t capacity = g_mmap_log.text_capacity;
419 size_t keep_size = capacity * 2 / 3;
420 if (current_pos <= keep_size) {
425 size_t skip_bytes = (size_t)current_pos - keep_size;
426 char *keep_start = g_mmap_log.text_region + skip_bytes;
430 while (skipped < keep_size && *keep_start !=
'\n') {
434 if (skipped < keep_size && *keep_start ==
'\n') {
439 size_t actual_keep = keep_size - skipped;
440 if (actual_keep == 0) {
442 atomic_store(&g_mmap_log.write_pos, 0);
443 memset(g_mmap_log.text_region,
'\n', capacity);
448 memmove(g_mmap_log.text_region, keep_start, actual_keep);
451 memset(g_mmap_log.text_region + actual_keep,
'\n', capacity - actual_keep);
454 atomic_store(&g_mmap_log.write_pos, actual_keep);
457 const char *rotate_msg =
"\n=== LOG ROTATED ===\n";
458 size_t rotate_len = strlen(rotate_msg);
459 if (actual_keep + rotate_len < capacity) {
460 memcpy(g_mmap_log.text_region + actual_keep, rotate_msg, rotate_len);
461 atomic_store(&g_mmap_log.write_pos, actual_keep + rotate_len);
464 atomic_fetch_add(&g_mmap_log.wrap_count, 1);
#define SAFE_STRNCPY(dst, src, size)
#define PLATFORM_MAX_PATH_LENGTH
unsigned long long uint64_t
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
#define log_warn(...)
Log a WARN message.
#define log_info(...)
Log an INFO message.
#define LOG_TIMESTAMP_BUFFER_SIZE
Maximum size of a timestamp string.
#define LOG_MMAP_MSG_BUFFER_SIZE
Maximum size of a log message in mmap mode.
📝 Logging API with multiple log levels and terminal output control
Lock-free memory-mapped text logging with crash safety.
#define LOG_MMAP_DEFAULT_SIZE
Default mmap log file size (4MB)
char file_path[PLATFORM_MAX_PATH_LENGTH]
asciichat_error_t log_mmap_init(const log_mmap_config_t *config)
Initialize mmap-based text logging.
void log_mmap_rotate(void)
Rotate the mmap log (tail-keeping rotation)
void log_mmap_install_crash_handlers(void)
Install signal handlers for crash safety.
void log_mmap_sync(void)
Force sync the mmap'd file to disk.
bool log_mmap_is_active(void)
Check if mmap logging is active.
void log_mmap_write(int level, const char *file, int line, const char *func, const char *fmt,...)
Write a log entry directly to the mmap'd file (lock-free)
bool log_mmap_get_usage(size_t *used, size_t *capacity)
Get current mmap log usage.
_Atomic uint64_t bytes_written
_Atomic uint64_t wrap_count
void log_mmap_destroy(void)
Shutdown mmap logging.
void log_mmap_get_stats(uint64_t *bytes_written, uint64_t *wrap_count)
Get statistics about the mmap log.
_Atomic uint64_t write_pos
asciichat_error_t log_mmap_init_simple(const char *log_path, size_t max_size)
Initialize mmap logging with simple parameters.
Configuration for mmap logging.
Cross-platform system functions interface for ascii-chat.
⏱️ High-precision timing utilities using sokol_time.h and uthash