ricecoder_workflows/
activity_log.rs

1//! Activity logging for workflow execution
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::VecDeque;
6
7/// Activity log entry
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ActivityLogEntry {
10    /// Timestamp of the activity
11    pub timestamp: DateTime<Utc>,
12    /// Activity type
13    pub activity_type: ActivityType,
14    /// Step ID (if applicable)
15    pub step_id: Option<String>,
16    /// Activity message
17    pub message: String,
18    /// Additional context
19    pub context: serde_json::Value,
20}
21
22/// Type of activity
23#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
24pub enum ActivityType {
25    /// Workflow started
26    #[serde(rename = "workflow_started")]
27    WorkflowStarted,
28    /// Workflow completed
29    #[serde(rename = "workflow_completed")]
30    WorkflowCompleted,
31    /// Workflow failed
32    #[serde(rename = "workflow_failed")]
33    WorkflowFailed,
34    /// Workflow paused
35    #[serde(rename = "workflow_paused")]
36    WorkflowPaused,
37    /// Workflow resumed
38    #[serde(rename = "workflow_resumed")]
39    WorkflowResumed,
40    /// Workflow cancelled
41    #[serde(rename = "workflow_cancelled")]
42    WorkflowCancelled,
43    /// Step started
44    #[serde(rename = "step_started")]
45    StepStarted,
46    /// Step completed
47    #[serde(rename = "step_completed")]
48    StepCompleted,
49    /// Step failed
50    #[serde(rename = "step_failed")]
51    StepFailed,
52    /// Step skipped
53    #[serde(rename = "step_skipped")]
54    StepSkipped,
55    /// Approval requested
56    #[serde(rename = "approval_requested")]
57    ApprovalRequested,
58    /// Approval granted
59    #[serde(rename = "approval_granted")]
60    ApprovalGranted,
61    /// Approval denied
62    #[serde(rename = "approval_denied")]
63    ApprovalDenied,
64    /// State transition
65    #[serde(rename = "state_transition")]
66    StateTransition,
67    /// Error occurred
68    #[serde(rename = "error")]
69    Error,
70}
71
72/// Activity logger for workflow execution
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct ActivityLogger {
75    /// Activity log entries (limited to max_entries)
76    entries: VecDeque<ActivityLogEntry>,
77    /// Maximum number of entries to keep
78    max_entries: usize,
79}
80
81impl ActivityLogger {
82    /// Create a new activity logger
83    pub fn new(max_entries: usize) -> Self {
84        ActivityLogger {
85            entries: VecDeque::new(),
86            max_entries,
87        }
88    }
89
90    /// Log an activity
91    pub fn log(
92        &mut self,
93        activity_type: ActivityType,
94        step_id: Option<String>,
95        message: String,
96        context: serde_json::Value,
97    ) {
98        let entry = ActivityLogEntry {
99            timestamp: Utc::now(),
100            activity_type,
101            step_id,
102            message,
103            context,
104        };
105
106        self.entries.push_back(entry);
107
108        // Remove oldest entry if we exceed max_entries
109        if self.entries.len() > self.max_entries {
110            self.entries.pop_front();
111        }
112    }
113
114    /// Log workflow started
115    pub fn log_workflow_started(&mut self, workflow_id: &str) {
116        self.log(
117            ActivityType::WorkflowStarted,
118            None,
119            format!("Workflow '{}' started", workflow_id),
120            serde_json::json!({"workflow_id": workflow_id}),
121        );
122    }
123
124    /// Log workflow completed
125    pub fn log_workflow_completed(&mut self, workflow_id: &str, duration_ms: u64) {
126        self.log(
127            ActivityType::WorkflowCompleted,
128            None,
129            format!("Workflow '{}' completed in {}ms", workflow_id, duration_ms),
130            serde_json::json!({"workflow_id": workflow_id, "duration_ms": duration_ms}),
131        );
132    }
133
134    /// Log workflow failed
135    pub fn log_workflow_failed(&mut self, workflow_id: &str, error: &str) {
136        self.log(
137            ActivityType::WorkflowFailed,
138            None,
139            format!("Workflow '{}' failed: {}", workflow_id, error),
140            serde_json::json!({"workflow_id": workflow_id, "error": error}),
141        );
142    }
143
144    /// Log workflow paused
145    pub fn log_workflow_paused(&mut self, workflow_id: &str) {
146        self.log(
147            ActivityType::WorkflowPaused,
148            None,
149            format!("Workflow '{}' paused", workflow_id),
150            serde_json::json!({"workflow_id": workflow_id}),
151        );
152    }
153
154    /// Log workflow resumed
155    pub fn log_workflow_resumed(&mut self, workflow_id: &str) {
156        self.log(
157            ActivityType::WorkflowResumed,
158            None,
159            format!("Workflow '{}' resumed", workflow_id),
160            serde_json::json!({"workflow_id": workflow_id}),
161        );
162    }
163
164    /// Log workflow cancelled
165    pub fn log_workflow_cancelled(&mut self, workflow_id: &str) {
166        self.log(
167            ActivityType::WorkflowCancelled,
168            None,
169            format!("Workflow '{}' cancelled", workflow_id),
170            serde_json::json!({"workflow_id": workflow_id}),
171        );
172    }
173
174    /// Log step started
175    pub fn log_step_started(&mut self, step_id: &str, step_name: &str) {
176        self.log(
177            ActivityType::StepStarted,
178            Some(step_id.to_string()),
179            format!("Step '{}' started", step_name),
180            serde_json::json!({"step_id": step_id, "step_name": step_name}),
181        );
182    }
183
184    /// Log step completed
185    pub fn log_step_completed(&mut self, step_id: &str, step_name: &str, duration_ms: u64) {
186        self.log(
187            ActivityType::StepCompleted,
188            Some(step_id.to_string()),
189            format!("Step '{}' completed in {}ms", step_name, duration_ms),
190            serde_json::json!({"step_id": step_id, "step_name": step_name, "duration_ms": duration_ms}),
191        );
192    }
193
194    /// Log step failed
195    pub fn log_step_failed(&mut self, step_id: &str, step_name: &str, error: &str) {
196        self.log(
197            ActivityType::StepFailed,
198            Some(step_id.to_string()),
199            format!("Step '{}' failed: {}", step_name, error),
200            serde_json::json!({"step_id": step_id, "step_name": step_name, "error": error}),
201        );
202    }
203
204    /// Log step skipped
205    pub fn log_step_skipped(&mut self, step_id: &str, step_name: &str) {
206        self.log(
207            ActivityType::StepSkipped,
208            Some(step_id.to_string()),
209            format!("Step '{}' skipped", step_name),
210            serde_json::json!({"step_id": step_id, "step_name": step_name}),
211        );
212    }
213
214    /// Log approval requested
215    pub fn log_approval_requested(&mut self, step_id: &str, message: &str) {
216        self.log(
217            ActivityType::ApprovalRequested,
218            Some(step_id.to_string()),
219            format!("Approval requested: {}", message),
220            serde_json::json!({"step_id": step_id, "message": message}),
221        );
222    }
223
224    /// Log approval granted
225    pub fn log_approval_granted(&mut self, step_id: &str) {
226        self.log(
227            ActivityType::ApprovalGranted,
228            Some(step_id.to_string()),
229            "Approval granted".to_string(),
230            serde_json::json!({"step_id": step_id}),
231        );
232    }
233
234    /// Log approval denied
235    pub fn log_approval_denied(&mut self, step_id: &str) {
236        self.log(
237            ActivityType::ApprovalDenied,
238            Some(step_id.to_string()),
239            "Approval denied".to_string(),
240            serde_json::json!({"step_id": step_id}),
241        );
242    }
243
244    /// Log state transition
245    pub fn log_state_transition(&mut self, from_state: &str, to_state: &str) {
246        self.log(
247            ActivityType::StateTransition,
248            None,
249            format!("State transition: {} -> {}", from_state, to_state),
250            serde_json::json!({"from_state": from_state, "to_state": to_state}),
251        );
252    }
253
254    /// Log error
255    pub fn log_error(&mut self, step_id: Option<&str>, error: &str) {
256        self.log(
257            ActivityType::Error,
258            step_id.map(|s| s.to_string()),
259            format!("Error: {}", error),
260            serde_json::json!({"error": error}),
261        );
262    }
263
264    /// Get all activity log entries
265    pub fn get_entries(&self) -> Vec<ActivityLogEntry> {
266        self.entries.iter().cloned().collect()
267    }
268
269    /// Get activity log entries filtered by activity type
270    pub fn get_entries_by_type(&self, activity_type: ActivityType) -> Vec<ActivityLogEntry> {
271        self.entries
272            .iter()
273            .filter(|entry| entry.activity_type == activity_type)
274            .cloned()
275            .collect()
276    }
277
278    /// Get activity log entries for a specific step
279    pub fn get_entries_for_step(&self, step_id: &str) -> Vec<ActivityLogEntry> {
280        self.entries
281            .iter()
282            .filter(|entry| entry.step_id.as_deref() == Some(step_id))
283            .cloned()
284            .collect()
285    }
286
287    /// Clear all activity log entries
288    pub fn clear(&mut self) {
289        self.entries.clear();
290    }
291
292    /// Get the number of activity log entries
293    pub fn len(&self) -> usize {
294        self.entries.len()
295    }
296
297    /// Check if activity log is empty
298    pub fn is_empty(&self) -> bool {
299        self.entries.is_empty()
300    }
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306
307    #[test]
308    fn test_create_activity_logger() {
309        let logger = ActivityLogger::new(100);
310        assert!(logger.is_empty());
311        assert_eq!(logger.len(), 0);
312    }
313
314    #[test]
315    fn test_log_activity() {
316        let mut logger = ActivityLogger::new(100);
317
318        logger.log(
319            ActivityType::WorkflowStarted,
320            None,
321            "Workflow started".to_string(),
322            serde_json::json!({}),
323        );
324
325        assert_eq!(logger.len(), 1);
326        assert!(!logger.is_empty());
327    }
328
329    #[test]
330    fn test_log_workflow_started() {
331        let mut logger = ActivityLogger::new(100);
332        logger.log_workflow_started("test-workflow");
333
334        assert_eq!(logger.len(), 1);
335        let entries = logger.get_entries();
336        assert_eq!(entries[0].activity_type, ActivityType::WorkflowStarted);
337    }
338
339    #[test]
340    fn test_log_step_started() {
341        let mut logger = ActivityLogger::new(100);
342        logger.log_step_started("step1", "Step 1");
343
344        assert_eq!(logger.len(), 1);
345        let entries = logger.get_entries();
346        assert_eq!(entries[0].activity_type, ActivityType::StepStarted);
347        assert_eq!(entries[0].step_id, Some("step1".to_string()));
348    }
349
350    #[test]
351    fn test_get_entries_by_type() {
352        let mut logger = ActivityLogger::new(100);
353        logger.log_workflow_started("test-workflow");
354        logger.log_step_started("step1", "Step 1");
355        logger.log_workflow_completed("test-workflow", 100);
356
357        let workflow_entries = logger.get_entries_by_type(ActivityType::WorkflowStarted);
358        assert_eq!(workflow_entries.len(), 1);
359
360        let step_entries = logger.get_entries_by_type(ActivityType::StepStarted);
361        assert_eq!(step_entries.len(), 1);
362    }
363
364    #[test]
365    fn test_get_entries_for_step() {
366        let mut logger = ActivityLogger::new(100);
367        logger.log_step_started("step1", "Step 1");
368        logger.log_step_completed("step1", "Step 1", 100);
369        logger.log_step_started("step2", "Step 2");
370
371        let step1_entries = logger.get_entries_for_step("step1");
372        assert_eq!(step1_entries.len(), 2);
373
374        let step2_entries = logger.get_entries_for_step("step2");
375        assert_eq!(step2_entries.len(), 1);
376    }
377
378    #[test]
379    fn test_max_entries_limit() {
380        let mut logger = ActivityLogger::new(3);
381
382        logger.log_workflow_started("workflow1");
383        logger.log_workflow_started("workflow2");
384        logger.log_workflow_started("workflow3");
385        logger.log_workflow_started("workflow4");
386
387        // Should only keep the last 3 entries
388        assert_eq!(logger.len(), 3);
389    }
390
391    #[test]
392    fn test_clear_entries() {
393        let mut logger = ActivityLogger::new(100);
394        logger.log_workflow_started("test-workflow");
395        logger.log_step_started("step1", "Step 1");
396
397        assert_eq!(logger.len(), 2);
398
399        logger.clear();
400        assert!(logger.is_empty());
401        assert_eq!(logger.len(), 0);
402    }
403
404    #[test]
405    fn test_log_error() {
406        let mut logger = ActivityLogger::new(100);
407        logger.log_error(Some("step1"), "Something went wrong");
408
409        assert_eq!(logger.len(), 1);
410        let entries = logger.get_entries();
411        assert_eq!(entries[0].activity_type, ActivityType::Error);
412        assert_eq!(entries[0].step_id, Some("step1".to_string()));
413    }
414
415    #[test]
416    fn test_log_approval_workflow() {
417        let mut logger = ActivityLogger::new(100);
418        logger.log_approval_requested("step1", "Please review");
419        logger.log_approval_granted("step1");
420
421        assert_eq!(logger.len(), 2);
422
423        let approval_entries = logger.get_entries_by_type(ActivityType::ApprovalRequested);
424        assert_eq!(approval_entries.len(), 1);
425
426        let granted_entries = logger.get_entries_by_type(ActivityType::ApprovalGranted);
427        assert_eq!(granted_entries.len(), 1);
428    }
429}