ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
Logging System

📝 Logging API with multiple log levels and terminal output control More...

Files

file  apple.c
 Apple unified logging (os_log) integration.
file  colorize.c
 Log message colorization for terminal output.
file  format.c
 Log format parser implementation.
file  json.c
 📝 JSON structured logging output using yyjson
file  log.c
 📝 Multi-level logging with terminal color support, file rotation, and async output
file  mmap.c
 Lock-free memory-mapped text logging implementation.
file  named.c
 Format named objects in log messages (replaces hex addresses with type/name descriptions).
file  wasm.c
 WASM logging support for routing to browser console.
file  colorize.h
 Log message colorization for terminal output.
file  json.h
 📝 JSON structured logging output
file  log.h
 📝 Logging API with multiple log levels and terminal output control
file  mmap.h
 Lock-free memory-mapped text logging with crash safety.
file  named.h
 Format named objects in log messages (replaces hex addresses with type/name descriptions).
file  types.h
 Log level types and constants.

Data Structures

struct  log_buffer_entry_t
 Heap-backed message waiting for the logging output thread. More...

Macros

#define LOG_ATOMIC_UINT64   atomic_t
#define LOG_ATOMIC_UINT64_INIT(val)
#define DEFAULT_LOG_LEVEL   LOG_DEBUG
 Default log level for debug builds (DEBUG and above).
#define MAX_LOG_SIZE   (3 * 1024 * 1024)
 Maximum log file size in bytes (3MB) before rotation.
#define MAX_TERMINAL_BUFFER_SIZE   (64 * 1024)
 Maximum size of terminal output buffer (64KB).
#define MAX_TERMINAL_BUFFER_ENTRIES   256
 Maximum number of buffered log entries.
#define LOG_MSG_BUFFER_SIZE   4096
 Maximum size of a single log message (including formatting).
#define LOG_MMAP_MSG_BUFFER_SIZE   1024
 Maximum size of a log message in mmap mode.
#define LOG_HEADER_BUFFER_SIZE   512
 Maximum size of a log header (timestamp, level, file:line:func).
#define LOG_TIMESTAMP_BUFFER_SIZE   32
 Maximum size of a timestamp string.
#define LOG_TERMINAL_BUFFER_SIZE   16384
#define LOG_TERMINAL_ENTRY_BUFFER_SIZE   (LOG_HEADER_BUFFER_SIZE + LOG_TERMINAL_BUFFER_SIZE)
#define log_colored_msg(level, colored_message, plain_message)
#define log_terminal_colored_msg(level, colored_message, plain_message)
#define log_plain(...)
 Plain logging - writes to both log file and stderr without timestamps or log levels.
#define log_plain_stderr(...)
 Plain logging to stderr with newline.
#define log_plain_stdout(...)
 Plain logging to stdout with newline.
#define log_plain_stderr_nonewline(...)
 Plain logging to stderr without newline - for interactive prompts.
#define log_file(...)
 File-only logging - writes to log file only, no stderr output.
#define log_every(log_level, interval_us, fmt, ...)
 Rate-limited logging macro (thread-safe).
#define log_nth(log_level, n, fmt, ...)
 Log every nth call to this code location (thread-safe).
#define log_once(log_level, fmt, ...)
 Log exactly once per call site (thread-safe).
#define LOG_BIT(n)
 Logging bit conversion.
#define LOG_CLIENT_IMPL(client, level, fmt, ...)
#define log_debug_client(client, fmt, ...)
 Server sends DEBUG log message to client.
#define log_info_client(client, fmt, ...)
 Server sends INFO log message to client.
#define log_warn_client(client, fmt, ...)
 Server sends WARN log message to client.
#define log_error_client(client, fmt, ...)
 Server sends ERROR log message to client.
#define log_fatal_client(client, fmt, ...)
 Server sends FATAL log message to client.
#define LOG_SERVER_IMPL(transport, level, fmt, ...)
#define log_debug_server(transport, fmt, ...)
 Client sends DEBUG log message to server.
#define log_info_server(transport, fmt, ...)
 Client sends INFO log message to server.
#define log_warn_server(transport, fmt, ...)
 Client sends WARN log message to server.
#define log_error_server(transport, fmt, ...)
 Client sends ERROR log message to server.
#define log_fatal_server(transport, fmt, ...)
 Client sends FATAL log message to server.

Typedefs

typedef struct color_scheme_t color_scheme_t
typedef struct session_log_buffer session_log_buffer_t
 Thread-safe fixed-size history ring for session-screen log lines.
typedef enum remote_log_direction remote_log_direction_t
 Remote log packet direction enumeration.

Enumerations

enum  log_color_t {
  LOG_COLOR_DEV = 0 , LOG_COLOR_DEBUG = 1 , LOG_COLOR_INFO = 2 , LOG_COLOR_WARN = 3 ,
  LOG_COLOR_ERROR = 4 , LOG_COLOR_FATAL = 5 , LOG_COLOR_GREY = 6 , LOG_COLOR_RESET = 7 ,
  LOG_COLOR_WHITE = 8
}
 Color enum for logging - indexes into color arrays. More...
enum  log_level_t {
  ASCIICHAT_LOG_DEV = 0 , ASCIICHAT_LOG_DEBUG , ASCIICHAT_LOG_INFO , ASCIICHAT_LOG_WARN ,
  ASCIICHAT_LOG_ERROR , ASCIICHAT_LOG_FATAL
}
 Logging levels enumeration. More...
enum  remote_log_direction { REMOTE_LOG_DIRECTION_UNKNOWN = 0 , REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT = 1 , REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER = 2 }
 Remote log packet direction enumeration. More...

Functions

log_template_t * log_template_parse (const char *format_str, bool console_only)
 Parse a format string into compiled format structure.
void log_template_free (log_template_t *format)
 Free compiled format structure.
int log_template_apply (const log_template_t *format, char *buf, size_t buf_size, log_level_t level, const char *timestamp, const char *file, int line, const char *func, uint64_t tid, const char *message, bool use_colors, uint64_t time_nanoseconds)
 Apply format to a log entry and write result to buffer.
void log_init (const char *filename, log_level_t level, bool force_stderr, bool use_mmap)
 Initialize the logging system.
void log_destroy (void)
 Destroy the logging system and close log file.
void log_system_init (void)
 Initialize the logging system internal state (system-level init).
void log_system_destroy (void)
 Shutdown the logging system internal state (system-level cleanup).
void log_set_level (log_level_t level)
 Set the minimum log level.
log_level_t log_get_level (void)
 Get the current minimum log level.
asciichat_error_t log_set_format (const char *format_str, bool console_only)
 Set a custom log format string.
void log_set_terminal_output (bool enabled)
 Control stderr output to terminal.
bool log_get_terminal_output (void)
 Get current terminal output setting.
void log_set_force_stderr (bool enabled)
 Force all terminal log output to stderr.
bool log_get_force_stderr (void)
 Get current force_stderr setting.
void log_disable_file_output (void)
 Disable file output and use stderr instead.
void log_truncate_if_large (void)
 Manually truncate large log files.
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_terminal_msg (log_level_t level, const char *file, int line, const char *func, const char *fmt,...)
 Log a message to terminal only (no file output).
void log_colored_msg_impl (log_level_t level, const char *file, int line, const char *func, const char *colored_message, const char *plain_message)
 Write semantically colored terminal text and an ANSI-free log equivalent.
void log_terminal_colored_msg_impl (log_level_t level, const char *file, int line, const char *func, const char *colored_message, const char *plain_message)
 Write semantically colored terminal text without persistent logging.
void log_plain_msg (const char *fmt,...)
 Plain logging without timestamps or levels.
void log_plain_stderr_msg (const char *fmt,...)
 Plain logging to stderr with newline.
void log_plain_stdout_msg (const char *fmt,...)
 Plain logging to stdout with newline.
void log_plain_stderr_nonewline_msg (const char *fmt,...)
 Plain logging to stderr without trailing newline.
void log_file_msg (const char *fmt,...)
 Log to file only, no stderr output.
void log_labeled (const char *label, log_color_t color, const char *message,...)
 Print a labeled message with color.
const char * log_level_color (log_color_t color)
 Get color string for a given color enum.
const char * log_level_color_for_output (log_color_t color, bool enabled)
const char ** log_get_color_array (void)
 Get the appropriate color array based on terminal capabilities.
const char * log_get_foreground_color (void)
int log_get_color_mode (void)
void log_redetect_terminal_capabilities (void)
 Re-detect terminal capabilities after logging is initialized.
void log_init_colors (void)
 Initialize logging color system with current terminal capabilities.
void log_set_color_scheme (const color_scheme_t *scheme)
 Set the color scheme for logging output.
bool log_lock_terminal (void)
 Lock terminal output for exclusive access by the calling thread.
void log_unlock_terminal (bool previous_state)
 Release terminal lock and flush buffered messages.
void log_set_flush_delay (unsigned int delay_ms)
 Set the delay between flushing buffered log entries.
char * format_message (const char *format, va_list args)
 Format a message using va_list.
size_t get_current_time_formatted (char *time_buf)
 Get current time as formatted string.
asciichat_error_t log_net_message (struct acip_transport *transport, 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).
asciichat_error_t log_enable_mmap (const char *log_path)
 Enable lock-free mmap-based logging.
asciichat_error_t log_enable_mmap_sized (const char *log_path, size_t max_size)
 Enable lock-free mmap logging with custom file size.
void log_disable_mmap (void)
 Disable mmap logging and return to mutex-based logging.
void log_shutdown_begin (void)
 Begin shutdown phase - disable console logging but keep file logging.
void log_shutdown_end (void)
 End shutdown phase - restore previous logging settings.
void log_cleanup_colors (void)
 Clean up compiled color scheme.
void log_set_session_log_buffer (session_log_buffer_t *buf)
 Register a session log buffer with the logger.
void log_clear_session_log_buffer (void)
 Unregister the session log buffer.
session_log_buffer_t * log_get_session_log_buffer (void)
 Get the currently registered session log buffer.
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_get_template (void)
 Get the current log format template (opaque pointer).
int log_named_format_message (const char *message, char *output, size_t output_size)
 Format hex addresses in a message as named object descriptions.
const char * log_named_format_or_original (const char *message)
 Format named objects in a message (thread-local buffer).
const char * colorize_named_string (const char *name_str)
 Colorize a "type/name" string for display.

Logging Macros

Note
Compile-time log level stripping: In release builds, log_dev() and log_debug() are compiled out completely (no runtime overhead). Override with LOG_COMPILE_LEVEL.
#define log_as(level, ...)
 Log a message given the associated level.
#define log_only(bitmask, level, ...)
 Log a message given its level and if the bitmask allows so.
#define log_dev(...)
 Log a DEV message (most verbose, development only).
#define log_debug(...)
 Log a DEBUG message.
#define log_info(...)
 Log an INFO message.
#define log_warn(...)
 Log a WARN message.
#define log_error(...)
 Log an ERROR message.
#define log_fatal(...)
 Log a FATAL message.

Detailed Description

📝 Logging API with multiple log levels and terminal output control

This header provides a comprehensive logging system with:

  • Multiple log levels (DEV, DEBUG, INFO, WARN, ERROR, FATAL)
  • File and terminal output with automatic color coding
  • Terminal capability detection (16-color, 256-color, truecolor)
  • Rate-limited logging macros for high-frequency logging
  • Thread-safe logging operations
  • Automatic log file rotation when size limit is reached
Note
In debug builds, log macros include file/line/function information. In release builds, this information is omitted.

This module formats named objects in log messages by scanning for hex addresses and replacing them with friendly descriptions from the named object registry when available.

For example:

  • Input: "Locked 0x7f1234567890 successfully"
  • Output: "Locked mutex: recv_mutex.2 (0x7f1234567890) successfully"

When an address is not registered, it's left unchanged.

Logging Subsystem Architecture

Overview

The ascii-chat logging subsystem provides a comprehensive, high-performance logging system designed for terminal-based applications with multiple concurrent threads and real-time constraints.

Key features:

  • Six severity levels (DEV, DEBUG, INFO, WARN, ERROR, FATAL)
  • Simultaneous file and terminal output with automatic color coding
  • Lock-free memory-mapped logging for high-performance scenarios
  • PCRE2-based regex filtering with context display
  • Flexible format customization for both console and file output
  • Rate-limited and call-count-based logging macros
  • Thread-safe logging with optional terminal locking for interactive prompts

System Architecture

The logging system is organized into modular layers:

┌──────────────────────────────────────────────────────────┐
│ Application Code (log_info, log_warn, etc.) │
└────────────────────┬─────────────────────────────────────┘
│
┌────────────────────▼─────────────────────────────────────┐
│ lib/log/logging.c - Core logging system │
│ - Terminal locking for interactive prompts │
└──┬──────────────────┬───────────────────┬────────────────┘
│ │ │
┌──▼──────────┐ ┌───▼──────────┐ ┌────▼─────────────────┐
│ Output │ │ Formatting │ │ Filtering & Colors │
│ │ │ │ │ │
│ - File I/O │ │- format.c │ │ - grep.c (PCRE2) │
│ - mmap.c │ │- Custom │ │ - colorize.c │
│ - Buffering │ │ templates │ │ - Terminal detection │
└─────────────┘ └──────────────┘ └─────────────────────┘
void log_plain_msg(const char *fmt,...)
Plain logging without timestamps or levels.
Definition log.c:1509
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
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.
Definition log.c:1196
void log_destroy(void)
Destroy the logging system and close log file.
Definition log.c:667
void log_set_level(log_level_t level)
Set the minimum log level.
Definition log.c:737
void log_init(const char *filename, log_level_t level, bool force_stderr, bool use_mmap)
Initialize the logging system.
Definition log.c:569
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
void log_terminal_msg(log_level_t level, const char *file, int line, const char *func, const char *fmt,...)
Log a message to terminal only (no file output).
Definition log.c:1389
platform_mmap_t mmap
Definition mmap.c:37

Log Levels

The logging system defines six severity levels, from most to least verbose:

Level Value Purpose Compile-Time Stripping Example
DEV 0 Development/trace logs Strippable in release Frame timing, buffer state, handshake details
DEBUG 1 Debug diagnostic messages Strippable in release Algorithm decisions, state transitions
INFO 2 Informational messages Strippable via LOG_COMPILE_LEVEL=LOG_WARN Initialization, connection events
WARN 3 Warning conditions Strippable via LOG_COMPILE_LEVEL=LOG_ERROR Recoverable errors, performance concerns
ERROR 4 Error conditions Strippable via LOG_COMPILE_LEVEL=LOG_FATAL Failed operations, broken connections
FATAL 5 Fatal errors (program exit) Never stripped Out of memory, crypto failure

Key characteristics:

  • DEV/DEBUG are compile-time strippable, allowing verbose debug builds while keeping release builds small. Override with LOG_COMPILE_LEVEL=LOG_DEV to keep them.
  • INFO/WARN use log_info_every() and log_warn_every() for high-frequency paths (video/audio loops) to avoid log spam while maintaining visibility.
  • ERROR/FATAL go to stderr; others go to stdout (unless force_stderr is enabled). This keeps stdout clean for client mode where ASCII art is rendered.

Macro API:

log_dev("Development message"); // Compile-time strippable
log_debug("Debug message"); // Compile-time strippable
log_info("Info message"); // Production-safe
log_warn("Warning: %d", value); // Warning condition
log_error("Error: %s", error_msg); // Error condition
log_fatal("Fatal: exiting"); // Program termination
#define log_dev(...)
Log a DEV message (most verbose, development only).
Definition log/log.h:581
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
#define log_fatal(...)
Log a FATAL message.
Definition log/log.h:646
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595

Message Formatting

The logging system uses a templated formatting system that allows customization of message output while maintaining performance.

Default format (debug mode):

[14:30:45] [DEBUG] [tid:1234] src/client.c:42 in client_init(): Starting client
int client_init(void)

Default format (release mode):

[14:30:45] [DEBUG] Starting client

Custom format support via log_set_format():

// Compact format
log_set_format("[%time(%H:%M:%S)] [%level_aligned] %message", false);
// Detailed format with colors
log_set_format("%color(LOG_DEBUG, [%level_aligned]) %message", false);
// Relative file paths
log_set_format("[%time(%H:%M:%S.%microseconds)] %file_relative:%line %message", false);
asciichat_error_t log_set_format(const char *format_str, bool console_only)
Set a custom log format string.
Definition log.c:790

Format specifiers:

  • time(fmt) - Time in strftime format (e.g., H:M:S, Y-m-d)
  • level - Log level name (DEBUG, INFO, WARN, ERROR)
  • level_aligned - Log level padded to 5 chars (for alignment)
  • file - Full file path (debug mode only)
  • file_relative - Relative file path (debug mode only)
  • line - Line number (debug mode only)
  • func - Function name (debug mode only)
  • tid - Thread ID (debug mode only)
  • message - Log message body
  • microseconds - Microsecond component of timestamp
  • nanoseconds - Nanosecond component of timestamp

Console-only formatting: By passing console_only=true to log_set_format(), the custom format applies only to terminal output. File logging uses the default format, which is useful for preserving readable logs while having fancy terminal colors.

Log Filtering with Grep

The filtering system uses PCRE2 regex to dynamically filter log output, with support for highlighting matched text.

CLI usage:

# Plain regex - matches "error" or "warning" case-insensitive
./ascii-chat server --grep "/error|warning/i"
# With context - show 2 lines before and after match
./ascii-chat client --grep "/handshake/C2"
# Fixed string search (no regex)
./ascii-chat server --grep "/DEBUG/F"
# Multiple patterns - OR logic
./ascii-chat client --grep "/error/i" --grep "/connection/i"
# Invert match - show everything EXCEPT matches
./ascii-chat server --grep "/debug|trace/iI"

