Skip to main content

Log

Struct Log 

Source
pub struct Log { /* private fields */ }
Expand description

Caller-owned heap history with fixed limits and monotonic append watermarks.

Implementations§

Source§

impl Log

Source

pub const fn new(limits: Limits) -> Self

Construct an empty log without preallocating the configured maximum.

Examples found in repository?
examples/byte-budgeted-export/main.rs (lines 78-83)
77fn history() -> Log {
78    let mut log = Log::new(Limits {
79        max_entries: NonZeroUsize::new(3).unwrap(),
80        max_message_bytes: 32,
81        max_label_bytes: 16,
82        max_text_bytes: 128,
83    });
84    for (time, message) in ["a", "é", "🙂"].into_iter().enumerate() {
85        log.append(EntryInput {
86            created_at_secs: u64::try_from(time).unwrap(),
87            source: "worker",
88            topic: None,
89            level: Level::Info,
90            message,
91        })
92        .unwrap();
93    }
94    log
95}
More examples
Hide additional examples
examples/worker.rs (lines 7-12)
6fn main() {
7    let mut log = Log::new(Limits {
8        max_entries: NonZeroUsize::new(8).expect("positive capacity"),
9        max_message_bytes: 32,
10        max_label_bytes: 16,
11        max_text_bytes: 512,
12    });
13    for (created_at_secs, level, message) in [
14        (10, Level::Info, "delivery accepted"),
15        (
16            11,
17            Level::Warn,
18            "provider reply is still unknown; reconcile before retry",
19        ),
20        (12, Level::Ok, "original delivery reconciled"),
21    ] {
22        log.append(EntryInput {
23            created_at_secs,
24            level,
25            source: "worker",
26            topic: Some("delivery"),
27            message,
28        })
29        .expect("valid bounded entry");
30    }
31    let scan = log.visit_page(
32        Filter::default(),
33        PageRequest {
34            before: None,
35            max_results: NonZeroUsize::new(8).expect("positive results"),
36            max_scanned: NonZeroUsize::new(8).expect("positive scan"),
37        },
38        |entry| {
39            println!("{} {:?}: {}", entry.sequence, entry.level, entry.message);
40            ControlFlow::Continue(())
41        },
42    );
43    assert_eq!(scan.matched, 3);
44    assert_eq!(log.checkpoint().next_sequence(), Some(3));
45    let batch = log.prune_expired(16, 5, 1);
46    assert_eq!(batch.removed, 1);
47    assert_eq!(log.next_expiry_secs(5), Some(17));
48}
Source

pub const fn limits(&self) -> Limits

Return the fixed bounds without copying retained messages.

Source

pub const fn retained_text_bytes(&self) -> usize

Combined retained text bytes; excludes metadata, spare capacity and allocator overhead.

Source

pub const fn checkpoint(&self) -> LogCheckpoint<'_>

Borrow rows and append watermarks together, without copying or consuming the log.

Examples found in repository?
examples/worker.rs (line 44)
6fn main() {
7    let mut log = Log::new(Limits {
8        max_entries: NonZeroUsize::new(8).expect("positive capacity"),
9        max_message_bytes: 32,
10        max_label_bytes: 16,
11        max_text_bytes: 512,
12    });
13    for (created_at_secs, level, message) in [
14        (10, Level::Info, "delivery accepted"),
15        (
16            11,
17            Level::Warn,
18            "provider reply is still unknown; reconcile before retry",
19        ),
20        (12, Level::Ok, "original delivery reconciled"),
21    ] {
22        log.append(EntryInput {
23            created_at_secs,
24            level,
25            source: "worker",
26            topic: Some("delivery"),
27            message,
28        })
29        .expect("valid bounded entry");
30    }
31    let scan = log.visit_page(
32        Filter::default(),
33        PageRequest {
34            before: None,
35            max_results: NonZeroUsize::new(8).expect("positive results"),
36            max_scanned: NonZeroUsize::new(8).expect("positive scan"),
37        },
38        |entry| {
39            println!("{} {:?}: {}", entry.sequence, entry.level, entry.message);
40            ControlFlow::Continue(())
41        },
42    );
43    assert_eq!(scan.matched, 3);
44    assert_eq!(log.checkpoint().next_sequence(), Some(3));
45    let batch = log.prune_expired(16, 5, 1);
46    assert_eq!(batch.removed, 1);
47    assert_eq!(log.next_expiry_secs(5), Some(17));
48}
Source

