CtrlK
BlogDocsLog inGet started
Tessl Logo

kopai/otel-instrumentation

Instrument applications with the OpenTelemetry SDK and prove the telemetry is good by validating it against a local Kopai backend. Use when setting up observability, adding tracing/logging/metrics, deciding what to instrument or which attributes to add, retrofitting OTel into an existing codebase, threading context through call chains, configuring sampling, or when traces/logs/metrics aren't appearing after setup. Also use when users say things like "my traces aren't showing up", "I don't see any data", or "how do I add observability to my app". Do NOT use to investigate existing telemetry for a root cause (use root-cause-analysis), to build dashboards (use create-dashboard), or to instrument LLM and agent calls (use otel-genai-instrumentation).

Quality

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

lang-rust.mdreferences/

titleimpacttags
Rust InstrumentationHIGHlang, rust, traces, logs, metrics

Rust Instrumentation

Set up OpenTelemetry SDK for Rust applications with traces, logs, and metrics.

Cargo.toml

Note: Replace * with the latest versions from crates.io.

[dependencies]
# Web framework (optional)
axum = "*"
tokio = { version = "*", features = ["full"] }
serde = { version = "*", features = ["derive"] }

# OpenTelemetry SDK
opentelemetry = "*"
opentelemetry_sdk = { version = "*", features = ["rt-tokio"] }
opentelemetry-otlp = { version = "*", features = ["http-proto", "http-json", "logs"] }

# Logging integration
opentelemetry-appender-tracing = "*"
tracing = "*"
tracing-subscriber = { version = "*", features = ["env-filter"] }

Configuration

use std::env;

fn get_endpoint() -> String {
    env::var("OTEL_EXPORTER_OTLP_ENDPOINT")
        .unwrap_or_else(|_| "http://localhost:4318".to_string())
}

fn get_service_name() -> String {
    env::var("OTEL_SERVICE_NAME")
        .unwrap_or_else(|_| "my-service".to_string())
}

Environment Variables:

