ascii-chat 0.11.33
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
 A single buffered log entry. 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_plain(...)   log_plain_msg(__VA_ARGS__)
 Plain logging - writes to both log file and stderr without timestamps or log levels.
 
#define log_plain_stderr(...)   log_plain_stderr_msg(__VA_ARGS__)
 Plain logging to stderr with newline.
 
#define log_plain_stdout(...)   log_plain_stdout_msg(__VA_ARGS__)
 Plain logging to stdout with newline.
 
#define log_plain_stderr_nonewline(...)   log_plain_stderr_nonewline_msg(__VA_ARGS__)
 Plain logging to stderr without newline - for interactive prompts.
 
#define log_file(...)   log_file_msg(__VA_ARGS__)
 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)   (1ULL << (n))
 Logging bit conversion.
 
#define LOG_CLIENT_IMPL(client, level, fmt, ...)
 
#define log_debug_client(client, fmt, ...)   LOG_CLIENT_IMPL(client, LOG_DEBUG, fmt, ##__VA_ARGS__)
 Server sends DEBUG log message to client.
 
#define log_info_client(client, fmt, ...)   LOG_CLIENT_IMPL(client, LOG_INFO, fmt, ##__VA_ARGS__)
 Server sends INFO log message to client.
 
#define log_warn_client(client, fmt, ...)   LOG_CLIENT_IMPL(client, LOG_WARN, fmt, ##__VA_ARGS__)
 Server sends WARN log message to client.
 
#define log_error_client(client, fmt, ...)   LOG_CLIENT_IMPL(client, LOG_ERROR, fmt, ##__VA_ARGS__)
 Server sends ERROR log message to client.
 
#define log_fatal_client(client, fmt, ...)   LOG_CLIENT_IMPL(client, LOG_FATAL, fmt, ##__VA_ARGS__)
 Server sends FATAL log message to client.
 
#define LOG_SERVER_IMPL(sockfd, crypto_ctx, level, fmt, ...)
 
#define log_debug_server(sockfd, crypto_ctx, fmt, ...)    LOG_SERVER_IMPL(sockfd, crypto_ctx, LOG_DEBUG, fmt, ##__VA_ARGS__)
 Client sends DEBUG log message to server.
 
#define log_info_server(sockfd, crypto_ctx, fmt, ...)   LOG_SERVER_IMPL(sockfd, crypto_ctx, LOG_INFO, fmt, ##__VA_ARGS__)
 Client sends INFO log message to server.
 
#define log_warn_server(sockfd, crypto_ctx, fmt, ...)   LOG_SERVER_IMPL(sockfd, crypto_ctx, LOG_WARN, fmt, ##__VA_ARGS__)
 Client sends WARN log message to server.
 
#define log_error_server(sockfd, crypto_ctx, fmt, ...)    LOG_SERVER_IMPL(sockfd, crypto_ctx, LOG_ERROR, fmt, ##__VA_ARGS__)
 Client sends ERROR log message to server.
 
#define log_fatal_server(sockfd, crypto_ctx, fmt, ...)    LOG_SERVER_IMPL(sockfd, crypto_ctx, LOG_FATAL, fmt, ##__VA_ARGS__)
 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
 
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
}
 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_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_get_color_array (void)
 Get the appropriate color array based on terminal capabilities.
 
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_network_message (socket_t sockfd, const struct crypto_context_t *crypto_ctx, log_level_t level, remote_log_direction_t direction, const char *fmt,...)
 Send a formatted log message over the network.
 
asciichat_error_t log_net_message (socket_t sockfd, const struct crypto_context_t *crypto_ctx, log_level_t level, remote_log_direction_t direction, const char *file, int line, const char *func, const char *fmt,...)
 Log a message to all destinations (network, file, and terminal).
 
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_msg(level, __FILE__, __LINE__, __func__, __VA_ARGS__)
 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_only(LOG_BIT(LOG_DEV), LOG_DEV, __VA_ARGS__)
 Log a DEV message (most verbose, development only)
 
#define log_debug(...)   log_only(LOG_BIT(LOG_DEBUG), LOG_DEBUG, __VA_ARGS__)
 Log a DEBUG message.
 
#define log_info(...)   log_only(LOG_BIT(LOG_INFO), LOG_INFO, __VA_ARGS__)
 Log an INFO message.
 
#define log_warn(...)   log_only(LOG_BIT(LOG_WARN), LOG_WARN, __VA_ARGS__)
 Log a WARN message.
 
#define log_error(...)   log_only(LOG_BIT(LOG_ERROR), LOG_ERROR, __VA_ARGS__)
 Log an ERROR message.
 
#define log_fatal(...)   log_only(LOG_BIT(LOG_FATAL), LOG_FATAL, __VA_ARGS__)
 Log a FATAL message.
 

Detailed Description

📝 Logging API with multiple log levels and terminal output control

This header provides a comprehensive logging system with:

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:

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/log.c:1215
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
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/log.c:1019
void log_destroy(void)
Destroy the logging system and close log file.
Definition log/log.c:624
void log_set_level(log_level_t level)
Set the minimum log level.
Definition log/log.c:691
void log_init(const char *filename, log_level_t level, bool force_stderr, bool use_mmap)
Initialize the logging system.
Definition log/log.c:533
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
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/log.c:1194
platform_mmap_t mmap
Definition mmap.c:34

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:534
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_fatal(...)
Log a FATAL message.
Definition log/log.h:599
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548

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

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/log.c:744

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/log.c:721
void log_disable_file_output(void)
Disable file output and use stderr instead.
Definition log/log.c:733
void log_set_terminal_output(bool enabled)
Control stderr output to terminal.
Definition log/log.c:700

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/log.c:787
void log_unlock_terminal(bool previous_state)
Release terminal lock and flush buffered messages.
Definition log/log.c:796

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:705
#define log_debug_nth(n, fmt,...)
Log DEBUG message every nth call.
Definition log/log.h:808
#define log_info_once(fmt,...)
Log INFO message exactly once.
Definition log/log.h:836

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:702

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, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ASCIICHAT_OK
Definition error_codes.h:51

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/log.c:1765
#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/log.c:1806
void log_shutdown_begin(void)
Begin shutdown phase - disable console logging but keep file logging.
Definition log/log.c:1795

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:135
@ LOG_COLOR_DEBUG
Definition log/log.h:132

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:625
#define log_file(...)
File-only logging - writes to log file only, no stderr output.
Definition log/log.h:646

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(socket_t sockfd, const struct crypto_context_t *crypto_ctx, log_level_t level, remote_log_direction_t direction, const char *file, int line, const char *func, const char *fmt,...)
Log a message to all destinations (network, file, and terminal).
Definition log/log.c:1560

Macro Definition Documentation

◆ DEFAULT_LOG_LEVEL

#define DEFAULT_LOG_LEVEL   LOG_DEBUG

#include <log.h>

Default log level for debug builds (DEBUG and above)

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

◆ log_as

#define log_as (   level,
  ... 
)    log_msg(level, __FILE__, __LINE__, __func__, __VA_ARGS__)

#include <log.h>

Log a message given the associated level.

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

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

◆ LOG_ATOMIC_UINT64

#define LOG_ATOMIC_UINT64   atomic_t

#include <log.h>

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

◆ LOG_ATOMIC_UINT64_INIT

#define LOG_ATOMIC_UINT64_INIT (   val)

#include <log.h>

