> ## Documentation Index
> Fetch the complete documentation index at: https://docs.appsignal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenTelemetry Rust Installation

This page shows how to send traces and metrics from your Rust application to AppSignal with OpenTelemetry. When you're done, the spans your application creates with the `tracing` crate appear on the "Performance > Traces" page in AppSignal.

Make sure to [install the AppSignal collector](https://docs.appsignal.com/opentelemetry/installation.md) before proceeding with these instructions.

## Install the OpenTelemetry crates

Use Cargo to add the OpenTelemetry SDK, the OTLP exporter, the `tracing` integration, and the crates the configuration below uses to your application's dependencies:

```sh Shell theme={null}
cargo add opentelemetry@0.28 --features trace,metrics
cargo add opentelemetry_sdk@0.28 --features trace,metrics
cargo add opentelemetry-otlp@0.28 --features http-proto,reqwest-blocking-client,reqwest-rustls,metrics
cargo add tracing@0.1 tracing-opentelemetry@0.29
cargo add tracing-subscriber@0.3 --features env-filter
cargo add nix@0.23
```

Which versions work together, and what some of these crates and features do:

* **`tracing-opentelemetry`**: its version must match the OpenTelemetry crates. Version 0.29 of `tracing-opentelemetry` works with version 0.28 of `opentelemetry`, `opentelemetry_sdk`, and `opentelemetry-otlp`. If you upgrade one, upgrade the other to a matching version.
* **`reqwest-rustls`**: this feature lets the exporter send data to `https://` collector URLs, such as a [hosted collector](https://docs.appsignal.com/collector/hosted-vs-self-hosted.md).
* **`nix`**: looks up the hostname of the machine your application runs on, which the configuration reports as `host.name`.
* **`env-filter`**: this `tracing-subscriber` feature lets you choose which spans to report with the `RUST_LOG` environment variable.

## Configure OpenTelemetry in your application

Add this `appsignal.rs` module to your application's `src` directory, next to `main.rs`. It sets up the exporters that send traces and metrics to the AppSignal collector, and the attributes AppSignal uses to recognize your application.

Before you run it, update these values at the top of `initialize_opentelemetry`:

* `name`: your AppSignal application name.
* `environment`: the environment your application runs in, such as `development`, `staging`, or `production`.
* `push_api_key`: your organization-level [Push API key](https://docs.appsignal.com/appsignal/terminology.md#push-api-key), which you can find on the [Push & deploy settings page](https://appsignal.com/redirect-to/app?to=info).
* `endpoint`: keep `http://localhost:8099` if the collector runs on the same machine as your application. Otherwise, use the address of your collector. For a hosted collector, open the ["Hosted Collector"](https://appsignal.com/redirect-to/organization?to=admin/hosted_collectors) page in your organization's settings, open your collector, and copy its "Collector URL". If the list is empty, create a collector there first.

```rust Rust theme={null}
use opentelemetry::{global, trace::TracerProvider as _, KeyValue};
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::{
    metrics::{PeriodicReader, SdkMeterProvider, Temporality},
    propagation::TraceContextPropagator,
    trace::SdkTracerProvider,
    Resource,
};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

pub struct Telemetry {
    tracer_provider: SdkTracerProvider,
    meter_provider: SdkMeterProvider,
}

impl Telemetry {
    // Send the remaining traces and metrics to the collector and stop the exporters.
    pub fn shutdown(self) {
        let _ = self.tracer_provider.shutdown();
        let _ = self.meter_provider.shutdown();
    }
}

pub fn initialize_opentelemetry(service_name: &str) -> Telemetry {
    // Replace these values with your AppSignal application name, environment
    // and push API key. These are used by the resource attributes configuration below.
    let name = "My app";
    let push_api_key = "0000-0000-0000-0000";
    let environment = "production";

    // Replace `http://localhost:8099` with the address of your AppSignal collector
    // if it's running on another host.
    let endpoint = "http://localhost:8099";

    // The version from your application's `Cargo.toml`, set when it's compiled.
    let revision = env!("CARGO_PKG_VERSION");

    let mut hostname_buffer = [0u8; 512];
    let hostname = nix::unistd::gethostname(&mut hostname_buffer)
        .map(|hostname| hostname.to_string_lossy().to_string())
        .unwrap_or_else(|_| "unknown".to_string());

    let app_path = std::env::current_dir()
        .map(|path| path.display().to_string())
        .unwrap_or_default();

    let resource = Resource::builder()
        // Customize the service name
        .with_service_name(service_name.to_string())
        .with_attributes([
            KeyValue::new("appsignal.config.name", name),
            KeyValue::new("appsignal.config.environment", environment),
            KeyValue::new("appsignal.config.push_api_key", push_api_key),
            KeyValue::new("appsignal.config.revision", revision),
            KeyValue::new("appsignal.config.language_integration", "rust"),
            KeyValue::new("appsignal.config.app_path", app_path),
            KeyValue::new("host.name", hostname),
        ])
        .build();

    global::set_text_map_propagator(TraceContextPropagator::new());

    let span_exporter = opentelemetry_otlp::SpanExporter::builder()
        .with_http()
        .with_endpoint(format!("{endpoint}/v1/traces"))
        .build()
        .expect("Failed to create the span exporter");
    let tracer_provider = SdkTracerProvider::builder()
        .with_batch_exporter(span_exporter)
        .with_resource(resource.clone())
        .build();
    global::set_tracer_provider(tracer_provider.clone());

    let metric_exporter = opentelemetry_otlp::MetricExporter::builder()
        .with_http()
        .with_endpoint(format!("{endpoint}/v1/metrics"))
        .with_temporality(Temporality::Delta)
        .build()
        .expect("Failed to create the metric exporter");
    let meter_provider = SdkMeterProvider::builder()
        .with_reader(PeriodicReader::builder(metric_exporter).build())
        .with_resource(resource)
        .build();
    global::set_meter_provider(meter_provider.clone());

    // Report the spans created with the `tracing` crate to OpenTelemetry, and
    // print your application's logs to the console.
    // Unless the `RUST_LOG` environment variable is set, the filter keeps spans
    // and logs at the `info` level and above, and only warnings and errors from
    // the OpenTelemetry crates themselves.
    let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| {
        EnvFilter::new("info,opentelemetry=warn,opentelemetry_sdk=warn,opentelemetry_otlp=warn")
    });
    let otel_layer = tracing_opentelemetry::layer()
        .with_tracer(tracer_provider.tracer(service_name.to_string()));
    tracing_subscriber::registry()
        .with(filter)
        .with(tracing_subscriber::fmt::layer())
        .with(otel_layer)
        .init();

    Telemetry {
        tracer_provider,
        meter_provider,
    }
}
```

The code reports your application's version from `Cargo.toml` as the [revision](https://docs.appsignal.com/opentelemetry/configuration/options.md#app-revision). AppSignal uses the revision to create a deploy marker each time you deploy a new version. A revision can be a version number or a Git commit SHA.

Cargo adds the version to the binary when it compiles your application. The revision is then set even when you build on a CI service, such as GitHub Actions, and run the binary without a Git repository.

Bump the version in `Cargo.toml` with each release, for example when you create a GitHub release. Each deploy then gets its own deploy marker.

## Start your application with OpenTelemetry

Call the `initialize_opentelemetry` function at the start of your application's `main` function, so spans created during startup are reported too. Pass it the name of the service that's being monitored. AppSignal groups traces by [service name](https://docs.appsignal.com/opentelemetry/configuration/options.md#service-name), so choose a name that describes this part of your application, such as "Web server" or "Background worker".

Before your application exits, call `shutdown` to send the remaining traces and metrics to the AppSignal collector.

The example below uses the Tokio runtime. If your application doesn't use Tokio yet, add it with `cargo add tokio@1 --features full`.

The `appsignal.rs` module also prints your application's logs to the console. If your application already sets up logging with `tracing_subscriber`, such as with `tracing_subscriber::fmt::init()`, remove that call. `tracing_subscriber` can only be set up once, so your application panics at startup if both run.

```rust Rust theme={null}
mod appsignal;

#[tokio::main]
async fn main() {
    // Set the name of the service that is being monitored. A common choice is the
    // name of the framework used. This is used to group traces and metrics in AppSignal.
    let telemetry = appsignal::initialize_opentelemetry("My service name");

    // Start your application here

    telemetry.shutdown();
}
```

If your application is a web server, it usually runs until it receives a stop signal, such as `SIGTERM` during a deploy. Make it shut down gracefully when it receives one, so the server stops, `main` continues, and the `shutdown` call runs. For example, use `with_graceful_shutdown` in axum. Without a graceful shutdown, your application exits before calling `shutdown`, and the traces and metrics it hasn't sent yet are lost.

## Create spans with the tracing crate

The `appsignal.rs` module reports the spans that your application creates with the [`tracing` crate](https://docs.rs/tracing) to AppSignal. For example, add the `#[tracing::instrument]` attribute to a function to create a span each time it's called. Each call to this `checkout` function creates a span named `checkout`:

```rust Rust theme={null}
#[tracing::instrument]
async fn checkout(order_id: u64) {
    // Your code here
}
```

The `#[tracing::instrument]` attribute creates spans at the `info` level by default. The `appsignal.rs` module reports spans at the `info` level and above, so these spans reach AppSignal without extra configuration. To also report `debug` spans, start your application with the `RUST_LOG` environment variable set to `debug`:

```sh Shell theme={null}
RUST_LOG=debug cargo run
```

## Test the app!

Now that all the components are connected, start your app and use it so it creates spans, for example by sending a few requests to it. Then open the ["Performance > Traces"](https://appsignal.com/redirect-to/app?to=performance/traces) page in AppSignal to see the traces arrive.

> [!NOTE]
>
> Errors that you record with the `tracing` crate don't appear on the "Errors >
> Issue list" page. The `tracing-opentelemetry` crate records them without the
> `exception.type` attribute, which AppSignal needs to report an error.

If after following our installation instructions you still don't see data in AppSignal, [let us know](mailto:support@appsignal.com?subject=OpenTelemetry%20issue) and we'll help you finalize your OpenTelemetry installation!


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.