stamparm/maltrail · error

unable to write event log

Error message

unable to write event log ({e})

What it means

The event log write(2) call itself failed, so the event record was not written at all. The sensor counts the failure in log_write_errors and logs the wrapped OS error, but continues running rather than crashing.

Solutions

  1. Read the wrapped {e} to identify the OS-level cause (ENOSPC → free space, EIO → check dmesg/storage health).
  2. If a log rotator is in use, configure it to signal/rename safely or use a rotation mechanism compatible with the sensor's open handle.
  3. Restart the sensor to re-open the event log if the handle has become stale (e.g. after rotation or unmount).
  4. Monitor log_write_errors in metrics to gauge how many events were lost during the outage.
Defensive patterns

Strategy: try-catch

Try / catch

// the sensor increments log_write_errors and keeps running;
// on the operator side, detect a stale handle and restart
if log_write_errors_rate > threshold {
    restart_sensor(); // reopens the event log after rotation/unmount
}

Prevention

When it happens

Trigger: file.write(line.as_bytes()) returns Err(e) during write_event_log — e.g. ENOSPC, EBADF after the handle was closed/rotated underneath the sensor, or EIO on the underlying device.

Common situations: Log rotation tooling moved/deleted the file while the sensor holds a stale handle; disk full; I/O errors on failing storage hardware; filesystem remounted read-only.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


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

Appendix: source

Thrown at sensor/src/output.rs:377

            // (An earlier comment here justified this with PIPE_BUF. That was wrong: PIPE_BUF
            // bounds atomic writes to PIPES, not regular files. The property being relied on is
            // O_APPEND's atomic offset-plus-write, which has no such size bound in practice but is
            // also not unlimited — hence treating a short write as an error rather than looping.)
            match file.write(line.as_bytes()) {
                Ok(n) if n == line.len() => {}
                Ok(n) => {
                    self.log_write_errors += 1;
                    log_error(
                        &format!(
                            "short write to the event log ({n} of {} bytes); the record may be truncated",
                            line.len()
                        ),
                        true,
                    )
                }
                Err(e) => {
                    self.log_write_errors += 1;
                    log_error(&format!("unable to write event log ({e})"), true)
                }
            }
        }
    }

    /// Matched against "<info> <reference>". Whether a verdict was corroborated lives in the
    /// reference, and REMOTE_SEVERITY_REGEX has to see it to rank a heuristic guess below a feed
    /// hit the way the dashboard does - `core/log.py:severity_of()` does the same.
    fn severity_for(&self, info: &str, reference: &str) -> Severity {
        let Some(re) = &self.cfg.severity_regex else {
            return Severity::Medium;
        };
        match re.captures(&format!("{info} {reference}")) {
            Ok(Some(caps)) => {
                for name in ["low", "medium", "high"] {
                    if caps.name(name).is_some() {
                        return match name {
                            "low" => Severity::Low,

View on GitHub (pinned to 77cfb06d76)