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