1use crate::InstanceState;
2use anyhow::anyhow;
3use anyhow::Result;
4use opentelemetry::trace::TraceContextExt;
5use opentelemetry_sdk::error::OTelSdkError;
6use opentelemetry_sdk::logs::LogProcessor;
7use opentelemetry_sdk::metrics::exporter::PushMetricExporter;
8use opentelemetry_sdk::trace::SpanProcessor;
9use spin_world::wasi;
10use tracing_opentelemetry::OpenTelemetrySpanExt;
11
12impl wasi::otel::tracing::Host for InstanceState {
13 async fn on_start(&mut self, context: wasi::otel::tracing::SpanContext) -> Result<()> {
14 let Some(tracing_state) = self.tracing_state.as_ref() else {
16 return Ok(());
17 };
18 let mut tracing_state = tracing_state.write().unwrap();
19
20 if tracing_state.original_host_span_id.is_none() {
22 tracing_state.original_host_span_id = Some(
23 tracing::Span::current()
24 .context()
25 .span()
26 .span_context()
27 .span_id(),
28 );
29 }
30
31 let span_context: opentelemetry::trace::SpanContext = context.into();
33 tracing_state
34 .guest_span_contexts
35 .insert(span_context.span_id(), span_context);
36
37 Ok(())
38 }
39
40 async fn on_end(&mut self, span_data: wasi::otel::tracing::SpanData) -> Result<()> {
41 let Some(tracing_state) = self.tracing_state.as_ref() else {
43 return Ok(());
44 };
45 let mut tracing_state = tracing_state.write().unwrap();
46
47 let span_context: opentelemetry::trace::SpanContext = span_data.span_context.clone().into();
48 let span_id: opentelemetry::trace::SpanId = span_context.span_id();
49
50 if tracing_state
51 .guest_span_contexts
52 .shift_remove(&span_id)
53 .is_none()
54 {
55 Err(anyhow!("Trying to end a span that was not started"))?;
56 }
57
58 tracing_state.span_processor.on_end(span_data.into());
59
60 Ok(())
61 }
62
63 async fn outer_span_context(&mut self) -> Result<wasi::otel::tracing::SpanContext> {
64 Ok(tracing::Span::current()
65 .context()
66 .span()
67 .span_context()
68 .clone()
69 .into())
70 }
71}
72
73impl wasi::otel::metrics::Host for InstanceState {
74 async fn export(
75 &mut self,
76 metrics: wasi::otel::metrics::ResourceMetrics,
77 ) -> anyhow::Result<Result<(), wasi::otel::metrics::Error>> {
78 let Some(metric_exporter) = self.metric_exporter.as_ref() else {
80 return Ok(Ok(()));
81 };
82
83 match metric_exporter.export(&mut metrics.into()).await {
84 Ok(_) => Ok(Ok(())),
85 Err(e) => match e {
86 OTelSdkError::AlreadyShutdown => {
87 let msg = "Shutdown has already been invoked";
88 tracing::error!(msg);
89 Ok(Err(msg.to_string()))
90 }
91 OTelSdkError::InternalFailure(e) => {
92 let detailed_msg = format!("Internal failure: {}", e);
93 tracing::error!(detailed_msg);
94 Ok(Err("Internal failure.".to_string()))
95 }
96 OTelSdkError::Timeout(d) => {
97 let detailed_msg = format!("Operation timed out after {} seconds", d.as_secs());
98 tracing::error!(detailed_msg);
99 Ok(Err("Operation timed out.".to_string()))
100 }
101 },
102 }
103 }
104}
105
106impl wasi::otel::logs::Host for InstanceState {
107 async fn on_emit(&mut self, data: wasi::otel::logs::LogRecord) -> anyhow::Result<()> {
108 let Some(log_processor) = self.log_processor.as_ref() else {
110 return Ok(());
111 };
112
113 let (mut record, scope) = spin_world::wasi_otel::parse_wasi_log_record(data);
114 log_processor.emit(&mut record, &scope);
115 Ok(())
116 }
117}