pub fn snapshot(&self) -> LogSnapshot

Copy all retained rows and both append watermarks for explicit checkpointing. Work and allocation scale with retained history, not a cursor-page limit.

Source

pub fn into_snapshot(self) -> LogSnapshot

Consume the log into a checkpoint without cloning retained entries. Fixed limits, row order and both append watermarks are preserved, including empty histories and permanent sequence exhaustion.

Source

pub fn restore(snapshot: LogSnapshot) -> Result<Self, RestoreError>

Admit a complete checkpoint without replaying entries or reissuing identities.

Empty retained history preserves its original append watermarks. Admission proves internal consistency, not source authenticity or host continuity. Byte decoders must bound allocation before constructing the checkpoint.

§Errors

Rejects bounds, row ordering, watermarks and truncation states that cannot result from this engine’s append/retention operations.

Source

pub fn len(&self) -> usize

Return the retained row count.

Source

pub fn is_empty(&self) -> bool

Whether this log currently has no retained rows.

Source

pub fn append( &mut self, input: EntryInput<'_>, ) -> Result<AppendReceipt, AppendError>

Admit one row, evicting the oldest rows until both count and text ceilings fit.

§Errors

Returns a typed refusal for oversized labels, regressed time or exhausted sequence identities, or a row that cannot fit the text budget by itself. Validation completes before any eviction or mutation. Error priority is source, topic, regressed time, sequence exhaustion, then total text budget.

Examples found in repository?
examples/byte-budgeted-export/main.rs (lines 85-91)
77fn history() -> Log {
78    let mut log = Log::new(Limits {
79        max_entries: NonZeroUsize::new(3).unwrap(),
80        max_message_bytes: 32,
81        max_label_bytes: 16,
82        max_text_bytes: 128,
83    });
84    for (time, message) in ["a", "é", "🙂"].into_iter().enumerate() {
85        log.append(EntryInput {
86            created_at_secs: u64::try_from(time).unwrap(),
87            source: "worker",
88            topic: None,
89            level: Level::Info,
90            message,
91        })
92        .unwrap();
93    }
94    log
95}
More examples
Hide additional examples
examples/worker.rs (lines 22-28)
6fn main() {
7    let mut log = Log::new(Limits {
8        max_entries: NonZeroUsize::new(8).expect("positive capacity"),
9        max_message_bytes: 32,
10        max_label_bytes: 16,
11        max_text_bytes: 512,
12    });
13    for (created_at_secs, level, message) in [
14        (10, Level::Info, "delivery accepted"),
15        (
16            11,
17            Level::Warn,
18            "provider reply is still unknown; reconcile before retry",
19        ),
20        (12, Level::Ok, "original delivery reconciled"),
21    ] {
22        log.append(EntryInput {
23            created_at_secs,
24            level,
25            source: "worker",
26            topic: Some("delivery"),
27            message,
28        })
29        .expect("valid bounded entry");
30    }
31    let scan = log.visit_page(
32        Filter::default(),
33        PageRequest {
34            before: None,
35            max_results: NonZeroUsize::new(8).expect("positive results"),
36            max_scanned: NonZeroUsize::new(8).expect("positive scan"),
37        },
38        |entry| {
39            println!("{} {:?}: {}", entry.sequence, entry.level, entry.message);
40            ControlFlow::Continue(())
41        },
42    );
43    assert_eq!(scan.matched, 3);
44    assert_eq!(log.checkpoint().next_sequence(), Some(3));
45    let batch = log.prune_expired(16, 5, 1);
46    assert_eq!(batch.removed, 1);
47    assert_eq!(log.next_expiry_secs(5), Some(17));
48}
Source

pub fn prune_expired( &mut self, now_secs: u64, max_age_secs: u64, max_remove: usize, ) -> RetentionBatch

Remove at most max_remove entries strictly older than the age cutoff. An entry remains available through its full configured age second.

Examples found in repository?
examples/worker.rs (line 45)
6fn main() {
7    let mut log = Log::new(Limits {
8        max_entries: NonZeroUsize::new(8).expect("positive capacity"),
9        max_message_bytes: 32,
10        max_label_bytes: 16,
11        max_text_bytes: 512,
12    });
13    for (created_at_secs, level, message) in [
14        (10, Level::Info, "delivery accepted"),
15        (
16            11,
17            Level::Warn,
18            "provider reply is still unknown; reconcile before retry",
19        ),
20        (12, Level::Ok, "original delivery reconciled"),
21    ] {
22        log.append(EntryInput {
23            created_at_secs,
24            level,
25            source: "worker",
26            topic: Some("delivery"),
27            message,
28        })
29        .expect("valid bounded entry");
30    }
31    let scan = log.visit_page(
32        Filter::default(),
33        PageRequest {
34            before: None,
35            max_results: NonZeroUsize::new(8).expect("positive results"),
36            max_scanned: NonZeroUsize::new(8).expect("positive scan"),
37        },
38        |entry| {
39            println!("{} {:?}: {}", entry.sequence, entry.level, entry.message);
40            ControlFlow::Continue(())
41        },
42    );
43    assert_eq!(scan.matched, 3);
44    assert_eq!(log.checkpoint().next_sequence(), Some(3));
45    let batch = log.prune_expired(16, 5, 1);
46    assert_eq!(batch.removed, 1);
47    assert_eq!(log.next_expiry_secs(5), Some(17));
48}
Source

pub fn next_expiry_secs(&self, max_age_secs: u64) -> Option<u64>

First second at which the oldest row expires; None for empty history or overflow.

Examples found in repository?
examples/worker.rs (line 47)
6fn main() {
7    let mut log = Log::new(Limits {
8        max_entries: NonZeroUsize::new(8).expect("positive capacity"),
9        max_message_bytes: 32,
10        max_label_bytes: 16,
11        max_text_bytes: 512,
12    });
13    for (created_at_secs, level, message) in [
14        (10, Level::Info, "delivery accepted"),
15        (
16            11,
17            Level::Warn,
18            "provider reply is still unknown; reconcile before retry",
19        ),
20        (12, Level::Ok, "original delivery reconciled"),
21    ] {
22        log.append(EntryInput {
23            created_at_secs,
24            level,
25            source: "worker",
26            topic: Some("delivery"),
27            message,
28        })
29        .expect("valid bounded entry");
30    }
31    let scan = log.visit_page(
32        Filter::default(),
33        PageRequest {
34            before: None,
35            max_results: NonZeroUsize::new(8).expect("positive results"),
36            max_scanned: NonZeroUsize::new(8).expect("positive scan"),
37        },
38        |entry| {
39            println!("{} {:?}: {}", entry.sequence, entry.level, entry.message);
40            ControlFlow::Continue(())
41        },
42    );
43    assert_eq!(scan.matched, 3);
44    assert_eq!(log.checkpoint().next_sequence(), Some(3));
45    let batch = log.prune_expired(16, 5, 1);
46    assert_eq!(batch.removed, 1);
47    assert_eq!(log.next_expiry_secs(5), Some(17));
48}
Source

pub fn page(&self, filter: Filter<'_>, request: PageRequest) -> Page<'_>

Scan newest-first without cloning retained messages or counting every match. Follow next_before with the same filters, including after an empty page.

Source

pub fn visit_page<'a>( &'a self, filter: Filter<'_>, request: PageRequest, visit: impl FnMut(&'a Entry) -> ControlFlow<()>, ) -> PageScan

