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 service(&self) -> &str {
102 &self.service
103 }
104
105 pub fn transport(&self) -> &'static str {
107 self.transport
108 }
109
110 pub fn route(&self) -> &str {
112 &self.route
113 }
114
115 pub fn method(&self) -> &str {
117 &self.method
118 }
119
120 pub fn traceparent(&self) -> Option<&str> {
122 self.traceparent.as_deref()
123 }
124
125 pub fn request_id(&self) -> Option<&str> {
127 self.request_id.as_deref()
128 }
129
130 pub fn trace_id(&self) -> Option<&str> {
132 self.trace_id.as_deref()
133 }
134
135 pub fn span_id(&self) -> Option<&str> {
137 self.span_id.as_deref()
138 }
139
140 #[cfg(feature = "core")]
142 pub fn into_log_fields(self) -> crate::core::logging::LogFields {
143 use crate::core::logging::LogFields;
144
145 let mut fields = LogFields::new(self.service)
146 .with_transport(self.transport)
147 .with_route(self.route)
148 .with_method(self.method);
149 if let Some(request_id) = self.request_id {
150 fields = fields.with_request_id(request_id);
151 }
152 if let Some(trace_id) = self.trace_id {
153 fields = fields.with_trace_id(trace_id);
154 }
155 if let Some(span_id) = self.span_id {
156 fields = fields.with_span_id(span_id);
157 }
158 if let Some(status) = self.status {
159 fields = fields.with_status(status);
160 }
161 fields
162 }
163
164 pub fn as_pairs(&self) -> Vec<(String, String)> {
166 let mut pairs = vec![("service".to_string(), self.service.clone())];
167 push_optional(&mut pairs, "transport", Some(self.transport));
168 push_optional(&mut pairs, "route", Some(&self.route));
169 push_optional(&mut pairs, "method", Some(&self.method));
170 push_optional(&mut pairs, "request_id", self.request_id.as_deref());
171 push_optional(&mut pairs, "trace_id", self.trace_id.as_deref());
172 push_optional(&mut pairs, "span_id", self.span_id.as_deref());
173 push_optional(&mut pairs, "status", self.status.as_deref());
174 pairs
175 }
176
177 fn with_request_id(mut self, request_id: Option<String>) -> Self {
178 self.request_id = request_id;
179 self
180 }
181
182 fn with_traceparent(mut self, traceparent: Option<String>) -> Self {
183 if let Some(value) = traceparent {
184 self.trace_id = trace_id_from_traceparent(&value).map(ToOwned::to_owned);
185 self.span_id = span_id_from_traceparent(&value).map(ToOwned::to_owned);
186 self.traceparent = Some(value);
187 }
188 self
189 }
190
191 fn with_current_span_context(mut self) -> Self {
192 if let Some(trace_id) = current_trace_id() {
193 self.trace_id = Some(trace_id);
194 }
195 if let Some(span_id) = current_span_id() {
196 self.span_id = Some(span_id);
197 }
198 self
199 }
200}
201
202fn push_optional(pairs: &mut Vec<(String, String)>, key: &str, value: Option<&str>) {
203 if let Some(value) = value
204 && !value.is_empty()
205 {
206 pairs.push((key.to_string(), value.to_string()));
207 }
208}