ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
mmap.h File Reference

Lock-free memory-mapped text logging with crash safety. More...

Go to the source code of this file.

Data Structures

struct  log_mmap_config
 Path and size limits for the memory-mapped log file. More...

Macros

#define LOG_MMAP_DEFAULT_SIZE   (4 * 1024 * 1024)
 Default mmap log file size (4MB).

Typedefs

typedef struct log_mmap_config log_mmap_config_t
 Path and size limits for the memory-mapped log file.

Functions

asciichat_error_t log_mmap_init (const log_mmap_config_t *config)
 Initialize mmap-based text logging.
asciichat_error_t log_mmap_init_simple (const char *log_path, size_t max_size)
 Initialize mmap logging with simple parameters.
void log_mmap_destroy (void)
 Shutdown mmap logging.
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_is_active (void)
 Check if mmap logging is active.
void log_mmap_sync (void)
 Force sync the mmap'd file to disk.
void log_mmap_install_crash_handlers (void)
 Install signal handlers for crash safety.
void log_mmap_get_stats (uint64_t *bytes_written, uint64_t *wrap_count)
 Get statistics about the mmap log.
bool log_mmap_get_usage (size_t *used, size_t *capacity)
 Get current mmap log usage.
void log_mmap_rotate (void)
 Rotate the mmap log (tail-keeping rotation).

Detailed Description

Lock-free memory-mapped text logging with crash safety.

This module provides crash-safe logging by writing human-readable text directly to a memory-mapped log file. Key features:

  • Lock-free logging: Uses atomic fetch_add for position claiming
  • Crash-safe: Text is written directly to mmap'd file, readable after crash
  • No flusher needed: Logs are human-readable in the file immediately
  • Pure text: No binary header, works with grep/rg/cat without special flags
  • Simple design: Just mmap the .log file and write text to it

Usage:

// Initialize mmap logging (call once at startup)
log_mmap_init("/tmp/myapp.log", 4 * 1024 * 1024); // 4MB log file
// Use normal log_* macros - they automatically use mmap backend
log_debug("This uses atomic operations, no mutex!");
// Cleanup on shutdown
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
asciichat_error_t log_mmap_init(const log_mmap_config_t *config)
Initialize mmap-based text logging.
Definition mmap.c:194
void log_mmap_destroy(void)
Shutdown mmap logging.
Definition mmap.c:263

Definition in file log/mmap.h.

Macro Definition Documentation

◆ LOG_MMAP_DEFAULT_SIZE

#define LOG_MMAP_DEFAULT_SIZE   (4 * 1024 * 1024)

Default mmap log file size (4MB).

Definition at line 46 of file log/mmap.h.

Referenced by log_mmap_init().

Typedef Documentation

◆ log_mmap_config_t

Path and size limits for the memory-mapped log file.

Path and size limits for the memory-mapped log file

log_path is borrowed for initialization; max_size bounds the mapping and falls back to the default when zero. The logging subsystem uses these values while it creates the mapping.

Function Documentation

◆ log_mmap_destroy()

void log_mmap_destroy ( void )

Shutdown mmap logging.

Syncs and unmaps the log file.

Definition at line 263 of file mmap.c.