Supported flags:

  • i - Case-insensitive matching
  • m - Multiline mode (^ and $ match line boundaries)
  • s - Dotall mode (. matches newlines)
  • x - Extended regex (ignore whitespace in pattern)
  • F - Fixed string literal (no regex, exact match)
  • g - Global highlighting (highlight all matches in line)
  • I - Invert match (show lines that DON'T match)
  • B<n> - Show n lines before match (context)
  • A<n> - Show n lines after match (context)
  • C<n> - Show n lines before and after (context)

Filter performance:

  • Each pattern is compiled once at startup (not on every log)
  • Matching uses thread-local storage (no allocations per match)
  • Matched text is highlighted with yellow background
  • Filtering is applied after formatting, so matches include colored output

Output Routing

The logging system routes messages to different outputs based on level and configuration.

Default routing:

  • DEV, DEBUG, INFO → stdout (unless force_stderr enabled)
  • WARN, ERROR, FATAL → stderr
  • All levels → log file (if configured)

Client mode special handling: Client mode keeps stdout clean for ASCII art rendering by enabling force_stderr:

log_init("client.log", LOG_DEBUG, true, false); // force_stderr=true
#define LOG_DEBUG
Definition types.h:39

This routes all log messages (including INFO) to stderr, keeping stdout available.

Output control functions:

log_set_terminal_output(false); // Disable all terminal output
log_set_force_stderr(true); // Force all logs to stderr
log_disable_file_output(); // Disable file output, use stderr fallback
void log_set_force_stderr(bool enabled)
Force all terminal log output to stderr.
Definition log.c:767
void log_disable_file_output(void)
Disable file output and use stderr instead.
Definition log.c:779
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
Definition log.c:746

Buffering for interactive prompts: When prompting for user input (passwords, yes/no questions), use terminal locking to ensure clean output:

bool prev = log_lock_terminal(); // Lock out other threads
fputs("Enter password: ", stdout);
// user types input
log_unlock_terminal(prev); // Unlock and flush buffered logs
bool log_lock_terminal(void)
Lock terminal output for exclusive access by the calling thread.
Definition log.c:833
void log_unlock_terminal(bool previous_state)
Release terminal lock and flush buffered messages.
Definition log.c:842

Performance Characteristics

The logging system is designed for low-latency, high-throughput scenarios common in real-time multimedia applications.

Lock-free mmap mode (recommended for production):

  • File output: Lock-free atomic operations on mmap'd memory
  • Terminal output: Atomic fprintf/fwrite (no mutex held)
  • No contention: Multiple threads can log simultaneously without blocking
  • Crash-safe: Text written directly to file, readable after crash
  • Performance: ~5-10 microseconds per log message (x86-64)

Mutex-based mode (fallback):

  • Single mutex protects both file and terminal output
  • Suitable for applications with low logging volume
  • Fallback when mmap initialization fails

Optimizations for high-frequency logging:

// Log at most once per second (1,000,000 microseconds)
log_info_every(1000000, "Frame buffer utilization: %d%%", percent);
// Log every 60th call (for 60 FPS, logs ~1x per second)
log_debug_nth(60, "Processing frame %zu", frame_count);
// Log exactly once per session
log_info_once("Client connected from %s", client_address);
#define log_info_every(interval_us, fmt,...)
Rate-limited INFO logging.
Definition log/log.h:752
#define log_debug_nth(n, fmt,...)
Log DEBUG message every nth call.
Definition log/log.h:855
#define log_info_once(fmt,...)
Log INFO message exactly once.
Definition log/log.h:883

Memory usage:

  • Terminal buffer: 64 KB maximum (256 buffered entries)
  • Single message: 4 KB maximum (truncated if exceeded)
  • File buffer: Variable (mmap mode) or unbuffered
  • Per-format specifier: ~100 bytes compiled size

CPU characteristics:

  • Message formatting: ~2-5% of CPU in typical scenarios
  • PCRE2 filtering: <1% overhead for simple patterns
  • Terminal color detection: One-time cost at startup
  • Rate-limited macros: Atomic operations only (no syscalls)

Appropriate Use Cases

Good use cases for logging:

  • Connection lifecycle events (connected, disconnected, handshake)
  • Configuration changes or mode transitions
  • Recoverable errors and retry attempts
  • Performance metrics and statistics (with rate limiting)
  • Authentication and security events
  • Frame drops or quality degradation
  • Shader compilation or resource loading

Poor use cases (leads to spam):

  • Every video frame processed (use log_nth or log_every)
  • Every byte received (use log_every instead)
  • Every mutex acquisition/release (use log_once or don't log)
  • Every packet in a high-frequency network loop (use sampling)

Performance-sensitive code patterns:

// ❌ BAD: 60 logs per second (60 FPS scenario)
void process_frame(void) {
log_debug("Processing frame %zu", frame_count);
}
// ✅ GOOD: 1 log per second using rate limiting
void process_frame(void) {
log_debug_every(1000000, "Processed %zu frames", frame_count);
}
// ✅ GOOD: 1 log per 60 frames using call counting
void process_frame(void) {
log_debug_nth(60, "Processing frames (batch complete)");
}
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:749

Error Handling in Logs

The logging system integrates with the application's error tracking system. For critical failures, use SET_ERRNO() to capture error context:

// Set errno with context message
if (crypto_failed) {
log_error("Crypto handshake failed: %s", reason);
return SET_ERRNO(ERROR_CRYPTO_FAILED, "Key exchange timeout");
}
// Check and log errors from other functions
asciichat_error_t result = process_network_packet();
if (result != ASCIICHAT_OK) {
log_error("Network error: %d", result);
return result;
}
#define SET_ERRNO(code,...)
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ASCIICHAT_OK
Definition error_codes.h:46

Initialization and Shutdown

Standard initialization (debug mode):

log_init("output.log", // Log file path
LOG_DEBUG, // Minimum log level
false, // force_stderr: no (INFO to stdout)
false); // use_mmap: no (use mutex-based)

Production initialization (server mode with mmap):

log_init("/var/log/asciichat.log",
LOG_INFO, // Only INFO and above
false, // Normal routing
true); // use_mmap: yes (lock-free)
// Optionally upgrade to lock-free mode
log_enable_mmap("/var/log/asciichat.log");
asciichat_error_t log_enable_mmap(const char *log_path)
Enable lock-free mmap-based logging.
Definition log.c:1977
#define LOG_INFO
Definition types.h:40

Client mode (keep stdout clean):

log_init("client.log",
true, // force_stderr: yes
false);

Graceful shutdown:

// Disable console output, keep file logging for shutdown messages
log_info("Shutting down server");
log_info("All clients disconnected");
// Destroy logging system
void log_shutdown_end(void)
End shutdown phase - restore previous logging settings.
Definition log.c:2018
void log_shutdown_begin(void)
Begin shutdown phase - disable console logging but keep file logging.
Definition log.c:2007

Thread Safety

All logging functions are thread-safe:

Interactive prompt pattern:

// Thread 1 (main thread) - handle user input
bool prev = log_lock_terminal();
char *password = getpass("Password: ");
// Now buffered logs from other threads are flushed
// Thread 2 (network thread) - can log normally
log_info("Received %d bytes", packet_size); // Buffered during lock

Debugging with Logs

Enable verbose logging:

# -V increases verbosity (can be stacked)
./ascii-chat --log-level debug -V server
./ascii-chat -VVV client # Maximum verbosity with DEV level

Log to file for analysis:

./ascii-chat --log-file debug.log server
# Or using CLI options
./ascii-chat --log-level dev --log-file debug.log server

Filter logs during execution:

# Watch crypto handshake
./ascii-chat --log-level debug server --grep "/handshake|crypto/ig"
# See only errors and warnings with context
./ascii-chat client --grep "/error|warn/iC3"
# Monitor specific component
./ascii-chat server --grep "/\\‍[AUDIO\\‍]/i"

Post-mortem analysis (from log file):

# Check for errors
grep -i error debug.log
# Find performance issues
grep "timeout\|delay\|slow" debug.log
# Track connection lifecycle
grep "connected\|disconnected\|handshake" debug.log

Code Examples

Basic logging:

log_info("Server started on port %d", 27224);
log_warn("Connection timeout after %d seconds", timeout);
log_error("Failed to bind socket: %s", strerror(errno));
int errno

Conditional logging:

if (verbose_mode) {
log_debug("Processing frame %zu in %llu µs", frame_num, elapsed_us);
}
// Or use compile-time stripping for release builds
log_dev("Frame pipeline state: input=%d, processing=%d, output=%d",
input_queue, process_queue, output_queue);

Labeled output:

log_labeled("[INIT]", LOG_COLOR_DEBUG, "Initializing subsystem");
log_labeled("[ERROR]", LOG_COLOR_ERROR, "Failed to create thread");
void log_labeled(const char *label, log_color_t color, const char *message,...)
Print a labeled message with color.
@ LOG_COLOR_ERROR
Definition log/log.h:152
@ LOG_COLOR_DEBUG
Definition log/log.h:149

Raw output (no formatting):

// Write to file only (no terminal)
log_file("Raw packet dump follows:");
log_file(" 00 01 02 03 04 05 06 07");
// Plain message to stderr
log_plain_stderr("Critical condition detected");
#define log_plain_stderr(...)
Plain logging to stderr with newline.
Definition log/log.h:672
#define log_file(...)
File-only logging - writes to log file only, no stderr output.
Definition log/log.h:693

Network logging:

// Log to remote server, file, and terminal simultaneously
log_net_message(server_socket, crypto_ctx, LOG_INFO, REMOTE_LOG_OUTBOUND,
NULL, 0, NULL, "Video quality: %d%% (%d kbps)", quality, bitrate);
asciichat_error_t log_net_message(struct acip_transport *transport, 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).
Definition log.c:1810

Macro Definition Documentation

◆ DEFAULT_LOG_LEVEL

#define DEFAULT_LOG_LEVEL   LOG_DEBUG

#include <include/ascii-chat/log/log.h>

Default log level for debug builds (DEBUG and above).

Definition at line 69 of file log/log.h.

Referenced by parse_log_level().

◆ log_as

#define log_as ( level,
... )

#include <include/ascii-chat/log/log.h>

Value:
log_msg(level, __FILE__, __LINE__, __func__, __VA_ARGS__)

Log a message given the associated level.

Parameters
levelThe log level to use
...Format string and arguments (printf-style)

Definition at line 555 of file log/log.h.

◆ LOG_ATOMIC_UINT64

#define LOG_ATOMIC_UINT64   atomic_t

#include <include/ascii-chat/log/log.h>

Definition at line 32 of file log/log.h.

◆ LOG_ATOMIC_UINT64_INIT

#define LOG_ATOMIC_UINT64_INIT ( val)

#include <include/ascii-chat/log/log.h>

Value:
{.impl = (uint64_t)0, \
.change_count = (uint64_t)0, \
.last_store_time_ns = (uint64_t)0, \
.last_load_time_ns = (uint64_t)0, \
.store_count = (uint64_t)0, \
.load_count = (uint64_t)0, \
.cas_count = (uint64_t)0, \
.cas_success_count = (uint64_t)0, \
.fetch_count = (uint64_t)0}
unsigned long long uint64_t
Definition common.h:63

Definition at line 37 of file log/log.h.

37#define LOG_ATOMIC_UINT64_INIT(val) \
38 {.impl = (uint64_t)0, \
39 .change_count = (uint64_t)0, \
40 .last_store_time_ns = (uint64_t)0, \
41 .last_load_time_ns = (uint64_t)0, \
42 .store_count = (uint64_t)0, \
43 .load_count = (uint64_t)0, \
44 .cas_count = (uint64_t)0, \
45 .cas_success_count = (uint64_t)0, \
46 .fetch_count = (uint64_t)0}

◆ LOG_BIT

#define LOG_BIT ( n)

#include <include/ascii-chat/log/types.h>

Value:
(1ULL << (n))

Logging bit conversion.

Converts a log level to a bit flag. Used to pack multiple log levels in the same number (array of flags).

Definition at line 19 of file types.h.

◆ LOG_CLIENT_IMPL

#define LOG_CLIENT_IMPL ( client,
level,
fmt,
... )

#include <include/ascii-chat/network/log.h>

Value:
do { \
if ((client)->security_ready && (client)->transport) { \
log_net_message((client)->transport, level, REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT, __FILE__, __LINE__, __func__, \
fmt, ##__VA_ARGS__); \
} \
} while (0)
@ REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT
Definition network/log.h:22

Definition at line 54 of file network/log.h.

54#define LOG_CLIENT_IMPL(client, level, fmt, ...) \
55 do { \
56 if ((client)->security_ready && (client)->transport) { \
57 log_net_message((client)->transport, level, REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT, __FILE__, __LINE__, __func__, \
58 fmt, ##__VA_ARGS__); \
59 } \
60 } while (0)

◆ log_colored_msg

#define log_colored_msg ( level,
colored_message,
plain_message )

#include <include/ascii-chat/log/log.h>

Value:
log_colored_msg_impl((level), __FILE__, __LINE__, __func__, (colored_message), (plain_message))
void log_colored_msg_impl(log_level_t level, const char *file, int line, const char *func, const char *colored_message, const char *plain_message)
Write semantically colored terminal text and an ANSI-free log equivalent.
Definition log.c:1499

Definition at line 320 of file log/log.h.

320#define log_colored_msg(level, colored_message, plain_message) \
321 log_colored_msg_impl((level), __FILE__, __LINE__, __func__, (colored_message), (plain_message))

◆ log_debug

#define log_debug ( ...)

#include <include/ascii-chat/log/log.h>

Value:
#define log_only(bitmask, level,...)
Log a message given its level and if the bitmask allows so.
Definition log/log.h:566
#define LOG_BIT(n)
Logging bit conversion.
Definition types.h:19

Log a DEBUG message.

Parameters
...Format string and arguments (printf-style)
Note
DEBUG messages are stripped at compile-time in release builds.
In debug builds, includes file/line/function.

Definition at line 595 of file log/log.h.

Referenced by acds_client_disconnect(), acds_identity_generate(), acds_server_shutdown(), acds_sign_session_create(), acds_sign_session_join(), acds_validate_timestamp(), acds_verify_session_create(), acds_verify_session_join(), acip_send_ascii_frame_contract(), acip_send_image_frame_h265(), acip_tcp_transport_create(), acip_transport_destroy(), acip_websocket_client_transport_create(), acip_websocket_server_transport_create(), add_known_host(), add_transport_client(), apply_palette_config(), ascii_otel_shutdown(), ascii_write_destroy(), asciichat_mdns_advertise(), asciichat_mdns_destroy(), asciichat_mdns_query(), asciichat_mdns_update(), audio_cleanup(), audio_client_init(), audio_connection_init(), audio_destroy(), audio_init(), audio_process_received_samples(), audio_ring_buffer_read(), audio_sender_cleanup(), audio_set_realtime_priority(), audio_start_duplex(), audio_start_thread(), audio_stop_duplex(), audio_stop_thread(), audio_terminate_portaudio_final(), buffer_domain_log_stats(), capture_init(), capture_start_thread(), check_gpg_key_expiry(), check_known_host_with_key(), client_audio_pipeline_create(), client_audio_pipeline_process_duplex(), client_audio_render_thread(), client_connection_checkpoint(), client_crypto_init(), client_main(), client_protocol_start(), client_video_render_thread(), colorscheme_set_active_scheme(), config_load_and_apply(), config_schema_build_from_configs(), consensus_coordinator_create(), consensus_coordinator_on_stats_update(), create_client_render_threads(), crypto_destroy(), crypto_generate_keypair(), crypto_handshake_client_auth_response(), crypto_handshake_client_key_exchange(), crypto_handshake_server_auth_challenge(), crypto_handshake_server_complete(), crypto_handshake_server_send_parameters(), crypto_handshake_server_start(), crypto_handshake_set_parameters(), crypto_process_auth_challenge(), crypto_process_auth_response(), crypto_rekey_commit(), crypto_rekey_init(), crypto_rekey_process_request(), crypto_rekey_process_response(), crypto_set_peer_public_key(), crypto_should_rekey(), crypto_verify_auth_response(), database_close(), database_session_add_key(), database_session_get_keys(), database_session_is_migration_ready(), database_session_lookup(), database_session_revoke_key(), database_stats_session_count(), database_stats_unique_clients(), detect_client_utf8_support(), detect_default_ssh_key(), disconnect_client_for_bad_data(), discover_session_parallel(), discovery_keys_clear_cache(), discovery_keys_download_https(), discovery_keys_load_file(), discovery_keys_save_cached(), discovery_keys_verify(), discovery_main(), discovery_session_check_host_alive(), discovery_session_create(), discovery_session_process(), dns_test_connectivity(), ed25519_sign_message(), ed25519_verify_signature(), env_pop_prompt_response(), ffmpeg_decoder_create_stdin(), ffmpeg_decoder_create_with_request_options(), ffmpeg_encoder_create_scaled(), ffmpeg_encoder_set_snapshot_actual_duration(), ffmpeg_encoder_write_frame(), fps_counter_render(), frame_validate_integrity(), framebuffer_write_multi_frame(), get_known_hosts_path(), get_manpage_content(), get_manpage_template(), gpg_agent_connect(), gpg_agent_sign(), gpg_get_public_key(), gpg_homedir_create(), gpg_homedir_destroy(), gpg_sign_detached_ed25519(), gpg_sign_with_key(), gpg_verify_detached_ed25519(), gpg_verify_signature(), gpg_verify_signature_with_binary(), h265_encoder_flush(), h265_server_get_client_decoder(), h265_server_remove_client(), handle_client_leave_packet(), handle_image_frame_packet(), handle_protocol_version_packet(), handle_stream_start_packet(), ice_add_remote_candidate(), ice_gather_candidates(), keepalive_stop_thread(), log_io_stop(), log_mmap_destroy(), log_recolor_plain_entry(), log_redetect_terminal_capabilities(), log_template_apply(), main(), manpage_content_generate_environment(), manpage_content_generate_environment_with_manual(), manpage_content_generate_examples(), manpage_content_generate_options(), manpage_content_generate_positional(), manpage_content_generate_usage(), manpage_merger_generate_synopsis(), manpage_merger_generate_usage(), manpage_merger_merge_environment(), manpage_merger_merge_options(), manpage_merger_merge_positional(), manpage_resources_load(), media_source_create_interruptible(), media_source_read_audio(), media_source_read_video(), media_source_seek(), memory_backend_create(), mirror_convert_frame(), mirror_main(), mixer_create_channels(), mixer_destroy(), mixer_set_source_active(), nat_compare_quality(), nat_detect_quality(), nat_upnp_close(), nat_upnp_open_protocol(), negotiate_init(), openpgp_extract_ed25519_from_mpi(), openpgp_parse_armored_pubkey(), openpgp_parse_armored_seckey(), openpgp_parse_packet_header(), openpgp_parse_public_key_packet(), openpgp_parse_secret_key_packet(), options_collect_identity_keys(), options_config_generate_manpage_merged(), options_config_generate_manpage_template(), options_init(), options_state_destroy(), options_state_init(), opus_codec_create_decoder_channels(), opus_codec_create_encoder_channels(), opus_codec_set_bitrate(), opus_codec_set_dtx(), parallel_connect(), parse_client_address(), parse_gpg_keys_from_response(), parse_mirror_media(), parse_private_key(), parse_public_keys(), process_decrypted_packet(), prompt_unknown_host_no_identity(), recv_with_timeout(), remove_known_host(), render_file_create(), sdp_detect_terminal_capabilities(), sdp_generate_answer(), sdp_generate_offer(), sdp_get_selected_video_codec(), sdp_parse(), send_crypto_parameters_packet(), send_server_state_to_client(), server_connection_set_ip(), server_connection_set_port(), server_main(), session_capture_create(), session_client_like_run(), session_consensus_create(), session_display_create(), session_display_encode_frame(), session_display_set_render_fps(), session_display_set_snapshot_actual_duration(), session_display_set_stdin_reader(), session_display_write_ascii_frame(), session_network_capture_create(), session_participant_connect(), session_pipeline_create(), session_pipeline_destroy(), session_pipeline_run_main(), session_protocol_build_capabilities(), session_render_loop(), session_server_like_run(), setup_signal_handlers(), signaling_broadcast(), signaling_relay_ice(), signaling_relay_sdp(), splash_set_update_notification(), sqlite_backend_create(), ssh_agent_add_key(), ssh_agent_has_key(), ssh_agent_sign(), start_transport_client_threads(), stats_runtime_toggle(), tcp_server_destroy(), term_renderer_create(), terminal_fd_reader_create(), terminal_fd_reader_next(), terminal_fd_writer_create(), thread_pool_create(), thread_pool_create_with_workers(), thread_pool_destroy(), thread_pool_interrupt_all(), thread_pool_spawn(), thread_pool_stop_all(), threaded_send_packet(), time_format_now(), timer_system_destroy(), turn_generate_credentials(), update_check_startup(), update_dimensions_to_terminal_size(), validate_palette_chars(), video_frame_buffer_create(), webrtc_add_remote_candidate(), webrtc_cleanup_transport(), webrtc_close_datachannel(), webrtc_close_peer_connection(), webrtc_create_datachannel(), webrtc_create_offer(), webrtc_create_peer_connection(), webrtc_datachannel_set_callbacks(), webrtc_peer_connection_close(), webrtc_peer_connection_destroy(), webrtc_peer_manager_connect(), webrtc_peer_manager_destroy(), webrtc_peer_manager_handle_ice(), webrtc_peer_manager_handle_sdp(), webrtc_set_acds_transport(), webrtc_set_remote_description(), webrtc_set_transport(), websocket_server_destroy(), websocket_server_init(), websocket_transport_send(), and yt_dlp_extract_with_options().

◆ log_debug_client

#define log_debug_client ( client,
fmt,
... )

#include <include/ascii-chat/network/log.h>

Value:
LOG_CLIENT_IMPL(client, LOG_DEBUG, fmt, ##__VA_ARGS__)
#define LOG_CLIENT_IMPL(client, level, fmt,...)
Definition network/log.h:54

Server sends DEBUG log message to client.

Definition at line 64 of file network/log.h.

◆ log_debug_server

#define log_debug_server ( transport,
fmt,
... )

#include <include/ascii-chat/network/log.h>

Value:
LOG_SERVER_IMPL(transport, LOG_DEBUG, fmt, ##__VA_ARGS__)
#define LOG_SERVER_IMPL(transport, level, fmt,...)
Definition network/log.h:83

Client sends DEBUG log message to server.

Definition at line 89 of file network/log.h.

◆ log_dev

◆ log_error

#define log_error ( ...)

#include <include/ascii-chat/log/log.h>

Value:
#define LOG_ERROR
Definition types.h:42

Log an ERROR message.

Parameters
...Format string and arguments (printf-style)
Note
ERROR messages can be stripped via LOG_COMPILE_LEVEL=LOG_FATAL.

Definition at line 634 of file log/log.h.

Referenced by acds_identity_fingerprint(), acip_client_receive_packet(), acip_handle_server_packet(), acip_send_image_frame_h265(), acip_server_receive_and_dispatch(), acip_tcp_transport_create(), acip_websocket_client_transport_create(), action_completions(), action_create_config(), action_create_manpage(), add_transport_client(), ascii_convert(), ascii_convert_with_buffers_stabilized(), ascii_write_init(), asciichat_fatal_with_context(), asciichat_pcre2_extract_group(), asciichat_pcre2_extract_named_group(), audio_client_init(), audio_start_thread(), check_gpg_key_expiry(), check_known_host_with_key(), client_audio_pipeline_create(), client_audio_pipeline_process_duplex(), client_audio_render_thread(), client_crypto_init(), client_protocol_start(), client_video_render_thread(), consensus_coordinator_process(), create_client_render_threads(), create_mixed_ascii_frame_for_client(), crypto_handshake_client_key_exchange(), crypto_handshake_server_start(), crypto_rekey_init(), database_init(), database_session_cleanup_expired(), database_session_find_by_id(), database_session_find_by_string(), database_session_is_migration_ready(), database_session_leave(), database_session_verify_key(), detect_default_ssh_key(), digital_rain_init(), digital_rain_render_at(), discovery_session_check_host_alive(), discovery_session_start(), get_known_hosts_path(), get_required_argument(), gpg_agent_connect(), gpg_agent_sign(), gpg_get_public_key(), gpg_homedir_create(), gpg_sign_detached_ed25519(), gpg_sign_with_key(), gpg_verify_detached_ed25519(), gpg_verify_signature(), gpg_verify_signature_with_binary(), grep_init(), h265_client_init_media_source(), h265_encode(), h265_encoder_flush(), handle_audio_batch_packet(), handle_audio_opus_batch_packet(), handle_audio_opus_packet(), handle_audio_packet(), handle_image_frame_h265_packet(), handle_image_frame_packet(), handle_stream_start_packet(), https_get(), is_session_string(), keepalive_start_thread(), log_named_format_or_original(), log_search_exit_mode(), log_set_format(), main(), mcp_server_main(), media_source_create_interruptible(), memory_backend_create(), mirror_convert_frame(), mixer_add_source(), mutex_stack_detect_deadlocks(), options_collect_identity_keys(), packet_parse_opus_batch(), parse_color_mode_option(), parse_log_level_option(), parse_palette_chars_option(), parse_palette_option(), parse_render_mode_option(), parse_snapshot_delay_option(), precalc_rgb_palettes(), process_decrypted_packet(), rate_limiter_create_memory(), rate_limiter_create_sqlite(), render_file_create(), server_connection_init(), server_main(), session_audio_create(), session_capture_destroy(), session_display_create(), session_display_init_render_file(), session_host_start_render(), session_participant_create(), session_participant_start_audio(), session_participant_start_video(), session_participant_stop_audio(), session_participant_stop_video(), session_pipeline_run_main(), session_render_loop(), session_server_like_run(), sqlite_backend_create(), term_renderer_feed(), thread_pool_create_with_workers(), thread_pool_destroy(), thread_pool_stop_all(), time_format_is_valid_strftime(), time_format_now(), ui_mdns_prompt_selection(), ui_session_key(), update_check_load_cache(), update_check_perform(), validate_fps_opt(), validate_options_and_report(), validate_port_opt(), validate_positive_int_opt(), validate_webcam_index(), video_frame_buffer_create(), webrtc_datachannel_send(), webrtc_peer_manager_check_gathering_timeouts(), webrtc_set_session_context(), websocket_server_init(), websocket_server_run(), websocket_transport_send(), and yt_dlp_extract_with_options().

◆ log_error_client

#define log_error_client ( client,
fmt,
... )

#include <include/ascii-chat/network/log.h>

Value:
LOG_CLIENT_IMPL(client, LOG_ERROR, fmt, ##__VA_ARGS__)

Server sends ERROR log message to client.

Definition at line 73 of file network/log.h.

◆ log_error_server

#define log_error_server ( transport,
fmt,
... )

#include <include/ascii-chat/network/log.h>

Value:
LOG_SERVER_IMPL(transport, LOG_ERROR, fmt, ##__VA_ARGS__)

Client sends ERROR log message to server.

Definition at line 98 of file network/log.h.

◆ log_every

#define log_every ( log_level,
interval_us,
fmt,
... )

#include <include/ascii-chat/log/log.h>

