ascii-chat 0.11.33
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
 Configuration for mmap logging. 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
 Configuration for mmap logging.
 

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:548
asciichat_error_t log_mmap_init(const log_mmap_config_t *config)
Initialize mmap-based text logging.
Definition mmap.c:191
void log_mmap_destroy(void)
Shutdown mmap logging.
Definition mmap.c:260

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.

Typedef Documentation

◆ log_mmap_config_t

Configuration for mmap logging.

Function Documentation

◆ log_mmap_destroy()

void log_mmap_destroy ( void  )

Shutdown mmap logging.

Syncs and unmaps the log file.

Definition at line 260 of file mmap.c.

260 {
261 if (!g_mmap_log.initialized) {
262 return;
263 }
264
265 /* Write shutdown marker */
266 log_mmap_write(1 /* LOG_INFO */, NULL, 0, NULL, "=== Log ended ===");
267
268 /* Sync to disk */
269 platform_mmap_sync(&g_mmap_log.mmap, true);
270
271 /* Truncate file to actual content size to save space
272 * This converts the large mmap file (4MB with newlines) to just the actual log content */
273 uint64_t final_pos = atomic_load_u64(&g_mmap_log.write_pos);
274 if (final_pos < g_mmap_log.text_capacity && strlen(g_mmap_log.file_path) > 0) {
275#ifdef _WIN32
276 /* Windows: Close mmap first, then truncate file, then reopen for truncation */
277 platform_mmap_close(&g_mmap_log.mmap);
278 HANDLE hFile =
279 CreateFileA(g_mmap_log.file_path, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
280 if (hFile != INVALID_HANDLE_VALUE) {
281 LARGE_INTEGER size;
282 size.QuadPart = (LONGLONG)final_pos;
283 if (SetFilePointerEx(hFile, size, NULL, FILE_BEGIN)) {
284 SetEndOfFile(hFile);
285 }
286 CloseHandle(hFile);
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);
289 }
290#else
291 /* POSIX: Use ftruncate() on the file descriptor */
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);
296 }
297 }
298 platform_mmap_close(&g_mmap_log.mmap);
299#endif
300 } else {
301 /* No truncation needed or path not set */
302 platform_mmap_close(&g_mmap_log.mmap);
303 }
304
305 g_mmap_log.text_region = NULL;
306 g_mmap_log.file_path[0] = '\0';
307
308 g_mmap_log.initialized = false;
309 log_debug("mmap log: destroyed");
310}
uint64_t atomic_load_u64(atomic_t *a)
Atomically load a uint64_t value.
Definition atomic.c:233
unsigned long long uint64_t
Definition common.h:59
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:312

References atomic_load_u64(), 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 400 of file mmap.c.

400 {
401 if (bytes_written) {
402 *bytes_written = atomic_load_u64(&g_mmap_log.bytes_written);
403 }
404 if (wrap_count) {
405 *wrap_count = atomic_load_u64(&g_mmap_log.wrap_count);
406 }
407}
atomic_t bytes_written
Definition mmap.c:42
atomic_t wrap_count
Definition mmap.c:43

References atomic_load_u64(), 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 409 of file mmap.c.

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

References atomic_load_u64().

◆ 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 191 of file mmap.c.

191 {
192 if (!config || !config->log_path) {
193 return SET_ERRNO(ERROR_INVALID_PARAM, "mmap log: config or log_path is NULL");
194 }
195
196 if (g_mmap_log.initialized) {
197 log_warn("mmap log: already initialized, destroying first");
199 }
200
201 /* Determine file size */
202 size_t file_size = config->max_size > 0 ? config->max_size : LOG_MMAP_DEFAULT_SIZE;
203 if (file_size < 1024) {
204 file_size = 1024; /* Minimum reasonable size */
205 }
206
207 /* Store file path for later truncation */
208 SAFE_STRNCPY(g_mmap_log.file_path, config->log_path, sizeof(g_mmap_log.file_path) - 1);
209
210 /* Open mmap file */
211 platform_mmap_init(&g_mmap_log.mmap);
212 asciichat_error_t result = platform_mmap_open("mmap_log_file", config->log_path, file_size, &g_mmap_log.mmap);
213 if (result != ASCIICHAT_OK) {
214 return result;
215 }
216
217 /* Entire file is text - no header */
218 g_mmap_log.text_region = (char *)g_mmap_log.mmap.addr;
219 g_mmap_log.text_capacity = file_size;
220
221 /* Find where existing content ends (scan for last newline before spaces/nulls) */
222 size_t existing_pos = find_content_end(g_mmap_log.text_region, file_size);
223 atomic_store_u64(&g_mmap_log.write_pos, existing_pos);
224
225 /* Clear unused portion with newlines (grep-friendly without needing -a flag)
226 * We truncate the file on clean shutdown to save space */
227 if (existing_pos < file_size) {
228 memset(g_mmap_log.text_region + existing_pos, '\n', file_size - existing_pos);
229 }
230
231 if (existing_pos > 0) {
232 log_info("mmap log: resumed existing log at position %zu", existing_pos);
233 } else {
234 log_info("mmap log: created new log file %s (%zu bytes)", config->log_path, file_size);
235 }
236
237 /* Reset statistics */
238 atomic_store_u64(&g_mmap_log.bytes_written, 0);
239 atomic_store_u64(&g_mmap_log.wrap_count, 0);
240
241 /* Install crash handlers */
243
244 g_mmap_log.initialized = true;
245
246 /* Write startup marker */
247 log_mmap_write(1 /* LOG_INFO */, NULL, 0, NULL, "=== Log started (mmap text mode, %zu bytes) ===", file_size);
248
249 return ASCIICHAT_OK;
250}
void atomic_store_u64(atomic_t *a, uint64_t value)
Atomically store a uint64_t value.
Definition atomic.c:241
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
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:144
size_t max_size
Definition log/mmap.h:53
const char * log_path
Definition log/mmap.h:52

