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}