Use callbacks to report measurements asynchronously with Observable instruments in OpenTelemetry Rust
Observable instruments (also called asynchronous instruments) report measurements via callbacks that are invoked during metric collection. They’re ideal for values that are expensive to compute, read from external sources, or already being tracked elsewhere.
Measurements should happen on a schedule (not inline)
Multiple instruments need the same source data
You don’t control when collection happens
Common examples:
CPU time from OS APIs
Memory usage from system calls
Process statistics
Sensor readings
External resource metrics
Observable instruments use callbacks that run during metric collection. The SDK determines when to call your callback, typically during export intervals.
use opentelemetry::{global, KeyValue};let meter = global::meter("my-app");// u64 observable counterlet _observable_counter = meter .u64_observable_counter("cpu_time") .with_description("CPU time used by the process") .with_unit("ms") .with_callback(|observer| { let cpu_time = get_process_cpu_time(); // Your function observer.observe(cpu_time, &[]); }) .build();// f64 observable counterlet _observable_counter = meter .f64_observable_counter("bytes_processed") .with_callback(|observer| { let bytes = get_total_bytes_processed(); observer.observe(bytes, &[]); }) .build();
let _observable_updown = meter .i64_observable_up_down_counter("active_connections") .with_description("Number of active connections") .with_callback(|observer| { let count = get_active_connection_count(); observer.observe(count, &[]); }) .build();
// Good - fast operationlet _gauge = meter .u64_observable_gauge("cache_size") .with_callback(|observer| { let size = CACHE.len(); // O(1) operation observer.observe(size as u64, &[]); }) .build();// Bad - slow operationlet _gauge = meter .u64_observable_gauge("expensive_metric") .with_callback(|observer| { let value = expensive_computation(); // Avoid! observer.observe(value, &[]); }) .build();
let _gauge = meter .f64_observable_gauge("temperature") .with_callback(|observer| { match read_temperature_sensor() { Ok(temp) => observer.observe(temp, &[]), Err(_) => {} // Don't observe on error, or use a default value } }) .build();
Keep observable instruments in scope! The callback will not be invoked if the instrument is dropped:
// Bad - instrument dropped immediatelymeter.u64_observable_counter("my_counter") .with_callback(|observer| observer.observe(100, &[])) .build();// Callback will never be called!// Good - keep instrument in scopelet _my_counter = meter .u64_observable_counter("my_counter") .with_callback(|observer| observer.observe(100, &[])) .build();// Callback will be called during collection