rs_zero/observability/
correlation.rs1use http::HeaderMap;
2
3use crate::observability::{
4 current_span_id, current_trace_id, request_id_from_headers, span_id_from_traceparent,
5 trace_id_from_traceparent, traceparent_from_headers,
6};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct CorrelationContext {
11 service: String,
12 transport: &'static str,
13 route: String,
14 method: String,
15 request_id: Option<String>,
16 traceparent: Option<String>,
17 trace_id: Option<String>,
18 span_id: Option<String>,
19 status: Option<String>,
20}
21
22impl CorrelationContext {
23 pub fn from_http_headers(
28 service: Option<&str>,
29 method: impl Into<String>,
30 route: Option<&str>,
31 headers: &HeaderMap,
32 ) -> Self {
33 let traceparent = traceparent_from_headers(headers);
34 Self::new(
35 service.unwrap_or("unknown"),
36 "http",
37 route.unwrap_or("unknown"),
38 method,
39 )
40 .with_request_id(request_id_from_headers(headers))
41 .with_traceparent(traceparent)
42 .with_current_span_context()
43 }
44
45 #[cfg(feature = "rpc")]
47 pub fn from_rpc_metadata(
48 service: impl Into<String>,
49 method: impl Into<String>,
50 metadata: &tonic::metadata::MetadataMap,
51 ) -> Self {
52 let method = method.into();
53 let traceparent = crate::observability::traceparent_from_metadata(metadata);
54 Self::new(service, "grpc", method.clone(), method)
55 .with_request_id(crate::observability::request_id_from_metadata(metadata))
56 .with_traceparent(traceparent)
57 .with_current_span_context()
58 }
59
60 pub fn from_rpc_parts(
62 service: impl Into<String>,
63 method: impl Into<String>,
64 request_id: Option<&str>,
65 traceparent: Option<&str>,
66 ) -> Self {
67 let method = method.into();
68 Self::new(service, "grpc", method.clone(), method)
69 .with_request_id(request_id.map(ToOwned::to_owned))
70 .with_traceparent(traceparent.map(ToOwned::to_owned))
71 .with_current_span_context()
72 }
73
74 pub fn new(
76 service: impl Into<String>,
77 transport: &'static str,
78 route: impl Into<String>,
79 method: impl Into<String>,
80 ) -> Self {
81 Self {
82 service: service.into(),
83 transport,
84 route: route.into(),
85 method: method.into(),
86 request_id: None,
87 traceparent: None,
88 trace_id: None,
89 span_id: None,
90 status: None,
91 }
92 }
93
94 pub fn with_status(mut self, status: impl Into<String>) -> Self {
96 self.status = Some(status.into());
97 self
98 }
99
100 pub fn route(&self) -> &str {
102 &self.route
103 }
104
105 pub fn traceparent(&self) -> Option<&str> {
107 self.traceparent.as_deref()
108 }
109
110 pub fn request_id(&self) -> Option<&str> {
112 self.request_id.as_deref()
113 }
114
115 pub fn trace_id(&self) -> Option<&str> {
117 self.trace_id.as_deref()
118 }
119
120 pub fn span_id(&self) -> Option<&str> {
122 self.span_id.as_deref()
123 }
124
125 #[cfg(feature = "core")]
127 pub fn into_log_fields(self) -> crate::core::logging::LogFields {
128 use crate::core::logging::LogFields;
129
130 let mut fields = LogFields::new(self.service)
131 .with_transport(self.transport)
132 .with_route(self.route)
133 .with_method(self.method);
134 if let Some(request_id) = self.request_id {
135 fields = fields.with_request_id(request_id);
136 }
137 if let Some(trace_id) = self.trace_id {
138 fields = fields.with_trace_id(trace_id);
139 }
140 if let Some(span_id) = self.span_id {
141 fields = fields.with_span_id(span_id);
142 }
143 if let Some(status) = self.status {
144 fields = fields.with_status(status);
145 }
146 fields
147 }
148
149 pub fn as_pairs(&self) -> Vec<(String, String)> {
151 let mut pairs = vec![("service".to_string(), self.service.clone())];
152 push_optional(&mut pairs, "transport", Some(self.transport));
153 push_optional(&mut pairs, "route", Some(&self.route));
154 push_optional(&mut pairs, "method", Some(&self.method));
155 push_optional(&mut pairs, "request_id", self.request_id.as_deref());
156 push_optional(&mut pairs, "trace_id", self.trace_id.as_deref());
157 push_optional(&mut pairs, "span_id", self.span_id.as_deref());
158 push_optional(&mut pairs, "status", self.status.as_deref());
159 pairs
160 }
161
162 fn with_request_id(mut self, request_id: Option<String>) -> Self {
163 self.request_id = request_id;
164 self
165 }
166
167 fn with_traceparent(mut self, traceparent: Option<String>) -> Self {
168 if let Some(value) = traceparent {
169 self.trace_id = trace_id_from_traceparent(&value).map(ToOwned::to_owned);
170 self.span_id = span_id_from_traceparent(&value).map(ToOwned::to_owned);
171 self.traceparent = Some(value);
172 }
173 self
174 }
175
176 fn with_current_span_context(mut self) -> Self {
177 if let Some(trace_id) = current_trace_id() {
178 self.trace_id = Some(trace_id);
179 }
180 if let Some(span_id) = current_span_id() {
181 self.span_id = Some(span_id);
182 }
183 self
184 }
185}
186
187fn push_optional(pairs: &mut Vec<(String, String)>, key: &str, value: Option<&str>) {
188 if let Some(value) = value
189 && !value.is_empty()
190 {
191 pairs.push((key.to_string(), value.to_string()));
192 }
193}