///|
import {
"moonrockz/moon-lander-logging/formatting",
"moonrockz/moon-lander-logging/rolling",
}| Type | Rule | Default |
|---|---|---|
| LogFileStem | 1 to 64 characters: an ASCII letter, then ASCII letters, digits, _ and -. Not a Windows device name (CON, PRN, AUX, NUL, COM0-COM9, LPT0-LPT9, any letter case). Not _ or - and digits at the end | None |
| FileByteLimit | More than 0 bytes | 10 MiB |
| RetainedFileCount | More than 0 files | 31 |
| RollingPolicy | Daily, Size(limit) or DailyAndSize(limit) | DailyAndSize(10 MiB) |
| Retention | KeepNewest(count) or KeepAll | KeepNewest(31) |
| FileFormat | Text (.log) or JsonLines (.jsonl) | Text |
| Policy | First file | Next files in the period |
|---|---|---|
| Daily or DailyAndSize | acme-20261027.log | acme-20261027_001.log, acme-20261027_002.log |
| Size | acme.log | acme_001.log, acme_002.log |
///|
test "the file set owns only its exact names" {
let stem = @rolling.LogFileStem::parse("acme")
let set = @rolling.FileSet::new(stem, Text, @rolling.RollingPolicy::default())
assert_true(set.parse_name("acme-20261027_001.log") is Some(_))
assert_true(set.parse_name("acme-old.log") is None)
assert_true(set.parse_name("acme-20261027.log.bak") is None)
assert_true(set.parse_name("acme-20261027.jsonl") is None)
assert_eq(set.marker_name(), "acme.log.lock")
assert_true(set.parse_name(set.marker_name()) is None)
}| Plan | Adapter action |
|---|---|
| Open(name, existing_size~) | No file is open. Open name to append (never truncate), then write |
| Append(name) | Write to the open file |
| Roll(name, existing_size~) | Close the active file, open name to append, write, then run retention |
///|
test "a day with two size rolls" {
let stem = @rolling.LogFileStem::parse("acme")
let limit = @rolling.FileByteLimit::bytes(100L)
let set = @rolling.FileSet::new(stem, JsonLines, DailyAndSize(limit))
let now = @formatting.LogTimestamp::from_epoch_millis(1793110987024L)
let mut state = @rolling.RollingState::new(set)
let names = []
for size in [60L, 40L, 1L, 99L, 1L] {
let bytes = @rolling.ByteCount::of(size)
let plan = state.plan(now, bytes, [])
match plan {
Append(name) => names.push("append \{name}")
Open(name, existing_size~) => {
state = state.opened(name, existing_size)
names.push("open \{name}")
}
Roll(name, existing_size~) => {
state = state.opened(name, existing_size)
names.push("roll \{name}")
}
}
state = state.wrote(bytes)
}
inspect(names.join("\n"), content=(
#|open acme-20261027.jsonl
#|append acme-20261027.jsonl
#|roll acme-20261027_001.jsonl
#|append acme-20261027_001.jsonl
#|roll acme-20261027_002.jsonl
))
}///|
test "a restart appends to the newest file of the day" {
let stem = @rolling.LogFileStem::parse("acme")
let set = @rolling.FileSet::new(stem, Text, @rolling.RollingPolicy::default())
let now = @formatting.LogTimestamp::from_epoch_millis(1793110987024L)
let listing : Array[@rolling.ListedFile] = [
{ name: "acme-20261027.log", size: @rolling.ByteCount::of(10485760L), regular: true },
{ name: "acme-20261027_001.log", size: @rolling.ByteCount::of(2048L), regular: true },
{ name: "acme.log.lock", size: @rolling.ByteCount::zero(), regular: true },
]
let plan = @rolling.RollingState::new(set).plan(
now,
@rolling.ByteCount::of(120L),
listing,
)
guard plan is Open(name, existing_size~) else { fail("no open plan") }
assert_eq(name.to_string(), "acme-20261027_001.log")
assert_eq(existing_size.to_int64(), 2048L)
}///|
test "keep the newest three files" {
let stem = @rolling.LogFileStem::parse("acme")
let set = @rolling.FileSet::new(stem, Text, Daily)
let names = [
"acme-20261024.log", "acme-20261025.log", "acme-20261026.log", "acme-20261027.log",
"acme-20261027.log.bak",
]
let listing = names.map(name => @rolling.ListedFile::{
name,
size: @rolling.ByteCount::of(1L),
regular: true,
})
guard set.parse_name("acme-20261027.log") is Some(active) else {
fail("not owned")
}
let retention = @rolling.Retention::KeepNewest(
@rolling.RetainedFileCount::count(3),
)
assert_eq(set.retention(retention, listing, active).map(name => name.to_string()), ["acme-20261024.log"])
}pub(all) enum FilePlan {
Append(LogFileName)
Open(LogFileName, existing_size~ : ByteCount)
Roll(LogFileName, existing_size~ : ByteCount)
} derive(Eq, Debug)| Policy | First file | Next files |
|---|---|---|
| Daily or DailyAndSize | acme-20261027.log | acme-20261027_001.log, acme-20261027_002.log |
| Size | acme.log | acme_001.log, acme_002.log |
fn FileSet::retention(self : FileSet, retention : Retention, listing : Array[ListedFile], active : LogFileName) -> Array[LogFileName]impl Show for LogFileNameimpl Show for LogFileStempub(all) enum RollingPolicy {
Daily
Size(FileByteLimit)
DailyAndSize(FileByteLimit)
} derive(Eq, Debug)fn RollingState::plan(self : RollingState, now : LogTimestamp, record_bytes : ByteCount, listing : Array[ListedFile]) -> FilePlan raise RollingErrorInstall
Download zipStructured logging with a bring-your-own logger trait for the Moon-Lander framework