ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
log/logging.c
Go to the documentation of this file.
1
7#include "common.h"
8#include "options/options.h"
9#include "options/rcu.h" // For RCU-based options access
11#include "platform/system.h"
12#include "util/path.h"
13#include <stdarg.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h>
17#include <time.h>
18#include <fcntl.h>
19#include <sys/stat.h>
20
21#include "logging.h"
22#include "log/mmap.h"
23#include "platform/terminal.h"
24#include "platform/thread.h"
25#include "platform/mutex.h"
26#include "network/packet.h"
27
28/* ============================================================================
29 * Logging System Internal State
30 * ============================================================================ */
31
32/* Log context struct - Lock-free logging with mutex-protected rotation
33 *
34 * Design: Logging itself is lock-free using atomic operations and atomic write() syscalls.
35 * Only log rotation uses a mutex, since it involves multiple file operations that must
36 * be atomic as a group (close, read tail, write temp, rename, reopen).
37 */
38#pragma GCC diagnostic push
39#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
40static struct log_context_t {
41 _Atomic int file; /* File descriptor (atomic for safe access) */
42 _Atomic int level; /* Log level as int for atomic ops */
43 _Atomic bool initialized; /* Initialization flag */
44 char filename[LOG_MSG_BUFFER_SIZE]; /* Store filename (set once at init) */
45 _Atomic size_t current_size; /* Track current file size */
46 _Atomic bool terminal_output_enabled; /* Control stderr output to terminal */
47 _Atomic bool level_manually_set; /* Track if level was set manually */
48 _Atomic bool force_stderr; /* Force all terminal logs to stderr (client mode) */
49 _Atomic bool terminal_locked; /* True when a thread has exclusive terminal access */
50 _Atomic uint64_t terminal_owner_thread; /* Thread that owns terminal output (stored as uint64) */
51 _Atomic unsigned int flush_delay_ms; /* Delay between each buffered log flush (0 = disabled) */
52 mutex_t rotation_mutex; /* Mutex for log rotation only (not for logging!) */
53 _Atomic bool rotation_mutex_initialized; /* Track if rotation mutex is ready */
54} g_log = {
55 .file = 2, /* STDERR_FILENO - fd 0 is STDIN (read-only!) */
56 .level = DEFAULT_LOG_LEVEL,
57 .initialized = false,
58 .filename = {0},
59 .current_size = 0,
60 .terminal_output_enabled = true,
61 .level_manually_set = false,
62 .force_stderr = false,
63 .terminal_locked = false,
64 .terminal_owner_thread = 0,
65 .flush_delay_ms = 0,
66 .rotation_mutex_initialized = false,
67};
68#pragma GCC diagnostic pop
69
70/* Level strings for log output */
71static const char *level_strings[] = {"DEV", "DEBUG", "INFO", "WARN", "ERROR", "FATAL"};
72
73/* Internal color mode enum for indexing into 2D color array */
74/* NOTE: Uses LOG_CMODE_ prefix to avoid conflict with public terminal_color_mode_t in options.h */
76
77#define LOG_COLOR_COUNT 7 /* DEV, DEBUG, INFO, WARN, ERROR, FATAL, RESET */
78
79/* 2D color array: [color_mode][log_color] */
80static const char *level_colors[LOG_CMODE_COUNT][LOG_COLOR_COUNT] = {
81 /* LOG_CMODE_16 */
82 {"\x1b[34m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m", "\x1b[0m"},
83 /* LOG_CMODE_256 */
84 {"\x1b[94m", "\x1b[96m", "\x1b[92m", "\x1b[33m", "\x1b[31m", "\x1b[35m", "\x1b[0m"},
85 /* LOG_CMODE_TRUECOLOR */
86 {"\x1b[38;2;150;100;205m", "\x1b[38;2;30;205;255m", "\x1b[38;2;50;205;100m", "\x1b[38;2;205;185;40m",
87 "\x1b[38;2;205;30;100m", "\x1b[38;2;205;80;205m", "\x1b[0m"},
88};
89
90/* Internal error macro - uses g_log directly, only used in this file */
91#ifdef NDEBUG
92#define LOGGING_INTERNAL_ERROR(error, message, ...) \
93 do { \
94 asciichat_set_errno_with_message(error, NULL, 0, NULL, message, ##__VA_ARGS__); \
95 static const char *msg_header = "CRITICAL LOGGING SYSTEM ERROR: "; \
96 safe_fprintf(stderr, "%s%s%s: %s", log_level_color(LOG_COLOR_ERROR), msg_header, log_level_color(LOG_COLOR_RESET), \
97 message); \
98 platform_write(g_log.file, msg_header, strlen(msg_header)); \
99 platform_write(g_log.file, message, strlen(message)); \
100 platform_write(g_log.file, "\n", 1); \
101 platform_print_backtrace(0); \
102 } while (0)
103#else
104#define LOGGING_INTERNAL_ERROR(error, message, ...) \
105 do { \
106 asciichat_set_errno_with_message(error, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__); \
107 static const char *msg_header = "CRITICAL LOGGING SYSTEM ERROR: "; \
108 safe_fprintf(stderr, "%s%s%s: %s", log_level_color(LOG_COLOR_ERROR), msg_header, log_level_color(LOG_COLOR_RESET), \
109 message); \
110 platform_write(g_log.file, msg_header, strlen(msg_header)); \
111 platform_write(g_log.file, message, strlen(message)); \
112 platform_write(g_log.file, "\n", 1); \
113 platform_print_backtrace(0); \
114 } while (0)
115#endif
116
117/* Terminal capabilities cache */
118static terminal_capabilities_t g_terminal_caps = {0};
119static bool g_terminal_caps_initialized = false;
120static bool g_terminal_caps_detecting = false; /* Guard against recursion */
121
122/* Shutdown logging state */
123static bool g_shutdown_saved_terminal_output = true; /* Saved state for log_shutdown_begin/end */
124static bool g_shutdown_in_progress = false; /* Track if shutdown phase is active */
125
126size_t get_current_time_formatted(char *time_buf) {
127 /* Log the rotation event */
128 struct timespec ts;
129 (void)clock_gettime(CLOCK_REALTIME, &ts);
130 struct tm tm_info;
131 platform_localtime(&ts.tv_sec, &tm_info);
132
133 // Format the time part first
134 // strftime returns 0 on error, not negative (and len is size_t/unsigned)
135 size_t len = strftime(time_buf, 32, "%H:%M:%S", &tm_info);
136 if (len == 0 || len >= 32) {
137 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format time");
138 return 0;
139 }
140
141 // Add microseconds manually
142 long microseconds = ts.tv_nsec / 1000;
143 if (microseconds < 0)
144 microseconds = 0;
145 if (microseconds > 999999)
146 microseconds = 999999;
147
148 int result = snprintf(time_buf + len, 32 - len, ".%06ld", microseconds);
149 if (result < 0 || result >= (int)(32 - len)) {
150 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format microseconds");
151 return 0;
152 }
153
154 return len + (size_t)result;
155}
156
157char *format_message(const char *format, va_list args) {
158 if (!format) {
159 return NULL;
160 }
161
162 // First, determine the size needed
163 va_list args_copy;
164 va_copy(args_copy, args);
165 int size = vsnprintf(NULL, 0, format, args_copy);
166 va_end(args_copy);
167
168 if (size < 0) {
169 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format context message");
170 return NULL;
171 }
172
173 // Allocate and format the message
174 char *message = SAFE_MALLOC(size + 1, char *);
175 int result = vsnprintf(message, (size_t)size + 1, format, args);
176 if (result < 0) {
177 SAFE_FREE(message);
178 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format context message");
179 return NULL;
180 }
181
182 return message;
183}
184
185/* ============================================================================
186 * Logging Implementation
187 * ============================================================================
188 */
189
190/* Parse LOG_LEVEL environment variable */
191static log_level_t parse_log_level_from_env(void) {
192 const char *env_level = SAFE_GETENV("LOG_LEVEL");
193 if (!env_level) {
194 return DEFAULT_LOG_LEVEL; // Default level based on build type
195 }
196
197 // Case-insensitive comparison
198 if (platform_strcasecmp(env_level, "DEV") == 0 || strcmp(env_level, "0") == 0) {
199 return LOG_DEV;
200 }
201 if (platform_strcasecmp(env_level, "DEBUG") == 0 || strcmp(env_level, "1") == 0) {
202 return LOG_DEBUG;
203 }
204 if (platform_strcasecmp(env_level, "INFO") == 0 || strcmp(env_level, "2") == 0) {
205 return LOG_INFO;
206 }
207 if (platform_strcasecmp(env_level, "WARN") == 0 || strcmp(env_level, "3") == 0) {
208 return LOG_WARN;
209 }
210 if (platform_strcasecmp(env_level, "ERROR") == 0 || strcmp(env_level, "4") == 0) {
211 return LOG_ERROR;
212 }
213 if (platform_strcasecmp(env_level, "FATAL") == 0 || strcmp(env_level, "5") == 0) {
214 return LOG_FATAL;
215 }
216
217 // Invalid value - return default
218 log_warn("Invalid LOG_LEVEL: %s", env_level);
219 return DEFAULT_LOG_LEVEL;
220}
221
222/* Log rotation function - keeps the tail (recent entries)
223 * REQUIRES: rotation_mutex must be held by caller
224 * This is the only operation that uses a mutex - regular logging is lock-free.
225 */
226static void rotate_log_locked(void) {
227 int file = atomic_load(&g_log.file);
228 size_t current_size = atomic_load(&g_log.current_size);
229
230 if (file < 0 || file == STDERR_FILENO || strlen(g_log.filename) == 0) {
231 return;
232 }
233
234 if (current_size < MAX_LOG_SIZE) {
235 return;
236 }
237
238 platform_close(file);
239 atomic_store(&g_log.file, -1);
240
241 /* Open file for reading to get the tail */
242 int read_file = platform_open(g_log.filename, O_RDONLY, 0);
243 if (read_file < 0) {
244 safe_fprintf(stderr, "Failed to open log file for tail rotation: %s\n", g_log.filename);
245 /* Fall back to regular truncation */
246 int fd = platform_open(g_log.filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
247 atomic_store(&g_log.file, fd);
248 atomic_store(&g_log.current_size, 0);
249 return;
250 }
251
252 /* Seek to position where we want to start keeping data (keep last 2MB) */
253 size_t keep_size = MAX_LOG_SIZE * 2 / 3; /* Keep last 2MB of 3MB file */
254 if (current_size < keep_size) {
255 platform_close(read_file);
256 /* Fall back to truncation since we don't have enough data to rotate */
257 int fd = platform_open(g_log.filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
258 atomic_store(&g_log.file, fd);
259 atomic_store(&g_log.current_size, 0);
260 return;
261 }
262 if (lseek(read_file, (off_t)(current_size - keep_size), SEEK_SET) == (off_t)-1) {
263 platform_close(read_file);
264 /* Fall back to truncation */
265 int fd = platform_open(g_log.filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
266 atomic_store(&g_log.file, fd);
267 atomic_store(&g_log.current_size, 0);
268 return;
269 }
270
271 /* Skip to next line boundary to avoid partial lines */
272 char c;
273 while (platform_read(read_file, &c, 1) > 0 && c != '\n') {
274 /* Skip characters until newline */
275 }
276
277 /* Read the tail into a temporary file */
278 char temp_filename[PLATFORM_MAX_PATH_LENGTH];
279 int result = snprintf(temp_filename, sizeof(temp_filename), "%s.tmp", g_log.filename);
280 if (result <= 0 || result >= (int)sizeof(temp_filename)) {
281 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format temp filename");
282 platform_close(read_file);
283 int fd = platform_open(g_log.filename, O_CREAT | O_RDWR | O_APPEND, FILE_PERM_PRIVATE);
284 atomic_store(&g_log.file, fd);
285 return;
286 }
287
288 int temp_file = platform_open(temp_filename, O_CREAT | O_WRONLY | O_TRUNC, FILE_PERM_PRIVATE);
289 if (temp_file < 0) {
290 platform_close(read_file);
291 /* Fall back to truncation */
292 int fd = platform_open(g_log.filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
293 atomic_store(&g_log.file, fd);
294 atomic_store(&g_log.current_size, 0);
295 return;
296 }
297
298 /* Copy tail to temp file */
299 char buffer[8192];
300 ssize_t bytes_read;
301 size_t new_size = 0;
302 while ((bytes_read = platform_read(read_file, buffer, sizeof(buffer))) > 0) {
303 ssize_t written = platform_write(temp_file, buffer, (size_t)bytes_read);
304 if (written != bytes_read) {
305 platform_close(read_file);
306 platform_close(temp_file);
307 unlink(temp_filename);
308 /* Fall back to truncation */
309 int fd = platform_open(g_log.filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
310 atomic_store(&g_log.file, fd);
311 atomic_store(&g_log.current_size, 0);
312 return;
313 }
314 new_size += (size_t)bytes_read;
315 }
316
317 platform_close(read_file);
318 platform_close(temp_file);
319
320 /* Replace original with temp file */
321 if (rename(temp_filename, g_log.filename) != 0) {
322 unlink(temp_filename); /* Clean up temp file */
323 /* Fall back to truncation */
324 int fd = platform_open(g_log.filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
325 atomic_store(&g_log.file, fd);
326 atomic_store(&g_log.current_size, 0);
327 return;
328 }
329
330 /* Reopen for appending */
331 int new_fd = platform_open(g_log.filename, O_CREAT | O_RDWR | O_APPEND, FILE_PERM_PRIVATE);
332 if (new_fd < 0) {
333 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to reopen rotated log file: %s", g_log.filename);
334 atomic_store(&g_log.file, STDERR_FILENO);
335 g_log.filename[0] = '\0';
336 atomic_store(&g_log.current_size, 0);
337 return;
338 }
339 atomic_store(&g_log.file, new_fd);
340 atomic_store(&g_log.current_size, new_size);
341
342 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
344
345 char log_msg[256];
346 int log_msg_len =
347 safe_snprintf(log_msg, sizeof(log_msg), "[%s] [INFO] Log tail-rotated (kept %zu bytes)\n", time_buf, new_size);
348 if (log_msg_len > 0 && log_msg_len < (int)sizeof(log_msg)) {
349 (void)platform_write(new_fd, log_msg, (size_t)log_msg_len);
350 }
351}
352
353/* Check if rotation is needed and perform it (acquires mutex only if needed) */
354static void maybe_rotate_log(void) {
355 /* Rotation mutex not initialized - skip rotation */
356 if (!atomic_load(&g_log.rotation_mutex_initialized)) {
357 return;
358 }
359
360 /* Check mmap path first */
361 if (log_mmap_is_active()) {
362 size_t used = 0, capacity = 0;
363 if (log_mmap_get_usage(&used, &capacity) && capacity > 0) {
364 /* Rotate when 90% full to leave room for writes */
365 if (used > capacity * 9 / 10) {
366 mutex_lock(&g_log.rotation_mutex);
368 mutex_unlock(&g_log.rotation_mutex);
369 }
370 }
371 return;
372 }
373
374 /* File path: quick atomic check - avoid mutex if not needed */
375 size_t current_size = atomic_load(&g_log.current_size);
376 if (current_size < MAX_LOG_SIZE) {
377 return;
378 }
379
380 /* Size exceeded - acquire mutex and rotate */
381 mutex_lock(&g_log.rotation_mutex);
382 rotate_log_locked();
383 mutex_unlock(&g_log.rotation_mutex);
384}
385
386void log_init(const char *filename, log_level_t level, bool force_stderr, bool use_mmap) {
387 // Initialize rotation mutex (only operation that uses a mutex)
388 if (!atomic_load(&g_log.rotation_mutex_initialized)) {
389 mutex_init(&g_log.rotation_mutex);
390 atomic_store(&g_log.rotation_mutex_initialized, true);
391 }
392
393 // Set basic config using atomic stores
394 atomic_store(&g_log.force_stderr, force_stderr);
395 bool preserve_terminal_output = atomic_load(&g_log.terminal_output_enabled);
396
397 // Close any existing file (atomic load/store)
398 int old_file = atomic_load(&g_log.file);
399 if (atomic_load(&g_log.initialized) && old_file >= 0 && old_file != STDERR_FILENO) {
400 platform_close(old_file);
401 atomic_store(&g_log.file, -1);
402 }
403
404 // Check LOG_LEVEL environment variable
405 const char *env_level_str = SAFE_GETENV("LOG_LEVEL");
406 if (env_level_str) {
407 atomic_store(&g_log.level, (int)parse_log_level_from_env());
408 } else {
409 atomic_store(&g_log.level, (int)level);
410 }
411
412 atomic_store(&g_log.level_manually_set, false);
413 atomic_store(&g_log.current_size, 0);
414
415 if (filename) {
416 SAFE_STRNCPY(g_log.filename, filename, sizeof(g_log.filename) - 1);
417
418 if (use_mmap) {
419 // Lock-free mmap path - writes go to mmap'd file
420 asciichat_error_t mmap_result = log_mmap_init_simple(filename, 0);
421 if (mmap_result == ASCIICHAT_OK) {
422 atomic_store(&g_log.file, -1); // No regular fd - using mmap for file output
423 } else {
424 // Mmap failed - use stderr only (atomic writes, lock-free)
425 if (preserve_terminal_output) {
426 safe_fprintf(stderr, "Mmap logging failed for %s, using stderr only (lock-free)\n", filename);
427 }
428 atomic_store(&g_log.file, STDERR_FILENO);
429 g_log.filename[0] = '\0';
430 }
431 } else {
432 // Lock-free file I/O path - uses atomic write() syscalls
433 int fd = platform_open(filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
434 atomic_store(&g_log.file, (fd >= 0) ? fd : STDERR_FILENO);
435 if (fd < 0) {
436 if (preserve_terminal_output) {
437 safe_fprintf(stderr, "Failed to open log file: %s\n", filename);
438 }
439 g_log.filename[0] = '\0';
440 }
441 }
442 } else {
443 atomic_store(&g_log.file, STDERR_FILENO);
444 g_log.filename[0] = '\0';
445 }
446
447 atomic_store(&g_log.initialized, true);
448 atomic_store(&g_log.terminal_output_enabled, preserve_terminal_output);
449
450 // Reset terminal detection if needed
451 if (g_terminal_caps_initialized && !g_terminal_caps.detection_reliable) {
452 g_terminal_caps_initialized = false;
453 }
454
455 // Detect terminal capabilities
457}
458
459void log_destroy(void) {
460 // Destroy mmap logging first (if active)
461 if (log_mmap_is_active()) {
463 }
464
465 // Lock-free cleanup using atomic operations
466 int old_file = atomic_load(&g_log.file);
467 if (old_file >= 0 && old_file != STDERR_FILENO) {
468 platform_close(old_file);
469 }
470 atomic_store(&g_log.file, -1);
471 atomic_store(&g_log.initialized, false);
472
473 // Destroy rotation mutex
474 if (atomic_load(&g_log.rotation_mutex_initialized)) {
475 mutex_destroy(&g_log.rotation_mutex);
476 atomic_store(&g_log.rotation_mutex_initialized, false);
477 }
478}
479
481 atomic_store(&g_log.level, (int)level);
482 atomic_store(&g_log.level_manually_set, true);
483}
484
486 return (log_level_t)atomic_load(&g_log.level);
487}
488
489void log_set_terminal_output(bool enabled) {
490 // Respect --quiet flag: if quiet is set, never enable terminal output
491 const options_t *opts = options_get();
492 if (!opts) {
493 log_error("Options not initialized");
494 return;
495 }
496 if (enabled && opts && opts->quiet) {
497 return; // Silently ignore attempts to enable terminal output when --quiet is set
498 }
499 atomic_store(&g_log.terminal_output_enabled, enabled);
500}
501
503 return atomic_load(&g_log.terminal_output_enabled);
504}
505
506void log_set_force_stderr(bool enabled) {
507 atomic_store(&g_log.force_stderr, enabled);
508}
509
511 return atomic_load(&g_log.force_stderr);
512}
513
515 bool previous_state = atomic_exchange(&g_log.terminal_locked, true);
516 atomic_store(&g_log.terminal_owner_thread, (uint64_t)asciichat_thread_self());
517 return previous_state;
518}
519
520void log_unlock_terminal(bool previous_state) {
521 atomic_store(&g_log.terminal_locked, previous_state);
522 if (!previous_state) {
523 atomic_store(&g_log.terminal_owner_thread, 0);
524 }
525}
526
527void log_set_flush_delay(unsigned int delay_ms) {
528 atomic_store(&g_log.flush_delay_ms, delay_ms);
529}
530
532 // Log rotation is inherently racy without locks - best-effort only
533 // For reliable rotation, use mmap mode or external logrotate
534 int file = atomic_load(&g_log.file);
535 if (file >= 0 && file != STDERR_FILENO && strlen(g_log.filename) > 0) {
536 struct stat st;
537 if (fstat(file, &st) == 0 && st.st_size > MAX_LOG_SIZE) {
538 atomic_store(&g_log.current_size, (size_t)st.st_size);
539 }
540 }
541}
542
543/* Helper: Write formatted log entry to file using atomic write() syscall
544 * POSIX guarantees write() is atomic for sizes <= PIPE_BUF (typically 4096 bytes)
545 */
546static void write_to_log_file_atomic(const char *buffer, int length) {
547 if (length <= 0 || buffer == NULL) {
548 return;
549 }
550
551 if (length > MAX_LOG_SIZE) {
552 return;
553 }
554
555 int file = atomic_load(&g_log.file);
556 if (file < 0 || file == STDERR_FILENO) {
557 return;
558 }
559
560 // Single atomic write() call - no locking needed
561 ssize_t written = platform_write(file, buffer, (size_t)length);
562 if (written > 0) {
563 atomic_fetch_add(&g_log.current_size, (size_t)written);
564 maybe_rotate_log();
565 }
566}
567
568/* Helper: Format log message header (timestamp, level, location info)
569 * Returns the number of characters written to the buffer
570 */
571static int format_log_header(char *buffer, size_t buffer_size, log_level_t level, const char *timestamp,
572 const char *file, int line, const char *func, bool use_colors, bool newline) {
573 const char **colors = use_colors ? log_get_color_array() : NULL;
574 // Safety check: if colors is NULL, disable colors to prevent crashes
575 if (use_colors && colors == NULL) {
576 use_colors = false;
577 }
578 const char *color = use_colors ? colors[level] : "";
579 const char *reset = use_colors ? colors[LOG_COLOR_RESET] : "";
580
581 const char *level_string = level_strings[level];
582 if (level_string == level_strings[LOG_INFO]) {
583 level_string = "INFO ";
584 } else if (level_string == level_strings[LOG_WARN]) {
585 level_string = "WARN ";
586 } else if (level_string == level_strings[LOG_DEV]) {
587 level_string = "DEV ";
588 }
589
590 const char *newline_or_not = newline ? "\n" : "";
591
592 int result = 0;
593
594#ifdef NDEBUG
595 (void)newline_or_not;
596 (void)file;
597 (void)line;
598 (void)func;
599
600 // Release mode: Simple one-line format without file/line/function
601 if (use_colors) {
602 result = snprintf(buffer, buffer_size, "[%s%s%s] [%s%s%s] ", color, timestamp, reset, color, level_string, reset);
603 } else {
604 result = snprintf(buffer, buffer_size, "[%s] [%s] ", timestamp, level_strings[level]);
605 }
606#else
607 // Debug mode: full format with file location and function
608 const char *rel_file = extract_project_relative_path(file);
609 if (use_colors) {
610 // Use specific colors for file/function info: file=yellow, line=magenta, function=blue
611 // Array indices: 0=DEV(Blue), 1=DEBUG(Cyan), 2=INFO(Green), 3=WARN(Yellow), 4=ERROR(Red), 5=FATAL(Magenta)
612 const char *file_color = colors[3]; // WARN: Yellow for file paths
613 const char *line_color = colors[5]; // FATAL: Magenta for line numbers
614 const char *func_color = colors[0]; // DEV: Blue for function names
615 result = snprintf(buffer, buffer_size, "[%s%s%s] [%s%s%s] %s%s%s:%s%d%s in %s%s%s(): %s%s", color, timestamp, reset,
616 color, level_string, reset, file_color, rel_file, reset, line_color, line, reset, func_color,
617 func, reset, reset, newline_or_not);
618 } else {
619 result = snprintf(buffer, buffer_size, "[%s] [%s] %s:%d in %s(): %s", timestamp, level_strings[level], rel_file,
620 line, func, newline_or_not);
621 }
622#endif
623
624 if (result <= 0 || result >= (int)buffer_size) {
625 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format log header");
626 return -1;
627 }
628
629 return result;
630}
631
632/* Note: Terminal buffering removed for lock-free design.
633 * When terminal is locked, other threads' output goes to file only.
634 */
635
636/* Helper: Write colored log entry to terminal (lock-free)
637 * Uses atomic loads for state checks, skips output if terminal is locked by another thread
638 */
639static void write_to_terminal_atomic(log_level_t level, const char *timestamp, const char *file, int line,
640 const char *func, const char *fmt, va_list args) {
641 // Choose output stream: errors/warnings to stderr, info/debug to stdout
642 // When force_stderr is enabled (client mode), ALL logs go to stderr to keep stdout clean
643 FILE *output_stream;
644 if (atomic_load(&g_log.force_stderr)) {
645 output_stream = stderr;
646 } else {
647 output_stream = (level == LOG_ERROR || level == LOG_WARN || level == LOG_FATAL) ? stderr : stdout;
648 }
649 int fd = output_stream == stderr ? STDERR_FILENO : STDOUT_FILENO;
650
651 // Format the header using centralized formatting
652 char header_buffer[512];
653 bool use_colors = isatty(fd);
654
655 int header_len =
656 format_log_header(header_buffer, sizeof(header_buffer), level, timestamp, file, line, func, use_colors, false);
657 if (header_len <= 0 || header_len >= (int)sizeof(header_buffer)) {
658 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format log header");
659 return;
660 }
661
662 // Check if terminal output is enabled (atomic load)
663 if (!atomic_load(&g_log.terminal_output_enabled)) {
664 return; // Terminal output disabled - skip (no buffering in lock-free mode)
665 }
666
667 // Check if terminal is locked by another thread
668 if (atomic_load(&g_log.terminal_locked)) {
669 uint64_t owner = atomic_load(&g_log.terminal_owner_thread);
670 if (owner != (uint64_t)asciichat_thread_self()) {
671 return; // Terminal locked by another thread - skip
672 }
673 }
674
675 if (use_colors) {
676 const char **colors = log_get_color_array();
677 if (colors == NULL) {
678 safe_fprintf(output_stream, "%s", header_buffer);
679 (void)vfprintf(output_stream, fmt, args);
680 safe_fprintf(output_stream, "\n");
681 } else {
682 safe_fprintf(output_stream, "%s", header_buffer);
683 safe_fprintf(output_stream, "%s", colors[LOG_COLOR_RESET]);
684 (void)vfprintf(output_stream, fmt, args);
685 safe_fprintf(output_stream, "%s\n", colors[LOG_COLOR_RESET]);
686 }
687 } else {
688 safe_fprintf(output_stream, "%s", header_buffer);
689 (void)vfprintf(output_stream, fmt, args);
690 safe_fprintf(output_stream, "\n");
691 }
692 (void)fflush(output_stream);
693}
694
695void log_msg(log_level_t level, const char *file, int line, const char *func, const char *fmt, ...) {
696 // All state access uses atomic operations - fully lock-free
697 if (!atomic_load(&g_log.initialized)) {
698 return;
699 }
700
701 if (level < (log_level_t)atomic_load(&g_log.level)) {
702 return;
703 }
704
705 /* =========================================================================
706 * MMAP PATH: When mmap logging is active, writes go to mmap'd file
707 * ========================================================================= */
708 if (log_mmap_is_active()) {
709 maybe_rotate_log();
710
711 va_list args;
712 va_start(args, fmt);
713 char msg_buffer[LOG_MMAP_MSG_BUFFER_SIZE];
714 vsnprintf(msg_buffer, sizeof(msg_buffer), fmt, args);
715 va_end(args);
716
717 log_mmap_write(level, file, line, func, "%s", msg_buffer);
718
719 // Terminal output (check with atomic loads)
720 if (atomic_load(&g_log.terminal_output_enabled) && !atomic_load(&g_log.terminal_locked)) {
721 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
723
724 FILE *output_stream;
725 if (atomic_load(&g_log.force_stderr)) {
726 output_stream = stderr;
727 } else {
728 output_stream = (level == LOG_ERROR || level == LOG_WARN || level == LOG_FATAL) ? stderr : stdout;
729 }
730 int fd = output_stream == stderr ? STDERR_FILENO : STDOUT_FILENO;
731 bool use_colors = isatty(fd);
732
733 char header_buffer[512];
734 int header_len =
735 format_log_header(header_buffer, sizeof(header_buffer), level, time_buf, file, line, func, use_colors, false);
736
737 if (header_len > 0 && header_len < (int)sizeof(header_buffer)) {
738 if (use_colors) {
739 const char **colors = log_get_color_array();
740 if (colors) {
741 safe_fprintf(output_stream, "%s%s%s%s\n", header_buffer, colors[LOG_COLOR_RESET], msg_buffer,
742 colors[LOG_COLOR_RESET]);
743 } else {
744 safe_fprintf(output_stream, "%s%s\n", header_buffer, msg_buffer);
745 }
746 } else {
747 safe_fprintf(output_stream, "%s%s\n", header_buffer, msg_buffer);
748 }
749 (void)fflush(output_stream);
750 }
751 }
752 return;
753 }
754
755 /* =========================================================================
756 * FILE I/O PATH: Lock-free using atomic write() syscalls
757 * ========================================================================= */
758 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
760
761 // Format message for file output
762 char log_buffer[LOG_MSG_BUFFER_SIZE];
763 va_list args;
764 va_start(args, fmt);
765
766 int header_len = format_log_header(log_buffer, sizeof(log_buffer), level, time_buf, file, line, func, false, false);
767 if (header_len <= 0 || header_len >= (int)sizeof(log_buffer)) {
768 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format log header");
769 va_end(args);
770 return;
771 }
772
773 int msg_len = header_len;
774 int formatted_len = vsnprintf(log_buffer + header_len, sizeof(log_buffer) - (size_t)header_len, fmt, args);
775 if (formatted_len < 0) {
776 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format log message");
777 va_end(args);
778 return;
779 }
780
781 msg_len += formatted_len;
782 if (msg_len >= (int)sizeof(log_buffer)) {
783 msg_len = sizeof(log_buffer) - 1;
784 log_buffer[msg_len] = '\0';
785 }
786
787 if (msg_len > 0 && msg_len < (int)sizeof(log_buffer) - 1) {
788 log_buffer[msg_len++] = '\n';
789 log_buffer[msg_len] = '\0';
790 }
791
792 va_end(args);
793
794 // Write to file (atomic write syscall)
795 int file_fd = atomic_load(&g_log.file);
796 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
797 write_to_log_file_atomic(log_buffer, msg_len);
798 }
799
800 // Write to terminal (atomic state checks)
801 va_list args_terminal;
802 va_start(args_terminal, fmt);
803 write_to_terminal_atomic(level, time_buf, file, line, func, fmt, args_terminal);
804 va_end(args_terminal);
805}
806
807void log_plain_msg(const char *fmt, ...) {
808 if (!atomic_load(&g_log.initialized)) {
809 return;
810 }
811
812 if (shutdown_is_requested()) {
813 return;
814 }
815
816 char log_buffer[LOG_MSG_BUFFER_SIZE];
817 va_list args;
818 va_start(args, fmt);
819 int msg_len = vsnprintf(log_buffer, sizeof(log_buffer), fmt, args);
820 va_end(args);
821
822 if (msg_len <= 0 || msg_len >= (int)sizeof(log_buffer)) {
823 return;
824 }
825
826 // Write to mmap if active
827 if (log_mmap_is_active()) {
828 log_mmap_write(LOG_INFO, NULL, 0, NULL, "%s", log_buffer);
829 } else {
830 // Write to file (atomic write syscall)
831 int file_fd = atomic_load(&g_log.file);
832 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
833 write_to_log_file_atomic(log_buffer, msg_len);
834 write_to_log_file_atomic("\n", 1);
835 }
836 }
837
838 // Terminal output (atomic state checks)
839 if (!atomic_load(&g_log.terminal_output_enabled)) {
840 return;
841 }
842 if (atomic_load(&g_log.terminal_locked)) {
843 uint64_t owner = atomic_load(&g_log.terminal_owner_thread);
844 if (owner != (uint64_t)asciichat_thread_self()) {
845 return;
846 }
847 }
848
849 safe_fprintf(stdout, "%s\n", log_buffer);
850 (void)fflush(stdout);
851}
852
853// Helper for log_plain_stderr variants (lock-free)
854static void log_plain_stderr_internal_atomic(const char *fmt, va_list args, bool add_newline) {
855 char log_buffer[LOG_MSG_BUFFER_SIZE];
856 int msg_len = vsnprintf(log_buffer, sizeof(log_buffer), fmt, args);
857
858 if (msg_len <= 0 || msg_len >= (int)sizeof(log_buffer)) {
859 return;
860 }
861
862 // Write to mmap if active
863 if (log_mmap_is_active()) {
864 log_mmap_write(LOG_INFO, NULL, 0, NULL, "%s", log_buffer);
865 } else {
866 // Write to file (atomic write syscall)
867 int file_fd = atomic_load(&g_log.file);
868 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
869 write_to_log_file_atomic(log_buffer, msg_len);
870 if (add_newline) {
871 write_to_log_file_atomic("\n", 1);
872 }
873 }
874 }
875
876 // Terminal output (atomic state checks)
877 if (!atomic_load(&g_log.terminal_output_enabled)) {
878 return;
879 }
880 if (atomic_load(&g_log.terminal_locked)) {
881 uint64_t owner = atomic_load(&g_log.terminal_owner_thread);
882 if (owner != (uint64_t)asciichat_thread_self()) {
883 return;
884 }
885 }
886
887 if (add_newline) {
888 safe_fprintf(stderr, "%s\n", log_buffer);
889 } else {
890 safe_fprintf(stderr, "%s", log_buffer);
891 }
892 (void)fflush(stderr);
893}
894
895void log_plain_stderr_msg(const char *fmt, ...) {
896 if (!atomic_load(&g_log.initialized)) {
897 return;
898 }
899 if (shutdown_is_requested()) {
900 return;
901 }
902
903 va_list args;
904 va_start(args, fmt);
905 log_plain_stderr_internal_atomic(fmt, args, true);
906 va_end(args);
907}
908
909void log_plain_stderr_nonewline_msg(const char *fmt, ...) {
910 if (!atomic_load(&g_log.initialized)) {
911 return;
912 }
913 if (shutdown_is_requested()) {
914 return;
915 }
916
917 va_list args;
918 va_start(args, fmt);
919 log_plain_stderr_internal_atomic(fmt, args, false);
920 va_end(args);
921}
922
923void log_file_msg(const char *fmt, ...) {
924 if (!atomic_load(&g_log.initialized)) {
925 return;
926 }
927
928 char log_buffer[LOG_MSG_BUFFER_SIZE];
929 va_list args;
930 va_start(args, fmt);
931 int msg_len = vsnprintf(log_buffer, sizeof(log_buffer), fmt, args);
932 va_end(args);
933
934 if (msg_len <= 0 || msg_len >= (int)sizeof(log_buffer)) {
935 return;
936 }
937
938 // Write to mmap if active, else to file
939 if (log_mmap_is_active()) {
940 log_mmap_write(LOG_INFO, NULL, 0, NULL, "%s", log_buffer);
941 } else {
942 int file_fd = atomic_load(&g_log.file);
943 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
944 write_to_log_file_atomic(log_buffer, msg_len);
945 write_to_log_file_atomic("\n", 1);
946 }
947 }
948}
949
950static const char *log_network_direction_label(remote_log_direction_t direction) {
951 switch (direction) {
953 return "serverโ†’client";
955 return "clientโ†’server";
956 default:
957 return "network";
958 }
959}
960
961static asciichat_error_t log_network_message_internal(socket_t sockfd, const struct crypto_context_t *crypto_ctx,
962 log_level_t level, remote_log_direction_t direction,
963 const char *file, int line, const char *func, const char *fmt,
964 va_list args) {
965 if (!fmt) {
966 return SET_ERRNO(ERROR_INVALID_PARAM, "Format string is NULL");
967 }
968
969 va_list args_copy;
970 va_copy(args_copy, args);
971 char *formatted = format_message(fmt, args_copy);
972 va_end(args_copy);
973
974 if (!formatted) {
975 asciichat_error_t current_error = GET_ERRNO();
976 if (current_error == ASCIICHAT_OK) {
977 current_error = SET_ERRNO(ERROR_MEMORY, "Failed to format network log message");
978 }
979 return current_error;
980 }
981
982 asciichat_error_t send_result = ASCIICHAT_OK;
983 if (sockfd == INVALID_SOCKET_VALUE) {
984 send_result = SET_ERRNO(ERROR_INVALID_PARAM, "Invalid socket descriptor");
985 log_msg(LOG_WARN, file, line, func, "Skipping remote log message: invalid socket descriptor");
986 } else {
987 send_result = packet_send_remote_log(sockfd, (const crypto_context_t *)crypto_ctx, level, direction, 0, formatted);
988 if (send_result != ASCIICHAT_OK) {
989 log_msg(LOG_WARN, file, line, func, "Failed to send remote log message: %s", asciichat_error_string(send_result));
990 }
991 }
992
993 const char *direction_label = log_network_direction_label(direction);
994 log_msg(level, file, line, func, "[NET %s] %s", direction_label, formatted);
995
996 SAFE_FREE(formatted);
997 return send_result;
998}
999
1001 remote_log_direction_t direction, const char *fmt, ...) {
1002 va_list args;
1003 va_start(args, fmt);
1004 asciichat_error_t result =
1005 log_network_message_internal(sockfd, crypto_ctx, level, direction, NULL, 0, NULL, fmt, args);
1006 va_end(args);
1007 return result;
1008}
1009
1011 remote_log_direction_t direction, const char *file, int line, const char *func,
1012 const char *fmt, ...) {
1013 va_list args;
1014 va_start(args, fmt);
1015 asciichat_error_t result =
1016 log_network_message_internal(sockfd, crypto_ctx, level, direction, file, line, func, fmt, args);
1017 va_end(args);
1018 return result;
1019}
1020
1021/* ============================================================================
1022 * Color Helper Functions
1023 * ============================================================================ */
1024
1025/* Initialize terminal capabilities if not already done */
1026static void init_terminal_capabilities(void) {
1027 // Guard against recursion - this can be called indirectly via log_get_color_array()
1028 // from detect_terminal_capabilities() which uses logging
1029 if (g_terminal_caps_detecting) {
1030 return;
1031 }
1032
1033 if (!g_terminal_caps_initialized) {
1034 // Set detecting flag to prevent recursion
1035 g_terminal_caps_detecting = true;
1036
1037 // NEVER call detect_terminal_capabilities() from here - it causes infinite recursion
1038 // because detect_terminal_capabilities() uses log_debug() which calls log_get_color_array()
1039 // which calls init_terminal_capabilities() again.
1040 // Always use defaults here - log_redetect_terminal_capabilities() will do the actual detection
1041 // Use safe fallback during logging initialization to avoid recursion
1042 g_terminal_caps.color_level = TERM_COLOR_16;
1043 g_terminal_caps.capabilities = TERM_CAP_COLOR_16;
1044 g_terminal_caps.color_count = 16;
1045 g_terminal_caps.detection_reliable = false;
1046 g_terminal_caps_initialized = true;
1047
1048 // Clear detecting flag
1049 g_terminal_caps_detecting = false;
1050 }
1051}
1052
1053/* Re-detect terminal capabilities after logging is initialized */
1055 // Guard against recursion
1056 if (g_terminal_caps_detecting) {
1057 return;
1058 }
1059
1060 // Detect if not initialized, or if we're using defaults (not reliably detected)
1061 // This ensures we get proper detection after logging is ready, replacing any defaults
1062 // Once we have reliable detection, never re-detect to keep colors consistent
1063 if (!g_terminal_caps_initialized || !g_terminal_caps.detection_reliable) {
1064 g_terminal_caps_detecting = true;
1065 g_terminal_caps = detect_terminal_capabilities();
1066 g_terminal_caps_detecting = false;
1067 g_terminal_caps_initialized = true;
1068
1069 // Now log the capabilities AFTER colors are set, so this log uses the correct colors
1070 log_debug("Terminal capabilities: color_level=%d, capabilities=0x%x, utf8=%s, fps=%d", g_terminal_caps.color_level,
1071 g_terminal_caps.capabilities, g_terminal_caps.utf8_support ? "yes" : "no", g_terminal_caps.desired_fps);
1072
1073 // Now that we've detected once with reliable results, keep these colors consistent for all future logs
1074 }
1075 // Once initialized with reliable detection, never re-detect to keep colors consistent
1076}
1077
1078/* Get the appropriate color array based on terminal capabilities */
1079const char **log_get_color_array(void) {
1080 init_terminal_capabilities();
1081
1082 log_color_mode_t mode;
1083 if (g_terminal_caps.color_level >= TERM_COLOR_TRUECOLOR) {
1084 mode = LOG_CMODE_TRUECOLOR;
1085 } else if (g_terminal_caps.color_level >= TERM_COLOR_256) {
1086 mode = LOG_CMODE_256;
1087 } else {
1088 mode = LOG_CMODE_16;
1089 }
1090
1091 /* Cast away const for the pointer-to-pointer return - the array is still const */
1092 return (const char **)level_colors[mode];
1093}
1094
1095const char *log_level_color(log_color_t color) {
1096 const char **colors = log_get_color_array();
1097 if (colors == NULL) {
1098 return ""; /* Return empty string if colors not available */
1099 }
1100 if (color >= 0 && color <= LOG_COLOR_RESET) {
1101 return colors[color];
1102 }
1103 return colors[LOG_COLOR_RESET]; /* Return reset color if invalid */
1104}
1105
1106/* ============================================================================
1107 * Lock-Free MMAP Logging Integration
1108 * ============================================================================ */
1109
1110asciichat_error_t log_enable_mmap(const char *log_path) {
1111 return log_enable_mmap_sized(log_path, 0); /* Use default size */
1112}
1113
1114asciichat_error_t log_enable_mmap_sized(const char *log_path, size_t max_size) {
1115 if (!log_path) {
1116 return SET_ERRNO(ERROR_INVALID_PARAM, "log_path is required");
1117 }
1118
1119 // Initialize mmap logging - text is written directly to the mmap'd file
1120 asciichat_error_t result = log_mmap_init_simple(log_path, max_size);
1121 if (result != ASCIICHAT_OK) {
1122 return result;
1123 }
1124
1125 log_info("Lock-free mmap logging enabled: %s", log_path);
1126 return ASCIICHAT_OK;
1127}
1128
1130 if (log_mmap_is_active()) {
1132 log_info("Lock-free mmap logging disabled");
1133 }
1134}
1135
1136/* ============================================================================
1137 * Shutdown Logging Control
1138 * ============================================================================ */
1139
1141 if (g_shutdown_in_progress) {
1142 return; /* Already in shutdown phase */
1143 }
1144
1145 /* Save current terminal output state and disable console output */
1146 g_shutdown_saved_terminal_output = atomic_load(&g_log.terminal_output_enabled);
1147 atomic_store(&g_log.terminal_output_enabled, false);
1148 g_shutdown_in_progress = true;
1149}
1150
1152 if (!g_shutdown_in_progress) {
1153 return; /* Not in shutdown phase */
1154 }
1155
1156 /* Restore previous terminal output state */
1157 atomic_store(&g_log.terminal_output_enabled, g_shutdown_saved_terminal_output);
1158 g_shutdown_in_progress = false;
1159}
๐Ÿ”Œ Cross-platform abstraction layer umbrella header for ascii-chat
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
#define SAFE_GETENV(name)
Definition common.h:378
#define PLATFORM_MAX_PATH_LENGTH
Definition common.h:91
unsigned long long uint64_t
Definition common.h:59
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
#define GET_ERRNO()
Get current error code (0 if no error)
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_INVALID_STATE
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_INVALID_PARAM
void log_truncate_if_large(void)
Manually truncate large log files.
void log_plain_stderr_nonewline_msg(const char *fmt,...)
Plain logging to stderr without trailing newline.
void log_plain_msg(const char *fmt,...)
Plain logging without timestamps or levels.
#define log_warn(...)
Log a WARN message.
bool log_get_terminal_output(void)
Get current terminal output setting.
asciichat_error_t log_enable_mmap(const char *log_path)
Enable lock-free mmap-based logging.
void log_msg(log_level_t level, const char *file, int line, const char *func, const char *fmt,...)
Log a message at a specific level.
void log_shutdown_end(void)
End shutdown phase - restore previous logging settings.
void log_destroy(void)
Destroy the logging system and close log file.
void log_set_force_stderr(bool enabled)
Force all terminal log output to stderr.
enum remote_log_direction remote_log_direction_t
Remote log packet direction enumeration.
log_level_t log_get_level(void)
Get the current minimum log level.
const char ** log_get_color_array(void)
Get the appropriate color array based on terminal capabilities.
const char * log_level_color(log_color_t color)
Get color string for a given color enum.
void log_set_flush_delay(unsigned int delay_ms)
Set the delay between flushing buffered log entries.
#define LOG_MSG_BUFFER_SIZE
Maximum size of a single log message (including formatting)
Definition log/logging.h:95
char * format_message(const char *format, va_list args)
Format a message using va_list.
log_color_t
Color enum for logging - indexes into color arrays.
void log_shutdown_begin(void)
Begin shutdown phase - disable console logging but keep file logging.
#define log_error(...)
Log an ERROR message.
bool log_get_force_stderr(void)
Get current force_stderr setting.
void log_set_level(log_level_t level)
Set the minimum log level.
void log_init(const char *filename, log_level_t level, bool force_stderr, bool use_mmap)
Initialize the logging system.
asciichat_error_t log_net_message(socket_t sockfd, const struct crypto_context_t *crypto_ctx, log_level_t level, remote_log_direction_t direction, const char *file, int line, const char *func, const char *fmt,...)
Log a message to all destinations (network, file, and terminal).
bool log_lock_terminal(void)
Lock terminal output for exclusive access by the calling thread.
asciichat_error_t log_network_message(socket_t sockfd, const struct crypto_context_t *crypto_ctx, log_level_t level, remote_log_direction_t direction, const char *fmt,...)
Send a formatted log message over the network.
log_level_t
Logging levels enumeration.
Definition log/logging.h:59
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
size_t get_current_time_formatted(char *time_buf)
Get current time as formatted string.
asciichat_error_t log_enable_mmap_sized(const char *log_path, size_t max_size)
Enable lock-free mmap logging with custom file size.
#define MAX_LOG_SIZE
Maximum log file size in bytes (3MB) before rotation.
Definition log/logging.h:79
void log_file_msg(const char *fmt,...)
Log to file only, no stderr output.
void log_disable_mmap(void)
Disable mmap logging and return to mutex-based logging.
#define LOG_TIMESTAMP_BUFFER_SIZE
Maximum size of a timestamp string.
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
#define LOG_MMAP_MSG_BUFFER_SIZE
Maximum size of a log message in mmap mode.
Definition log/logging.h:98
void log_plain_stderr_msg(const char *fmt,...)
Plain logging to stderr with newline.
void log_redetect_terminal_capabilities(void)
Re-detect terminal capabilities after logging is initialized.
void log_unlock_terminal(bool previous_state)
Release terminal lock and flush buffered messages.
#define DEFAULT_LOG_LEVEL
Default log level for debug builds (DEBUG and above)
Definition log/logging.h:75
@ REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT
@ REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER
@ LOG_COLOR_RESET
@ LOG_ERROR
Definition log/logging.h:64
@ LOG_INFO
Definition log/logging.h:62
@ LOG_DEBUG
Definition log/logging.h:61
@ LOG_FATAL
Definition log/logging.h:65
@ LOG_WARN
Definition log/logging.h:63
@ LOG_DEV
Definition log/logging.h:60
asciichat_error_t packet_send_remote_log(socket_t sockfd, const crypto_context_t *crypto_ctx, log_level_t level, remote_log_direction_t direction, uint16_t flags, const char *message)
Send a remote log packet with optional encryption context.
Definition packet.c:896
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:222
thread_id_t asciichat_thread_self(void)
Get the current thread's ID.
int safe_fprintf(FILE *stream, const char *format,...)
Safe version of fprintf.
int mutex_init(mutex_t *mutex)
Initialize a mutex.
int socket_t
Socket handle type (POSIX: int)
Definition socket.h:50
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe version of snprintf that ensures null termination.
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
Definition mutex.h:140
pthread_mutex_t mutex_t
Mutex type (POSIX: pthread_mutex_t)
Definition mutex.h:38
#define INVALID_SOCKET_VALUE
Invalid socket value (POSIX: -1)
Definition socket.h:52
int platform_strcasecmp(const char *s1, const char *s2)
Case-insensitive string comparison.
asciichat_error_t platform_localtime(const time_t *timer, struct tm *result)
Platform-safe localtime wrapper.
int platform_close(int fd)
Safe file close (close replacement)
terminal_capabilities_t detect_terminal_capabilities(void)
Detect terminal capabilities.
int platform_open(const char *pathname, int flags,...)
Safe file open (open replacement)
ssize_t platform_read(int fd, void *buf, size_t count)
Safe file read (read replacement)
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Definition mutex.h:175
ssize_t platform_write(int fd, const void *buf, size_t count)
Platform-safe write function.
#define FILE_PERM_PRIVATE
File permission: Private (owner read/write only)
Definition system.h:637
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
@ TERM_CAP_COLOR_16
16-color support (TERM_CAP_COLOR_16)
Definition terminal.h:448
@ TERM_COLOR_16
16-color support (standard ANSI colors)
Definition terminal.h:430
@ TERM_COLOR_256
256-color support (extended ANSI palette)
Definition terminal.h:432
@ TERM_COLOR_TRUECOLOR
24-bit truecolor support (RGB colors)
Definition terminal.h:434
bool shutdown_is_requested(void)
Check if shutdown has been requested.
Definition common.c:45
const char * extract_project_relative_path(const char *file)
Extract relative path from an absolute path.
Definition path.c:127
#define LOG_COLOR_COUNT
Definition log/logging.c:77
log_color_mode_t
Definition log/logging.c:75
@ LOG_CMODE_256
Definition log/logging.c:75
@ LOG_CMODE_TRUECOLOR
Definition log/logging.c:75
@ LOG_CMODE_16
Definition log/logging.c:75
@ LOG_CMODE_COUNT
Definition log/logging.c:75
#define LOGGING_INTERNAL_ERROR(error, message,...)
Lock-free memory-mapped text logging with crash safety.
void log_mmap_rotate(void)
Rotate the mmap log (tail-keeping rotation)
Definition mmap.c:408
bool log_mmap_is_active(void)
Check if mmap logging is active.
Definition mmap.c:375
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:309
bool log_mmap_get_usage(size_t *used, size_t *capacity)
Get current mmap log usage.
Definition mmap.c:394
void log_mmap_destroy(void)
Shutdown mmap logging.
Definition mmap.c:257
bool initialized
Definition mmap.c:36
asciichat_error_t log_mmap_init_simple(const char *log_path, size_t max_size)
Initialize mmap logging with simple parameters.
Definition mmap.c:249
Cross-platform mutex interface for ascii-chat.
โš™๏ธ Command-line options parsing and configuration management for ascii-chat
Packet protocol implementation with encryption and compression support.
๐Ÿ“‚ Path Manipulation Utilities
๐Ÿงต Cross-platform thread interface for ascii-chat
Cryptographic context structure.
Consolidated options structure.
Definition options.h:439
unsigned short int quiet
Quiet mode (suppress logs)
Definition options.h:530
Complete terminal capabilities structure.
Definition terminal.h:485
terminal_color_mode_t color_level
Detected color support level (terminal_color_mode_t)
Definition terminal.h:487
uint8_t desired_fps
Client's desired frame rate (1-144 FPS)
Definition terminal.h:509
bool utf8_support
True if terminal supports UTF-8 encoding.
Definition terminal.h:493
uint32_t capabilities
Capability flags bitmask (terminal_capability_flags_t)
Definition terminal.h:489
uint32_t color_count
Maximum number of colors (16, 256, or 16777216)
Definition terminal.h:491
bool detection_reliable
True if detection is confident (reliable detection)
Definition terminal.h:495
Cross-platform system functions interface for ascii-chat.
๐Ÿ–ฅ๏ธ Cross-platform terminal interface for ascii-chat
Test logging control utilities.
โฑ๏ธ High-precision timing utilities using sokol_time.h and uthash