Value:
do { \
static LOG_ATOMIC_UINT64 _log_every_last_time = LOG_ATOMIC_UINT64_INIT(0); \
uint64_t _log_every_last = atomic_load_u64(&_log_every_last_time); \
if (_log_every_now - _log_every_last >= (uint64_t)(interval_us)) { \
if (atomic_cas_u64(&_log_every_last_time, &_log_every_last, _log_every_now)) { \
log_msg(LOG_##log_level, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__); \
} \
} \
} while (0)
#define LOG_ATOMIC_UINT64
Definition log/log.h:32
#define LOG_ATOMIC_UINT64_INIT(val)
Definition log/log.h:37
uint64_t platform_get_monotonic_time_us(void)
Get monotonic time in microseconds.

Rate-limited logging macro (thread-safe).

Logs at most once per specified time interval. Useful for threads that have an FPS and functions they call to prevent spammy logs.

Parameters
log_levelLog level (DEV, DEBUG, INFO, WARN, ERROR, FATAL)
interval_usMinimum microseconds between log messages
fmtFormat string (printf-style)
...Format arguments
Note
Each call site maintains its own static atomic timer, so different call sites can log independently. Thread-safe via atomic compare-exchange.
Uses platform_get_monotonic_time_us() for cross-platform time.

Definition at line 727 of file log/log.h.

727#define log_every(log_level, interval_us, fmt, ...) \
728 do { \
729 static LOG_ATOMIC_UINT64 _log_every_last_time = LOG_ATOMIC_UINT64_INIT(0); \
730 uint64_t _log_every_now = platform_get_monotonic_time_us(); \
731 uint64_t _log_every_last = atomic_load_u64(&_log_every_last_time); \
732 if (_log_every_now - _log_every_last >= (uint64_t)(interval_us)) { \
733 if (atomic_cas_u64(&_log_every_last_time, &_log_every_last, _log_every_now)) { \
734 log_msg(LOG_##log_level, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__); \
735 } \
736 } \
737 } while (0)

◆ log_fatal

#define log_fatal ( ...)

#include <include/ascii-chat/log/log.h>

Value:
#define LOG_FATAL
Definition types.h:43

Log a FATAL message.

Parameters
...Format string and arguments (printf-style)
Note
FATAL messages are never stripped (always compiled in).

Definition at line 646 of file log/log.h.

Referenced by discovery_main().

◆ log_fatal_client

#define log_fatal_client ( client,
fmt,
... )

#include <include/ascii-chat/network/log.h>

Value:
LOG_CLIENT_IMPL(client, LOG_FATAL, fmt, ##__VA_ARGS__)

Server sends FATAL log message to client.

Definition at line 76 of file network/log.h.

◆ log_fatal_server

#define log_fatal_server ( transport,
fmt,
... )

#include <include/ascii-chat/network/log.h>

Value:
LOG_SERVER_IMPL(transport, LOG_FATAL, fmt, ##__VA_ARGS__)

Client sends FATAL log message to server.

Definition at line 101 of file network/log.h.

◆ log_file

#define log_file ( ...)

#include <include/ascii-chat/log/log.h>

Value:
log_file_msg(__VA_ARGS__)
void log_file_msg(const char *fmt,...)
Log to file only, no stderr output.
Definition log.c:1765

File-only logging - writes to log file only, no stderr output.

Parameters
...Format string and arguments (printf-style)

Definition at line 693 of file log/log.h.

Referenced by asciichat_shared_init(), and main().

◆ LOG_HEADER_BUFFER_SIZE

#define LOG_HEADER_BUFFER_SIZE   512

#include <include/ascii-chat/log/log.h>

Maximum size of a log header (timestamp, level, file:line:func).

Definition at line 98 of file log/log.h.

Referenced by log_msg(), and log_plain_msg().

◆ log_info

#define log_info ( ...)

#include <include/ascii-chat/log/log.h>

Value:

Log an INFO message.

Parameters
...Format string and arguments (printf-style)
Note
INFO messages can be stripped via LOG_COMPILE_LEVEL=LOG_WARN.

Definition at line 608 of file log/log.h.

Referenced by acds_client_connect(), acds_identity_load(), acds_identity_save(), acds_on_host_announcement_ex(), acds_server_init(), acds_server_shutdown(), acip_server_receive_and_dispatch(), acip_tcp_transport_create(), action_create_config(), action_create_manpage(), ascii_otel_init(), asciichat_mdns_query(), asciichat_mdns_unadvertise(), audio_analysis_destroy(), audio_analysis_init(), audio_init(), audio_ring_buffer_read(), audio_start_duplex(), check_known_host_with_key(), client_audio_pipeline_create(), client_audio_pipeline_reset(), client_connection_create(), client_connection_destroy(), client_connection_start(), client_init(), consensus_coordinator_on_election_result(), consensus_coordinator_on_ring_members(), crypto_handshake_client_complete(), crypto_handshake_client_key_exchange(), crypto_handshake_server_auth_challenge(), crypto_handshake_server_complete(), database_init(), database_session_cleanup_expired(), database_session_clear_host(), database_session_leave(), database_session_start_migration(), database_session_update_host(), debug_stats_print(), digital_rain_init(), discover_session_parallel(), discovery_keys_verify(), discovery_keys_verify_change(), discovery_mdns_query(), discovery_session_create(), discovery_session_start(), discovery_session_stop(), ffmpeg_encoder_create_scaled(), ffmpeg_encoder_set_snapshot_actual_duration(), ffmpeg_encoder_write_frame(), grep_init(), h265_client_context_create(), h265_client_init_media_source(), h265_decoder_create(), h265_server_context_create(), handle_client_capabilities_packet(), handle_client_join_packet(), handle_client_leave_packet(), handle_image_frame_h265_packet(), handle_image_frame_packet(), handle_protocol_version_packet(), handle_size_packet(), handle_stream_start_packet(), handle_stream_stop_packet(), https_get(), log_disable_mmap(), log_enable_mmap_sized(), log_mmap_init(), log_server_stats(), main(), mcp_server_main(), media_source_start_video(), mirror_init(), mixer_add_source(), mixer_remove_source(), nat_detect_quality(), nat_upnp_close(), nat_upnp_open_protocol(), nat_upnp_refresh(), negotiate_determine_result(), network_connection_open(), options_collect_identity_keys(), parallel_connect(), render_file_create(), server_main(), session_capture_create(), session_connection_establish(), session_display_create(), session_display_init_render_file(), session_host_add_memory_participant(), session_host_receive_packet(), session_host_set_client_transport(), session_host_start_render(), session_host_stop_render(), session_participant_connect(), session_participant_create(), session_participant_set_transport(), session_participant_start_audio(), session_participant_start_video(), session_protocol_dispatch(), session_quic_listeners_open(), session_server_like_mdns_advertise(), session_server_like_run(), stats_logger_thread(), terminal_fd_reader_next(), thread_pool_create_with_workers(), ui_mdns_log_append(), ui_session_key(), update_check_perform(), video_encoder_create(), video_frame_buffer_create(), webrtc_create_peer_connection(), webrtc_peer_manager_check_gathering_timeouts(), webrtc_peer_manager_connect(), webrtc_peer_manager_create(), webrtc_peer_manager_handle_sdp(), webrtc_set_session_context(), websocket_callback_timing_log_stats(), websocket_server_init(), and websocket_server_run().

◆ log_info_client

#define log_info_client ( client,
fmt,
... )

#include <include/ascii-chat/network/log.h>

Value:
LOG_CLIENT_IMPL(client, LOG_INFO, fmt, ##__VA_ARGS__)

Server sends INFO log message to client.

Definition at line 67 of file network/log.h.

Referenced by handle_client_join_packet(), handle_image_frame_h265_packet(), handle_image_frame_packet(), handle_stream_start_packet(), and handle_stream_stop_packet().

◆ log_info_server

#define log_info_server ( transport,
fmt,
... )

#include <include/ascii-chat/network/log.h>

Value:
LOG_SERVER_IMPL(transport, LOG_INFO, fmt, ##__VA_ARGS__)

Client sends INFO log message to server.

Definition at line 92 of file network/log.h.

◆ LOG_MMAP_MSG_BUFFER_SIZE

#define LOG_MMAP_MSG_BUFFER_SIZE   1024

#include <include/ascii-chat/log/log.h>

Maximum size of a log message in mmap mode.

Definition at line 95 of file log/log.h.

Referenced by log_mmap_write(), and log_msg().

◆ LOG_MSG_BUFFER_SIZE

#define LOG_MSG_BUFFER_SIZE   4096

#include <include/ascii-chat/log/log.h>

Maximum size of a single log message (including formatting).

Definition at line 92 of file log/log.h.

Referenced by colorize_log_message_with_color(), log_file_msg(), log_msg(), log_plain_msg(), and log_recolor_plain_entry().

◆ log_nth

#define log_nth ( log_level,
n,
fmt,
... )

#include <include/ascii-chat/log/log.h>

Value:
do { \
static LOG_ATOMIC_UINT64 _log_nth_counter = LOG_ATOMIC_UINT64_INIT(0); \
uint64_t _log_nth_count = atomic_load_u64(&_log_nth_counter); \
uint64_t _log_nth_new = _log_nth_count + 1; \
atomic_store_u64(&_log_nth_counter, _log_nth_new); \
if (_log_nth_new % (uint64_t)(n) == 0) { \
log_msg(LOG_##log_level, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__); \
} \
} while (0)

Log every nth call to this code location (thread-safe).

Logs a message every nth time the code is executed. Useful for logging periodic events in tight loops without spamming the log. For example, log_nth(INFO, 1000, "Processed items") logs every 1000 calls.

Each call site maintains its own static counter, so different call sites can log independently at different frequencies.

Parameters
log_levelLog level (DEV, DEBUG, INFO, WARN, ERROR, FATAL)
nLog every nth call (1 = every call, 2 = every 2nd call, etc.)
fmtFormat string (printf-style)
...Format arguments
Note
Thread-safe via atomic fetch_add.

Definition at line 795 of file log/log.h.

795#define log_nth(log_level, n, fmt, ...) \
796 do { \
797 static LOG_ATOMIC_UINT64 _log_nth_counter = LOG_ATOMIC_UINT64_INIT(0); \
798 uint64_t _log_nth_count = atomic_load_u64(&_log_nth_counter); \
799 uint64_t _log_nth_new = _log_nth_count + 1; \
800 atomic_store_u64(&_log_nth_counter, _log_nth_new); \
801 if (_log_nth_new % (uint64_t)(n) == 0) { \
802 log_msg(LOG_##log_level, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__); \
803 } \
804 } while (0)

◆ log_once

#define log_once ( log_level,
fmt,
... )

#include <include/ascii-chat/log/log.h>

Value:
do { \
static LOG_ATOMIC_UINT64 _log_once_counter = LOG_ATOMIC_UINT64_INIT(0); \
uint64_t _log_once_count = atomic_load_u64(&_log_once_counter); \
if (_log_once_count == 0) { \
atomic_store_u64(&_log_once_counter, 1); \
log_msg(LOG_##log_level, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__); \
} \
} while (0)

Log exactly once per call site (thread-safe).

Logs a message exactly once, no matter how many times the code is executed. Each call site maintains its own static counter, so different call sites can log independently.

Useful for one-time initialization messages, warnings, or debug output that should only appear once per session.

Parameters
log_levelLog level (DEV, DEBUG, INFO, WARN, ERROR, FATAL)
fmtFormat string (printf-style)
...Format arguments
Note
Thread-safe via atomic operations.

Definition at line 835 of file log/log.h.

835#define log_once(log_level, fmt, ...) \
836 do { \
837 static LOG_ATOMIC_UINT64 _log_once_counter = LOG_ATOMIC_UINT64_INIT(0); \
838 uint64_t _log_once_count = atomic_load_u64(&_log_once_counter); \
839 if (_log_once_count == 0) { \
840 atomic_store_u64(&_log_once_counter, 1); \
841 log_msg(LOG_##log_level, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__); \
842 } \
843 } while (0)

◆ log_only

#define log_only ( bitmask,
level,
... )

#include <include/ascii-chat/log/log.h>

Value:
({ \
if ((bitmask) & LOG_BIT(level)) \
log_as(level, __VA_ARGS__); \
})

Log a message given its level and if the bitmask allows so.

Parameters
bitmaskThe severity bitmask, where level n is encoded as 2^n.
levelThe log level used by the message
...Format string and arguments (printf-style)

Definition at line 566 of file log/log.h.

566#define log_only(bitmask, level, ...) \
567 ({ \
568 if ((bitmask) & LOG_BIT(level)) \
569 log_as(level, __VA_ARGS__); \
570 })

◆ log_plain

#define log_plain ( ...)

#include <include/ascii-chat/log/log.h>

Value:
log_plain_msg(__VA_ARGS__)

Plain logging - writes to both log file and stderr without timestamps or log levels.

Parameters
...Format string and arguments (printf-style)

Definition at line 665 of file log/log.h.

Referenced by action_completions(), asciichat_error_stats_print(), audio_analysis_print_report(), config_create_default(), errno_print_chain(), errno_print_hash_stats(), errno_print_history(), errno_print_stacks(), named_print_hash_stats(), and prompt_unknown_host_no_identity().

◆ log_plain_stderr

#define log_plain_stderr ( ...)

#include <include/ascii-chat/log/log.h>

Value:
void log_plain_stderr_msg(const char *fmt,...)
Plain logging to stderr with newline.
Definition log.c:1658

Plain logging to stderr with newline.

Parameters
...Format string and arguments (printf-style)

Definition at line 672 of file log/log.h.

◆ log_plain_stderr_nonewline

#define log_plain_stderr_nonewline ( ...)

#include <include/ascii-chat/log/log.h>

Value:
void log_plain_stderr_nonewline_msg(const char *fmt,...)
Plain logging to stderr without trailing newline.
Definition log.c:1672

Plain logging to stderr without newline - for interactive prompts.

Parameters
...Format string and arguments (printf-style)

Definition at line 686 of file log/log.h.

◆ log_plain_stdout

#define log_plain_stdout ( ...)

#include <include/ascii-chat/log/log.h>

Value:
void log_plain_stdout_msg(const char *fmt,...)
Plain logging to stdout with newline.
Definition log.c:1751

Plain logging to stdout with newline.

Parameters
...Format string and arguments (printf-style)

Definition at line 679 of file log/log.h.

◆ LOG_SERVER_IMPL

#define LOG_SERVER_IMPL ( transport,
level,
fmt,
... )

#include <include/ascii-chat/network/log.h>

Value:
log_net_message(transport, level, REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER, __FILE__, __LINE__, __func__, \
fmt, ##__VA_ARGS__)
@ REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER
Definition network/log.h:23

Definition at line 83 of file network/log.h.

83#define LOG_SERVER_IMPL(transport, level, fmt, ...) \
84 log_net_message(transport, level, REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER, __FILE__, __LINE__, __func__, \
85 fmt, ##__VA_ARGS__)

◆ LOG_TERMINAL_BUFFER_SIZE

#define LOG_TERMINAL_BUFFER_SIZE   16384

#include <include/ascii-chat/log/log.h>

Terminal formatting budget, including NUL; distinct from persisted message storage.

Definition at line 104 of file log/log.h.

Referenced by grep_highlight_colored().

◆ log_terminal_colored_msg

#define log_terminal_colored_msg ( level,
colored_message,
plain_message )

#include <include/ascii-chat/log/log.h>

Value:
log_terminal_colored_msg_impl((level), __FILE__, __LINE__, __func__, (colored_message), (plain_message))
void log_terminal_colored_msg_impl(log_level_t level, const char *file, int line, const char *func, const char *colored_message, const char *plain_message)
Write semantically colored terminal text without persistent logging.
Definition log.c:1504

Definition at line 334 of file log/log.h.

334#define log_terminal_colored_msg(level, colored_message, plain_message) \
335 log_terminal_colored_msg_impl((level), __FILE__, __LINE__, __func__, (colored_message), (plain_message))

◆ LOG_TERMINAL_ENTRY_BUFFER_SIZE

#define LOG_TERMINAL_ENTRY_BUFFER_SIZE   (LOG_HEADER_BUFFER_SIZE + LOG_TERMINAL_BUFFER_SIZE)

#include <include/ascii-chat/log/log.h>

Header plus terminal formatting budget, including NUL.

Definition at line 107 of file log/log.h.

◆ LOG_TIMESTAMP_BUFFER_SIZE

#define LOG_TIMESTAMP_BUFFER_SIZE   32

#include <include/ascii-chat/log/log.h>

Maximum size of a timestamp string.

Definition at line 101 of file log/log.h.

Referenced by log_json_async_safe(), log_json_write(), log_mmap_write(), log_msg(), log_plain_msg(), and log_terminal_msg().

◆ log_warn

#define log_warn ( ...)

#include <include/ascii-chat/log/log.h>

Value:
#define LOG_WARN
Definition types.h:41

Log a WARN message.

Parameters
...Format string and arguments (printf-style)
Note
WARN messages can be stripped via LOG_COMPILE_LEVEL=LOG_ERROR.

Definition at line 621 of file log/log.h.

Referenced by acds_on_host_announcement_ex(), acds_send_current_host(), acds_server_init(), acds_validate_timestamp(), acds_verify_session_create(), acds_verify_session_join(), acip_tcp_transport_create(), acip_transport_destroy(), actions_execute_deferred(), add_transport_client(), ascii_convert_with_buffers_stabilized(), ascii_otel_shutdown(), ascii_otel_sqlite(), ascii_otel_sqlite_catalog(), ascii_write(), ascii_write_destroy(), ascii_write_init(), audio_process_received_samples(), audio_start_duplex(), audio_start_thread(), audio_stop_duplex(), audio_stop_thread(), broadcast_clear_console_to_all_clients(), capture_start_thread(), capture_stop_thread(), check_gpg_key_expiry(), check_known_host_no_identity(), client_audio_pipeline_create(), client_audio_pipeline_process_duplex(), client_protocol_start(), client_video_render_thread(), config_schema_build_from_configs(), connect_with_timeout(), consensus_coordinator_on_collection_start(), consensus_coordinator_on_election_result(), consensus_coordinator_on_ring_members(), consensus_coordinator_on_stats_update(), consensus_coordinator_process(), crypto_handshake_client_key_exchange(), crypto_rekey_abort(), crypto_rekey_init(), database_init(), database_session_join(), disconnect_client_for_bad_data(), discover_session_parallel(), discovery_keys_verify(), discovery_mdns_query(), discovery_session_check_host_alive(), discovery_session_create(), discovery_session_handle_host_disconnect(), display_init(), dns_test_connectivity(), env_pop_prompt_response(), ffmpeg_decoder_create_stdin(), ffmpeg_decoder_create_with_request_options(), ffmpeg_decoder_stop_prefetch(), find_client_by_id(), frame_validate_integrity(), gpg_agent_connect(), gpg_agent_sign(), gpg_get_public_key(), gpg_homedir_create(), gpg_homedir_destroy(), h265_decode(), handle_audio_opus_batch_packet(), handle_client_leave_packet(), handle_image_frame_h265_packet(), handle_image_frame_packet(), handle_protocol_version_packet(), keepalive_start_thread(), keepalive_stop_thread(), log_mmap_init(), log_search_enter_mode(), log_search_exit_mode(), main(), media_source_create_interruptible(), media_source_seek(), media_source_set_loop(), mixer_add_source(), nat_upnp_close(), nat_upnp_refresh(), options_state_init(), packet_receive(), packet_receive_via_transport(), packet_send_error(), parse_gpg_keys_from_response(), parse_public_keys(), precalc_rgb_palettes(), prompt_password(), prompt_password_simple_kind(), prompt_unknown_host(), render_file_create(), select_compatible_palette(), session_capture_create(), session_display_create(), session_display_encode_frame(), session_host_broadcast_frame(), session_host_receive_packet(), session_host_render_audio(), session_host_set_client_transport(), session_protocol_build_capabilities(), session_protocol_dispatch(), session_render_loop(), session_server_like_mdns_advertise(), session_server_like_run(), signaling_broadcast(), socket_configure_buffers(), splash_intro_start(), splash_wait_for_animation(), ssh_agent_has_key(), stun_servers_parse(), tcp_server_init(), term_renderer_create(), terminal_fd_reader_next(), thread_pool_interrupt_all(), thread_pool_stop_all(), ui_controller_toggle_logs(), ui_notice_first_use_complete(), ui_session_key(), update_banner_install_update(), update_banner_wait_for_check(), update_check_load_cache(), update_check_perform(), update_dimensions_to_terminal_size(), validate_gpg_key_format(), video_encoder_create(), webcam_cleanup_context(), webrtc_get_gathering_state(), webrtc_get_state(), webrtc_get_user_data(), webrtc_peer_manager_check_gathering_timeouts(), and websocket_server_init().

◆ log_warn_client

#define log_warn_client ( client,
fmt,
... )

#include <include/ascii-chat/network/log.h>

Value:
LOG_CLIENT_IMPL(client, LOG_WARN, fmt, ##__VA_ARGS__)

Server sends WARN log message to client.

Definition at line 70 of file network/log.h.

◆ log_warn_server

#define log_warn_server ( transport,
fmt,
... )

#include <include/ascii-chat/network/log.h>

Value:
LOG_SERVER_IMPL(transport, LOG_WARN, fmt, ##__VA_ARGS__)

Client sends WARN log message to server.

Definition at line 95 of file network/log.h.

◆ MAX_LOG_SIZE

#define MAX_LOG_SIZE   (3 * 1024 * 1024)

#include <include/ascii-chat/log/log.h>

Maximum log file size in bytes (3MB) before rotation.

Definition at line 76 of file log/log.h.

Referenced by log_truncate_if_large().

◆ MAX_TERMINAL_BUFFER_ENTRIES

#define MAX_TERMINAL_BUFFER_ENTRIES   256

#include <include/ascii-chat/log/log.h>

Maximum number of buffered log entries.

Definition at line 82 of file log/log.h.

◆ MAX_TERMINAL_BUFFER_SIZE

#define MAX_TERMINAL_BUFFER_SIZE   (64 * 1024)

#include <include/ascii-chat/log/log.h>

Maximum size of terminal output buffer (64KB).

Definition at line 79 of file log/log.h.

Typedef Documentation

◆ color_scheme_t

typedef struct color_scheme_t color_scheme_t

#include <include/ascii-chat/log/log.h>

Definition at line 57 of file log/log.h.

◆ remote_log_direction_t

#include <include/ascii-chat/network/log.h>

Remote log packet direction enumeration.

Indicates the originator of a remote log message so receivers can annotate logs clearly.

Note
This typedef MUST be defined before includes to avoid circular dependency issues with packet.h

◆ session_log_buffer_t

#include <src/common/session/session_log_buffer.c>

Thread-safe fixed-size history ring for session-screen log lines.

Opaque handle to log buffer instance.

Thread-safe fixed-size history ring for session-screen log lines.

entries stores formatted messages, while the atomic write position and sequence counter preserve ordering as new lines overwrite old ones. The mutex protects snapshots and clearing; the buffer owns its synchronization state until destruction.

Enumeration Type Documentation

◆ log_color_t

#include <include/ascii-chat/log/log.h>

Color enum for logging - indexes into color arrays.

Values through RESET index into level_colors arrays; WHITE is resolved separately. Order matches DEV, DEBUG, WARN, INFO, ERROR, FATAL, GREY, RESET.

Enumerator
LOG_COLOR_DEV 

Blue - DEV messages

LOG_COLOR_DEBUG 

Cyan - DEBUG messages

LOG_COLOR_INFO 

Green - INFO messages

LOG_COLOR_WARN 

Yellow - WARN messages

LOG_COLOR_ERROR 

Red - ERROR messages

LOG_COLOR_FATAL 

Magenta - FATAL messages

LOG_COLOR_GREY 

Grey - for neutral messages or labels

LOG_COLOR_RESET 

Reset to default

LOG_COLOR_WHITE 

White text, independent of the accent palette

Definition at line 147 of file log/log.h.

147 {
148 LOG_COLOR_DEV = 0,
149 LOG_COLOR_DEBUG = 1,
150 LOG_COLOR_INFO = 2,
151 LOG_COLOR_WARN = 3,
152 LOG_COLOR_ERROR = 4,
153 LOG_COLOR_FATAL = 5,
154 LOG_COLOR_GREY = 6,
155 LOG_COLOR_RESET = 7,
156 LOG_COLOR_WHITE = 8
log_color_t
Color enum for logging - indexes into color arrays.
Definition log/log.h:147
@ LOG_COLOR_RESET
Definition log/log.h:155
@ LOG_COLOR_GREY
Definition log/log.h:154
@ LOG_COLOR_FATAL
Definition log/log.h:153
@ LOG_COLOR_DEV
Definition log/log.h:148
@ LOG_COLOR_INFO
Definition log/log.h:150
@ LOG_COLOR_WARN
Definition log/log.h:151
@ LOG_COLOR_WHITE
Definition log/log.h:156

◆ log_level_t

#include <include/ascii-chat/log/types.h>

Logging levels enumeration.

Defines the severity levels for log messages. Used throughout the logging system without circular dependencies.

Enumerator
ASCIICHAT_LOG_DEV 

Development messages (most verbose)

ASCIICHAT_LOG_DEBUG 

Debug messages

ASCIICHAT_LOG_INFO 

Informational messages

ASCIICHAT_LOG_WARN 

Warning messages

ASCIICHAT_LOG_ERROR 

Error messages

ASCIICHAT_LOG_FATAL 

Fatal error messages (most severe)

Definition at line 29 of file types.h.

29 {
log_level_t
Logging levels enumeration.
Definition types.h:29
@ ASCIICHAT_LOG_DEBUG
Definition types.h:31
@ ASCIICHAT_LOG_WARN
Definition types.h:33
@ ASCIICHAT_LOG_INFO
Definition types.h:32
@ ASCIICHAT_LOG_DEV
Definition types.h:30
@ ASCIICHAT_LOG_FATAL
Definition types.h:35
@ ASCIICHAT_LOG_ERROR
Definition types.h:34

◆ remote_log_direction

#include <include/ascii-chat/network/log.h>

Remote log packet direction enumeration.

Indicates the originator of a remote log message so receivers can annotate logs clearly.

Note
This typedef MUST be defined before includes to avoid circular dependency issues with packet.h
Enumerator
REMOTE_LOG_DIRECTION_UNKNOWN 
REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT 
REMOTE_LOG_DIRECTION_CLIENT_TO_SERVER 

Definition at line 20 of file network/log.h.

20 {
enum remote_log_direction remote_log_direction_t
Remote log packet direction enumeration.
@ REMOTE_LOG_DIRECTION_UNKNOWN
Definition network/log.h:21

Function Documentation

◆ colorize_named_string()

const char * colorize_named_string ( const char * name_str)

#include <include/ascii-chat/log/named.h>

Colorize a "type/name" string for display.

Parameters
name_strString in "type/name" or "type/name.id" format
Returns
Colorized string with ANSI codes: type(yellow)/name(blue)

Used by memory report and other displays to colorize named objects. Applies colors:

  • Type: yellow (LOG_COLOR_WARN)
  • Name: blue (LOG_COLOR_DEV)
  • Separator: uncolored

Example: "thread/splash_anim.0" displays with type in yellow, name in blue

Note
Returns pointer to rotating static buffer (4 buffers)
Safe to use in printf-style calls: "%s"

Colorize a "type/name" string for display.

Parameters
name_strString in "type/name (0xaddress)" format (e.g., "thread/splash_anim.0 (0x7f123456)")
Returns
Colorized string with ANSI codes: type(yellow)/name(blue) (id(grey))

Used by memory report to colorize named object display. Returns a pointer to a static rotating buffer (4 buffers, valid until next call).

Example: "thread/splash_anim.0 (0x7f123456)" displays with:

  • "thread" in yellow
  • "splash_anim.0" in blue
  • "0x7f123456" in grey

Definition at line 951 of file log/named.c.

951 {
952#define COLORIZE_BUFFERS 4
953#define COLORIZE_BUFFER_SIZE 512
954 static char buffers[COLORIZE_BUFFERS][COLORIZE_BUFFER_SIZE];
955 static int buffer_idx = 0;
956
957 if (!name_str || name_str[0] == '\0') {
958 return name_str;
959 }
960
961 char *current_buf = buffers[buffer_idx];
962 buffer_idx = (buffer_idx + 1) % COLORIZE_BUFFERS;
963
964 /* Find the slash separator (type/name) */
965 const char *slash = strchr(name_str, '/');
966 if (!slash) {
967 /* No slash found - just return the string as-is */
968 return name_str;
969 }
970
971 /* Find opening paren for address */
972 const char *paren = strchr(name_str, '(');
973
974 /* Extract type (before slash) */
975 size_t type_len = slash - name_str;
977 size_t copy_len = type_len < sizeof(type_buf) - 1 ? type_len : sizeof(type_buf) - 1;
978 memcpy(type_buf, name_str, copy_len);
979 type_buf[copy_len] = '\0';
980
981 /* Extract name (between slash and paren, or end of string) */
982 const char *name_start = slash + 1;
983 size_t name_len;
984 if (paren && paren > slash) {
985 /* Skip spaces before paren */
986 const char *name_end = paren - 1;
987 while (name_end > name_start && *name_end == ' ') {
988 name_end--;
989 }
990 name_len = name_end - name_start + 1;
991 } else {
992 name_len = strlen(name_start);
993 }
994
995 char name_buf[LOG_NAMED_NAME_BUFFER_SIZE];
996 size_t name_copy_len = name_len < sizeof(name_buf) - 1 ? name_len : sizeof(name_buf) - 1;
997 memcpy(name_buf, name_start, name_copy_len);
998 name_buf[name_copy_len] = '\0';
999
1000 /* Apply colors */
1001 const char *type_colored = colored_string(LOG_COLOR_WARN, type_buf);
1002 const char *name_colored = colored_string(LOG_COLOR_DEV, name_buf);
1003
1004 /* Format: type(yellow)/name(blue) and add address part if present */
1005 if (paren) {
1006 /* Extract address (0x...) from parentheses */
1007 const char *addr_start = paren + 1;
1008 const char *addr_end = strchr(addr_start, ')');
1009 if (addr_end) {
1010 size_t addr_len = addr_end - addr_start;
1011 char addr_buf[LOG_NAMED_ADDR_BUFFER_SIZE];
1012 size_t addr_copy_len = addr_len < sizeof(addr_buf) - 1 ? addr_len : sizeof(addr_buf) - 1;
1013 memcpy(addr_buf, addr_start, addr_copy_len);
1014 addr_buf[addr_copy_len] = '\0';
1015
1016 const char *addr_colored = colored_string(LOG_COLOR_GREY, addr_buf);
1017 safe_snprintf(current_buf, COLORIZE_BUFFER_SIZE, "%s/%s (%s)", type_colored, name_colored, addr_colored);
1018 } else {
1019 safe_snprintf(current_buf, COLORIZE_BUFFER_SIZE, "%s/%s %s", type_colored, name_colored, paren);
1020 }
1021 } else {
1022 safe_snprintf(current_buf, COLORIZE_BUFFER_SIZE, "%s/%s", type_colored, name_colored);
1023 }
1024
1025 return current_buf;
1026#undef COLORIZE_BUFFERS
1027#undef COLORIZE_BUFFER_SIZE
1028}
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Platform-safe snprintf wrapper.
const char * colored_string(log_color_t color, const char *text)
Build a colored string for terminal output.
Definition string.c:284
#define LOG_NAMED_ADDR_BUFFER_SIZE
Definition log/named.c:26
#define COLORIZE_BUFFER_SIZE
#define LOG_NAMED_TYPE_EXTENDED_BUFFER_SIZE
Definition log/named.c:24
#define COLORIZE_BUFFERS
#define LOG_NAMED_NAME_BUFFER_SIZE
Definition log/named.c:25

References colored_string(), COLORIZE_BUFFER_SIZE, COLORIZE_BUFFERS, LOG_COLOR_DEV, LOG_COLOR_GREY, LOG_COLOR_WARN, LOG_NAMED_ADDR_BUFFER_SIZE, LOG_NAMED_NAME_BUFFER_SIZE, LOG_NAMED_TYPE_EXTENDED_BUFFER_SIZE, and safe_snprintf().

◆ format_message()

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

#include <include/ascii-chat/log/log.h>

Format a message using va_list.

Parameters
formatFormat string
argsVariable arguments list
Returns
Formatted message string (must be freed by caller)

Definition at line 278 of file log.c.

278 {
279 if (!format) {
280 return NULL;
281 }
282
283 // First, determine the size needed
284 va_list args_copy;
285 va_copy(args_copy, args);
286 int size = safe_vsnprintf(NULL, 0, format, args_copy);
287 va_end(args_copy);
288
289 if (size < 0) {
290 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format context message");
291 return NULL;
292 }
293
294 // Allocate and format the message
295 char *message = SAFE_MALLOC(size + 1, char *);
296 int result = safe_vsnprintf(message, (size_t)size + 1, format, args);
297 if (result < 0) {
298 SAFE_FREE(message);
299 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format context message");
300 return NULL;
301 }
302
303 return message;
304}
action_args_t args
Definition actions.c:78
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
@ ERROR_INVALID_STATE
int safe_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list ap)
Platform-safe vsnprintf wrapper.
#define LOGGING_INTERNAL_ERROR(error, message,...)
Definition log.c:233

References args, ERROR_INVALID_STATE, LOGGING_INTERNAL_ERROR, SAFE_FREE, SAFE_MALLOC, and safe_vsnprintf().

Referenced by log_net_message().

◆ get_current_time_formatted()

size_t get_current_time_formatted ( char * time_buf)

#include <include/ascii-chat/log/log.h>

Get current time as formatted string.

Parameters
time_bufOutput buffer for formatted time
Returns
Number of characters written (excluding null terminator)

Definition at line 246 of file log.c.

246 {
247 /* Get wall-clock time in nanoseconds */
248 uint64_t ts_ns = time_get_realtime_ns();
249 // Extract seconds and nanoseconds from total nanoseconds
250 time_t seconds = (time_t)(ts_ns / NS_PER_SEC_INT);
251 long nanoseconds = (long)(ts_ns % NS_PER_SEC_INT);
252 struct tm tm_info;
253 platform_localtime(&seconds, &tm_info);
254 // Format the time part first
255 // strftime returns 0 on error, not negative (and len is size_t/unsigned)
256 size_t len = strftime(time_buf, 32, "%H:%M:%S", &tm_info);
257 if (len == 0 || len >= 32) {
258 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format time");
259 return 0;
260 }
261
262 // Add microseconds manually (convert nanoseconds to microseconds for display)
263 long microseconds = nanoseconds / 1000;
264 if (microseconds < 0)
265 microseconds = 0;
266 if (microseconds > 999999)
267 microseconds = 999999;
268
269 int result = safe_snprintf(time_buf + len, 32 - len, ".%06ld", microseconds);
270 if (result < 0 || result >= (int)(32 - len)) {
271 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format microseconds");
272 return 0;
273 }
274
275 return len + (size_t)result;
276}
#define NS_PER_SEC_INT
Definition time.h:169
asciichat_error_t platform_localtime(const time_t *timer, struct tm *result)
Platform-safe localtime wrapper.

References ERROR_INVALID_STATE, LOGGING_INTERNAL_ERROR, NS_PER_SEC_INT, platform_localtime(), and safe_snprintf().

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

◆ log_cleanup_colors()

void log_cleanup_colors ( void )

#include <include/ascii-chat/log/log.h>

Clean up compiled color scheme.

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

Definition at line 2034 of file log.c.

2034{}

Referenced by asciichat_shared_destroy().

◆ log_clear_session_log_buffer()

void log_clear_session_log_buffer ( void )

#include <include/ascii-chat/log/log.h>

Unregister the session log buffer.

No-op if no buffer is currently registered.

Definition at line 2057 of file log.c.

2057 {
2058 atomic_ptr_store(&g_log.session_log_buffer, NULL);
2059 // An atomic pointer swap alone does not protect a writer that loaded the old
2060 // buffer. Wait for those writers before the UI releases its storage.
2061 while (atomic_load_u64_impl(&g_session_log_readers))
2062 platform_sleep_us(100);
2063}

Referenced by log_system_destroy(), splash_log_destroy(), ui_mdns_log_destroy(), ui_session_destroy(), and ui_status_log_destroy().

◆ log_colored_msg_impl()

void log_colored_msg_impl ( log_level_t level,
const char * file,
int line,
const char * func,
const char * colored_message,
const char * plain_message )

#include <include/ascii-chat/log/log.h>

Write semantically colored terminal text and an ANSI-free log equivalent.

Parameters
levelSeverity used for terminal routing and JSON output
colored_messageMessage containing semantic ANSI colors
plain_messageEquivalent message without ANSI colors

Preserves caller-supplied semantic coloring for terminal output while using the plain version for filtering, file/mmap logging, JSON, and non-color terminals. Both representations are routed as one logical log entry.

Definition at line 1499 of file log.c.

1500 {
1501 log_colored_msg_internal(level, file, line, func, colored_message, plain_message, true);
1502}

◆ log_destroy()

void log_destroy ( void )

#include <include/ascii-chat/log/log.h>

Destroy the logging system and close log file.

Definition at line 667 of file log.c.

667 {
668 // Destroy mmap logging first (if active)
669 if (log_mmap_is_active()) {
671 }
672
673 // Cleanup grep filter
674 grep_destroy();
675
676 /* Mark logging system as shutdown FIRST (state machine transition)
677 * This signals worker threads to stop logging immediately before we free memory.
678 * This prevents TOCTOU race where a worker thread could see non-NULL format pointer
679 * after we've freed it but before lifecycle_shutdown() is called. */
680 lifecycle_shutdown(&g_log.lifecycle);
681
682 // Cleanup custom format structures: All worker threads have been signaled to stop by this point.
683 // Safe to free templates now without use-after-free concerns.
684 if (g_log.format) {
685 log_template_free(g_log.format);
686 g_log.format = NULL;
687 }
688 if (g_log.format_console_only) {
689 log_template_free(g_log.format_console_only);
690 g_log.format_console_only = NULL;
691 }
692 atomic_store_bool(&g_log.has_custom_format, false);
693
694 // Lock-free cleanup using atomic operations
695 int old_file = atomic_load_int(&g_log.file);
696 if (old_file >= 0 && old_file != STDERR_FILENO) {
697 platform_close(old_file);
698 }
699 atomic_store_int(&g_log.file, -1);
700
701 // Destroy rotation mutex
702 lifecycle_shutdown(&g_log.rotation_mutex_lifecycle);
703}
void grep_destroy(void)
Clean up filter resources.
Definition grep.c:1005
void log_template_free(log_template_t *format)
Free compiled format structure.
Definition log/format.c:332
bool log_mmap_is_active(void)
Check if mmap logging is active.
Definition mmap.c:393
void log_mmap_destroy(void)
Shutdown mmap logging.
Definition mmap.c:263

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

Referenced by asciichat_shared_destroy(), and main().

◆ log_disable_file_output()

void log_disable_file_output ( void )

#include <include/ascii-chat/log/log.h>

Disable file output and use stderr instead.

Closes the current log file and redirects file output to stderr. Used when switching to JSON-only logging or when disabling text file output.

Definition at line 779 of file log.c.

779 {
780 /* Close the current file if it's not stderr */
781 int old_file = atomic_load_int(&g_log.file);
782 if (old_file >= 0 && old_file != STDERR_FILENO) {
783 platform_close(old_file);
784 }
785 /* Redirect file output to stderr */
786 atomic_store_int(&g_log.file, STDERR_FILENO);
787 g_log.filename[0] = '\0';
788}

Referenced by main().

◆ log_disable_mmap()

void log_disable_mmap ( void )

#include <include/ascii-chat/log/log.h>

Disable mmap logging and return to mutex-based logging.

Flushes remaining entries and closes the mmap file.

Definition at line 1996 of file log.c.

1996 {
1997 if (log_mmap_is_active()) {
1999 log_info("Lock-free mmap logging disabled");
2000 }
2001}

References log_info, log_mmap_destroy(), and log_mmap_is_active().

◆ log_enable_mmap()

asciichat_error_t log_enable_mmap ( const char * log_path)

#include <include/ascii-chat/log/log.h>

Enable lock-free mmap-based logging.

When enabled, log messages bypass the mutex and use atomic operations to write directly to a memory-mapped log file as human-readable text.

Benefits:

  • No mutex contention between logging threads
  • Crash-safe: text is written directly to mmap'd file, readable after crash
  • Fast path uses atomic fetch_add, no locks
  • ERROR/FATAL messages sync immediately for visibility
  • Simple: log file IS the mmap file (no separate binary format)
Parameters
log_pathPath to the log file (will be memory-mapped)
Returns
ASCIICHAT_OK on success, error code on failure
Note
Call log_init() first, then log_enable_mmap() to upgrade to lock-free

Definition at line 1977 of file log.c.

1977 {
1978 return log_enable_mmap_sized(log_path, 0); /* Use default size */
1979}
asciichat_error_t log_enable_mmap_sized(const char *log_path, size_t max_size)
Enable lock-free mmap logging with custom file size.
Definition log.c:1981

References log_enable_mmap_sized().

◆ log_enable_mmap_sized()

asciichat_error_t log_enable_mmap_sized ( const char * log_path,
size_t max_size )

#include <include/ascii-chat/log/log.h>

Enable lock-free mmap logging with custom file size.

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

Definition at line 1981 of file log.c.

1981 {
1982 if (!log_path) {
1983 return SET_ERRNO(ERROR_INVALID_PARAM, "log_path is required");
1984 }
1985
1986 // Initialize mmap logging - text is written directly to the mmap'd file
1987 asciichat_error_t result = log_mmap_init_simple(log_path, max_size);
1988 if (result != ASCIICHAT_OK) {
1989 return result;
1990 }
1991
1992 log_info("Lock-free mmap logging enabled: %s", log_path);
1993 return ASCIICHAT_OK;
1994}
@ ERROR_INVALID_PARAM
asciichat_error_t log_mmap_init_simple(const char *log_path, size_t max_size)
Initialize mmap logging with simple parameters.
Definition mmap.c:255

References ASCIICHAT_OK, ERROR_INVALID_PARAM, log_info, log_mmap_init_simple(), and SET_ERRNO.

Referenced by log_enable_mmap().

◆ log_file_msg()

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

#include <include/ascii-chat/log/log.h>

Log to file only, no stderr output.

Parameters
fmtFormat string (printf-style)
...Format arguments

Writes to log file only, without terminal output.

Definition at line 1765 of file log.c.

1765 {
1766 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
1767 return;
1768 }
1769
1770 char log_buffer[LOG_MSG_BUFFER_SIZE];
1771 va_list args;
1772 va_start(args, fmt);
1773 int msg_len = safe_vsnprintf(log_buffer, sizeof(log_buffer), fmt, args);
1774 va_end(args);
1775
1776 if (msg_len <= 0) {
1777 return;
1778 }
1779
1780 // Truncate at whole line boundaries to avoid UTF-8 issues
1781 msg_len = truncate_at_whole_line(log_buffer, msg_len, sizeof(log_buffer));
1782
1783 // Validate UTF-8 in formatted message
1784 validate_log_message_utf8(log_buffer, "file-only log");
1785 session_log_buffer_append(g_history, log_buffer);
1786
1787 // Write to mmap if active, else to file
1788 if (log_mmap_is_active()) {
1789 log_mmap_write(LOG_INFO, NULL, 0, NULL, "%s", log_buffer);
1790 } else {
1791 int file_fd = atomic_load_u64(&g_log.file);
1792 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
1793 write_to_log_file_atomic(log_buffer, msg_len, NULL);
1794 write_to_log_file_atomic("\n", 1, NULL);
1795 }
1796 }
1797}
#define LOG_MSG_BUFFER_SIZE
Maximum size of a single log message (including formatting).
Definition log/log.h:92
void log_mmap_write(int level, const char *file, int line, const char *func, const char *fmt,...)
Write a log entry directly to the mmap'd file (lock-free).
Definition mmap.c:315
void session_log_buffer_append(session_log_buffer_t *buf, const char *message)
Append a log message to the buffer.

References args, LOG_INFO, log_mmap_is_active(), log_mmap_write(), LOG_MSG_BUFFER_SIZE, safe_vsnprintf(), and session_log_buffer_append().

◆ log_get_color_array()

const char ** log_get_color_array ( void )

#include <include/ascii-chat/log/log.h>

Get the appropriate color array based on terminal capabilities.

Returns
Pointer to color array (16-color, 256-color, or truecolor)

Automatically detects terminal capabilities and returns the appropriate color array.

Definition at line 1911 of file log.c.

1911 {
1912 init_terminal_capabilities();
1913 colorscheme_current_palette(&live_log_palette, g_log.terminal_caps.color_level);
1914 for (int i = 0; i < 8; ++i)
1915 live_log_codes[i] = live_log_palette.codes[i];
1916 return live_log_codes;
1917}
void colorscheme_current_palette(colorscheme_palette_t *palette, terminal_color_mode_t mode)

References colorscheme_current_palette().

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

◆ log_get_color_mode()

int log_get_color_mode ( void )

#include <include/ascii-chat/log/log.h>

Current detected terminal color depth, shared by UI components.

Definition at line 1919 of file log.c.

1919 {
1920 init_terminal_capabilities();
1921 return g_log.terminal_caps.color_level;
1922}

Referenced by log_level_color_for_output(), and ui_buttons_render().

◆ log_get_force_stderr()

bool log_get_force_stderr ( void )

#include <include/ascii-chat/log/log.h>

Get current force_stderr setting.

Returns
true if all logs are forced to stderr, false otherwise

Definition at line 771 of file log.c.

771 {
772 return atomic_load_u64(&g_log.force_stderr);
773}

Referenced by terminal_choose_log_fd().

◆ log_get_foreground_color()

const char * log_get_foreground_color ( void )

#include <include/ascii-chat/log/log.h>

Theme- and capability-aware neutral text color from the active scheme.

Definition at line 1924 of file log.c.

1924 {
1926 return live_log_palette.foreground;
1927}
const char ** log_get_color_array(void)
Get the appropriate color array based on terminal capabilities.
Definition log.c:1911

References log_get_color_array().

◆ log_get_level()

log_level_t log_get_level ( void )

#include <include/ascii-chat/log/log.h>

Get the current minimum log level.

Returns
Current log level

Definition at line 742 of file log.c.

742 {
743 return (log_level_t)atomic_load_u64(&g_log.level);
744}

Referenced by errno_print_chain().

◆ log_get_session_log_buffer()

session_log_buffer_t * log_get_session_log_buffer ( void )

#include <include/ascii-chat/log/log.h>

Get the currently registered session log buffer.

Returns the buffer that was previously registered with log_set_session_log_buffer(). Returns NULL if no buffer is currently registered.

Returns
Pointer to registered buffer, or NULL

Returns the buffer that was previously registered with log_set_session_log_buffer(). Returns NULL if no buffer is currently registered.

Definition at line 2071 of file log.c.

2071 {
2072 return (session_log_buffer_t *)atomic_ptr_load(&g_log.session_log_buffer);
2073}
struct session_log_buffer session_log_buffer_t
Thread-safe fixed-size history ring for session-screen log lines.

Referenced by log_search_gather_and_filter_logs(), and ui_session_destroy().

◆ log_get_template()

void * log_get_template ( void )

#include <include/ascii-chat/log/log.h>

Get the current log format template (opaque pointer).

Returns
Opaque pointer to the current log format template (may be NULL if not set) Cast to log_template_t* after including log/format.h

Returns the compiled log format template used by the logging system. This is useful for code that needs to format log entries using the same template as the rest of the logging system (e.g., platform code).

Note
The returned pointer is valid only for the lifetime of the logging system
It's safe to call before log_init() (will return NULL)
Return type is void* (opaque) to avoid circular dependency with format.h

Get the current log format template (opaque pointer).

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 2375 of file log.c.

2375 {
2376 return (void *)g_log.format;
2377}

◆ log_get_terminal_output()

bool log_get_terminal_output ( void )

#include <include/ascii-chat/log/log.h>

Get current terminal output setting.

Returns
true if terminal output is enabled, false otherwise

Definition at line 763 of file log.c.

763 {
764 return atomic_load_u64(&g_log.terminal_output_enabled);
765}

Referenced by config_load_and_apply().

◆ log_init()

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

#include <include/ascii-chat/log/log.h>

Initialize the logging system.

Parameters
filenameLog file path (or NULL for no file logging)
levelMinimum log level to output
force_stderrIf true, route ALL logs to stderr (for client mode to keep stdout clean)
use_mmapIf true, use fully lock-free mmap logging (recommended). If mmap fails, uses stderr only (no mutex fallback).
Note
When use_mmap=true, the entire logging path is lock-free:
  • File output uses atomic operations on mmap'd memory
  • Terminal output uses atomic fprintf/fwrite to fd
  • No mutex is ever acquired in the hot path

Definition at line 569 of file log.c.

569 {
570
571 if (!g_history)
572 g_history = session_log_buffer_create();
573
574 // Initialize rotation mutex (only operation that uses a mutex)
575 g_log.rotation_mutex_lifecycle.sync_type = LIFECYCLE_SYNC_MUTEX;
576 g_log.rotation_mutex_lifecycle.sync.mutex = &g_log.rotation_mutex;
577 lifecycle_init(&g_log.rotation_mutex_lifecycle, "log_rotation");
578
579 // Set basic config using atomic stores
580 atomic_store_u64(&g_log.force_stderr, force_stderr);
581 bool preserve_terminal_output = atomic_load_u64(&g_log.terminal_output_enabled);
582
583 // Force logs to stderr if stdout is piped/redirected
584 // This prevents logs from contaminating piped/redirected output in snapshot/batch modes
585 // Per terminal.h: "force stderr when piped to prevent data corruption"
586 if (terminal_is_piped_output()) {
587 atomic_store_bool(&g_log.force_stderr, true);
588 }
589
590 // Close any existing file (atomic load/store)
591 int old_file = atomic_load_int(&g_log.file);
592 if (lifecycle_is_initialized(&g_log.lifecycle) && old_file >= 0 && old_file != STDERR_FILENO) {
593 platform_close(old_file);
594 atomic_store_int(&g_log.file, -1);
595 }
596
597 // Check LOG_LEVEL environment variable
598 const char *env_level_str = SAFE_GETENV("LOG_LEVEL");
599 if (env_level_str) {
600 atomic_store_u64(&g_log.level, (int)parse_log_level_from_env());
601 } else {
602 atomic_store_u64(&g_log.level, (int)level);
603 }
604
605 atomic_store_bool(&g_log.level_manually_set, false);
606 atomic_store_u64(&g_log.current_size, 0);
607
608 if (filename && strcmp(filename, "/dev/null") == 0) {
609 g_log.filename[0] = '\0';
610 atomic_store_int(&g_log.file, -1);
611 atomic_store_bool(&g_log.terminal_output_enabled, false);
612 } else if (filename) {
613 SAFE_STRNCPY(g_log.filename, filename, sizeof(g_log.filename) - 1);
614
615 if (use_mmap) {
616 // Lock-free mmap path - writes go to mmap'd file
617 asciichat_error_t mmap_result = log_mmap_init_simple(filename, 0);
618 if (mmap_result == ASCIICHAT_OK) {
619 atomic_store_int(&g_log.file, -1); // No regular fd - using mmap for file output
620 } else {
621 // Mmap failed - use stderr only (atomic writes, lock-free)
622 if (preserve_terminal_output) {
623 log_terminal_printf(STDERR_FILENO, "Mmap logging failed for %s, using stderr only (lock-free)\n", filename);
624 }
625 atomic_store_int(&g_log.file, STDERR_FILENO);
626 g_log.filename[0] = '\0';
627 }
628 } else {
629 // Lock-free file I/O path - uses atomic write() syscalls
630 int fd = platform_open("log_file", filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
631 atomic_store_int(&g_log.file, (fd >= 0) ? fd : STDERR_FILENO);
632 if (fd < 0) {
633 if (preserve_terminal_output) {
634 log_terminal_printf(STDERR_FILENO, "Failed to open log file: %s\n", filename);
635 }
636 g_log.filename[0] = '\0';
637 }
638 }
639 } else {
640 atomic_store_int(&g_log.file, STDERR_FILENO);
641 g_log.filename[0] = '\0';
642 }
643
644 /* Initialize default log format (NULL means use mode-specific default) */
645 log_set_format(NULL, false);
646
647 /* Mark logging system as initialized (state machine transition) */
648 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
649 lifecycle_init(&g_log.lifecycle, "logging");
650 }
651
652 atomic_store_u64(&g_log.terminal_output_enabled, preserve_terminal_output);
653
654 // Reset terminal detection if needed
655 if (g_log.terminal_caps_initialized && !g_log.terminal_caps.detection_reliable) {
656 g_log.terminal_caps_initialized = false;
657 }
658
659 // Detect terminal capabilities
661
662 // NOTE: Color initialization happens separately via log_set_color_scheme()
663 // after options are parsed. Logging works without colors until then.
664 // NOTE: Grep filter initialization happens in main.c after options_init() completes.
665}
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:423
#define SAFE_GETENV(name)
Definition common.h:449
void log_redetect_terminal_capabilities(void)
Re-detect terminal capabilities after logging is initialized.
Definition log.c:1880
session_log_buffer_t * session_log_buffer_create(void)
Create a new session log buffer instance.

References ASCIICHAT_OK, log_mmap_init_simple(), log_redetect_terminal_capabilities(), log_set_format(), SAFE_GETENV, SAFE_STRNCPY, and session_log_buffer_create().

Referenced by asciichat_shared_init(), client_init(), main(), and mirror_init().

◆ log_init_colors()

void log_init_colors ( void )

#include <include/ascii-chat/log/log.h>

Initialize logging color system with current terminal capabilities.

Compiles the active color scheme to ANSI codes based on terminal capabilities. Called automatically during terminal capability detection.

Definition at line 1963 of file log.c.

1963 {
1964 if (g_log.terminal_caps_detecting || !lifecycle_is_initialized(&g_log.lifecycle))
1965 return;
1967}
const color_scheme_t * colorscheme_get_active_scheme(void)
Get currently active color scheme.

References colorscheme_get_active_scheme().

Referenced by log_system_init(), and main().

◆ log_labeled()

void log_labeled ( const char * label,
log_color_t color,
const char * message,
... )

#include <include/ascii-chat/log/log.h>

Print a labeled message with color.

Parameters
labelThe label text to print (appears before the message)
colorColor for the label (from log_color_t enum)
messageFormat string (printf-style) for the message
...Format arguments

Used for consistent formatting of section headers and labeled output. The label is colored, followed by the message content. Output goes to both stderr and log file.

References args, and enabled.

◆ log_level_color()

const char * log_level_color ( log_color_t color)

#include <include/ascii-chat/log/log.h>

Get color string for a given color enum.

Parameters
colorColor enum value
Returns
ANSI color code string

Definition at line 1929 of file log.c.

1929 {
1930 if (color == LOG_COLOR_WHITE)
1931 return "\033[37m";
1932 const char **colors = log_get_color_array();
1933 if (colors == NULL) {
1934 return ""; /* Return empty string if colors not available */
1935 }
1936 if (color >= 0 && color <= LOG_COLOR_RESET) {
1937 return colors[color];
1938 }
1939 return colors[LOG_COLOR_RESET]; /* Return reset color if invalid */
1940}
unsigned char colors[(512 *256 *2) *3]
Definition fft.c:22

References colors, LOG_COLOR_RESET, LOG_COLOR_WHITE, and log_get_color_array().

Referenced by main().

◆ log_level_color_for_output()

const char * log_level_color_for_output ( log_color_t color,
bool enabled )

#include <include/ascii-chat/log/log.h>

Resolve a color after the caller has selected an output policy. Explicitly colored non-terminal output uses the portable 16-color palette. The returned thread-local string is valid until the next call.

Definition at line 1942 of file log.c.

1942 {
1943 if (!enabled)
1944 return "";
1945 if (color == LOG_COLOR_WHITE)
1946 return "\033[37m";
1947 static _Thread_local colorscheme_palette_t palette;
1948 // The caller already resolved whether this destination accepts color. Do not
1949 // re-check stdout here: reports can target a terminal independent of stdout.
1950 terminal_color_mode_t mode = GET_OPTION(color_mode);
1951 if (mode == TERM_COLOR_AUTO)
1952 mode = (terminal_color_mode_t)log_get_color_mode();
1953 if (mode == TERM_COLOR_NONE && GET_OPTION(color_mode) == TERM_COLOR_AUTO)
1954 mode = TERM_COLOR_16;
1955 colorscheme_current_palette(&palette, mode);
1956 return palette.codes[color >= 0 && color <= LOG_COLOR_RESET ? color : LOG_COLOR_RESET];
1957}
int color_mode
Definition fft.c:25
bool enabled
Is filtering active?
Definition grep.c:62
int log_get_color_mode(void)
Definition log.c:1919
#define GET_OPTION(field)
Safely get a specific option field (lock-free read).
Definition options.h:1336

References color_mode, colorscheme_current_palette(), enabled, GET_OPTION, LOG_COLOR_RESET, LOG_COLOR_WHITE, and log_get_color_mode().

Referenced by colored_string(), and log_template_apply().

◆ log_lock_terminal()

bool log_lock_terminal ( void )

#include <include/ascii-chat/log/log.h>

Lock terminal output for exclusive access by the calling thread.

Call this before interactive prompts (like password entry, yes/no questions) to ensure only the calling thread can output to the terminal. Other threads' log messages will be buffered and flushed when the terminal is unlocked.

While locked:

  • The locking thread can use log_plain() to write to terminal
  • Other threads' log messages go to log file and are buffered
  • Buffered messages are flushed to terminal on unlock

Must be paired with log_unlock_terminal().

Returns
The previous terminal lock state (for nested calls)

Definition at line 833 of file log.c.

833 {
834 bool expected = atomic_load_bool(&g_log.terminal_locked);
835 while (!atomic_cas_bool(&g_log.terminal_locked, &expected, true)) {
836 // Retry if CAS failed
837 }
838 atomic_store_u64(&g_log.terminal_owner_thread, (uint64_t)thread_self());
839 return expected;
840}

◆ log_msg()

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

#include <include/ascii-chat/log/log.h>

Log a message at a specific level.

Parameters
levelLog level (LOG_DEV, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL)
fileSource file name (or NULL to omit)
lineSource line number (or 0 to omit)
funcFunction name (or NULL to omit)
fmtFormat string (printf-style)
...Format arguments

Definition at line 1196 of file log.c.

1196 {
1197#ifndef __EMSCRIPTEN__
1198 va_list telemetry_args;
1199 va_start(telemetry_args, fmt);
1200 ascii_otel_log((int)level, file, line, func, fmt, telemetry_args);
1201 va_end(telemetry_args);
1202#endif
1203
1204 // All state access uses atomic operations - fully lock-free
1205 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
1206 return;
1207 }
1208
1209 // Safety check: if main format template has been freed during shutdown, skip logging
1210 // This prevents heap-use-after-free when threads call logging after log_destroy() has freed g_log.format
1211 if (!g_log.format) {
1212 return;
1213 }
1214
1215 uint64_t loaded_level = atomic_load_u64(&g_log.level);
1216
1217 if (level < (log_level_t)loaded_level && !(g_notice_text && g_notice_unfiltered)) {
1218 return;
1219 }
1220 /* =========================================================================
1221 * MMAP PATH: When mmap logging is active, writes go to mmap'd file
1222 * ========================================================================= */
1223 bool mmap_active = log_mmap_is_active();
1224
1225 if (mmap_active) {
1226 maybe_rotate_log();
1227
1228 va_list args;
1229 va_start(args, fmt);
1230 char msg_buffer[LOG_MMAP_MSG_BUFFER_SIZE];
1231 int msg_len = safe_vsnprintf(msg_buffer, sizeof(msg_buffer), fmt, args);
1232 va_end(args);
1233
1234 // Truncate at whole line boundaries to avoid UTF-8 issues
1235 if (msg_len > 0) {
1236 msg_len = truncate_at_whole_line(msg_buffer, msg_len, sizeof(msg_buffer));
1237 }
1238
1239 // Validate UTF-8 in formatted message
1240 validate_log_message_utf8(msg_buffer, "mmap log message");
1241
1242 if (!g_notice_text)
1243 session_log_buffer_append(g_history, msg_buffer);
1244 log_mmap_write(level, file, line, func, "%s", msg_buffer);
1245 platform_systemd_journal_log(level, msg_buffer, "ascii-chat", file, line, func);
1246
1247 if (g_notice_text) {
1248 output_notice(level, file, line, func);
1249 return;
1250 }
1251
1252 // Terminal output (check with atomic loads)
1253 if (atomic_load_u64(&g_log.terminal_output_enabled) && !atomic_load_bool(&g_log.terminal_locked) &&
1255 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
1256 uint64_t time_ns = time_get_realtime_ns();
1258
1259 // Choose output stream using unified routing logic
1260 int fd = terminal_choose_log_fd(level);
1261 FILE *output_stream = (fd == STDERR_FILENO) ? stderr : stdout;
1262 bool use_colors = terminal_should_color_output(fd, options_terminal_settings());
1263
1264 char header_buffer[LOG_HEADER_BUFFER_SIZE];
1265 int header_len = format_log_header(header_buffer, sizeof(header_buffer), level, time_buf, file, line, func,
1266 use_colors, time_ns);
1267
1268 if (header_len >= 0 && header_len < (int)sizeof(header_buffer)) {
1269 if (use_colors) {
1270 const char *colorized_msg = colorize_log_message(msg_buffer);
1271 const char **colors = log_get_color_array();
1272 if (colors) {
1273 log_terminal_printf(output_stream == stdout ? STDOUT_FILENO : STDERR_FILENO, "%s%s%s%s\n", header_buffer,
1274 colors[LOG_COLOR_RESET], colorized_msg, colors[LOG_COLOR_RESET]);
1275 } else {
1276 log_terminal_printf(output_stream == stdout ? STDOUT_FILENO : STDERR_FILENO, "%s%s\n", header_buffer,
1277 colorized_msg);
1278 }
1279 } else {
1280 log_terminal_printf(output_stream == stdout ? STDOUT_FILENO : STDERR_FILENO, "%s%s\n", header_buffer,
1281 msg_buffer);
1282 }
1283 (void)fflush(output_stream);
1284 }
1285 } else {
1286 }
1287 return;
1288 }
1289 /* =========================================================================
1290 * FILE I/O PATH: Lock-free using atomic write() syscalls
1291 * ========================================================================= */
1292 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
1293 uint64_t time_ns = time_get_realtime_ns();
1295 // Format message for file output
1296 char log_buffer[LOG_MSG_BUFFER_SIZE];
1297 va_list args;
1298 va_start(args, fmt);
1299 int header_len = format_log_header(log_buffer, sizeof(log_buffer), level, time_buf, file, line, func, false, time_ns);
1300 if (header_len < 0 || header_len >= (int)sizeof(log_buffer)) {
1301 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format log header");
1302 va_end(args);
1303 return;
1304 }
1305 int msg_len = header_len;
1306 int formatted_len = safe_vsnprintf(log_buffer + header_len, sizeof(log_buffer) - (size_t)header_len, fmt, args);
1307 if (formatted_len < 0) {
1308 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format log message");
1309 va_end(args);
1310 return;
1311 }
1312
1313 msg_len += formatted_len;
1314 // Truncate at whole line boundaries to avoid UTF-8 issues
1315 msg_len = truncate_at_whole_line(log_buffer, msg_len, sizeof(log_buffer));
1316 // Add newline if there's room and message doesn't already end with one
1317 if (msg_len > 0 && msg_len < (int)sizeof(log_buffer) - 1) {
1318 if (log_buffer[msg_len - 1] != '\n') {
1319 log_buffer[msg_len++] = '\n';
1320 log_buffer[msg_len] = '\0';
1321 }
1322 }
1323 va_end(args);
1324 // Validate UTF-8 in formatted message
1325 validate_log_message_utf8(log_buffer, "leveled log message");
1326 platform_systemd_journal_log(level, log_buffer + header_len, "ascii-chat", file, line, func);
1327
1328 // Extract the user message part (without header) for JSON logging
1329 const char *user_message = log_buffer + header_len;
1330
1331 // For JSON output: strip automatic trailing newline (keep explicit ones)
1332 char json_message_buf[LOG_MSG_BUFFER_SIZE];
1333 const char *json_message = user_message;
1334 size_t user_msg_len = strlen(user_message);
1335
1336 // If message ends with newline and it was added automatically (not in original message)
1337 if (user_msg_len > 0 && user_message[user_msg_len - 1] == '\n') {
1338 // The newline was added automatically if the message part didn't end with one
1339 // before we added it on line 1067. We can check this by looking at msg_len - 1
1340 // If msg_len >= header_len + 2, then msg_len - 2 would be the char before the newline
1341 int msg_content_len = msg_len - header_len;
1342 // NOLINTNEXTLINE(clang-analyzer-security.ArrayBound) - msg_content_len > 1 bounds check above
1343 if (msg_content_len > 1 && log_buffer[msg_len - 2] != '\n') {
1344 // Newline was added automatically - strip it for JSON
1345 if (user_msg_len - 1 < sizeof(json_message_buf)) {
1346 memcpy(json_message_buf, user_message, user_msg_len - 1);
1347 json_message_buf[user_msg_len - 1] = '\0';
1348 json_message = json_message_buf;
1349 }
1350 }
1351 }
1352
1353 // Check if JSON format is enabled
1354 int json_fd = atomic_load_int(&g_log.json_file);
1355 bool json_format_enabled = (json_fd >= 0);
1356
1357 // If JSON format is enabled, output ONLY JSON (skip text output)
1358 if (json_format_enabled) {
1359 session_log_buffer_append(g_history, log_buffer);
1360 // Output JSON to the JSON file descriptor
1361 log_json_write(json_fd, level, time_ns, file, line, func, json_message);
1362 // Also output JSON to console using unified routing logic, respecting quiet flag
1363 if (atomic_load_u64(&g_log.terminal_output_enabled)) {
1364 int console_fd = terminal_choose_log_fd(level);
1365 log_json_write(console_fd, level, time_ns, file, line, func, json_message);
1366 }
1367 } else {
1368 // Text format: output to file and terminal
1369 // No heap allocation - use log_buffer directly
1370 // Write to file (atomic write syscall) - use original buffer (with ANSI codes)
1371 int file_fd = (int)atomic_load_u64(&g_log.file);
1372 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
1373 write_to_log_file_atomic(log_buffer, msg_len, NULL);
1374 }
1375
1376 if (g_notice_text) {
1377 output_notice(level, file, line, func);
1378 return;
1379 }
1380
1381 // Write to terminal (atomic state checks)
1382 va_list args_terminal;
1383 va_start(args_terminal, fmt);
1384 write_to_terminal_atomic(level, time_buf, file, line, func, fmt, args_terminal, time_ns, true);
1385 va_end(args_terminal);
1386 }
1387}
const char * colorize_log_message(const char *message)
Colorize a log message for terminal output.
Definition colorize.c:536
#define LOG_HEADER_BUFFER_SIZE
Maximum size of a log header (timestamp, level, file:line:func).
Definition log/log.h:98
size_t get_current_time_formatted(char *time_buf)
Get current time as formatted string.
Definition log.c:246
#define LOG_TIMESTAMP_BUFFER_SIZE
Maximum size of a timestamp string.
Definition log/log.h:101
#define LOG_MMAP_MSG_BUFFER_SIZE
Maximum size of a log message in mmap mode.
Definition log/log.h:95
#define options_terminal_settings()
Definition options.h:1296
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)
Write a log entry as a JSON object to the json output fd.
Definition log/json.c:104
int terminal_choose_log_fd(int level)
Definition log/terminal.c:7
void ascii_otel_log(int level, const char *file, int line, const char *function, const char *format, va_list args)
Definition otel.c:700
bool ui_controller_is_presenting(void)

