pub mod metrics;
pub mod prelude;
pub mod tracing;
pub use cadence;
pub use cadence_macros;
use std::env;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum TelemetryError {
#[error("Datadog tracing disabled via DD_TRACE_ENABLED=false")]
DatadogDisabled,
#[error("Failed to set global subscriber: {0}")]
SubscriberInit(String),
#[error("Metrics disabled via METRICS_ENABLED=false")]
MetricsDisabled,
#[error("Failed to bind UDP socket: {0}")]
SocketBind(std::io::Error),
#[error("Failed to create metric sink: {0}")]
SinkCreation(cadence::MetricError),
}
#[derive(Debug, Clone)]
pub struct TelemetryConfig {
pub datadog_enabled: bool,
pub dd_service: String,
pub dd_env: String,
pub dd_trace_agent_url: String,
pub rust_log: String,
pub metrics_enabled: bool,
pub statsd_host: String,
pub statsd_port: u16,
pub metrics_prefix: String,
pub global_tags: Vec<(String, String)>,
pub dd_logs_enabled: bool,
pub json_logging: bool,
}
impl Default for TelemetryConfig {
fn default() -> Self {
Self {
datadog_enabled: true,
dd_service: "sideways-service".to_string(),
dd_env: "development".to_string(),
dd_trace_agent_url: "http://localhost:8126".to_string(),
rust_log: "info".to_string(),
metrics_enabled: true,
statsd_host: "localhost".to_string(),
statsd_port: 8125,
metrics_prefix: "sideways".to_string(),
global_tags: Vec::new(),
dd_logs_enabled: true,
json_logging: false,
}
}
}
impl TelemetryConfig {
pub fn from_env() -> Self {
let mut config = Self::default();
if let Ok(enabled) = env::var("DD_TRACE_ENABLED") {
if enabled.to_lowercase() == "false" {
config.datadog_enabled = false;
}
}
if let Ok(enabled) = env::var("METRICS_ENABLED") {
if enabled.to_lowercase() == "false" {
config.metrics_enabled = false;
}
}
if let Ok(service) = env::var("DD_SERVICE") {
config.dd_service = service;
}
if let Ok(dd_env) = env::var("DD_ENV") {
config.dd_env = dd_env;
}
if let Ok(url) = env::var("DD_TRACE_AGENT_URL") {
config.dd_trace_agent_url = url;
}
if let Ok(rust_log) = env::var("RUST_LOG") {
config.rust_log = rust_log;
}
if let Ok(host) = env::var("STATSD_HOST") {
config.statsd_host = host;
}
if let Ok(port) = env::var("STATSD_PORT") {
if let Ok(port_num) = port.parse() {
config.statsd_port = port_num;
}
}
if let Ok(prefix) = env::var("METRICS_PREFIX") {
config.metrics_prefix = prefix;
}
if let Ok(tags_str) = env::var("STATSD_GLOBAL_TAGS") {
config.global_tags = Self::parse_tags(&tags_str);
}
if !config.global_tags.iter().any(|(k, _)| k == "env") {
config.global_tags.push(("env".to_string(), config.dd_env.clone()));
}
if let Ok(enabled) = env::var("DD_LOGS_ENABLED") {
if enabled.to_lowercase() == "false" {
config.dd_logs_enabled = false;
}
}
if let Ok(enabled) = env::var("JSON_LOGGING") {
if enabled.to_lowercase() == "true" {
config.json_logging = true;
}
}
config
}
fn parse_tags(tags_str: &str) -> Vec<(String, String)> {
tags_str
.split(',')
.filter_map(|tag| {
let parts: Vec<&str> = tag.trim().splitn(2, ':').collect();
if parts.len() == 2 {
Some((parts[0].to_string(), parts[1].to_string()))
} else {
None
}
})
.collect()
}
pub fn builder() -> TelemetryConfigBuilder {
TelemetryConfigBuilder::default()
}
}
#[derive(Debug, Default)]
pub struct TelemetryConfigBuilder {
config: TelemetryConfig,
}
impl TelemetryConfigBuilder {
pub fn datadog_enabled(mut self, enabled: bool) -> Self {
self.config.datadog_enabled = enabled;
self
}
pub fn dd_service(mut self, service: impl Into<String>) -> Self {
self.config.dd_service = service.into();
self
}
pub fn dd_env(mut self, env: impl Into<String>) -> Self {
self.config.dd_env = env.into();
self
}
pub fn dd_trace_agent_url(mut self, url: impl Into<String>) -> Self {
self.config.dd_trace_agent_url = url.into();
self
}
pub fn rust_log(mut self, filter: impl Into<String>) -> Self {
self.config.rust_log = filter.into();
self
}
pub fn metrics_enabled(mut self, enabled: bool) -> Self {
self.config.metrics_enabled = enabled;
self
}
pub fn statsd_host(mut self, host: impl Into<String>) -> Self {
self.config.statsd_host = host.into();
self
}
pub fn statsd_port(mut self, port: u16) -> Self {
self.config.statsd_port = port;
self
}
pub fn metrics_prefix(mut self, prefix: impl Into<String>) -> Self {
self.config.metrics_prefix = prefix.into();
self
}
pub fn global_tags(mut self, tags: Vec<(String, String)>) -> Self {
self.config.global_tags = tags;
self
}
pub fn with_global_tag(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.config.global_tags.push((key.into(), value.into()));
self
}
pub fn dd_logs_enabled(mut self, enabled: bool) -> Self {
self.config.dd_logs_enabled = enabled;
self
}
pub fn json_logging(mut self, enabled: bool) -> Self {
self.config.json_logging = enabled;
self
}
pub fn build(self) -> TelemetryConfig {
self.config
}
}
pub struct Telemetry {
pub tracer_provider: Option<opentelemetry_sdk::trace::SdkTracerProvider>,
pub logger_provider: Option<opentelemetry_sdk::logs::SdkLoggerProvider>,
}
pub async fn init_telemetry(config: TelemetryConfig) -> Telemetry {
eprintln!("🦀 Sideways Telemetry: Initializing...");
let (tracer_provider, logger_provider) = if config.datadog_enabled {
match tracing::init_datadog(&config) {
Ok((tp, lp)) => {
eprintln!("✅ Sideways Telemetry: Datadog tracing initialized");
if lp.is_some() {
eprintln!("✅ Sideways Telemetry: Datadog log ingestion initialized");
}
(Some(tp), lp)
}
Err(err) => {
eprintln!("⚠️ Sideways Telemetry: Datadog tracing unavailable: {}", err);
(None, None)
}
}
} else {
eprintln!("📊 Sideways Telemetry: Datadog tracing disabled");
tracing::init_console_logging(&config);
(None, None)
};
if config.metrics_enabled {
if let Err(err) = metrics::init_metrics(&config) {
eprintln!("⚠️ Sideways Telemetry: Metrics unavailable: {}", err);
}
} else {
eprintln!("📊 Sideways Telemetry: Metrics disabled");
}
Telemetry {
tracer_provider,
logger_provider,
}
}