ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
logging.c File Reference

📝 Multi-level logging with terminal color support, file rotation, and async output More...

Go to the source code of this file.

Data Structures

struct  log_context_t
 

Macros

#define LOG_COLOR_COUNT   8 /* DEV, DEBUG, WARN, INFO, ERROR, FATAL, GREY, RESET */
 
#define LOGGING_INTERNAL_ERROR(error, message, ...)
 

Functions

 __attribute__ ((weak))
 
const char * get_level_string_padded (log_level_t level)
 Get padded level string for consistent alignment.
 
size_t get_current_time_formatted (char *time_buf)
 
char * format_message (const char *format, va_list args)
 
void log_init (const char *filename, log_level_t level, bool force_stderr, bool use_mmap)
 
void log_destroy (void)
 
void log_set_level (log_level_t level)
 
log_level_t log_get_level (void)
 
void log_set_terminal_output (bool enabled)
 
bool log_get_terminal_output (void)
 
void log_set_force_stderr (bool enabled)
 
bool log_get_force_stderr (void)
 
void log_set_json_output (int fd)
 
void log_disable_file_output (void)
 
asciichat_error_t log_set_format (const char *format_str, bool console_only)
 
bool log_lock_terminal (void)
 
void log_unlock_terminal (bool previous_state)
 
void log_set_flush_delay (unsigned int delay_ms)
 
void log_truncate_if_large (void)
 
void log_msg (log_level_t level, const char *file, int line, const char *func, const char *fmt,...)
 
void log_terminal_msg (log_level_t level, const char *file, int line, const char *func, const char *fmt,...)
 
void log_plain_msg (const char *fmt,...)
 
void log_plain_stderr_msg (const char *fmt,...)
 
void log_plain_stderr_nonewline_msg (const char *fmt,...)
 
void log_file_msg (const char *fmt,...)
 
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,...)
 
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,...)
 
void log_redetect_terminal_capabilities (void)
 
const char ** log_get_color_array (void)
 
const char * log_level_color (log_color_t color)
 
void log_init_colors (void)
 
void log_set_color_scheme (const color_scheme_t *scheme)
 
asciichat_error_t log_enable_mmap (const char *log_path)
 
asciichat_error_t log_enable_mmap_sized (const char *log_path, size_t max_size)
 
void log_disable_mmap (void)
 
void log_shutdown_begin (void)
 
void log_shutdown_end (void)
 
void log_cleanup_colors (void)
 Clean up compiled color scheme.
 
size_t log_recolor_plain_entry (const char *plain_line, char *colored_buf, size_t buf_size)
 Recolor a plain (non-colored) log line with proper ANSI codes.
 
void log_console_impl (log_level_t level, const char *file, int line, const char *func, const char *message)
 
void * log_get_template (void)
 Get the current log format template.
 

Detailed Description

📝 Multi-level logging with terminal color support, file rotation, and async output

Definition in file log/logging.c.

Macro Definition Documentation

◆ LOG_COLOR_COUNT

#define LOG_COLOR_COUNT   8 /* DEV, DEBUG, WARN, INFO, ERROR, FATAL, GREY, RESET */

Definition at line 146 of file log/logging.c.

◆ LOGGING_INTERNAL_ERROR

#define LOGGING_INTERNAL_ERROR (   error,
  message,
  ... 
)
Value:
do { \
asciichat_set_errno_with_message(error, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__); \
static const char *msg_header = "CRITICAL LOGGING SYSTEM ERROR: "; \
safe_fprintf(stderr, "%s %s\n", colored_string(LOG_COLOR_ERROR, msg_header), message); \
platform_write(g_log.file, msg_header, strlen(msg_header)); \
platform_write(g_log.file, message, strlen(message)); \
platform_write(g_log.file, "\n", 1); \
platform_print_backtrace(0); \
} while (0)
const char * colored_string(log_color_t color, const char *text)

Definition at line 165 of file log/logging.c.