Value:
{.impl = (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:59

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

36 {.impl = (uint64_t)0, \
37 .last_store_time_ns = (uint64_t)0, \
38 .last_load_time_ns = (uint64_t)0, \
39 .store_count = (uint64_t)0, \
40 .load_count = (uint64_t)0, \
41 .cas_count = (uint64_t)0, \
42 .cas_success_count = (uint64_t)0, \
43 .fetch_count = (uint64_t)0}

◆ LOG_BIT

#define LOG_BIT (   n)    (1ULL << (n))

#include <types.h>

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 <log.h>

Value:
do { \
if ((client)->crypto_initialized) { \
const struct crypto_context_t *_ctx = crypto_handshake_get_context(&(client)->crypto_handshake_ctx); \
log_net_message((client)->socket, _ctx, level, REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT, __FILE__, __LINE__, \
__func__, fmt, ##__VA_ARGS__); \
} \
} while (0)
const crypto_context_t * crypto_handshake_get_context(const crypto_handshake_context_t *ctx)
Get the crypto context for encryption/decryption.
@ REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT
Definition network/log.h:22
Cryptographic context structure.

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

56 { \
57 if ((client)->crypto_initialized) { \
58 const struct crypto_context_t *_ctx = crypto_handshake_get_context(&(client)->crypto_handshake_ctx); \
59 log_net_message((client)->socket, _ctx, level, REMOTE_LOG_DIRECTION_SERVER_TO_CLIENT, __FILE__, __LINE__, \
60 __func__, fmt, ##__VA_ARGS__); \
61 } \
62 } while (0)

◆ log_debug

#define log_debug (   ...)    log_only(LOG_BIT(LOG_DEBUG), LOG_DEBUG, __VA_ARGS__)

#include <log.h>

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 548 of file log/log.h.

◆ log_debug_client

#define log_debug_client (   client,
  fmt,
  ... 
)    LOG_CLIENT_IMPL(client, LOG_DEBUG, fmt, ##__VA_ARGS__)

#include <log.h>

Server sends DEBUG log message to client.

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

◆ log_debug_server

#define log_debug_server (   sockfd,
  crypto_ctx,
  fmt,
  ... 
)     LOG_SERVER_IMPL(sockfd, crypto_ctx, LOG_DEBUG, fmt, ##__VA_ARGS__)

#include <log.h>

Client sends DEBUG log message to server.

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

◆ log_dev

#define log_dev (   ...)    log_only(LOG_BIT(LOG_DEV), LOG_DEV, __VA_ARGS__)

#include <log.h>

Log a DEV message (most verbose, development only)

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

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

◆ log_error

#define log_error (   ...)    log_only(LOG_BIT(LOG_ERROR), LOG_ERROR, __VA_ARGS__)

#include <log.h>

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 587 of file log/log.h.

◆ log_error_client

#define log_error_client (   client,
  fmt,
  ... 
)    LOG_CLIENT_IMPL(client, LOG_ERROR, fmt, ##__VA_ARGS__)

#include <log.h>

Server sends ERROR log message to client.

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

◆ log_error_server

#define log_error_server (   sockfd,
  crypto_ctx,
  fmt,
  ... 
)     LOG_SERVER_IMPL(sockfd, crypto_ctx, LOG_ERROR, fmt, ##__VA_ARGS__)

#include <log.h>

Client sends ERROR log message to server.

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

◆ log_every

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

#include <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)
bool atomic_cas_u64(atomic_t *a, uint64_t *expected, uint64_t new_value)
Atomically compare-and-swap a uint64_t.
Definition atomic.c:264
uint64_t atomic_load_u64(atomic_t *a)
Atomically load a uint64_t value.
Definition atomic.c:233
#define LOG_ATOMIC_UINT64
Definition log/log.h:30
#define LOG_ATOMIC_UINT64_INIT(val)
Definition log/log.h:35
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 680 of file log/log.h.

681 { \
682 static LOG_ATOMIC_UINT64 _log_every_last_time = LOG_ATOMIC_UINT64_INIT(0); \
683 uint64_t _log_every_now = platform_get_monotonic_time_us(); \
684 uint64_t _log_every_last = atomic_load_u64(&_log_every_last_time); \
685 if (_log_every_now - _log_every_last >= (uint64_t)(interval_us)) { \
686 if (atomic_cas_u64(&_log_every_last_time, &_log_every_last, _log_every_now)) { \
687 log_msg(LOG_##log_level, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__); \
688 } \
689 } \
690 } while (0)

◆ log_fatal

#define log_fatal (   ...)    log_only(LOG_BIT(LOG_FATAL), LOG_FATAL, __VA_ARGS__)

#include <log.h>

Log a FATAL message.

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

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

◆ log_fatal_client

#define log_fatal_client (   client,
  fmt,
  ... 
)    LOG_CLIENT_IMPL(client, LOG_FATAL, fmt, ##__VA_ARGS__)

#include <log.h>

Server sends FATAL log message to client.

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

◆ log_fatal_server

#define log_fatal_server (   sockfd,
  crypto_ctx,
  fmt,
  ... 
)     LOG_SERVER_IMPL(sockfd, crypto_ctx, LOG_FATAL, fmt, ##__VA_ARGS__)

#include <log.h>

Client sends FATAL log message to server.

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

◆ log_file

#define log_file (   ...)    log_file_msg(__VA_ARGS__)

#include <log.h>

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

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

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

◆ LOG_HEADER_BUFFER_SIZE

#define LOG_HEADER_BUFFER_SIZE   512

#include <log.h>

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

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

◆ log_info

#define log_info (   ...)    log_only(LOG_BIT(LOG_INFO), LOG_INFO, __VA_ARGS__)

#include <log.h>

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 561 of file log/log.h.

◆ log_info_client

#define log_info_client (   client,
  fmt,
  ... 
)    LOG_CLIENT_IMPL(client, LOG_INFO, fmt, ##__VA_ARGS__)

#include <log.h>

Server sends INFO log message to client.

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

◆ log_info_server

#define log_info_server (   sockfd,
  crypto_ctx,
  fmt,
  ... 
)    LOG_SERVER_IMPL(sockfd, crypto_ctx, LOG_INFO, fmt, ##__VA_ARGS__)

#include <log.h>

Client sends INFO log message to server.

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

◆ LOG_MMAP_MSG_BUFFER_SIZE

#define LOG_MMAP_MSG_BUFFER_SIZE   1024

#include <log.h>

Maximum size of a log message in mmap mode.

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

◆ LOG_MSG_BUFFER_SIZE

#define LOG_MSG_BUFFER_SIZE   4096

#include <log.h>

Maximum size of a single log message (including formatting)

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

◆ log_nth

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

#include <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 748 of file log/log.h.

749 { \
750 static LOG_ATOMIC_UINT64 _log_nth_counter = LOG_ATOMIC_UINT64_INIT(0); \
751 uint64_t _log_nth_count = atomic_load_u64(&_log_nth_counter); \
752 uint64_t _log_nth_new = _log_nth_count + 1; \
753 atomic_store_u64(&_log_nth_counter, _log_nth_new); \
754 if (_log_nth_new % (uint64_t)(n) == 0) { \
755 log_msg(LOG_##log_level, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__); \
756 } \
757 } while (0)

◆ log_once

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

#include <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 788 of file log/log.h.

789 { \
790 static LOG_ATOMIC_UINT64 _log_once_counter = LOG_ATOMIC_UINT64_INIT(0); \
791 uint64_t _log_once_count = atomic_load_u64(&_log_once_counter); \
792 if (_log_once_count == 0) { \
793 atomic_store_u64(&_log_once_counter, 1); \
794 log_msg(LOG_##log_level, __FILE__, __LINE__, __func__, fmt, ##__VA_ARGS__); \
795 } \
796 } while (0)

◆ log_only

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

#include <log.h>

Value:
({ \
if ((bitmask) & LOG_BIT(level)) \
log_as(level, __VA_ARGS__); \
})
#define LOG_BIT(n)
Logging bit conversion.
Definition types.h:19

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 519 of file log/log.h.

520 { \
521 if ((bitmask) & LOG_BIT(level)) \
522 log_as(level, __VA_ARGS__); \
523 })

◆ log_plain

#define log_plain (   ...)    log_plain_msg(__VA_ARGS__)

#include <log.h>

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

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

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

◆ log_plain_stderr

#define log_plain_stderr (   ...)    log_plain_stderr_msg(__VA_ARGS__)

#include <log.h>

Plain logging to stderr with newline.

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

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

◆ log_plain_stderr_nonewline

#define log_plain_stderr_nonewline (   ...)    log_plain_stderr_nonewline_msg(__VA_ARGS__)

#include <log.h>

Plain logging to stderr without newline - for interactive prompts.

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

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

◆ log_plain_stdout

#define log_plain_stdout (   ...)    log_plain_stdout_msg(__VA_ARGS__)

#include <log.h>

Plain logging to stdout with newline.

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

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

◆ LOG_SERVER_IMPL

#define LOG_SERVER_IMPL (   sockfd,
  crypto_ctx,
  level,
  fmt,
  ... 
)

#include <log.h>

Value:
log_net_message(sockfd, (const struct crypto_context_t *)(crypto_ctx), 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 86 of file network/log.h.

◆ LOG_TIMESTAMP_BUFFER_SIZE

#define LOG_TIMESTAMP_BUFFER_SIZE   32

#include <log.h>

Maximum size of a timestamp string.

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

◆ log_warn

#define log_warn (   ...)    log_only(LOG_BIT(LOG_WARN), LOG_WARN, __VA_ARGS__)

#include <log.h>

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 574 of file log/log.h.

◆ log_warn_client

#define log_warn_client (   client,
  fmt,
  ... 
)    LOG_CLIENT_IMPL(client, LOG_WARN, fmt, ##__VA_ARGS__)

#include <log.h>

Server sends WARN log message to client.

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

◆ log_warn_server

#define log_warn_server (   sockfd,
  crypto_ctx,
  fmt,
  ... 
)    LOG_SERVER_IMPL(sockfd, crypto_ctx, LOG_WARN, fmt, ##__VA_ARGS__)

#include <log.h>

Client sends WARN log message to server.

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

◆ MAX_LOG_SIZE

#define MAX_LOG_SIZE   (3 * 1024 * 1024)

#include <log.h>

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

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

◆ MAX_TERMINAL_BUFFER_ENTRIES

#define MAX_TERMINAL_BUFFER_ENTRIES   256

#include <log.h>

Maximum number of buffered log entries.

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

◆ MAX_TERMINAL_BUFFER_SIZE

#define MAX_TERMINAL_BUFFER_SIZE   (64 * 1024)

#include <log.h>

Maximum size of terminal output buffer (64KB)

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

Typedef Documentation

◆ color_scheme_t

#include <log.h>

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

◆ remote_log_direction_t

#include <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 <log.h>

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

Enumeration Type Documentation

◆ log_color_t

#include <log.h>

Color enum for logging - indexes into color arrays.

These values directly index into level_colors arrays. 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

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

130 {
131 LOG_COLOR_DEV = 0,
132 LOG_COLOR_DEBUG = 1,
133 LOG_COLOR_INFO = 2,
134 LOG_COLOR_WARN = 3,
135 LOG_COLOR_ERROR = 4,
136 LOG_COLOR_FATAL = 5,
137 LOG_COLOR_GREY = 6,
138 LOG_COLOR_RESET = 7
log_color_t
Color enum for logging - indexes into color arrays.
Definition log/log.h:130
@ LOG_COLOR_RESET
Definition log/log.h:138
@ LOG_COLOR_GREY
Definition log/log.h:137
@ LOG_COLOR_FATAL
Definition log/log.h:136
@ LOG_COLOR_DEV
Definition log/log.h:131
@ LOG_COLOR_INFO
Definition log/log.h:133
@ LOG_COLOR_WARN
Definition log/log.h:134

◆ log_level_t

#include <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 <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 <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 972 of file log/named.c.

972 {
973#define COLORIZE_BUFFERS 4
974#define COLORIZE_BUFFER_SIZE 512
975 static char buffers[COLORIZE_BUFFERS][COLORIZE_BUFFER_SIZE];
976 static int buffer_idx = 0;
977
978 if (!name_str || name_str[0] == '\0') {
979 return name_str;
980 }
981
982 char *current_buf = buffers[buffer_idx];
983 buffer_idx = (buffer_idx + 1) % COLORIZE_BUFFERS;
984
985 /* Find the slash separator (type/name) */
986 const char *slash = strchr(name_str, '/');
987 if (!slash) {
988 /* No slash found - just return the string as-is */
989 return name_str;
990 }
991
992 /* Find opening paren for address */
993 const char *paren = strchr(name_str, '(');
994
995 /* Extract type (before slash) */
996 size_t type_len = slash - name_str;
997 char type_buf[256];
998 size_t copy_len = type_len < sizeof(type_buf) - 1 ? type_len : sizeof(type_buf) - 1;
999 memcpy(type_buf, name_str, copy_len);
1000 type_buf[copy_len] = '\0';
1001
1002 /* Extract name (between slash and paren, or end of string) */
1003 const char *name_start = slash + 1;
1004 size_t name_len;
1005 if (paren && paren > slash) {
1006 /* Skip spaces before paren */
1007 const char *name_end = paren - 1;
1008 while (name_end > name_start && *name_end == ' ') {
1009 name_end--;
1010 }
1011 name_len = name_end - name_start + 1;
1012 } else {
1013 name_len = strlen(name_start);
1014 }
1015
1016 char name_buf[256];
1017 size_t name_copy_len = name_len < sizeof(name_buf) - 1 ? name_len : sizeof(name_buf) - 1;
1018 memcpy(name_buf, name_start, name_copy_len);
1019 name_buf[name_copy_len] = '\0';
1020
1021 /* Apply colors */
1022 const char *type_colored = colored_string(LOG_COLOR_WARN, type_buf);
1023 const char *name_colored = colored_string(LOG_COLOR_DEV, name_buf);
1024
1025 /* Format: type(yellow)/name(blue) and add address part if present */
1026 if (paren) {
1027 /* Extract address (0x...) from parentheses */
1028 const char *addr_start = paren + 1;
1029 const char *addr_end = strchr(addr_start, ')');
1030 if (addr_end) {
1031 size_t addr_len = addr_end - addr_start;
1032 char addr_buf[128];
1033 size_t addr_copy_len = addr_len < sizeof(addr_buf) - 1 ? addr_len : sizeof(addr_buf) - 1;
1034 memcpy(addr_buf, addr_start, addr_copy_len);
1035 addr_buf[addr_copy_len] = '\0';
1036
1037 const char *addr_colored = colored_string(LOG_COLOR_GREY, addr_buf);
1038 safe_snprintf(current_buf, COLORIZE_BUFFER_SIZE, "%s/%s (%s)", type_colored, name_colored, addr_colored);
1039 } else {
1040 safe_snprintf(current_buf, COLORIZE_BUFFER_SIZE, "%s/%s %s", type_colored, name_colored, paren);
1041 }
1042 } else {
1043 safe_snprintf(current_buf, COLORIZE_BUFFER_SIZE, "%s/%s", type_colored, name_colored);
1044 }
1045
1046 return current_buf;
1047#undef COLORIZE_BUFFERS
1048#undef COLORIZE_BUFFER_SIZE
1049}
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
const char * colored_string(log_color_t color, const char *text)
Build a colored string for terminal output.
#define COLORIZE_BUFFER_SIZE
#define COLORIZE_BUFFERS

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

◆ format_message()

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

#include <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 248 of file log/log.c.

248 {
249 if (!format) {
250 return NULL;
251 }
252
253 // First, determine the size needed
254 va_list args_copy;
255 va_copy(args_copy, args);
256 int size = safe_vsnprintf(NULL, 0, format, args_copy);
257 va_end(args_copy);
258
259 if (size < 0) {
260 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format context message");
261 return NULL;
262 }
263
264 // Allocate and format the message
265 char *message = SAFE_MALLOC(size + 1, char *);
266 int result = safe_vsnprintf(message, (size_t)size + 1, format, args);
267 if (result < 0) {
268 SAFE_FREE(message);
269 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format context message");
270 return NULL;
271 }
272
273 return message;
274}
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
@ ERROR_INVALID_STATE
int safe_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list ap)
Safe formatted string printing with va_list.
Definition system.c:199
#define LOGGING_INTERNAL_ERROR(error, message,...)
Definition log/log.c:201
action_args_t args

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

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

◆ get_current_time_formatted()

size_t get_current_time_formatted ( char *  time_buf)

#include <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 216 of file log/log.c.

216 {
217 /* Get wall-clock time in nanoseconds */
219 // Extract seconds and nanoseconds from total nanoseconds
220 time_t seconds = (time_t)(ts_ns / NS_PER_SEC_INT);
221 long nanoseconds = (long)(ts_ns % NS_PER_SEC_INT);
222 struct tm tm_info;
223 platform_localtime(&seconds, &tm_info);
224 // Format the time part first
225 // strftime returns 0 on error, not negative (and len is size_t/unsigned)
226 size_t len = strftime(time_buf, 32, "%H:%M:%S", &tm_info);
227 if (len == 0 || len >= 32) {
228 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format time");
229 return 0;
230 }
231
232 // Add microseconds manually (convert nanoseconds to microseconds for display)
233 long microseconds = nanoseconds / 1000;
234 if (microseconds < 0)
235 microseconds = 0;
236 if (microseconds > 999999)
237 microseconds = 999999;
238
239 int result = safe_snprintf(time_buf + len, 32 - len, ".%06ld", microseconds);
240 if (result < 0 || result >= (int)(32 - len)) {
241 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format microseconds");
242 return 0;
243 }
244
245 return len + (size_t)result;
246}
uint64_t time_get_realtime_ns(void)
Get current wall-clock (real) time in nanoseconds.
Definition util/time.c:119
#define NS_PER_SEC_INT
Definition time.h:157
asciichat_error_t platform_localtime(const time_t *timer, struct tm *result)
Platform-safe localtime wrapper.
Definition util.c:50

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

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

◆ log_cleanup_colors()

void log_cleanup_colors ( void  )

#include <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 1822 of file log/log.c.

1822 {
1823 colorscheme_cleanup_compiled(&g_log.compiled_colors);
1824}
void colorscheme_cleanup_compiled(compiled_color_scheme_t *compiled)
Clean up allocated strings in a compiled color scheme.

References colorscheme_cleanup_compiled().

Referenced by asciichat_shared_destroy().

◆ log_clear_session_log_buffer()

void log_clear_session_log_buffer ( void  )

#include <log.h>

Unregister the session log buffer.

No-op if no buffer is currently registered.

Definition at line 1847 of file log/log.c.

1847 {
1848 atomic_ptr_store(&g_log.session_log_buffer, NULL);
1849}
void atomic_ptr_store(atomic_ptr_t *a, void *value)
Atomically store a pointer.
Definition atomic.c:280

References atomic_ptr_store().

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

◆ log_destroy()

void log_destroy ( void  )

#include <log.h>

Destroy the logging system and close log file.

Definition at line 624 of file log/log.c.

624 {
625 // Destroy mmap logging first (if active)
626 if (log_mmap_is_active()) {
628 }
629
630 // Cleanup grep filter
631 grep_destroy();
632
633 /* Mark logging system as shutdown FIRST (state machine transition)
634 * This signals worker threads to stop logging immediately before we free memory.
635 * This prevents TOCTOU race where a worker thread could see non-NULL format pointer
636 * after we've freed it but before lifecycle_shutdown() is called. */
637 lifecycle_shutdown(&g_log.lifecycle);
638
639 // Cleanup custom format structures: All worker threads have been signaled to stop by this point.
640 // Safe to free templates now without use-after-free concerns.
641 if (g_log.format) {
642 log_template_free(g_log.format);
643 g_log.format = NULL;
644 }
645 if (g_log.format_console_only) {
646 log_template_free(g_log.format_console_only);
647 g_log.format_console_only = NULL;
648 }
649 atomic_store_bool(&g_log.has_custom_format, false);
650
651 // Lock-free cleanup using atomic operations
652 int old_file = atomic_load_int(&g_log.file);
653 if (old_file >= 0 && old_file != STDERR_FILENO) {
654 platform_close(old_file);
655 }
656 atomic_store_int(&g_log.file, -1);
657
658 // Destroy rotation mutex
659 lifecycle_shutdown(&g_log.rotation_mutex_lifecycle);
660}
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177
void atomic_store_int(atomic_t *a, int value)
Atomically store an int value.
Definition atomic.c:202
int atomic_load_int(atomic_t *a)
Atomically load an int value.
Definition atomic.c:194
void grep_destroy(void)
Clean up filter resources.
Definition grep.c:1177
void log_template_free(log_template_t *format)
Free compiled format structure.
Definition log/format.c:325
int platform_close(int fd)
Safe file close (close replacement)
bool lifecycle_shutdown(lifecycle_t *lc)
Definition lifecycle.c:108
bool log_mmap_is_active(void)
Check if mmap logging is active.
Definition mmap.c:390
void log_mmap_destroy(void)
Shutdown mmap logging.
Definition mmap.c:260

References atomic_load_int(), atomic_store_bool(), atomic_store_int(), grep_destroy(), lifecycle_shutdown(), log_mmap_destroy(), log_mmap_is_active(), log_template_free(), and platform_close().

Referenced by asciichat_shared_destroy(), and main().

◆ log_disable_file_output()

void log_disable_file_output ( void  )

#include <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 733 of file log/log.c.

733 {
734 /* Close the current file if it's not stderr */
735 int old_file = atomic_load_int(&g_log.file);
736 if (old_file >= 0 && old_file != STDERR_FILENO) {
737 platform_close(old_file);
738 }
739 /* Redirect file output to stderr */
740 atomic_store_int(&g_log.file, STDERR_FILENO);
741 g_log.filename[0] = '\0';
742}

References atomic_load_int(), atomic_store_int(), and platform_close().

◆ log_disable_mmap()

void log_disable_mmap ( void  )

#include <log.h>

Disable mmap logging and return to mutex-based logging.

Flushes remaining entries and closes the mmap file.

Definition at line 1784 of file log/log.c.

1784 {
1785 if (log_mmap_is_active()) {
1787 log_info("Lock-free mmap logging disabled");
1788 }
1789}

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

1765 {
1766 return log_enable_mmap_sized(log_path, 0); /* Use default size */
1767}
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/log.c:1769

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

1769 {
1770 if (!log_path) {
1771 return SET_ERRNO(ERROR_INVALID_PARAM, "log_path is required");
1772 }
1773
1774 // Initialize mmap logging - text is written directly to the mmap'd file
1775 asciichat_error_t result = log_mmap_init_simple(log_path, max_size);
1776 if (result != ASCIICHAT_OK) {
1777 return result;
1778 }
1779
1780 log_info("Lock-free mmap logging enabled: %s", log_path);
1781 return ASCIICHAT_OK;
1782}
@ 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:252

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

1467 {
1468 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
1469 return;
1470 }
1471
1472 char log_buffer[LOG_MSG_BUFFER_SIZE];
1473 va_list args;
1474 va_start(args, fmt);
1475 int msg_len = safe_vsnprintf(log_buffer, sizeof(log_buffer), fmt, args);
1476 va_end(args);
1477
1478 if (msg_len <= 0) {
1479 return;
1480 }
1481
1482 // Truncate at whole line boundaries to avoid UTF-8 issues
1483 msg_len = truncate_at_whole_line(log_buffer, msg_len, sizeof(log_buffer));
1484
1485 // Validate UTF-8 in formatted message
1486 validate_log_message_utf8(log_buffer, "file-only log");
1487
1488 // Write to mmap if active, else to file
1489 if (log_mmap_is_active()) {
1490 log_mmap_write(LOG_INFO, NULL, 0, NULL, "%s", log_buffer);
1491 } else {
1492 int file_fd = atomic_load_u64(&g_log.file);
1493 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
1494 write_to_log_file_atomic(log_buffer, msg_len, NULL);
1495 write_to_log_file_atomic("\n", 1, NULL);
1496 }
1497 }
1498}
#define LOG_MSG_BUFFER_SIZE
Maximum size of a single log message (including formatting)
Definition log/log.h:88
bool lifecycle_is_initialized(const lifecycle_t *lc)
Definition lifecycle.c:155
void log_mmap_write(int level, const char *file, int line, const char *func, const char *fmt,...)
Write a log entry directly to the mmap'd file (lock-free)
Definition mmap.c:312

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

Referenced by backtrace_print().

◆ log_get_color_array()

const char ** log_get_color_array ( void  )

#include <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 1630 of file log/log.c.

1630 {
1631 init_terminal_capabilities();
1632
1633 /* Colors should be initialized via log_system_init(); this is just a safety check */
1634 if (!g_log.log_colorscheme_initialized) {
1635 /* Fallback: initialize colors if not already done (should not happen after log_system_init) */
1637 }
1638
1639 /* Safety check: if colors are still not initialized, return NULL to prevent crashes from null pointers */
1640 if (!g_log.log_colorscheme_initialized) {
1641 return NULL;
1642 }
1643
1644 /* Return the compiled color scheme based on terminal capabilities
1645 * codes_16, codes_256, codes_truecolor are now proper arrays of pointers (const char *[8]),
1646 * so we can safely cast them to const char **. */
1647 if (g_log.terminal_caps.color_level >= TERM_COLOR_TRUECOLOR) {
1648 return (const char **)g_log.compiled_colors.codes_truecolor;
1649 } else if (g_log.terminal_caps.color_level >= TERM_COLOR_256) {
1650 return (const char **)g_log.compiled_colors.codes_256;
1651 } else {
1652 return (const char **)g_log.compiled_colors.codes_16;
1653 }
1654}
void log_init_colors(void)
Initialize logging color system with current terminal capabilities.
Definition log/log.c:1671
@ TERM_COLOR_256
256-color support (extended ANSI palette)
Definition terminal.h:586
@ TERM_COLOR_TRUECOLOR
24-bit truecolor support (RGB colors)
Definition terminal.h:588

References log_init_colors(), TERM_COLOR_256, and TERM_COLOR_TRUECOLOR.

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

◆ log_get_force_stderr()

bool log_get_force_stderr ( void  )

#include <log.h>

Get current force_stderr setting.

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

Definition at line 725 of file log/log.c.

725 {
726 return atomic_load_u64(&g_log.force_stderr);
727}

References atomic_load_u64().

Referenced by terminal_choose_log_fd().

◆ log_get_level()

log_level_t log_get_level ( void  )

#include <log.h>

Get the current minimum log level.

Returns
Current log level

Definition at line 696 of file log/log.c.

696 {
697 return (log_level_t)atomic_load_u64(&g_log.level);
698}

References atomic_load_u64().

◆ log_get_session_log_buffer()

session_log_buffer_t * log_get_session_log_buffer ( void  )

#include <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 1857 of file log/log.c.

1857 {
1858 return (session_log_buffer_t *)atomic_ptr_load(&g_log.session_log_buffer);
1859}
void * atomic_ptr_load(atomic_ptr_t *a)
Atomically load a pointer.
Definition atomic.c:272
Internal circular buffer structure.

References atomic_ptr_load().

Referenced by log_search_gather_and_filter_logs().

◆ log_get_template()

void * log_get_template ( void  )

#include <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 2156 of file log/log.c.

2156 {
2157 return (void *)g_log.format;
2158}

Referenced by backtrace_print().

◆ log_get_terminal_output()

bool log_get_terminal_output ( void  )

#include <log.h>

Get current terminal output setting.

Returns
true if terminal output is enabled, false otherwise

Definition at line 717 of file log/log.c.

717 {
718 return atomic_load_u64(&g_log.terminal_output_enabled);
719}

References atomic_load_u64().

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

533 {
534
535 // Initialize rotation mutex (only operation that uses a mutex)
536 g_log.rotation_mutex_lifecycle.sync_type = LIFECYCLE_SYNC_MUTEX;
537 g_log.rotation_mutex_lifecycle.sync.mutex = &g_log.rotation_mutex;
538 lifecycle_init(&g_log.rotation_mutex_lifecycle, "log_rotation");
539
540 // Set basic config using atomic stores
541 atomic_store_u64(&g_log.force_stderr, force_stderr);
542 bool preserve_terminal_output = atomic_load_u64(&g_log.terminal_output_enabled);
543
544 // Force logs to stderr if stdout is piped/redirected
545 // This prevents logs from contaminating piped/redirected output in snapshot/batch modes
546 // Per terminal.h: "force stderr when piped to prevent data corruption"
548 atomic_store_bool(&g_log.force_stderr, true);
549 }
550
551 // Close any existing file (atomic load/store)
552 int old_file = atomic_load_int(&g_log.file);
553 if (lifecycle_is_initialized(&g_log.lifecycle) && old_file >= 0 && old_file != STDERR_FILENO) {
554 platform_close(old_file);
555 atomic_store_int(&g_log.file, -1);
556 }
557
558 // Check LOG_LEVEL environment variable
559 const char *env_level_str = SAFE_GETENV("LOG_LEVEL");
560 if (env_level_str) {
561 atomic_store_u64(&g_log.level, (int)parse_log_level_from_env());
562 } else {
563 atomic_store_u64(&g_log.level, (int)level);
564 }
565
566 atomic_store_bool(&g_log.level_manually_set, false);
567 atomic_store_u64(&g_log.current_size, 0);
568
569 if (filename) {
570 SAFE_STRNCPY(g_log.filename, filename, sizeof(g_log.filename) - 1);
571
572 if (use_mmap) {
573 // Lock-free mmap path - writes go to mmap'd file
574 asciichat_error_t mmap_result = log_mmap_init_simple(filename, 0);
575 if (mmap_result == ASCIICHAT_OK) {
576 atomic_store_int(&g_log.file, -1); // No regular fd - using mmap for file output
577 } else {
578 // Mmap failed - use stderr only (atomic writes, lock-free)
579 if (preserve_terminal_output) {
580 safe_fprintf(stderr, "Mmap logging failed for %s, using stderr only (lock-free)\n", filename);
581 }
582 atomic_store_int(&g_log.file, STDERR_FILENO);
583 g_log.filename[0] = '\0';
584 }
585 } else {
586 // Lock-free file I/O path - uses atomic write() syscalls
587 int fd = platform_open("log_file", filename, O_CREAT | O_RDWR | O_TRUNC, FILE_PERM_PRIVATE);
588 atomic_store_int(&g_log.file, (fd >= 0) ? fd : STDERR_FILENO);
589 if (fd < 0) {
590 if (preserve_terminal_output) {
591 safe_fprintf(stderr, "Failed to open log file: %s\n", filename);
592 }
593 g_log.filename[0] = '\0';
594 }
595 }
596 } else {
597 atomic_store_int(&g_log.file, STDERR_FILENO);
598 g_log.filename[0] = '\0';
599 }
600
601 /* Initialize default log format (NULL means use mode-specific default) */
602 log_set_format(NULL, false);
603
604 /* Mark logging system as initialized (state machine transition) */
605 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
606 lifecycle_init(&g_log.lifecycle, "logging");
607 }
608
609 atomic_store_u64(&g_log.terminal_output_enabled, preserve_terminal_output);
610
611 // Reset terminal detection if needed
612 if (g_log.terminal_caps_initialized && !g_log.terminal_caps.detection_reliable) {
613 g_log.terminal_caps_initialized = false;
614 }
615
616 // Detect terminal capabilities
618
619 // NOTE: Color initialization happens separately via log_set_color_scheme()
620 // after options are parsed. Logging works without colors until then.
621 // NOTE: Grep filter initialization happens in main.c after options_init() completes.
622}
void atomic_store_u64(atomic_t *a, uint64_t value)
Atomically store a uint64_t value.
Definition atomic.c:241
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:414
#define SAFE_GETENV(name)
Definition common.h:434
void log_redetect_terminal_capabilities(void)
Re-detect terminal capabilities after logging is initialized.
Definition log/log.c:1602
int safe_fprintf(FILE *stream, const char *format,...)
Safe formatted output to file stream.
Definition system.c:172
int platform_open(const char *name, const char *pathname, int flags,...)
Safe file open (open replacement)
#define FILE_PERM_PRIVATE
File permission: Private (owner read/write only)
Definition filesystem.h:187
bool lifecycle_init(lifecycle_t *lc, const char *name)
Definition lifecycle.c:26
@ LIFECYCLE_SYNC_MUTEX
Contains mutex_t pointer.
Definition lifecycle.h:50
bool terminal_is_piped_output(void)
Check if stdout is piped or redirected.

References ASCIICHAT_OK, atomic_load_int(), atomic_load_u64(), atomic_store_bool(), atomic_store_int(), atomic_store_u64(), FILE_PERM_PRIVATE, lifecycle_init(), lifecycle_is_initialized(), LIFECYCLE_SYNC_MUTEX, log_mmap_init_simple(), log_redetect_terminal_capabilities(), log_set_format(), platform_close(), platform_open(), safe_fprintf(), SAFE_GETENV, SAFE_STRNCPY, and terminal_is_piped_output().

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

◆ log_init_colors()

void log_init_colors ( void  )

#include <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 1671 of file log/log.c.

1671 {
1672
1673 /* Skip color initialization during terminal detection to avoid mutex deadlock */
1674 if (g_log.terminal_caps_detecting) {
1675 return;
1676 }
1677
1678 /* Skip color initialization before logging is fully initialized */
1679 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
1680 return;
1681 }
1682
1683 if (g_log.log_colorscheme_initialized) {
1684 return;
1685 }
1686
1687 /* Get active color scheme - this ensures color system is initialized */
1689 if (!scheme) {
1690 /* Don't mark as initialized if we can't get a color scheme - return NULL instead */
1691 return;
1692 }
1693
1694 /* Acquire mutex for compilation (mutex is now initialized by colorscheme_init) */
1696
1697 /* Debug: Check if g_log.compiled_colors is actually zero-initialized */
1698 /* Zero the structure on first use to avoid freeing garbage pointers */
1699 /* (static = {0} produces garbage in this build for unknown reasons) */
1700 static bool first_compile = true;
1701 if (first_compile) {
1702 memset(&g_log.compiled_colors, 0, sizeof(g_log.compiled_colors));
1703 first_compile = false;
1704 }
1705
1706 /* Detect terminal background */
1708 /* Determine color mode for compilation */
1710 if (g_log.terminal_caps.color_level >= TERM_COLOR_TRUECOLOR) {
1711 mode = TERM_COLOR_TRUECOLOR;
1712 } else if (g_log.terminal_caps.color_level >= TERM_COLOR_256) {
1713 mode = TERM_COLOR_256;
1714 } else {
1715 mode = TERM_COLOR_16;
1716 }
1717 /* Compile the color scheme to ANSI codes */
1718 asciichat_error_t result = colorscheme_compile_scheme(scheme, mode, background, &g_log.compiled_colors);
1719 g_log.log_colorscheme_initialized = true;
1721
1722 /* Log outside of mutex lock to avoid recursive lock deadlock */
1723 if (result != ASCIICHAT_OK) {
1724 log_debug("Failed to compile color scheme: %d", result);
1725 }
1726}
const color_scheme_t * colorscheme_get_active_scheme(void)
Get currently active color scheme.
terminal_background_t detect_terminal_background(void)
Detect the user's terminal theme (dark or light background)
asciichat_error_t colorscheme_compile_scheme(const color_scheme_t *scheme, terminal_color_mode_t mode, terminal_background_t background, compiled_color_scheme_t *compiled)
Compile a color scheme to ANSI codes.
mutex_t g_colorscheme_mutex
Shared mutex for color scheme compilation.
Definition colorscheme.c:39
terminal_background_t
Terminal theme detection result.
Definition colorscheme.h:50
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Color scheme definition.
Definition colorscheme.h:62
terminal_color_mode_t
Terminal color support levels.
Definition terminal.h:578
@ TERM_COLOR_16
16-color support (standard ANSI colors)
Definition terminal.h:584

References ASCIICHAT_OK, colorscheme_compile_scheme(), colorscheme_get_active_scheme(), detect_terminal_background(), g_colorscheme_mutex, lifecycle_is_initialized(), log_debug, mutex_lock, mutex_unlock, TERM_COLOR_16, TERM_COLOR_256, and TERM_COLOR_TRUECOLOR.

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

◆ log_labeled()

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

#include <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.

Definition at line 95 of file asciichat_errno.c.

95 {
96 va_list args;
97 va_start(args, message);
98 char *formatted_message = format_message(message, args);
99 va_end(args);
100
101 safe_fprintf(stderr, "%s: %s\n", colored_string(color, label), formatted_message);
102
103 log_file("%s: %s", label, formatted_message);
104
105 SAFE_FREE(formatted_message);
106}
char * format_message(const char *format, va_list args)
Format a message using va_list.
Definition log/log.c:248

References args, colored_string(), format_message(), log_file, safe_fprintf(), and SAFE_FREE.

Referenced by asciichat_fatal_with_context().

◆ log_level_color()

const char * log_level_color ( log_color_t  color)

#include <log.h>

Get color string for a given color enum.

Parameters
colorColor enum value
Returns
ANSI color code string

Definition at line 1656 of file log/log.c.

1656 {
1657 const char **colors = log_get_color_array();
1658 if (colors == NULL) {
1659 return ""; /* Return empty string if colors not available */
1660 }
1661 if (color >= 0 && color <= LOG_COLOR_RESET) {
1662 return colors[color];
1663 }
1664 return colors[LOG_COLOR_RESET]; /* Return reset color if invalid */
1665}
const char ** log_get_color_array(void)
Get the appropriate color array based on terminal capabilities.
Definition log/log.c:1630

References LOG_COLOR_RESET, and log_get_color_array().

Referenced by colored_string().

◆ log_lock_terminal()

bool log_lock_terminal ( void  )

#include <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 787 of file log/log.c.

787 {
788 bool expected = atomic_load_bool(&g_log.terminal_locked);
789 while (!atomic_cas_bool(&g_log.terminal_locked, &expected, true)) {
790 // Retry if CAS failed
791 }
792 atomic_store_u64(&g_log.terminal_owner_thread, (uint64_t)asciichat_thread_self());
793 return expected;
794}
bool atomic_load_bool(atomic_t *a)
Atomically load a boolean value.
Definition atomic.c:169
bool atomic_cas_bool(atomic_t *a, bool *expected, bool new_value)
Atomically compare-and-swap a boolean.
Definition atomic.c:184
#define asciichat_thread_self()

References asciichat_thread_self, atomic_cas_bool(), atomic_load_bool(), and atomic_store_u64().

Referenced by client_crypto_handshake(), client_main(), prompt_password(), prompt_unknown_host(), and ui_mdns_select().

◆ log_msg()

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

#include <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 1019 of file log/log.c.

1019 {
1020
1021 // All state access uses atomic operations - fully lock-free
1022 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
1023 return;
1024 }
1025
1026 // Safety check: if main format template has been freed during shutdown, skip logging
1027 // This prevents heap-use-after-free when threads call logging after log_destroy() has freed g_log.format
1028 if (!g_log.format) {
1029 return;
1030 }
1031
1032 uint64_t loaded_level = atomic_load_u64(&g_log.level);
1033
1034 if (level < (log_level_t)loaded_level) {
1035 return;
1036 }
1037 /* =========================================================================
1038 * MMAP PATH: When mmap logging is active, writes go to mmap'd file
1039 * ========================================================================= */
1040 bool mmap_active = log_mmap_is_active();
1041
1042 if (mmap_active) {
1043 maybe_rotate_log();
1044
1045 va_list args;
1046 va_start(args, fmt);
1047 char msg_buffer[LOG_MMAP_MSG_BUFFER_SIZE];
1048 int msg_len = safe_vsnprintf(msg_buffer, sizeof(msg_buffer), fmt, args);
1049 va_end(args);
1050
1051 // Truncate at whole line boundaries to avoid UTF-8 issues
1052 if (msg_len > 0) {
1053 msg_len = truncate_at_whole_line(msg_buffer, msg_len, sizeof(msg_buffer));
1054 }
1055
1056 // Validate UTF-8 in formatted message
1057 validate_log_message_utf8(msg_buffer, "mmap log message");
1058
1059 log_mmap_write(level, file, line, func, "%s", msg_buffer);
1060
1061 // Terminal output (check with atomic loads)
1062 if (atomic_load_u64(&g_log.terminal_output_enabled) && !atomic_load_bool(&g_log.terminal_locked)) {
1063 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
1064 uint64_t time_ns = time_get_realtime_ns();
1066
1067 // Choose output stream using unified routing logic
1068 int fd = terminal_choose_log_fd(level);
1069 FILE *output_stream = (fd == STDERR_FILENO) ? stderr : stdout;
1070 // Check if colors should be used
1071 // Priority 1: If --color was explicitly passed, force colors
1072 bool use_colors = true; // Default: enable colors
1074 use_colors = false; // --color=false explicitly disables colors
1075 }
1076 // Priority 2: If --color NOT explicitly passed, enable colors by default
1077
1078 char header_buffer[512];
1079 int header_len = format_log_header(header_buffer, sizeof(header_buffer), level, time_buf, file, line, func,
1080 use_colors, time_ns);
1081
1082 if (header_len >= 0 && header_len < (int)sizeof(header_buffer)) {
1083 if (use_colors) {
1084 const char *colorized_msg = colorize_log_message(msg_buffer);
1085 const char **colors = log_get_color_array();
1086 if (colors) {
1087 safe_fprintf(output_stream, "%s%s%s%s\n", header_buffer, colors[LOG_COLOR_RESET], colorized_msg,
1088 colors[LOG_COLOR_RESET]);
1089 } else {
1090 safe_fprintf(output_stream, "%s%s\n", header_buffer, colorized_msg);
1091 }
1092 } else {
1093 safe_fprintf(output_stream, "%s%s\n", header_buffer, msg_buffer);
1094 }
1095 (void)fflush(output_stream);
1096 }
1097 } else {
1098 }
1099 return;
1100 }
1101 /* =========================================================================
1102 * FILE I/O PATH: Lock-free using atomic write() syscalls
1103 * ========================================================================= */
1104 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
1105 uint64_t time_ns = time_get_realtime_ns();
1107 // Format message for file output
1108 char log_buffer[LOG_MSG_BUFFER_SIZE];
1109 va_list args;
1110 va_start(args, fmt);
1111 int header_len = format_log_header(log_buffer, sizeof(log_buffer), level, time_buf, file, line, func, false, time_ns);
1112 if (header_len < 0 || header_len >= (int)sizeof(log_buffer)) {
1113 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format log header");
1114 va_end(args);
1115 return;
1116 }
1117 int msg_len = header_len;
1118 int formatted_len = safe_vsnprintf(log_buffer + header_len, sizeof(log_buffer) - (size_t)header_len, fmt, args);
1119 if (formatted_len < 0) {
1120 LOGGING_INTERNAL_ERROR(ERROR_INVALID_STATE, "Failed to format log message");
1121 va_end(args);
1122 return;
1123 }
1124
1125 msg_len += formatted_len;
1126 // Truncate at whole line boundaries to avoid UTF-8 issues
1127 msg_len = truncate_at_whole_line(log_buffer, msg_len, sizeof(log_buffer));
1128 // Add newline if there's room and message doesn't already end with one
1129 if (msg_len > 0 && msg_len < (int)sizeof(log_buffer) - 1) {
1130 if (log_buffer[msg_len - 1] != '\n') {
1131 log_buffer[msg_len++] = '\n';
1132 log_buffer[msg_len] = '\0';
1133 }
1134 }
1135 va_end(args);
1136 // Validate UTF-8 in formatted message
1137 validate_log_message_utf8(log_buffer, "leveled log message");
1138
1139 // Extract the user message part (without header) for JSON logging
1140 const char *user_message = log_buffer + header_len;
1141
1142 // For JSON output: strip automatic trailing newline (keep explicit ones)
1143 char json_message_buf[LOG_MSG_BUFFER_SIZE];
1144 const char *json_message = user_message;
1145 size_t user_msg_len = strlen(user_message);
1146
1147 // If message ends with newline and it was added automatically (not in original message)
1148 if (user_msg_len > 0 && user_message[user_msg_len - 1] == '\n') {
1149 // The newline was added automatically if the message part didn't end with one
1150 // before we added it on line 1067. We can check this by looking at msg_len - 1
1151 // If msg_len >= header_len + 2, then msg_len - 2 would be the char before the newline
1152 int msg_content_len = msg_len - header_len;
1153 // NOLINTNEXTLINE(clang-analyzer-security.ArrayBound) - msg_content_len > 1 bounds check above
1154 if (msg_content_len > 1 && log_buffer[msg_len - 2] != '\n') {
1155 // Newline was added automatically - strip it for JSON
1156 if (user_msg_len - 1 < sizeof(json_message_buf)) {
1157 memcpy(json_message_buf, user_message, user_msg_len - 1);
1158 json_message_buf[user_msg_len - 1] = '\0';
1159 json_message = json_message_buf;
1160 }
1161 }
1162 }
1163
1164 // Check if JSON format is enabled
1165 int json_fd = atomic_load_int(&g_log.json_file);
1166 bool json_format_enabled = (json_fd >= 0);
1167
1168 // If JSON format is enabled, output ONLY JSON (skip text output)
1169 if (json_format_enabled) {
1170 // Output JSON to the JSON file descriptor
1171 log_json_write(json_fd, level, time_ns, file, line, func, json_message);
1172 // Also output JSON to console using unified routing logic, respecting quiet flag
1173 if (atomic_load_u64(&g_log.terminal_output_enabled)) {
1174 int console_fd = terminal_choose_log_fd(level);
1175 log_json_write(console_fd, level, time_ns, file, line, func, json_message);
1176 }
1177 } else {
1178 // Text format: output to file and terminal
1179 // No heap allocation - use log_buffer directly
1180 // Write to file (atomic write syscall) - use original buffer (with ANSI codes)
1181 int file_fd = (int)atomic_load_u64(&g_log.file);
1182 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
1183 write_to_log_file_atomic(log_buffer, msg_len, NULL);
1184 }
1185
1186 // Write to terminal (atomic state checks)
1187 va_list args_terminal;
1188 va_start(args_terminal, fmt);
1189 write_to_terminal_atomic(level, time_buf, file, line, func, fmt, args_terminal, time_ns);
1190 va_end(args_terminal);
1191 }
1192}
const char * colorize_log_message(const char *message)
Colorize a log message for terminal output.
Definition colorize.c:509
bool g_color_flag_value
Value of –color flag (true if –color was in argv) Set by options_init() before RCU is initialized,...
Definition common.c:58
bool g_color_flag_passed
Was –color explicitly passed in command-line arguments? Set by options_init() before RCU is initializ...
Definition common.c:57
size_t get_current_time_formatted(char *time_buf)
Get current time as formatted string.
Definition log/log.c:216
#define LOG_TIMESTAMP_BUFFER_SIZE
Maximum size of a timestamp string.
Definition log/log.h:97
#define LOG_MMAP_MSG_BUFFER_SIZE
Maximum size of a log message in mmap mode.
Definition log/log.h:91
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 json.c:94
int terminal_choose_log_fd(log_level_t level)
Choose output file descriptor for logging based on level and interactivity.

