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
- Read the wrapped {e} in the log line to see the exact OS error for the path shown ('{path}').
- Create the log directory and fix permissions (chown/chmod) so the sensor's user can write the file.
- Fix the event log path in sensor configuration and ensure the directory exists before startup.
- 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
- Create and chmod the event log directory in deployment automation before the sensor starts
- Run the sensor under a user that owns the log directory
- Check fd limits (ulimit -n) for long-running sensors
- Use absolute log paths in configuration
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
- create log dir
- short write to the event log
- unable to write event log
- invalid configuration value for 'LOG_SERVER
- missing 'USER_WHITELIST' file
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)