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
func – func (or NULL)
line – LINE (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_LEVELenvironment variable.Format (
valueof the env var):Each entry isAXL_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
<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 settingAXL_LOG_LEVELin 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_levelcalls always win because the lazy init runs first inside those setters before they apply the explicit level. This matchesRUST_LOGsemantics — env defines the floor, code overrides.Level keywords are case-insensitive (
debug,Debug,DEBUGall 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/--debugnamespace free for tool-specific use. Mirrors theRUST_LOG/GST_DEBUG/G_MESSAGES_DEBUGconventions.
-
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_warningwill recurse and corrupt the in-flight message.- Parameters:
handler – callback
data – opaque data passed to handler
- Returns:
AXL_OK on success, AXL_ERR if
handleris 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
handleris 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 populateEFI_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