References args, atomic_load_bool(), atomic_load_int(), atomic_load_u64(), colorize_log_message(), ERROR_INVALID_STATE, g_color_flag_passed, g_color_flag_value, get_current_time_formatted(), lifecycle_is_initialized(), LOG_COLOR_RESET, log_get_color_array(), 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, safe_fprintf(), safe_vsnprintf(), terminal_choose_log_fd(), and time_get_realtime_ns().

Referenced by cond_log_state(), handle_remote_log_packet_from_client(), mutex_log_state(), and rwlock_log_state().

◆ log_named_format_message()

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

#include <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 591 of file log/named.c.

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

References named_get(), named_get_by_type_and_id(), named_get_fd(), named_get_packet_type(), and named_get_type().

Referenced by log_named_format_or_original().

◆ log_named_format_or_original()

const char * log_named_format_or_original ( const char *  message)

#include <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:913
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 913 of file log/named.c.

913 {
914 if (!message) {
915 return message;
916 }
917
918 static _Thread_local char format_buffer[NAMED_FORMAT_BUFFER_SIZE];
919 static _Thread_local char work_buffer[NAMED_FORMAT_BUFFER_SIZE];
920
921 /* Apply formatting iteratively until the message stabilizes (no more changes) */
922 const char *current = message;
923 size_t max_iterations =
924 3; /* Reduce iterations to prevent recursion (prevent transforming already-transformed output) */
925
926 for (size_t iter = 0; iter < max_iterations; iter++) {
927 int result = log_named_format_message(current, format_buffer, sizeof(format_buffer));
928
929 if (result <= 0) {
930 /* No transformation was made, we've reached a fixpoint */
931 return current == message ? message : format_buffer;
932 }
933
934 /* Check if the message changed */
935 if (strcmp(current, format_buffer) == 0) {
936 /* No actual change, return the current version */
937 return format_buffer;
938 }
939
940 /* Message changed, prepare for next iteration */
941 if (current == message) {
942 /* First iteration, use format_buffer as input for next iteration */
943 current = format_buffer;
944 } else {
945 /* Subsequent iterations, swap buffers */
946 asciichat_error_t strcpy_result = SAFE_STRCPY(work_buffer, sizeof(work_buffer), format_buffer);
947 if (strcpy_result != ASCIICHAT_OK) {
948 log_error("Failed to copy format buffer: %s", asciichat_error_string(strcpy_result));
949 return format_buffer;
950 }
951 current = work_buffer;
952 }
953 }
954
955 /* Max iterations reached, return last formatted version */
956 return format_buffer;
957}
#define SAFE_STRCPY(dest, dest_size, src)
Definition common.h:471
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:591
#define NAMED_FORMAT_BUFFER_SIZE
Definition log/named.c:25