Visit matching rows without allocating a page collection or cloning text. Break stops after consuming that matched row; the returned cursor resumes at the next older candidate. Empty filtered scans can still have a cursor. Callback work/allocation belongs to the caller; filter/limits match page.

Examples found in repository?
examples/worker.rs (lines 31-42)
6fn main() {
7    let mut log = Log::new(Limits {
8        max_entries: NonZeroUsize::new(8).expect("positive capacity"),
9        max_message_bytes: 32,
10        max_label_bytes: 16,
11        max_text_bytes: 512,
12    });
13    for (created_at_secs, level, message) in [
14        (10, Level::Info, "delivery accepted"),
15        (
16            11,
17            Level::Warn,
18            "provider reply is still unknown; reconcile before retry",
19        ),
20        (12, Level::Ok, "original delivery reconciled"),
21    ] {
22        log.append(EntryInput {
23            created_at_secs,
24            level,
25            source: "worker",
26            topic: Some("delivery"),
27            message,
28        })
29        .expect("valid bounded entry");
30    }
31    let scan = log.visit_page(
32        Filter::default(),
33        PageRequest {
34            before: None,
35            max_results: NonZeroUsize::new(8).expect("positive results"),
36            max_scanned: NonZeroUsize::new(8).expect("positive scan"),
37        },
38        |entry| {
39            println!("{} {:?}: {}", entry.sequence, entry.level, entry.message);
40            ControlFlow::Continue(())
41        },
42    );
43    assert_eq!(scan.matched, 3);
44    assert_eq!(log.checkpoint().next_sequence(), Some(3));
45    let batch = log.prune_expired(16, 5, 1);
46    assert_eq!(batch.removed, 1);
47    assert_eq!(log.next_expiry_secs(5), Some(17));
48}
More examples
Hide additional examples
examples/byte-budgeted-export/main.rs (lines 35-53)
25fn export(
26    log: &Log,
27    filter: Filter<'_>,
28    request: PageRequest,
29    max_bytes: usize,
30) -> Result<ExportPage, ExportError> {
31    let mut body = String::new();
32    let mut emitted = 0;
33    let mut last_emitted = None;
34    let mut stopped_for_budget = Ok(false);
35    let scan = log.visit_page(filter, request, |entry| {
36        let encoded = format!("{}: {}\n", entry.sequence, entry.message);
37        if encoded.len() > max_bytes.saturating_sub(body.len()) {
38            stopped_for_budget = if emitted == 0 {
39                Err(ExportError::EntryTooLarge {
40                    sequence: entry.sequence,
41                    required_bytes: encoded.len(),
42                    max_bytes,
43                })
44            } else {
45                Ok(true)
46            };
47            return ControlFlow::Break(());
48        }
49        body.push_str(&encoded);
50        emitted += 1;
51        last_emitted = Some(entry.sequence);
52        ControlFlow::Continue(())
53    });
54    let stopped_for_budget = stopped_for_budget?;
55    Ok(ExportPage {
56        body,
57        emitted,
58        scanned: scan.scanned,
59        // Break consumes its candidate in the engine. The candidate that did
60        // not fit has not been emitted, so resume from the last delivered row.
61        next_before: if stopped_for_budget {
62            last_emitted
63        } else {
64            scan.next_before
65        },
66    })
67}

Trait Implementations§

Source§

impl Debug for Log

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Log

§

impl RefUnwindSafe for Log

§

impl Send for Log

§

impl Sync for Log

§

impl Unpin for Log

§

impl UnsafeUnpin for Log

§

impl UnwindSafe for Log

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.