#![allow(clippy::single_match)]
use std::collections::HashMap;
use std::fmt::Display;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[cfg(feature = "opentelemetry")]
use {
opentelemetry_otlp::{WithTonicConfig},
opentelemetry::{global, KeyValue},
opentelemetry_sdk::{trace::SdkTracerProvider, Resource},
opentelemetry_otlp::{SpanExporter, WithExportConfig},
tonic::metadata::{MetadataMap, MetadataValue},
opentelemetry_sdk::propagation::TraceContextPropagator,
opentelemetry_semantic_conventions::attribute::{
SERVICE_VERSION, SERVICE_INSTANCE_ID, DEPLOYMENT_ENVIRONMENT
}
};
#[derive(Error, Debug)]
pub enum OpenTelemetryError {
#[error("configuration error: {0}")]
ConfigError(#[from] crate::config::error::ConfigError),
#[error("OpenTelemetry initialization error: {0}")]
InitError(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OpenTelemetryConfig {
#[serde(default = "default_endpoint")]
pub endpoint: String,
#[serde(default = "default_service_name")]
pub service_name: String,
#[serde(default = "default_include_headers")]
pub include_headers: bool,
#[serde(default = "default_include_bodies")]
pub include_bodies: bool,
#[serde(default = "default_max_body_size")]
pub max_body_size: usize,
#[serde(default)]
pub span_annotations: HashMap<String, String>,
#[serde(default)]
pub collector_headers: HashMap<String, String>,
#[serde(default)]
pub resource_attributes: HashMap<String, String>,
}
fn default_endpoint() -> String {
"http://localhost:4317".to_string()
}
fn default_service_name() -> String {
"foxy-proxy".to_string()
}
fn default_include_headers() -> bool {
true
}
fn default_include_bodies() -> bool {
false
}
fn default_max_body_size() -> usize {
1024
}
impl Default for OpenTelemetryConfig {
fn default() -> Self {
Self {
endpoint: default_endpoint(),
service_name: default_service_name(),
include_headers: default_include_headers(),
include_bodies: default_include_bodies(),
max_body_size: default_max_body_size(),
span_annotations: HashMap::new(),
collector_headers: HashMap::new(),
resource_attributes: HashMap::new(),
}
}
}
impl Display for OpenTelemetryConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "OpenTelemetryConfig {{ endpoint: {}, service_name: {}, include_headers: {}, include_bodies: {}, max_body_size: {}, span_annotations: {:?}, collector_headers: {:?}, resource_attributes: {:?} }}", self.endpoint, self.service_name, self.include_headers, self.include_bodies, self.max_body_size, self.span_annotations, self.collector_headers, self.resource_attributes)
}
}
#[cfg(feature = "opentelemetry")]
pub fn init(config: Option<OpenTelemetryConfig>) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if config.is_some() && ! config.as_ref().unwrap().endpoint.is_empty() {
let config_ref = config.as_ref().unwrap();
global::set_text_map_propagator(TraceContextPropagator::new());
let mut exporter_builder = SpanExporter::builder()
.with_tonic()
.with_endpoint(config_ref.endpoint.clone());
if !config_ref.collector_headers.is_empty() {
let mut meta = MetadataMap::with_capacity(config_ref.collector_headers.len());
for (k, v) in &config_ref.collector_headers {
if let (Ok(key), Ok(val)) = (
k.parse::<tonic::metadata::MetadataKey<_>>(),
MetadataValue::try_from(v.as_str()),
) {
meta.insert(key, val);
}
}
exporter_builder = exporter_builder.with_metadata(meta);
}
let exporter = exporter_builder.build().expect("An error occurred building the OpenTelemetry exporter");
let svc_version = env!("CARGO_PKG_VERSION");
let deploy_env = std::env::var("FOXY_DEPLOY_ENV").unwrap_or_else(|_| "local".into());
let instance_id = hostname::get()
.ok()
.and_then(|h| h.into_string().ok())
.unwrap_or_else(|| "unknown-host".into());
let mut res_builder = Resource::builder().with_service_name(config_ref.service_name.clone())
.with_attribute(KeyValue::new(SERVICE_VERSION, svc_version))
.with_attribute(KeyValue::new(DEPLOYMENT_ENVIRONMENT, deploy_env))
.with_attribute(KeyValue::new(SERVICE_INSTANCE_ID, instance_id));
if !config_ref.resource_attributes.is_empty() {
let attrs = config_ref.resource_attributes.iter().map(|(k, v)| KeyValue::new(k.clone(), v.clone()));
res_builder = res_builder.with_attributes(attrs);
};
let resource = res_builder.build();
let provider = SdkTracerProvider::builder()
.with_batch_exporter(exporter)
.with_resource(resource)
.build();
global::set_tracer_provider(provider);
}
Ok(())
}
#[cfg(not(feature = "opentelemetry"))]
pub fn init(_cfg: Option<&serde_json::Value>) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { Ok(()) }