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