micromegas_telemetry_sink/
lib.rs1#![allow(
7 unsafe_code,
8 missing_docs,
9 clippy::missing_errors_doc,
10 clippy::new_without_default
11)]
12
13#[cfg(not(target_arch = "wasm32"))]
15pub mod api_key_decorator;
16#[cfg(not(target_arch = "wasm32"))]
17pub mod composite_event_sink;
18#[cfg(not(target_arch = "wasm32"))]
19pub mod http_event_sink;
20#[cfg(not(target_arch = "wasm32"))]
21pub mod local_event_sink;
22#[cfg(not(target_arch = "wasm32"))]
23pub mod log_interop;
24#[cfg(not(target_arch = "wasm32"))]
25pub mod oidc_client_credentials_decorator;
26#[cfg(not(target_arch = "wasm32"))]
27pub mod request_decorator;
28#[cfg(not(target_arch = "wasm32"))]
29pub mod stream_block;
30#[cfg(not(target_arch = "wasm32"))]
31pub mod stream_info;
32#[cfg(not(target_arch = "wasm32"))]
33pub mod system_monitor;
34#[cfg(not(target_arch = "wasm32"))]
35pub mod tracing_interop;
36
37#[cfg(target_arch = "wasm32")]
39mod console_event_sink;
40#[cfg(target_arch = "wasm32")]
41pub use console_event_sink::*;
42
43#[cfg(not(target_arch = "wasm32"))]
45mod native {
46 use std::any::TypeId;
47 use std::collections::HashMap;
48 use std::str::FromStr;
49 use std::sync::{Arc, Mutex, Weak};
50
51 use crate::log_interop::install_log_interop;
52 use crate::request_decorator::RequestDecorator;
53 use crate::tracing_interop::install_tracing_interop;
54 use micromegas_tracing::event::BoxedEventSink;
55 use micromegas_tracing::info;
56 use micromegas_tracing::{
57 event::EventSink,
58 guards::{TracingSystemGuard, TracingThreadGuard},
59 prelude::*,
60 };
61
62 use crate::composite_event_sink::CompositeSink;
63 use crate::http_event_sink::{HttpEventSink, HttpSinkConfig};
64 use crate::local_event_sink::LocalEventSink;
65 use crate::system_monitor::spawn_system_monitor;
66
67 pub mod tokio_retry {
68 pub use tokio_retry2::*;
69 }
70
71 pub mod reqwest {
72 pub use reqwest::*;
73 }
74
75 pub struct TelemetryGuardBuilder {
76 logs_buffer_size: usize,
77 metrics_buffer_size: usize,
78 threads_buffer_size: usize,
79 target_max_levels: HashMap<String, String>,
80 max_level_override: Option<LevelFilter>,
81 interop_max_level_override: Option<LevelFilter>,
82 install_log_capture: bool,
83 install_tracing_capture: bool,
84 local_sink_enabled: bool,
85 local_sink_max_level: LevelFilter,
86 telemetry_sink_url: Option<String>,
87 telemetry_sink_max_level: LevelFilter,
88 telemetry_max_queue_bytes: Option<usize>,
89 telemetry_hard_queue_bytes: Option<usize>,
90 telemetry_max_in_flight_requests: Option<usize>,
91 telemetry_request_timeout: Option<std::time::Duration>,
92 telemetry_retry_by_priority:
93 Option<[core::iter::Take<tokio_retry::strategy::ExponentialBackoff>; 4]>,
94 telemetry_make_request_decorator: Box<dyn FnOnce() -> Arc<dyn RequestDecorator> + Send>,
95 extra_sinks: HashMap<TypeId, (LevelFilter, BoxedEventSink)>,
96 system_metrics_enabled: bool,
97 default_system_properties_enabled: bool,
98 process_properties: HashMap<String, String>,
99 }
100
101 impl Default for TelemetryGuardBuilder {
102 fn default() -> Self {
103 Self {
104 logs_buffer_size: 10 * 1024 * 1024,
105 metrics_buffer_size: 1024 * 1024,
106 threads_buffer_size: 10 * 1024 * 1024,
107 local_sink_enabled: true,
108 local_sink_max_level: LevelFilter::Info,
109 telemetry_sink_url: None,
110 telemetry_sink_max_level: LevelFilter::Debug,
111 telemetry_max_queue_bytes: None,
112 telemetry_hard_queue_bytes: None,
113 telemetry_max_in_flight_requests: None,
114 telemetry_request_timeout: None,
115 telemetry_retry_by_priority: None,
116 telemetry_make_request_decorator: Box::new(|| {
117 Arc::new(crate::request_decorator::TrivialRequestDecorator {})
118 }),
119 target_max_levels: HashMap::default(),
120 max_level_override: None,
121 interop_max_level_override: None,
122 install_log_capture: false,
123 install_tracing_capture: true,
124 extra_sinks: HashMap::default(),
125 system_metrics_enabled: true,
126 default_system_properties_enabled: true,
127 process_properties: HashMap::default(),
128 }
129 }
130 }
131
132 impl TelemetryGuardBuilder {
133 #[must_use]
135 pub fn add_sink<Sink>(mut self, max_level: LevelFilter, sink: Sink) -> Self
136 where
137 Sink: EventSink + 'static,
138 {
139 let type_id = TypeId::of::<Sink>();
140
141 self.extra_sinks
142 .entry(type_id)
143 .or_insert_with(|| (max_level, Box::new(sink)));
144
145 self
146 }
147
148 #[must_use]
150 pub fn with_max_level_override(mut self, level_filter: LevelFilter) -> Self {
151 self.max_level_override = Some(level_filter);
152 self
153 }
154
155 #[must_use]
156 pub fn with_local_sink_enabled(mut self, enabled: bool) -> Self {
157 self.local_sink_enabled = enabled;
158 self
159 }
160
161 #[must_use]
162 pub fn with_interop_max_level_override(mut self, level_filter: LevelFilter) -> Self {
163 self.interop_max_level_override = Some(level_filter);
164 self
165 }
166
167 #[must_use]
168 pub fn with_install_log_capture(mut self, enabled: bool) -> Self {
169 self.install_log_capture = enabled;
170 self
171 }
172
173 #[must_use]
174 pub fn with_install_tracing_capture(mut self, enabled: bool) -> Self {
175 self.install_tracing_capture = enabled;
176 self
177 }
178
179 #[must_use]
180 pub fn with_local_sink_max_level(mut self, level_filter: LevelFilter) -> Self {
181 self.local_sink_max_level = level_filter;
182 self
183 }
184
185 #[must_use]
186 pub fn with_ctrlc_handling(self) -> Self {
187 ctrlc::set_handler(move || {
188 info!("Ctrl+C was hit!");
189 micromegas_tracing::guards::shutdown_telemetry();
190 std::process::exit(1);
191 })
192 .expect("Error setting Ctrl+C handler");
193 self
194 }
195
196 #[must_use]
201 pub fn with_max_queue_bytes(mut self, bytes: usize) -> Self {
202 self.telemetry_max_queue_bytes = Some(bytes);
203 self
204 }
205
206 #[must_use]
211 pub fn with_hard_queue_bytes(mut self, bytes: usize) -> Self {
212 self.telemetry_hard_queue_bytes = Some(bytes);
213 self
214 }
215
216 #[must_use]
220 pub fn with_max_in_flight_requests(mut self, max_in_flight_requests: usize) -> Self {
221 self.telemetry_max_in_flight_requests = Some(max_in_flight_requests);
222 self
223 }
224
225 #[must_use]
233 pub fn with_request_timeout(mut self, timeout: std::time::Duration) -> Self {
234 self.telemetry_request_timeout = Some(timeout);
235 self
236 }
237
238 #[must_use]
242 pub fn with_retry_by_priority(
243 mut self,
244 retry_by_priority: [core::iter::Take<tokio_retry::strategy::ExponentialBackoff>; 4],
245 ) -> Self {
246 self.telemetry_retry_by_priority = Some(retry_by_priority);
247 self
248 }
249
250 #[must_use]
251 pub fn with_request_decorator(
252 mut self,
253 make_decorator: Box<dyn FnOnce() -> Arc<dyn RequestDecorator> + Send>,
254 ) -> Self {
255 self.telemetry_make_request_decorator = make_decorator;
256 self
257 }
258
259 #[must_use]
284 pub fn with_auth_from_env(mut self) -> Self {
285 use crate::api_key_decorator::ApiKeyRequestDecorator;
286 use crate::oidc_client_credentials_decorator::OidcClientCredentialsDecorator;
287
288 if let Ok(decorator) = ApiKeyRequestDecorator::from_env() {
290 info!("Configured telemetry sink with API key authentication");
291 self.telemetry_make_request_decorator = Box::new(move || Arc::new(decorator));
292 return self;
293 }
294
295 if let Ok(decorator) = OidcClientCredentialsDecorator::from_env() {
297 info!("Configured telemetry sink with OIDC client credentials authentication");
298 self.telemetry_make_request_decorator = Box::new(move || Arc::new(decorator));
299 return self;
300 }
301
302 info!(
304 "Telemetry sink authentication not configured - sending unauthenticated requests"
305 );
306 self
307 }
308
309 #[must_use]
310 pub fn with_system_metrics_enabled(mut self, enabled: bool) -> Self {
311 self.system_metrics_enabled = enabled;
312 self
313 }
314
315 #[must_use]
316 pub fn with_default_system_properties_enabled(mut self, enabled: bool) -> Self {
317 self.default_system_properties_enabled = enabled;
318 self
319 }
320
321 #[must_use]
326 pub fn with_telemetry_sink_url(mut self, url: String) -> Self {
327 self.telemetry_sink_url = Some(url);
328 self
329 }
330
331 #[must_use]
337 pub fn with_process_property(mut self, key: String, value: String) -> Self {
338 self.process_properties.insert(key, value);
339 self
340 }
341
342 #[must_use]
348 pub fn with_process_properties(
349 mut self,
350 process_properties: HashMap<String, String>,
351 ) -> Self {
352 self.process_properties.extend(process_properties);
353 self
354 }
355
356 fn populate_default_system_properties(&mut self) {
357 let props = &mut self.process_properties;
358 type DefaultProperties = Vec<(&'static str, Box<dyn FnOnce() -> String>)>;
360 let defaults: DefaultProperties = vec![
361 (
362 "exe",
363 Box::new(|| {
364 std::env::current_exe()
365 .unwrap_or_default()
366 .to_string_lossy()
367 .into_owned()
368 }),
369 ),
370 ("username", Box::new(whoami::username)),
371 ("realname", Box::new(whoami::realname)),
372 ("computer", Box::new(whoami::devicename)),
373 ("distro", Box::new(whoami::distro)),
374 (
375 "cpu_brand",
376 Box::new(|| {
377 #[cfg(target_arch = "x86_64")]
378 {
379 raw_cpuid::CpuId::new()
380 .get_processor_brand_string()
381 .map_or_else(|| "unknown".to_owned(), |b| b.as_str().to_owned())
382 }
383 #[cfg(not(target_arch = "x86_64"))]
384 {
385 String::from(std::env::consts::ARCH)
386 }
387 }),
388 ),
389 (
390 "physical_core_count",
391 Box::new(|| {
392 sysinfo::System::physical_core_count()
393 .map(|c: usize| c.to_string())
394 .unwrap_or_default()
395 }),
396 ),
397 (
398 "logical_cpu_count",
399 Box::new(|| {
400 use sysinfo::{CpuRefreshKind, RefreshKind};
401 let system = sysinfo::System::new_with_specifics(
402 RefreshKind::nothing().with_cpu(CpuRefreshKind::nothing()),
403 );
404 system.cpus().len().to_string()
405 }),
406 ),
407 (
408 "total_memory",
409 Box::new(|| {
410 use sysinfo::{MemoryRefreshKind, RefreshKind};
411 let system = sysinfo::System::new_with_specifics(
412 RefreshKind::nothing()
413 .with_memory(MemoryRefreshKind::nothing().with_ram()),
414 );
415 system.total_memory().to_string()
416 }),
417 ),
418 ];
419 for (key, make_value) in defaults {
420 props.entry(key.to_string()).or_insert_with(make_value);
421 }
422 }
423
424 pub fn build(mut self) -> anyhow::Result<TelemetryGuard> {
425 if self.default_system_properties_enabled {
426 self.populate_default_system_properties();
427 }
428 let target_max_level: Vec<_> = self
429 .target_max_levels
430 .into_iter()
431 .filter(|(key, _val)| key != "MAX_LEVEL")
432 .map(|(key, val)| {
433 (
434 key,
435 LevelFilter::from_str(val.as_str()).unwrap_or(LevelFilter::Off),
436 )
437 })
438 .collect();
439
440 let guard = {
441 lazy_static::lazy_static! {
442 static ref GLOBAL_WEAK_GUARD: Mutex<Weak<TracingSystemGuard>> = Mutex::new(Weak::new());
443 }
444 let mut weak_guard = GLOBAL_WEAK_GUARD.lock().unwrap();
445 let weak = &mut *weak_guard;
446
447 if let Some(arc) = weak.upgrade() {
448 arc
449 } else {
450 let mut sinks: Vec<(LevelFilter, BoxedEventSink)> = vec![];
451 let telemetry_sink_url = self
452 .telemetry_sink_url
453 .or_else(|| std::env::var("MICROMEGAS_TELEMETRY_URL").ok())
454 .filter(|url| !url.trim().is_empty());
455
456 if let Some(url) = telemetry_sink_url {
457 let max_queue_bytes = self
458 .telemetry_max_queue_bytes
459 .or_else(|| {
460 std::env::var("MICROMEGAS_TELEMETRY_MAX_QUEUE_BYTES")
461 .ok()
462 .and_then(|v| v.parse().ok())
463 })
464 .unwrap_or(HttpSinkConfig::DEFAULT_MAX_QUEUE_BYTES);
465 let hard_queue_bytes = self
466 .telemetry_hard_queue_bytes
467 .or_else(|| {
468 std::env::var("MICROMEGAS_TELEMETRY_HARD_QUEUE_BYTES")
469 .ok()
470 .and_then(|v| v.parse().ok())
471 })
472 .unwrap_or(HttpSinkConfig::DEFAULT_HARD_QUEUE_BYTES);
473 let max_in_flight_requests = self
474 .telemetry_max_in_flight_requests
475 .or_else(|| {
476 std::env::var("MICROMEGAS_TELEMETRY_MAX_IN_FLIGHT_REQUESTS")
477 .ok()
478 .and_then(|v| v.parse().ok())
479 })
480 .unwrap_or(HttpSinkConfig::DEFAULT_MAX_IN_FLIGHT_REQUESTS);
481 let request_timeout = self
482 .telemetry_request_timeout
483 .or_else(|| {
484 std::env::var("MICROMEGAS_TELEMETRY_REQUEST_TIMEOUT_SECS")
485 .ok()
486 .and_then(|v| v.parse().ok())
487 .map(std::time::Duration::from_secs)
488 })
489 .unwrap_or(HttpSinkConfig::DEFAULT_REQUEST_TIMEOUT);
490 let retry_by_priority = self
491 .telemetry_retry_by_priority
492 .unwrap_or_else(HttpSinkConfig::default_retry_by_priority);
493 let config = HttpSinkConfig {
494 max_queue_bytes,
495 hard_queue_bytes,
496 max_in_flight_requests,
497 request_timeout,
498 retry_by_priority,
499 };
500 sinks.push((
501 self.telemetry_sink_max_level,
502 Box::new(HttpEventSink::new(
503 &url,
504 config,
505 self.telemetry_make_request_decorator,
506 )),
507 ));
508 }
509 if self.local_sink_enabled {
510 sinks.push((self.local_sink_max_level, Box::new(LocalEventSink::new())));
511 }
512 let mut extra_sinks = self.extra_sinks.into_values().collect();
513 sinks.append(&mut extra_sinks);
514
515 let sink: BoxedEventSink = Box::new(CompositeSink::new(
516 sinks,
517 target_max_level,
518 self.max_level_override,
519 ));
520
521 if self.install_log_capture {
523 install_log_interop(self.interop_max_level_override);
524 }
525 if self.install_tracing_capture {
526 install_tracing_interop(self.interop_max_level_override);
527 }
528
529 let arc = Arc::<TracingSystemGuard>::new(TracingSystemGuard::new(
530 self.logs_buffer_size,
531 self.metrics_buffer_size,
532 self.threads_buffer_size,
533 sink.into(),
534 self.process_properties,
535 std::env::var("MICROMEGAS_ENABLE_CPU_TRACING")
536 .map(|v| v == "true")
537 .unwrap_or(false), )?);
539
540 if self.system_metrics_enabled {
541 spawn_system_monitor();
542 }
543
544 *weak = Arc::<TracingSystemGuard>::downgrade(&arc);
545 arc
546 }
547 };
548 Ok(TelemetryGuard {
550 _guard: guard,
551 _thread_guard: TracingThreadGuard::new(),
552 })
553 }
554 }
555
556 pub struct TelemetryGuard {
557 _thread_guard: TracingThreadGuard,
559 _guard: Arc<TracingSystemGuard>,
560 }
561
562 impl TelemetryGuard {
563 pub fn new() -> anyhow::Result<Self> {
564 TelemetryGuardBuilder::default().build()
565 }
566 }
567}
568
569#[cfg(not(target_arch = "wasm32"))]
570pub use native::*;
571
572#[cfg(target_arch = "wasm32")]
574mod wasm {
575 use std::collections::HashMap;
576 use std::sync::Arc;
577
578 use micromegas_tracing::guards::TracingSystemGuard;
579
580 use crate::ConsoleEventSink;
581
582 pub struct TelemetryGuard {
583 _guard: Arc<TracingSystemGuard>,
584 }
585
586 pub fn init_telemetry() -> anyhow::Result<TelemetryGuard> {
587 let guard = Arc::new(TracingSystemGuard::new(
588 0,
589 0,
590 0,
591 Arc::new(ConsoleEventSink),
592 HashMap::new(),
593 false,
594 )?);
595 Ok(TelemetryGuard { _guard: guard })
596 }
597}
598
599#[cfg(target_arch = "wasm32")]
600pub use wasm::*;