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
- Check for a port conflict with `ss -ltnp | grep <port>` and stop the other process or change the metrics port.
- Choose an unprivileged port (e.g. >1024) if running as a non-root user.
- Bind to 0.0.0.0 or a valid local IP instead of an address not assigned to the host.
- 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
- Use a unique, unprivileged metrics port per instance
- Check `ss -ltnp` for port conflicts before deployment
- In containers, publish/avoid conflicting host port mappings explicitly
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
- bind
- [!] no VERSION constant found in
- [!] no maltrail-sensor package entry found in
- [!] not built - cargo build --release --manifest-path…
- invalid USERS entry ' ' [?] (hint: add whitespace at start…
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)