| Crates.io | otel-instrument |
| lib.rs | otel-instrument |
| version | 0.1.7 |
| created_at | 2025-09-04 15:37:50.369522+00 |
| updated_at | 2025-09-09 16:09:00.392524+00 |
| description | Instrument macro for opentelemetry |
| homepage | |
| repository | https://github.com/ozwaldorf/otel-instrument |
| max_upload_size | |
| id | 1824406 |
| size | 73,667 |
A procedural macro for instrumenting Rust functions with OpenTelemetry tracing. Similar to the tracing crate's #[instrument] macro but specifically designed for OpenTelemetry spans. Supports both async and sync functions.
Add this to your Cargo.toml:
[dependencies]
otel-instrument = "0.1.0"
use otel_instrument::{instrument, tracer_name};
// Define a tracer name for the instrument macros. Must be in module scope
tracer_name!("my-service");
#[instrument]
async fn my_async_function(user_id: u64, name: &str) -> Result<String, Box<dyn std::error::Error>> {
// Your async code here
Ok(format!("Hello, {}", name))
}
use otel_instrument::{instrument, tracer_name};
// Define a tracer name for the instrument macros. Must be in module scope
tracer_name!("my-service");
#[instrument]
fn my_sync_function(user_id: u64, name: &str) -> Result<String, Box<dyn std::error::Error>> {
// Your sync code here
Ok(format!("Hello, {}", name))
}
Skip specific parameters from being recorded as span attributes:
use otel_instrument::{instrument, tracer_name};
tracer_name!("auth-service");
#[derive(Debug)]
struct User { name: String }
#[derive(thiserror::Error, Debug)]
#[error("failed to authenticate")]
struct AuthError;
#[instrument(skip(password))]
async fn login(username: &str, password: &str) -> Result<User, AuthError> {
// password won't be recorded as a span attribute
Ok(User { name: username.to_string() })
}
Skip all parameters:
use otel_instrument::{instrument, tracer_name};
tracer_name!("secure-service");
#[instrument(skip_all)]
async fn sensitive_operation(secret: &str) -> Result<(), Box<dyn std::error::Error>> {
// No parameters will be recorded
Ok(())
}
Add custom fields to your spans:
use otel_instrument::{instrument, tracer_name};
tracer_name!("database-service");
#[derive(Debug)]
struct User { id: u64 }
#[instrument(fields(operation = "user_lookup", component = "auth"))]
async fn find_user(id: u64) -> Result<User, Box<dyn std::error::Error>> {
Ok(User { id })
}
Record return values and enhanced error information:
use otel_instrument::{instrument, tracer_name};
tracer_name!("math-service");
#[derive(thiserror::Error, Debug)]
enum MathError {
#[error("division by zero")]
DivisionByZero,
}
#[instrument(ret, err)]
async fn calculate(a: i32, b: i32) -> Result<i32, MathError> {
if b == 0 {
Err(MathError::DivisionByZero)
} else {
Ok(a / b)
}
}
Set a parent context to create child spans:
use otel_instrument::{instrument, tracer_name};
use opentelemetry::{global, trace::{Tracer, TraceContextExt}, Context};
tracer_name!("parent-child-service");
#[derive(Debug)]
struct UserData { id: u64 }
// Using a context parameter
#[instrument(parent = parent_ctx)]
async fn child_operation(
user_id: u64,
parent_ctx: opentelemetry::Context
) -> Result<UserData, Box<dyn std::error::Error>> {
Ok(UserData { id: user_id })
}
// Using an expression
#[instrument(parent = get_parent_context())]
async fn another_child_operation(data: &str) -> Result<String, Box<dyn std::error::Error>> {
Ok(data.to_uppercase())
}
fn get_parent_context() -> opentelemetry::Context {
let tracer = global::tracer("parent-tracer");
let span = tracer.start("parent-span");
opentelemetry::Context::current_with_span(span)
}
Use multiple attributes together:
use otel_instrument::{instrument, tracer_name};
tracer_name!("auth-service");
#[derive(Debug)]
struct AuthToken { token: String }
#[derive(thiserror::Error, Debug)]
#[error("failed to authenticate")]
struct AuthError;
#[instrument(
skip(password),
fields(service = "auth", version = "1.0"),
ret,
err
)]
async fn authenticate_user(
username: &str,
password: &str,
ip_address: &str,
) -> Result<AuthToken, AuthError> {
Ok(AuthToken { token: "token123".to_string() })
}
// Combined with parent
#[instrument(
parent = parent_ctx,
name = "user_validation",
skip(password),
ret
)]
async fn validate_user(
parent_ctx: opentelemetry::Context,
username: &str,
password: &str,
) -> Result<bool, AuthError> {
Ok(username == "admin")
}
The macro works identically with synchronous functions:
use otel_instrument::{instrument, tracer_name};
tracer_name!("sync-service");
#[derive(thiserror::Error, Debug)]
#[error("failed to process")]
struct ProcessingError;
// Basic sync function
#[instrument]
fn process_data(input: &str) -> Result<String, ProcessingError> {
Ok(input.to_uppercase())
}
// Sync function with custom attributes
#[instrument(
skip(secret_key),
fields(operation = "encryption", version = "1.0"),
ret,
err
)]
fn encrypt_data(data: &str, secret_key: &str) -> Result<String, ProcessingError> {
// Encryption logic here
Ok(format!("encrypted_{}", data))
}
// Sync function with parent context
#[instrument(parent = parent_ctx)]
fn child_processing(
parent_ctx: opentelemetry::Context,
data: &str,
) -> Result<String, ProcessingError> {
Ok(format!("processed: {}", data))
}
skip(param1, param2, ...)Skip specific function parameters from being recorded as span attributes.
skip_allSkip all function parameters from being recorded as span attributes.
fields(key = value, ...)Add custom fields/attributes to the span. Values are evaluated and formatted using Debug.
retRecord the return value as a span attribute named "return".
errRecord error values as span attributes and set appropriate span status. When an error occurs, the span status is set to error with the error description.
parent = <expression>Set a parent context for the span. The expression must evaluate to something that implements Into<opentelemetry::Context>. This allows creating child spans with explicit parent-child relationships.
async or synchronoustracer_name! macro