Skip to content

Commit f3a6add

Browse files
committed
feat(factor-otel): adding metrics
Signed-off-by: Andrew Steurer <[email protected]>
1 parent 0e2555f commit f3a6add

File tree

14 files changed

+2300
-1167
lines changed

14 files changed

+2300
-1167
lines changed

Cargo.lock

Lines changed: 1182 additions & 991 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,9 @@ indexmap = "2"
146146
itertools = "0.14"
147147
lazy_static = "1.5"
148148
opentelemetry = "0.28"
149-
opentelemetry-otlp = "0.28"
150-
opentelemetry_sdk = "0.28"
149+
# The default `reqwest-blocking-client` causes a runtime panic
150+
opentelemetry-otlp = { version = "0.28", default-features = false, features = ["http-proto", "reqwest-client", "logs"]}
151+
opentelemetry_sdk = {version = "0.28", features = ["experimental_metrics_periodicreader_with_async_runtime", "experimental_trace_batch_span_processor_with_async_runtime"]}
151152
path-absolutize = "3"
152153
pin-project-lite = "0.2.16"
153154
quote = "1"

crates/factor-otel/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ anyhow = { workspace = true }
99
indexmap = "2.2.6"
1010
opentelemetry = { workspace = true }
1111
opentelemetry_sdk = { workspace = true }
12-
opentelemetry-otlp = { workspace = true, features = ["http-proto", "http", "reqwest-client"] }
12+
opentelemetry-otlp = { workspace = true }
1313
spin-core = { path = "../core" }
1414
spin-factors = { path = "../factors" }
1515
spin-resource-table = { path = "../table" }
@@ -22,4 +22,4 @@ tracing-opentelemetry = { workspace = true }
2222
toml = "0.5"
2323

2424
[lints]
25-
workspace = true
25+
workspace = true

crates/factor-otel/src/host.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
use anyhow::anyhow;
22
use anyhow::Result;
33
use opentelemetry::trace::TraceContextExt;
4+
use opentelemetry_sdk::metrics::exporter::PushMetricExporter;
45
use opentelemetry_sdk::trace::SpanProcessor;
5-
use spin_world::wasi::otel::tracing as wasi_otel;
6-
6+
use spin_world::wasi;
77
use tracing_opentelemetry::OpenTelemetrySpanExt;
88

99
use crate::InstanceState;
1010