166 { \
167 asciichat_set_errno_with_message(error, __FILE__, __LINE__, __func__, message, ##__VA_ARGS__); \
168 static const char *msg_header = "CRITICAL LOGGING SYSTEM ERROR: "; \
169 safe_fprintf(stderr, "%s %s\n", colored_string(LOG_COLOR_ERROR, msg_header), message); \
170 platform_write(g_log.file, msg_header, strlen(msg_header)); \
171 platform_write(g_log.file, message, strlen(message)); \
172 platform_write(g_log.file, "\n", 1); \
173 platform_print_backtrace(0); \
174 } while (0)

Function Documentation

◆ __attribute__()

__attribute__ ( (weak)  )

Definition at line 39 of file log/logging.c.

39 {
40 (void)level;
41 (void)message;
42 // Default: no-op
43}

◆ format_message()

char * format_message ( const char *  format,
va_list  args 
)

Definition at line 224 of file log/logging.c.

224 {
225 if (!format) {
226 return NULL;
227 }
228
229 // First, determine the size needed
230 va_list args_copy;
231 va_copy(args_copy, args);
232 int size = safe_vsnprintf(NULL, 0, format, args_copy);
233 va_end(args_copy);
234
235 if (size < 0) {
236 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format context message");
237 return NULL;
238 }
239
240 // Allocate and format the message
241 char *message = SAFE_MALLOC(size + 1, char *);
242 int result = safe_vsnprintf(message, (size_t)size + 1, format, args);
243 if (result < 0) {
244 SAFE_FREE(message);
245 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format context message");
246 return NULL;
247 }
248
249 return message;
250}
#define LOGGING_INTERNAL_ERROR(error, message,...)
action_args_t args
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:507

References args, LOGGING_INTERNAL_ERROR, and safe_vsnprintf().

Referenced by asciichat_fatal_with_context(), asciichat_set_errno_with_message(), asciichat_set_errno_with_system_error_and_message(), and log_labeled().

◆ get_current_time_formatted()

size_t get_current_time_formatted ( char *  time_buf)

Definition at line 192 of file log/logging.c.

192 {
193 /* Get wall-clock time in nanoseconds */
194 uint64_t ts_ns = time_get_realtime_ns();
195 // Extract seconds and nanoseconds from total nanoseconds
196 time_t seconds = (time_t)(ts_ns / NS_PER_SEC_INT);
197 long nanoseconds = (long)(ts_ns % NS_PER_SEC_INT);
198 struct tm tm_info;
199 platform_localtime(&seconds, &tm_info);
200 // Format the time part first
201 // strftime returns 0 on error, not negative (and len is size_t/unsigned)
202 size_t len = strftime(time_buf, 32, "%H:%M:%S", &tm_info);
203 if (len == 0 || len >= 32) {
204 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format time");
205 return 0;
206 }
207
208 // Add microseconds manually (convert nanoseconds to microseconds for display)
209 long microseconds = nanoseconds / 1000;
210 if (microseconds < 0)
211 microseconds = 0;
212 if (microseconds > 999999)
213 microseconds = 999999;
214
215 int result = safe_snprintf(time_buf + len, 32 - len, ".%06ld", microseconds);
216 if (result < 0 || result >= (int)(32 - len)) {
217 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format microseconds");
218 return 0;
219 }
220
221 return len + (size_t)result;
222}
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456
uint64_t time_get_realtime_ns(void)
Definition util/time.c:59
asciichat_error_t platform_localtime(const time_t *timer, struct tm *result)
Definition util.c:48

References LOGGING_INTERNAL_ERROR, platform_localtime(), safe_snprintf(), and time_get_realtime_ns().

Referenced by log_msg(), log_plain_msg(), and log_terminal_msg().

◆ get_level_string_padded()

const char * get_level_string_padded ( log_level_t  level)

Get padded level string for consistent alignment.

Returns level names padded to 5 characters for visual alignment. Used consistently in both colored and plain text formatters to ensure grep pattern matching works correctly.

Parameters
levelLog level
Returns
Padded level string (e.g., "INFO ", "WARN ", "DEBUG")

Definition at line 114 of file log/logging.c.

114 {
115 const char *result;
116 switch (level) {
117 case LOG_INFO:
118 result = "INFO "; // 5 chars: INFO + 1 space
119 break;
120 case LOG_WARN:
121 result = "WARN "; // 5 chars: WARN + 1 space
122 break;
123 case LOG_DEV:
124 result = "DEV "; // 5 chars: DEV + 2 spaces
125 break;
126 case LOG_DEBUG:
127 result = "DEBUG"; // 5 chars: DEBUG (no padding needed)
128 break;
129 case LOG_ERROR:
130 result = "ERROR"; // 5 chars: ERROR (no padding needed)
131 break;
132 case LOG_FATAL:
133 result = "FATAL"; // 5 chars: FATAL (no padding needed)
134 break;
135 default:
136 result = "?????"; // Invalid level - return 5 question marks
137 }
138 // Verify length
139 if (strlen(result) != 5) {
140 fprintf(stderr, "ERROR: get_level_string_padded() returned non-5-char string: '%s' (len=%zu)\n", result,
141 strlen(result));
142 }
143 return result;
144}

Referenced by log_template_apply().

◆ log_cleanup_colors()

void log_cleanup_colors ( void  )

Clean up compiled color scheme.

Should be called AFTER memory reporting to ensure colored output. Safe to call multiple times (idempotent).

Definition at line 1666 of file log/logging.c.

1666 {
1667 colorscheme_cleanup_compiled(&g_compiled_colors);
1668}
void colorscheme_cleanup_compiled(compiled_color_scheme_t *compiled)

References colorscheme_cleanup_compiled().

Referenced by asciichat_shared_destroy().

◆ log_console_impl()

void log_console_impl ( log_level_t  level,
const char *  file,
int  line,
const char *  func,
const char *  message 
)

Definition at line 1932 of file log/logging.c.

1932 {
1933 if (!message) {
1934 return;
1935 }
1936
1937 int fd = terminal_choose_log_fd(level);
1938 if (fd < 0) {
1939 return;
1940 }
1941
1942 // Check if JSON output is enabled (json_file >= 0 means enabled)
1943 int json_fd = atomic_load(&g_log.json_file);
1944 bool use_json = (json_fd >= 0);
1945
1946 if (use_json) {
1947 // Use async-safe JSON formatter (safe for signal handlers)
1948 extern void log_json_async_safe(int fd, log_level_t level, const char *file, int line, const char *func,
1949 const char *message);
1950 log_json_async_safe(fd, level, file, line, func, message);
1951 } else {
1952 // Text output to console using platform_write_all to handle partial writes
1953 size_t msg_len = strlen(message);
1954 platform_write_all(fd, (const uint8_t *)message, msg_len);
1955 if (msg_len == 0 || message[msg_len - 1] != '\n') {
1956 platform_write_all(fd, (const uint8_t *)"\n", 1);
1957 }
1958 }
1959}
size_t platform_write_all(int fd, const void *buf, size_t count)
Write all data to file descriptor with automatic retry on transient errors.
Definition abstraction.c:39
void log_json_async_safe(int fd, log_level_t level, const char *file, int line, const char *func, const char *message)
Async-safe JSON logging for signal handlers.
Definition json.c:251
int terminal_choose_log_fd(log_level_t level)

References log_json_async_safe(), platform_write_all(), and terminal_choose_log_fd().

◆ log_destroy()

void log_destroy ( void  )

Definition at line 587 of file log/logging.c.

587 {
588 // Destroy mmap logging first (if active)
589 if (log_mmap_is_active()) {
591 }
592
593 // Cleanup grep filter
594 grep_destroy();
595
596 // Cleanup custom format structures
597 if (g_log.format) {
598 log_template_free(g_log.format);
599 g_log.format = NULL;
600 }
601 if (g_log.format_console_only) {
602 log_template_free(g_log.format_console_only);
603 g_log.format_console_only = NULL;
604 }
605 atomic_store(&g_log.has_custom_format, false);
606
607 // Lock-free cleanup using atomic operations
608 int old_file = atomic_load(&g_log.file);
609 if (old_file >= 0 && old_file != STDERR_FILENO) {
610 platform_close(old_file);
611 }
612 atomic_store(&g_log.file, -1);
613 atomic_store(&g_log.initialized, false);
614
615 // Destroy rotation mutex
616 if (atomic_load(&g_log.rotation_mutex_initialized)) {
617 mutex_destroy(&g_log.rotation_mutex);
618 atomic_store(&g_log.rotation_mutex_initialized, false);
619 }
620}
void grep_destroy(void)
Definition grep.c:1240
void log_template_free(log_template_t *format)
Definition log/format.c:293
bool log_mmap_is_active(void)
Definition mmap.c:390
void log_mmap_destroy(void)
Definition mmap.c:260
int mutex_destroy(mutex_t *mutex)
Definition threading.c:21
int platform_close(int fd)

References grep_destroy(), log_mmap_destroy(), log_mmap_is_active(), log_template_free(), mutex_destroy(), and platform_close().

Referenced by asciichat_shared_destroy(), main(), and server_main().

◆ log_disable_file_output()

void log_disable_file_output ( void  )

Definition at line 658 of file log/logging.c.

658 {
659 /* Close the current file if it's not stderr */
660 int old_file = atomic_load(&g_log.file);
661 if (old_file >= 0 && old_file != STDERR_FILENO) {
662 platform_close(old_file);
663 }
664 /* Redirect file output to stderr */
665 atomic_store(&g_log.file, STDERR_FILENO);
666 g_log.filename[0] = '\0';
667}

References platform_close().

◆ log_disable_mmap()

void log_disable_mmap ( void  )

Definition at line 1628 of file log/logging.c.

1628 {
1629 if (log_mmap_is_active()) {
1631 log_info("Lock-free mmap logging disabled");
1632 }
1633}

References log_mmap_destroy(), and log_mmap_is_active().

◆ log_enable_mmap()

asciichat_error_t log_enable_mmap ( const char *  log_path)

Definition at line 1609 of file log/logging.c.

1609 {
1610 return log_enable_mmap_sized(log_path, 0); /* Use default size */
1611}
asciichat_error_t log_enable_mmap_sized(const char *log_path, size_t max_size)

References log_enable_mmap_sized().

◆ log_enable_mmap_sized()

asciichat_error_t log_enable_mmap_sized ( const char *  log_path,
size_t  max_size 
)

Definition at line 1613 of file log/logging.c.

1613 {
1614 if (!log_path) {
1615 return SET_ERRNO(ERROR_INVALID_PARAM, "log_path is required");
1616 }
1617
1618 // Initialize mmap logging - text is written directly to the mmap'd file
1619 asciichat_error_t result = log_mmap_init_simple(log_path, max_size);
1620 if (result != ASCIICHAT_OK) {
1621 return result;
1622 }
1623
1624 log_info("Lock-free mmap logging enabled: %s", log_path);
1625 return ASCIICHAT_OK;
1626}
asciichat_error_t log_mmap_init_simple(const char *log_path, size_t max_size)
Definition mmap.c:252

References log_mmap_init_simple().

Referenced by log_enable_mmap().

◆ log_file_msg()

void log_file_msg ( const char *  fmt,
  ... 
)

Definition at line 1316 of file log/logging.c.

1316 {
1317 if (!atomic_load(&g_log.initialized)) {
1318 return;
1319 }
1320
1321 char log_buffer[LOG_MSG_BUFFER_SIZE];
1322 va_list args;
1323 va_start(args, fmt);
1324 int msg_len = safe_vsnprintf(log_buffer, sizeof(log_buffer), fmt, args);
1325 va_end(args);
1326
1327 if (msg_len <= 0) {
1328 return;
1329 }
1330
1331 // Truncate at whole line boundaries to avoid UTF-8 issues
1332 msg_len = truncate_at_whole_line(log_buffer, msg_len, sizeof(log_buffer));
1333
1334 // Validate UTF-8 in formatted message
1335 validate_log_message_utf8(log_buffer, "file-only log");
1336
1337 // Write to mmap if active, else to file
1338 if (log_mmap_is_active()) {
1339 log_mmap_write(LOG_INFO, NULL, 0, NULL, "%s", log_buffer);
1340 } else {
1341 int file_fd = atomic_load(&g_log.file);
1342 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
1343 write_to_log_file_atomic(log_buffer, msg_len);
1344 write_to_log_file_atomic("\n", 1);
1345 }
1346 }
1347}
void log_mmap_write(int level, const char *file, int line, const char *func, const char *fmt,...)
Definition mmap.c:312

