rtb-telemetry 0.7.5

Opt-in anonymous usage telemetry with pluggable sinks. Part of the phpboyscout Rust toolkit.
Documentation
---
title: TelemetryContext
description: Builder fields, defaults, panics and record/flush semantics for TelemetryContext.
---

# `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](../explanation/two-level-opt-in.md).

## 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:

```text
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

```rust
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](consent-file.md).

`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

```rust
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](../explanation/redaction-boundary.md).

## 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](sinks.md#otlpsink).

## 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.