# walogs
A crash-safe write-ahead log (WAL) library for Rust.
`walogs` gives you a durable, append-only sequence of byte entries with automatic crash recovery. You hand it bytes; it stores them in order and returns them intact after a restart — even if the process is killed mid-write.
## What it does
- **Durable appends** — every successful `append()` call has been `fdatasync`'d before returning `Ok`. If you get `Ok(lsn)`, that entry is on disk.
- **Crash recovery** — on `open()`, any partial (torn) write at the end of the last segment is detected and truncated automatically before the WAL becomes writable again. The partially-written entry is discarded cleanly; no garbage is buried.
- **Multi-segment rotation** — segments are named `wal-000001.log`, `wal-000002.log`, etc. Auto-rotation triggers when a segment reaches a configurable size limit (default 64 MiB). Old segments are removed with `checkpoint()`.
- **Integrity checking** — every frame carries a CRC-32 covering length + LSN + data. Corruption in any completed segment is detected on open and returned as an error.
- **Single-writer safety** — `&mut self` on `append` means the borrow checker enforces the single-writer contract at compile time.
## What it does NOT do
- No transactions or multi-entry atomicity. Each `append` is its own durable unit.
- No replay or state machine. The WAL stores bytes; you decide what they mean.
- No file locking. If two processes open the same directory for writing, they will corrupt each other. You are responsible for ensuring at most one writer at a time.
- No async API.
- No seek-by-LSN. `iter()` walks the whole log from the beginning.
## Usage
```rust
use walogs::{Wal, WalConfig, Lsn, TailState};
// Open (or create) a WAL directory.
let mut wal = Wal::open("/path/to/wal-dir")?;
// Append entries. Each Ok means the entry is durable on disk.
let lsn1 = wal.append(b"first entry")?;
let lsn2 = wal.append(b"second entry")?;
// Iterate all entries.
for result in wal.iter() {
let (lsn, data) = result?;
println!("lsn={} data={:?}", lsn.0, data);
}
// Checkpoint: delete all completed segments up to and including lsn1.
wal.checkpoint(lsn1)?;
// Drop closes the file handles.
drop(wal);
// On the next open, recovery is automatic.
let wal = Wal::open("/path/to/wal-dir")?;
match wal.tail_state() {
TailState::Clean => {} // clean shutdown
TailState::TruncatedAt(offset) => { // crash — partial write discarded
println!("recovered from crash at offset {offset}");
}
}
```
### Auto-rotation
```rust
use walogs::{Wal, WalConfig};
// Rotate every 4 MiB.
let config = WalConfig { max_segment_size: Some(4 * 1024 * 1024) };
let mut wal = Wal::open_with_config("/path/to/wal-dir", config)?;
```
## Guarantees
| Durable on `Ok` | `fdatasync` completes before `append` returns `Ok` |
| At most one lost entry after crash | The last in-flight write may be lost; everything before it is intact |
| Dense LSNs | LSNs are `1, 2, 3, ...` with no gaps across all segments |
| No buried garbage | Corrupt tails are truncated before any new write |
| Segment integrity | LSN continuity and sequence gaps are detected on open |
## Cargo
```toml
[dependencies]
walogs = "0.1"
```
Minimum Rust version: **1.85** (required for Rust 2024 edition and `let`-chain expressions).
## License
MIT — see [LICENSE](LICENSE).