foxy/opentelemetry/
mod.rs1#![allow(clippy::single_match)]
19
20use std::collections::HashMap;
21use std::fmt::Display;
22use serde::{Deserialize, Serialize};
23use thiserror::Error;
24#[cfg(feature = "opentelemetry")]
25use {
26 opentelemetry_otlp::{WithTonicConfig},
27 opentelemetry::{global, KeyValue},
28 opentelemetry_sdk::{trace::SdkTracerProvider, Resource},
29 opentelemetry_otlp::{SpanExporter, WithExportConfig},
30 tonic::metadata::{MetadataMap, MetadataValue},
31 opentelemetry_sdk::propagation::TraceContextPropagator,
32 opentelemetry_semantic_conventions::attribute::{
33 SERVICE_VERSION, SERVICE_INSTANCE_ID, DEPLOYMENT_ENVIRONMENT
34 }
35};
36
37#[derive(Error, Debug)]
39pub enum OpenTelemetryError {
40 #[error("configuration error: {0}")]
42 ConfigError(#[from] crate::config::error::ConfigError),
43
44 #[error("OpenTelemetry initialization error: {0}")]
46 InitError(String),
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct OpenTelemetryConfig {
52 #[serde(default = "default_endpoint")]
54 pub endpoint: String,
55
56 #[serde(default = "default_service_name")]
58 pub service_name: String,
59
60 #[serde(default = "default_include_headers")]
62 pub include_headers: bool,
63
64 #[serde(default = "default_include_bodies")]
66 pub include_bodies: bool,
67
68 #[serde(default = "default_max_body_size")]
70 pub max_body_size: usize,
71
72 #[serde(default)]
75 pub span_annotations: HashMap<String, String>,
76
77 #[serde(default)]
80 pub collector_headers: HashMap<String, String>,
81
82 #[serde(default)]
87 pub resource_attributes: HashMap<String, String>,
88}
89
90fn default_endpoint() -> String {
91 "http://localhost:4317".to_string()
92}
93
94fn default_service_name() -> String {
95 "foxy-proxy".to_string()
96}
97
98fn default_include_headers() -> bool {
99 true
100}
101
102fn default_include_bodies() -> bool {
103 false
104}
105
106fn default_max_body_size() -> usize {
107 1024
108}
109
110impl Default for OpenTelemetryConfig {
111 fn default() -> Self {
112 Self {
113 endpoint: default_endpoint(),
114 service_name: default_service_name(),
115 include_headers: default_include_headers(),
116 include_bodies: default_include_bodies(),
117 max_body_size: default_max_body_size(),
118 span_annotations: HashMap::new(),
119 collector_headers: HashMap::new(),
120 resource_attributes: HashMap::new(),
121 }
122 }
123}
124
125impl Display for OpenTelemetryConfig {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 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)
128 }
129}
130
131#[cfg(feature = "opentelemetry")]
133pub fn init(config: Option<OpenTelemetryConfig>) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
134
135 if config.is_some() && ! config.as_ref().unwrap().endpoint.is_empty() {
136 let config_ref = config.as_ref().unwrap();
137
138 global::set_text_map_propagator(TraceContextPropagator::new());
139
140 let mut exporter_builder = SpanExporter::builder()
142 .with_tonic()
143 .with_endpoint(config_ref.endpoint.clone());
144
145 if !config_ref.collector_headers.is_empty() {
146 let mut meta = MetadataMap::with_capacity(config_ref.collector_headers.len());
147 for (k, v) in &config_ref.collector_headers {
148 if let (Ok(key), Ok(val)) = (
149 k.parse::<tonic::metadata::MetadataKey<_>>(),
150 MetadataValue::try_from(v.as_str()),
151 ) {
152 meta.insert(key, val);
153 }
154 }
155 exporter_builder = exporter_builder.with_metadata(meta);
156 }
157 let exporter = exporter_builder.build().expect("An error occurred building the OpenTelemetry exporter");
158
159 let svc_version = env!("CARGO_PKG_VERSION");
161 let deploy_env = std::env::var("FOXY_DEPLOY_ENV").unwrap_or_else(|_| "local".into());
162 let instance_id = hostname::get()
163 .ok()
164 .and_then(|h| h.into_string().ok())
165 .unwrap_or_else(|| "unknown-host".into());
166
167 let mut res_builder = Resource::builder().with_service_name(config_ref.service_name.clone())
168 .with_attribute(KeyValue::new(SERVICE_VERSION, svc_version))
169 .with_attribute(KeyValue::new(DEPLOYMENT_ENVIRONMENT, deploy_env))
170 .with_attribute(KeyValue::new(SERVICE_INSTANCE_ID, instance_id));
171
172
173 if !config_ref.resource_attributes.is_empty() {
174 let attrs = config_ref.resource_attributes.iter().map(|(k, v)| KeyValue::new(k.clone(), v.clone()));
175 res_builder = res_builder.with_attributes(attrs);
176 };
177
178 let resource = res_builder.build();
179
180 let provider = SdkTracerProvider::builder()
182 .with_batch_exporter(exporter)
183 .with_resource(resource)
184 .build();
185 global::set_tracer_provider(provider);
186 }
187
188 Ok(())
189}
190
191#[cfg(not(feature = "opentelemetry"))]
193pub fn init(_cfg: Option<&serde_json::Value>) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { Ok(()) }
194