stamparm/maltrail · error

short write to the event log

Error message

short write to the event log ({n} of {} bytes); the record may be truncated

What it means

A write to the event log wrote fewer bytes than the serialized event line, meaning the record may be truncated on disk. The code performs a single write(2) per event (relying on O_APPEND atomicity) and deliberately treats a short write as an error instead of looping, because the append size bound makes partial writes abnormal.

Solutions

  1. Check disk space on the event log filesystem (df -h) and free space or rotate the log.
  2. Ensure the event log path is a regular file on local storage, not a pipe or network mount.
  3. Reduce event line size (payload truncation settings) if events are extremely large.
  4. Treat affected records as suspect: inspect the log tail and re-verify events around the error timestamp.
Defensive patterns

Strategy: validation

Validate before calling

// ensure the log target is a regular file on a filesystem with headroom
let md = std::fs::metadata(log_path)?;
if !md.is_file() { eprintln!("event log must be a regular file"); }
let free = fs4::available_space(log_path)?;
if free < 64 * 1024 * 1024 { eprintln!("low disk space for event log"); }

Prevention

When it happens

Trigger: file.write(line.as_bytes()) returns Ok(n) where n < line.len() during write_event_log. Typically only when the write exceeds the pipe/append size bound or the disk fills mid-write.

Common situations: Very large event lines combined with a full or nearly full filesystem; writes to a log on a pipe/special file rather than a regular file; kernel resource pressure interrupting large writes.

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/644041b2cf0caf9c. Report an issue: GitHub.

Appendix: source

Thrown at sensor/src/output.rs:367

        }
        if let Some(file) = self.log_file.as_mut() {
            // ONE write(2) per event, deliberately — not `write_all`.
            //
            // The guarantee that matters with several workers appending to one file is that each
            // event line lands whole: O_APPEND makes the kernel pick the append offset and perform
            // the copy atomically *per system call*, so one call per line means workers interleave
            // whole records. `write_all` loops on a short write, which would split a line across
            // two calls and let another worker's line land in the middle of it.
            //
            // (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.

View on GitHub (pinned to 77cfb06d76)