11-
impl wasi_otel::Host for InstanceState {
12-
async fn on_start(&mut self, context: wasi_otel::SpanContext) -> Result<()> {
11+
impl wasi::otel::tracing::Host for InstanceState {
12+
async fn on_start(&mut self, context: wasi::otel::tracing::SpanContext) -> Result<()> {
1313
let mut state = self.state.write().unwrap();
1414

1515
// Before we do anything make sure we track the original host span ID for reparenting
@@ -32,7 +32,7 @@ impl wasi_otel::Host for InstanceState {
3232
Ok(())
3333
}
3434

35-
async fn on_end(&mut self, span_data: wasi_otel::SpanData) -> Result<()> {
35+
async fn on_end(&mut self, span_data: wasi::otel::tracing::SpanData) -> Result<()> {
3636
let mut state = self.state.write().unwrap();
3737

3838
let span_context: opentelemetry::trace::SpanContext = span_data.span_context.clone().into();
@@ -42,12 +42,12 @@ impl wasi_otel::Host for InstanceState {
4242
Err(anyhow!("Trying to end a span that was not started"))?;
4343
}
4444

45-
self.processor.on_end(span_data.into());
45+
self.span_processor.on_end(span_data.into());
4646

4747
Ok(())
4848
}
4949

50-
async fn outer_span_context(&mut self) -> Result<wasi_otel::SpanContext> {
50+
async fn outer_span_context(&mut self) -> Result<wasi::otel::tracing::SpanContext> {
5151
Ok(tracing::Span::current()
5252
.context()
5353
.span()
@@ -56,3 +56,16 @@ impl wasi_otel::Host for InstanceState {
5656
.into())
5757
}
5858
}
59+
60+
impl wasi::otel::metrics::Host for InstanceState {
61+
async fn collect(
62+
&mut self,
63+
metrics: wasi::otel::metrics::ResourceMetrics,
64+
) -> spin_core::wasmtime::Result<std::result::Result<(), wasi::otel::metrics::OtelError>> {
65+
let mut rm: opentelemetry_sdk::metrics::data::ResourceMetrics = metrics.into();
66+
match self.metric_exporter.export(&mut rm).await {
67+
Ok(_) => Ok(Ok(())),
68+
Err(e) => Ok(Err(e.into())),
69+
}
70+
}
71+
}

crates/factor-otel/src/lib.rs

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
mod host;
22

3-
use std::{
4-
sync::{Arc, RwLock},
5-
};
6-
73
use anyhow::bail;
84
use indexmap::IndexMap;
95
use opentelemetry::{
106
trace::{SpanContext, SpanId, TraceContextExt},
117
Context,
128
};
9+
use opentelemetry_otlp::MetricExporter;
1310
use opentelemetry_sdk::{
14-
resource::{EnvResourceDetector, TelemetryResourceDetector},
15-
trace::{BatchSpanProcessor, SpanProcessor},
11+
resource::{EnvResourceDetector, ResourceDetector, TelemetryResourceDetector},
12+
runtime::Tokio,
13+
trace::{span_processor_with_async_runtime::BatchSpanProcessor, SpanProcessor},
1614
Resource,
1715
};
1816
use spin_factors::{Factor, FactorData, PrepareContext, RuntimeFactors, SelfInstanceBuilder};
1917
use spin_telemetry::{detector::SpinResourceDetector, env::OtlpProtocol};
18+
use std::sync::{Arc, RwLock};
2019
use tracing_opentelemetry::OpenTelemetrySpanExt;
2120

2221
pub struct OtelFactor {
23-
processor: Arc<BatchSpanProcessor>,
22+
span_processor: Arc<BatchSpanProcessor<Tokio>>,
23+
metric_exporter: Arc<MetricExporter>,
2424
}
2525

2626
impl Factor for OtelFactor {
@@ -30,6 +30,7 @@ impl Factor for OtelFactor {
3030

3131
fn init(&mut self, ctx: &mut impl spin_factors::InitContext<Self>) -> anyhow::Result<()> {
3232
ctx.link_bindings(spin_world::wasi::otel::tracing::add_to_linker::<_, FactorData<Self>>)?;
33+
ctx.link_bindings(spin_world::wasi::otel::metrics::add_to_linker::<_, FactorData<Self>>)?;
3334
Ok(())
3435
}
3536

@@ -49,15 +50,31 @@ impl Factor for OtelFactor {
4950
guest_span_contexts: Default::default(),
5051
original_host_span_id: None,
5152
})),
52-
processor: self.processor.clone(),
53+
span_processor: self.span_processor.clone(),
54+
metric_exporter: self.metric_exporter.clone(),
5355
})
5456
}
5557
}
5658

5759
impl OtelFactor {
5860
pub fn new() -> anyhow::Result<Self> {
61+
// This is a hack b/c we know the version of this crate will be the same as the version of Spin
62+
let spin_version = env!("CARGO_PKG_VERSION").to_string();
63+
64+
let resource = Resource::builder()
65+
.with_detectors(&[
66+
// Set service.name from env OTEL_SERVICE_NAME > env OTEL_RESOURCE_ATTRIBUTES > spin
67+
// Set service.version from Spin metadata
68+
Box::new(SpinResourceDetector::new(spin_version)) as Box<dyn ResourceDetector>,
69+
// Sets fields from env OTEL_RESOURCE_ATTRIBUTES
70+
Box::new(EnvResourceDetector::new()),
71+
// Sets telemetry.sdk{name, language, version}
72+
Box::new(TelemetryResourceDetector),
73+
])
74+
.build();
75+
5976
// This will configure the exporter based on the OTEL_EXPORTER_* environment variables.
60-
let exporter = match OtlpProtocol::traces_protocol_from_env() {
77+
let span_exporter = match OtlpProtocol::traces_protocol_from_env() {
6178
OtlpProtocol::Grpc => opentelemetry_otlp::SpanExporter::builder()
6279
.with_tonic()
6380
.build()?,
@@ -67,32 +84,31 @@ impl OtelFactor {
6784
OtlpProtocol::HttpJson => bail!("http/json OTLP protocol is not supported"),
6885
};
6986

70-
let mut processor = opentelemetry_sdk::trace::BatchSpanProcessor::builder(exporter).build();
87+
let mut span_processor = BatchSpanProcessor::builder(span_exporter, Tokio).build();
7188

72-
// This is a hack b/c we know the version of this crate will be the same as the version of Spin
73-
let spin_version = env!("CARGO_PKG_VERSION").to_string();
89+
span_processor.set_resource(&resource);
7490

75-
let detectors: &[Box<dyn opentelemetry_sdk::resource::ResourceDetector>; 3] = &[
76-
// Set service.name from env OTEL_SERVICE_NAME > env OTEL_RESOURCE_ATTRIBUTES > spin
77-
// Set service.version from Spin metadata
78-
Box::new(SpinResourceDetector::new(spin_version)),
79-
// Sets fields from env OTEL_RESOURCE_ATTRIBUTES
80-
Box::new(EnvResourceDetector::new()),
81-
// Sets telemetry.sdk{name, language, version}
82-
Box::new(TelemetryResourceDetector),
83-
];
84-
85-
processor.set_resource(&Resource::builder().with_detectors(detectors).build());
91+
let metric_exporter = match OtlpProtocol::metrics_protocol_from_env() {
92+
OtlpProtocol::Grpc => opentelemetry_otlp::MetricExporter::builder()
93+
.with_tonic()
94+
.build()?,
95+
OtlpProtocol::HttpProtobuf => opentelemetry_otlp::MetricExporter::builder()
96+
.with_http()
97+
.build()?,
98+
OtlpProtocol::HttpJson => bail!("http/json OTLP protocol is not supported"),
99+
};
86100

87101
Ok(Self {
88-
processor: Arc::new(processor),
102+
span_processor: Arc::new(span_processor),
103+
metric_exporter: Arc::new(metric_exporter),
89104
})
90105
}
91106
}
92107

93108
pub struct InstanceState {
94109
pub(crate) state: Arc<RwLock<State>>,
95-
pub(crate) processor: Arc<BatchSpanProcessor>,
110+
pub(crate) span_processor: Arc<BatchSpanProcessor<Tokio>>,
111+
pub(crate) metric_exporter: Arc<MetricExporter>,
96112
}
97113

98114
impl SelfInstanceBuilder for InstanceState {}

crates/telemetry/src/metrics.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use anyhow::{bail, Result};
22
use opentelemetry::global;
33
use opentelemetry_sdk::{
4-
metrics::{PeriodicReader, SdkMeterProvider},
4+
metrics::{periodic_reader_with_async_runtime::PeriodicReader, SdkMeterProvider},
55
resource::{EnvResourceDetector, ResourceDetector, TelemetryResourceDetector},
6+
runtime::Tokio,
67
Resource,
78
};
89
use tracing::Subscriber;
@@ -45,7 +46,7 @@ pub(crate) fn otel_metrics_layer<S: Subscriber + for<'span> LookupSpan<'span>>(
4546
OtlpProtocol::HttpJson => bail!("http/json OTLP protocol is not supported"),
4647
};
4748

48-
let reader = PeriodicReader::builder(exporter).build();
49+
let reader = PeriodicReader::builder(exporter, Tokio).build();
4950
let meter_provider = SdkMeterProvider::builder()
5051
.with_reader(reader)
5152
.with_resource(resource)

crates/telemetry/src/traces.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use anyhow::bail;
22
use opentelemetry::{global, trace::TracerProvider};
33
use opentelemetry_sdk::{
44
resource::{EnvResourceDetector, ResourceDetector, TelemetryResourceDetector},
5+
runtime::Tokio,
56
Resource,
67
};
78
use tracing::Subscriber;
@@ -42,7 +43,11 @@ pub(crate) fn otel_tracing_layer<S: Subscriber + for<'span> LookupSpan<'span>>(
4243
OtlpProtocol::HttpJson => bail!("http/json OTLP protocol is not supported"),
4344
};
4445

45-
let span_processor = opentelemetry_sdk::trace::BatchSpanProcessor::builder(exporter).build();
46+
let span_processor =
47+
opentelemetry_sdk::trace::span_processor_with_async_runtime::BatchSpanProcessor::builder(
48+
exporter, Tokio,
49+
)
50+
.build();
4651

4752
let tracer_provider = opentelemetry_sdk::trace::SdkTracerProvider::builder()
4853
.with_resource(resource)

0 commit comments

Comments
 (0)