#insight #applications #axum #tracing #appinsights

axum-insights

An Azure Application Insights exporter for axum via tracing

8 releases

0.3.2 Oct 25, 2023
0.3.1 Oct 25, 2023
0.2.3 Sep 1, 2023
0.1.0 Aug 30, 2023

#1560 in Web programming

MIT license

62KB
779 lines

Build and Test codecov Version Crates.io Documentation Rust License:MIT

axum-insights

An Azure Application Insights exporter for axum via tracing.

Usage

This library is meant to be used as a layer for axum. It will automatically instrument your axum application, and send telemetry to Azure Application Insights. As the ecosystem matures, more features will be added.

Example

The following example is a "complete" example, which means that it includes all of the optional features of this library.

use serde::{Serialize, Deserialize};
use axum::Router;
use axum_insights::AppInsights;
use axum_insights::AppInsightsError;
use tracing_subscriber::filter::LevelFilter;
use std::collections::HashMap;
use tracing::Instrument;

// Define some helper types for the example.

#[derive(Default, Serialize, Deserialize, Clone)]
struct WebError {
    message: String,
}

impl AppInsightsError for WebError {
    fn message(&self) -> Option<String> {
        Some(self.message.clone())
    }

    fn backtrace(&self) -> Option<String> {
        None
    }
}

// Set up the exporter, and get the `tower::Service` layer.

let telemetry_layer = AppInsights::default()
    // Accepts an optional connection string.  If None, then no telemetry is sent.
    .with_connection_string(None)
    // Sets the service namespace and name.  Default is empty.
    .with_service_config("namespace", "name")
    // Sets the HTTP client to use for sending telemetry.  Default is reqwest async client.
    .with_client(reqwest::Client::new())
    // Sets whether or not live metrics are collected.  Default is false.
    .with_live_metrics(true)
    // Sets the sample rate for telemetry.  Default is 1.0.
    .with_sample_rate(1.0)
    // Sets the minimum level for telemetry.  Default is INFO.
    .with_minimum_level(LevelFilter::INFO)
    // Sets the subscriber to use for telemetry.  Default is a new subscriber.
    .with_subscriber(tracing_subscriber::registry())
    // Sets the runtime to use for telemetry.  Default is Tokio.
    .with_runtime(opentelemetry::runtime::Tokio)
    // Sets whether or not to catch panics, and emit a trace for them.  Default is false.
    .with_catch_panic(true)
    // Sets whether or not to make this telemetry layer a noop.  Default is false.
    .with_noop(true)
    // Sets a function to extract extra fields from the request.  Default is no extra fields.
    .with_field_mapper(|parts| {
        let mut map = HashMap::new();
        map.insert("extra_field".to_owned(), "extra_value".to_owned());
        map
    })
    // Sets a function to extract extra fields from a panic.  Default is a default error.
    .with_panic_mapper(|panic| {
        (500, WebError { message: panic })
    })
    // Sets a function to determine the success-iness of a status.  Default is (100 - 399 => true).
    .with_success_filter(|status| {
        status.is_success() || status.is_redirection() || status.is_informational() || status == http::StatusCode::NOT_FOUND
    })
    // Sets the common error type for the application, and will automatically extract information from handlers that return that error.
    .with_error_type::<WebError>()
    .build_and_set_global_default()
    .unwrap()
    .layer();

// Add the layer to your app.

// You likely will not need to specify `Router<()>` in your implementation.  This is just for the example.
let app: Router<()> = Router::new()
    // ...
    .layer(telemetry_layer);

// Then, in a handler, you would use the `tracing` macros to emit telemetry.

use axum::response::IntoResponse;
use axum::Json;
use tracing::{Level, instrument, debug, error, info, warn, event};

// Instrument async handlers to get method-specific tracing.
#[instrument]
async fn handler(Json(body): Json<String>) -> Result<impl IntoResponse, WebError> {
    // Emit events using the `tracing` macros.
    debug!("Debug message");
    info!("Info message");
    warn!("Warn message");
    error!("Error message");
    event!(name: "exception", Level::ERROR, exception.message = "error message");

    // Create new spans using the `tracing` macros.
    let span = tracing::info_span!("DB Query");
    
    db_query().instrument(span).await;
    
    if body == "error" {
        return Err(WebError { message: "Error".to_owned() });
    }

    Ok(())
}

async fn db_query() {
    // ...
}

Acknowledgements

This library depends on individual efforts of other maintainers such as:

Test

cargo test --features web

Dependencies

~17–32MB
~511K SLoC