263 {
264 if (!g_mmap_log.initialized) {
265 return;
266 }
267
268 /* Write shutdown marker */
269 log_mmap_write(1 /* LOG_INFO */, NULL, 0, NULL, "=== Log ended ===");
270
271 /* Sync to disk */
272 platform_mmap_sync(&g_mmap_log.mmap, true);
273
274 /* Truncate file to actual content size to save space
275 * This converts the large mmap file (4MB with newlines) to just the actual log content */
276 uint64_t final_pos = atomic_load_u64(&g_mmap_log.write_pos);
277 if (final_pos < g_mmap_log.text_capacity && strlen(g_mmap_log.file_path) > 0) {
278#ifdef _WIN32
279 /* Windows: Close mmap first, then truncate file, then reopen for truncation */
280 platform_mmap_close(&g_mmap_log.mmap);
281 HANDLE hFile =
282 CreateFileA(g_mmap_log.file_path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
283 if (hFile != INVALID_HANDLE_VALUE) {
284 LARGE_INTEGER size;
285 size.QuadPart = (LONGLONG)final_pos;
286 if (SetFilePointerEx(hFile, size, NULL, FILE_BEGIN)) {
287 SetEndOfFile(hFile);
288 }
289 CloseHandle(hFile);
290 log_debug("mmap log: truncated %s to %zu bytes (was %zu MB)", g_mmap_log.file_path, (size_t)final_pos,
291 g_mmap_log.text_capacity / 1024 / 1024);
292 }
293#else
294 /* POSIX: Use ftruncate() on the file descriptor */
295 if (g_mmap_log.mmap.fd >= 0) {
296 if (ftruncate(g_mmap_log.mmap.fd, (off_t)final_pos) == 0) {
297 log_debug("mmap log: truncated %s to %zu bytes (was %zu MB)", g_mmap_log.file_path, (size_t)final_pos,
298 g_mmap_log.text_capacity / 1024 / 1024);
299 }
300 }
301 platform_mmap_close(&g_mmap_log.mmap);
302#endif
303 } else {
304 /* No truncation needed or path not set */
305 platform_mmap_close(&g_mmap_log.mmap);
306 }
307
308 g_mmap_log.text_region = NULL;
309 g_mmap_log.file_path[0] = '\0';
310
311 g_mmap_log.initialized = false;
312 log_debug("mmap log: destroyed");
313}
unsigned long long uint64_t
Definition common.h:63
void platform_mmap_close(platform_mmap_t *mapping)
Unmap and close a memory-mapped file.
void platform_mmap_sync(platform_mmap_t *mapping, bool async)
Flush memory-mapped changes to disk.
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).
Definition mmap.c:315

References log_debug, log_mmap_write(), platform_mmap_close(), and platform_mmap_sync().

Referenced by log_destroy(), log_disable_mmap(), and log_mmap_init().

◆ log_mmap_get_stats()

void log_mmap_get_stats ( uint64_t * bytes_written,
uint64_t * wrap_count )

Get statistics about the mmap log.

Parameters
[out]bytes_writtenTotal bytes written since init
[out]wrap_countNumber of times log has wrapped

Definition at line 403 of file mmap.c.

403 {
404 if (bytes_written) {
405 *bytes_written = atomic_load_u64(&g_mmap_log.bytes_written);
406 }
407 if (wrap_count) {
408 *wrap_count = atomic_load_u64(&g_mmap_log.wrap_count);
409 }
410}
atomic_t bytes_written
Definition mmap.c:45
atomic_t wrap_count
Definition mmap.c:46

References bytes_written, and wrap_count.

◆ log_mmap_get_usage()

bool log_mmap_get_usage ( size_t * used,
size_t * capacity )

Get current mmap log usage.

Parameters
[out]usedCurrent bytes used in the log
[out]capacityTotal capacity of the log
Returns
true if mmap is active and values are valid

Definition at line 412 of file mmap.c.

412 {
413 if (!g_mmap_log.initialized) {
414 return false;
415 }
416
417 if (used) {
418 *used = (size_t)atomic_load_u64(&g_mmap_log.write_pos);
419 }
420 if (capacity) {
421 *capacity = g_mmap_log.text_capacity;
422 }
423 return true;
424}

◆ log_mmap_init()

asciichat_error_t log_mmap_init ( const log_mmap_config_t * config)

Initialize mmap-based text logging.

Creates or opens a memory-mapped log file. Text is written directly to the file, so it's readable even after a crash.

Parameters
configConfiguration options
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 194 of file mmap.c.

