blob: 0361cc388358cd0dffb78881f4ef98c2844be7a2 [file] [view] [edit]
# `util_zfmt`
`util_zfmt` is a utility crate that implements a client-server logging system using the `zfmt` structured logging library over Pigweed Maize IPC channels.
It allows userspace processes (clients) to log structured events efficiently by sending them as serialized binary payloads over IPC. The logging server (`logmgr`) buffers these events in memory and handles rendering them to text (e.g., for UART output) or exposing them to other readers (e.g., USB).
## Architecture
The logging system uses a deferred-rendering, client-server architecture:
1. **Client Logging**: Clients use `zfmt` macros (wrapped by `util_zfmt` macros) to serialize log arguments into a compact binary format at the log site. This binary payload is sent over an IPC channel to the log server.
2. **Server Buffering**: The log server receives the binary payload, injects a 64-bit timestamp and a sequence number, and stores it in a circular ring buffer (`LogBuffer`).
3. **Deferred Rendering**: Logs are kept in binary format in the buffer. They are only rendered to human-readable text when requested by a consumer (e.g., when `logmgr` prints them to UART, or when a USB manager reads them). This keeps the client-side logging latency extremely low.
NOTE: Future work will enable binary log streams with client-side decoding.
```mermaid
sequenceDiagram
participant Client
participant LogServer (logmgr)
participant UART/USB
Client->>LogServer (logmgr): IPC: OPCODE_LOG_WRITE (serialized event)
Note over LogServer (logmgr): Inject timestamp & seq
Note over LogServer (logmgr): Push to LogBuffer (binary)
LogServer (logmgr)-->>Client: IPC: Response (success)
Note over LogServer (logmgr): service_uart_tx (or USB read)
LogServer (logmgr)->>LogServer (logmgr): Read binary from LogBuffer
LogServer (logmgr)->>LogServer (logmgr): render_event (binary -> text)
LogServer (logmgr)->>UART/USB: Transmit text
```
## Key Components
### Client Side
* **`IpcLogger`**: Implements the `zfmt::Logger` trait. It wraps an `IpcChannel` (conventionally channel `0` in multi-process apps) and sends serialized log packets using `OPCODE_LOG_WRITE`.
* **Logging Macros**: `info!`, `warn!`, and `error!` are provided for convenience. They use a global `IpcLogger` instance bound to channel 0.
### Server Side
* **`LogBuffer<const N: usize>`**: A circular byte buffer that stores variable-length serialized `zfmt` frames. If the buffer is full, it automatically evicts the oldest frames to make room. It is designed to support cursors for multiple independent readers.
* **`LogServer<const N: usize>`**: Wraps `LogBuffer` and processes incoming IPC requests. It handles:
* Injecting 64-bit microsecond-resolution timestamps (via target-specific clocks) and 24-bit sequence numbers into the `EventHeader` of incoming logs.
* Handling read requests from external log consumers (like `usbmgr`) using client-provided cursors.
* **`render` Module**: Provides `render_event` which parses the binary format (detecting `StreamStart`, `EventHeader`, and `DebugMessage` tags) and formats the output into a `zfmt::FixedBuf` as a human-readable ASCII string.
## IPC Protocol
The log server listens for requests with the following opcodes:
* **`OPCODE_LOG_WRITE` (`"WLOG"`)**:
* **Request**: `[OPCODE (4B) | Serialized Event Payload]`
* **Response**: Empty on success.
* **`OPCODE_LOG_READ` (`"RLOG"`)**:
* **Request**: `[OPCODE (4B) | Cursor (8B)]`
* **Response**: `[Status (4B) | Actual Cursor Used (8B) | Log Frame ]`
* **`OPCODE_CLEAR_NOTIFIER` (`"CLRN"`)**:
* Used by consumers to acknowledge wakeups.
* **Request**: `[OPCODE (4B)]`
* **Response**: Empty.
## Configuration & Feature Flags
* **`clock-earlgrey`**: Enables querying the hardware timer on the Earlgrey platform to inject real timestamps. If disabled, timestamps default to `0`.
## Usage Examples
### 1. Logging from a Client Process
```rust
use util_zfmt::{info, warn};
use zfmt::Zfmt;
#[derive(Zfmt)]
#[zfmt(format = "Hello, user {}! Status code: {}")]
struct UserLoginEvent<'a> {
username: &'a str,
status: u32,
}
fn handle_login(username: &str) {
// ...
info!(UserLoginEvent { username, status: 0 });
}
```
### 2. Reading and Rendering Logs (Server/Consumer Side)
```rust
use util_zfmt::{LogServer, render::render_event, FixedBuf};
fn print_logs<const N: usize>(server: &LogServer<N>, cursor: &mut u64) {
let actual_cursor = if *cursor < server.buffer.read {
server.buffer.read
} else {
*cursor
};
*cursor = actual_cursor;
if let Some((_tag, s1, s2)) = server.buffer.next_frame_slice(actual_cursor) {
let mut temp_frame = [0u8; 260];
let frame_len = s1.len() + s2.len();
// Reconstruct contiguous frame if it wrapped around
temp_frame[..s1.len()].copy_from_slice(s1);
temp_frame[s1.len()..frame_len].copy_from_slice(s2);
let mut buf = FixedBuf::<512>::new();
if let Some(consumed) = render_event(&temp_frame[..frame_len], &mut buf) {
// Print the rendered ASCII string
print_to_uart(buf.as_slice());
print_to_uart(b"\r\n");
*cursor += consumed as u64;
} else {
// Skip corrupted frame
*cursor += frame_len as u64;
}
}
}
```