// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// A service-shaped application: a console slot, a status slot that keeps only
/// warnings, a sampled slot for a chatty neighbour, and a remote slot that is
/// down with an alarm that falls back to the console.
///
/// moon run src/examples/03-service
struct Remote {
lines : Array[String]
failing : Array[Bool]
}
///|
pub impl @serlog.Sink for Remote with fn write(self, entry) {
if self.failing[0] {
Err("connection refused")
} else {
self.lines.push(entry.line)
Ok(())
}
}
///|
fn main raise {
let now = [1_700_000_000_000_000_000L]
let console = @serlog.Memory::new()
let status = @serlog.Memory::new()
let sampled = @serlog.Memory::new()
let remote : Remote = { lines: [], failing: [true], }
let hub = @serlog.Hub::new(
identity=@serlog.Identity::of(app="gateway", env="prod", instance="g-3"),
clock=fn() { now[0] },
)
let _ = hub.add(
@serlog.Slot::new(console, name="console")
.kind("memory")
.layout(
@serlog.Pattern::parse(
"{time:%H:%M:%S%.3f} {level:<5} {target}: {msg}{fields}",
),
),
)
let _ = hub.add(
@serlog.Slot::new(status, name="status")
.kind("memory")
.at(@serlog.warn)
.process(@serlog.Redact::new().key("authorization")),
)
let _ = hub.add(
@serlog.Slot::new(sampled, name="sampled")
.kind("memory")
.at(@serlog.trace)
.layout(@serlog.Logfmt::new())
.process(@serlog.Sample::new(first=2, every=10, per=60_000_000_000L)),
)
// A remote sink that is down: its failures go to the console instead.
let oncall = @serlog.Tiers::from([["console"]])
let _ = hub.add(
@serlog.Slot::new(remote, name="remote")
.kind("remote")
.layout(@serlog.Logfmt::new())
.on_fail(@serlog.OnFail::Alarm(oncall)),
)
// One request, logged the way a handler logs it: a logger with the request's
// own field bound to it, so every line of the request carries it.
let request = hub
.logger("gateway::http")
.bound("request", @serlog.Value::of_str("r-1"))
request.info("GET /mirror", fields=[
("path", @serlog.Value::of_str("/mirror")),
("authorization", @serlog.Value::of_str("Bearer t0ken")),
])
now[0] = now[0] + 1_000_000_000L
request.warn("slow upstream", fields=[
("took_ms", @serlog.Value::of_int(1200)),
])
// A chatty health probe, the same message every time: two of every ten are
// kept, and the ones kept carry the number they were.
for i in 0..<25 {
hub
.logger("gateway::health")
.info("probe", fields=[("n", @serlog.Value::of_int(i))])
}
println("— the console —")
let lines = console.lines()
let shown = if lines.length() > 6 { 6 } else { lines.length() }
for i in 0.. shown {
println(" … " + (lines.length() - shown).to_string() + " more")
}
println("")
println("— the status slot: warnings only, one field masked —")
for line in status.lines() {
println(" " + line)
}
println("")
println(
"— the sampled slot: " +
sampled.lines().length().to_string() +
" lines kept of 27 written —",
)
for line in sampled.lines() {
println(" " + line)
}
println("")
println("— where the slots stand —")
for slot in hub.status() {
println(
" " +
slot.name +
" (" +
slot.kind +
"): " +
slot.health.name().to_owned() +
", dropped " +
slot.dropped.to_string() +
", skipped " +
slot.skipped.to_string(),
)
}
}