hyperstack_server/
telemetry.rs1use tracing_subscriber::layer::SubscriberExt;
7use tracing_subscriber::util::SubscriberInitExt;
8use tracing_subscriber::EnvFilter;
9
10#[derive(Debug, Clone)]
11pub struct TelemetryConfig {
12 pub service_name: String,
13 pub json_logs: bool,
14 #[cfg(feature = "otel")]
15 pub otlp_endpoint: Option<String>,
16}
17
18impl Default for TelemetryConfig {
19 fn default() -> Self {
20 Self {
21 service_name: "hyperstack".to_string(),
22 json_logs: false,
23 #[cfg(feature = "otel")]
24 otlp_endpoint: None,
25 }
26 }
27}
28
29impl TelemetryConfig {
30 pub fn new(service_name: impl Into<String>) -> Self {
31 Self {
32 service_name: service_name.into(),
33 ..Default::default()
34 }
35 }
36
37 pub fn with_json_logs(mut self, enabled: bool) -> Self {
38 self.json_logs = enabled;
39 self
40 }
41
42 #[cfg(feature = "otel")]
43 pub fn with_otlp_endpoint(mut self, endpoint: impl Into<String>) -> Self {
44 self.otlp_endpoint = Some(endpoint.into());
45 self
46 }
47}
48
49pub fn init(config: TelemetryConfig) -> anyhow::Result<()> {
50 let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
51
52 let registry = tracing_subscriber::registry().with(env_filter);
53
54 if config.json_logs {
55 let fmt_layer = tracing_subscriber::fmt::layer().json().flatten_event(true);
56 registry.with(fmt_layer).init();
57 } else {
58 let fmt_layer = tracing_subscriber::fmt::layer();
59 registry.with(fmt_layer).init();
60 }
61
62 Ok(())
63}
64
65#[cfg(feature = "otel")]
66pub fn init_with_otel(config: TelemetryConfig) -> anyhow::Result<TelemetryGuard> {
67 use opentelemetry::global;
68 use opentelemetry_otlp::WithExportConfig;
69 use opentelemetry_sdk::propagation::TraceContextPropagator;
70 use opentelemetry_sdk::trace::Tracer;
71 use opentelemetry_sdk::Resource;
72
73 global::set_text_map_propagator(TraceContextPropagator::new());
74
75 let endpoint = config
76 .otlp_endpoint
77 .as_deref()
78 .unwrap_or("http://localhost:4317");
79
80 let tracer: Tracer = opentelemetry_otlp::new_pipeline()
81 .tracing()
82 .with_exporter(
83 opentelemetry_otlp::new_exporter()
84 .tonic()
85 .with_endpoint(endpoint),
86 )
87 .with_trace_config(
88 opentelemetry_sdk::trace::config().with_resource(Resource::new(vec![
89 opentelemetry::KeyValue::new("service.name", config.service_name.clone()),
90 ])),
91 )
92 .install_batch(opentelemetry_sdk::runtime::Tokio)?;
93
94 let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer);
95
96 let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
97
98 let registry = tracing_subscriber::registry()
99 .with(env_filter)
100 .with(otel_layer);
101
102 if config.json_logs {
103 let fmt_layer = tracing_subscriber::fmt::layer().json().flatten_event(true);
104 registry.with(fmt_layer).init();
105 } else {
106 let fmt_layer = tracing_subscriber::fmt::layer();
107 registry.with(fmt_layer).init();
108 }
109
110 Ok(TelemetryGuard)
111}
112
113#[cfg(feature = "otel")]
114pub struct TelemetryGuard;
115
116#[cfg(feature = "otel")]
117impl Drop for TelemetryGuard {
118 fn drop(&mut self) {
119 opentelemetry::global::shutdown_tracer_provider();
120 }
121}