1pub mod metrics;
43pub mod prelude;
44pub mod tracing;
45
46pub use cadence;
48pub use cadence_macros;
49
50use std::env;
51use thiserror::Error;
52
53#[derive(Debug, Error)]
54pub enum TelemetryError {
55 #[error("Datadog tracing disabled via DD_TRACE_ENABLED=false")]
56 DatadogDisabled,
57
58 #[error("Failed to set global subscriber: {0}")]
59 SubscriberInit(String),
60
61 #[error("Metrics disabled via METRICS_ENABLED=false")]
62 MetricsDisabled,
63
64 #[error("Failed to bind UDP socket: {0}")]
65 SocketBind(std::io::Error),
66
67 #[error("Failed to create metric sink: {0}")]
68 SinkCreation(cadence::MetricError),
69}
70
71#[derive(Debug, Clone)]
73pub struct TelemetryConfig {
74 pub datadog_enabled: bool,
76 pub dd_service: String,
78 pub dd_env: String,
80 pub dd_trace_agent_url: String,
82 pub rust_log: String,
84
85 pub metrics_enabled: bool,
87 pub statsd_host: String,
89 pub statsd_port: u16,
91 pub metrics_prefix: String,
93 pub global_tags: Vec<(String, String)>,
95
96 pub dd_logs_enabled: bool,
98 pub json_logging: bool,
100}
101
102impl Default for TelemetryConfig {
103 fn default() -> Self {
104 Self {
105 datadog_enabled: true,
106 dd_service: "sideways-service".to_string(),
107 dd_env: "development".to_string(),
108 dd_trace_agent_url: "http://localhost:8126".to_string(),
109 rust_log: "info".to_string(),
110 metrics_enabled: true,
111 statsd_host: "localhost".to_string(),
112 statsd_port: 8125,
113 metrics_prefix: "sideways".to_string(),
114 global_tags: Vec::new(),
115 dd_logs_enabled: true,
116 json_logging: false,
117 }
118 }
119}
120
121impl TelemetryConfig {
122 pub fn from_env() -> Self {
124 let mut config = Self::default();
125
126 if let Ok(enabled) = env::var("DD_TRACE_ENABLED") {
128 if enabled.to_lowercase() == "false" {
129 config.datadog_enabled = false;
130 }
131 }
132
133 if let Ok(enabled) = env::var("METRICS_ENABLED") {
135 if enabled.to_lowercase() == "false" {
136 config.metrics_enabled = false;
137 }
138 }
139
140 if let Ok(service) = env::var("DD_SERVICE") {
142 config.dd_service = service;
143 }
144 if let Ok(dd_env) = env::var("DD_ENV") {
145 config.dd_env = dd_env;
146 }
147 if let Ok(url) = env::var("DD_TRACE_AGENT_URL") {
148 config.dd_trace_agent_url = url;
149 }
150
151 if let Ok(rust_log) = env::var("RUST_LOG") {
153 config.rust_log = rust_log;
154 }
155
156 if let Ok(host) = env::var("STATSD_HOST") {
158 config.statsd_host = host;
159 }
160 if let Ok(port) = env::var("STATSD_PORT") {
161 if let Ok(port_num) = port.parse() {
162 config.statsd_port = port_num;
163 }
164 }
165 if let Ok(prefix) = env::var("METRICS_PREFIX") {
166 config.metrics_prefix = prefix;
167 }
168 if let Ok(tags_str) = env::var("STATSD_GLOBAL_TAGS") {
169 config.global_tags = Self::parse_tags(&tags_str);
170 }
171
172 if let Ok(enabled) = env::var("DD_LOGS_ENABLED") {
174 if enabled.to_lowercase() == "false" {
175 config.dd_logs_enabled = false;
176 }
177 }
178
179 if let Ok(enabled) = env::var("JSON_LOGGING") {
181 if enabled.to_lowercase() == "true" {
182 config.json_logging = true;
183 }
184 }
185
186 config
187 }
188
189 fn parse_tags(tags_str: &str) -> Vec<(String, String)> {
191 tags_str
192 .split(',')
193 .filter_map(|tag| {
194 let parts: Vec<&str> = tag.trim().splitn(2, ':').collect();
195 if parts.len() == 2 {
196 Some((parts[0].to_string(), parts[1].to_string()))
197 } else {
198 None
199 }
200 })
201 .collect()
202 }
203
204 pub fn builder() -> TelemetryConfigBuilder {
206 TelemetryConfigBuilder::default()
207 }
208}
209
210#[derive(Debug, Default)]
212pub struct TelemetryConfigBuilder {
213 config: TelemetryConfig,
214}
215
216impl TelemetryConfigBuilder {
217 pub fn datadog_enabled(mut self, enabled: bool) -> Self {
218 self.config.datadog_enabled = enabled;
219 self
220 }
221
222 pub fn dd_service(mut self, service: impl Into<String>) -> Self {
223 self.config.dd_service = service.into();
224 self
225 }
226
227 pub fn dd_env(mut self, env: impl Into<String>) -> Self {
228 self.config.dd_env = env.into();
229 self
230 }
231
232 pub fn dd_trace_agent_url(mut self, url: impl Into<String>) -> Self {
233 self.config.dd_trace_agent_url = url.into();
234 self
235 }
236
237 pub fn rust_log(mut self, filter: impl Into<String>) -> Self {
238 self.config.rust_log = filter.into();
239 self
240 }
241
242 pub fn metrics_enabled(mut self, enabled: bool) -> Self {
243 self.config.metrics_enabled = enabled;
244 self
245 }
246
247 pub fn statsd_host(mut self, host: impl Into<String>) -> Self {
248 self.config.statsd_host = host.into();
249 self
250 }
251
252 pub fn statsd_port(mut self, port: u16) -> Self {
253 self.config.statsd_port = port;
254 self
255 }
256
257 pub fn metrics_prefix(mut self, prefix: impl Into<String>) -> Self {
258 self.config.metrics_prefix = prefix.into();
259 self
260 }
261
262 pub fn global_tags(mut self, tags: Vec<(String, String)>) -> Self {
264 self.config.global_tags = tags;
265 self
266 }
267
268 pub fn with_global_tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
270 self.config.global_tags.push((key.into(), value.into()));
271 self
272 }
273
274 pub fn dd_logs_enabled(mut self, enabled: bool) -> Self {
275 self.config.dd_logs_enabled = enabled;
276 self
277 }
278
279 pub fn json_logging(mut self, enabled: bool) -> Self {
280 self.config.json_logging = enabled;
281 self
282 }
283
284 pub fn build(self) -> TelemetryConfig {
285 self.config
286 }
287}
288
289pub struct Telemetry {
291 pub tracer_provider: Option<opentelemetry_sdk::trace::SdkTracerProvider>,
293 pub logger_provider: Option<opentelemetry_sdk::logs::SdkLoggerProvider>,
295}
296
297pub async fn init_telemetry(config: TelemetryConfig) -> Telemetry {
307 eprintln!("🦀 Sideways Telemetry: Initializing...");
308
309 let (tracer_provider, logger_provider) = if config.datadog_enabled {
311 match tracing::init_datadog(&config) {
312 Ok((tp, lp)) => {
313 eprintln!("✅ Sideways Telemetry: Datadog tracing initialized");
314 if lp.is_some() {
315 eprintln!("✅ Sideways Telemetry: Datadog log ingestion initialized");
316 }
317 (Some(tp), lp)
318 }
319 Err(err) => {
320 eprintln!("⚠️ Sideways Telemetry: Datadog tracing unavailable: {}", err);
321 (None, None)
322 }
323 }
324 } else {
325 eprintln!("📊 Sideways Telemetry: Datadog tracing disabled");
326 tracing::init_console_logging(&config);
327 (None, None)
328 };
329
330 if config.metrics_enabled {
332 if let Err(err) = metrics::init_metrics(&config) {
333 eprintln!("⚠️ Sideways Telemetry: Metrics unavailable: {}", err);
334 }
335 } else {
336 eprintln!("📊 Sideways Telemetry: Metrics disabled");
337 }
338
339 Telemetry {
340 tracer_provider,
341 logger_provider,
342 }
343}