feldera_observability/
lib.rs1use actix_http::header::{HeaderName, HeaderValue};
2use awc::ClientRequest;
3use reqwest::RequestBuilder;
4use sentry::{ClientInitGuard, TransactionContext};
5use std::borrow::Cow;
6use std::env;
7
8pub fn init(dsn: &str, service_name: &str, release: &str) -> Option<ClientInitGuard> {
12 env::var("FELDERA_SENTRY_ENABLED")
13 .ok()
14 .filter(|v| v == "1")?;
15
16 const DEFAULT_SAMPLE_RATE: f32 = 1.0;
17 const DEFAULT_TRACE_SAMPLE_RATE: f32 = 0.1;
18 const MAX_BREADCRUMBS: usize = 10;
19
20 let dsn = env::var("SENTRY_DSN").ok().unwrap_or(dsn.to_string());
21 let sample_rate = env::var("SENTRY_SAMPLE_RATE")
22 .ok()
23 .and_then(|rate| rate.parse::<f32>().ok())
24 .unwrap_or(DEFAULT_SAMPLE_RATE);
25 let traces_sample_rate = env::var("SENTRY_TRACES_SAMPLE_RATE")
26 .ok()
27 .and_then(|rate| rate.parse::<f32>().ok())
28 .unwrap_or(DEFAULT_TRACE_SAMPLE_RATE);
29 let environment = env::var("SENTRY_ENVIRONMENT")
30 .unwrap_or_else(|_| String::from("dev"))
31 .into();
32 let accept_invalid_certs = environment == "ci" || environment == "dev";
33 let max_breadcrumbs = env::var("SENTRY_MAX_BREADCRUMBS")
34 .ok()
35 .and_then(|rate| rate.parse::<usize>().ok())
36 .unwrap_or(MAX_BREADCRUMBS);
37
38 let guard = sentry::init((
39 dsn,
40 sentry::ClientOptions {
41 environment: Some(environment),
42 release: Some(Cow::Owned(release.to_string())),
43 max_breadcrumbs,
44 sample_rate,
45 traces_sample_rate,
46 enable_logs: true,
47 attach_stacktrace: true,
48 accept_invalid_certs,
49 ..Default::default()
50 },
51 ));
52
53 sentry::configure_scope(|scope| scope.set_tag("service", service_name));
54 Some(guard)
55}
56
57fn sentry_enabled() -> bool {
58 sentry::Hub::current().client().is_some()
59}
60
61pub fn actix_middleware() -> sentry::integrations::actix::Sentry {
63 sentry::integrations::actix::Sentry::builder()
64 .emit_header(true)
65 .capture_server_errors(true)
66 .start_transaction(true)
67 .finish()
68}
69
70pub mod fips;
71pub mod json_logging;
72
73fn trace_header_value() -> Option<String> {
74 if !sentry_enabled() {
75 return None;
76 }
77
78 let mut header = None;
79 sentry::configure_scope(|scope| {
80 if let Some(span) = scope.get_span() {
81 header = span.iter_headers().next().map(|(_, value)| value);
82 }
83 });
84 if header.is_none() {
85 let transaction =
86 sentry::start_transaction(TransactionContext::new("http.client", "http.client"));
87 header = transaction.iter_headers().next().map(|(_, value)| value);
88 transaction.finish();
89 }
90 header
91}
92
93pub trait AwcRequestTracingExt {
95 fn with_sentry_tracing(self) -> Self;
96}
97
98impl AwcRequestTracingExt for ClientRequest {
99 fn with_sentry_tracing(mut self) -> Self {
100 if let Some(value) = trace_header_value()
101 && let Ok(header_value) = HeaderValue::from_str(&value)
102 {
103 self = self.insert_header((HeaderName::from_static("sentry-trace"), header_value));
104 }
105 self
106 }
107}
108
109pub trait ReqwestTracingExt {
111 fn with_sentry_tracing(self) -> Self;
112}
113
114impl ReqwestTracingExt for RequestBuilder {
115 fn with_sentry_tracing(self) -> Self {
116 if let Some(value) = trace_header_value()
117 && let Ok(header_value) = reqwest::header::HeaderValue::from_str(&value)
118 {
119 return self.header(
120 reqwest::header::HeaderName::from_static("sentry-trace"),
121 header_value,
122 );
123 }
124 self
125 }
126}