spin_telemetry/
traces.rs

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