use opentelemetry::trace::TracerProvider as _;
use opentelemetry_otlp::{SpanExporter, WithExportConfig as _};
use opentelemetry_sdk::Resource;
use opentelemetry_sdk::trace::{SdkTracer, SdkTracerProvider};
use super::ObserveError;
pub const DEFAULT_ENDPOINT: &str = "http://127.0.0.1:4317";
#[derive(Debug)]
pub struct Telemetry {
provider: SdkTracerProvider,
service_name: String,
}
impl Telemetry {
#[must_use]
pub fn builder(service_name: impl Into<String>) -> TelemetryBuilder {
TelemetryBuilder {
service_name: service_name.into(),
endpoint: None,
timeout: None,
}
}
#[must_use]
pub fn tracer(&self) -> SdkTracer {
self.provider.tracer(self.service_name.clone())
}
#[must_use]
pub fn tracing_layer<S>(&self) -> tracing_opentelemetry::OpenTelemetryLayer<S, SdkTracer>
where
S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
{
tracing_opentelemetry::layer().with_tracer(self.tracer())
}
pub fn shutdown(self) -> Result<(), ObserveError> {
self.provider
.shutdown()
.map_err(|_| ObserveError::Telemetry {
reason: "the tracer provider did not shut down cleanly",
})
}
}
#[derive(Debug, Clone)]
pub struct TelemetryBuilder {
service_name: String,
endpoint: Option<String>,
timeout: Option<std::time::Duration>,
}
impl TelemetryBuilder {
#[must_use]
pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.endpoint = Some(endpoint.into());
self
}
#[must_use]
pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn build(self) -> Result<Telemetry, ObserveError> {
let mut exporter = SpanExporter::builder().with_tonic();
if let Some(endpoint) = &self.endpoint {
exporter = exporter.with_endpoint(endpoint.clone());
}
if let Some(timeout) = self.timeout {
exporter = exporter.with_timeout(timeout);
}
let exporter = exporter.build().map_err(|_| ObserveError::Telemetry {
reason: "the OTLP span exporter could not be built",
})?;
let resource = Resource::builder()
.with_service_name(self.service_name.clone())
.build();
let provider = SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.with_resource(resource)
.build();
Ok(Telemetry {
provider,
service_name: self.service_name,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_builder_keeps_the_service_name_and_endpoint() {
let builder = Telemetry::builder("checkout").endpoint("http://collector:4317");
assert_eq!(builder.service_name, "checkout");
assert_eq!(builder.endpoint.as_deref(), Some("http://collector:4317"));
}
}