Common definitions, error handling, logging system, and configuration.
Core error handling, memory management, logging, and type definitions shared throughout the ascii-chat application.
Overview
The common module (lib/common.h, lib/common.c) provides fundamental infrastructure:
- Comprehensive error handling with context tracking
- Memory allocation with leak detection in debug builds
- Rate-limited logging for high-frequency code paths
- Platform-independent type definitions
- Shared constants and macros
All application code should use these common definitions to ensure consistency, proper error handling, and debuggability.
Error Handling System
ascii-chat uses a comprehensive error system (lib/asciichat_errno.h) that provides:
- Specific error codes for different failure modes
- Contextual error messages with file/line/function information
- System error integration (errno on POSIX, GetLastError on Windows)
- Thread-local error state (thread-safe)
- Stack traces in debug builds
Error Code Enumeration
typedef enum {
ASCIICHAT_OK = 0,
ERROR_GENERAL = 1,
ERROR_USAGE = 2,
ERROR_MEMORY = 3,
ERROR_CONFIG = 4,
ERROR_CRYPTO_INIT = 5,
ERROR_LOGGING_INIT = 6,
ERROR_PLATFORM_INIT = 7,
ERROR_WEBCAM = 20,
ERROR_WEBCAM_IN_USE = 21,
ERROR_WEBCAM_PERMISSION = 22,
ERROR_AUDIO = 23,
ERROR_AUDIO_IN_USE = 24,
ERROR_TERMINAL = 25,
ERROR_NETWORK = 40,
ERROR_NETWORK_BIND = 41,
ERROR_NETWORK_CONNECT = 42,
ERROR_NETWORK_TIMEOUT = 43,
ERROR_NETWORK_PROTOCOL = 44,
ERROR_NETWORK_SIZE = 45,
ERROR_CRYPTO = 60,
ERROR_CRYPTO_KEY = 61,
ERROR_CRYPTO_AUTH = 62,
ERROR_CRYPTO_HANDSHAKE = 63,
ERROR_CRYPTO_VERIFICATION = 64,
ERROR_THREAD = 80,
ERROR_BUFFER = 81,
ERROR_BUFFER_FULL = 82,
ERROR_BUFFER_OVERFLOW = 83,
ERROR_DISPLAY = 84,
ERROR_INVALID_STATE = 85,
ERROR_INVALID_PARAM = 86,
ERROR_INVALID_FRAME = 87,
ERROR_RESOURCE_EXHAUSTED = 88,
ERROR_FORMAT = 89,
ERROR_STRING = 90,
ERROR_SIGNAL_INTERRUPT = 100,
ERROR_SIGNAL_CRASH = 101,
ERROR_ASSERTION_FAILED = 102,
ERROR_COMPRESSION = 103,
ERROR_DECOMPRESSION = 104,
} asciichat_error_t;
Error Context
Each error includes rich contextual information:
typedef struct {
asciichat_error_t code;
const char *file;
int line;
const char *function;
char *context_message;
uint64_t timestamp;
int system_errno;
int wsa_error;
void *backtrace[32];
char **backtrace_symbols;
int stack_depth;
bool has_system_error;
bool has_wsa_error;
} asciichat_error_context_t;
Error Handling Macros
SET_ERRNO: Set error with custom message
asciichat_error_t open_file(const char *path) {
if (!path) {
return SET_ERRNO(ERROR_INVALID_PARAM, "File path is NULL");
}
int fd = open(path, O_RDONLY);
if (fd < 0) {
return SET_ERRNO_SYS(ERROR_NOT_FOUND, "Failed to open file: %s", path);
}
return ASCIICHAT_OK;
}
SET_ERRNO_SYS: Set error with system errno
asciichat_error_t create_socket(void) {
socket_t sockfd = socket(AF_INET6, SOCK_STREAM, 0);
if (sockfd == INVALID_SOCKET_VALUE) {
return SET_ERRNO_SYS(ERROR_NETWORK_SOCKET,
"socket() failed (errno=%d)", errno);
}
return ASCIICHAT_OK;
}
HAS_ERRNO: Check if error occurred
asciichat_error_context_t err_ctx;
asciichat_error_t result = risky_operation();
if (HAS_ERRNO(&err_ctx)) {
log_error("Operation failed: %s (code=%d, file=%s:%d)",
err_ctx.context_message, err_ctx.error_code,
err_ctx.file, err_ctx.line);
return result;
}
PRINT_ERRNO_CONTEXT: Debug print error details
if (HAS_ERRNO(&err_ctx)) {
PRINT_ERRNO_CONTEXT(&err_ctx);
}
CLEAR_ERRNO: Clear error state
GET_ERRNO: Get current error code
asciichat_error_t current_error = GET_ERRNO();
if (current_error != ASCIICHAT_OK) {
}
Memory Management
Custom memory allocation macros with automatic leak detection in debug builds:
Memory Allocation Macros
SAFE_MALLOC: Allocate memory with leak tracking
uint8_t *buffer = SAFE_MALLOC(1024, uint8_t*);
if (!buffer) {
return SET_ERRNO(ERROR_MEMORY, "Failed to allocate 1024 bytes");
}
SAFE_CALLOC: Allocate zeroed memory
int *array = SAFE_CALLOC(100, sizeof(int), int*);
if (!array) {
return ERROR_MEMORY;
}
SAFE_REALLOC: Reallocate memory
uint8_t *new_buffer = SAFE_REALLOC(buffer, 2048, uint8_t*);
if (!new_buffer) {
SAFE_FREE(buffer);
return ERROR_MEMORY;
}
buffer = new_buffer;
SAFE_FREE: Free memory and set to NULL
Debug Memory Tracking
When CMAKE_BUILD_TYPE=Debug and DEBUG_MEMORY is defined:
Leak Detection:
- All SAFE_MALLOC/CALLOC/REALLOC calls are recorded
- Each allocation stores: address, size, file, line, timestamp
- On program exit, reports any allocations not freed
- Example output:
Memory leak detected:
Address: 0x7f1234567890
Size: 1024 bytes
Allocated at: src/client.c:123 in process_frame()
Thread: 0x7f123456
Total leaked: 1024 bytes in 1 allocation
Allocation Logging:
Double-Free Detection:
- Freeing already-freed memory triggers assertion
- Prevents use-after-free bugs
Memory Reports:
void asciichat_common_print_memory_stats(void);
Logging System
Multi-level logging with rate limiting for high-frequency code:
Log Levels
typedef enum {
LOG_DEBUG = 0,
LOG_INFO = 1,
LOG_WARN = 2,
LOG_ERROR = 3,
LOG_FATAL = 4,
} log_level_t;
Control via environment:
export LOG_LEVEL=0 # DEBUG (all messages)
export LOG_LEVEL=1 # INFO and above
export LOG_LEVEL=2 # WARN and above
export LOG_LEVEL=3 # ERROR and above
export LOG_LEVEL=4 # FATAL only
Standard Logging Macros
log_debug("Frame captured: %dx%d", width, height);
log_info("Client connected from %s:%d", ip, port);
log_warn("Audio buffer underrun detected");
log_error("Failed to send packet: %s", strerror(errno));
log_fatal("Critical error, terminating");
Output format:
[2025-01-15 14:32:45.123] [DEBUG] Frame captured: 1920x1080
[2025-01-15 14:32:45.456] [INFO] Client connected from 192.168.1.100:12345
[2025-01-15 14:32:45.789] [WARN] Audio buffer underrun detected
[2025-01-15 14:32:46.012] [ERROR] Failed to send packet: Broken pipe
[2025-01-15 14:32:46.345] [FATAL] Critical error, terminating
Rate-Limited Logging
For high-frequency code paths (e.g., video/audio processing at 60+ FPS):
log_debug_every(1000000, "Processing frame %d", frame_count);
log_info_every(5000000, "Client status: %d connected", num_clients);
log_warn_every(10000000, "High CPU usage: %.1f%%", cpu_usage);
Implementation:
- Uses thread-local timestamp tracking
- First call always logs
- Subsequent calls within interval are suppressed
- Different log sites have independent rate limiting
Example (video processing loop at 60 FPS):
while (running) {
capture_frame();
log_debug_every(1000000, "Frame captured (count=%d)", frame_count);
frame_count++;
}
Logging Configuration
asciichat_error_t
log_init(
const char *log_file, log_level_t level);
void log_set_color(bool enable);
void log_set_level(log_level_t level)
void log_init(const char *filename, log_level_t level, bool force_stderr, bool use_mmap)
Usage:
log_init(
"/tmp/ascii-chat.log", LOG_INFO);
Common Type Definitions
Fixed-width integers (platform-independent):
#include <stdint.h>
int8_t, uint8_t
int16_t, uint16_t
int32_t, uint32_t
int64_t, uint64_t
size_t
ssize_t
Boolean (C99 style):
NULL pointer:
Common Constants
#define MAX_PACKET_SIZE 65536
#define MAX_PATH_LEN 4096
#define MAX_CLIENTS 9
#define DEFAULT_PORT 27224
#define PACKET_MAGIC 0xDEADBEEF
#define DEFAULT_WIDTH 80
#define DEFAULT_HEIGHT 24
#define DEFAULT_FPS 30
#define SAMPLE_RATE 48000
#define AUDIO_CHANNELS 1
#define FRAMES_PER_BUFFER 512
Usage Examples
Comprehensive Error Handling
asciichat_error_t initialize_system(const char *config_file) {
asciichat_error_t err;
options_t opts;
err = config_load(&opts, config_file);
if (err != ASCIICHAT_OK) {
asciichat_error_context_t ctx;
if (HAS_ERRNO(&ctx)) {
log_error("Config load failed: %s", ctx.context_message);
PRINT_ERRNO_CONTEXT(&ctx);
}
return err;
}
crypto_context_t crypto_ctx;
if (err != ASCIICHAT_OK) {
options_destroy(&opts);
return err;
}
audio_context_t audio_ctx;
if (err != ASCIICHAT_OK) {
options_destroy(&opts);
return err;
}
return ASCIICHAT_OK;
}
asciichat_error_t audio_init(audio_context_t *ctx)
crypto_result_t crypto_init(crypto_context_t *ctx)
void crypto_destroy(crypto_context_t *ctx)
Memory Management with Leak Detection
void process_data(void) {
uint8_t *buffer1 = SAFE_MALLOC(1024, uint8_t*);
uint8_t *buffer2 = SAFE_CALLOC(512, sizeof(uint8_t), uint8_t*);
if (!buffer1 || !buffer2) {
log_error("Memory allocation failed");
SAFE_FREE(buffer1);
SAFE_FREE(buffer2);
return;
}
if (need_more_space) {
uint8_t *new_buffer = SAFE_REALLOC(buffer1, 2048, uint8_t*);
if (!new_buffer) {
log_error("Reallocation failed");
SAFE_FREE(buffer1);
SAFE_FREE(buffer2);
return;
}
buffer1 = new_buffer;
}
SAFE_FREE(buffer1);
SAFE_FREE(buffer2);
}
Rate-Limited Logging in Video Loop
void video_processing_loop(void) {
uint64_t frame_count = 0;
uint64_t dropped_frames = 0;
while (running) {
uint8_t *frame_data;
asciichat_error_t err = webcam_capture_frame(webcam, &frame_data, &frame_len);
if (err != ASCIICHAT_OK) {
dropped_frames++;
log_warn_every(1000000, "Frame capture failed (dropped %lu frames)",
dropped_frames);
continue;
}
frame_count++;
log_info_every(5000000, "Processed %lu frames (%.1f FPS)",
frame_count, calculate_fps());
}
}
- See also
- common.h
-
asciichat_errno.h
-
logging.h
Command-line argument parsing and TOML-based configuration file management for flexible server and client setup.
Overview
ascii-chat supports two configuration methods:
- Command-line arguments: Quick configuration for ad-hoc usage
- TOML configuration files: Persistent, structured configuration
Command-line arguments override configuration file settings, allowing:
- Default configuration in
~/.config/ascii-chat/config.toml
- Per-session overrides via command-line flags
- Easy scripting and automation
TOML Configuration Files
ascii-chat uses TOML (Tom's Obvious, Minimal Language) for configuration files, parsed by the tomlc17 library.
File Format
Complete example configuration:
# ascii-chat Configuration File
# Location: ~/.config/ascii-chat/config.toml
[server]
# Bind address ("::" for all IPv6/IPv4, "0.0.0.0" for IPv4 only)
address = "::"
# Server port (default: 27224)
port = 27224
# Maximum simultaneous clients (1-9)
max_clients = 9
# Server identity key (SSH Ed25519 format)
key_file = "~/.ssh/ascii-chat-server"
# Authorized client public keys (SSH format or github:username)
# Leave empty to allow all clients (less secure)
client_keys = [
"github:alice",
"github:bob",
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... charlie@example.com"
]
[client]
# Server address to connect to
address = "localhost"
# Server port
port = 27224
# Expected server public key (for TOFU verification)
# Leave empty to accept any server (prompts for confirmation)
server_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."
# Client identity key
key_file = "~/.ssh/id_ed25519"
# Connection timeout (seconds)
timeout = 10
[video]
# Terminal width (auto-detected if not specified)
width = 160
# Terminal height (auto-detected if not specified)
height = 45
# Enable color output (true/false)
color = true
# Use half-block characters for 2x vertical resolution
half_blocks = true
# ASCII palette (characters from dark to bright)
palette = " .:-=+*#%@"
# Webcam device index (0 = first camera)
webcam_device = 0
# Capture resolution (before scaling to terminal size)
capture_width = 1920
capture_height = 1080
# Target framerate
fps = 30
[audio]
# Enable audio capture/playback
enabled = true
# Sample rate (Hz)
sample_rate = 48000
# Audio channels (1=mono, 2=stereo)
channels = 1
# PortAudio buffer size (frames)
buffer_size = 512
# Audio input device (-1 = default)
input_device = -1
# Audio output device (-1 = default)
output_device = -1
[logging]
# Log file path (empty = stderr only)
file = "/tmp/ascii-chat.log"
# Log level (debug, info, warn, error, fatal)
level = "info"
# Enable color in log output
color = true
Configuration Sections
Section Names: The section names in the TOML file ([server], [client], [video], [audio], [logging]) correspond directly to the option categories displayed in ascii-chat --help and the man page. You can find the complete list of available sections by running ascii-chat --help and looking at the category labels for each option.
Key Names: Configuration keys are the long-form command-line option names with dashes converted to underscores. For example:
--color-mode flag becomes color_mode key in config
--max-clients flag becomes max_clients key in config
--server-key flag becomes server_key key in config
To find the correct key name for any configuration option, take the long-form flag name (displayed in --help output), remove the leading dashes, and replace all dashes with underscores.
Conflicting Options: Some configuration options conflict with each other and will cause the configuration file to fail loading if set simultaneously. For example:
--file and --url cannot both be set (URL takes priority)
--loop cannot be used with --url (network streams cannot be looped)
--seek requires either --file or --url
If your configuration file fails to load with validation errors, check the error message for conflicting options and remove one of them.
Loading Configuration
Load Config File:
#include "config.h"
options_t opts;
asciichat_error_t err = config_load(&opts, "~/.config/ascii-chat/config.toml");
if (err != ASCIICHAT_OK) {
log_error("Failed to load config file");
return err;
}
log_info("Server address: %s:%d", opts.address, opts.port);
log_info("Max clients: %d", opts.max_clients);
log_info("Video: %dx%d @ %d FPS", opts.width, opts.height, opts.fps);
options_destroy(&opts);
WASM option accessor declarations.
Apply Defaults:
options_t opts = {0};
apply_default_options(&opts);
Command-Line Options
Server Options
# Start server with defaults
./ascii-chat server
# Bind to specific address and port
./ascii-chat server :: --port 27224
# Bind to IPv4 only
./ascii-chat server 0.0.0.0 --port 27224
# Use server identity key
./ascii-chat server --key ~/.ssh/ascii-chat-server
# Authorize specific clients
./ascii-chat server --client-keys github:alice,github:bob
# Limit maximum clients
./ascii-chat server --max-clients 4
# Enable color and set log file
./ascii-chat server --color --log-file /tmp/server.log
# Use configuration file
./ascii-chat --config ~/.config/ascii-chat/server.toml server
Server-specific flags:
[address1] [address2] (positional): Bind addresses, 0-2 IPv4/IPv6 addresses (default: 127.0.0.1 and ::1)
--port, -p PORT: Listen port (default: 27224)
--key KEY_FILE: Server identity key (SSH Ed25519)
--client-keys KEYS: Comma-separated authorized client keys
--max-clients N: Maximum simultaneous clients (1-9, default: 9)
Client Options
# Connect to localhost (default)
./ascii-chat client
# Connect to specific server
./ascii-chat client example.com:27224
# Specify terminal dimensions
./ascii-chat client --width 160 --height 45
# Enable color and half-blocks
./ascii-chat client --color --half-blocks
# Use custom ASCII palette
./ascii-chat client --palette " .:-=+*#%@"
# Enable audio
./ascii-chat client --audio
# Verify server identity
./ascii-chat client --server-key ~/.ssh/server_key.pub
# Use client identity key
./ascii-chat client --key ~/.ssh/id_ed25519
# Snapshot mode (capture and exit)
./ascii-chat client --snapshot --snapshot-delay 10
# Use configuration file
./ascii-chat --config ~/.config/ascii-chat/client.toml client example.com
Client-specific flags:
[address][:port] (positional): Server address with optional port (default: localhost:27224)
--port, -p PORT: Server port (default: 27224) - conflicts with port in positional argument
--width, -w WIDTH: Terminal width (default: auto-detect)
--height, -h HEIGHT: Terminal height (default: auto-detect)
--color, -c: Enable color output
--half-blocks: Use half-block characters (2x vertical resolution)
--palette, -P PALETTE: ASCII palette type (standard, blocks, digital, minimal, cool, custom)
--palette-chars, -C CHARS: Custom palette characters string
--audio, -A: Enable audio capture/playback
--server-key KEY: Expected server public key (TOFU)
--key KEY_FILE: Client identity key
--snapshot: Snapshot mode (capture once and exit)
--snapshot-delay SECONDS: Snapshot duration before exit
Common Options
Available for both server and client:
--config FILE: Configuration file path
- –log-file FILE: Log file path
- –help, -?: Show help message
- –version, -v: Show version information
Default Config Locations
All ascii-chat data files (config.toml, known_hosts) are stored in a single directory:
Linux/macOS:
$XDG_CONFIG_HOME/ascii-chat/ (if XDG_CONFIG_HOME is set)
~/.ascii-chat/ (default when XDG_CONFIG_HOME is not set)
Windows:
APPDATA%\ascii-chat\ (if APPDATA is set)
~\.ascii-chat\ (default when APPDATA is not set)
Creating a Default Config:
# Create config at default location
./ascii-chat --config-create
# Create config at custom location
./ascii-chat --config-create ~/.my-config.toml
# Overwrite existing config (scripted - supports piped input)
echo "y" | ./ascii-chat --config-create
yes | ./ascii-chat --config-create /tmp/server.toml
Using a Config File:
./ascii-chat --config ~/.ascii-chat/config.toml server
./ascii-chat --config ~/.ascii-chat/config.toml client example.com
Overriding Config with CLI:
# Config file sets port=27224, but override it with --port
./ascii-chat --config ~/.ascii-chat/config.toml client --port 8080
Configuration Precedence
Settings are applied in this order (later overrides earlier):
- Built-in defaults - Hardcoded default values
- Config file - Values from TOML configuration (if specified with –config)
- Environment variables - LOG_LEVEL, etc.
- Command-line arguments - Highest priority
Example:
# Config file has port=27224
# Environment has LOG_LEVEL=1
# Command line has --port 8080
# Result: port=8080 (CLI wins), log_level=1 (from env)
Configuration Validation
The configuration system automatically validates all settings:
Automatic Validation:
- Port range validation (1-65535)
- Webcam index validation
- File path validation (key files, config files)
- Log level validation (0-4)
- IP address format validation
- Dimensions validation (width: 20-500, height: 10-200)
- Client limits (max_clients: 1-9, grid layout constraint)
- Tilde expansion for home directory (~/)
- SSH keys must be Ed25519 format
- Palette strings (must have at least 2 characters, ordered dark → bright)
- UTF-8 validation for multi-byte characters
Error Reporting:
asciichat_error_t err = config_load(&opts, "~/.ascii-chat/config.toml");
if (err != ASCIICHAT_OK) {
asciichat_error_context_t ctx;
if (HAS_ERRNO(&ctx)) {
log_error("Invalid configuration: %s", ctx.context_message);
}
return err;
}
Example validation code:
asciichat_error_t validate_config(const options_t *opts) {
if (opts->port < 1 || opts->port > 65535) {
return SET_ERRNO(ERROR_CONFIG_INVALID,
"Port must be 1-65535 (got %d)", opts->port);
}
if (opts->max_clients < 1 || opts->max_clients > 9) {
return SET_ERRNO(ERROR_CONFIG_INVALID,
"max_clients must be 1-9 (got %d)", opts->max_clients);
}
if (opts->palette && strlen(opts->palette) < 2) {
return SET_ERRNO(ERROR_CONFIG_INVALID,
"Palette must have at least 2 characters");
}
return ASCIICHAT_OK;
}
Usage Examples
Parsing Command-Line and Config File
int main(
int argc,
char **argv) {
options_t opts;
asciichat_error_t err;
err = options_parse(&opts, argc, argv);
if (err != ASCIICHAT_OK) {
fprintf(stderr, "Usage: %s [server|client] [OPTIONS]\n", argv[0]);
return 1;
}
if (opts.config_file) {
err = config_load(&opts, opts.config_file);
if (err != ASCIICHAT_OK) {
log_error("Failed to load config: %s", opts.config_file);
options_destroy(&opts);
return 1;
}
}
err = validate_config(&opts);
if (err != ASCIICHAT_OK) {
asciichat_error_context_t ctx;
if (HAS_ERRNO(&ctx)) {
log_error("Invalid configuration: %s", ctx.context_message);
}
options_destroy(&opts);
return 1;
}
if (opts.mode == MODE_SERVER) {
run_server(&opts);
} else {
run_client(&opts);
}
options_destroy(&opts);
return 0;
}
int main(int argc, char *argv[])
Setting Defaults
void options_set_defaults(options_t *opts) {
memset(opts, 0, sizeof(*opts));
opts->port = DEFAULT_PORT;
opts->address = strdup("::");
opts->width = DEFAULT_WIDTH;
opts->height = DEFAULT_HEIGHT;
opts->fps = DEFAULT_FPS;
opts->color = true;
opts->half_blocks = false;
opts->palette = strdup(" .:-=+*#%@");
opts->audio = false;
opts->sample_rate = SAMPLE_RATE;
opts->audio_channels = AUDIO_CHANNELS;
opts->log_level = LOG_INFO;
opts->log_color = true;
}
- See also
- Options for command-line argument parsing
-
options.h
-
config.h
Overview
Welcome to the logging system! If you've ever tried to debug a complex program without good logging, you know the pain. The logging system is your debugging best friend—it helps you understand what's happening in your application, track down bugs, and monitor behavior in production.
Think of logging as your application's diary. It writes down everything that happens: when clients connect, when errors occur, when packets are processed. You can configure how chatty it should be (from super verbose DEBUG mode to quiet ERROR-only mode), and you can send the output to the console, to a file, or both!
Implementation: lib/logging.h
What makes this logging system helpful?
- Six log levels: DEV, DEBUG, INFO, WARN, ERROR, FATAL (choose your verbosity)
- Rate limiting: Prevents log spam in tight loops (your disk will thank you)
- Flexible output: Console, file, or both simultaneously
- Thread-safe: Multiple threads can log without stepping on each other
- Context tracking: Automatic timestamps and source location (file:line)
- Pretty colors: Colored console output so errors jump out at you
- Runtime configuration: Change log level on the fly via environment variable
Log Levels
The logging system has six levels, from most verbose to least:
DEV (0) - The super chatty one:
- Use this for development-only messages that are too verbose for normal debugging
- Useful for tracing code paths during active development
- Usually disabled even in debug builds unless you need extreme detail
DEBUG (1) - The chatty one:
- Use this when you need detailed diagnostic information
- Only available in debug builds (stripped out in release for performance)
- Perfect for understanding exactly what's happening step by step
INFO (2) - The reporter:
- General informational messages about normal operations
- "Client connected", "Frame processed", that sort of thing
- What you'd want to see during normal operation
WARN (3) - The concerned citizen:
- Something's not quite right, but we can keep going
- "Buffer is getting full", "Retrying connection"
- Pay attention, but don't panic
ERROR (4) - Houston, we have a problem:
- Operation failures that are recoverable
- "Failed to send packet", "Client disconnected unexpectedly"
- Things went wrong, but we're handling it
FATAL (5) - The showstopper:
- Unrecoverable errors that mean we can't continue
- "Cannot initialize crypto", "Out of memory"
- Time to gracefully shut down
Basic Usage
Basic Logging
Log Messages:
log_debug("Processing frame %d", frame_number);
log_info("Client connected: %s:%d", ip, port);
log_warn("Buffer nearly full: %zu/%zu", used, capacity);
log_error("Failed to send packet: %s", error_msg);
log_fatal("Cannot initialize crypto: %s", reason);
Format Specifiers:
log_info("Integer: %d", 42);
log_info("String: %s", "hello");
log_info("Pointer: %p", ptr);
log_info("Size: %zu bytes", size);
log_info("Hex: 0x%08x", value);
Rate-Limited Logging
Rate Limiting (prevents log spam in tight loops):
for (int i = 0; i < 1000000; i++) {
log_debug_every(1000000, "Processing item %d", i);
}
while (video_running) {
log_info_every(5000000, "Video frames processed: %d", frame_count);
process_frame();
}
Available Rate-Limited Macros:
- log_debug_every(usec, ...)
- log_info_every(usec, ...)
- log_warn_every(usec, ...)
- log_error_every(usec, ...)
Configuration
Setting Log Level
Runtime Configuration:
set_log_level(LOG_LEVEL_DEBUG);
set_log_level(LOG_LEVEL_INFO);
set_log_level(LOG_LEVEL_ERROR);
Environment Variable:
# Set log level via environment
export LOG_LEVEL=0 # DEV
export LOG_LEVEL=1 # DEBUG
export LOG_LEVEL=2 # INFO
export LOG_LEVEL=3 # WARN
export LOG_LEVEL=4 # ERROR
export LOG_LEVEL=5 # FATAL
# Run with debug logging
LOG_LEVEL=1 ./bin/ascii-chat server
File Output
Configure File Logging:
set_log_file("/tmp/ascii-chat-server.log");
log_info("This appears in both console and file");
close_log_file();
Command-line Option:
# Log to file via command line
./bin/ascii-chat server --log-file=/tmp/server.log
./bin/ascii-chat client --log-file=/tmp/client.log
Output Format
Log messages have a consistent format that makes them easy to read and understand. Every message includes a timestamp, level, source location, and the actual message text. Let's look at the format:
What does a log message look like?
Log Message Format:
[TIMESTAMP] LEVEL [file:line] message
The format is simple and consistent—timestamp first (so you can see when things happened), level next (so you know how important it is), source location (so you know where in the code it came from), and the message itself.
Example Output:
[2025-01-15 14:23:45] DEBUG [server.c:234] Starting video capture thread
[2025-01-15 14:23:45] INFO [network.c:456] Client connected: 192.168.1.100:54321
[2025-01-15 14:23:46] WARN [audio.c:123] Audio buffer 80% full
[2025-01-15 14:23:47] ERROR [crypto.c:789] Handshake failed: invalid signature
Notice how you can see exactly when things happened, what level they are, where in the code they came from, and what the message is. This makes debugging much easier!
What about colors?
Console Colors (ANSI terminals):
- DEV: Dim Gray (even more subtle, for development tracing)
- DEBUG: Gray (subtle, so it doesn't distract from important messages)
- INFO: White (default color, for normal messages)
- WARN: Yellow (gets your attention, but not too alarming)
- ERROR: Red (you should pay attention to this!)
- FATAL: Bold Red (this is serious!)
Colors make it easy to scan logs—errors jump out in red, warnings in yellow. If you're in a terminal that supports colors, this makes debugging much easier.
Thread Safety
Mutex Protection:
- All logging operations are protected by a global mutex
- Safe for concurrent logging from multiple threads
- No output interleaving
Thread Identification:
- Thread ID automatically captured (debug builds)
- Helps trace multi-threaded execution
Example Multi-threaded Logging:
void* worker_thread(void *arg) {
log_info("Worker thread started");
for (int i = 0; i < 1000; i++) {
log_debug_every(1000000, "Processing item %d", i);
}
log_info("Worker thread finished");
return NULL;
}
Performance Considerations
Logging performance is important—you don't want logging to slow down your application. We've designed the system to be efficient, but there are some things to keep in mind:
How do we keep logging fast?
Rate Limiting:
- Use log_*_every() in high-frequency code paths: If you're logging in a tight loop, use rate limiting so you don't flood the logs
- Prevents log spam and performance degradation: Without rate limiting, logging can slow down your application significantly
- Minimal overhead when rate limit is active: Once the rate limit is active, logging is essentially free (we just check if enough time has passed, then skip logging)
Rate limiting is your friend in tight loops—you can log every iteration without actually logging every iteration!
How about file I/O?
File I/O:
- File logging adds I/O overhead: Writing to a file is slower than writing to console, but it's still pretty fast for most use cases
- Consider async file writing for high-throughput: If you're logging a lot (thousands of messages per second), you might want async file writing
- Use buffered output for better performance: We use buffered I/O, so multiple log messages can be written in one operation
File logging is pretty fast—we use buffered I/O, so most of the time you won't notice the overhead. If you're logging hundreds of messages per second, you might want to consider rate limiting or async logging.
What about string formatting?
String Formatting:
- Avoid expensive formatting in hot paths: String formatting (like
sprintf()) can be slow if you're doing it thousands of times per second
- Use rate limiting for frequent messages: If you're logging frequently, rate limiting reduces the number of format operations
- Consider conditional compilation for debug logs: Debug logs can be compiled out entirely in release builds, so they have zero overhead
String formatting is usually pretty fast, but if you're doing it millions of times per second, it can add up. Use rate limiting for frequent messages, and remember that debug logs are compiled out in release builds!
Best Practices
DO:
- Use appropriate log levels
- Use rate limiting in tight loops
- Include relevant context (IDs, values)
- Use structured messages
- Log errors with error codes
DON'T:
- Don't use printf() directly (use log_*() instead)
- Don't log in tight loops without rate limiting
- Don't log sensitive data (passwords, keys)
- Don't use DEBUG level for production
- Don't log every iteration of a loop
Examples:
for (int i = 0; i < 1000000; i++) {
log_debug_every(1000000, "Processed %d items", i);
}
for (int i = 0; i < 1000000; i++) {
log_debug("Processing item %d", i);
}
log_error("Failed to send packet to client %u: %s", client_id, error_msg);
log_error("Send failed");
Integration with Error System
Automatic Error Logging:
- SET_ERRNO() macros automatically log errors
- Error context included in log messages
- File/line/function captured automatically
Example:
if (socket_fd < 0) {
return SET_ERRNO(ERROR_NETWORK_SOCKET, "Failed to create socket");
}
- See also
- logging.h
-
asciichat_errno.h
-
common.h
Overview
The Options system provides command-line argument parsing support for ascii-chat. It handles server and client options, validates settings, and manages application configuration.
Implementation: lib/options.h, lib/config.h
Key Features:
- Command-line argument parsing with getopt
- Server and client mode options
- Automatic help generation
- Configuration validation
- Default value management
Operation Modes
Server Mode:
./ascii-chat server [options]
Client Mode:
./ascii-chat client [options]
Common Options
Network Options:
[address][:port] (positional for client) - Server address with optional port (default: localhost:27224)
[address1] [address2] (positional for server) - Bind addresses, 0-2 IPv4/IPv6 addresses
--port PORT - Server port (default: 27224)
Display Options:
--width WIDTH - Terminal width (default: auto-detect)
--height HEIGHT - Terminal height (default: auto-detect)
--color - Enable color output
--stretch - Stretch video to fill terminal
--half-block - Use half-block rendering (2x vertical resolution)
Video Options:
--webcam INDEX - Webcam device index (default: 0)
--webcam-flip - Flip webcam horizontally
--test-pattern - Use test pattern instead of webcam
--snapshot - Capture single frame and exit
--snapshot-delay SEC - Delay before snapshot (default: 1.0)
Audio Options:
--audio - Enable audio (client only, server always has audio)
--no-audio - Disable audio
Crypto Options:
--key PATH - SSH private key for authentication
--server-key KEY - Server public key or github:username
--password PASS - Shared password for encryption
--insecure - Skip host identity verification (NOT RECOMMENDED)
--client-keys KEYS - Whitelist of allowed client keys (comma-separated)
Logging Options:
--log-file PATH - Write logs to file
--log-level LEVEL - Set log level (0-4)
Configuration Options:
--config PATH - Load configuration from TOML file
--config-create [PATH] - Create default config file and exit
Usage Examples
Server Examples
Basic Server:
Server with Authentication:
./ascii-chat server --key ~/.ssh/id_ed25519 --client-keys github:user1,github:user2
Server with Custom Port:
./ascii-chat server --port 8080
Server with Logging:
./ascii-chat server --log-file /var/log/ascii-chat.log --log-level 1
Client Examples
Basic Client:
Connect to Remote Server:
./ascii-chat client 192.168.1.100:27224
Client with Audio and Color:
./ascii-chat client --audio --color
Client with Custom Webcam:
./ascii-chat client --webcam 1 --webcam-flip
Snapshot Mode:
./ascii-chat client --snapshot --snapshot-delay 5
Client with Server Verification:
./ascii-chat client --server-key github:zfogg
Programmatic API
Parse Options:
options_t opts;
asciichat_error_t err = parse_options(argc, argv, &opts);
if (err != ASCIICHAT_OK) {
return err;
}
Error Handling:
asciichat_error_t err = parse_options(argc, argv, &opts);
if (err != ASCIICHAT_OK) {
asciichat_error_context_t ctx;
if (HAS_ERRNO(&ctx)) {
log_error("Invalid options: %s", ctx.context_message);
}
return err;
}
Help System
Show Help:
./ascii-chat --help
./ascii-chat server --help
./ascii-chat client --help
Help Output:
- Lists all available options
- Shows default values
- Provides usage examples
- Displays mode-specific options
- See also
- Configuration for configuration files and precedence
-
options.h
-
config.h