Event and its JSON shape¶
An Event is one telemetry record. TelemetryContext::record builds one
per call; sinks serialise it.
Fields¶
| Field | Type | Set by | Notes |
|---|---|---|---|
name |
String |
caller | The event name, e.g. command.invoke, tool.start. |
tool |
String |
context | The owning tool's name. |
tool_version |
String |
context | The owning tool's version string. |
machine_id |
String |
context | 64-char hex, salted SHA-256. Empty string on a Disabled context. |
timestamp_utc |
String |
Event::now |
RFC 3339 UTC, with sub-second precision. |
args |
Option<String> |
caller | Raw command-line args. Redacted by out-of-process sinks. |
err_msg |
Option<String> |
caller | Error or panic message. Redacted by out-of-process sinks. |
attrs |
HashMap<String, String> |
caller | Free-form attributes. Never redacted. |
Event is #[non_exhaustive], so it cannot be constructed with a struct
literal from outside the crate, and new fields can appear in a minor
release. Use the constructors.
Constructors and setters¶
Event::now(name, tool, tool_version, machine_id) // stamps the current UTC time
Event::with_timestamp(name, tool, tool_version, machine_id, ts) // caller supplies the timestamp
Both take impl Into<String> for every argument. with_timestamp expects
a string already formatted as RFC 3339 UTC — it does not parse or validate
what you pass, so a malformed timestamp reaches the sink unchanged. It is
the constructor tests use for reproducible output.
The fluent setters each return Self:
event.with_attr(key, value) // inserts, overwriting an existing key
event.with_args(args) // sets Some(args)
event.with_err_msg(msg) // sets Some(msg)
event.redacted() // clone with args and err_msg passed through rtb-redact
The JSON a sink writes¶
Event derives Serialize; field names on the wire are the Rust field
names. args and err_msg are omitted entirely when None; attrs is
always present, as {} when empty. This is a real line written by
FileSink:
{"name":"command.invoke","tool":"tutorial-check","tool_version":"0.1.0","machine_id":"0052e913c636c14e8ddd57119588170cb500a875cd971efe40d45c9d0b2f09a5","timestamp_utc":"2026-08-02T20:31:52.918459561Z","attrs":{"outcome":"ok","command":"greet"}}
Two consequences for anything consuming that output:
attrskey order is not stable. It is aHashMap, and the order changes between runs of the same program. Do not diff raw lines or use them as a cache key without normalising first.- The timestamp carries nanoseconds.
Event::nowformatstime::OffsetDateTime::now_utc()as RFC 3339, which keeps the sub-second component:2026-08-02T20:31:52.918459561Z, not2026-08-02T20:31:52Z. A parser that only accepts whole seconds will reject it.
If the system clock cannot be formatted as RFC 3339 — which in practice
means a clock far outside the representable range — Event::now falls back
to 1970-01-01T00:00:00Z rather than failing the call.
Severity¶
rtb_telemetry::event::severity_of(&event) returns a &'static str:
| Condition | Value |
|---|---|
err_msg is Some(_) |
"ERROR" |
err_msg is None |
"INFO" |
That is the whole rule. Nothing else — not the event name, not an attribute
called outcome — affects severity, so an event recording a failure needs
err_msg set for it to be classified as one.
HttpSink adds the result as a top-level severity field alongside the
event fields in the POST body. OtlpSink maps "ERROR" to
Severity::Error and everything else to Severity::Info on the log
record. FileSink and MemorySink do not record severity at all; a
consumer of the JSONL derives it from the presence of err_msg.
severity_of is not re-exported at the crate root — import it from
rtb_telemetry::event.