stamparm/maltrail · error

unable to open event log

Error message

unable to open event log '{}' ({e})

What it means

The event log file could not be opened for writing. write_event_log attempts to (re)open the configured event log path; if File::open/create fails, the error is logged, log_write_errors is incremented, and the event is dropped rather than crashing the sensor.

Solutions

  1. Read the wrapped {e} in the log line to see the exact OS error for the path shown ('{path}').
  2. Create the log directory and fix permissions (chown/chmod) so the sensor's user can write the file.
  3. Fix the event log path in sensor configuration and ensure the directory exists before startup.
  4. Check ulimit -n and disk space if the error appears only after long runs.
Defensive patterns

Strategy: validation

Validate before calling

use std::fs;
// before starting the sensor, verify the event log location is writable
let dir = std::path::Path::new(log_dir);
if !dir.is_dir() { fs::create_dir_all(dir)?; }
let probe = dir.join(".write_probe");
fs::write(&probe, b"")?;
let _ = fs::remove_file(&probe);

Prevention

When it happens

Trigger: write_line calls write_event_log while self.log_file is None, and opening the configured log path returns Err (path does not exist, no write permission, too many open fds, read-only filesystem).

Common situations: Event-log directory missing after deployment; sensor running as a user without write access to the log path; disk full or fd limit exhausted; log path misconfigured (typo, relative path wrong working directory).

Understand the failure class

Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.

Related errors


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

Appendix: source

Thrown at sensor/src/output.rs:345

            // ONE atomic open. The previous exists()-then-File::create() sequence was a race
            // between workers: each has its own sink, so two could both find the file missing at a
            // day boundary and the second `File::create` would TRUNCATE events the first had
            // already written. `create(true).append(true)` with the mode set in the same call
            // cannot truncate, and gives Python's 0644 on creation without a second syscall.
            // .mode() is a Unix extension; on Windows the file inherits the directory's ACL and
            // there is no mode to request.
            #[cfg(unix)]
            let opened = OpenOptions::new().append(true).create(true).mode(0o644).open(&path);
            #[cfg(not(unix))]
            let opened = OpenOptions::new().append(true).create(true).open(&path);
            match opened {
                Ok(f) => {
                    self.log_file = Some(f);
                    self.log_path = Some(path);
                }
                Err(e) => {
                    self.log_write_errors += 1;
                    log_error(&format!("unable to open event log '{}' ({e})", path.display()), true);
                    return;
                }
            }
        }
        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()) {

View on GitHub (pinned to 77cfb06d76)