References args, ascii_otel_log(), colorize_log_message(), colors, ERROR_INVALID_STATE, get_current_time_formatted(), LOG_COLOR_RESET, log_get_color_array(), LOG_HEADER_BUFFER_SIZE, log_json_write(), log_mmap_is_active(), LOG_MMAP_MSG_BUFFER_SIZE, log_mmap_write(), LOG_MSG_BUFFER_SIZE, LOG_TIMESTAMP_BUFFER_SIZE, LOGGING_INTERNAL_ERROR, options_terminal_settings, safe_vsnprintf(), session_log_buffer_append(), terminal_choose_log_fd(), and ui_controller_is_presenting().

Referenced by handle_remote_log_packet_from_client(), log_net_message(), and ui_notice_confirm_kind().

◆ log_named_format_message()

int log_named_format_message ( const char * message,
char * output,
size_t output_size )

#include <include/ascii-chat/log/named.h>

Format hex addresses in a message as named object descriptions.

Parameters
messageInput message (may contain hex addresses like 0x123456)
outputOutput buffer for formatted message
output_sizeSize of output buffer (must be > 0)
Returns
Length of output string (excluding null terminator) if any formatting was applied, -1 if no formatting was applied or on error

Scans the input message for hex addresses in the format 0x[0-9a-fA-F]+. For each address found, checks the named object registry. If registered, replaces the address with "type: name (0xaddress)" format. If not registered, leaves the original hex address unchanged.