References 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 ( socket_t  sockfd,
const struct crypto_context_t *  crypto_ctx,
log_level_t  level,
remote_log_direction_t  direction,
const char *  file,
int  line,
const char *  func,
const char *  fmt,
  ... 
)

#include <log.h>

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

Parameters
sockfdDestination socket
crypto_ctxOptional crypto context for encryption (NULL if not ready)
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 1560 of file log/log.c.

1562 {
1563 va_list args;
1564 va_start(args, fmt);
1565 asciichat_error_t result =
1566 log_network_message_internal(sockfd, crypto_ctx, level, direction, file, line, func, fmt, args);
1567 va_end(args);
1568 return result;
1569}

References args.

◆ log_network_message()

asciichat_error_t log_network_message ( socket_t  sockfd,
const struct crypto_context_t *  crypto_ctx,
log_level_t  level,
remote_log_direction_t  direction,
const char *  fmt,
  ... 
)

#include <log.h>

Send a formatted log message over the network.

Parameters
sockfdDestination socket
crypto_ctxOptional crypto context for encryption (NULL if not ready)
levelLog severity used for remote and local logging
directionRemote log direction metadata
fmtFormat string (printf-style)
...Format arguments
Returns
ASCIICHAT_OK on success, error code otherwise

Definition at line 1550 of file log/log.c.

