Skip to content

TelemetryContext and its builder

TelemetryContext is the handle tool code records events through. It is cheap to clone: clones share the same sink, machine ID and policy through Arc, so passing it around a program does not duplicate any of them.

Builder fields, defaults and requirements

TelemetryContext::builder() returns a TelemetryContextBuilder. Every setter takes self and returns Self, so calls chain.

Method Type Default Required
.tool(…) impl Into<String> none Always
.tool_version(…) impl Into<String> none Always
.salt(…) impl Into<String> none Only when policy is Enabled
.sink(…) Arc<dyn TelemetrySink> Arc::new(NoopSink) No
.policy(…) CollectionPolicy CollectionPolicy::Disabled No

A context built with the defaults — no sink, no policy — collects nothing and sends nothing. That is deliberate; see Two-level opt-in.

What happens when a required field is missing

build() panics rather than returning an error, because a missing tool name is a programming mistake in the calling tool, not a runtime condition its users can hit. The panic message names the missing field:

TelemetryContextBuilder: tool is required
TelemetryContextBuilder: tool_version is required
TelemetryContextBuilder: salt is required when policy is Enabled

The salt is only consulted when the policy is Enabled. A Disabled context built without a salt is valid, and building it derives no machine ID at all — nothing on the host is read.

CollectionPolicy: the runtime switch

pub enum CollectionPolicy {
    Disabled, // default
    Enabled,
}

Disabled is the Default. ConsentState converts into it with From, so policy(consent.state.into()) is the usual wiring — see the consent file.

ctx.policy() returns the current value. The policy is fixed at build time: there is no setter on a built context, so a tool that lets a user toggle consent mid-run rebuilds the context.

What record does

pub async fn record(&self, event_name: &str) -> Result<(), TelemetryError>
pub async fn record_with_attrs(&self, event_name: &str, attrs: HashMap<String, String>)
    -> Result<(), TelemetryError>
pub async fn flush(&self) -> Result<(), TelemetryError>

Under CollectionPolicy::Disabled all three return Ok(()) immediately. No Event is constructed, no attribute map is read, and the sink is never called — not even the flush.

Under Enabled, record builds an Event stamped with the current UTC time and awaits sink.emit(&event). The sink's error is returned to the caller unchanged; the crate does not retry, buffer or swallow it. Whether a failed telemetry write should fail the surrounding command is the calling tool's decision, and the usual answer is no.

record_with_attrs replaces the event's attribute map wholesale with the map you pass. Attributes are not redacted — see Where redaction applies.

What flush does

flush delegates to the sink's flush, whose default implementation is a no-op. Only OtlpSink overrides it, and for OtlpSink the flush is where export failures actually surface — emit returns Ok(()) regardless. See Sinks.

Async, and the runtime it needs

record, record_with_attrs and flush are async, and FileSink uses Tokio's filesystem API, so the crate needs a Tokio runtime to be useful. There is no blocking convenience wrapper.

Thread-safety

TelemetryContext is Clone + Send + Sync + 'static, and the TelemetrySink trait requires Send + Sync + 'static of every implementation. Sharing one context across tasks is the intended usage.

Debug output does not leak the machine ID

The hand-written Debug implementation prints the tool, the tool version, the policy, machine_id_len — the length, not the value — and "<dyn TelemetrySink>" in place of the sink. Debug-printing a context in a log line will not put a machine identity in that log line.