Skip to main content

spin_telemetry/
traces.rs

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