1551 {
1552 va_list args;
1553 va_start(args, fmt);
1554 asciichat_error_t result =
1555 log_network_message_internal(sockfd, crypto_ctx, level, direction, NULL, 0, NULL, fmt, args);
1556 va_end(args);
1557 return result;
1558}

References args.

Referenced by disconnect_client_for_bad_data(), and threaded_send_client_join_packet().

◆ log_plain_msg()

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

#include <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 1215 of file log/log.c.

1215 {
1216 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
1217 return;
1218 }
1219
1220 if (shutdown_is_requested()) {
1221 return;
1222 }
1223
1224 char log_buffer[LOG_MSG_BUFFER_SIZE];
1225 va_list args;
1226 va_start(args, fmt);
1227 int msg_len = safe_vsnprintf(log_buffer, sizeof(log_buffer), fmt, args);
1228 va_end(args);
1229
1230 if (msg_len <= 0) {
1231 return;
1232 }
1233
1234 // Truncate at whole line boundaries to avoid UTF-8 issues
1235 msg_len = truncate_at_whole_line(log_buffer, msg_len, sizeof(log_buffer));
1236
1237 // Validate UTF-8 in formatted message
1238 validate_log_message_utf8(log_buffer, "plain text log");
1239
1240 // Write to mmap if active
1241 if (log_mmap_is_active()) {
1242 log_mmap_write(LOG_INFO, NULL, 0, NULL, "%s", log_buffer);
1243 } else {
1244 // Write to file with headers (atomic write syscall)
1245 int file_fd = atomic_load_u64(&g_log.file);
1246 if (file_fd >= 0 && file_fd != STDERR_FILENO) {
1247 // Add header with timestamp and log level to file output
1248 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
1249 uint64_t time_ns = time_get_realtime_ns();
1251
1252 char header_buffer[512];
1253 int header_len = format_log_header(header_buffer, sizeof(header_buffer), LOG_INFO, time_buf, "lib/log/logging.c",
1254 0, "log_plain_msg", false, time_ns);
1255
1256 if (header_len > 0) {
1257 write_to_log_file_atomic(header_buffer, header_len, NULL);
1258 }
1259 write_to_log_file_atomic(log_buffer, msg_len, NULL);
1260 write_to_log_file_atomic("\n", 1, NULL);
1261 }
1262 }
1263
1264 // Terminal output (atomic state checks)
1265 if (!atomic_load_u64(&g_log.terminal_output_enabled)) {
1266 return;
1267 }
1268 if (atomic_load_u64(&g_log.terminal_locked)) {
1269 uint64_t owner = atomic_load_u64(&g_log.terminal_owner_thread);
1270 if (owner != (uint64_t)asciichat_thread_self()) {
1271 return;
1272 }
1273 }
1274
1275 // Check if JSON format is enabled
1276 int json_fd = atomic_load_int(&g_log.json_file);
1277 bool json_format_enabled = (json_fd >= 0);
1278
1279 if (json_format_enabled) {
1280 // Output JSON for plain messages too
1281 uint64_t time_ns = time_get_realtime_ns();
1282 int console_fd = terminal_choose_log_fd(LOG_INFO);
1283 log_json_write(console_fd, LOG_INFO, time_ns, __FILE__, __LINE__, "log_plain_msg", log_buffer);
1284 } else {
1285 // Choose output stream using unified routing logic (LOG_INFO level)
1287 FILE *output_stream = (fd == STDERR_FILENO) ? stderr : stdout;
1288
1289 // Apply colorization for TTY output
1291 const char *colorized_msg = colorize_log_message(log_buffer);
1292 safe_fprintf(output_stream, "%s\n", colorized_msg);
1293 } else {
1294 safe_fprintf(output_stream, "%s\n", log_buffer);
1295 }
1296 (void)fflush(output_stream);
1297 }
1298}
bool shutdown_is_requested(void)
Check if shutdown has been requested.
Definition common.c:73
bool terminal_should_color_output(int fd)
Determine if color output should be used.

