AxlLog — Logging

Domain-based logging with level filtering, custom handlers, ring buffer storage, and file output. GLib-style API with convenience macros (axl_error, axl_info, etc.) that inject __func__/__LINE__.

Header: <axl/axl-log.h>

Overview

AXL’s logging system lets each source file declare a log domain (a short string like "net" or "http"). Messages are filtered by level (ERROR through TRACE) globally and per-domain.

Basic Usage

#include <axl.h>

AXL_LOG_DOMAIN("mymodule");  // declare at file scope

void my_function(void) {
    axl_info("starting up");           // [INFO]  mymodule: starting up
    axl_debug("value=%d", 42);         // [DEBUG] mymodule: value=42
    axl_error("failed: %s", reason);   // [ERROR] mymodule: failed: ...
}

Log Levels

From most to least severe:

Level

Value

When to use

ERROR

0

Unrecoverable failures

WARNING

1

Recoverable problems

INFO

2

Significant state changes (default visible)

DEBUG

3

Detailed diagnostic info

TRACE

4

Very verbose, per-packet/per-call

The default level is INFO — messages at DEBUG and TRACE are suppressed unless explicitly enabled.

Level Filtering

// Show DEBUG messages globally
axl_log_set_level(AXL_LOG_DEBUG);

// Suppress everything below ERROR for the "net" domain
axl_log_set_domain_level("net", AXL_LOG_ERROR);

// Clear per-domain override (reverts to global level)
axl_log_clear_domain_level("net");

Custom Handlers

Route log messages to a custom function:

void my_handler(int level, const char *domain,
                const char *message, void *data) {
    // Write to a network socket, store in a buffer, etc.
}

axl_log_add_handler(my_handler, my_context);

Ring Buffer

Capture the last N messages in memory for crash reports or diagnostics:

AxlLogRing *ring = axl_log_ring_new(100);  // keep last 100 messages
axl_log_ring_attach(ring);

// ... application runs ...

// Retrieve captured messages (newest first)
for (size_t i = 0; i < axl_log_ring_count(ring); i++) {
    axl_printf("  %s\n", axl_log_ring_get(ring, i));
}
axl_log_ring_free(ring);

File Logging

Write log messages to a file on the UEFI filesystem:

axl_log_file_attach("fs0:/app.log");
// ... all log messages are now also written to the file ...
axl_log_flush();         // optional — buffered output is drained periodically
axl_log_file_detach();   // pair with attach for explicit teardown

axl_log_file_attach opens the file, registers a buffered handler, and starts forwarding log messages to disk. Calling it again with a new path transparently detaches the previous one — _detach is only needed when you want to stop logging without re-opening.

axl_log_file_detach is the symmetric teardown — flushes the buffer, removes the internal handler, closes the file. NULL-safe on not-attached state. The typical AxlService pattern is: attach in driver setup, detach in driver teardown, before firmware UnloadImage tears the per-image static state down.

API Reference

Defines

AXL_LOG_ERROR

axl-log.h:

Domain-based logging with level filtering, custom handlers, ring buffer storage, and file output.

GLib-style API: axl_log_set_level, axl_log_add_handler, etc. Convenience macros (axl_error, axl_info, …) inject func/__LINE__.

AXL_LOG_WARNING
AXL_LOG_INFO
AXL_LOG_DEBUG
AXL_LOG_TRACE
AXL_LOG_DOMAIN(d)

AXL_LOG_DOMAIN:

Declare the log domain for the current source file. Place at the top of each .c file.

axl_error(...)

Convenience macros — inject func and LINE. Stay uppercase because they are macros, not functions.

axl_warning(...)
axl_info(...)
axl_debug(...)
axl_trace(...)

Typedefs

typedef void (*AxlLogHandler)(int level, const char *domain, const char *message, void *data)

Custom log handler callback.

typedef struct AxlLogRing AxlLogRing

Functions

void axl_log_full(int level, const char *domain, const char *func, int line, const char *fmt, ...)

Log a message with full source location.

Prefer the convenience macros (axl_error, axl_info, etc.) which fill in func/line.

Parameters:
  • level – log level (AXL_LOG_ERROR..AXL_LOG_TRACE)

  • domain – module name, or NULL

  • funcfunc (or NULL)

  • lineLINE (or 0)

  • fmt – standard C printf format string

void axl_log(int level, const char *domain, const char *fmt, ...)

Log a message without source location.

Parameters:
  • level – log level

  • domain – module name, or NULL

  • fmt – standard C printf format string

void axl_log_set_level(int level)

Set the global log level.

Messages above this level are suppressed. Default: AXL_LOG_INFO.

Parameters:
  • level – new global level

void axl_log_set_domain_level(const char *domain, int level)

Set the log level for a specific domain.

Parameters:
  • domain – domain name

  • level – level for this domain, or -1 to clear the override

void axl_log_init_from_env(void)

Apply log-level configuration from the AXL_LOG_LEVEL environment variable.

Format (value of the env var):

AXL_LOG_LEVEL=debug                     # all domains, debug
AXL_LOG_LEVEL=smbus:debug               # smbus only
AXL_LOG_LEVEL=smbus:debug,net:info      # multi-domain
AXL_LOG_LEVEL=*:warn,smbus:debug        # explicit default
AXL_LOG_LEVEL=all                       # alias for *:debug
AXL_LOG_LEVEL=off                       # alias for *:none
Each entry is <domain>:<level> separated by commas. Level keywords (case-insensitive): off / none, error, warning / warn, info, debug, trace. Domain * sets the global default. Bare <level> (no colon) is a shorthand for *:<level>.