194 {
195 if (!config || !config->log_path) {
196 return SET_ERRNO(ERROR_INVALID_PARAM, "mmap log: config or log_path is NULL");
197 }
198
199 if (g_mmap_log.initialized) {
200 log_warn("mmap log: already initialized, destroying first");
202 }
203
204 /* Determine file size */
205 size_t file_size = config->max_size > 0 ? config->max_size : LOG_MMAP_DEFAULT_SIZE;
206 if (file_size < 1024) {
207 file_size = 1024; /* Minimum reasonable size */
208 }
209
210 /* Store file path for later truncation */
211 SAFE_STRNCPY(g_mmap_log.file_path, config->log_path, sizeof(g_mmap_log.file_path) - 1);
212
213 /* Open mmap file */
214 platform_mmap_init(&g_mmap_log.mmap);
215 asciichat_error_t result = platform_mmap_open("mmap_log_file", config->log_path, file_size, &g_mmap_log.mmap);
216 if (result != ASCIICHAT_OK) {
217 return result;
218 }
219
220 /* Entire file is text - no header */
221 g_mmap_log.text_region = (char *)g_mmap_log.mmap.addr;
222 g_mmap_log.text_capacity = file_size;
223
224 /* Find where existing content ends (scan for last newline before spaces/nulls) */
225 size_t existing_pos = find_content_end(g_mmap_log.text_region, file_size);
226 atomic_store_u64(&g_mmap_log.write_pos, existing_pos);
227
228 /* Clear unused portion with newlines (grep-friendly without needing -a flag)
229 * We truncate the file on clean shutdown to save space */
230 if (existing_pos < file_size) {
231 memset(g_mmap_log.text_region + existing_pos, '\n', file_size - existing_pos);
232 }
233
234 if (existing_pos > 0) {
235 log_info("mmap log: resumed existing log at position %zu", existing_pos);
236 } else {
237 log_info("mmap log: created new log file %s (%zu bytes)", config->log_path, file_size);
238 }
239
240 /* Reset statistics */
241 atomic_store_u64(&g_mmap_log.bytes_written, 0);
242 atomic_store_u64(&g_mmap_log.wrap_count, 0);
243
244 /* Install crash handlers */
246
247 g_mmap_log.initialized = true;
248
249 /* Write startup marker */
250 log_mmap_write(1 /* LOG_INFO */, NULL, 0, NULL, "=== Log started (mmap text mode, %zu bytes) ===", file_size);
251
252 return ASCIICHAT_OK;
253}
#define SET_ERRNO(code,...)
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:423
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
asciichat_error_t platform_mmap_open(const char *name, const char *path, size_t size, platform_mmap_t *out)
Memory-map a file for read/write access.
void platform_mmap_init(platform_mmap_t *mapping)
Initialize a platform_mmap_t structure.
#define LOG_MMAP_DEFAULT_SIZE
Default mmap log file size (4MB).
Definition log/mmap.h:46
void log_mmap_install_crash_handlers(void)
Install signal handlers for crash safety.
Definition mmap.c:147
size_t max_size
Definition log/mmap.h:58
const char * log_path
Definition log/mmap.h:57

References ASCIICHAT_OK, ERROR_INVALID_PARAM, log_info, LOG_MMAP_DEFAULT_SIZE, log_mmap_destroy(), log_mmap_install_crash_handlers(), log_mmap_write(), log_mmap_config::log_path, log_warn, log_mmap_config::max_size, platform_mmap_init(), platform_mmap_open(), SAFE_STRNCPY, and SET_ERRNO.

Referenced by log_mmap_init_simple().

◆ log_mmap_init_simple()

asciichat_error_t log_mmap_init_simple ( const char * log_path,
size_t max_size )

Initialize mmap logging with simple parameters.

Convenience wrapper around log_mmap_init().

Parameters
log_pathPath to log file
max_sizeMaximum file size (0 = default 4MB)
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 255 of file mmap.c.

255 {
256 log_mmap_config_t config = {
257 .log_path = log_path,
258 .max_size = max_size,
259 };
260 return log_mmap_init(&config);
261}
struct log_mmap_config log_mmap_config_t
Definition log/log.h:908

References log_mmap_init().

Referenced by log_enable_mmap_sized(), and log_init().

◆ log_mmap_install_crash_handlers()

void log_mmap_install_crash_handlers ( void )

Install signal handlers for crash safety.

Registers handlers for SIGSEGV, SIGABRT, etc. that sync the mmap before the process terminates.

Note
Signal handlers are automatically installed by log_mmap_init().

Definition at line 147 of file mmap.c.

147 {
148#ifndef _WIN32
149 struct sigaction sa = {0};
150 sa.sa_handler = crash_signal_handler;
151 sigemptyset(&sa.sa_mask);
152 sa.sa_flags = (int)SA_RESETHAND; /* One-shot */
153
154 sigaction(SIGSEGV, &sa, NULL);
155 sigaction(SIGABRT, &sa, NULL);
156 sigaction(SIGBUS, &sa, NULL);
157 sigaction(SIGFPE, &sa, NULL);
158 sigaction(SIGILL, &sa, NULL);
159#else
160 SetUnhandledExceptionFilter(windows_crash_handler);
161#endif
162}