References args, log_mmap_is_active(), log_mmap_write(), and safe_vsnprintf().

Referenced by platform_print_backtrace_symbols().

◆ log_get_color_array()

const char ** log_get_color_array ( void  )

Definition at line 1478 of file log/logging.c.

1478 {
1479 init_terminal_capabilities();
1480 /* Initialize colors if not already done */
1481 if (!g_log_colorscheme_initialized) {
1483 }
1484
1485 /* Safety check: if colors are not initialized, return NULL to prevent crashes from null pointers */
1486 if (!g_log_colorscheme_initialized) {
1487 return NULL;
1488 }
1489
1490 /* Return the compiled color scheme based on terminal capabilities
1491 * codes_16, codes_256, codes_truecolor are now proper arrays of pointers (const char *[8]),
1492 * so we can safely cast them to const char **. */
1493 if (g_terminal_caps.color_level >= TERM_COLOR_TRUECOLOR) {
1494 return (const char **)g_compiled_colors.codes_truecolor;
1495 } else if (g_terminal_caps.color_level >= TERM_COLOR_256) {
1496 return (const char **)g_compiled_colors.codes_256;
1497 } else {
1498 return (const char **)g_compiled_colors.codes_16;
1499 }
1500}
void log_init_colors(void)

References log_init_colors().

Referenced by log_level_color(), log_msg(), and log_recolor_plain_entry().

◆ log_get_force_stderr()

bool log_get_force_stderr ( void  )

Definition at line 650 of file log/logging.c.

650 {
651 return atomic_load(&g_log.force_stderr);
652}

Referenced by terminal_choose_log_fd().

◆ log_get_level()

log_level_t log_get_level ( void  )

Definition at line 627 of file log/logging.c.

627 {
628 return (log_level_t)atomic_load(&g_log.level);
629}

Referenced by options_init().

◆ log_get_template()

void * log_get_template ( void  )

Get the current log format template.

Returns
Opaque pointer to the current log format template (may be NULL if not set)

Returns the compiled log format template used by the logging system. This is used by platform code (e.g., backtrace formatting) to format log entries using the same template as the rest of the logging system. Returns void* (opaque) to avoid circular dependency with format.h.

Definition at line 1970 of file log/logging.c.

1970 {
1971 return (void *)g_log.format;
1972}

Referenced by platform_print_backtrace_symbols().

◆ log_get_terminal_output()

bool log_get_terminal_output ( void  )

Definition at line 642 of file log/logging.c.

642 {
643 return atomic_load(&g_log.terminal_output_enabled);
644}

Referenced by config_load_and_apply().

◆ log_init()

void log_init ( const char *  filename,
log_level_t  level,
bool  force_stderr,
bool  use_mmap 
)

Definition at line 507 of file log/logging.c.