The output buffer is always null-terminated.

Note
The formatting is idempotent - applying it twice gives the same result
This function is efficient for messages with few hex addresses (common case)
Works correctly with 32-bit and 64-bit address formats

Definition at line 572 of file log/named.c.

572 {
573 if (!message || !output || output_size == 0) {
574 return -1;
575 }
576
577 size_t out_pos = 0;
578 const char *p = message;
579 bool any_transformed = false;
580
581 while (*p && out_pos < output_size - 1) {
582 /* Look for "0x" prefix indicating a hex address */
583 if (*p == '0' && *(p + 1) == 'x' && isxdigit(*(p + 2))) {
584 const char *hex_start = p;
585 if (try_format_hex_address(message, hex_start, output, output_size, &out_pos, &p)) {
586 any_transformed = true;
587 continue;
588 }
589
590 /* Not registered or formatting failed - copy original hex address */
591 const char *hex_end = hex_start + 2; /* Start after "0x" */
592 while (*hex_end && isxdigit(*hex_end)) {
593 hex_end++;
594 }
595 size_t hex_len = hex_end - hex_start;
596 if (hex_len < output_size - out_pos) {
597 memcpy(output + out_pos, hex_start, hex_len);
598 out_pos += hex_len;
599 p = hex_end;
600 } else {
601 /* Buffer overflow - truncate and stop */
602 break;
603 }
604 } else if (isdigit(*p)) {
605 /* Look for decimal integers that might be FDs or packet types */
606 const char *int_start = p;
607 int fd_value = 0;
608 int digit_count = 0;
609
610 /* Parse decimal digits */
611 while (*p && isdigit(*p) && digit_count < 6) { /* FDs rarely exceed 999999 */
612 fd_value = fd_value * 10 + (*p - '0');
613 p++;
614 digit_count++;
615 }
616
617 /* Skip if this integer is part of already-formatted output like "(fd=20)" or "(type/name (...))" */
618 bool is_already_formatted = false;
619 if (int_start - message >= 5) {
620 /* Check for "(fd=", "(pkt_type=", "(socket=", "(sockfd=", or "(type/" patterns to prevent re-formatting.
621 * Named objects are formatted as: type/name (0xaddress) or type/name (key=value)
622 * We need to detect if we're already inside a formatted output.
623 */
624 const char *check = int_start - 1;
625 while (check > message && isspace(*check))
626 check--;
627
628 // Only access *check if pointer is within bounds
629 if (check >= message && *check == '=') {
630 /* Found "=", now check what prefix it has for patterns like "(fd=20)" or "(socket=20)" or "(pkt_type=123)" */
631 const char *eq_pos = check;
632 check--;
633 while (check > message && (isalnum(*check) || *check == '_')) {
634 check--;
635 }
636 check++;
637
638 size_t prefix_len = eq_pos - check;
639 if ((prefix_len == 2 && strncmp(check, "fd", 2) == 0) ||
640 (prefix_len == 6 && strncmp(check, "socket", 6) == 0) ||
641 (prefix_len == 6 && strncmp(check, "sockfd", 6) == 0) ||
642 (prefix_len == 8 && strncmp(check, "pkt_type", 8) == 0)) {
643 /* Only mark as already-formatted if inside formatted output:
644 * - "(" before keyword: parenthesized value like "(sockfd=15)"
645 * - "/" before keyword: name part of type/name like "fd/fd=7"
646 * Raw log patterns like "sockfd=15" should still be eligible for replacement. */
647 const char *before_kw = check - 1;
648 while (before_kw > message && isspace(*before_kw))
649 before_kw--;
650 if (before_kw >= message && (*before_kw == '(' || *before_kw == '/')) {
651 is_already_formatted = true;
652 }
653 }
654 } else if (check >= message && *check == '/') {
655 /* Found "/", now check if this is a type/name pattern.
656 * Format is "(type/name (...)" where type is word characters (thread, mutex, socket, etc.)
657 * Must verify "/" is preceded by word characters AND "(" to ensure it's a formatted type.
658 */
659 const char *slash_pos = check;
660 check--;
661 while (check > message && (isalnum(*check) || *check == '_')) {
662 check--;
663 }
664 check++;
665
666 /* Check that the "/" was preceded by word characters (indicating a type name) */
667 size_t type_len = slash_pos - check;
668 if (type_len > 0 && (isalpha(*check) || *check == '_')) {
669 /* Verify this is a registered type/name pattern by checking for "(" before the type
670 * AND checking if the type actually exists in the registry */
671 const char *type_start = check;
672 const char *before_type = type_start - 1;
673 while (before_type > message && isspace(*before_type)) {
674 before_type--;
675 }
676
677 /* Only mark as formatted if:
678 * 1. Preceded by "(" - ensures pattern looks like "(type/name ...)"
679 * 2. Type exists in registry - prevents false positives on random text */
680 if (before_type >= message && *before_type == '(' && is_type_in_registry(type_start, type_len)) {
681 is_already_formatted = true;
682 }
683 }
684 }
685 }
686
687 /* Check if this is a registered packet type */
688 if (!is_already_formatted && digit_count > 0 && has_packet_type_prefix(message, int_start)) {
689 named_metadata_t metadata;
690 const char *name = named_packet_metadata_copy(fd_value, &metadata) ? metadata.name : NULL;
691 if (name) {
692 const char *prefix_start = find_packet_type_prefix_start(message, int_start);
693 size_t prefix_len = int_start - prefix_start;
694 size_t saved_pos = out_pos;
695
696 if (out_pos >= prefix_len) {
697 out_pos -= prefix_len;
698 }
699
700 if (write_formatted_packet_type(fd_value, name, output, output_size, &out_pos)) {
701 any_transformed = true;
702 continue;
703 }
704
705 out_pos = saved_pos; /* Restore on failure */
706 }
707 }
708
709 /* Check if this is a registered file descriptor */
710 if (!is_already_formatted && digit_count > 0 && has_fd_prefix(message, int_start)) {
711 named_metadata_t metadata;
712 const char *name = named_fd_metadata_copy(fd_value, &metadata) ? metadata.name : NULL;
713 const char *reg_type = NULL;
714 if (!name && named_metadata_copy((uintptr_t)(intptr_t)fd_value, &metadata)) {
715 name = metadata.name;
716 reg_type = metadata.type;
717 }
718
719 if (name) {
720 const char *prefix_start = find_fd_prefix_start(message, int_start);
721 size_t prefix_len = int_start - prefix_start;
722 size_t saved_pos = out_pos;
723
724 if (out_pos >= prefix_len) {
725 out_pos -= prefix_len;
726 }
727
728 bool formatted = false;
729 if (reg_type) {
730 /* From main registry — use registered type and original prefix keyword */
731 const char *kw_end = prefix_start;
732 while (kw_end < int_start && (isalnum(*kw_end) || *kw_end == '_'))
733 kw_end++;
734 size_t kw_len = kw_end - prefix_start;
735
736 char kw_buf[LOG_NAMED_KW_BUFFER_SIZE];
737 if (kw_len > 0 && kw_len < sizeof(kw_buf)) {
738 memcpy(kw_buf, prefix_start, kw_len);
739 kw_buf[kw_len] = '\0';
740 } else {
741 snprintf(kw_buf, sizeof(kw_buf), "%s", reg_type);
742 }
743
745 int written = snprintf(temp, sizeof(temp), "%s/%s (%s=%d)", reg_type, name, kw_buf, fd_value);
746 if (written > 0 && (size_t)written < sizeof(temp) && out_pos + (size_t)written < output_size - 1) {
747 memcpy(output + out_pos, temp, (size_t)written);
748 out_pos += (size_t)written;
749 formatted = true;
750 }
751 } else {
752 formatted = write_formatted_fd(fd_value, name, output, output_size, &out_pos);
753 }
754
755 if (formatted) {
756 any_transformed = true;
757 continue;
758 }
759
760 out_pos = saved_pos; /* Restore on failure */
761 }
762 }
763
764 /* Check if this integer has a generic type prefix (socket, client, connection, etc.)
765 * DISABLED: This feature has a backtracking bug that corrupts output when applied after
766 * other formatting operations. Re-enable only after fixing the backtracking logic to
767 * properly track correspondence between input and output positions.
768 * See commit b86fed2a8 which introduced the bug.
769 */
770 // NOLINTNEXTLINE(readability-simplify-boolean-expr) - Intentionally disabled code, see comment above
771 if (false && !is_already_formatted && digit_count > 0) {
772 const char *type_name = NULL;
773 size_t type_len = 0;
774 const char *prefix_start = find_generic_type_prefix(message, int_start, &type_name, &type_len);
775
776 if (type_name && prefix_start != int_start) {
777 /* Skip if already formatted with a name (e.g., "socket/listener.0" already has a /) */
778 bool skip_format = false;
779 /* Check if this looks like it's already been formatted by looking for / in the pattern */
780 const char *check_ptr = prefix_start;
781 while (check_ptr < int_start && *check_ptr && *check_ptr != '/') {
782 check_ptr++;
783 }
784 if (check_ptr < int_start && *check_ptr == '/') {
785 /* Already formatted (contains /), skip */
786 skip_format = true;
787 }
788
789 if (!skip_format) {
790 /* We found a type prefix like "socket", "client", etc.
791 * Try to look up a registered name for this type+id */
792
793 /* Normalize type name aliases: "sockfd" → "socket", "descriptor" → "fd" */
794 const char *lookup_type = type_name;
795 size_t lookup_len = type_len;
796 if (type_len == 6 && strncmp(type_name, "sockfd", 6) == 0) {
797 lookup_type = "socket";
798 lookup_len = 6;
799 } else if (type_len == 10 && strncmp(type_name, "descriptor", 10) == 0) {
800 lookup_type = "fd";
801 lookup_len = 2;
802 }
803
804 const char *name = named_get_by_type_and_id(lookup_type, lookup_len, fd_value);
805
806 char temp_output[LOG_NAMED_TEMP_OUTPUT_BUFFER_SIZE];
808 char id_buffer[LOG_NAMED_ID_BUFFER_SIZE];
809
810 /* Safely copy type name */
811 if (type_len < sizeof(type_str)) {
812 memcpy(type_str, type_name, type_len);
813 type_str[type_len] = '\0';
814
815 int id_written = snprintf(id_buffer, sizeof(id_buffer), "%d", fd_value);
816 if (id_written > 0 && id_written < (int)sizeof(id_buffer)) {
817 int temp_written = 0;
818 if (name) {
819 /* Format with name: type/name (type=value) */
820 temp_written =
821 snprintf(temp_output, sizeof(temp_output), "%s/%s (%s=%s)", type_str, name, type_str, id_buffer);
822
823 if (temp_written > 0 && (size_t)temp_written < sizeof(temp_output)) {
824 /* Backtrack to remove the prefix we already copied */
825 size_t prefix_len = int_start - prefix_start;
826 if (out_pos >= prefix_len) {
827 out_pos -= prefix_len;
828 }
829
830 int copy_len = temp_written;
831 if (out_pos + copy_len < output_size - 1) {
832 memcpy(output + out_pos, temp_output, copy_len);
833 out_pos += copy_len;
834 any_transformed = true;
835 continue;
836 }
837 }
838 }
839 }
840 }
841 }
842 }
843 }
844
845 /* Not a registered FD or formatting failed - copy original number */
846 if (!copy_unformatted_decimal(int_start, p, output, output_size, &out_pos)) {
847 break;
848 }
849 } else {
850 /* Check if this is the start of "type=DIGIT" pattern */
851 if (*p == 't' && p + 4 < message + strlen(message) && is_type_equals_pattern(p)) {
852 if (!type_equals_inside_parens(p, message)) {
853 const char *digit_end = p + 5;
854 int pkt_value = parse_decimal_digits(digit_end, &digit_end);
855
856 if (pkt_value > 0) {
857 named_metadata_t metadata;
858 const char *name = named_packet_metadata_copy(pkt_value, &metadata) ? metadata.name : NULL;
859 if (name && write_formatted_type_equals(pkt_value, name, output, output_size, &out_pos)) {
860 p = digit_end;
861 any_transformed = true;
862 continue;
863 }
864 }
865 }
866 }
867
868 /* Regular character - copy it */
869 copy_char_to_output(output, &out_pos, *p++);
870 }
871 }
872
873 /* Null terminate */
874 if (out_pos < output_size) {
875 output[out_pos] = '\0';
876 } else {
877 output[output_size - 1] = '\0';
878 }
879
880 return any_transformed ? (int)out_pos : -1;
881}
bool named_packet_metadata_copy(int packet_type, named_metadata_t *output)
bool named_metadata_copy(uintptr_t key, named_metadata_t *output)
bool named_fd_metadata_copy(int fd, named_metadata_t *output)
const char * named_get_by_type_and_id(const char *type_name, size_t type_len, int id)
Look up an integer ID by type name and value.
#define LOG_NAMED_KW_BUFFER_SIZE
Definition log/named.c:21
#define LOG_NAMED_TYPE_SHORT_BUFFER_SIZE
Definition log/named.c:23
#define LOG_NAMED_ID_BUFFER_SIZE
Definition log/named.c:17
#define LOG_NAMED_TEMP_OUTPUT_BUFFER_SIZE
Definition log/named.c:18
#define LOG_NAMED_TEMP_BUFFER_SIZE
Definition log/named.c:22
The useful part of named metadata is in its fields: together they describe what is pending,...

