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 for 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, because 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.