1use std::collections::HashMap;
6use std::sync::atomic::{AtomicU64, Ordering};
7use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
8
9use crate::security::{
10 is_sensitive_key, sanitize_field_key, sanitize_field_value, sanitize_text, REDACTED,
11};
12
13#[derive(Debug, Clone)]
15pub struct TracingConfig {
16 pub service_name: String,
18 pub enabled: bool,
20 pub sample_rate: f64,
22 pub otlp_endpoint: Option<String>,
24 pub include_bodies: bool,
26}
27
28impl Default for TracingConfig {
29 fn default() -> Self {
30 Self {
31 service_name: "dcp-server".to_string(),
32 enabled: true,
33 sample_rate: 1.0,
34 otlp_endpoint: None,
35 include_bodies: false,
36 }
37 }
38}
39
40#[derive(Debug, Default)]
42pub struct RequestIdGenerator {
43 counter: AtomicU64,
44 node_id: u16,
45}
46
47impl RequestIdGenerator {
48 pub fn new() -> Self {
50 Self::with_node_id(0)
51 }
52
53 pub fn with_node_id(node_id: u16) -> Self {
55 Self {
56 counter: AtomicU64::new(0),
57 node_id,
58 }
59 }
60
61 pub fn generate(&self) -> String {
64 let timestamp = SystemTime::now()
65 .duration_since(UNIX_EPOCH)
66 .unwrap_or_default()
67 .as_millis() as u64;
68 let counter = self.counter.fetch_add(1, Ordering::Relaxed);
69 format!("{:x}-{:04x}-{:08x}", timestamp, self.node_id, counter)
70 }
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum SpanStatus {
76 Ok,
78 Error,
80 Cancelled,
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum SpanKind {
87 Internal,
89 Server,
91 Client,
93 Producer,
95 Consumer,
97}
98
99#[derive(Debug)]
101pub struct Span {
102 pub name: String,
104 pub trace_id: String,
106 pub span_id: String,
108 pub parent_span_id: Option<String>,
110 pub kind: SpanKind,
112 pub start_time: Instant,
114 pub end_time: Option<Instant>,
116 pub status: SpanStatus,
118 pub attributes: HashMap<String, SpanValue>,
120 pub events: Vec<SpanEvent>,
122}
123
124#[derive(Debug, Clone)]
126pub enum SpanValue {
127 String(String),
128 Int(i64),
129 Float(f64),
130 Bool(bool),
131}
132
133impl From<&str> for SpanValue {
134 fn from(s: &str) -> Self {
135 SpanValue::String(s.to_string())
136 }
137}
138
139impl From<String> for SpanValue {
140 fn from(s: String) -> Self {
141 SpanValue::String(s)
142 }
143}
144
145impl From<i64> for SpanValue {
146 fn from(v: i64) -> Self {
147 SpanValue::Int(v)
148 }
149}
150
151impl From<f64> for SpanValue {
152 fn from(v: f64) -> Self {
153 SpanValue::Float(v)
154 }
155}
156
157impl From<bool> for SpanValue {
158 fn from(v: bool) -> Self {
159 SpanValue::Bool(v)
160 }
161}
162
163#[derive(Debug, Clone)]
165pub struct SpanEvent {
166 pub name: String,
168 pub timestamp: Instant,
170 pub attributes: HashMap<String, SpanValue>,
172}
173
174impl Span {
175 pub fn new(name: impl Into<String>, trace_id: String, span_id: String) -> Self {
177 Self {
178 name: sanitize_text(&name.into()),
179 trace_id,
180 span_id,
181 parent_span_id: None,
182 kind: SpanKind::Internal,
183 start_time: Instant::now(),
184 end_time: None,
185 status: SpanStatus::Ok,
186 attributes: HashMap::new(),
187 events: Vec::new(),
188 }
189 }
190
191 pub fn with_parent(mut self, parent_span_id: String) -> Self {
193 self.parent_span_id = Some(parent_span_id);
194 self
195 }
196
197 pub fn with_kind(mut self, kind: SpanKind) -> Self {
199 self.kind = kind;
200 self
201 }
202
203 pub fn set_attribute(&mut self, key: impl Into<String>, value: impl Into<SpanValue>) {
205 let key = key.into();
206 let value = sanitize_span_value(&key, value.into());
207 self.attributes.insert(sanitize_field_key(&key), value);
208 }
209
210 pub fn add_event(&mut self, name: impl Into<String>) {
212 self.events.push(SpanEvent {
213 name: sanitize_text(&name.into()),
214 timestamp: Instant::now(),
215 attributes: HashMap::new(),
216 });
217 }
218
219 pub fn add_event_with_attributes(
221 &mut self,
222 name: impl Into<String>,
223 attributes: HashMap<String, SpanValue>,
224 ) {
225 let attributes = attributes
226 .into_iter()
227 .map(|(key, value)| {
228 let value = sanitize_span_value(&key, value);
229 (sanitize_field_key(&key), value)
230 })
231 .collect();
232
233 self.events.push(SpanEvent {
234 name: sanitize_text(&name.into()),
235 timestamp: Instant::now(),
236 attributes,
237 });
238 }
239
240 pub fn set_status(&mut self, status: SpanStatus) {
242 self.status = status;
243 }
244
245 pub fn end(&mut self) {
247 self.end_time = Some(Instant::now());
248 }
249
250 pub fn duration(&self) -> Option<Duration> {
252 self.end_time.map(|end| end.duration_since(self.start_time))
253 }
254}
255
256fn generate_span_id() -> String {
258 use std::sync::atomic::AtomicU64;
259 static COUNTER: AtomicU64 = AtomicU64::new(0);
260 let id = COUNTER.fetch_add(1, Ordering::Relaxed);
261 let random = std::time::SystemTime::now()
262 .duration_since(UNIX_EPOCH)
263 .unwrap_or_default()
264 .as_nanos() as u64;
265 format!("{:016x}", id ^ random)
266}
267
268fn generate_trace_id() -> String {
270 let timestamp = SystemTime::now()
271 .duration_since(UNIX_EPOCH)
272 .unwrap_or_default()
273 .as_nanos() as u64;
274 let random = std::time::SystemTime::now()
275 .duration_since(UNIX_EPOCH)
276 .unwrap_or_default()
277 .subsec_nanos() as u64;
278 format!("{:016x}{:016x}", timestamp, random)
279}
280
281pub fn create_span(name: impl Into<String>) -> Span {
283 Span::new(name, generate_trace_id(), generate_span_id())
284}
285
286pub fn create_child_span(name: impl Into<String>, parent: &Span) -> Span {
288 Span::new(name, parent.trace_id.clone(), generate_span_id()).with_parent(parent.span_id.clone())
289}
290
291#[derive(Debug)]
293pub struct Tracer {
294 config: TracingConfig,
295 request_id_generator: RequestIdGenerator,
296}
297
298impl Tracer {
299 pub fn new(config: TracingConfig) -> Self {
301 Self {
302 config,
303 request_id_generator: RequestIdGenerator::new(),
304 }
305 }
306
307 pub fn with_defaults() -> Self {
309 Self::new(TracingConfig::default())
310 }
311
312 pub fn generate_request_id(&self) -> String {
314 self.request_id_generator.generate()
315 }
316
317 pub fn start_span(&self, name: impl Into<String>) -> Span {
319 create_span(name)
320 }
321
322 pub fn start_child_span(&self, name: impl Into<String>, parent: &Span) -> Span {
324 create_child_span(name, parent)
325 }
326
327 pub fn is_enabled(&self) -> bool {
329 self.config.enabled
330 }
331
332 pub fn config(&self) -> &TracingConfig {
334 &self.config
335 }
336}
337
338impl Default for Tracer {
339 fn default() -> Self {
340 Self::with_defaults()
341 }
342}
343
344pub struct RequestSpan {
346 span: Span,
347 request_id: String,
348}
349
350impl RequestSpan {
351 pub fn new(method: impl Into<String>, request_id: String) -> Self {
353 let method = method.into();
354 let mut span = create_span(&method);
355 span.set_attribute("rpc.method", method);
356 span.set_attribute("request.id", request_id.clone());
357 span.kind = SpanKind::Server;
358 Self { span, request_id }
359 }
360
361 pub fn request_id(&self) -> &str {
363 &self.request_id
364 }
365
366 pub fn trace_id(&self) -> &str {
368 &self.span.trace_id
369 }
370
371 pub fn span_id(&self) -> &str {
373 &self.span.span_id
374 }
375
376 pub fn set_attribute(&mut self, key: impl Into<String>, value: impl Into<SpanValue>) {
378 self.span.set_attribute(key, value);
379 }
380
381 pub fn add_event(&mut self, name: impl Into<String>) {
383 self.span.add_event(name);
384 }
385
386 pub fn set_error(&mut self, error_message: impl Into<String>) {
388 self.span.set_status(SpanStatus::Error);
389 let error_message = error_message.into();
390 self.span
391 .set_attribute("error.message", sanitize_text(&error_message));
392 }
393
394 pub fn finish(mut self) -> Span {
396 self.span.set_status(SpanStatus::Ok);
397 self.span.end();
398 self.span
399 }
400
401 pub fn finish_with_error(mut self, error: impl Into<String>) -> Span {
403 self.span.set_status(SpanStatus::Error);
404 let error = error.into();
405 self.span
406 .set_attribute("error.message", sanitize_text(&error));
407 self.span.end();
408 self.span
409 }
410
411 pub fn span(&self) -> &Span {
413 &self.span
414 }
415
416 pub fn span_mut(&mut self) -> &mut Span {
418 &mut self.span
419 }
420}
421
422fn sanitize_span_value(key: &str, value: SpanValue) -> SpanValue {
423 match value {
424 SpanValue::String(value) => SpanValue::String(sanitize_field_value(key, &value)),
425 other => {
426 if is_sensitive_key(key) {
427 SpanValue::String(REDACTED.to_string())
428 } else {
429 other
430 }
431 }
432 }
433}
434
435pub fn init_tracing(config: TracingConfig) -> Tracer {
437 Tracer::new(config)
438}
439
440#[derive(Debug, Default)]
442pub struct SpanCollector {
443 spans: std::sync::RwLock<Vec<Span>>,
444}
445
446impl SpanCollector {
447 pub fn new() -> Self {
449 Self::default()
450 }
451
452 pub fn collect(&self, span: Span) {
454 self.spans.write().unwrap().push(span);
455 }
456
457 pub fn spans(&self) -> Vec<Span> {
459 self.spans.read().unwrap().clone()
460 }
461
462 pub fn clear(&self) {
464 self.spans.write().unwrap().clear();
465 }
466
467 pub fn len(&self) -> usize {
469 self.spans.read().unwrap().len()
470 }
471
472 pub fn is_empty(&self) -> bool {
474 self.spans.read().unwrap().is_empty()
475 }
476}
477
478impl Clone for Span {
479 fn clone(&self) -> Self {
480 Self {
481 name: self.name.clone(),
482 trace_id: self.trace_id.clone(),
483 span_id: self.span_id.clone(),
484 parent_span_id: self.parent_span_id.clone(),
485 kind: self.kind,
486 start_time: self.start_time,
487 end_time: self.end_time,
488 status: self.status,
489 attributes: self.attributes.clone(),
490 events: self.events.clone(),
491 }
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498
499 #[test]
500 fn test_request_id_generation() {
501 let generator = RequestIdGenerator::new();
502 let id1 = generator.generate();
503 let id2 = generator.generate();
504
505 assert_ne!(id1, id2);
506 assert!(id1.contains('-'));
507 assert!(id2.contains('-'));
508 }
509
510 #[test]
511 fn test_span_creation() {
512 let span = create_span("test_operation");
513
514 assert_eq!(span.name, "test_operation");
515 assert!(!span.trace_id.is_empty());
516 assert!(!span.span_id.is_empty());
517 assert!(span.parent_span_id.is_none());
518 assert_eq!(span.status, SpanStatus::Ok);
519 }
520
521 #[test]
522 fn test_child_span() {
523 let parent = create_span("parent");
524 let child = create_child_span("child", &parent);
525
526 assert_eq!(child.trace_id, parent.trace_id);
527 assert_eq!(child.parent_span_id, Some(parent.span_id.clone()));
528 assert_ne!(child.span_id, parent.span_id);
529 }
530
531 #[test]
532 fn test_span_attributes() {
533 let mut span = create_span("test");
534 span.set_attribute("key1", "value1");
535 span.set_attribute("key2", 42i64);
536 span.set_attribute("key3", 3.14f64);
537 span.set_attribute("key4", true);
538
539 assert_eq!(span.attributes.len(), 4);
540 }
541
542 #[test]
543 fn test_span_events() {
544 let mut span = create_span("test");
545 span.add_event("event1");
546 span.add_event("event2");
547
548 assert_eq!(span.events.len(), 2);
549 assert_eq!(span.events[0].name, "event1");
550 assert_eq!(span.events[1].name, "event2");
551 }
552
553 #[test]
554 fn test_span_duration() {
555 let mut span = create_span("test");
556 std::thread::sleep(std::time::Duration::from_millis(1));
557 span.end();
558
559 let duration = span.duration().unwrap();
560 assert!(duration.as_micros() > 0);
561 }
562
563 #[test]
564 fn test_request_span() {
565 let request_id = "test-request-123".to_string();
566 let mut span = RequestSpan::new("tools/call", request_id.clone());
567
568 assert_eq!(span.request_id(), "test-request-123");
569 span.set_attribute("tool.name", "my_tool");
570
571 let finished = span.finish();
572 assert_eq!(finished.status, SpanStatus::Ok);
573 assert!(finished.end_time.is_some());
574 }
575
576 #[test]
577 fn test_tracer() {
578 let tracer = Tracer::with_defaults();
579
580 let request_id = tracer.generate_request_id();
581 assert!(!request_id.is_empty());
582
583 let span = tracer.start_span("test");
584 assert_eq!(span.name, "test");
585 }
586
587 #[test]
588 fn test_span_collector() {
589 let collector = SpanCollector::new();
590
591 let mut span1 = create_span("span1");
592 span1.end();
593 collector.collect(span1);
594
595 let mut span2 = create_span("span2");
596 span2.end();
597 collector.collect(span2);
598
599 assert_eq!(collector.len(), 2);
600
601 let spans = collector.spans();
602 assert_eq!(spans[0].name, "span1");
603 assert_eq!(spans[1].name, "span2");
604 }
605}