use crate::prelude::*;
use crate::error::OpError;
use crate::op::Op;
use crate::{DryContext, OpMetadata, WetContext};
use async_trait::async_trait;
use std::time::Instant;
use tracing;
const YELLOW: &str = "\x1b[33m";
const GREEN: &str = "\x1b[32m";
const RED: &str = "\x1b[31m";
const RESET: &str = "\x1b[0m";
pub struct LoggingWrapper<T> {
wrapped_op: Box<dyn Op<T>>,
trigger_name: String,
logger_name: Option<String>,
}
impl<T> LoggingWrapper<T> {
pub fn new(op: Box<dyn Op<T>>, name: String) -> Self {
Self {
wrapped_op: op,
trigger_name: name,
logger_name: None,
}
}
pub fn with_logger(op: Box<dyn Op<T>>, name: String, logger_name: String) -> Self {
Self {
wrapped_op: op,
trigger_name: name,
logger_name: Some(logger_name),
}
}
fn get_logger_name(&self) -> &str {
self.logger_name.as_deref().unwrap_or("LoggingWrapper")
}
fn log_op_start(&self) {
tracing::info!(
logger = self.get_logger_name(),
"{}Starting op: {}{}",
YELLOW,
self.trigger_name,
RESET
);
}
fn log_op_success(&self, duration: std::time::Duration) {
let seconds = duration.as_secs_f64();
tracing::info!(
logger = self.get_logger_name(),
"{}Op '{}' completed in {:.3} seconds{}",
GREEN,
self.trigger_name,
seconds,
RESET
);
}
fn log_op_failure(&self, error: &OpError, duration: std::time::Duration) {
let seconds = duration.as_secs_f64();
tracing::error!(
logger = self.get_logger_name(),
"{}Op '{}' failed after {:.3} seconds: {:?}{}",
RED,
self.trigger_name,
seconds,
error,
RESET
);
}
}
#[async_trait]
impl<T> Op<T> for LoggingWrapper<T>
where
T: Send + 'static,
{
async fn perform(&self, dry: &mut DryContext, wet: &mut WetContext) -> OpResult<T> {
let start_time = Instant::now();
self.log_op_start();
let result = self.wrapped_op.perform(dry, wet).await;
let duration = start_time.elapsed();
match &result {
Ok(_) => self.log_op_success(duration),
Err(error) => {
self.log_op_failure(error, duration);
return Err(crate::ops::wrap_nested_op_exception(
&self.trigger_name,
OpError::ExecutionFailed(format!("{:?}", error)),
));
}
}
result
}
fn metadata(&self) -> OpMetadata {
self.wrapped_op.metadata()
}
}
pub fn create_context_aware_logger<T>(op: Box<dyn Op<T>>) -> LoggingWrapper<T>
where
T: Send + 'static,
{
let caller_name = crate::ops::get_caller_trigger_name();
LoggingWrapper::with_logger(op, caller_name.clone(), caller_name)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::op::Op;
struct TestOp;
#[async_trait]
impl Op<i32> for TestOp {
async fn perform(&self, _dry: &mut DryContext, _wet: &mut WetContext) -> OpResult<i32> {
Ok(42)
}
fn metadata(&self) -> OpMetadata {
OpMetadata::builder("TestOp").build()
}
}
#[tokio::test]
async fn test0029_logging_wrapper_success() {
tracing_subscriber::fmt::try_init().ok();
let mut dry = DryContext::new();
let mut wet = WetContext::new();
let op = Box::new(TestOp);
let logging_wrapper = LoggingWrapper::new(op, "TestOp".to_string());
let result = logging_wrapper.perform(&mut dry, &mut wet).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), 42);
}
struct FailingOp;
#[async_trait]
impl Op<i32> for FailingOp {
async fn perform(&self, _dry: &mut DryContext, _wet: &mut WetContext) -> OpResult<i32> {
Err(OpError::ExecutionFailed("test error".to_string()))
}
fn metadata(&self) -> OpMetadata {
OpMetadata::builder("FailingOp").build()
}
}
#[tokio::test]
async fn test0030_logging_wrapper_failure() {
tracing_subscriber::fmt::try_init().ok();
let mut dry = DryContext::new();
let mut wet = WetContext::new();
let op = Box::new(FailingOp);
let logging_wrapper: LoggingWrapper<i32> = LoggingWrapper::new(op, "FailingOp".to_string());
let result = logging_wrapper.perform(&mut dry, &mut wet).await;
assert!(result.is_err());
match result.unwrap_err() {
OpError::ExecutionFailed(msg) => {
assert!(msg.contains("FailingOp"));
}
_ => panic!("Expected ExecutionFailed error"),
}
}
struct StringOp;
#[async_trait]
impl Op<String> for StringOp {
async fn perform(&self, _dry: &mut DryContext, _wet: &mut WetContext) -> OpResult<String> {
Ok("test".to_string())
}
fn metadata(&self) -> OpMetadata {
OpMetadata::builder("StringOp").build()
}
}
#[tokio::test]
async fn test0031_context_aware_logger() {
let mut dry = DryContext::new();
let mut wet = WetContext::new();
let op = Box::new(StringOp);
let logging_wrapper = create_context_aware_logger(op);
let result = logging_wrapper.perform(&mut dry, &mut wet).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "test");
}
#[test]
fn test0032_ansi_color_constants() {
assert_eq!(YELLOW, "\x1b[33m");
assert_eq!(GREEN, "\x1b[32m");
assert_eq!(RED, "\x1b[31m");
assert_eq!(RESET, "\x1b[0m");
}
}