507 {
508 // Initialize rotation mutex (only operation that uses a mutex)
509 if (!atomic_load(&g_log.rotation_mutex_initialized)) {
510 mutex_init(&g_log.rotation_mutex);
511 atomic_store(&g_log.rotation_mutex_initialized, true);
512 }
513
514 // Set basic config using atomic stores
515 atomic_store(&g_log.force_stderr, force_stderr);
516 bool preserve_terminal_output = atomic_load(&g_log.terminal_output_enabled);
517
518 // Close any existing file (atomic load/store)
519 int old_file = atomic_load(&g_log.file);
520 if (atomic_load(&g_log.initialized) && old_file >= 0 && old_file != STDERR_FILENO) {
521 platform_close(old_file);
522 atomic_store(&g_log.file, -1);
523 }
524
525 // Check LOG_LEVEL environment variable
526 const char *env_level_str = SAFE_GETENV("LOG_LEVEL");
527 if (env_level_str) {
528 atomic_store(&g_log.level, (int)parse_log_level_from_env());
529 } else {
530 atomic_store(&g_log.level, (int)level);
531 }
532
533 atomic_store(&g_log.level_manually_set, false);
534 atomic_store(&g_log.current_size, 0);
535
536 if (filename) {
537 SAFE_STRNCPY(g_log.filename, filename, sizeof(g_log.filename) - 1);
538
539 if (use_mmap) {
540 // Lock-free mmap path - writes go to mmap'd file
541 asciichat_error_t mmap_result = log_mmap_init_simple(filename, 0);
542 if (mmap_result == ASCIICHAT_OK) {
543 atomic_store(&g_log.file, -1); // No regular fd - using mmap for file output
544 } else {
545 // Mmap failed - use stderr only (atomic writes, lock-free)
546 if (preserve_terminal_output) {
547 safe_fprintf(stderr, "Mmap logging failed for %s, using stderr only (lock-free)\n", filename);
548 }
549 atomic_store(&g_log.file, STDERR_FILENO);
550 g_log.filename[0] = '\0';
551 }
552 } else {
553 // Lock-free file I/O path - uses atomic write() syscalls
554 int fd = platform_open(filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
555 atomic_store(&g_log.file, (fd >= 0) ? fd : STDERR_FILENO);
556 if (fd < 0) {
557 if (preserve_terminal_output) {
558 safe_fprintf(stderr, "Failed to open log file: %s\n", filename);
559 }
560 g_log.filename[0] = '\0';
561 }
562 }
563 } else {
564 atomic_store(&g_log.file, STDERR_FILENO);
565 g_log.filename[0] = '\0';
566 }
567
568 /* Initialize default log format (NULL means use mode-specific default) */
569 log_set_format(NULL, false);
570
571 atomic_store(&g_log.initialized, true);
572 atomic_store(&g_log.terminal_output_enabled, preserve_terminal_output);
573
574 // Reset terminal detection if needed
575 if (g_terminal_caps_initialized && !g_terminal_caps.detection_reliable) {
576 g_terminal_caps_initialized = false;
577 }
578
579 // Detect terminal capabilities
581
582 // NOTE: Color initialization happens separately via log_set_color_scheme()
583 // after options are parsed. Logging works without colors until then.
584 // NOTE: Grep filter initialization happens in main.c after options_init() completes.
585}
asciichat_error_t log_set_format(const char *format_str, bool console_only)
void log_redetect_terminal_capabilities(void)
int safe_fprintf(FILE *stream, const char *format,...)
Safe formatted output to file stream.
Definition system.c:480
int mutex_init(mutex_t *mutex)
Definition threading.c:16
int platform_open(const char *pathname, int flags,...)

References log_mmap_init_simple(), log_redetect_terminal_capabilities(), log_set_format(), mutex_init(), platform_close(), platform_open(), and safe_fprintf().

Referenced by asciichat_shared_init(), client_init_with_args(), main(), main(), mirror_init_with_args(), and options_init().

◆ log_init_colors()

void log_init_colors ( void  )

Definition at line 1517 of file log/logging.c.

1517 {
1518 /* Skip color initialization during terminal detection to avoid mutex deadlock */
1519 if (g_terminal_caps_detecting) {
1520 return;
1521 }
1522
1523 /* Skip color initialization before logging is fully initialized */
1524 if (!atomic_load(&g_log.initialized)) {
1525 return;
1526 }
1527
1528 if (g_log_colorscheme_initialized) {
1529 return;
1530 }
1531
1532 /* Get active color scheme - this ensures color system is initialized */
1533 const color_scheme_t *scheme = colorscheme_get_active_scheme();
1534 if (!scheme) {
1535 /* Don't mark as initialized if we can't get a color scheme - return NULL instead */
1536 return;
1537 }
1538
1539 /* Acquire mutex for compilation (mutex is now initialized by colorscheme_init) */
1540 mutex_lock(&g_colorscheme_mutex);
1541 /* Debug: Check if g_compiled_colors is actually zero-initialized */
1542 /* Zero the structure on first use to avoid freeing garbage pointers */
1543 /* (static = {0} produces garbage in this build for unknown reasons) */
1544 static bool first_compile = true;
1545 if (first_compile) {
1546 memset(&g_compiled_colors, 0, sizeof(g_compiled_colors));
1547 first_compile = false;
1548 }
1549
1550 /* Detect terminal background */
1551 terminal_background_t background = detect_terminal_background();
1552 /* Determine color mode for compilation */
1553 terminal_color_mode_t mode;
1554 if (g_terminal_caps.color_level >= TERM_COLOR_TRUECOLOR) {
1555 mode = TERM_COLOR_TRUECOLOR;
1556 } else if (g_terminal_caps.color_level >= TERM_COLOR_256) {
1557 mode = TERM_COLOR_256;
1558 } else {
1559 mode = TERM_COLOR_16;
1560 }
1561 /* Compile the color scheme to ANSI codes */
1562 asciichat_error_t result = colorscheme_compile_scheme(scheme, mode, background, &g_compiled_colors);
1563 g_log_colorscheme_initialized = true;
1564 mutex_unlock(&g_colorscheme_mutex);
1565
1566 /* Log outside of mutex lock to avoid recursive lock deadlock */
1567 if (result != ASCIICHAT_OK) {
1568 log_debug("Failed to compile color scheme: %d", result);
1569 }
1570}
const color_scheme_t * colorscheme_get_active_scheme(void)
terminal_background_t detect_terminal_background(void)
asciichat_error_t colorscheme_compile_scheme(const color_scheme_t *scheme, terminal_color_mode_t mode, terminal_background_t background, compiled_color_scheme_t *compiled)
mutex_t g_colorscheme_mutex
Definition colorscheme.c:38

References colorscheme_compile_scheme(), colorscheme_get_active_scheme(), detect_terminal_background(), and g_colorscheme_mutex.

Referenced by log_get_color_array(), and main().

◆ log_level_color()

const char * log_level_color ( log_color_t  color)

Definition at line 1502 of file log/logging.c.

1502 {
1503 const char **colors = log_get_color_array();
1504 if (colors == NULL) {
1505 return ""; /* Return empty string if colors not available */
1506 }
1507 if (color >= 0 && color <= LOG_COLOR_RESET) {
1508 return colors[color];
1509 }
1510 return colors[LOG_COLOR_RESET]; /* Return reset color if invalid */
1511}
const char ** log_get_color_array(void)

References log_get_color_array().

Referenced by colored_string().

◆ log_lock_terminal()

bool log_lock_terminal ( void  )

Definition at line 710 of file log/logging.c.

710 {
711 bool previous_state = atomic_exchange(&g_log.terminal_locked, true);
712 atomic_store(&g_log.terminal_owner_thread, (uint64_t)asciichat_thread_self());
713 return previous_state;
714}
asciichat_thread_t asciichat_thread_self(void)
Definition threading.c:54

References asciichat_thread_self().

Referenced by client_crypto_handshake(), client_main(), discovery_tui_select(), prompt_password(), prompt_unknown_host(), and session_display_render_frame().

◆ log_msg()

void log_msg ( log_level_t  level,
const char *  file,
int  line,
const char *  func,
const char *  fmt,
  ... 
)

Definition at line 946 of file log/logging.c.

946 {
947 // All state access uses atomic operations - fully lock-free
948 if (!atomic_load(&g_log.initialized)) {
949 return;
950 }
951 if (level < (log_level_t)atomic_load(&g_log.level)) {
952 return;
953 }
954 /* =========================================================================
955 * MMAP PATH: When mmap logging is active, writes go to mmap'd file
956 * ========================================================================= */
957 bool mmap_active = log_mmap_is_active();
958 if (mmap_active) {
959 maybe_rotate_log();
960
961 va_list args;
962 va_start(args, fmt);
963 char msg_buffer[LOG_MMAP_MSG_BUFFER_SIZE];
964 int msg_len = safe_vsnprintf(msg_buffer, sizeof(msg_buffer), fmt, args);
965 va_end(args);
966
967 // Truncate at whole line boundaries to avoid UTF-8 issues
968 if (msg_len > 0) {
969 msg_len = truncate_at_whole_line(msg_buffer, msg_len, sizeof(msg_buffer));
970 }
971
972 // Validate UTF-8 in formatted message
973 validate_log_message_utf8(msg_buffer, "mmap log message");
974
975 log_mmap_write(level, file, line, func, "%s", msg_buffer);
976
977 // Terminal output (check with atomic loads)
978 if (atomic_load(&g_log.terminal_output_enabled) && !atomic_load(&g_log.terminal_locked)) {
979 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
980 uint64_t time_ns = time_get_realtime_ns();
982
983 // Choose output stream using unified routing logic
984 int fd = terminal_choose_log_fd(level);
985 FILE *output_stream = (fd == STDERR_FILENO) ? stderr : stdout;
986 // Check if colors should be used
987 // Priority 1: If --color was explicitly passed, force colors
988 extern bool g_color_flag_passed;
989 extern bool g_color_flag_value;
990 bool use_colors = true; // Default: enable colors
992 use_colors = false; // --color=false explicitly disables colors
993 }
994 // Priority 2: If --color NOT explicitly passed, enable colors by default
995
996 char header_buffer[512];
997 int header_len = format_log_header(header_buffer, sizeof(header_buffer), level, time_buf, file, line, func,
998 use_colors, time_ns);
999
1000 if (header_len >= 0 && header_len < (int)sizeof(header_buffer)) {
1001 // Platform-specific log hook (e.g., for WASM browser console)
1002 platform_log_hook(level, msg_buffer);
1003
1004 if (use_colors) {
1005 const char *colorized_msg = colorize_log_message(msg_buffer);
1006 const char **colors = log_get_color_array();
1007 if (colors) {
1008 safe_fprintf(output_stream, "%s%s%s%s\n", header_buffer, colors[LOG_COLOR_RESET], colorized_msg,
1009 colors[LOG_COLOR_RESET]);
1010 } else {
1011 safe_fprintf(output_stream, "%s%s\n", header_buffer, colorized_msg);
1012 }
1013 } else {
1014 safe_fprintf(output_stream, "%s%s\n", header_buffer, msg_buffer);
1015 }
1016 (void)fflush(output_stream);
1017 }
1018 }
1019 return;
1020 }
1021 /* =========================================================================
1022 * FILE I/O PATH: Lock-free using atomic write() syscalls
1023 * ========================================================================= */
1024 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
1025 uint64_t time_ns = time_get_realtime_ns();
1027 // Format message for file output
1028 char log_buffer[LOG_MSG_BUFFER_SIZE];
1029 va_list args;
1030 va_start(args, fmt);
1031 int header_len = format_log_header(log_buffer, sizeof(log_buffer), level, time_buf, file, line, func, false, time_ns);
1032 if (header_len < 0 || header_len >= (int)sizeof(log_buffer)) {
1033 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format log header");
1034 va_end(args);
1035 return;
1036 }
1037 int msg_len = header_len;
1038 int formatted_len = safe_vsnprintf(log_buffer + header_len, sizeof(log_buffer) - (size_t)header_len, fmt, args);
1039 if (formatted_len < 0) {
1040 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format log message");
1041 va_end(args);
1042 return;
1043 }
1044
1045 msg_len += formatted_len;
1046 // Truncate at whole line boundaries to avoid UTF-8 issues
1047 msg_len = truncate_at_whole_line(log_buffer, msg_len, sizeof(log_buffer));
1048 // Add newline if there's room and message doesn't already end with one
1049 if (msg_len > 0 && msg_len < (int)sizeof(log_buffer) - 1) {
1050 if (log_buffer[msg_len - 1] != '\n') {
1051 log_buffer[msg_len++] = '\n';
1052 log_buffer[msg_len] = '\0';
1053 }
1054 }
1055 va_end(args);
1056 // Validate UTF-8 in formatted message
1057 validate_log_message_utf8(log_buffer, "leveled log message");
1058
1059 // Extract the user message part (without header) for JSON logging
1060 const char *user_message = log_buffer + header_len;
1061
1062 // For JSON output: strip automatic trailing newline (keep explicit ones)
1063 char json_message_buf[LOG_MSG_BUFFER_SIZE];
1064 const char *json_message = user_message;
1065 size_t user_msg_len = strlen(user_message);
1066
1067 // If message ends with newline and it was added automatically (not in original message)
1068 if (user_msg_len > 0 && user_message[user_msg_len - 1] == '\n') {
1069 // The newline was added automatically if the message part didn't end with one
1070 // before we added it on line 1067. We can check this by looking at msg_len - 1
1071 // If msg_len >= header_len + 2, then msg_len - 2 would be the char before the newline
1072 int msg_content_len = msg_len - header_len;
1073 if (msg_content_len > 1 && log_buffer[msg_len - 2] != '\n') {
1074 // Newline was added automatically - strip it for JSON
1075 if (user_msg_len - 1 < sizeof(json_message_buf)) {
1076 memcpy(json_message_buf, user_message, user_msg_len - 1);
1077 json_message_buf[user_msg_len - 1] = '\0';
1078 json_message = json_message_buf;
1079 }
1080 }
1081 }
1082
1083 // Check if JSON format is enabled
1084 int json_fd = atomic_load(&g_log.json_file);
1085 bool json_format_enabled = (json_fd >= 0);
1086
1087 // If JSON format is enabled, output ONLY JSON (skip text output)
1088 if (json_format_enabled) {
1089 // Output JSON to the JSON file descriptor
1090 log_json_write(json_fd, level, time_ns, file, line, func, json_message);
1091 // Also output JSON to console using unified routing logic, respecting quiet flag
1092 if (atomic_load(&g_log.terminal_output_enabled)) {
1093 int console_fd = terminal_choose_log_fd(level);
1094 log_json_write(console_fd, level, time_ns, file, line, func, json_message);
1095 }
1096 } else {
1097 // Text format: output to file and terminal
1098 // Write to file (atomic write syscall)
1099 int file_fd = atomic_load(&g_log.file);
1100 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
1101 write_to_log_file_atomic(log_buffer, msg_len);
1102 }
1103
1104 // Write to terminal (atomic state checks)
1105 va_list args_terminal;
1106 va_start(args_terminal, fmt);
1107 write_to_terminal_atomic(level, time_buf, file, line, func, fmt, args_terminal, time_ns);
1108 va_end(args_terminal);
1109 }
1110}
const char * colorize_log_message(const char *message)
Colorize a log message for terminal output.
Definition colorize.c:513
ASCIICHAT_API bool g_color_flag_value
Definition common.c:50
ASCIICHAT_API bool g_color_flag_passed
Definition common.c:49
void platform_log_hook(log_level_t level, const char *message)
Definition console.c:40
void log_json_write(int fd, log_level_t level, uint64_t time_nanoseconds, const char *file, int line, const char *func, const char *message)
Definition json.c:93
size_t get_current_time_formatted(char *time_buf)

