use std::collections::VecDeque;
use std::sync::RwLock;
use std::time::{Duration, SystemTime};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ExecutionType {
Publish,
HandlerExecution,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ExecutionStatus {
Success,
Failed,
Timeout,
Lagged,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExecutionRecord {
pub id: u64,
pub event_name: String,
pub timestamp: SystemTime,
pub execution_type: ExecutionType,
pub status: ExecutionStatus,
pub duration: Option<Duration>,
pub error: Option<String>,
pub subscriber_id: Option<usize>,
pub receiver_count: Option<usize>,
pub lagged_count: Option<usize>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ExecutionLogQuery {
pub event_name: Option<String>,
pub execution_type: Option<ExecutionType>,
pub status: Option<ExecutionStatus>,
pub since: Option<SystemTime>,
pub until: Option<SystemTime>,
pub limit: Option<usize>,
pub offset: Option<usize>,
}
pub trait ExecutionLogStorage: Send + Sync + 'static {
fn record(&self, record: ExecutionRecord);
fn query(&self, filter: &ExecutionLogQuery) -> Vec<ExecutionRecord>;
fn count(&self, filter: &ExecutionLogQuery) -> usize;
fn clear(&self);
}
pub struct InMemoryExecutionLog {
records: RwLock<VecDeque<ExecutionRecord>>,
max_records: usize,
}
impl InMemoryExecutionLog {
pub fn new() -> Self {
Self::with_capacity(10000)
}
pub fn with_capacity(max_records: usize) -> Self {
Self {
records: RwLock::new(VecDeque::with_capacity(max_records)),
max_records,
}
}
}
impl Default for InMemoryExecutionLog {
fn default() -> Self {
Self::new()
}
}
impl ExecutionLogStorage for InMemoryExecutionLog {
fn record(&self, record: ExecutionRecord) {
let mut records = self.records.write().unwrap();
if records.len() >= self.max_records {
records.pop_front();
}
records.push_back(record);
}
fn query(&self, filter: &ExecutionLogQuery) -> Vec<ExecutionRecord> {
let records = self.records.read().unwrap();
let mut results: Vec<ExecutionRecord> = records
.iter()
.filter(|r| {
if let Some(ref name) = filter.event_name {
if r.event_name != *name {
return false;
}
}
if let Some(ref et) = filter.execution_type {
if r.execution_type != *et {
return false;
}
}
if let Some(ref status) = filter.status {
if r.status != *status {
return false;
}
}
if let Some(since) = filter.since {
if r.timestamp < since {
return false;
}
}
if let Some(until) = filter.until {
if r.timestamp > until {
return false;
}
}
true
})
.cloned()
.collect();
results.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
let offset = filter.offset.unwrap_or(0);
let limit = filter.limit.unwrap_or(usize::MAX);
results.into_iter().skip(offset).take(limit).collect()
}
fn count(&self, filter: &ExecutionLogQuery) -> usize {
let records = self.records.read().unwrap();
records
.iter()
.filter(|r| {
if let Some(ref name) = filter.event_name {
if r.event_name != *name {
return false;
}
}
if let Some(ref et) = filter.execution_type {
if r.execution_type != *et {
return false;
}
}
if let Some(ref status) = filter.status {
if r.status != *status {
return false;
}
}
if let Some(since) = filter.since {
if r.timestamp < since {
return false;
}
}
if let Some(until) = filter.until {
if r.timestamp > until {
return false;
}
}
true
})
.count()
}
fn clear(&self) {
let mut records = self.records.write().unwrap();
records.clear();
}
}
pub struct ExecutionLog {
storage: Box<dyn ExecutionLogStorage>,
}
impl ExecutionLog {
pub fn new(storage: Box<dyn ExecutionLogStorage>) -> Self {
Self { storage }
}
pub fn in_memory() -> Self {
Self::new(Box::new(InMemoryExecutionLog::new()))
}
pub fn in_memory_with_capacity(capacity: usize) -> Self {
Self::new(Box::new(InMemoryExecutionLog::with_capacity(capacity)))
}
pub fn record(&self, record: ExecutionRecord) {
self.storage.record(record);
}
pub fn query(&self, filter: ExecutionLogQuery) -> Vec<ExecutionRecord> {
self.storage.query(&filter)
}
pub fn count(&self, filter: &ExecutionLogQuery) -> usize {
self.storage.count(filter)
}
pub fn clear(&self) {
self.storage.clear();
}
}
pub struct ExecutionLogTelemetry {
log: ExecutionLog,
next_id: RwLock<u64>,
}
impl ExecutionLogTelemetry {
pub fn new(log: ExecutionLog) -> Self {
Self {
log,
next_id: RwLock::new(0),
}
}
fn next_id(&self) -> u64 {
let mut id = self.next_id.write().unwrap();
let current = *id;
*id += 1;
current
}
}
impl crate::telemetry::Telemetry for ExecutionLogTelemetry {
fn on_publish(&self, event_name: &str, receivers: usize) {
let record = ExecutionRecord {
id: self.next_id(),
event_name: event_name.to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::Publish,
status: ExecutionStatus::Success,
duration: None,
error: None,
subscriber_id: None,
receiver_count: Some(receivers),
lagged_count: None,
};
self.log.record(record);
}
fn on_publish_complete(&self, _event_name: &str, _elapsed: Duration) {
}
fn on_subscribe(&self, _event_name: &str, _sub_id: usize) {
}
fn on_handler_start(&self, _event_name: &str, _sub_id: usize) {
}
fn on_handler_complete(
&self,
event_name: &str,
sub_id: usize,
elapsed: Duration,
error: Option<&str>,
) {
let status = if error.is_some() {
ExecutionStatus::Failed
} else {
ExecutionStatus::Success
};
let record = ExecutionRecord {
id: self.next_id(),
event_name: event_name.to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::HandlerExecution,
status,
duration: Some(elapsed),
error: error.map(|s| s.to_string()),
subscriber_id: Some(sub_id),
receiver_count: None,
lagged_count: None,
};
self.log.record(record);
}
fn on_handler_lagged(&self, event_name: &str, sub_id: usize, lagged_count: usize) {
let record = ExecutionRecord {
id: self.next_id(),
event_name: event_name.to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::HandlerExecution,
status: ExecutionStatus::Lagged,
duration: None,
error: None,
subscriber_id: Some(sub_id),
receiver_count: None,
lagged_count: Some(lagged_count),
};
self.log.record(record);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_in_memory_log_record_and_query() {
let log = InMemoryExecutionLog::new();
log.record(ExecutionRecord {
id: 1,
event_name: "user.created".to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::Publish,
status: ExecutionStatus::Success,
duration: None,
error: None,
subscriber_id: None,
receiver_count: Some(2),
lagged_count: None,
});
log.record(ExecutionRecord {
id: 2,
event_name: "user.created".to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::HandlerExecution,
status: ExecutionStatus::Success,
duration: Some(Duration::from_millis(5)),
error: None,
subscriber_id: Some(1),
receiver_count: None,
lagged_count: None,
});
let all = log.query(&ExecutionLogQuery::default());
assert_eq!(all.len(), 2);
}
#[test]
fn test_query_by_event_name() {
let log = InMemoryExecutionLog::new();
log.record(ExecutionRecord {
id: 1,
event_name: "user.created".to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::Publish,
status: ExecutionStatus::Success,
duration: None,
error: None,
subscriber_id: None,
receiver_count: Some(1),
lagged_count: None,
});
log.record(ExecutionRecord {
id: 2,
event_name: "order.placed".to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::Publish,
status: ExecutionStatus::Success,
duration: None,
error: None,
subscriber_id: None,
receiver_count: Some(1),
lagged_count: None,
});
let results = log.query(&ExecutionLogQuery {
event_name: Some("user.created".to_string()),
..Default::default()
});
assert_eq!(results.len(), 1);
assert_eq!(results[0].event_name, "user.created");
}
#[test]
fn test_query_by_status() {
let log = InMemoryExecutionLog::new();
log.record(ExecutionRecord {
id: 1,
event_name: "user.created".to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::HandlerExecution,
status: ExecutionStatus::Success,
duration: Some(Duration::from_millis(5)),
error: None,
subscriber_id: Some(1),
receiver_count: None,
lagged_count: None,
});
log.record(ExecutionRecord {
id: 2,
event_name: "user.created".to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::HandlerExecution,
status: ExecutionStatus::Failed,
duration: Some(Duration::from_millis(10)),
error: Some("something went wrong".to_string()),
subscriber_id: Some(2),
receiver_count: None,
lagged_count: None,
});
let failed = log.query(&ExecutionLogQuery {
status: Some(ExecutionStatus::Failed),
..Default::default()
});
assert_eq!(failed.len(), 1);
assert_eq!(failed[0].error.as_ref().unwrap(), "something went wrong");
}
#[test]
fn test_query_by_execution_type() {
let log = InMemoryExecutionLog::new();
log.record(ExecutionRecord {
id: 1,
event_name: "user.created".to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::Publish,
status: ExecutionStatus::Success,
duration: None,
error: None,
subscriber_id: None,
receiver_count: Some(1),
lagged_count: None,
});
log.record(ExecutionRecord {
id: 2,
event_name: "user.created".to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::HandlerExecution,
status: ExecutionStatus::Success,
duration: Some(Duration::from_millis(3)),
error: None,
subscriber_id: Some(1),
receiver_count: None,
lagged_count: None,
});
let handlers = log.query(&ExecutionLogQuery {
execution_type: Some(ExecutionType::HandlerExecution),
..Default::default()
});
assert_eq!(handlers.len(), 1);
assert_eq!(handlers[0].subscriber_id, Some(1));
}
#[test]
fn test_query_pagination() {
let log = InMemoryExecutionLog::new();
for i in 0..20 {
log.record(ExecutionRecord {
id: i,
event_name: "test.event".to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::Publish,
status: ExecutionStatus::Success,
duration: None,
error: None,
subscriber_id: None,
receiver_count: Some(1),
lagged_count: None,
});
}
let page1 = log.query(&ExecutionLogQuery {
limit: Some(5),
offset: Some(0),
..Default::default()
});
assert_eq!(page1.len(), 5);
let page2 = log.query(&ExecutionLogQuery {
limit: Some(5),
offset: Some(5),
..Default::default()
});
assert_eq!(page2.len(), 5);
}
#[test]
fn test_count() {
let log = InMemoryExecutionLog::new();
log.record(ExecutionRecord {
id: 1,
event_name: "user.created".to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::Publish,
status: ExecutionStatus::Success,
duration: None,
error: None,
subscriber_id: None,
receiver_count: Some(1),
lagged_count: None,
});
log.record(ExecutionRecord {
id: 2,
event_name: "user.created".to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::HandlerExecution,
status: ExecutionStatus::Failed,
duration: Some(Duration::from_millis(5)),
error: Some("err".to_string()),
subscriber_id: Some(1),
receiver_count: None,
lagged_count: None,
});
let total = log.count(&ExecutionLogQuery::default());
assert_eq!(total, 2);
let failed = log.count(&ExecutionLogQuery {
status: Some(ExecutionStatus::Failed),
..Default::default()
});
assert_eq!(failed, 1);
}
#[test]
fn test_max_capacity_eviction() {
let log = InMemoryExecutionLog::with_capacity(3);
for i in 0..5 {
log.record(ExecutionRecord {
id: i,
event_name: format!("event.{}", i),
timestamp: SystemTime::now(),
execution_type: ExecutionType::Publish,
status: ExecutionStatus::Success,
duration: None,
error: None,
subscriber_id: None,
receiver_count: None,
lagged_count: None,
});
}
let all = log.query(&ExecutionLogQuery::default());
assert_eq!(all.len(), 3);
let names: Vec<&str> = all.iter().map(|r| r.event_name.as_str()).collect();
assert!(!names.contains(&"event.0"));
assert!(!names.contains(&"event.1"));
}
#[test]
fn test_clear() {
let log = InMemoryExecutionLog::new();
log.record(ExecutionRecord {
id: 1,
event_name: "test".to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::Publish,
status: ExecutionStatus::Success,
duration: None,
error: None,
subscriber_id: None,
receiver_count: None,
lagged_count: None,
});
assert_eq!(log.count(&ExecutionLogQuery::default()), 1);
log.clear();
assert_eq!(log.count(&ExecutionLogQuery::default()), 0);
}
#[test]
fn test_execution_log_wrapper() {
let log = ExecutionLog::in_memory();
log.record(ExecutionRecord {
id: 1,
event_name: "test".to_string(),
timestamp: SystemTime::now(),
execution_type: ExecutionType::Publish,
status: ExecutionStatus::Success,
duration: None,
error: None,
subscriber_id: None,
receiver_count: None,
lagged_count: None,
});
let results = log.query(ExecutionLogQuery::default());
assert_eq!(results.len(), 1);
}
#[test]
fn test_execution_log_telemetry() {
use crate::telemetry::Telemetry;
let storage = Box::new(InMemoryExecutionLog::new());
let log = ExecutionLog::new(storage);
let _telemetry = ExecutionLogTelemetry::new(ExecutionLog::in_memory());
let tel: &dyn Telemetry = &_telemetry;
tel.on_publish("test.event", 3);
tel.on_handler_complete("test.event", 1, Duration::from_millis(5), None);
tel.on_handler_complete("test.event", 2, Duration::from_millis(10), Some("error msg"));
tel.on_handler_lagged("test.event", 1, 42);
let results = log.query(ExecutionLogQuery::default());
assert_eq!(results.len(), 0); }
#[test]
fn test_time_range_query() {
let log = InMemoryExecutionLog::new();
let now = SystemTime::now();
let one_hour_ago = now - Duration::from_secs(3600);
let two_hours_ago = now - Duration::from_secs(7200);
log.record(ExecutionRecord {
id: 1,
event_name: "old.event".to_string(),
timestamp: two_hours_ago,
execution_type: ExecutionType::Publish,
status: ExecutionStatus::Success,
duration: None,
error: None,
subscriber_id: None,
receiver_count: None,
lagged_count: None,
});
log.record(ExecutionRecord {
id: 2,
event_name: "new.event".to_string(),
timestamp: now,
execution_type: ExecutionType::Publish,
status: ExecutionStatus::Success,
duration: None,
error: None,
subscriber_id: None,
receiver_count: None,
lagged_count: None,
});
let recent = log.query(&ExecutionLogQuery {
since: Some(one_hour_ago),
..Default::default()
});
assert_eq!(recent.len(), 1);
assert_eq!(recent[0].event_name, "new.event");
let old = log.query(&ExecutionLogQuery {
until: Some(one_hour_ago),
..Default::default()
});
assert_eq!(old.len(), 1);
assert_eq!(old[0].event_name, "old.event");
}
}