Referenced by log_mmap_init().

◆ log_mmap_is_active()

bool log_mmap_is_active ( void )

Check if mmap logging is active.

Returns
true if mmap logging is initialized and active

Definition at line 393 of file mmap.c.

393 {
394 return g_mmap_log.initialized;
395}

Referenced by log_destroy(), log_disable_mmap(), log_file_msg(), log_msg(), and log_plain_msg().

◆ log_mmap_rotate()

void log_mmap_rotate ( void )

Rotate the mmap log (tail-keeping rotation).

Keeps the most recent log entries (tail) and discards old ones. This is the mmap equivalent of file-based log rotation.

Note
Caller must hold the rotation mutex from logging.c. This is called by maybe_rotate_log() which handles locking.

Definition at line 426 of file mmap.c.

426 {
427 if (!g_mmap_log.initialized || !g_mmap_log.text_region) {
428 return;
429 }
430
431 /* NOTE: Caller must hold the rotation mutex from logging.c */
432
433 uint64_t current_pos = atomic_load_u64(&g_mmap_log.write_pos);
434 size_t capacity = g_mmap_log.text_capacity;
435
436 /* Keep last 2/3 of the log (same ratio as file rotation) */
437 size_t keep_size = capacity * 2 / 3;
438 if (current_pos <= keep_size) {
439 return;
440 }
441
442 /* Find where to start keeping (skip to beginning of current_pos - keep_size) */
443 size_t skip_bytes = (size_t)current_pos - keep_size;
444 char *keep_start = g_mmap_log.text_region + skip_bytes;
445
446 /* Skip to next line boundary to avoid partial lines */
447 size_t skipped = 0;
448 while (skipped < keep_size && *keep_start != '\n') {
449 keep_start++;
450 skipped++;
451 }
452 if (skipped < keep_size && *keep_start == '\n') {
453 keep_start++;
454 skipped++;
455 }
456
457 size_t actual_keep = keep_size - skipped;
458 if (actual_keep == 0) {
459 /* Nothing to keep - just reset */
460 atomic_store_u64(&g_mmap_log.write_pos, 0);
461 memset(g_mmap_log.text_region, '\n', capacity);
462 return;
463 }
464
465 /* Move the tail to the beginning using memmove (handles overlap) */
466 memmove(g_mmap_log.text_region, keep_start, actual_keep);
467
468 /* Clear the rest with newlines (grep-friendly without needing -a flag) */
469 memset(g_mmap_log.text_region + actual_keep, '\n', capacity - actual_keep);
470
471 /* Update write position */
472 atomic_store_u64(&g_mmap_log.write_pos, actual_keep);
473
474 /* Write rotation marker */
475 const char *rotate_msg = "\n=== LOG ROTATED ===\n";
476 size_t rotate_len = strlen(rotate_msg);
477 if (actual_keep + rotate_len < capacity) {
478 memcpy(g_mmap_log.text_region + actual_keep, rotate_msg, rotate_len);
479 atomic_store_u64(&g_mmap_log.write_pos, actual_keep + rotate_len);
480 }
481
482 atomic_fetch_add_u64(&g_mmap_log.wrap_count, 1);
483
484 /* Sync after rotation */
485 platform_mmap_sync(&g_mmap_log.mmap, true);
486}

References platform_mmap_sync().

◆ log_mmap_sync()

void log_mmap_sync ( void )

Force sync the mmap'd file to disk.

Ensures all written data is flushed to the underlying file. Called automatically for ERROR/FATAL levels and on shutdown.

Definition at line 397 of file mmap.c.

397 {
398 if (g_mmap_log.initialized) {
399 platform_mmap_sync(&g_mmap_log.mmap, true);
400 }
401}

References platform_mmap_sync().

◆ log_mmap_write()

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).

Formats the log message and writes it directly as human-readable text. Uses atomic operations only, no mutex.

