Skip to main content

stasis/application/telemetry/
operation.rs

1use std::sync::Arc;
2use std::time::Instant;
3
4use crate::application::telemetry::keys::{
5    MEMORY_RECALL_DURATION_MS, MEMORY_RECALL_ERRORS_TOTAL, MEMORY_RECALL_TOTAL,
6    MEMORY_STORE_DURATION_MS, MEMORY_STORE_ERRORS_TOTAL, MEMORY_STORE_TOTAL,
7};
8use crate::application::telemetry::spans;
9use crate::ports::outbound::runtime::runtime_metrics::RuntimeMetrics;
10use crate::ports::outbound::runtime::runtime_tracing::{OtelAttribute, RuntimeTracing, SpanGuard};
11
12/// Shared metrics + tracing helper for handler-level observability.
13#[derive(Clone)]
14pub struct OperationTelemetry {
15    metrics: Arc<dyn RuntimeMetrics>,
16    tracing: Arc<dyn RuntimeTracing>,
17}
18
19impl OperationTelemetry {
20    pub fn new(metrics: Arc<dyn RuntimeMetrics>, tracing: Arc<dyn RuntimeTracing>) -> Self {
21        Self { metrics, tracing }
22    }
23
24    pub fn start_span(&self, name: &'static str, attributes: &[OtelAttribute]) -> SpanGuard {
25        self.tracing.start_span(name, attributes)
26    }
27
28    pub fn recall_span(&self, correlation_id: &str) -> SpanGuard {
29        self.start_span(
30            spans::MEMORY_RECALL,
31            &[OtelAttribute::string(
32                "stasis.memory.correlation_id",
33                correlation_id.to_string(),
34            )],
35        )
36    }
37
38    pub fn store_span(&self, correlation_id: &str) -> SpanGuard {
39        self.start_span(
40            spans::MEMORY_STORE,
41            &[OtelAttribute::string(
42                "stasis.memory.correlation_id",
43                correlation_id.to_string(),
44            )],
45        )
46    }
47
48    pub fn grapheme_span(&self, job_id: &str) -> SpanGuard {
49        self.start_span(
50            spans::GRAPHEME_EXECUTE,
51            &[OtelAttribute::string("stasis.job.id", job_id.to_string())],
52        )
53    }
54
55    pub fn outbox_publish_span(&self, event_type: &str, job_id: &str) -> SpanGuard {
56        self.start_span(
57            spans::OUTBOX_PUBLISH,
58            &[
59                OtelAttribute::string("stasis.outbox.event_type", event_type.to_string()),
60                OtelAttribute::string("stasis.outbox.job_id", job_id.to_string()),
61            ],
62        )
63    }
64
65    pub fn record_recall_started(&self) {
66        self.metrics.incr_counter(MEMORY_RECALL_TOTAL, 1);
67    }
68
69    pub fn record_recall_success(&self, started: Instant) {
70        self.metrics.observe_duration_ms(
71            MEMORY_RECALL_DURATION_MS,
72            started.elapsed().as_millis() as u64,
73        );
74    }
75
76    pub fn record_recall_error(&self, started: Instant) {
77        self.metrics.incr_counter(MEMORY_RECALL_ERRORS_TOTAL, 1);
78        self.metrics.observe_duration_ms(
79            MEMORY_RECALL_DURATION_MS,
80            started.elapsed().as_millis() as u64,
81        );
82    }
83
84    pub fn record_store_started(&self) {
85        self.metrics.incr_counter(MEMORY_STORE_TOTAL, 1);
86    }
87
88    pub fn record_store_success(&self, started: Instant) {
89        self.metrics.observe_duration_ms(
90            MEMORY_STORE_DURATION_MS,
91            started.elapsed().as_millis() as u64,
92        );
93    }
94
95    pub fn record_store_error(&self, started: Instant) {
96        self.metrics.incr_counter(MEMORY_STORE_ERRORS_TOTAL, 1);
97        self.metrics.observe_duration_ms(
98            MEMORY_STORE_DURATION_MS,
99            started.elapsed().as_millis() as u64,
100        );
101    }
102}
103
104pub fn runtime_event_type_name(event_type: &crate::domain::runtime::outbox::RuntimeEventType) -> &'static str {
105    match event_type {
106        crate::domain::runtime::outbox::RuntimeEventType::JobSucceeded => "job.succeeded",
107        crate::domain::runtime::outbox::RuntimeEventType::JobRetryScheduled => "job.retry_scheduled",
108        crate::domain::runtime::outbox::RuntimeEventType::JobDeadLettered => "job.dead_lettered",
109    }
110}