Skip to main content

spin_telemetry/
traces.rs

1use anyhow::bail;
2use opentelemetry::{global, trace::TracerProvider};
3use opentelemetry_sdk::{
4    resource::{EnvResourceDetector, ResourceDetector, TelemetryResourceDetector},
5    runtime::Tokio,
6    Resource,
7};
8use tracing::Subscriber;
9use tracing_opentelemetry::OpenTelemetrySpanExt as _;
10use tracing_subscriber::{registry::LookupSpan, EnvFilter, Layer};
11
12use crate::detector::SpinResourceDetector;
13use crate::env::OtlpProtocol;
14
15/// Constructs a layer for the tracing subscriber that sends spans to an OTEL collector.
16///
17/// It pulls OTEL configuration from the environment based on the variables defined
18/// [here](https://opentelemetry.io/docs/specs/otel/protocol/exporter/) and
19/// [here](https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration).
20pub(crate) fn otel_tracing_layer<S: Subscriber + for<'span> LookupSpan<'span>>(
21    spin_version: String,
22) -> anyhow::Result<impl Layer<S>> {
23    let resource = Resource::builder()
24        .with_detectors(&[
25            // Set service.name from env OTEL_SERVICE_NAME > env OTEL_RESOURCE_ATTRIBUTES > spin
26            // Set service.version from Spin metadata
27            Box::new(SpinResourceDetector::new(spin_version)) as Box<dyn ResourceDetector>,
28            // Sets fields from env OTEL_RESOURCE_ATTRIBUTES
29            Box::new(EnvResourceDetector::new()),
30            // Sets telemetry.sdk{name, language, version}
31            Box::new(TelemetryResourceDetector),
32        ])
33        .build();
34
35    // This will configure the exporter based on the OTEL_EXPORTER_* environment variables.
36    let exporter = match OtlpProtocol::traces_protocol_from_env() {
37        OtlpProtocol::Grpc => opentelemetry_otlp::SpanExporter::builder()
38            .with_tonic()
39            .build()?,
40        OtlpProtocol::HttpProtobuf => opentelemetry_otlp::SpanExporter::builder()
41            .with_http()
42            .build()?,
43        OtlpProtocol::HttpJson => bail!("http/json OTLP protocol is not supported"),
44    };
45
46    let span_processor =
47        opentelemetry_sdk::trace::span_processor_with_async_runtime::BatchSpanProcessor::builder(
48            exporter, Tokio,
49        )
50        .build();
51
52    let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
53        .with_resource(resource)
54        .with_span_processor(span_processor)
55        .build();
56
57    global::set_tracer_provider(tracer_provider.clone());
58
59    let env_filter = match EnvFilter::try_from_env("SPIN_OTEL_TRACING_LEVEL") {
60        Ok(filter) => filter,
61        // If it isn't set or it fails to parse default to info
62        Err(_) => EnvFilter::new("info"),
63    };
64
65    Ok(tracing_opentelemetry::layer()
66        .with_tracer(tracer_provider.tracer("spin"))
67        .with_threads(false)
68        .with_filter(env_filter))
69}
70
71/// Indicate whether the error is more likely caused by the guest or the host.
72///
73/// This can be used to filter errors in telemetry or logging systems.
74#[derive(Debug)]
75pub enum Blame {
76    /// The error is most likely caused by the guest.
77    Guest,
78    /// The error might have been caused by the host.
79    Host,
80}
81
82impl Blame {
83    fn as_str(&self) -> &'static str {
84        match self {
85            Blame::Guest => "guest",
86            Blame::Host => "host",
87        }
88    }
89}
90
91/// Marks the current span as an error with the given error message and optional blame.
92pub fn mark_as_error<E: std::fmt::Display>(err: &E, blame: Option<Blame>) {
93    let current_span = tracing::Span::current();
94    current_span.set_status(opentelemetry::trace::Status::error(err.to_string()));
95    if let Some(blame) = blame {
96        current_span.set_attribute("error.blame", blame.as_str());
97    }
98}