Parameters
levelLog level
fileSource file (can be NULL)
lineSource line
funcFunction name (can be NULL)
fmtFormat string
...Format arguments

Definition at line 315 of file mmap.c.

315 {
316 if (!g_mmap_log.initialized || !g_mmap_log.text_region) {
317 return;
318 }
319
320 static const char *level_names[] = {"DEV", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"};
321 const char *level_name = (level >= 0 && level < 6) ? level_names[level] : "???";
322
323 /* Format the complete log line into a local buffer first */
324 char line_buf[LOG_MMAP_MSG_BUFFER_SIZE];
325 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
326 format_timestamp(time_buf, sizeof(time_buf));
327
328 int prefix_len;
329 if (file && func) {
330 prefix_len =
331 safe_snprintf(line_buf, sizeof(line_buf), "[%s] [%s] %s:%d in %s(): ", time_buf, level_name, file, line, func);
332 } else {
333 prefix_len = safe_snprintf(line_buf, sizeof(line_buf), "[%s] [%s] ", time_buf, level_name);
334 }
335
336 if (prefix_len < 0) {
337 return;
338 }
339
340 /* Format the message */
341 va_list args;
342 va_start(args, fmt);
343 int msg_len = safe_vsnprintf(line_buf + prefix_len, sizeof(line_buf) - (size_t)prefix_len - 1, fmt, args);
344 va_end(args);
345
346 if (msg_len < 0) {
347 return;
348 }
349
350 /* Add newline */
351 size_t total_len = (size_t)prefix_len + (size_t)msg_len;
352 if (total_len >= sizeof(line_buf) - 1) {
353 total_len = sizeof(line_buf) - 2; /* Truncate if too long */
354 }
355 line_buf[total_len] = '\n';
356 total_len++;
357 line_buf[total_len] = '\0';
358
359 /* Strip ANSI escape codes from the log message before writing to file */
360 char *stripped = ansi_strip_escapes(line_buf, total_len);
361 const char *write_buf = stripped ? stripped : line_buf;
362 size_t write_len = stripped ? strlen(stripped) : total_len;
363
364 /* Atomically claim space in the mmap'd region */
365 uint64_t pos = atomic_fetch_add_u64(&g_mmap_log.write_pos, write_len);
366
367 /* Check if we exceeded capacity - drop this message if so */
368 /* Rotation is handled by maybe_rotate_log() called from logging.c */
369 if (pos + write_len > g_mmap_log.text_capacity) {
370 /* Undo our claim - we can't fit */
371 atomic_fetch_sub_u64(&g_mmap_log.write_pos, write_len);
372 if (stripped) {
373 SAFE_FREE(stripped);
374 }
375 return;
376 }
377
378 /* Copy formatted text to mmap'd region */
379 memcpy(g_mmap_log.text_region + pos, write_buf, write_len);
380
381 atomic_fetch_add_u64(&g_mmap_log.bytes_written, write_len);
382
383 if (stripped) {
384 SAFE_FREE(stripped);
385 }
386
387 /* Sync for ERROR/FATAL to ensure visibility on crash */
388 if (level >= 4 /* LOG_ERROR */) {
389 platform_mmap_sync(&g_mmap_log.mmap, false);
390 }
391}
action_args_t args
Definition actions.c:78
char * ansi_strip_escapes(const char *input, size_t input_len)
When code needs to work with rgb error, it keeps r, g, b here instead of scattering it across globals...
Definition ansi.c:27
#define SAFE_FREE(ptr)
Definition common.h:377
#define LOG_TIMESTAMP_BUFFER_SIZE
Maximum size of a timestamp string.
Definition log/log.h:101
#define LOG_MMAP_MSG_BUFFER_SIZE
Maximum size of a log message in mmap mode.
Definition log/log.h:95
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Platform-safe snprintf wrapper.
int safe_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list ap)
Platform-safe vsnprintf wrapper.

References ansi_strip_escapes(), args, LOG_MMAP_MSG_BUFFER_SIZE, LOG_TIMESTAMP_BUFFER_SIZE, platform_mmap_sync(), SAFE_FREE, safe_snprintf(), and safe_vsnprintf().

Referenced by log_file_msg(), log_mmap_destroy(), log_mmap_init(), log_msg(), and log_plain_msg().