References LOG_NAMED_ID_BUFFER_SIZE, LOG_NAMED_KW_BUFFER_SIZE, LOG_NAMED_TEMP_BUFFER_SIZE, LOG_NAMED_TEMP_OUTPUT_BUFFER_SIZE, LOG_NAMED_TYPE_SHORT_BUFFER_SIZE, named_metadata_t::name, named_fd_metadata_copy(), named_get_by_type_and_id(), named_metadata_copy(), named_packet_metadata_copy(), and named_metadata_t::type.

Referenced by log_named_format_or_original().

◆ log_named_format_or_original()

const char * log_named_format_or_original ( const char * message)

#include <include/ascii-chat/log/named.h>

Format named objects in a message (thread-local buffer).

Parameters
messageInput message
Returns
Formatted message string if any formatting was applied, original message if no formatting was applied

Convenience wrapper that handles buffer management internally using a thread-local buffer. The returned pointer is valid only until the next call to this function from the same thread.

Safe to use directly in log macros:

const char * log_named_format_or_original(const char *message)
Format named objects in a message (thread-local buffer).
Definition log/named.c:892
Note
Maximum formatted message length is 4096 bytes
If format buffer overflows, returns original message
Parameters
messageInput message
Returns
Formatted message string (thread-local buffer, valid only until next call)

Convenience wrapper that uses a thread-local buffer internally. Applies formatting iteratively until the message stops changing (fixpoint). Returns the original message if no formatting was applied.

Definition at line 892 of file log/named.c.

892 {
893 if (!message) {
894 return message;
895 }
896
897 static _Thread_local char format_buffer[NAMED_FORMAT_BUFFER_SIZE];
898 static _Thread_local char work_buffer[NAMED_FORMAT_BUFFER_SIZE];
899
900 /* Apply formatting iteratively until the message stabilizes (no more changes) */
901 const char *current = message;
902 size_t max_iterations =
903 3; /* Reduce iterations to prevent recursion (prevent transforming already-transformed output) */
904
905 for (size_t iter = 0; iter < max_iterations; iter++) {
906 int result = log_named_format_message(current, format_buffer, sizeof(format_buffer));
907
908 if (result <= 0) {
909 /* No transformation was made, we've reached a fixpoint */
910 return current == message ? message : format_buffer;
911 }
912
913 /* Check if the message changed */
914 if (strcmp(current, format_buffer) == 0) {
915 /* No actual change, return the current version */
916 return format_buffer;
917 }
918
919 /* Message changed, prepare for next iteration */
920 if (current == message) {
921 /* First iteration, use format_buffer as input for next iteration */
922 current = format_buffer;
923 } else {
924 /* Subsequent iterations, swap buffers */
925 asciichat_error_t strcpy_result = SAFE_STRCPY(work_buffer, sizeof(work_buffer), format_buffer);
926 if (strcpy_result != ASCIICHAT_OK) {
927 log_error("Failed to copy format buffer: %s", asciichat_error_format(NULL, strcpy_result));
928 return format_buffer;
929 }
930 current = work_buffer;
931 }
932 }
933
934 /* Max iterations reached, return last formatted version */
935 return format_buffer;
936}
const char * asciichat_error_format(const char *message, asciichat_error_t code)
#define SAFE_STRCPY(dest, dest_size, src)
Definition common.h:486
int log_named_format_message(const char *message, char *output, size_t output_size)
Format hex addresses in a message as named object descriptions.
Definition log/named.c:572
#define NAMED_FORMAT_BUFFER_SIZE
Definition log/named.c:37

References asciichat_error_format(), ASCIICHAT_OK, log_error, log_named_format_message(), NAMED_FORMAT_BUFFER_SIZE, and SAFE_STRCPY.

Referenced by log_template_apply().

◆ log_net_message()

asciichat_error_t log_net_message ( struct acip_transport * transport,
log_level_t level,
remote_log_direction_t direction,
const char * file,
int line,
const char * func,
const char * fmt,
... )

#include <include/ascii-chat/log/log.h>

Log a message to all destinations (network, file, and terminal).

Parameters
transportDestination connection transport
levelLog severity used for remote and local logging
directionRemote log direction metadata
fileSource file name (or NULL to omit)
lineSource line number (or 0 to omit)
funcFunction name (or NULL to omit)
fmtFormat string (printf-style)
...Format arguments
Returns
ASCIICHAT_OK on success, error code otherwise

Definition at line 1810 of file log.c.

1811 {
1812 if (!fmt) {
1813 return SET_ERRNO(ERROR_INVALID_PARAM, "Format string is NULL");
1814 }
1815
1816 va_list args_copy;
1817 va_start(args_copy, fmt);
1819 char *formatted = format_message(fmt, args_copy);
1820 va_end(args_copy);
1821
1822 if (!formatted) {
1824 asciichat_error_t current_error = errno_peek_since(format_scope, &current) ? current.code : ASCIICHAT_OK;
1825 if (current_error == ASCIICHAT_OK) {
1826 current_error = SET_ERRNO(ERROR_MEMORY, "Failed to format network log message");
1827 }
1828 return current_error;
1829 }
1830
1831 asciichat_error_t send_result = ASCIICHAT_OK;
1832 if (!transport) {
1833 send_result = SET_ERRNO(ERROR_INVALID_PARAM, "Missing log transport");
1834 log_msg(LOG_WARN, file, line, func, "Skipping remote log message: transport unavailable");
1835 } else {
1836 send_result = acip_send_remote_log(transport, level, direction, formatted);
1837 if (send_result != ASCIICHAT_OK) {
1838 log_msg(LOG_WARN, file, line, func, "Failed to send remote log message: %s", asciichat_error_string(send_result));
1839 }
1840 }
1841
1842 const char *direction_label = log_network_direction_label(direction);
1843 log_msg(level, file, line, func, "[NET %s] %s", direction_label, formatted);
1844
1845 SAFE_FREE(formatted);
1846 return send_result;
1847}
bool errno_peek_since(asciichat_errno_scope_t scope, asciichat_error_context_t *out)
asciichat_errno_scope_t errno_checkpoint(void)
@ ERROR_MEMORY
Definition error_codes.h:51
char * format_message(const char *format, va_list args)
Format a message using va_list.
Definition log.c:278
asciichat_error_t acip_send_remote_log(acip_transport_t *transport, uint8_t log_level, uint8_t direction, const char *message)
Send remote log packet.
Definition send.c:459
When code needs to work with asciichat errno snapshot, it keeps total, copied here instead of scatter...

References acip_send_remote_log(), ASCIICHAT_OK, asciichat_error_context_t::code, errno_checkpoint(), errno_peek_since(), ERROR_INVALID_PARAM, ERROR_MEMORY, format_message(), log_msg(), LOG_WARN, SAFE_FREE, and SET_ERRNO.

Referenced by threaded_send_client_join_packet().

◆ log_plain_msg()

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

#include <include/ascii-chat/log/log.h>

Plain logging without timestamps or levels.

Parameters
fmtFormat string (printf-style)
...Format arguments

Writes to both log file and stderr without timestamps or log levels.

Definition at line 1509 of file log.c.

1509 {
1510 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
1511 return;
1512 }
1513
1514 char log_buffer[LOG_MSG_BUFFER_SIZE];
1515 va_list args;
1516 va_start(args, fmt);
1517 int msg_len = safe_vsnprintf(log_buffer, sizeof(log_buffer), fmt, args);
1518 va_end(args);
1519
1520 if (msg_len <= 0) {
1521 return;
1522 }
1523
1524 // Truncate at whole line boundaries to avoid UTF-8 issues
1525 msg_len = truncate_at_whole_line(log_buffer, msg_len, sizeof(log_buffer));
1526
1527 // Validate UTF-8 in formatted message
1528 validate_log_message_utf8(log_buffer, "plain text log");
1529
1530 session_log_buffer_append(g_history, log_buffer);
1531
1532 // Write to mmap if active
1533 if (log_mmap_is_active()) {
1534 log_mmap_write(LOG_INFO, NULL, 0, NULL, "%s", log_buffer);
1535 } else {
1536 // Write to file with headers (atomic write syscall)
1537 int file_fd = atomic_load_u64(&g_log.file);
1538 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
1539 // Add header with timestamp and log level to file output
1540 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
1541 uint64_t time_ns = time_get_realtime_ns();
1543
1544 char header_buffer[LOG_HEADER_BUFFER_SIZE];
1545 int header_len = format_log_header(header_buffer, sizeof(header_buffer), LOG_INFO, time_buf, "lib/log/logging.c",
1546 0, "log_plain_msg", false, time_ns);
1547
1548 if (header_len > 0) {
1549 write_to_log_file_atomic(header_buffer, header_len, NULL);
1550 }
1551 write_to_log_file_atomic(log_buffer, msg_len, NULL);
1552 write_to_log_file_atomic("\n", 1, NULL);
1553 }
1554 }
1555
1556 // Preserve diagnostics in the log file during shutdown without repainting the terminal.
1558 return;
1559
1560 // Terminal output (atomic state checks)
1561 if (!atomic_load_u64(&g_log.terminal_output_enabled)) {
1562 return;
1563 }
1564 if (atomic_load_u64(&g_log.terminal_locked)) {
1565 uint64_t owner = atomic_load_u64(&g_log.terminal_owner_thread);
1566 if (owner != (uint64_t)thread_self()) {
1567 return;
1568 }
1569 }
1570
1571 // Check if JSON format is enabled
1572 int json_fd = atomic_load_int(&g_log.json_file);
1573 bool json_format_enabled = (json_fd >= 0);
1574
1575 if (json_format_enabled) {
1576 // Output JSON for plain messages too
1577 uint64_t time_ns = time_get_realtime_ns();
1578 int console_fd = terminal_choose_log_fd(LOG_INFO);
1579 log_json_write(console_fd, LOG_INFO, time_ns, __FILE__, __LINE__, "log_plain_msg", log_buffer);
1580 } else {
1581 // Choose output stream using unified routing logic (LOG_INFO level)
1583 FILE *output_stream = (fd == STDERR_FILENO) ? stderr : stdout;
1584
1585 // Apply colorization for TTY output
1586 if (terminal_should_color_output(fd, options_terminal_settings())) {
1587 const char *colorized_msg = colorize_log_message(log_buffer);
1588 log_terminal_printf(output_stream == stdout ? STDOUT_FILENO : STDERR_FILENO, "%s\n", colorized_msg);
1589 } else {
1590 log_terminal_printf(output_stream == stdout ? STDOUT_FILENO : STDERR_FILENO, "%s\n", log_buffer);
1591 }
1592 (void)fflush(output_stream);
1593 }
1594}
bool shutdown_is_requested(void)
Check if shutdown has been requested.
Definition common.c:86

