Logging and Telemetry

The library emits log events through the tracing crate and installs no subscriber of its own. What happens with the events is decided by the application: print them, write them to files, or export them as telemetry.

Quick Setup

For prototyping, configure_logging() installs a subscriber that prints human-readable log lines to stderr. Call it once at the start of your program. The examples use their own variant of it, common::init_logging(), which does the same without the crate target in each line:

pub fn init_logging() {
    use tracing_subscriber::EnvFilter;

    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
    tracing_subscriber::fmt()
        .with_env_filter(filter)
        .with_target(false)
        .init();
}

The default level is info. The RUST_LOG environment variable selects the level and can filter by crate:

# Debug messages from everything:
RUST_LOG=debug cargo run -p voraus-robot-arm --example quick_start

# Only warnings:
RUST_LOG=warn cargo run -p voraus-robot-arm --example quick_start

# Debug messages from the library, info from everything else:
RUST_LOG=voraus_robot_arm=debug,info cargo run -p voraus-robot-arm --example quick_start

Call configure_logging() only from an application’s entry point, never from library code. The subscriber is process-global: if one is already installed, the call returns an error and does nothing, but if the call comes first, the application’s own try_init() fails later and its init() panics.

Production Use

For production, install your own tracing subscriber with the output you need. The library’s events are captured by whatever subscriber the application installs; nothing in the library has to be configured. A subscriber is a tracing_subscriber registry with layers, one for filtering and one or more for output:

fn main() {
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
    tracing_subscriber::registry()
        .with(filter)
        .with(fmt::layer())
        .init();

    tracing::info!("The library's events now go through the application's subscriber");
}

Replace or add layers for other outputs: fmt::layer().json() (the json feature of tracing-subscriber) writes JSON lines, and fmt::layer().with_writer(...) with a writer from the tracing-appender crate writes rotating log files. The library deliberately does not write log files: file names, rotation, and retention are the application’s decision.

Telemetry

The library also opens tracing spans around its operations:

  • Every command and reader opens an info span named after its operation, such as stop_motion, set_time_override, set_active_tool, or joint_pose, recording the call’s arguments. The lifecycle calls do the same with the spans connect, send_enable, send_disable, and disconnect, which enable() and disable() open as well; connect records the host and port.

  • A queued instruction (a motion, a wait, a jogging setpoint) opens an instruction span with its instruction.id as a child of the call that queued it, for example move_ptp. The span stays open until the instruction’s handle resolves, so its duration is the instruction’s time from queuing to completion. A send child span covers the call that hands the instruction to the robot control, and an execute child span with the instruction.id covers the time the robot control executes it, so a trace shows which instruction was active at any point. A failed instruction records the error field and sets otel.status_code to error before its span closes.

  • A motion block groups its steps under a motion_block span with the number of steps.

With the fmt subscriber from configure_logging(), a log line inside a span is prefixed with the span and its fields. To export the spans, add a layer from the tracing-opentelemetry crate to your subscriber: it turns each span into an OpenTelemetry span, with the library’s events as span events, and sends them to an OpenTelemetry collector through the exporter you configure. The library itself does not depend on OpenTelemetry.

API Summary

/// Install a `tracing` subscriber that prints to stderr.
///
/// The filter comes from `RUST_LOG`; without it, `info` and above are
/// printed. Call it once at the start of an application's `main`, never
/// from library code. The subscriber is process-global: if this call comes
/// first, the application's own `try_init()` fails later and its `init()`
/// panics.
///
/// # Returns
///
/// `Ok(())` once the subscriber is installed.
///
/// # Errors
///
/// [`TryInitError`] if a global subscriber is already installed; the call
/// then has no effect.
///
/// # Example
///
/// ```rust
/// use voraus_robot_arm::logging::configure_logging;
///
/// assert!(configure_logging().is_ok());
/// // The second call finds the subscriber installed by the first.
/// assert!(configure_logging().is_err());
/// ```
pub fn configure_logging() -> Result<(), TryInitError> {