References args, colorize_log_message(), g_color_flag_passed, g_color_flag_value, get_current_time_formatted(), log_get_color_array(), log_json_write(), log_mmap_is_active(), log_mmap_write(), LOGGING_INTERNAL_ERROR, platform_log_hook(), safe_fprintf(), safe_vsnprintf(), terminal_choose_log_fd(), and time_get_realtime_ns().

Referenced by handle_remote_log_packet_from_client().

◆ log_net_message()

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

Definition at line 1409 of file log/logging.c.

1411 {
1412 va_list args;
1413 va_start(args, fmt);
1414 asciichat_error_t result =
1415 log_network_message_internal(sockfd, crypto_ctx, level, direction, file, line, func, fmt, args);
1416 va_end(args);
1417 return result;
1418}

References args.

◆ log_network_message()

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

Definition at line 1399 of file log/logging.c.

1400 {
1401 va_list args;
1402 va_start(args, fmt);
1403 asciichat_error_t result =
1404 log_network_message_internal(sockfd, crypto_ctx, level, direction, NULL, 0, NULL, fmt, args);
1405 va_end(args);
1406 return result;
1407}

References args.

Referenced by disconnect_client_for_bad_data(), and threaded_send_client_join_packet().

◆ log_plain_msg()

void log_plain_msg ( const char *  fmt,
  ... 
)

Definition at line 1133 of file log/logging.c.