References args, asciichat_thread_self, atomic_load_int(), atomic_load_u64(), colorize_log_message(), get_current_time_formatted(), lifecycle_is_initialized(), LOG_INFO, log_json_write(), log_mmap_is_active(), log_mmap_write(), LOG_MSG_BUFFER_SIZE, LOG_TIMESTAMP_BUFFER_SIZE, safe_fprintf(), safe_vsnprintf(), shutdown_is_requested(), terminal_choose_log_fd(), terminal_should_color_output(), and time_get_realtime_ns().

◆ log_plain_stderr_msg()

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

#include <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 1361 of file log/log.c.

1361 {
1362 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
1363 return;
1364 }
1365 if (shutdown_is_requested()) {
1366 return;
1367 }
1368
1369 va_list args;
1370 va_start(args, fmt);
1371 log_plain_stderr_internal_atomic(fmt, args, true);
1372 va_end(args);
1373}

References args, lifecycle_is_initialized(), and shutdown_is_requested().

◆ log_plain_stderr_nonewline_msg()

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

#include <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 1375 of file log/log.c.

1375 {
1376 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
1377 return;
1378 }
1379 if (shutdown_is_requested()) {
1380 return;
1381 }
1382
1383 va_list args;
1384 va_start(args, fmt);
1385 log_plain_stderr_internal_atomic(fmt, args, false);
1386 va_end(args);
1387}

References args, lifecycle_is_initialized(), and shutdown_is_requested().

◆ log_plain_stdout_msg()

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

#include <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 1453 of file log/log.c.

1453 {
1454 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
1455 return;
1456 }
1457 if (shutdown_is_requested()) {
1458 return;
1459 }
1460
1461 va_list args;
1462 va_start(args, fmt);
1463 log_plain_stdout_internal_atomic(fmt, args, true);
1464 va_end(args);
1465}

References args, lifecycle_is_initialized(), 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 <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 1871 of file log/log.c.

