Structured tracing for MoonBit with spans, structured fields, pluggable subscribers.
Dependencies
moon add brickfrog/moontracefn main {
// One-liner setup with colored console output
@console.initialize()
// Structured events
@moontrace.info("server started", fields=[@moontrace.field("port", 8080)])
@moontrace.warn("slow query", fields=[@moontrace.field("ms", 250)])
// Spans with duration tracking
@moontrace.with_span("handle_request", fn() {
@moontrace.info("processing")
})
}14:31:43.903 | INFO | myapp — server started port=8080
14:31:43.904 | WARN | myapp — slow query ms=250
14:31:43.904 | TRACE | moontrace — span.enter handle_request
14:31:43.904 | INFO | myapp — processing
14:31:43.904 | TRACE | moontrace — span.exit handle_request duration_ns=12345@moontrace.trace("verbose detail")
@moontrace.debug("debugging info")
@moontrace.info("normal operation")
@moontrace.warn("something unexpected")
@moontrace.error("something broke")@moontrace.info("request handled",
fields=[
@moontrace.field("method", "GET"),
@moontrace.field("path", "/api/users"),
@moontrace.field("status", 200),
])@moontrace.set_global_field("service", "my-app")
@moontrace.set_global_field("version", "1.2.0")
@moontrace.info("started") // automatically includes service and version fields@moontrace.remove_global_field("version") // remove a single field
@moontrace.clear_global_fields() // remove all@moontrace.set_module_filter("my/db/package", @moontrace.Debug)
@moontrace.set_module_filter("my/http/package", @moontrace.Warn)let s = @moontrace.span("db_query")
.with_field("table", "users")
.with_field("limit", 100)
s.enter()
// ... do work ...
s.record("rows", 42) // add fields after creation
s.exit()
// s.duration() returns elapsed nanoseconds@moontrace.with_span_ctx("db_query", fn(s) {
match run_query() {
Ok(result) => s.set_status(@moontrace.Ok)
Err(e) => s.record_error(e.to_string()) // sets SpanError + records error field
}
})// Auto enter/exit
@moontrace.with_span("operation", fn() {
@moontrace.info("inside span")
})
// Access the span inside the closure
@moontrace.with_span_ctx("operation", fn(span) {
span.record("result", "ok")
})
// Nested spans with parent linking
@moontrace.with_span_ctx("parent_op", fn(parent) {
@moontrace.with_child_span(parent, "child_op", fn(_child) {
@moontrace.info("in child span")
})
})let s = @moontrace.span_with_trace("handle_request", external_trace_id)@moontrace.with_span_ctx("request", fn(parent) {
async_work(parent)
})
pub async fn async_work(parent : @moontrace.Span) -> Unit {
@moontrace.with_child_span(parent, "async_step", fn(_s) {
// child inherits parent's trace_id
})
}@moontrace.set_subscriber(fn(event) {
println(event.format())
})@console.initialize()
// or with options:
@moontrace.set_subscriber(@console.subscriber(
min_level=@moontrace.Info,
color=true,
))14:31:43.903 | INFO | server — UDS server listening path=".choir/server.sock"
14:31:44.012 | WARN | poller — retry attempt=3 max=5
14:31:44.500 | ERROR | handler — delivery failed target="leaf-1" exit_code=2@json.initialize()
// or with options:
@moontrace.set_subscriber(@json.subscriber(min_level=@moontrace.Warn))let exp = @otlp.exporter(
log_output=fn(json) { send_to_collector(json) },
span_output=fn(json) { send_to_collector(json) },
capacity=100,
)
@moontrace.set_subscriber(exp.subscriber())
// Spans must be added manually
let s = @moontrace.span("operation")
s.enter()
// ... work ...
s.exit()
exp.add_span(@otlp.span_to_otlp(s))
exp.flush() // send remaining batched data@moontrace.set_subscriber(@moontrace.compose([
@console.subscriber(min_level=@moontrace.Info),
@json.subscriber(min_level=@moontrace.Debug),
]))@moontrace.set_subscriber(
@moontrace.with_filter(@json.subscriber(), @moontrace.Warn)
)// No-op (for benchmarks/testing)
@moontrace.set_subscriber(@moontrace.noop())
// Intercept (run a side-effect before the main subscriber)
@moontrace.set_subscriber(@moontrace.intercept(
main_subscriber,
fn(e) { metrics.increment(e.level.to_string()) },
))let buf = @moontrace.buffer(@json.subscriber(), capacity=100)
@moontrace.set_subscriber(buf.subscriber())
// ... events are batched ...
buf.flush() // send all buffered events@moontrace.set_min_level(@moontrace.Info)
// trace() and debug() calls now short-circuit before allocating Eventlet event_json = event.to_json().stringify()
let span_json = span.to_json().stringify()let plain = event.format() // no color
let colored = event.format(color=true) // ANSI coloredprintln(event) // human-readable output
let s = "\{span}" // string interpolation works@moontrace # core — what libraries depend on
@moontrace/json # JSON subscriber (structured output)
@moontrace/console # console subscriber (colored, human-readable)
@moontrace/otlp # OpenTelemetry JSON format conversiongit clone https://github.com/brickfrog/moontrace
cd moontrace
git config core.hooksPath .githooks
moon test --target nativepub(all) struct Span {
name : String
fields : Array[Field]
trace_id : String
span_id : String
parent_span_id : String
kind : SpanKind
start_time : UInt64
end_time : UInt64
active : Bool
status : SpanStatus
status_message : String
}impl Show for SpanStatus#deprecated("Use `format_timestamp_utc` instead")
fn format_timestamp_hms(timestamp : UInt64) -> Stringfn format_timestamp_utc(timestamp : UInt64) -> StringStructured tracing for MoonBit with spans, structured fields, pluggable subscribers.
Dependencies