1133 {
1134 if (!atomic_load(&g_log.initialized)) {
1135 return;
1136 }
1137
1138 if (shutdown_is_requested()) {
1139 return;
1140 }
1141
1142 char log_buffer[LOG_MSG_BUFFER_SIZE];
1143 va_list args;
1144 va_start(args, fmt);
1145 int msg_len = safe_vsnprintf(log_buffer, sizeof(log_buffer), fmt, args);
1146 va_end(args);
1147
1148 if (msg_len <= 0) {
1149 return;
1150 }
1151
1152 // Truncate at whole line boundaries to avoid UTF-8 issues
1153 msg_len = truncate_at_whole_line(log_buffer, msg_len, sizeof(log_buffer));
1154
1155 // Validate UTF-8 in formatted message
1156 validate_log_message_utf8(log_buffer, "plain text log");
1157
1158 // Write to mmap if active
1159 if (log_mmap_is_active()) {
1160 log_mmap_write(LOG_INFO, NULL, 0, NULL, "%s", log_buffer);
1161 } else {
1162 // Write to file with headers (atomic write syscall)
1163 int file_fd = atomic_load(&g_log.file);
1164 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
1165 // Add header with timestamp and log level to file output
1166 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
1167 uint64_t time_ns = time_get_realtime_ns();
1169
1170 char header_buffer[512];
1171 int header_len = format_log_header(header_buffer, sizeof(header_buffer), LOG_INFO, time_buf, "lib/log/logging.c",
1172 0, "log_plain_msg", false, time_ns);
1173
1174 if (header_len > 0) {
1175 write_to_log_file_atomic(header_buffer, header_len);
1176 }
1177 write_to_log_file_atomic(log_buffer, msg_len);
1178 write_to_log_file_atomic("\n", 1);
1179 }
1180 }
1181
1182 // Terminal output (atomic state checks)
1183 if (!atomic_load(&g_log.terminal_output_enabled)) {
1184 return;
1185 }
1186 if (atomic_load(&g_log.terminal_locked)) {
1187 uint64_t owner = atomic_load(&g_log.terminal_owner_thread);
1188 if (owner != (uint64_t)asciichat_thread_self()) {
1189 return;
1190 }
1191 }
1192
1193 // Check if JSON format is enabled
1194 int json_fd = atomic_load(&g_log.json_file);
1195 bool json_format_enabled = (json_fd >= 0);
1196
1197 if (json_format_enabled) {
1198 // Output JSON for plain messages too
1199 uint64_t time_ns = time_get_realtime_ns();
1200 int console_fd = terminal_choose_log_fd(LOG_INFO);
1201 log_json_write(console_fd, LOG_INFO, time_ns, __FILE__, __LINE__, "log_plain_msg", log_buffer);
1202 } else {
1203 // Choose output stream using unified routing logic (LOG_INFO level)
1204 int fd = terminal_choose_log_fd(LOG_INFO);
1205 FILE *output_stream = (fd == STDERR_FILENO) ? stderr : stdout;
1206
1207 // Apply colorization for TTY output
1209 const char *colorized_msg = colorize_log_message(log_buffer);
1210 safe_fprintf(output_stream, "%s\n", colorized_msg);
1211 } else {
1212 safe_fprintf(output_stream, "%s\n", log_buffer);
1213 }
1214 (void)fflush(output_stream);
1215 }
1216}
bool shutdown_is_requested(void)
Definition common.c:65
bool terminal_should_color_output(int fd)
Determine if color output should be used.

References args, asciichat_thread_self(), colorize_log_message(), get_current_time_formatted(), log_json_write(), log_mmap_is_active(), log_mmap_write(), safe_fprintf(), safe_vsnprintf(), shutdown_is_requested(), terminal_choose_log_fd(), terminal_should_color_output(), and time_get_realtime_ns().

◆ log_plain_stderr_msg()

void log_plain_stderr_msg ( const char *  fmt,
  ... 
)

Definition at line 1288 of file log/logging.c.

1288 {
1289 if (!atomic_load(&g_log.initialized)) {
1290 return;
1291 }
1292 if (shutdown_is_requested()) {
1293 return;
1294 }
1295
1296 va_list args;
1297 va_start(args, fmt);
1298 log_plain_stderr_internal_atomic(fmt, args, true);
1299 va_end(args);
1300}

References args, and shutdown_is_requested().

◆ log_plain_stderr_nonewline_msg()

void log_plain_stderr_nonewline_msg ( const char *  fmt,
  ... 
)

Definition at line 1302 of file log/logging.c.

1302 {
1303 if (!atomic_load(&g_log.initialized)) {
1304 return;
1305 }
1306 if (shutdown_is_requested()) {
1307 return;
1308 }
1309
1310 va_list args;
1311 va_start(args, fmt);
1312 log_plain_stderr_internal_atomic(fmt, args, false);
1313 va_end(args);
1314}

References args, and shutdown_is_requested().

◆ log_recolor_plain_entry()

size_t log_recolor_plain_entry ( const char *  plain_line,
char *  colored_buf,
size_t  buf_size 
)

Recolor a plain (non-colored) log line with proper ANSI codes.

Parses plain log format: [TIMESTAMP] [LEVEL] [tid:THREAD_ID] FILE:LINE in FUNC(): MESSAGE And recolors it with appropriate ANSI codes matching the colored format.

Definition at line 1680 of file log/logging.c.

