Skip to main content

rskit_observability/
context.rs

1//! Operation context for tracking cross-cutting observability concerns.
2
3use std::sync::Arc;
4use std::time::Instant;
5
6use tracing::Span;
7
8use crate::metrics::MetricsHandle;
9use crate::tracer::set_operation_attributes;
10
11/// Tracks observability context for a single operation.
12///
13/// Mirrors Go's `observability.OperationContext`.
14pub struct OperationContext {
15    service_name: String,
16    operation_name: String,
17    request_id: String,
18    user_id: String,
19    start_time: Instant,
20    metrics: Option<Arc<MetricsHandle>>,
21}
22
23impl OperationContext {
24    /// Create a new operation context.
25    pub fn new(
26        service: impl Into<String>,
27        operation: impl Into<String>,
28        request_id: impl Into<String>,
29        user_id: impl Into<String>,
30    ) -> Self {
31        Self {
32            service_name: service.into(),
33            operation_name: operation.into(),
34            request_id: request_id.into(),
35            user_id: user_id.into(),
36            start_time: Instant::now(),
37            metrics: None,
38        }
39    }
40
41    /// Attach a metrics handle to this context.
42    #[must_use]
43    pub fn with_metrics(mut self, metrics: Arc<MetricsHandle>) -> Self {
44        self.metrics = Some(metrics);
45        self
46    }
47
48    /// Name of the service performing the operation.
49    #[must_use]
50    pub fn service_name(&self) -> &str {
51        &self.service_name
52    }
53
54    /// Name of the operation being performed.
55    #[must_use]
56    pub fn operation_name(&self) -> &str {
57        &self.operation_name
58    }
59
60    /// Unique request identifier for correlation.
61    #[must_use]
62    pub fn request_id(&self) -> &str {
63        &self.request_id
64    }
65
66    /// Identifier of the user initiating the request.
67    ///
68    /// This is retained for caller-side correlation and is not emitted to spans
69    /// by default to avoid leaking user identifiers into telemetry backends.
70    #[must_use]
71    pub fn user_id(&self) -> &str {
72        &self.user_id
73    }
74
75    /// Create a tracing span for a sub-operation.
76    pub fn start_span(&self, name: &str) -> Span {
77        let span = tracing::info_span!("operation", otel.name = name);
78        set_operation_attributes(
79            &span,
80            &self.service_name,
81            &self.operation_name,
82            &self.request_id,
83        );
84        span
85    }
86
87    /// Record the end of the operation with status and optional error.
88    pub fn end_operation(&self, status: &str, error: Option<&rskit_errors::AppError>) {
89        let duration = self.elapsed();
90        tracing::info!(
91            service = %self.service_name,
92            operation = %self.operation_name,
93            request_id = %self.request_id,
94            status = status,
95            duration_ms = duration.as_millis() as u64,
96            error = error.map(|e| e.to_string()).as_deref(),
97            "operation completed"
98        );
99    }
100
101    /// Time elapsed since the context was created.
102    pub fn elapsed(&self) -> std::time::Duration {
103        self.start_time.elapsed()
104    }
105}