stamparm/maltrail · error

stats endpoint disabled

Error message

stats endpoint disabled: {e}

What it means

The sensor's metrics/stats HTTP endpoint failed to bind its listener; the code logs "stats endpoint disabled: {e}" via output::log_error after printing the bind error to stderr. Monitoring is lost for this process, but the sensor itself keeps running.

Solutions

  1. Check for a port conflict with `ss -ltnp | grep <port>` and stop the other process or change the metrics port.
  2. Choose an unprivileged port (e.g. >1024) if running as a non-root user.
  3. Bind to 0.0.0.0 or a valid local IP instead of an address not assigned to the host.
  4. Read the ({e}) text to confirm the bind error kind before further changes.
Defensive patterns

Strategy: try-catch

Validate before calling

// pick a free port before starting the metrics server
let listener = std::net::TcpListener::bind(("0.0.0.0", 0))?;
let free_port = listener.local_addr()?.port();

Type guard

fn port_bindable(port: u16) -> bool {
    std::net::TcpListener::bind(("0.0.0.0", port)).is_ok()
}

Try / catch

match TcpListener::bind(addr) {
    Ok(l) => start_metrics(l),
    Err(e) => { log_metrics_disabled(&e); continue_sensor_without_metrics(); }
}

Prevention

When it happens

Trigger: The metrics server bind (TcpListener::bind or equivalent) returns Err(e) in run(): the configured address/port is already in use, the address is not local, or binding to a privileged port without permissions.

Common situations: Two sensor instances started with the same metrics port (address-already-in-use); old process still holding the port during restart; binding to :80/:9100-style low ports as non-root; container network namespace conflicts.

Related errors


AI-assisted analysis of stamparm/maltrail@77cfb06d76 (2026-09-13). Data as JSON: /api/errors/eb8f6ad3eccd5fe8. Report an issue: GitHub.

Appendix: source

Thrown at sensor/src/main.rs:598

                        }
                    }
                }
            })
            .ok();
    }

    // --- Prometheus endpoint ---------------------------------------------------
    // Opt-in, and never fatal: a sensor that cannot bind its metrics port must still detect.
    if !cfg.stats_address.is_empty() {
        match maltrail_sensor::stats::spawn(&cfg.stats_address, registry.clone(), Instant::now()) {
            Ok(bound) => {
                if !args.quiet {
                    cprintln!("[i] metrics endpoint: http://{bound}/metrics");
                }
            }
            Err(e) => {
                ceprintln!("[!] metrics endpoint disabled: {e}");
                output::log_error(&format!("stats endpoint disabled: {e}"), true);
            }
        }
    }

    // --- metrics thread --------------------------------------------------------
    if cfg.metrics_interval > 0 && !cfg.is_offline_replay() {
        let reg_metrics = registry.clone();
        let shutdown_metrics = shutdown.clone();
        let interval = Duration::from_secs(cfg.metrics_interval);
        std::thread::Builder::new()
            .name("metrics".into())
            .spawn(move || {
                let mut next = Instant::now() + interval;
                loop {
                    std::thread::sleep(Duration::from_millis(500));
                    if shutdown_metrics.load(Ordering::Relaxed) {
                        break;
                    }

View on GitHub (pinned to 77cfb06d76)