llm_optimizer_processor/telemetry/
context.rs1use opentelemetry::{
7 baggage::BaggageExt,
8 global,
9 propagation::{Extractor, Injector, TextMapPropagator},
10 trace::{TraceContextExt, TraceError},
11 Context, KeyValue,
12};
13use std::collections::HashMap;
14
15#[derive(Debug, Clone, Default)]
17pub struct HeaderCarrier {
18 headers: HashMap<String, String>,
19}
20
21impl HeaderCarrier {
22 pub fn new() -> Self {
24 Self::default()
25 }
26
27 pub fn from_headers(headers: HashMap<String, String>) -> Self {
29 Self { headers }
30 }
31
32 pub fn headers(&self) -> &HashMap<String, String> {
34 &self.headers
35 }
36
37 pub fn into_headers(self) -> HashMap<String, String> {
39 self.headers
40 }
41
42 pub fn get(&self, key: &str) -> Option<&String> {
44 self.headers.get(key)
45 }
46
47 pub fn set(&mut self, key: String, value: String) {
49 self.headers.insert(key, value);
50 }
51}
52
53impl Injector for HeaderCarrier {
54 fn set(&mut self, key: &str, value: String) {
55 self.headers.insert(key.to_string(), value);
56 }
57}
58
59impl Extractor for HeaderCarrier {
60 fn get(&self, key: &str) -> Option<&str> {
61 self.headers.get(key).map(|v| v.as_str())
62 }
63
64 fn keys(&self) -> Vec<&str> {
65 self.headers.keys().map(|k| k.as_str()).collect()
66 }
67}
68
69pub fn inject_context(context: &Context, carrier: &mut dyn Injector) {
79 global::get_text_map_propagator(|propagator| {
80 propagator.inject_context(context, carrier);
81 });
82}
83
84pub fn extract_context(carrier: &dyn Extractor) -> Context {
94 global::get_text_map_propagator(|propagator| propagator.extract(carrier))
95}
96
97pub fn inject_into_headers(
99 context: &Context,
100 headers: &mut HashMap<String, String>,
101) {
102 let mut carrier = HeaderCarrier::from_headers(headers.clone());
103 inject_context(context, &mut carrier);
104 *headers = carrier.into_headers();
105}
106
107pub fn extract_from_headers(headers: &HashMap<String, String>) -> Context {
109 let carrier = HeaderCarrier::from_headers(headers.clone());
110 extract_context(&carrier)
111}
112
113#[derive(Debug, Clone)]
115pub struct BaggageItem {
116 pub key: String,
117 pub value: String,
118}
119
120impl BaggageItem {
121 pub fn new(key: impl Into<String>, value: impl Into<String>) -> Self {
122 Self {
123 key: key.into(),
124 value: value.into(),
125 }
126 }
127}
128
129pub fn with_baggage(context: &Context, items: Vec<BaggageItem>) -> Context {
133 let mut ctx = context.clone();
134 for item in items {
135 ctx = ctx.with_value(KeyValue::new(item.key, item.value));
136 }
137 ctx
138}
139
140pub fn get_baggage(context: &Context, key: &str) -> Option<String> {
142 context.baggage().get(key).map(|v| v.to_string())
143}
144
145pub fn context_with_span(span: impl opentelemetry::trace::Span + Send + Sync + 'static) -> Context {
147 Context::current_with_span(span)
148}
149
150pub async fn with_context<F, T>(context: Context, f: F) -> T
162where
163 F: std::future::Future<Output = T>,
164{
165 let _guard = context.attach();
166 f.await
167}
168
169#[macro_export]
181macro_rules! in_context {
182 ($context:expr, $body:block) => {{
183 let _guard = $context.attach();
184 $body
185 }};
186}
187
188pub async fn propagate_context<F, T>(f: F) -> T
199where
200 F: std::future::Future<Output = T>,
201{
202 let context = Context::current();
203 with_context(context, f).await
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_header_carrier_new() {
212 let carrier = HeaderCarrier::new();
213 assert!(carrier.headers.is_empty());
214 }
215
216 #[test]
217 fn test_header_carrier_from_headers() {
218 let mut headers = HashMap::new();
219 headers.insert("traceparent".to_string(), "test-value".to_string());
220
221 let carrier = HeaderCarrier::from_headers(headers.clone());
222 assert_eq!(carrier.get("traceparent"), Some(&"test-value".to_string()));
223 }
224
225 #[test]
226 fn test_header_carrier_set_get() {
227 let mut carrier = HeaderCarrier::new();
228 carrier.set("test-key".to_string(), "test-value".to_string());
229 assert_eq!(carrier.get("test-key"), Some(&"test-value".to_string()));
230 }
231
232 #[test]
233 fn test_header_carrier_injector() {
234 let mut carrier = HeaderCarrier::new();
235 Injector::set(&mut carrier, "key", "value".to_string());
236 assert_eq!(carrier.headers.get("key"), Some(&"value".to_string()));
237 }
238
239 #[test]
240 fn test_header_carrier_extractor() {
241 let mut headers = HashMap::new();
242 headers.insert("key".to_string(), "value".to_string());
243 let carrier = HeaderCarrier::from_headers(headers);
244
245 assert_eq!(Extractor::get(&carrier, "key"), Some("value"));
246 assert_eq!(Extractor::keys(&carrier), vec!["key"]);
247 }
248
249 #[test]
250 fn test_baggage_item() {
251 let item = BaggageItem::new("tenant-id", "12345");
252 assert_eq!(item.key, "tenant-id");
253 assert_eq!(item.value, "12345");
254 }
255
256 #[test]
257 fn test_inject_extract_context() {
258 let context = Context::current();
259 let mut carrier = HeaderCarrier::new();
260
261 inject_context(&context, &mut carrier);
262 let extracted = extract_context(&carrier);
263
264 assert!(extracted.span().span_context().is_valid() || !extracted.span().span_context().is_valid());
266 }
267
268 #[test]
269 fn test_inject_extract_headers() {
270 let mut headers = HashMap::new();
271 let context = Context::current();
272
273 inject_into_headers(&context, &mut headers);
274 let extracted = extract_from_headers(&headers);
275
276 assert!(extracted.span().span_context().is_valid() || !extracted.span().span_context().is_valid());
277 }
278
279 #[tokio::test]
280 async fn test_with_context() {
281 let context = Context::current();
282 let result = with_context(context, async { 42 }).await;
283 assert_eq!(result, 42);
284 }
285
286 #[tokio::test]
287 async fn test_propagate_context() {
288 let result = propagate_context(async { "test" }).await;
289 assert_eq!(result, "test");
290 }
291}