References args, colorize_log_message(), get_current_time_formatted(), LOG_HEADER_BUFFER_SIZE, LOG_INFO, log_json_write(), log_mmap_is_active(), log_mmap_write(), LOG_MSG_BUFFER_SIZE, LOG_TIMESTAMP_BUFFER_SIZE, options_terminal_settings, safe_vsnprintf(), session_log_buffer_append(), shutdown_is_requested(), and terminal_choose_log_fd().

◆ log_plain_stderr_msg()

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

#include <include/ascii-chat/log/log.h>

Plain logging to stderr with newline.

Parameters
fmtFormat string (printf-style)
...Format arguments

Writes to both log file and stderr without timestamps or log levels, with trailing newline.

Definition at line 1658 of file log.c.

1658 {
1659 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
1660 return;
1661 }
1662 if (shutdown_is_requested()) {
1663 return;
1664 }
1665
1666 va_list args;
1667 va_start(args, fmt);
1668 log_plain_stderr_internal_atomic(fmt, args, true);
1669 va_end(args);
1670}

References args, and shutdown_is_requested().

◆ log_plain_stderr_nonewline_msg()

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

#include <include/ascii-chat/log/log.h>

Plain logging to stderr without trailing newline.

Parameters
fmtFormat string (printf-style)
...Format arguments

Writes to both log file and stderr without timestamps, log levels, or trailing newline. Useful for interactive prompts where the user's response should be on the same line.

Definition at line 1672 of file log.c.

1672 {
1673 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
1674 return;
1675 }
1676 if (shutdown_is_requested()) {
1677 return;
1678 }
1679
1680 va_list args;
1681 va_start(args, fmt);
1682 log_plain_stderr_internal_atomic(fmt, args, false);
1683 va_end(args);
1684}

References args, and shutdown_is_requested().

◆ log_plain_stdout_msg()

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

#include <include/ascii-chat/log/log.h>

Plain logging to stdout with newline.

Parameters
fmtFormat string (printf-style)
...Format arguments

Writes to both log file and stdout without timestamps or log levels, with trailing newline. Used for informational output like device listings.

Definition at line 1751 of file log.c.

1751 {
1752 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
1753 return;
1754 }
1755 if (shutdown_is_requested()) {
1756 return;
1757 }
1758
1759 va_list args;
1760 va_start(args, fmt);
1761 log_plain_stdout_internal_atomic(fmt, args, true);
1762 va_end(args);
1763}

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 )

#include <include/ascii-chat/log/log.h>

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

Converts a plain text log line (from log file) into a colored version matching the format used for terminal output. Applies colors to:

  • Timestamp and level based on log level
  • Thread ID in grey
  • File path in cyan
  • Line number in magenta
  • Function name in orange/DEV color
  • Message body colorized appropriately

Expected plain format (debug mode): [TIMESTAMP] [LEVEL] [tid:THREAD_ID] FILE:LINE in FUNC(): MESSAGE

Parameters
plain_linePlain text log line (from log file)
colored_bufOutput buffer for colored version (must be large enough)
buf_sizeSize of colored_buf
Returns
Length of colored string, 0 on error or invalid format
Note
Uses static buffer internally, result is reused across calls

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 2085 of file log.c.

2085 {
2086 if (!plain_line || !colored_buf || buf_size < 128) {
2087 return 0;
2088 }
2089
2090 static char work_buffer[LOG_MSG_BUFFER_SIZE + 1024];
2091
2092 // Parse format FIRST, regardless of color availability
2093 // Parse format: [TIMESTAMP] [LEVEL] [tid:THREAD_ID] FILE:LINE in FUNC(): MESSAGE
2094 const char *p = plain_line;
2095
2096 // Extract timestamp [TIMESTAMP]
2097 if (*p != '[') {
2098 return 0; // Invalid format
2099 }
2100 p++;
2101 const char *timestamp_start = p;
2102
2103 // Find the closing ] for timestamp - look for pattern that ends with proper timestamp format
2104 // Valid timestamp: HH:MM:SS.UUUUUU (time with microseconds, no date)
2105 while (*p && *p != ']') {
2106 p++;
2107 }
2108 if (*p != ']') {
2109 return 0; // Malformed - no closing bracket
2110 }
2111
2112 size_t timestamp_len = p - timestamp_start;
2113 char timestamp[LOG_PARSE_TIMESTAMP_BUFFER_SIZE];
2114 if (timestamp_len >= sizeof(timestamp) || timestamp_len == 0) {
2115 return 0; // Invalid timestamp - empty or too long
2116 }
2117 SAFE_STRNCPY(timestamp, timestamp_start, timestamp_len);
2118 timestamp[timestamp_len] = '\0';
2119 p++; // Skip ]
2120
2121 // Skip whitespace after timestamp
2122 while (*p && *p == ' ') {
2123 p++;
2124 }
2125
2126 // Extract level [LEVEL]
2127 if (*p != '[') {
2128 return 0; // Missing opening bracket for level
2129 }
2130 p++;
2131 const char *level_start = p;
2132 while (*p && *p != ']') {
2133 p++;
2134 }
2135 if (*p != ']') {
2136 return 0; // Missing closing bracket for level
2137 }
2138 size_t level_len = p - level_start;
2139 char level_str[LOG_LEVEL_BUFFER_SIZE];
2140 if (level_len >= sizeof(level_str) || level_len == 0) {
2141 return 0; // Level string too long or empty
2142 }
2143 memcpy(level_str, level_start, level_len);
2144 level_str[level_len] = '\0';
2145
2146 // Determine log level for color selection
2147 log_level_t level = LOG_INFO; // Default
2148 if (strstr(level_str, "DEV") || strstr(level_str, "DEBUG")) {
2149 level = LOG_DEBUG;
2150 } else if (strstr(level_str, "INFO")) {
2151 level = LOG_INFO;
2152 } else if (strstr(level_str, "WARN")) {
2153 level = LOG_WARN;
2154 } else if (strstr(level_str, "ERROR")) {
2155 level = LOG_ERROR;
2156 } else if (strstr(level_str, "FATAL")) {
2157 level = LOG_FATAL;
2158 }
2159
2160 p++; // Skip ]
2161
2162 // Skip whitespace after level
2163 while (*p && *p == ' ') {
2164 p++;
2165 }
2166
2167 // Extract thread ID [tid:THREAD_ID] - optional field
2168 uint64_t tid = 0;
2169 if (*p == '[' && strncmp(p, "[tid:", 5) == 0) {
2170 p += 5;
2171 char *tid_end = NULL;
2172 tid = strtoull(p, &tid_end, 10);
2173 if (!tid_end || *tid_end != ']') {
2174 // tid parsing failed, but continue anyway (might still recover)
2175 // Try to find the closing bracket
2176 while (*p && *p != ']') {
2177 p++;
2178 }
2179 if (*p == ']') {
2180 p++;
2181 }
2182 } else {
2183 p = tid_end + 1; // Skip past the ]
2184 }
2185 // Skip whitespace after tid
2186 while (*p && *p == ' ') {
2187 p++;
2188 }
2189 }
2190 // tid is optional - continue parsing if not present
2191
2192 // Extract file path (everything up to :LINE)
2193 const char *file_start = p;
2194 while (*p && *p != ':') {
2195 p++;
2196 }
2197 if (*p != ':') {
2198 return 0; // Malformed
2199 }
2200 size_t file_len = p - file_start;
2202 if (file_len >= sizeof(file_path)) {
2203 return 0;
2204 }
2205 memcpy(file_path, file_start, file_len);
2206 file_path[file_len] = '\0';
2207 p++; // Skip :
2208
2209 // Extract line number (digits only)
2210 int line_num = 0;
2211 const char *line_start = p;
2212 while (*p && isdigit((unsigned char)*p)) {
2213 p++;
2214 }
2215 if (p == line_start) {
2216 return 0; // No line number
2217 }
2218 line_num = (int)strtol(line_start, NULL, 10);
2219
2220 // Skip whitespace and find "in" keyword (be very lenient)
2221 while (*p && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
2222 p++;
2223 }
2224
2225 // Try to find "in " - if not found, might still be valid, just harder to parse
2226 if (strncmp(p, "in ", 3) == 0) {
2227 p += 3; // Skip "in "
2228 } else if (*p == 'i' && *(p + 1) == 'n' && (*(p + 2) == ' ' || *(p + 2) == '\t')) {
2229 // Allow tab after "in"
2230 p += 2; // Skip "in"
2231 while (*p && (*p == ' ' || *p == '\t')) {
2232 p++;
2233 }
2234 } else {
2235 // Missing "in" keyword - this is a format error
2236 return 0;
2237 }
2238
2239 // Extract function name (everything up to "()")
2240 const char *func_start = p;
2241 while (*p && *p != '(') {
2242 p++;
2243 }
2244 if (*p != '(' || func_start == p) {
2245 return 0; // Missing function name or parentheses
2246 }
2247 size_t func_len = p - func_start;
2248
2249 // Trim trailing whitespace from function name
2250 while (func_len > 0 && (func_start[func_len - 1] == ' ' || func_start[func_len - 1] == '\t')) {
2251 func_len--;
2252 }
2253
2254 char func_name[LOG_FUNC_NAME_BUFFER_SIZE];
2255 if (func_len >= sizeof(func_name)) {
2256 func_len = sizeof(func_name) - 1;
2257 }
2258 if (func_len > 0) {
2259 memcpy(func_name, func_start, func_len);
2260 }
2261 func_name[func_len] = '\0';
2262
2263 // Skip "(" and ")" - be lenient about what's between them
2264 if (*p == '(') {
2265 p++;
2266 while (*p && *p != ')') {
2267 p++;
2268 }
2269 if (*p == ')') {
2270 p++;
2271 }
2272 }
2273
2274 // Skip whitespace and optional colon(s) and other separators
2275 while (*p && (*p == ' ' || *p == ':' || *p == '\t')) {
2276 p++;
2277 }
2278
2279 // Remaining is the message
2280 const char *message = p;
2281
2282 // Format is valid, get colors from logging system
2283 const char **colors = log_get_color_array();
2284 if (!colors) {
2285 // No colors available, return plain text
2286 static bool warned_once = false;
2287 if (!warned_once) {
2288 log_debug("Colors not initialized - returning plain text");
2289 warned_once = true;
2290 }
2291 size_t len = strlen(plain_line);
2292 if (len >= buf_size) {
2293 return 0;
2294 }
2295 SAFE_STRNCPY(colored_buf, plain_line, buf_size - 1);
2296 colored_buf[buf_size - 1] = '\0';
2297 return len;
2298 }
2299
2300 // Build colored output
2301 const char *level_color = colors[level];
2302 const char *reset = colors[LOG_COLOR_RESET];
2303 const char *file_color = colors[1]; // DEBUG/Cyan
2304 const char *line_color = colors[6]; // GREY (matching tid)
2305 const char *func_color = colors[0]; // DEV/Orange
2306 const char *tid_color = colors[6]; // GREY
2307
2308 int len = safe_snprintf(work_buffer, sizeof(work_buffer),
2309 "[%s%s%s] [%s%s%s] [tid:%s%llu%s] %s%s%s:%s%d%s in %s%s%s(): %s", level_color, timestamp,
2310 reset, level_color, level_str, reset, tid_color, (unsigned long long)tid, reset, file_color,
2311 file_path, reset, line_color, line_num, reset, func_color, func_name, reset, message);
2312
2313 if (len <= 0 || len >= (int)sizeof(work_buffer)) {
2314 return 0;
2315 }
2316
2317 // Colorize the message part
2318 const char *colorized_msg = colorize_log_message(message);
2319 len = safe_snprintf(work_buffer, sizeof(work_buffer),
2320 "[%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,
2321 level_color, level_str, reset, tid_color, (unsigned long long)tid, reset, file_color, file_path,
2322 reset, line_color, line_num, reset, func_color, func_name, reset, colorized_msg);
2323
2324 if (len <= 0 || len >= (int)sizeof(work_buffer) || len >= (int)buf_size) {
2325 return 0;
2326 }
2327
2328 SAFE_STRNCPY(colored_buf, work_buffer, buf_size - 1);
2329 colored_buf[buf_size - 1] = '\0';
2330 return (size_t)len;
2331}
#define LOG_LEVEL_BUFFER_SIZE
Definition log.c:6
#define LOG_FILE_PATH_BUFFER_SIZE
Definition log.c:7
#define LOG_PARSE_TIMESTAMP_BUFFER_SIZE
Definition log.c:5
#define LOG_FUNC_NAME_BUFFER_SIZE
Definition log.c:8
char file_path[4096]
Definition mmap.c:42

References colorize_log_message(), colors, file_path, LOG_COLOR_RESET, LOG_DEBUG, log_debug, LOG_ERROR, LOG_FATAL, LOG_FILE_PATH_BUFFER_SIZE, LOG_FUNC_NAME_BUFFER_SIZE, log_get_color_array(), LOG_INFO, LOG_LEVEL_BUFFER_SIZE, LOG_MSG_BUFFER_SIZE, LOG_PARSE_TIMESTAMP_BUFFER_SIZE, LOG_WARN, safe_snprintf(), and SAFE_STRNCPY.

◆ log_redetect_terminal_capabilities()

void log_redetect_terminal_capabilities ( void )

#include <include/ascii-chat/log/log.h>

Re-detect terminal capabilities after logging is initialized.

Useful when terminal capabilities change or need to be refreshed.

Definition at line 1880 of file log.c.

1880 {
1881
1882 // Guard against recursion
1883 if (g_log.terminal_caps_detecting) {
1884 return;
1885 }
1886
1887 // Detect if not initialized, or if we're using defaults (not reliably detected)
1888 // This ensures we get proper detection after logging is ready, replacing any defaults
1889 // Once we have reliable detection, never re-detect to keep colors consistent
1890 if (!g_log.terminal_caps_initialized || !g_log.terminal_caps.detection_reliable) {
1891 g_log.terminal_caps_detecting = true;
1892 detect_terminal_capabilities_into(options_terminal_settings(), &g_log.terminal_caps);
1893 g_log.terminal_caps_detecting = false;
1894 g_log.terminal_caps_initialized = true;
1895
1896 // Now log the capabilities AFTER colors are set, so this log uses the correct colors
1897 log_debug("Terminal capabilities: color_level=%d, capabilities=0x%x, utf8=%s, fps=%d",
1898 g_log.terminal_caps.color_level, g_log.terminal_caps.capabilities,
1899 g_log.terminal_caps.utf8_support ? "yes" : "no", g_log.terminal_caps.desired_fps);
1900
1901 // Now that we've detected once with reliable results, keep these colors consistent for all future logs
1902 } else {
1903 }
1904 // Once initialized with reliable detection, never re-detect to keep colors consistent
1905}

References log_debug, and options_terminal_settings.

Referenced by log_init(), log_system_init(), and main().

◆ log_set_color_scheme()

void log_set_color_scheme ( const color_scheme_t * scheme)

#include <include/ascii-chat/log/log.h>

Set the color scheme for logging output.

Parameters
schemeColor scheme to apply (must not be NULL)

Updates the compiled ANSI color codes based on the new color scheme. Must be called after colors_init() to have an effect.

Definition at line 1969 of file log.c.

1969 {
1970 colorscheme_apply(scheme);
1971}
void colorscheme_apply(const color_scheme_t *scheme)

References colorscheme_apply().

Referenced by main().

◆ log_set_flush_delay()

void log_set_flush_delay ( unsigned int delay_ms)

#include <include/ascii-chat/log/log.h>

Set the delay between flushing buffered log entries.

When terminal output is re-enabled after an interactive prompt, buffered log entries are flushed to the terminal. This setting adds a delay between each entry for a visual animation effect.

Parameters
delay_msDelay in milliseconds between each log entry (0 = no delay)

Definition at line 849 of file log.c.

849 {
850 atomic_store_u64(&g_log.flush_delay_ms, delay_ms);
851}

◆ log_set_force_stderr()

void log_set_force_stderr ( bool enabled)

#include <include/ascii-chat/log/log.h>

Force all terminal log output to stderr.

Parameters
enabledtrue to force all logs to stderr, false for normal routing

When enabled, all log messages (including INFO, DEBUG, DEV) go to stderr instead of the default behavior where INFO/DEBUG/DEV go to stdout and WARN/ERROR/FATAL go to stderr. This is used by the client to keep stdout clean for ASCII art output.

Definition at line 767 of file log.c.

767 {
768 atomic_store_u64(&g_log.force_stderr, enabled);
769}

References enabled.

Referenced by main(), and session_display_create().

◆ log_set_format()

asciichat_error_t log_set_format ( const char * format_str,
bool console_only )

#include <include/ascii-chat/log/log.h>

Set a custom log format string.

Parameters
format_strFormat string with specifiers like time(H:M:S), level, message, etc. Pass NULL to use default format. Empty string "" also uses default.
console_onlyIf true, apply format only to console output (file logs use default)
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 790 of file log.c.

790 {
791 /* Free old format if it exists */
792 if (g_log.format) {
793 log_template_free(g_log.format);
794 g_log.format = NULL;
795 }
796 if (g_log.format_console_only) {
797 log_template_free(g_log.format_console_only);
798 g_log.format_console_only = NULL;
799 }
800
801 /* Use default format if NULL or empty string */
802 const char *format_to_use = (format_str && format_str[0] != '\0') ? format_str : OPT_LOG_TEMPLATE_DEFAULT;
803 bool is_custom = (format_str && format_str[0] != '\0');
804
805 /* Parse the format string (always parse, never skip) */
806 log_template_t *parsed_format = log_template_parse(format_to_use, false);
807
808 if (!parsed_format) {
809 log_error("Failed to parse log format: %s", format_to_use);
810 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid log format string");
811 }
812
813 /* If console_only is true, we also need the default format for file output */
814 if (console_only && is_custom) {
816
817 if (!default_format) {
818 log_template_free(parsed_format);
819 log_error("Failed to parse default log format");
820 return SET_ERRNO(ERROR_INVALID_STATE, "Failed to parse default format");
821 }
822 g_log.format = default_format;
823 g_log.format_console_only = parsed_format;
824 } else {
825 g_log.format = parsed_format;
826 g_log.format_console_only = NULL;
827 }
828
829 atomic_store_u64(&g_log.has_custom_format, is_custom);
830 return ASCIICHAT_OK;
831}
log_template_t * log_template_parse(const char *format_str, bool console_only)
Parse a format string into compiled format structure.
Definition log/format.c:328
#define OPT_LOG_TEMPLATE_DEFAULT
Default log template string (selected based on build mode).
Definition options.h:391
Compiled log format ready for use in log_template_apply().
Definition log/format.h:72

References ASCIICHAT_OK, ERROR_INVALID_STATE, log_error, log_template_free(), log_template_parse(), OPT_LOG_TEMPLATE_DEFAULT, and SET_ERRNO.

Referenced by client_init(), log_init(), main(), and mirror_init().

◆ log_set_level()

void log_set_level ( log_level_t level)

#include <include/ascii-chat/log/log.h>

Set the minimum log level.

Parameters
levelMinimum log level to output

Definition at line 737 of file log.c.

737 {
738 atomic_store_u64(&g_log.level, (int)level);
739 atomic_store_bool(&g_log.level_manually_set, true);
740}

Referenced by initial_options(), and main().

◆ log_set_session_log_buffer()

void log_set_session_log_buffer ( session_log_buffer_t * buf)

#include <include/ascii-chat/log/log.h>

Register a session log buffer with the logger.

Once registered, all log messages are appended to the buffer in addition to being written to files/terminal. Used by splash and status screens.

Parameters
bufBuffer instance to register (can be NULL to unregister)

Definition at line 2048 of file log.c.

2048 {
2049 atomic_ptr_store(&g_log.session_log_buffer, (void *)buf);
2050}

Referenced by splash_log_init(), ui_mdns_log_init(), ui_session_capture_logs(), and ui_status_log_init().

◆ log_set_terminal_output()

void log_set_terminal_output ( bool enabled)

#include <include/ascii-chat/log/log.h>

Control stderr output to terminal.

Parameters
enabledtrue to enable terminal output, false to disable

Definition at line 746 of file log.c.

746 {
747 // Respect --quiet flag: if quiet is set, never enable terminal output
748 // But allow disabling even if options are unavailable (for shutdown cleanup)
749
750 // Only check quiet flag when ENABLING output to avoid deadlock with
751 // concurrent logging from other threads (options_get() can deadlock when
752 // called while other threads are actively logging due to RCU synchronization)
753 if (enabled) {
754 const options_t *opts = options_get();
755 if (opts && opts->quiet) {
756 return; // Silently ignore attempts to enable terminal output when --quiet is set
757 }
758 }
759
760 atomic_store_u64(&g_log.terminal_output_enabled, enabled);
761}
struct options_state options_t
Immutable, RCU-published snapshot of the application's runtime configuration.
const options_t * options_get(void)
Get pointer to current options struct (lock-free, thread-safe).
Definition rcu.c:511
bool quiet
Quiet mode (suppress logs).
Definition options.h:1147

References enabled, options_get(), and options_state::quiet.