VariableDescription
OTEL_EXPORTER_OTLP_ENDPOINTOTLP endpoint (e.g., http://localhost:4318)
OTEL_SERVICE_NAMEService name shown in observability backend

Traces (SDK)

use opentelemetry::{global, trace::{Status, TraceContextExt, Tracer}, KeyValue};
use opentelemetry_otlp::{Protocol, WithExportConfig};
use opentelemetry_sdk::{trace::SdkTracerProvider, Resource};

fn init_tracer_provider() -> SdkTracerProvider {
    let endpoint = get_endpoint();
    let service_name = get_service_name();

    let exporter = opentelemetry_otlp::SpanExporter::builder()
        .with_http()
        .with_protocol(Protocol::HttpJson)
        .with_endpoint(format!("{}/v1/traces", endpoint))
        .build()
        .expect("Failed to create span exporter");

    let resource = Resource::builder()
        .with_service_name(service_name)
        .with_attribute(KeyValue::new("telemetry.sdk.language", "rust"))
        .build();

    let provider = SdkTracerProvider::builder()
        .with_resource(resource)
        .with_batch_exporter(exporter)
        .build();

    global::set_tracer_provider(provider.clone());
    provider
}

// Usage
let tracer = global::tracer("my-service");
tracer.in_span("my-operation", |cx| {
    let span = cx.span();
    span.set_attribute(KeyValue::new("key", "value"));
    // Your code here
    span.set_status(Status::Ok);
});

Metrics (SDK)

use opentelemetry::global;
use opentelemetry_otlp::{Protocol, WithExportConfig};
use opentelemetry_sdk::metrics::{PeriodicReader, SdkMeterProvider};
use std::time::Duration;

fn init_meter_provider() -> SdkMeterProvider {
    let endpoint = get_endpoint();
    let service_name = get_service_name();

    let exporter = opentelemetry_otlp::MetricExporter::builder()
        .with_http()
        .with_protocol(Protocol::HttpJson)
        .with_endpoint(format!("{}/v1/metrics", endpoint))
        .build()
        .expect("Failed to create metric exporter");

    let reader = PeriodicReader::builder(exporter)
        .with_interval(Duration::from_secs(5))
        .build();

    let resource = Resource::builder()
        .with_service_name(service_name)
        .build();

    let provider = SdkMeterProvider::builder()
        .with_resource(resource)
        .with_reader(reader)
        .build();

    global::set_meter_provider(provider.clone());
    provider
}

// Usage
let meter = global::meter("my-service");
let counter = meter.u64_counter("http.requests").build();
counter.add(1, &[KeyValue::new("endpoint", "/api/users")]);

Logs (SDK)

use opentelemetry_otlp::{Protocol, WithExportConfig};
use opentelemetry_sdk::logs::SdkLoggerProvider;
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;

fn init_logger_provider() -> SdkLoggerProvider {
    let endpoint = get_endpoint();
    let service_name = get_service_name();

    let exporter = opentelemetry_otlp::LogExporter::builder()
        .with_http()
        .with_protocol(Protocol::HttpJson)
        .with_endpoint(format!("{}/v1/logs", endpoint))
        .build()
        .expect("Failed to create log exporter");

    let resource = Resource::builder()
        .with_service_name(service_name)
        .build();

    SdkLoggerProvider::builder()
        .with_resource(resource)
        .with_batch_exporter(exporter)
        .build()
}

// Setup tracing subscriber with OpenTelemetry bridge
fn init_tracing(logger_provider: &SdkLoggerProvider) {
    let otel_layer = OpenTelemetryTracingBridge::new(logger_provider);

    tracing_subscriber::registry()
        .with(tracing_subscriber::fmt::layer())
        .with(otel_layer)
        .init();
}

// Usage - logs via tracing macros
tracing::info!(endpoint = "/hello", "Hello endpoint called");
tracing::error!("Something went wrong");

Complete Setup Example

#[tokio::main]
async fn main() {
    // Initialize OpenTelemetry providers
    let tracer_provider = init_tracer_provider();
    let meter_provider = init_meter_provider();
    let logger_provider = init_logger_provider();

    // Setup tracing subscriber with OTel bridge
    init_tracing(&logger_provider);

    // Your application code here

    // Graceful shutdown on exit
    let _ = tracer_provider.shutdown();
    let _ = meter_provider.shutdown();
    let _ = logger_provider.shutdown();
}

Important Notes

  1. Static Names: global::tracer() and global::meter() require &'static str names.

  2. TraceContextExt: Import TraceContextExt to access span() on Context within in_span.

  3. HTTP JSON Protocol: Use Protocol::HttpJson for JSON format export.

  4. Graceful Shutdown: Always call shutdown() on providers before process exit.

  5. Tokio Runtime: Requires Tokio runtime for async batching (rt-tokio feature).

Reference

OpenTelemetry Rust

Next

SDK setup is step 3 of six. It gets bytes flowing; it does not make the telemetry good.

  1. Decide what earns a span — instrument-spans.md
  2. Add the context that makes spans answerable — instrument-attributes.md
  3. Instrument the error paths — instrument-errors.md
  4. Drive traffic yourself — drive-traffic.md
  5. Assert on what arrived — validate-traces.md

Confirm before moving on: the SDK starts before any application code that could create a span, and shutdown flushes on SIGTERM (validate-shutdown.md). Both fail silently.

references

_sections.md

architectural-patterns.md

attributes.md

cli-reference.md

context-propagation.md

custom-instrumentation.md

drive-traffic.md

instrument-attributes.md

instrument-errors.md

instrument-spans.md

lang-cpp.md

lang-dotnet.md

lang-erlang.md

lang-fastify.md

lang-go.md

lang-java.md

lang-nextjs.md

lang-nodejs.md

lang-php.md

lang-python.md

lang-ruby.md

lang-rust.md

layered-telemetry.md

nextjs-examples.md

otel-docs.md

sampling.md

setup-backend.md

setup-environment.md

troubleshoot-missing-attrs.md

troubleshoot-missing-spans.md

troubleshoot-no-data.md

troubleshoot-wrong-port.md

validate-logs.md

validate-metrics.md

validate-shutdown.md

validate-traces.md

CHANGELOG.md

SKILL.md

tile.json