Skip to main content

spin_telemetry/
lib.rs

1use std::io::IsTerminal;
2
3use anyhow::Context;
4use env::otel_logs_enabled;
5use env::otel_metrics_enabled;
6use env::otel_tracing_enabled;
7use opentelemetry_sdk::propagation::TraceContextPropagator;
8use tracing_subscriber::{EnvFilter, Layer, fmt, prelude::*, registry};
9
10mod alert_in_dev;
11pub mod detector;
12pub mod env;
13pub mod logs;
14pub mod metrics;
15mod propagation;
16pub mod traces;
17
18#[cfg(feature = "testing")]
19pub mod testing;
20
21pub use metrics::HistogramBuckets;
22pub use propagation::extract_trace_context;
23pub use propagation::inject_trace_context;
24
25/// Initializes telemetry for Spin using the [tracing] library.
26///
27/// Under the hood this involves initializing a [tracing::Subscriber] with multiple [Layer]s. One
28/// [Layer] emits [tracing] events to stderr, and another sends spans to an OTel collector. Metrics
29/// are handled separately from the tracing [Layer]s: a global OTel meter provider is registered
30/// directly, and the metric macros in [`metrics`] record to it without going through `tracing`.
31///
32/// Configuration for the OTel layers and the meter provider is pulled from the environment. This
33/// sets the global [tracing::Subscriber] and the global OTel meter provider, so it should be
34/// called early in the process before any other code that emits telemetry.
35///
36/// Examples of emitting traces from Spin:
37///
38/// ```no_run
39/// # use tracing::instrument;
40/// # use tracing::Level;
41/// #[instrument(name = "span_name", err(level = Level::INFO), fields(otel.name = "dynamically set name"))]
42/// fn func_you_want_to_trace() -> anyhow::Result<String> {
43///     Ok("Hello, world!".to_string())
44/// }
45/// ```
46///
47/// Some notes on tracing:
48///
49/// - If you don't want the span to be collected by default emit it at a trace or debug level.
50/// - Make sure you `.in_current_span()` any spawned tasks so the span context is propagated.
51/// - Use the otel.name attribute to dynamically set the span name.
52/// - Use the err argument to have instrument automatically handle errors.
53///
54/// Examples of emitting metrics from Spin:
55///
56/// ```no_run
57/// spin_telemetry::metrics::counter!(spin.metric_name = 1, metric_attribute = "value");
58/// ```
59///
60/// `histogram_buckets` lets callers override the OTel default histogram boundaries for specific
61/// metrics (e.g. those recorded on a 0.0..=1.0 scale rather than millisecond durations). Pass an
62/// empty `Vec` to use the defaults for everything.
63pub fn init(spin_version: String, histogram_buckets: Vec<HistogramBuckets>) -> anyhow::Result<()> {
64    // This layer will print all tracing library log messages to stderr.
65    let fmt_layer = fmt::layer()
66        .with_writer(std::io::stderr)
67        .with_ansi(std::io::stderr().is_terminal())
68        .with_filter(
69            // Filter directives explained here https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
70            EnvFilter::from_default_env()
71                // Wasmtime is too noisy
72                .add_directive("wasmtime_wasi_http=warn".parse()?)
73                // Watchexec is too noisy
74                .add_directive("watchexec=off".parse()?)
75                // We don't want to duplicate application logs
76                .add_directive("[{app_log}]=off".parse()?)
77                .add_directive("[{app_log_non_utf8}]=off".parse()?),
78        );
79
80    let otel_tracing_layer = if otel_tracing_enabled() {
81        Some(
82            traces::otel_tracing_layer(spin_version.clone())
83                .context("failed to initialize otel tracing")?,
84        )
85    } else {
86        None
87    };
88
89    let alert_in_dev_layer = alert_in_dev::alert_in_dev_layer();
90
91    // Build a registry subscriber with the layers we want to use.
92    registry()
93        .with(otel_tracing_layer)
94        .with(fmt_layer)
95        .with(alert_in_dev_layer)
96        .init();
97
98    // Used to propagate trace information in the standard W3C TraceContext format. Even if the otel
99    // layer is disabled we still want to propagate trace context.
100    opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
101
102    if otel_metrics_enabled() {
103        let meter_provider = metrics::metrics_provider(spin_version.clone(), histogram_buckets)
104            .context("failed to initialize otel metrics")?;
105        opentelemetry::global::set_meter_provider(meter_provider);
106    }
107
108    if otel_logs_enabled() {
109        logs::init_otel_logging_backend(spin_version)
110            .context("failed to initialize otel logging")?;
111    }
112
113    Ok(())
114}
115
116/// Build a reqwest::Client that explicitly uses rustls as the TLS backend with native root certs.
117pub(crate) fn rustls_reqwest_client() -> anyhow::Result<reqwest::Client> {
118    reqwest::Client::builder()
119        .use_rustls_tls()
120        .build()
121        .context("failed to build rustls reqwest client for OTLP exporter")
122}