Referenced by asciichat_shared_init(), discovery_main(), display_disable_logging_for_first_frame(), main(), mcp_server_main(), mirror_main(), options_init(), and session_render_loop().

◆ log_shutdown_begin()

void log_shutdown_begin ( void )

#include <include/ascii-chat/log/log.h>

Begin shutdown phase - disable console logging but keep file logging.

Call this before logging shutdown messages. Disables console output but keeps file logging so messages are recorded for debugging.

Useful when you want final messages (like "no servers found") to go to the log file only, not to stdout where it might interfere with output.

Definition at line 2007 of file log.c.

2007 {
2008 if (g_log.shutdown_in_progress) {
2009 return; /* Already in shutdown phase */
2010 }
2011
2012 /* Save current terminal output state and disable console output */
2013 g_log.shutdown_saved_terminal_output = atomic_load_u64(&g_log.terminal_output_enabled);
2014 atomic_store_bool(&g_log.terminal_output_enabled, false);
2015 g_log.shutdown_in_progress = true;
2016}

Referenced by asciichat_shared_destroy().

◆ log_shutdown_end()

void log_shutdown_end ( void )

#include <include/ascii-chat/log/log.h>

End shutdown phase - restore previous logging settings.

Call after shutdown messages have been logged to restore console output.

Definition at line 2018 of file log.c.

2018 {
2019 if (!g_log.shutdown_in_progress) {
2020 return; /* Not in shutdown phase, skip terminal state restoration */
2021 }
2022
2023 /* Restore previous terminal output state */
2024 atomic_store_u64(&g_log.terminal_output_enabled, g_log.shutdown_saved_terminal_output);
2025 g_log.shutdown_in_progress = false;
2026}

Referenced by asciichat_shared_destroy().

◆ log_system_destroy()

void log_system_destroy ( void )

#include <include/ascii-chat/log/log.h>

Shutdown the logging system internal state (system-level cleanup).

Restores terminal state and clears registered session log buffer. Must be called once at program shutdown AFTER log_destroy(). Safe to call multiple times (idempotent).

Clears registered session log buffer. Color cleanup is handled separately in asciichat_shared_destroy() after memory reporting. Must be called once at program shutdown AFTER log_destroy(). Safe to call multiple times (idempotent).

Definition at line 729 of file log.c.

729 {
730 // Unregister session log buffer if registered
734 g_history = NULL;
735}
void log_clear_session_log_buffer(void)
Unregister the session log buffer.
Definition log.c:2057
void log_search_destroy(void)
Clean up interactive grep subsystem.
Definition search.c:203
void session_log_buffer_destroy(session_log_buffer_t *buf)
Cleanup session log buffer instance.

References log_clear_session_log_buffer(), log_search_destroy(), and session_log_buffer_destroy().

Referenced by asciichat_shared_destroy().

◆ log_system_init()

void log_system_init ( void )

#include <include/ascii-chat/log/log.h>

Initialize the logging system internal state (system-level init).

Initializes g_log struct, detects terminal capabilities, and compiles colors. Must be called once at program startup BEFORE log_init(). Safe to call multiple times (idempotent).

Definition at line 712 of file log.c.

712 {
713
714 // Detect terminal capabilities if not already done
716
717 // Initialize color scheme based on terminal capabilities
719}
void log_init_colors(void)
Initialize logging color system with current terminal capabilities.
Definition log.c:1963

References log_init_colors(), and log_redetect_terminal_capabilities().

Referenced by asciichat_shared_init().

◆ log_template_apply()

int log_template_apply ( const log_template_t * format,
char * buf,
size_t buf_size,
log_level_t level,
const char * timestamp,
const char * file,
int line,
const char * func,
uint64_t tid,
const char * message,
bool use_colors,
uint64_t time_nanoseconds )

#include <include/ascii-chat/log/format.h>

Apply format to a log entry and write result to buffer.

Parameters
formatCompiled format (from log_template_parse)
bufOutput buffer
buf_sizeOutput buffer size
levelLog level
timestampPre-formatted timestamp string
fileSource file name (or NULL)
lineSource line number (or 0)
funcFunction name (or NULL)
tidThread ID
messageLog message text
use_colorsIf true, apply ANSI color codes
time_nanosecondsRaw wall-clock time in nanoseconds (for microseconds and nanoseconds)
Returns
Number of characters written (excluding null terminator), or -1 on error

Renders the compiled format using provided log entry values. Evaluates each specifier and writes results to output buffer.

Example:

log_template_t *fmt = log_template_parse("[%time(%H:%M:%S)] [%level] %microseconds %message", false);
char output[512];
uint64_t now = time_get_realtime_ns();
int len = log_template_apply(fmt, output, sizeof(output),
LOG_INFO, "14:30:45", "test.c", 42, "main",
1234, "Test message", false, now);
int log_template_apply(const log_template_t *format, char *buf, size_t buf_size, log_level_t level, const char *timestamp, const char *file, int line, const char *func, uint64_t tid, const char *message, bool use_colors, uint64_t time_nanoseconds)
Apply format to a log entry and write result to buffer.
Definition log/format.c:498

Definition at line 498 of file log/format.c.

500 {
501 /* Check all critical pointers - format could be freed by another thread (TOCTOU race) */
502 if (!format || !buf || buf_size == 0) {
503 return -1;
504 }
505
506 int total_written = 0;
507 char *p = buf;
508 size_t remaining = buf_size - 1; /* Reserve space for null terminator */
509
510 (void)timestamp; /* May be used in future; for now, LOG_FORMAT_TIME uses custom formatting */
511
512 /* Safety check: verify format->specs is valid before dereferencing
513 * This protects against use-after-free if format is freed by another thread (TOCTOU race).
514 * Between our initial NULL check and here, the main thread may have freed this memory. */
515 if (!format || !format->specs || format->spec_count == 0) {
516 return -1;
517 }
518
519 for (size_t i = 0; i < format->spec_count; i++) {
520 /* Re-check specs pointer in loop to catch use-after-free during iteration */
521 if (!format->specs || i >= format->spec_count) {
522 break; /* Format was freed, stop processing */
523 }
524 const log_format_spec_t *spec = &format->specs[i];
525
526 /* Additional safety check: if spec is null or invalid, stop processing */
527 if (!spec) {
528 break;
529 }
530
531 int written = 0;
532
533 switch (spec->type) {
535 written = safe_snprintf(p, remaining + 1, "%s", spec->literal ? spec->literal : "");
536 break;
537
538 case LOG_FORMAT_TIME:
539 /* Format time using custom time formatter */
540 if (spec->literal) {
541 written = time_format_now(spec->literal, p, remaining + 1);
542 if (written <= 0) {
543 log_debug("time_format_now failed for format: %s", spec->literal);
544 written = 0;
545 }
546 }
547 break;
548
549 case LOG_FORMAT_LEVEL:
550 /* Log level as string (DEV, DEBUG, INFO, WARN, ERROR, FATAL) */
551 written = safe_snprintf(p, remaining + 1, "%s", get_level_string(level));
552 break;
553
555 /* Log level padded to exactly 5 characters (prevents truncation to [DEBU]/[ERRO]) */
556 written = safe_snprintf(p, remaining + 1, "%s", get_level_string_padded(level));
557 break;
558
559 case LOG_FORMAT_FILE:
560 if (file) {
561 written = safe_snprintf(p, remaining + 1, "%s", file);
562 }
563 break;
564
566 if (file) {
567 const char *rel_file = extract_project_relative_path(file, ASCIICHAT_OWNED_SOURCE_DIRS);
568 written = safe_snprintf(p, remaining + 1, "%s", rel_file);
569 }
570 break;
571
572 case LOG_FORMAT_LINE:
573 if (line > 0) {
574 written = safe_snprintf(p, remaining + 1, "%d", line);
575 }
576 break;
577
578 case LOG_FORMAT_FUNC:
579 if (func) {
580 written = safe_snprintf(p, remaining + 1, "%s", func);
581 }
582 break;
583
584 case LOG_FORMAT_TID:
585 written = safe_snprintf(p, remaining + 1, "%llu", (unsigned long long)tid);
586 break;
587
588 case LOG_FORMAT_TNAME: {
589 // Registry thread keys use the same numeric identity as log records.
590 uintptr_t key = (uintptr_t)tid;
591 named_metadata_t metadata;
592 if (named_metadata_copy(key, &metadata)) {
593 written = safe_snprintf(p, remaining + 1, "%s", metadata.name);
594 } else {
595 written = safe_snprintf(p, remaining + 1, "%llu", (unsigned long long)tid);
596 }
597 break;
598 }
599
601 /* Extract microseconds from nanoseconds (ns_value % 1_000_000_000 / 1000) */
602 long nanoseconds = (long)(time_nanoseconds % NS_PER_SEC_INT);
603 long microseconds = nanoseconds / 1000;
604 if (microseconds < 0)
605 microseconds = 0;
606 if (microseconds > 999999)
607 microseconds = 999999;
608 written = safe_snprintf(p, remaining + 1, "%06ld", microseconds);
609 break;
610 }
611
613 /* Extract nanoseconds component (ns_value % 1_000_000_000) */
614 long nanoseconds = (long)(time_nanoseconds % NS_PER_SEC_INT);
615 if (nanoseconds < 0)
616 nanoseconds = 0;
617 if (nanoseconds > 999999999)
618 nanoseconds = 999999999;
619 written = safe_snprintf(p, remaining + 1, "%09ld", nanoseconds);
620 break;
621 }
622
624 /* strftime format code (like %H, %M, %S, %A, %B, etc.)
625 * Let strftime handle all the parsing and validation */
627 /* Construct format string with % prefix on stack */
628 char format_str[LOG_FORMAT_FORMAT_BUFFER_SIZE]; /* 1 (%) + 64 (literal) + 1 (null) */
629 format_str[0] = '%';
630 memcpy(format_str + 1, spec->literal, spec->literal_len);
631 format_str[spec->literal_len + 1] = '\0';
632 written = time_format_now(format_str, p, remaining + 1);
633 if (written <= 0) {
634 log_debug("time_format_now failed for format code: %s", format_str);
635 written = 0;
636 }
637 }
638 break;
639 }
640
642 if (message) {
643 /* Format hex addresses as named object descriptions (type/name format) */
644 const char *formatted_msg = log_named_format_or_original(message);
645 written = safe_snprintf(p, remaining + 1, "%s", formatted_msg);
646 }
647 break;
648
650 /* Apply colorize_log_message() for number/unit/hex highlighting, then format named objects */
651 if (message) {
652 const char *formatted_msg = log_named_format_or_original(message);
653 const char *colorized_msg = colorize_log_message_with_color(formatted_msg, use_colors);
654 written = safe_snprintf(p, remaining + 1, "%s", colorized_msg);
655 }
656 break;
657 }
658
660 /* Color code for the log level (placeholder for future color support) */
661 (void)use_colors; /* Suppress unused parameter warning */
662 written = 0;
663 break;
664
665 case LOG_FORMAT_COLOR: {
666 /* Parse and apply %color(LEVEL, content)
667 * literal stores "LEVEL,content" where LEVEL is the color level name (or "*" for current level)
668 * and content is a format string to render and colorize */
669 if (!spec->literal || spec->literal_len == 0) {
670 written = 0;
671 break;
672 }
673
674 /* Find the comma separating LEVEL and content */
675 const char *comma_pos = strchr(spec->literal, ',');
676 if (!comma_pos) {
677 /* Invalid format - no comma found */
678 log_debug("%%color format missing comma in: %s", spec->literal);
679 written = 0;
680 break;
681 }
682
683 /* Extract level name (before comma) */
684 size_t level_len = comma_pos - spec->literal;
685 char level_name[LOG_FORMAT_LEVEL_NAME_BUFFER_SIZE];
686 if (level_len >= sizeof(level_name)) {
687 /* Level name too long */
688 written = 0;
689 break;
690 }
691 memcpy(level_name, spec->literal, level_len);
692 level_name[level_len] = '\0';
693
694 /* Parse level name to log_color_t (pass current level for "*" support) */
695 log_color_t color = parse_color_level(level_name, level);
696
697 /* Extract content (after comma), skip leading whitespace */
698 const char *content_start = comma_pos + 1;
699 while (*content_start == ' ' || *content_start == '\t') {
700 content_start++;
701 }
702
703 /* Render content recursively */
704 char content_buf[LOG_FORMAT_CONTENT_BUFFER_SIZE];
705 int content_len = render_format_content(content_start, content_buf, sizeof(content_buf), level, timestamp, file,
706 line, func, tid, message, use_colors, time_nanoseconds);
707
708 if (content_len < 0 || content_len >= (int)sizeof(content_buf)) {
709 written = 0;
710 break;
711 }
712
713 // The caller already selected colors. Respect that policy for both file
714 // and session logs instead of asking the console again for every field.
715 const char *start = log_level_color_for_output(color, use_colors);
716 const char *reset = log_level_color_for_output(LOG_COLOR_RESET, use_colors);
717 written = safe_snprintf(p, remaining + 1, "%s%s%s", start ? start : "", content_buf, reset ? reset : "");
718 break;
719 }
720
722 /* Platform-aware newline */
723#ifdef _WIN32
724 written = safe_snprintf(p, remaining + 1, "\r\n");
725#else
726 written = safe_snprintf(p, remaining + 1, "\n");
727#endif
728 break;
729
730 default:
731 break;
732 }
733
734 if (written < 0) {
735 /* snprintf error */
736 return -1;
737 }
738
739 if ((size_t)written > remaining) {
740 /* Buffer overflow prevention */
741 log_debug("buffer overflow prevented");
742 return -1;
743 }
744
745 p += written;
746 remaining -= written;
747 total_written += written;
748 }
749
750 *p = '\0';
751 return total_written;
752}
const char * colorize_log_message_with_color(const char *message, bool enabled)
Definition colorize.c:543
const char * log_level_color_for_output(log_color_t color, bool enabled)
Definition log.c:1942
int time_format_now(const char *format_str, char *buf, size_t buf_size)
Format current time using strftime format string.
Definition time.c:722
const char * extract_project_relative_path(const char *file, const char *const *owned_dirs)
Extract a path relative to the source root embedded by CMake.
#define LOG_FORMAT_FORMAT_BUFFER_SIZE
Definition log/format.c:9
#define LOG_FORMAT_CONTENT_BUFFER_SIZE
Definition log/format.c:11
#define LOG_FORMAT_STRFTIME_LITERAL_MAX
Definition log/format.c:8
#define LOG_FORMAT_LEVEL_NAME_BUFFER_SIZE
Definition log/format.c:10
@ LOG_FORMAT_LEVEL
Definition log/format.h:30
@ LOG_FORMAT_COLOR
Definition log/format.h:40
@ LOG_FORMAT_NEWLINE
Definition log/format.h:45
@ LOG_FORMAT_FILE_RELATIVE
Definition log/format.h:33
@ LOG_FORMAT_LINE
Definition log/format.h:34
@ LOG_FORMAT_MICROSECONDS
Definition log/format.h:42
@ LOG_FORMAT_TID
Definition log/format.h:36
@ LOG_FORMAT_COLORED_MESSAGE
Definition log/format.h:41
@ LOG_FORMAT_LITERAL
Definition log/format.h:28
@ LOG_FORMAT_COLORLOG_LEVEL
Definition log/format.h:39
@ LOG_FORMAT_MESSAGE
Definition log/format.h:38
@ LOG_FORMAT_TIME
Definition log/format.h:29
@ LOG_FORMAT_LEVEL_ALIGNED
Definition log/format.h:31
@ LOG_FORMAT_FUNC
Definition log/format.h:35
@ LOG_FORMAT_NANOSECONDS
Definition log/format.h:43
@ LOG_FORMAT_TNAME
Definition log/format.h:37
@ LOG_FORMAT_STRFTIME_CODE
Definition log/format.h:44
@ LOG_FORMAT_FILE
Definition log/format.h:32
const char * get_level_string_padded(log_level_t level)
Get padded level string for consistent alignment.
Definition log.c:183
One literal or conversion token in a compiled log format.
Definition log/format.h:59
log_template_type_t type
Definition log/format.h:60
log_format_spec_t * specs
Definition log/format.h:73
size_t spec_count
Definition log/format.h:74

References colorize_log_message_with_color(), extract_project_relative_path(), get_level_string_padded(), log_format_spec_t::literal, log_format_spec_t::literal_len, LOG_COLOR_RESET, log_debug, LOG_FORMAT_COLOR, LOG_FORMAT_COLORED_MESSAGE, LOG_FORMAT_COLORLOG_LEVEL, LOG_FORMAT_CONTENT_BUFFER_SIZE, LOG_FORMAT_FILE, LOG_FORMAT_FILE_RELATIVE, LOG_FORMAT_FORMAT_BUFFER_SIZE, LOG_FORMAT_FUNC, LOG_FORMAT_LEVEL, LOG_FORMAT_LEVEL_ALIGNED, LOG_FORMAT_LEVEL_NAME_BUFFER_SIZE, LOG_FORMAT_LINE, LOG_FORMAT_LITERAL, LOG_FORMAT_MESSAGE, LOG_FORMAT_MICROSECONDS, LOG_FORMAT_NANOSECONDS, LOG_FORMAT_NEWLINE, LOG_FORMAT_STRFTIME_CODE, LOG_FORMAT_STRFTIME_LITERAL_MAX, LOG_FORMAT_TID, LOG_FORMAT_TIME, LOG_FORMAT_TNAME, log_level_color_for_output(), log_named_format_or_original(), named_metadata_t::name, named_metadata_copy(), NS_PER_SEC_INT, safe_snprintf(), log_template_t::spec_count, log_template_t::specs, time_format_now(), and log_format_spec_t::type.

◆ log_template_free()

void log_template_free ( log_template_t * format)

#include <include/ascii-chat/log/format.h>

Free compiled format structure.

Parameters
formatPointer to format to free (safe to call with NULL)

Definition at line 332 of file log/format.c.

332 {
333 if (!format) {
334 SET_ERRNO(ERROR_INVALID_PARAM, "null format");
335 return;
336 }
337
338 if (format->specs) {
339 for (size_t i = 0; i < format->spec_count; i++) {
340 if (format->specs[i].literal) {
341 free(format->specs[i].literal);
342 format->specs[i].literal = NULL;
343 }
344 }
345 free(format->specs);
346 format->specs = NULL;
347 }
348
349 if (format->original) {
350 free(format->original);
351 format->original = NULL;
352 }
353
354 free(format);
355 format = NULL;
356}
char * original
Definition log/format.h:75

References ERROR_INVALID_PARAM, log_format_spec_t::literal, log_template_t::original, SET_ERRNO, log_template_t::spec_count, and log_template_t::specs.

Referenced by log_destroy(), and log_set_format().

◆ log_template_parse()

log_template_t * log_template_parse ( const char * format_str,
bool console_only )

#include <include/ascii-chat/log/format.h>

Parse a format string into compiled format structure.

Parameters
format_strFormat string (e.g., "[%time(%H:%M:%S)] [%level_aligned] %message")
console_onlyIf true, format applies only to console (not file)
Returns
Compiled format, or NULL on parse error

Parses format string and compiles it into efficient specifier array. Format string must be valid UTF-8. Returns NULL on:

  • Invalid UTF-8 in format string
  • Invalid format specifiers
  • Malformed time(format) syntax
  • Memory allocation failure
Note
Errors are logged with details about what failed

Definition at line 328 of file log/format.c.

328 {
329 return parse_format_string(format_str, console_only);
330}

Referenced by log_set_format().

◆ log_terminal_colored_msg_impl()

void log_terminal_colored_msg_impl ( log_level_t level,
const char * file,
int line,
const char * func,
const char * colored_message,
const char * plain_message )

#include <include/ascii-chat/log/log.h>

Write semantically colored terminal text without persistent logging.

Parameters
levelSeverity used for terminal routing and JSON console output
colored_messageMessage containing semantic ANSI colors
plain_messageEquivalent message without ANSI colors

This is the terminal-only counterpart to log_colored_msg().

Definition at line 1504 of file log.c.

1505 {
1506 log_colored_msg_internal(level, file, line, func, colored_message, plain_message, false);
1507}

◆ log_terminal_msg()

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

#include <include/ascii-chat/log/log.h>

Log a message to terminal only (no file output).

Parameters
levelLog level (LOG_DEV, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL)
fileSource file name (or NULL to omit)
lineSource line number (or 0 to omit)
funcFunction name (or NULL to omit)
fmtFormat string (printf-style)
...Format arguments

Logs to terminal only, skipping file/mmap output. WARN/ERROR/FATAL go to stderr, other levels go to stdout (unless force_stderr is enabled).

Definition at line 1389 of file log.c.

1389 {
1390 // Lock-free: only uses atomic loads, no mutex
1391 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
1392 return;
1393 }
1394
1395 if (level < (log_level_t)atomic_load_u64(&g_log.level)) {
1396 return;
1397 }
1398
1399 // Terminal output only - no file/mmap writing
1400 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
1401 uint64_t time_ns = time_get_realtime_ns();
1403
1404 va_list args;
1405 va_start(args, fmt);
1406 write_to_terminal_atomic(level, time_buf, file, line, func, fmt, args, time_ns, true);
1407 va_end(args);
1408}

References args, get_current_time_formatted(), and LOG_TIMESTAMP_BUFFER_SIZE.

◆ log_truncate_if_large()

void log_truncate_if_large ( void )

#include <include/ascii-chat/log/log.h>

Manually truncate large log files.

Checks if the log file exceeds MAX_LOG_SIZE and truncates it if necessary.

Definition at line 853 of file log.c.

853 {
854 // Log rotation is inherently racy without locks - best-effort only
855 // For reliable rotation, use mmap mode or external logrotate
856 int file = atomic_load_int(&g_log.file);
857 if (file >= 0 && file != STDERR_FILENO && strlen(g_log.filename) > 0) {
858 struct stat st;
859 if (fstat(file, &st) == 0 && st.st_size > MAX_LOG_SIZE) {
860 atomic_store_u64(&g_log.current_size, (size_t)st.st_size);
861 }
862 }
863}
#define MAX_LOG_SIZE
Maximum log file size in bytes (3MB) before rotation.
Definition log/log.h:76

References MAX_LOG_SIZE.

Referenced by main().

◆ log_unlock_terminal()

void log_unlock_terminal ( bool previous_state)

#include <include/ascii-chat/log/log.h>

Release terminal lock and flush buffered messages.

Call this after interactive prompts complete to release the terminal lock. Buffered log messages from other threads will be flushed to terminal.

Parameters
previous_stateThe value returned by log_lock_terminal()

Definition at line 842 of file log.c.

842 {
843 atomic_store_u64(&g_log.terminal_locked, previous_state);
844 if (!previous_state) {
845 atomic_store_u64(&g_log.terminal_owner_thread, 0);
846 }
847}