References ASCIICHAT_OK, atomic_store_u64(), 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 252 of file mmap.c.

252 {
253 log_mmap_config_t config = {
254 .log_path = log_path,
255 .max_size = max_size,
256 };
257 return log_mmap_init(&config);
258}
Configuration for mmap logging.
Definition log/mmap.h:51

References ASCIICHAT_OK, log_mmap_init(), and log_mmap_config::log_path.

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 144 of file mmap.c.

144 {
145#ifndef _WIN32
146 struct sigaction sa = {0};
147 sa.sa_handler = crash_signal_handler;
148 sigemptyset(&sa.sa_mask);
149 sa.sa_flags = (int)SA_RESETHAND; /* One-shot */
150
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);
156#else
157 SetUnhandledExceptionFilter(windows_crash_handler);
158#endif
159}

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 390 of file mmap.c.

390 {
391 return g_mmap_log.initialized;
392}

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 423 of file mmap.c.

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

References atomic_fetch_add_u64(), atomic_load_u64(), atomic_store_u64(), and 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 394 of file mmap.c.

394 {
395 if (g_mmap_log.initialized) {
396 platform_mmap_sync(&g_mmap_log.mmap, true);
397 }
398}

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 312 of file mmap.c.

312 {
313 if (!g_mmap_log.initialized || !g_mmap_log.text_region) {
314 return;
315 }
316
317 static const char *level_names[] = {"DEV", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"};
318 const char *level_name = (level >= 0 && level < 6) ? level_names[level] : "???";
319
320 /* Format the complete log line into a local buffer first */
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));
324
325 int prefix_len;
326 if (file && func) {
327 prefix_len =
328 safe_snprintf(line_buf, sizeof(line_buf), "[%s] [%s] %s:%d in %s(): ", time_buf, level_name, file, line, func);
329 } else {
330 prefix_len = safe_snprintf(line_buf, sizeof(line_buf), "[%s] [%s] ", time_buf, level_name);
331 }
332
333 if (prefix_len < 0) {
334 return;
335 }
336
337 /* Format the message */
338 va_list args;
339 va_start(args, fmt);
340 int msg_len = safe_vsnprintf(line_buf + prefix_len, sizeof(line_buf) - (size_t)prefix_len - 1, fmt, args);
341 va_end(args);
342
343 if (msg_len < 0) {
344 return;
345 }
346
347 /* Add newline */
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; /* Truncate if too long */
351 }
352 line_buf[total_len] = '\n';
353 total_len++;
354 line_buf[total_len] = '\0';
355
356 /* Strip ANSI escape codes from the log message before writing to file */
357 char *stripped = ansi_strip_escapes(line_buf, total_len);
358 const char *write_buf = stripped ? stripped : line_buf;
359 size_t write_len = stripped ? strlen(stripped) : total_len;
360
361 /* Atomically claim space in the mmap'd region */
362 uint64_t pos = atomic_fetch_add_u64(&g_mmap_log.write_pos, write_len);
363
364 /* Check if we exceeded capacity - drop this message if so */
365 /* Rotation is handled by maybe_rotate_log() called from logging.c */
366 if (pos + write_len > g_mmap_log.text_capacity) {
367 /* Undo our claim - we can't fit */
368 atomic_fetch_sub_u64(&g_mmap_log.write_pos, write_len);
369 if (stripped) {
370 SAFE_FREE(stripped);
371 }
372 return;
373 }
374
375 /* Copy formatted text to mmap'd region */
376 memcpy(g_mmap_log.text_region + pos, write_buf, write_len);
377
378 atomic_fetch_add_u64(&g_mmap_log.bytes_written, write_len);
379
380 if (stripped) {
381 SAFE_FREE(stripped);
382 }
383
384 /* Sync for ERROR/FATAL to ensure visibility on crash */
385 if (level >= 4 /* LOG_ERROR */) {
386 platform_mmap_sync(&g_mmap_log.mmap, false);
387 }
388}
uint64_t atomic_fetch_sub_u64(atomic_t *a, uint64_t delta)
Atomically subtract from a uint64_t and return the previous value.
Definition atomic.c:256
char * ansi_strip_escapes(const char *input, size_t input_len)
Strip all ANSI escape sequences from a string.
Definition ansi.c:23
#define SAFE_FREE(ptr)
Definition common.h:376
#define LOG_TIMESTAMP_BUFFER_SIZE
Maximum size of a timestamp string.
Definition log/log.h:97
#define LOG_MMAP_MSG_BUFFER_SIZE
Maximum size of a log message in mmap mode.
Definition log/log.h:91
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
int safe_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list ap)
Safe formatted string printing with va_list.
Definition system.c:199
action_args_t args

References ansi_strip_escapes(), args, atomic_fetch_add_u64(), atomic_fetch_sub_u64(), 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().