Configuration and observability
Typed configuration#
Task. Program parameters - port, name, debug mode - with defaults, profiles and an override from outside.
config App {
port: u16 = 8080
name: str = "demo"
debug: bool = false
profile dev { debug = true } // the dev profile is on by default
profile prod { port = 443 }
}
fn main() io {
let app = App.get() // the value is fixed for the lifetime of the node
print("{app.name} listens on {app.port}, debug={app.debug}")
}Output
demo listens on 8080, debug=true
config App { … }is a typed config: fields likedata, default values,profileblocks.App.get()returns an immutable value fixed for the whole run.- Sources in descending priority: the flag
--set App.port=8080-> the environment variableAPP_PORT-> the fileconfig/<profile>.toml-> aprofileblock -> the default. Try:APP_PORT=9000 oxl run prog.oxloroxl run prog.oxl --profile prod. - A wrong type or a violated refinement (
port: Portwith a range) gives a message naming the source and exit code 78 before the program starts: an incorrect configuration does not live to see the first request. - Declare secrets with the type
secret<str>: they do not get into logs and are not serialized. - Values that must change without a restart (limits, flags) are described by
dynamic(§14.2).
Logs, metrics, spans#
Task. Record events, count requests and time sections of code.
use std.obs { log, Level, metric, span }
fn handle(id: i64) -> i64 io {
log.info("handling request {id}") // records go to stderr, not stdout
id * 2
}
fn main() io {
log.setLevel(Level.Info)
let served = metric.counter("requests_total", "requests served")
let inflight = metric.gauge("inflight", "requests in flight")
var total = 0
for id in 1..=3 {
inflight.inc()
total += span.with("handle", () => handle(id)) // a span times a section and joins the trace
served.inc()
inflight.dec()
}
log.debug("not printed: level is Info")
print("total {total}, served {served.value()}, in flight {inflight.value()}")
print("debug enabled: {log.enabled(Level.Debug)}")
}Output
total 12, served 3.0, in flight 0.0 debug enabled: false
log.info("…")writes a record to stderr (only the program output stays instdout); the level is set bylog.setLevel(Level.Info)or the run flag--log-level. Records contain the time, the level and fields;log.setJson(true)switches to JSON for log collectors.metric.counter("requests_total", "…"),gauge,histogramare node metrics; in a clusteroxl inspectshows them.span.with("handle", () => …)times a section and nests into the request trace; in services the trace is propagated between nodes automatically (traceIdinctx).- If a message is expensive to build, check
log.enabled(Level.Debug)first.
Back: cookbook.