The function is idempotent and safe to call multiple times. It is invoked automatically:

  • on first log emission (axl_log / axl_log_full)

  • on first call to axl_log_set_level / axl_log_set_domain_level — so setting AXL_LOG_LEVEL in the shell before invoking a tool takes effect with no further configuration.

Precedence: the env var is the baseline; programmatic axl_log_set_level / axl_log_set_domain_level calls always win because the lazy init runs first inside those setters before they apply the explicit level. This matches RUST_LOG semantics — env defines the floor, code overrides.

Level keywords are case-insensitive (debug, Debug, DEBUG all parse the same). Unrecognized levels and malformed entries are silently ignored — no log churn during init.

Per-domain configuration is the larger value-add over a CLI flag — keeps -d / -v / --debug namespace free for tool-specific use. Mirrors the RUST_LOG / GST_DEBUG / G_MESSAGES_DEBUG conventions.

int axl_log_add_handler(AxlLogHandler handler, void *data)

Add a global handler.

Receives all messages that pass level filtering. The handler table is bounded; once full, additional registrations are rejected.

Re-entrancy. Handlers must not allocate, send HTTP responses, or do anything that can itself emit a log line — the dispatcher is not re-entrant. A handler that triggers another axl_warning will recurse and corrupt the in-flight message.

Parameters:
  • handler – callback

  • data – opaque data passed to handler

Returns:

AXL_OK on success, AXL_ERR if handler is NULL or the table is full.

int axl_log_add_domain_handler(const char *domain, int max_level, AxlLogHandler handler, void *data)

Add a handler that only fires for a specific domain and level range.

Parameters:
  • domain – domain to filter (NULL matches all)

  • max_level – maximum level to deliver

  • handler – callback

  • data – opaque data

Returns:

AXL_OK on success, AXL_ERR if handler is NULL or the table is full.

void axl_log_remove_handler(AxlLogHandler handler)

Remove a previously added handler.

Parameters:
  • handler – handler to remove

void axl_log_suppress_console(void)

Suppress default console output.

Call after adding custom handlers.

void axl_log_set_console_timestamp(bool enable)

Enable or disable console timestamps (on by default).

Parameters:
  • enable – true to show HH:MM:SS[.uuuuuu] timestamps (the fractional field is omitted on platforms whose firmware doesn’t populate EFI_TIME.Nanosecond)

void axl_log_set_console_color(bool enable)

Enable or disable console color output (on by default).

When disabled, log output is plain ASCII with no EFI console attribute changes. Useful for serial consoles and log capture.

Parameters:
  • enable – true for color, false for plain ASCII

void axl_log_set_fatal_level(int level)

Set the fatal level.

Messages at or below this level cause exit. Pass -1 to disable.

Parameters:
  • level – level at or below which messages cause exit

void axl_log_set_fatal_image_handle(void *image_handle)

Set the image handle needed for fatal exit via gBS->Exit.

Parameters:
  • image_handle – application image handle (void* to avoid EFI_HANDLE leak)

AxlLogRing *axl_log_ring_new(size_t max_entries, size_t entry_size)

Create a new log ring buffer.

Parameters:
  • max_entries – ring capacity

  • entry_size – max message length per entry (bytes)

Returns:

new ring, or NULL on failure.

void axl_log_ring_free(AxlLogRing *ring)

Free a log ring buffer. NULL-safe.

Parameters:
  • ring – ring to free

void axl_log_ring_attach(AxlLogRing *ring)

Attach a ring as a log handler.

Parameters:
  • ring – ring to attach

size_t axl_log_ring_count(AxlLogRing *ring)

Get the number of entries stored in a ring.

Parameters:
  • ring – ring to query

Returns:

number of entries stored.

bool axl_log_ring_get(AxlLogRing *ring, size_t index, AxlLogEntry *entry)

Retrieve an entry from a ring by index.

Parameters:
  • ring – ring to query

  • index – entry index (0 = newest)

  • entry – filled with entry data

Returns:

true if entry returned, false if index out of range.

int axl_log_file_attach(const char *path)

Open a log file and register a handler that buffers output.

Calling this with a file already attached transparently detaches the previous one (flush + remove handler + close) before opening the new path — no separate detach call needed for the single-file-at-a-time pattern. Pair with axl_log_file_detach for the explicit-teardown pattern (typical of AxlService driver teardown that runs before image unload).

Parameters:
  • path – UTF-8 file path (e.g. “fs0:/app.log”)

Returns:

AXL_OK on success, AXL_ERR on failure.

void axl_log_file_detach(void)

Detach the file handler installed by axl_log_file_attach.

Flushes any buffered output, removes the internal handler from the dispatcher, and closes the file. NULL-safe when no file is attached — calling this against a never-attached or already-detached state is a no-op (the underlying flush / remove_handler / file_close primitives all tolerate cleared state).

Symmetric with axl_log_file_attach for consumers — typically AxlService driver teardown — that need explicit cleanup before image unload. Without an explicit detach, firmware UnloadImage eventually tears down the per-image static state, but that’s a resource-leak-by-design that doesn’t match the rest of AxlService’s setup/teardown ergonomics.

void axl_log_flush(void)

Flush the file handler’s buffer to disk.

struct AxlLogEntry

Public Members

const char *message
int level
const char *domain
uint64_t timestamp