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}