1871 {
1872 if (!plain_line || !colored_buf || buf_size < 128) {
1873 return 0;
1874 }
1875
1876 static char work_buffer[LOG_MSG_BUFFER_SIZE + 1024];
1877
1878 // Parse format FIRST, regardless of color availability
1879 // Parse format: [TIMESTAMP] [LEVEL] [tid:THREAD_ID] FILE:LINE in FUNC(): MESSAGE
1880 const char *p = plain_line;
1881
1882 // Extract timestamp [TIMESTAMP]
1883 if (*p != '[') {
1884 return 0; // Invalid format
1885 }
1886 p++;
1887 const char *timestamp_start = p;
1888
1889 // Find the closing ] for timestamp - look for pattern that ends with proper timestamp format
1890 // Valid timestamp: HH:MM:SS.UUUUUU (time with microseconds, no date)
1891 while (*p && *p != ']') {
1892 p++;
1893 }
1894 if (*p != ']') {
1895 return 0; // Malformed - no closing bracket
1896 }
1897
1898 size_t timestamp_len = p - timestamp_start;
1899 char timestamp[64];
1900 if (timestamp_len >= sizeof(timestamp) || timestamp_len == 0) {
1901 return 0; // Invalid timestamp - empty or too long
1902 }
1903 SAFE_STRNCPY(timestamp, timestamp_start, timestamp_len);
1904 timestamp[timestamp_len] = '\0';
1905 p++; // Skip ]
1906
1907 // Skip whitespace after timestamp
1908 while (*p && *p == ' ') {
1909 p++;
1910 }
1911
1912 // Extract level [LEVEL]
1913 if (*p != '[') {
1914 return 0; // Missing opening bracket for level
1915 }
1916 p++;
1917 const char *level_start = p;
1918 while (*p && *p != ']') {
1919 p++;
1920 }
1921 if (*p != ']') {
1922 return 0; // Missing closing bracket for level
1923 }
1924 size_t level_len = p - level_start;
1925 char level_str[16];
1926 if (level_len >= sizeof(level_str) || level_len == 0) {
1927 return 0; // Level string too long or empty
1928 }
1929 memcpy(level_str, level_start, level_len);
1930 level_str[level_len] = '\0';
1931
1932 // Determine log level for color selection
1933 log_level_t level = LOG_INFO; // Default
1934 if (strstr(level_str, "DEV") || strstr(level_str, "DEBUG")) {
1935 level = LOG_DEBUG;
1936 } else if (strstr(level_str, "INFO")) {
1937 level = LOG_INFO;
1938 } else if (strstr(level_str, "WARN")) {
1939 level = LOG_WARN;
1940 } else if (strstr(level_str, "ERROR")) {
1941 level = LOG_ERROR;
1942 } else if (strstr(level_str, "FATAL")) {
1943 level = LOG_FATAL;
1944 }
1945
1946 p++; // Skip ]
1947
1948 // Skip whitespace after level
1949 while (*p && *p == ' ') {
1950 p++;
1951 }
1952
1953 // Extract thread ID [tid:THREAD_ID] - optional field
1954 uint64_t tid = 0;
1955 if (*p == '[' && strncmp(p, "[tid:", 5) == 0) {
1956 p += 5;
1957 char *tid_end = NULL;
1958 tid = strtoull(p, &tid_end, 10);
1959 if (!tid_end || *tid_end != ']') {
1960 // tid parsing failed, but continue anyway (might still recover)
1961 // Try to find the closing bracket
1962 while (*p && *p != ']') {
1963 p++;
1964 }
1965 if (*p == ']') {
1966 p++;
1967 }
1968 } else {
1969 p = tid_end + 1; // Skip past the ]
1970 }
1971 // Skip whitespace after tid
1972 while (*p && *p == ' ') {
1973 p++;
1974 }
1975 }
1976 // tid is optional - continue parsing if not present
1977
1978 // Extract file path (everything up to :LINE)
1979 const char *file_start = p;
1980 while (*p && *p != ':') {
1981 p++;
1982 }
1983 if (*p != ':') {
1984 return 0; // Malformed
1985 }
1986 size_t file_len = p - file_start;
1987 char file_path[256];
1988 if (file_len >= sizeof(file_path)) {
1989 return 0;
1990 }
1991 memcpy(file_path, file_start, file_len);
1992 file_path[file_len] = '\0';
1993 p++; // Skip :
1994
1995 // Extract line number (digits only)
1996 int line_num = 0;
1997 const char *line_start = p;
1998 while (*p && isdigit((unsigned char)*p)) {
1999 p++;
2000 }
2001 if (p == line_start) {
2002 return 0; // No line number
2003 }
2004 line_num = (int)strtol(line_start, NULL, 10);
2005
2006 // Skip whitespace and find "in" keyword (be very lenient)
2007 while (*p && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) {
2008 p++;
2009 }
2010
2011 // Try to find "in " - if not found, might still be valid, just harder to parse
2012 if (strncmp(p, "in ", 3) == 0) {
2013 p += 3; // Skip "in "
2014 } else if (*p == 'i' && *(p + 1) == 'n' && (*(p + 2) == ' ' || *(p + 2) == '\t')) {
2015 // Allow tab after "in"
2016 p += 2; // Skip "in"
2017 while (*p && (*p == ' ' || *p == '\t')) {
2018 p++;
2019 }
2020 } else {
2021 // Missing "in" keyword - this is a format error
2022 return 0;
2023 }
2024
2025 // Extract function name (everything up to "()")
2026 const char *func_start = p;
2027 while (*p && *p != '(') {
2028 p++;
2029 }
2030 if (*p != '(' || func_start == p) {
2031 return 0; // Missing function name or parentheses
2032 }
2033 size_t func_len = p - func_start;
2034
2035 // Trim trailing whitespace from function name
2036 while (func_len > 0 && (func_start[func_len - 1] == ' ' || func_start[func_len - 1] == '\t')) {
2037 func_len--;
2038 }
2039
2040 char func_name[256];
2041 if (func_len >= sizeof(func_name)) {
2042 func_len = sizeof(func_name) - 1;
2043 }
2044 if (func_len > 0) {
2045 memcpy(func_name, func_start, func_len);
2046 }
2047 func_name[func_len] = '\0';
2048
2049 // Skip "(" and ")" - be lenient about what's between them
2050 if (*p == '(') {
2051 p++;
2052 while (*p && *p != ')') {
2053 p++;
2054 }
2055 if (*p == ')') {
2056 p++;
2057 }
2058 }
2059
2060 // Skip whitespace and optional colon(s) and other separators
2061 while (*p && (*p == ' ' || *p == ':' || *p == '\t')) {
2062 p++;
2063 }
2064
2065 // Remaining is the message
2066 const char *message = p;
2067
2068 // Format is valid, get colors from logging system
2069 const char **colors = log_get_color_array();
2070 if (!colors) {
2071 // No colors available, return plain text
2072 static bool warned_once = false;
2073 if (!warned_once) {
2074 log_debug("WARNING: log_recolor_plain_entry() called but colors not initialized - returning plain text");
2075 warned_once = true;
2076 }
2077 size_t len = strlen(plain_line);
2078 if (len >= buf_size) {
2079 return 0;
2080 }
2081 SAFE_STRNCPY(colored_buf, plain_line, buf_size - 1);
2082 colored_buf[buf_size - 1] = '\0';
2083 return len;
2084 }
2085
2086 // Build colored output
2087 const char *level_color = colors[level];
2088 const char *reset = colors[LOG_COLOR_RESET];
2089 const char *file_color = colors[1]; // DEBUG/Cyan
2090 const char *line_color = colors[6]; // GREY (matching tid)
2091 const char *func_color = colors[0]; // DEV/Orange
2092 const char *tid_color = colors[6]; // GREY
2093
2094 int len = safe_snprintf(work_buffer, sizeof(work_buffer),
2095 "[%s%s%s] [%s%s%s] [tid:%s%llu%s] %s%s%s:%s%d%s in %s%s%s(): %s", level_color, timestamp,
2096 reset, level_color, level_str, reset, tid_color, (unsigned long long)tid, reset, file_color,
2097 file_path, reset, line_color, line_num, reset, func_color, func_name, reset, message);
2098
2099 if (len <= 0 || len >= (int)sizeof(work_buffer)) {
2100 return 0;
2101 }
2102
2103 // Colorize the message part
2104 const char *colorized_msg = colorize_log_message(message);
2105 len = safe_snprintf(work_buffer, sizeof(work_buffer),
2106 "[%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,
2107 level_color, level_str, reset, tid_color, (unsigned long long)tid, reset, file_color, file_path,
2108 reset, line_color, line_num, reset, func_color, func_name, reset, colorized_msg);
2109
2110 if (len <= 0 || len >= (int)sizeof(work_buffer) || len >= (int)buf_size) {
2111 return 0;
2112 }
2113
2114 SAFE_STRNCPY(colored_buf, work_buffer, buf_size - 1);
2115 colored_buf[buf_size - 1] = '\0';
2116 return (size_t)len;
2117}
char file_path[PLATFORM_MAX_PATH_LENGTH]
Definition mmap.c:39
#define LOG_FATAL
Definition types.h:43
#define LOG_ERROR
Definition types.h:42
#define LOG_WARN
Definition types.h:41

References colorize_log_message(), file_path, LOG_COLOR_RESET, log_debug, LOG_DEBUG, LOG_ERROR, LOG_FATAL, log_get_color_array(), LOG_INFO, LOG_MSG_BUFFER_SIZE, LOG_WARN, safe_snprintf(), and SAFE_STRNCPY.

◆ log_redetect_terminal_capabilities()

void log_redetect_terminal_capabilities ( void  )

#include <log.h>

Re-detect terminal capabilities after logging is initialized.

Useful when terminal capabilities change or need to be refreshed.

Definition at line 1602 of file log/log.c.

1602 {
1603
1604 // Guard against recursion
1605 if (g_log.terminal_caps_detecting) {
1606 return;
1607 }
1608
1609 // Detect if not initialized, or if we're using defaults (not reliably detected)
1610 // This ensures we get proper detection after logging is ready, replacing any defaults
1611 // Once we have reliable detection, never re-detect to keep colors consistent
1612 if (!g_log.terminal_caps_initialized || !g_log.terminal_caps.detection_reliable) {
1613 g_log.terminal_caps_detecting = true;
1614 g_log.terminal_caps = detect_terminal_capabilities();
1615 g_log.terminal_caps_detecting = false;
1616 g_log.terminal_caps_initialized = true;
1617
1618 // Now log the capabilities AFTER colors are set, so this log uses the correct colors
1619 log_debug("Terminal capabilities: color_level=%d, capabilities=0x%x, utf8=%s, fps=%d",
1620 g_log.terminal_caps.color_level, g_log.terminal_caps.capabilities,
1621 g_log.terminal_caps.utf8_support ? "yes" : "no", g_log.terminal_caps.desired_fps);
1622
1623 // Now that we've detected once with reliable results, keep these colors consistent for all future logs
1624 } else {
1625 }
1626 // Once initialized with reliable detection, never re-detect to keep colors consistent
1627}
terminal_capabilities_t detect_terminal_capabilities(void)
Detect terminal capabilities.

References detect_terminal_capabilities(), and log_debug.

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

◆ log_set_color_scheme()

void log_set_color_scheme ( const color_scheme_t *  scheme)

#include <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 1728 of file log/log.c.

1728 {
1729 if (!scheme) {
1730 return;
1731 }
1732
1733 /* Mutex is managed by colors.c - just use it */
1735
1736 /* Detect terminal background */
1738
1739 /* Determine color mode for compilation */
1741 if (g_log.terminal_caps.color_level >= TERM_COLOR_TRUECOLOR) {
1742 mode = TERM_COLOR_TRUECOLOR;
1743 } else if (g_log.terminal_caps.color_level >= TERM_COLOR_256) {
1744 mode = TERM_COLOR_256;
1745 } else {
1746 mode = TERM_COLOR_16;
1747 }
1748
1749 /* Compile the new color scheme */
1750 asciichat_error_t result = colorscheme_compile_scheme(scheme, mode, background, &g_log.compiled_colors);
1751
1752 g_log.log_colorscheme_initialized = true;
1754
1755 /* Log outside of mutex lock to avoid recursive lock deadlock */
1756 if (result != ASCIICHAT_OK) {
1757 log_debug("Failed to compile color scheme: %d", result);
1758 }
1759}

