1use std::{collections::HashMap, time::Duration};
20
21use color_eyre::eyre::Context;
22use eyes_subscriber::{EyesLayer, EyesSubscriberBuilder};
23use opentelemetry_otlp::WithExportConfig;
24use sentry::ClientInitGuard;
25use tracing_opentelemetry::OpenTelemetryLayer;
26use tracing_subscriber::{
27 EnvFilter, Layer as _, Registry, layer::SubscriberExt as _, util::SubscriberInitExt as _,
28};
29use tracing_tree::HierarchicalLayer;
30use uuid::Uuid;
31
32pub use eyes_subscriber::EyesShutdownHandle;
34
35pub fn setup_sentry() -> Option<ClientInitGuard> {
36 let git_commit: Option<std::borrow::Cow<_>> =
37 option_env!("VERGEN_GIT_SHA").map(std::convert::Into::into);
38 let release_name =
39 git_commit.unwrap_or_else(|| sentry::release_name!().unwrap_or_else(|| "dev".into()));
40
41 if let Ok(sentry_dsn) = std::env::var("SENTRY_DSN") {
42 println!("Sentry enabled");
43
44 Some(sentry::init((
45 sentry_dsn,
46 sentry::ClientOptions {
47 traces_sample_rate: 0.5,
48 release: Some(release_name),
49 ..Default::default()
50 },
51 )))
52 } else {
53 println!("Sentry not configured in this environment");
54
55 None
56 }
57}
58
59pub fn setup_tracing(crate_name: &str) -> color_eyre::Result<Option<EyesShutdownHandle>> {
73 let rust_log = std::env::var("RUST_LOG")
74 .unwrap_or_else(|_| format!("info,{crate_name}=trace,tower_http=debug,serenity=error"));
75
76 let env_filter = EnvFilter::builder().parse(&rust_log).wrap_err_with(|| {
77 color_eyre::eyre::eyre!("Couldn't create env filter from {}", rust_log)
78 })?;
79
80 let opentelemetry_layer = if let Ok(honeycomb_key) = std::env::var("HONEYCOMB_API_KEY") {
81 let mut map = HashMap::<String, String>::new();
82 map.insert("x-honeycomb-team".to_string(), honeycomb_key);
83 map.insert("x-honeycomb-dataset".to_string(), "coreyja.com".to_string());
84
85 let tracer = opentelemetry_otlp::new_pipeline()
86 .tracing()
87 .with_exporter(
88 opentelemetry_otlp::new_exporter()
89 .http()
90 .with_endpoint("https://api.honeycomb.io/v1/traces")
91 .with_timeout(Duration::from_secs(3))
92 .with_headers(map),
93 )
94 .install_batch(opentelemetry_sdk::runtime::Tokio)?;
95
96 let opentelemetry_layer = OpenTelemetryLayer::new(tracer);
97 println!("Honeycomb layer configured");
98
99 Some(opentelemetry_layer)
100 } else {
101 println!("Skipping Honeycomb layer");
102
103 None
104 };
105
106 let (eyes_layer, eyes_shutdown_handle) = setup_eyes_layer()?;
108
109 let stdout_layer = if std::env::var("JSON_LOGS").is_ok() {
110 println!("Logging to STDOUT as JSON");
111
112 tracing_subscriber::fmt::layer()
113 .json()
114 .with_current_span(true)
115 .boxed()
116 } else {
117 let hierarchical = HierarchicalLayer::default()
118 .with_writer(std::io::stdout)
119 .with_indent_lines(true)
120 .with_indent_amount(2)
121 .with_thread_names(true)
122 .with_thread_ids(true)
123 .with_verbose_exit(true)
124 .with_verbose_entry(true)
125 .with_targets(true);
126
127 println!("Logging to STDOUT as hierarchical");
128
129 hierarchical.boxed()
130 };
131
132 Registry::default()
133 .with(stdout_layer)
134 .with(opentelemetry_layer)
135 .with(eyes_layer)
136 .with(env_filter)
137 .try_init()?;
138
139 Ok(eyes_shutdown_handle)
140}
141
142fn setup_eyes_layer() -> color_eyre::Result<(Option<EyesLayer>, Option<EyesShutdownHandle>)> {
144 let org_id = std::env::var("EYES_ORG_ID").ok();
145 let app_id = std::env::var("EYES_APP_ID").ok();
146
147 match (org_id, app_id) {
148 (Some(org_id_str), Some(app_id_str)) => {
149 let org_id = Uuid::parse_str(&org_id_str)
150 .wrap_err_with(|| format!("Invalid EYES_ORG_ID: {org_id_str}"))?;
151 let app_id = Uuid::parse_str(&app_id_str)
152 .wrap_err_with(|| format!("Invalid EYES_APP_ID: {app_id_str}"))?;
153
154 let (layer, shutdown_handle) = EyesSubscriberBuilder::build_from_env(org_id, app_id)
155 .wrap_err("Failed to build Eyes subscriber")?;
156
157 let eyes_url = std::env::var("EYES_URL")
158 .unwrap_or_else(|_| "https://eyes.coreyja.com".to_string());
159 println!("Eyes layer configured (org: {org_id}, app: {app_id}, url: {eyes_url})");
160
161 Ok((Some(layer), Some(shutdown_handle)))
162 }
163 (Some(_), None) => {
164 println!("Skipping Eyes layer: EYES_ORG_ID set but EYES_APP_ID missing");
165 Ok((None, None))
166 }
167 (None, Some(_)) => {
168 println!("Skipping Eyes layer: EYES_APP_ID set but EYES_ORG_ID missing");
169 Ok((None, None))
170 }
171 (None, None) => {
172 println!("Skipping Eyes layer");
173 Ok((None, None))
174 }
175 }
176}