1680 {
1681 if (!plain_line || !colored_buf || buf_size < 128) {
1682 return 0;
1683 }
1684
1685 static char work_buffer[LOG_MSG_BUFFER_SIZE + 1024];
1686
1687 // Parse format FIRST, regardless of color availability
1688 // Parse format: [TIMESTAMP] [LEVEL] [tid:THREAD_ID] FILE:LINE in FUNC(): MESSAGE
1689 const char *p = plain_line;
1690
1691 // Extract timestamp [TIMESTAMP]
1692 if (*p != '[') {
1693 return 0; // Invalid format
1694 }
1695 p++;
1696 const char *timestamp_start = p;
1697
1698 // Find the closing ] for timestamp - look for pattern that ends with proper timestamp format
1699 // Valid timestamp: HH:MM:SS.UUUUUU (time with microseconds, no date)
1700 while (*p && *p != ']') {
1701 p++;
1702 }
1703 if (*p != ']') {
1704 return 0; // Malformed - no closing bracket
1705 }
1706
1707 size_t timestamp_len = p - timestamp_start;
1708 char timestamp[64];
1709 if (timestamp_len >= sizeof(timestamp) || timestamp_len == 0) {
1710 return 0; // Invalid timestamp - empty or too long
1711 }
1712 SAFE_STRNCPY(timestamp, timestamp_start, timestamp_len);
1713 timestamp[timestamp_len] = '\0';
1714 p++; // Skip ]
1715
1716 // Skip whitespace after timestamp
1717 while (*p && *p == ' ') {
1718 p++;
1719 }
1720
1721 // Extract level [LEVEL]
1722 if (*p != '[') {
1723 return 0; // Missing opening bracket for level
1724 }
1725 p++;
1726 const char *level_start = p;
1727 while (*p && *p != ']') {
1728 p++;
1729 }
1730 if (*p != ']') {
1731 return 0; // Missing closing bracket for level
1732 }
1733 size_t level_len = p - level_start;
1734 char level_str[16];
1735 if (level_len >= sizeof(level_str) || level_len == 0) {
1736 return 0; // Level string too long or empty
1737 }
1738 /* Use strncpy directly for substring extraction (not null-terminated source) */
1739 strncpy(level_str, level_start, level_len);
1740 level_str[level_len] = '\0';
1741
1742 // Determine log level for color selection
1743 log_level_t level = LOG_INFO; // Default
1744 if (strstr(level_str, "DEV") || strstr(level_str, "DEBUG")) {
1745 level = LOG_DEBUG;
1746 } else if (strstr(level_str, "INFO")) {
1747 level = LOG_INFO;
1748 } else if (strstr(level_str, "WARN")) {
1749 level = LOG_WARN;
1750 } else if (strstr(level_str, "ERROR")) {
1751 level = LOG_ERROR;
1752 } else if (strstr(level_str, "FATAL")) {
1753 level = LOG_FATAL;
1754 }
1755
1756 p++; // Skip ]
1757
1758 // Skip whitespace after level
1759 while (*p && *p == ' ') {
1760 p++;
1761 }
1762
1763 // Extract thread ID [tid:THREAD_ID] - optional field
1764 uint64_t tid = 0;
1765 if (*p == '[' && strncmp(p, "[tid:", 5) == 0) {
1766 p += 5;
1767 char *tid_end = NULL;
1768 tid = strtoull(p, &tid_end, 10);
1769 if (!tid_end || *tid_end != ']') {
1770 // tid parsing failed, but continue anyway (might still recover)
1771 // Try to find the closing bracket
1772 while (*p && *p != ']') {
1773 p++;
1774 }
1775 if (*p == ']') {
1776 p++;
1777 }
1778 } else {
1779 p = tid_end + 1; // Skip past the ]
1780 }
1781 // Skip whitespace after tid
1782 while (*p && *p == ' ') {
1783 p++;
1784 }
1785 }
1786 // tid is optional - continue parsing if not present
1787
1788 // Extract file path (everything up to :LINE)
1789 const char *file_start = p;
1790 while (*p && *p != ':') {
1791 p++;
1792 }
1793 if (*p != ':') {
1794 return 0; // Malformed
1795 }
1796 size_t file_len = p - file_start;
1797 char file_path[256];
1798 if (file_len >= sizeof(file_path)) {
1799 return 0;
1800 }
1801 /* Use strncpy directly for substring extraction (not null-terminated source) */
1802 strncpy(file_path, file_start, file_len);
1803 file_path[file_len] = '\0';
1804 p++; // Skip :
1805
1806 // Extract line number (digits only)
1807 int line_num = 0;
1808 const char *line_start = p;
1809 while (*p && isdigit((unsigned char)*p)) {
1810 p++;
1811 }
1812 if (p == line_start) {
1813 return 0; // No line number
1814 }
1815 line_num = (int)strtol(line_start, NULL, 10);
1816
1817 // Skip whitespace and find "in" keyword (be very lenient)
1818 while (*p && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
1819 p++;
1820 }
1821
1822 // Try to find "in " - if not found, might still be valid, just harder to parse
1823 if (strncmp(p, "in ", 3) == 0) {
1824 p += 3; // Skip "in "
1825 } else if (*p == 'i' && *(p + 1) == 'n' && (*(p + 2) == ' ' || *(p + 2) == '\t')) {
1826 // Allow tab after "in"
1827 p += 2; // Skip "in"
1828 while (*p && (*p == ' ' || *p == '\t')) {
1829 p++;
1830 }
1831 } else {
1832 // Missing "in" keyword - this is a format error
1833 return 0;
1834 }
1835
1836 // Extract function name (everything up to "()")
1837 const char *func_start = p;
1838 while (*p && *p != '(') {
1839 p++;
1840 }
1841 if (*p != '(' || func_start == p) {
1842 return 0; // Missing function name or parentheses
1843 }
1844 size_t func_len = p - func_start;
1845
1846 // Trim trailing whitespace from function name
1847 while (func_len > 0 && (func_start[func_len - 1] == ' ' || func_start[func_len - 1] == '\t')) {
1848 func_len--;
1849 }
1850
1851 char func_name[256];
1852 if (func_len >= sizeof(func_name)) {
1853 func_len = sizeof(func_name) - 1;
1854 }
1855 if (func_len > 0) {
1856 /* Use strncpy directly for substring extraction (not null-terminated source) */
1857 strncpy(func_name, func_start, func_len);
1858 }
1859 func_name[func_len] = '\0';
1860
1861 // Skip "(" and ")" - be lenient about what's between them
1862 if (*p == '(') {
1863 p++;
1864 while (*p && *p != ')') {
1865 p++;
1866 }
1867 if (*p == ')') {
1868 p++;
1869 }
1870 }
1871
1872 // Skip whitespace and optional colon(s) and other separators
1873 while (*p && (*p == ' ' || *p == ':' || *p == '\t')) {
1874 p++;
1875 }
1876
1877 // Remaining is the message
1878 const char *message = p;
1879
1880 // Format is valid, get colors from logging system
1881 const char **colors = log_get_color_array();
1882 if (!colors) {
1883 // No colors available, return plain text
1884 static bool warned_once = false;
1885 if (!warned_once) {
1886 log_debug("WARNING: log_recolor_plain_entry() called but colors not initialized - returning plain text");
1887 warned_once = true;
1888 }
1889 size_t len = strlen(plain_line);
1890 if (len >= buf_size) {
1891 return 0;
1892 }
1893 SAFE_STRNCPY(colored_buf, plain_line, buf_size - 1);
1894 colored_buf[buf_size - 1] = '\0';
1895 return len;
1896 }
1897
1898 // Build colored output
1899 const char *level_color = colors[level];
1900 const char *reset = colors[LOG_COLOR_RESET];
1901 const char *file_color = colors[1]; // DEBUG/Cyan
1902 const char *line_color = colors[6]; // GREY (matching tid)
1903 const char *func_color = colors[0]; // DEV/Orange
1904 const char *tid_color = colors[6]; // GREY
1905
1906 int len = safe_snprintf(work_buffer, sizeof(work_buffer),
1907 "[%s%s%s] [%s%s%s] [tid:%s%llu%s] %s%s%s:%s%d%s in %s%s%s(): %s", level_color, timestamp,
1908 reset, level_color, level_str, reset, tid_color, (unsigned long long)tid, reset, file_color,
1909 file_path, reset, line_color, line_num, reset, func_color, func_name, reset, message);
1910
1911 if (len <= 0 || len >= (int)sizeof(work_buffer)) {
1912 return 0;
1913 }
1914
1915 // Colorize the message part
1916 const char *colorized_msg = colorize_log_message(message);
1917 len = safe_snprintf(work_buffer, sizeof(work_buffer),
1918 "[%s%s%s] [%s%s%s] [tid:%s%llu%s] %s%s%s:%s%d%s in %s%s%s(): %s", level_color, timestamp, reset,
1919 level_color, level_str, reset, tid_color, (unsigned long long)tid, reset, file_color, file_path,
1920 reset, line_color, line_num, reset, func_color, func_name, reset, colorized_msg);
1921
1922 if (len <= 0 || len >= (int)sizeof(work_buffer) || len >= (int)buf_size) {
1923 return 0;
1924 }
1925
1926 SAFE_STRNCPY(colored_buf, work_buffer, buf_size - 1);
1927 colored_buf[buf_size - 1] = '\0';
1928 return (size_t)len;
1929}
char file_path[PLATFORM_MAX_PATH_LENGTH]
Definition mmap.c:39

References colorize_log_message(), file_path, log_get_color_array(), and safe_snprintf().

◆ log_redetect_terminal_capabilities()

void log_redetect_terminal_capabilities ( void  )

Definition at line 1453 of file log/logging.c.

1453 {
1454 // Guard against recursion
1455 if (g_terminal_caps_detecting) {
1456 return;
1457 }
1458
1459 // Detect if not initialized, or if we're using defaults (not reliably detected)
1460 // This ensures we get proper detection after logging is ready, replacing any defaults
1461 // Once we have reliable detection, never re-detect to keep colors consistent
1462 if (!g_terminal_caps_initialized || !g_terminal_caps.detection_reliable) {
1463 g_terminal_caps_detecting = true;
1464 g_terminal_caps = detect_terminal_capabilities();
1465 g_terminal_caps_detecting = false;
1466 g_terminal_caps_initialized = true;
1467
1468 // Now log the capabilities AFTER colors are set, so this log uses the correct colors
1469 log_debug("Terminal capabilities: color_level=%d, capabilities=0x%x, utf8=%s, fps=%d", g_terminal_caps.color_level,
1470 g_terminal_caps.capabilities, g_terminal_caps.utf8_support ? "yes" : "no", g_terminal_caps.desired_fps);
1471
1472 // Now that we've detected once with reliable results, keep these colors consistent for all future logs
1473 }
1474 // Once initialized with reliable detection, never re-detect to keep colors consistent
1475}
terminal_capabilities_t detect_terminal_capabilities(void)

References detect_terminal_capabilities().

Referenced by log_init(), and main().

◆ log_set_color_scheme()

void log_set_color_scheme ( const color_scheme_t *  scheme)

Definition at line 1572 of file log/logging.c.