References ASCIICHAT_OK, colorscheme_compile_scheme(), detect_terminal_background(), g_colorscheme_mutex, log_debug, mutex_lock, mutex_unlock, TERM_COLOR_16, TERM_COLOR_256, and TERM_COLOR_TRUECOLOR.

Referenced by main(), and options_init().

◆ log_set_flush_delay()

void log_set_flush_delay ( unsigned int  delay_ms)

#include <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 803 of file log/log.c.

803 {
804 atomic_store_u64(&g_log.flush_delay_ms, delay_ms);
805}

References atomic_store_u64().

◆ log_set_force_stderr()

void log_set_force_stderr ( bool  enabled)

#include <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 721 of file log/log.c.

721 {
722 atomic_store_u64(&g_log.force_stderr, enabled);
723}
bool enabled
Is filtering active?
Definition grep.c:84

References atomic_store_u64(), and enabled.

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

◆ log_set_format()

asciichat_error_t log_set_format ( const char *  format_str,
bool  console_only 
)

#include <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 744 of file log/log.c.

744 {
745 /* Free old format if it exists */
746 if (g_log.format) {
747 log_template_free(g_log.format);
748 g_log.format = NULL;
749 }
750 if (g_log.format_console_only) {
751 log_template_free(g_log.format_console_only);
752 g_log.format_console_only = NULL;
753 }
754
755 /* Use default format if NULL or empty string */
756 const char *format_to_use = (format_str && format_str[0] != '\0') ? format_str : OPT_LOG_TEMPLATE_DEFAULT;
757 bool is_custom = (format_str && format_str[0] != '\0');
758
759 /* Parse the format string (always parse, never skip) */
760 log_template_t *parsed_format = log_template_parse(format_to_use, false);
761
762 if (!parsed_format) {
763 log_error("Failed to parse log format: %s", format_to_use);
764 return SET_ERRNO(ERROR_INVALID_STATE, "Invalid log format string");
765 }
766
767 /* If console_only is true, we also need the default format for file output */
768 if (console_only && is_custom) {
770
771 if (!default_format) {
772 log_template_free(parsed_format);
773 log_error("Failed to parse default log format");
774 return SET_ERRNO(ERROR_INVALID_STATE, "Failed to parse default format");
775 }
776 g_log.format = default_format;
777 g_log.format_console_only = parsed_format;
778 } else {
779 g_log.format = parsed_format;
780 g_log.format_console_only = NULL;
781 }
782
783 atomic_store_u64(&g_log.has_custom_format, is_custom);
784 return ASCIICHAT_OK;
785}
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:321
#define OPT_LOG_TEMPLATE_DEFAULT
Default log template string (selected based on build mode)
Compiled log format ready for use in log_template_apply()
Definition log/format.h:66

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

Referenced by client_init_with_args(), log_init(), main(), and mirror_init_with_args().

◆ log_set_level()

void log_set_level ( log_level_t  level)

#include <log.h>

Set the minimum log level.

Parameters
levelMinimum log level to output

Definition at line 691 of file log/log.c.

691 {
692 atomic_store_u64(&g_log.level, (int)level);
693 atomic_store_bool(&g_log.level_manually_set, true);
694}

References atomic_store_bool(), and atomic_store_u64().

Referenced by main().

◆ log_set_session_log_buffer()

void log_set_session_log_buffer ( session_log_buffer_t *  buf)

#include <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 1838 of file log/log.c.

1838 {
1839 atomic_ptr_store(&g_log.session_log_buffer, (void *)buf);
1840}

References atomic_ptr_store().

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

◆ log_set_terminal_output()

void log_set_terminal_output ( bool  enabled)

#include <log.h>

Control stderr output to terminal.

Parameters
enabledtrue to enable terminal output, false to disable

Definition at line 700 of file log/log.c.

700 {
701 // Respect --quiet flag: if quiet is set, never enable terminal output
702 // But allow disabling even if options are unavailable (for shutdown cleanup)
703
704 // Only check quiet flag when ENABLING output to avoid deadlock with
705 // concurrent logging from other threads (options_get() can deadlock when
706 // called while other threads are actively logging due to RCU synchronization)
707 if (enabled) {
708 const options_t *opts = options_get();
709 if (opts && opts->quiet) {
710 return; // Silently ignore attempts to enable terminal output when --quiet is set
711 }
712 }
713
714 atomic_store_u64(&g_log.terminal_output_enabled, enabled);
715}
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:496
Consolidated options structure.
bool quiet
Quiet mode (suppress logs)

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

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

◆ log_shutdown_begin()

void log_shutdown_begin ( void  )

#include <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 1795 of file log/log.c.

1795 {
1796 if (g_log.shutdown_in_progress) {
1797 return; /* Already in shutdown phase */
1798 }
1799
1800 /* Save current terminal output state and disable console output */
1801 g_log.shutdown_saved_terminal_output = atomic_load_u64(&g_log.terminal_output_enabled);
1802 atomic_store_bool(&g_log.terminal_output_enabled, false);
1803 g_log.shutdown_in_progress = true;
1804}

References atomic_load_u64(), and atomic_store_bool().

Referenced by asciichat_shared_destroy().

◆ log_shutdown_end()

void log_shutdown_end ( void  )

#include <log.h>

End shutdown phase - restore previous logging settings.

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

Definition at line 1806 of file log/log.c.

1806 {
1807 if (!g_log.shutdown_in_progress) {
1808 return; /* Not in shutdown phase, skip terminal state restoration */
1809 }
1810
1811 /* Restore previous terminal output state */
1812 atomic_store_u64(&g_log.terminal_output_enabled, g_log.shutdown_saved_terminal_output);
1813 g_log.shutdown_in_progress = false;
1814}

References atomic_store_u64().

Referenced by asciichat_shared_destroy().

◆ log_system_destroy()

void log_system_destroy ( void  )

#include <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 686 of file log/log.c.

686 {
687 // Unregister session log buffer if registered
689}
void log_clear_session_log_buffer(void)
Unregister the session log buffer.
Definition log/log.c:1847

References log_clear_session_log_buffer().

Referenced by asciichat_shared_destroy().

◆ log_system_init()

void log_system_init ( void  )

#include <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 669 of file log/log.c.

669 {
670
671 // Detect terminal capabilities if not already done
673
674 // Initialize color scheme based on terminal capabilities
676}

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 <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];
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:491

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

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

References asciichat_thread_to_key(), colored_string(), colorize_log_message(), extract_project_relative_path(), get_level_string_padded(), log_format_spec_t::literal, log_format_spec_t::literal_len, log_debug, LOG_FORMAT_COLOR, LOG_FORMAT_COLORED_MESSAGE, LOG_FORMAT_COLORLOG_LEVEL, LOG_FORMAT_FILE, LOG_FORMAT_FILE_RELATIVE, LOG_FORMAT_FUNC, LOG_FORMAT_LEVEL, LOG_FORMAT_LEVEL_ALIGNED, LOG_FORMAT_LINE, LOG_FORMAT_LITERAL, LOG_FORMAT_MESSAGE, LOG_FORMAT_MICROSECONDS, LOG_FORMAT_NANOSECONDS, LOG_FORMAT_NEWLINE, LOG_FORMAT_STRFTIME_CODE, LOG_FORMAT_TID, LOG_FORMAT_TIME, LOG_FORMAT_TNAME, log_named_format_or_original(), named_get(), NS_PER_SEC_INT, safe_snprintf(), log_template_t::spec_count, log_template_t::specs, time_format_now(), and log_format_spec_t::type.

Referenced by backtrace_print().

◆ log_template_free()

void log_template_free ( log_template_t *  format)

#include <format.h>

Free compiled format structure.

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

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

325 {
326 if (!format) {
327 SET_ERRNO(ERROR_INVALID_PARAM, "null format");
328 return;
329 }
330
331 if (format->specs) {
332 for (size_t i = 0; i < format->spec_count; i++) {
333 if (format->specs[i].literal) {
334 free(format->specs[i].literal);
335 format->specs[i].literal = NULL;
336 }
337 }
338 free(format->specs);
339 format->specs = NULL;
340 }
341
342 if (format->original) {
343 free(format->original);
344 format->original = NULL;
345 }
346
347 free(format);
348 format = NULL;
349}
char * original
Definition log/format.h:69

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 <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 321 of file log/format.c.

321 {
322 return parse_format_string(format_str, console_only);
323}

Referenced by log_set_format().

◆ log_terminal_msg()

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

#include <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 1194 of file log/log.c.

1194 {
1195 // Lock-free: only uses atomic loads, no mutex
1196 if (!lifecycle_is_initialized(&g_log.lifecycle)) {
1197 return;
1198 }
1199
1200 if (level < (log_level_t)atomic_load_u64(&g_log.level)) {
1201 return;
1202 }
1203
1204 // Terminal output only - no file/mmap writing
1205 char time_buf[LOG_TIMESTAMP_BUFFER_SIZE];
1206 uint64_t time_ns = time_get_realtime_ns();
1208
1209 va_list args;
1210 va_start(args, fmt);
1211 write_to_terminal_atomic(level, time_buf, file, line, func, fmt, args, time_ns);
1212 va_end(args);
1213}

References args, atomic_load_u64(), get_current_time_formatted(), lifecycle_is_initialized(), LOG_TIMESTAMP_BUFFER_SIZE, and time_get_realtime_ns().

◆ log_truncate_if_large()

void log_truncate_if_large ( void  )

#include <log.h>

Manually truncate large log files.

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

Definition at line 807 of file log/log.c.

807 {
808 // Log rotation is inherently racy without locks - best-effort only
809 // For reliable rotation, use mmap mode or external logrotate
810 int file = atomic_load_int(&g_log.file);
811 if (file >= 0 && file != STDERR_FILENO && strlen(g_log.filename) > 0) {
812 struct stat st;
813 if (fstat(file, &st) == 0 && st.st_size > MAX_LOG_SIZE) {
814 atomic_store_u64(&g_log.current_size, (size_t)st.st_size);
815 }
816 }
817}
#define MAX_LOG_SIZE
Maximum log file size in bytes (3MB) before rotation.
Definition log/log.h:72

References atomic_load_int(), atomic_store_u64(), and MAX_LOG_SIZE.

Referenced by main().

◆ log_unlock_terminal()

void log_unlock_terminal ( bool  previous_state)

#include <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 796 of file log/log.c.

796 {
797 atomic_store_u64(&g_log.terminal_locked, previous_state);
798 if (!previous_state) {
799 atomic_store_u64(&g_log.terminal_owner_thread, 0);
800 }
801}

References atomic_store_u64().

Referenced by client_crypto_handshake(), prompt_password(), prompt_unknown_host(), and ui_mdns_select().