hermod-tracer 1.0.1

Rust implementation of the Cardano tracing infrastructure
Documentation

hermod

Rust implementation of the hermod trace-forward protocol. Allows Rust applications (such as alternative Cardano nodes and tooling) to forward structured traces to a hermod-tracer acceptor and to act as a trace acceptor themselves.

Features

  • Wire-compatible: Byte-identical CBOR encoding to the Haskell hermod-tracing implementation
  • Both roles: Ship as a forwarder or receive as an acceptor
  • Async I/O: Built on Tokio for high-performance async networking
  • Tracing integration: Works with the Rust tracing ecosystem via hermod::tracer
  • Dispatcher: Route, filter, and rate-limit traces before forwarding
  • Automatic reconnection: Exponential backoff on connection failure
  • Standalone binary: hermod-acceptor binary for drop-in use with any forwarder

Architecture

Your Rust app
    │  tracing::info!("...")
    ▼
hermod::tracer           — tracing subscriber that captures Rust log events
    │
    ▼
hermod::dispatcher       — filter/route/rate-limit (optional)
    │
    ▼
hermod::forwarder        — CBOR-encodes and sends via Unix socket
    │  (trace-forward protocol over Ouroboros Network mux)
    ▼
hermod-tracer acceptor   — Haskell or hermod-acceptor binary

Protocol Messages

The trace-forward protocol uses three CBOR-encoded messages:

Message Wire format Direction
MsgTraceObjectsRequest array(3)[1, blocking: bool, array(2)[0, count: u16]] Acceptor → Forwarder
MsgTraceObjectsReply array(2)[3, array(N)[TraceObject...]] Forwarder → Acceptor
MsgDone array(1)[2] Acceptor → Forwarder

TraceObject Fields

Field Type Description
to_human Option<String> Human-readable text (optional)
to_machine String Machine-readable JSON
to_namespace Vec<String> Hierarchical namespace, e.g. ["node", "chain"]
to_severity Severity Debug / Info / Notice / Warning / Error / Critical / Alert / Emergency
to_details DetailLevel DMinimal / DNormal / DDetailed / DMaximum
to_timestamp DateTime<Utc> UTC timestamp (CBOR tag 1000)
to_hostname String Source hostname
to_thread_id String Source thread ID

Usage

As a Forwarder (sending traces)

use hermod::forwarder::{ForwarderConfig, TraceForwarder};
use hermod::tracer::HermodTracerBuilder;
use std::path::PathBuf;
use tracing::info;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let config = ForwarderConfig {
        socket_path: PathBuf::from("/tmp/hermod-tracer.sock"),
        queue_size: 1000,
        max_reconnect_delay: 45,
        network_magic: 764824073,
    };

    let forwarder = TraceForwarder::new(config);
    let handle = forwarder.handle();

    // Run the forwarder in the background
    tokio::spawn(async move { forwarder.run().await });

    // Wire up the tracing subscriber
    HermodTracerBuilder::new(handle).init();

    info!("Application started");
    Ok(())
}

Sending TraceObjects Directly

use hermod::forwarder::{ForwarderConfig, TraceForwarder};
use hermod::protocol::{DetailLevel, Severity, TraceObject};
use chrono::Utc;

let config = ForwarderConfig::default();
let forwarder = TraceForwarder::new(config);
let handle = forwarder.handle();

tokio::spawn(async move { forwarder.run().await });

handle.send(TraceObject {
    to_human: Some("User logged in".to_string()),
    to_machine: r#"{"user_id": 123}"#.to_string(),
    to_namespace: vec!["auth".to_string(), "login".to_string()],
    to_severity: Severity::Info,
    to_details: DetailLevel::DNormal,
    to_timestamp: Utc::now(),
    to_hostname: "node-1".to_string(),
    to_thread_id: format!("{:?}", std::thread::current().id()),
}).await?;

As an Acceptor (receiving traces)

use hermod::acceptor::{AcceptorConfig, TraceAcceptor};
use std::path::PathBuf;

let config = AcceptorConfig {
    socket_path: PathBuf::from("/tmp/hermod-tracer.sock"),
    network_magic: 764824073,
    request_count: 100,
    channel_capacity: 1000,
};

let (acceptor, mut handle) = TraceAcceptor::new(config);
tokio::spawn(async move { acceptor.run().await });

while let Some(trace) = handle.recv().await {
    println!("{}: {}", trace.to_severity, trace.to_machine);
}

Standalone Acceptor Binary

# Print all received traces as JSON to stdout
hermod-acceptor --socket /tmp/hermod-tracer.sock --magic 764824073

Building

nix build          # build hermod-acceptor binary
nix develop        # enter dev shell with nightly Rust toolchain
cargo build
cargo test

Testing

nix develop --command cargo test

See TESTING.md for conformance test details.

Wire Protocol Compatibility

This implementation maintains byte-level compatibility with the Haskell hermod-tracing library:

  • Same CBOR encoding for all messages and types
  • Haskell Generic Serialise conventions: product types as array(N+1)[constructor_index, fields...], nullary enum variants as array(1)[constructor_index]
  • UTCTime encoded as CBOR tag 1000 + map(2){1: i64_secs, -12: u64_psecs} (picoseconds)
  • Indefinite-length CBOR arrays for Haskell [a] (non-empty lists)

License

Apache-2.0