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}