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::{fmt, prelude::*, registry, EnvFilter, Layer};
9
10mod alert_in_dev;
11pub mod detector;
12mod env;
13pub mod logs;
14pub mod metrics;
15mod propagation;
16mod traces;
17
18#[cfg(feature = "testing")]
19pub mod testing;
20
21pub use propagation::extract_trace_context;
22pub use propagation::inject_trace_context;
23
24/// Initializes telemetry for Spin using the [tracing] library.
25///
26/// Under the hood this involves initializing a [tracing::Subscriber] with multiple [Layer]s. One
27/// [Layer] emits [tracing] events to stderr, another sends spans to an OTel collector, and another
28/// sends metrics to an OTel collector.
29///
30/// Configuration for the OTel layers is pulled from the environment.
31///
32/// Examples of emitting traces from Spin:
33///
34/// ```no_run
35/// # use tracing::instrument;
36/// # use tracing::Level;
37/// #[instrument(name = "span_name", err(level = Level::INFO), fields(otel.name = "dynamically set name"))]
38/// fn func_you_want_to_trace() -> anyhow::Result<String> {
39///     Ok("Hello, world!".to_string())
40/// }
41/// ```
42///
43/// Some notes on tracing:
44///
45/// - If you don't want the span to be collected by default emit it at a trace or debug level.
46/// - Make sure you `.in_current_span()` any spawned tasks so the span context is propagated.
47/// - Use the otel.name attribute to dynamically set the span name.
48/// - Use the err argument to have instrument automatically handle errors.
49///
50/// Examples of emitting metrics from Spin:
51///
52/// ```no_run
53/// spin_telemetry::metrics::monotonic_counter!(spin.metric_name = 1, metric_attribute = "value");
54/// ```
55pub fn init(spin_version: String) -> anyhow::Result<()> {
56    // This layer will print all tracing library log messages to stderr.
57    let fmt_layer = fmt::layer()
58        .with_writer(std::io::stderr)
59        .with_ansi(std::io::stderr().is_terminal())
60        .with_filter(
61            // Filter directives explained here https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
62            EnvFilter::from_default_env()
63                // Wasmtime is too noisy
64                .add_directive("wasmtime_wasi_http=warn".parse()?)
65                // Watchexec is too noisy
66                .add_directive("watchexec=off".parse()?)
67                // We don't want to duplicate application logs
68                .add_directive("[{app_log}]=off".parse()?)
69                .add_directive("[{app_log_non_utf8}]=off".parse()?),
70        );
71
72    let otel_tracing_layer = if otel_tracing_enabled() {
73        Some(
74            traces::otel_tracing_layer(spin_version.clone())
75                .context("failed to initialize otel tracing")?,
76        )
77    } else {
78        None
79    };
80
81    let otel_metrics_layer = if otel_metrics_enabled() {
82        Some(
83            metrics::otel_metrics_layer(spin_version.clone())
84                .context("failed to initialize otel metrics")?,
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(otel_metrics_layer)
96        .with(fmt_layer)
97        .with(alert_in_dev_layer)
98        .init();
99
100    // Used to propagate trace information in the standard W3C TraceContext format. Even if the otel
101    // layer is disabled we still want to propagate trace context.
102    opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
103
104    if otel_logs_enabled() {
105        logs::init_otel_logging_backend(spin_version)
106            .context("failed to initialize otel logging")?;
107    }
108
109    Ok(())
110}