// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// What a sink is handed: the record, and the line its slot's layout made of
/// it. The line is made once, whether or not the sink reads it.
pub(all) struct Entry {
record : Record
line : String
}
///|
/// Where entries go.
///
/// Only `write` is required. Queues, retries, health and alarms belong to the
/// slot the sink is registered in, so a sink delivers and says what went wrong
/// and nothing more. `Err` is a failure worth telling someone about; a sink that
/// cannot fail answers `Ok`.
pub(open) trait Sink {
/// Writes one entry; `Err` says why it could not.
fn write(Self, Entry) -> Result[Unit, String]
}
///|
/// A sink that writes each line through a function.
///
/// This is the seam: `Lines::new(fn(line) { println(line) })` prints, and
/// `Lines::new(fn(line) { held.push(line) })` is how a test reads what was
/// written without a terminal in the way.
pub struct Lines {
write : (String) -> Unit
}
///|
/// A sink that hands each line to `write`.
pub fn Lines::new(write : (String) -> Unit) -> Lines {
{ write, }
}
///|
pub impl Sink for Lines with fn write(self, entry) {
(self.write)(entry.line)
Ok(())
}
///|
pub extend Lines with Sink::{write}
///|
/// A sink that drops everything, for an application that configures logging off
/// and a test that would rather not have output.
pub struct Discard {}
///|
/// A sink that accepts every entry and keeps none.
pub fn Discard::new() -> Discard {
Discard::{ }
}
///|
pub impl Sink for Discard with fn write(_self, _entry) {
Ok(())
}
///|
pub extend Discard with Sink::{write}
///|
/// A sink that keeps the newest entries in memory, for a test or a status page.
pub struct Memory {
size : Int32
entries : Array[Entry]
}
///|
/// Keeps the newest `size` entries; 0 keeps everything.
pub fn Memory::new(size~ : Int32 = 0) -> Memory {
{ size, entries: [], }
}
///|
pub impl Sink for Memory with fn write(self, entry) {
self.entries.push(entry)
if self.size > 0 && self.entries.length() > self.size {
let kept : Array[Entry] = []
let from = self.entries.length() - self.size
for i in from.. Array[Record] {
let out : Array[Record] = []
for entry in self.entries {
out.push(entry.record)
}
out
}
///|
/// The lines it kept, oldest first.
pub fn Memory::lines(self : Memory) -> Array[String] {
let out : Array[String] = []
for entry in self.entries {
out.push(entry.line)
}
out
}
///|
/// The entries whose line contains `text`.
pub fn Memory::find(self : Memory, text : String) -> Array[Entry] {
let out : Array[Entry] = []
for entry in self.entries {
if entry.line.contains(text) {
out.push(entry)
}
}
out
}
///|
/// Forgets everything it kept.
pub fn Memory::clear(self : Memory) -> Unit {
self.entries.clear()
}