1572 {
1573 if (!scheme) {
1574 return;
1575 }
1576
1577 /* Mutex is managed by colors.c - just use it */
1578 mutex_lock(&g_colorscheme_mutex);
1579
1580 /* Detect terminal background */
1581 terminal_background_t background = detect_terminal_background();
1582
1583 /* Determine color mode for compilation */
1584 terminal_color_mode_t mode;
1585 if (g_terminal_caps.color_level >= TERM_COLOR_TRUECOLOR) {
1586 mode = TERM_COLOR_TRUECOLOR;
1587 } else if (g_terminal_caps.color_level >= TERM_COLOR_256) {
1588 mode = TERM_COLOR_256;
1589 } else {
1590 mode = TERM_COLOR_16;
1591 }
1592
1593 /* Compile the new color scheme */
1594 asciichat_error_t result = colorscheme_compile_scheme(scheme, mode, background, &g_compiled_colors);
1595
1596 g_log_colorscheme_initialized = true;
1597 mutex_unlock(&g_colorscheme_mutex);
1598
1599 /* Log outside of mutex lock to avoid recursive lock deadlock */
1600 if (result != ASCIICHAT_OK) {
1601 log_debug("Failed to compile color scheme: %d", result);
1602 }
1603}

References colorscheme_compile_scheme(), detect_terminal_background(), and g_colorscheme_mutex.

Referenced by main(), and options_init().

◆ log_set_flush_delay()

void log_set_flush_delay ( unsigned int  delay_ms)

Definition at line 723 of file log/logging.c.

723 {
724 atomic_store(&g_log.flush_delay_ms, delay_ms);
725}

◆ log_set_force_stderr()

void log_set_force_stderr ( bool  enabled)

Definition at line 646 of file log/logging.c.

646 {
647 atomic_store(&g_log.force_stderr, enabled);
648}
bool enabled
Is filtering active?
Definition grep.c:78

References enabled.

Referenced by main(), session_client_like_run(), and session_display_create().

◆ log_set_format()

asciichat_error_t log_set_format ( const char *  format_str,
bool  console_only 
)

Definition at line 669 of file log/logging.c.

669 {
670 /* Free old format if it exists */
671 if (g_log.format) {
672 log_template_free(g_log.format);
673 g_log.format = NULL;
674 }
675 if (g_log.format_console_only) {
676 log_template_free(g_log.format_console_only);
677 g_log.format_console_only = NULL;
678 }
679
680 /* Use default format if NULL or empty string */
681 const char *format_to_use = (format_str && format_str[0] != '\0') ? format_str : OPT_LOG_TEMPLATE_DEFAULT;
682 bool is_custom = (format_str && format_str[0] != '\0');
683
684 /* Parse the format string (always parse, never skip) */
685 log_template_t *parsed_format = log_template_parse(format_to_use, false);
686 if (!parsed_format) {
687 log_error("Failed to parse log format: %s", format_to_use);
688 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid log format string");
689 }
690
691 /* If console_only is true, we also need the default format for file output */
692 if (console_only && is_custom) {
693 log_template_t *default_format = log_template_parse(OPT_LOG_TEMPLATE_DEFAULT, false);
694 if (!default_format) {
695 log_template_free(parsed_format);
696 log_error("Failed to parse default log format");
697 return SET_ERRNO(ERROR_INVALID_STATE, "Failed to parse default format");
698 }
699 g_log.format = default_format;
700 g_log.format_console_only = parsed_format;
701 } else {
702 g_log.format = parsed_format;
703 g_log.format_console_only = NULL;
704 }
705
706 atomic_store(&g_log.has_custom_format, is_custom);
707 return ASCIICHAT_OK;
708}
log_template_t * log_template_parse(const char *format_str, bool console_only)
Definition log/format.c:289

References log_template_free(), and log_template_parse().

Referenced by log_init(), and main().

◆ log_set_json_output()

void log_set_json_output ( int  fd)

Definition at line 654 of file log/logging.c.

654 {
655 atomic_store(&g_log.json_file, fd);
656}

Referenced by main().

◆ log_set_level()

void log_set_level ( log_level_t  level)

Definition at line 622 of file log/logging.c.

622 {
623 atomic_store(&g_log.level, (int)level);
624 atomic_store(&g_log.level_manually_set, true);
625}

Referenced by options_init().

◆ log_set_terminal_output()

void log_set_terminal_output ( bool  enabled)

Definition at line 631 of file log/logging.c.

631 {
632 // Respect --quiet flag: if quiet is set, never enable terminal output
633 // But allow disabling even if options are unavailable (for shutdown cleanup)
634 const options_t *opts = options_get();
635
636 if (enabled && opts && opts->quiet) {
637 return; // Silently ignore attempts to enable terminal output when --quiet is set
638 }
639 atomic_store(&g_log.terminal_output_enabled, enabled);
640}
const options_t * options_get(void)
Definition rcu.c:347

References enabled, and options_get().

Referenced by display_disable_logging_for_first_frame(), main(), options_init(), server_connection_cleanup(), server_connection_close(), server_connection_establish(), session_client_like_run(), and session_render_loop().

◆ log_shutdown_begin()

void log_shutdown_begin ( void  )

Definition at line 1639 of file log/logging.c.

1639 {
1640 if (g_shutdown_in_progress) {
1641 return; /* Already in shutdown phase */
1642 }
1643
1644 /* Save current terminal output state and disable console output */
1645 g_shutdown_saved_terminal_output = atomic_load(&g_log.terminal_output_enabled);
1646 atomic_store(&g_log.terminal_output_enabled, false);
1647 g_shutdown_in_progress = true;
1648}

Referenced by asciichat_shared_destroy().

◆ log_shutdown_end()

void log_shutdown_end ( void  )

Definition at line 1650 of file log/logging.c.

1650 {
1651 if (!g_shutdown_in_progress) {
1652 return; /* Not in shutdown phase, skip terminal state restoration */
1653 }
1654
1655 /* Restore previous terminal output state */
1656 atomic_store(&g_log.terminal_output_enabled, g_shutdown_saved_terminal_output);
1657 g_shutdown_in_progress = false;
1658}

Referenced by asciichat_shared_destroy().

◆ log_terminal_msg()

void log_terminal_msg ( log_level_t  level,
const char *  file,
int  line,
const char *  func,
const char *  fmt,
  ... 
)

Definition at line 1112 of file log/logging.c.

1112 {
1113 // Lock-free: only uses atomic loads, no mutex
1114 if (!atomic_load(&g_log.initialized)) {
1115 return;
1116 }
1117
1118 if (level < (log_level_t)atomic_load(&g_log.level)) {
1119 return;
1120 }
1121
1122 // Terminal output only - no file/mmap writing
1123 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
1124 uint64_t time_ns = time_get_realtime_ns();
1126
1127 va_list args;
1128 va_start(args, fmt);
1129 write_to_terminal_atomic(level, time_buf, file, line, func, fmt, args, time_ns);
1130 va_end(args);
1131}

References args, get_current_time_formatted(), and time_get_realtime_ns().

◆ log_truncate_if_large()

void log_truncate_if_large ( void  )

Definition at line 727 of file log/logging.c.

727 {
728 // Log rotation is inherently racy without locks - best-effort only
729 // For reliable rotation, use mmap mode or external logrotate
730 int file = atomic_load(&g_log.file);
731 if (file >= 0 && file != STDERR_FILENO && strlen(g_log.filename) > 0) {
732 struct stat st;
733 if (fstat(file, &st) == 0 && st.st_size > MAX_LOG_SIZE) {
734 atomic_store(&g_log.current_size, (size_t)st.st_size);
735 }
736 }
737}

Referenced by main().

◆ log_unlock_terminal()

void log_unlock_terminal ( bool  previous_state)

Definition at line 716 of file log/logging.c.

716 {
717 atomic_store(&g_log.terminal_locked, previous_state);
718 if (!previous_state) {
719 atomic_store(&g_log.terminal_owner_thread, 0);
720 }
721}

Referenced by client_crypto_handshake(), discovery_tui_select(), prompt_password(), prompt_unknown_host(), and session_display_render_frame().