use std::time::Duration;
use tracing::{debug, error, info, warn};
use crate::error::AiError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogLevel {
Debug,
Info,
Warn,
Error,
}
#[derive(Debug, Clone)]
pub struct LlmOperation {
pub operation: String,
pub provider: String,
pub model: String,
pub latency: Option<Duration>,
pub tokens: Option<u32>,
pub success: bool,
pub error: Option<String>,
}
impl LlmOperation {
pub fn new(
operation: impl Into<String>,
provider: impl Into<String>,
model: impl Into<String>,
) -> Self {
Self {
operation: operation.into(),
provider: provider.into(),
model: model.into(),
latency: None,
tokens: None,
success: false,
error: None,
}
}
#[must_use]
pub fn with_latency(mut self, latency: Duration) -> Self {
self.latency = Some(latency);
self
}
#[must_use]
pub fn with_tokens(mut self, tokens: u32) -> Self {
self.tokens = Some(tokens);
self
}
#[must_use]
pub fn success(mut self) -> Self {
self.success = true;
self
}
#[must_use]
pub fn failed(mut self, error: &AiError) -> Self {
self.success = false;
self.error = Some(format!("{error}"));
self
}
pub fn log(&self, level: LogLevel) {
let latency_ms = self.latency.map_or(0, |d| d.as_millis());
let tokens = self.tokens.unwrap_or(0);
match level {
LogLevel::Debug => {
debug!(
operation = %self.operation,
provider = %self.provider,
model = %self.model,
latency_ms = latency_ms,
tokens = tokens,
success = self.success,
error = ?self.error,
"LLM operation"
);
}
LogLevel::Info => {
info!(
operation = %self.operation,
provider = %self.provider,
model = %self.model,
latency_ms = latency_ms,
tokens = tokens,
success = self.success,
error = ?self.error,
"LLM operation"
);
}
LogLevel::Warn => {
warn!(
operation = %self.operation,
provider = %self.provider,
model = %self.model,
latency_ms = latency_ms,
tokens = tokens,
success = self.success,
error = ?self.error,
"LLM operation"
);
}
LogLevel::Error => {
error!(
operation = %self.operation,
provider = %self.provider,
model = %self.model,
latency_ms = latency_ms,
tokens = tokens,
success = self.success,
error = ?self.error,
"LLM operation"
);
}
}
}
}
pub fn log_evaluation(
operation: &str,
subject: &str,
score: f64,
confidence: f64,
latency: Duration,
) {
info!(
operation = %operation,
subject = %subject,
score = score,
confidence = confidence,
latency_ms = latency.as_millis(),
"AI evaluation completed"
);
}
pub fn log_verification(
operation: &str,
evidence_type: &str,
approved: bool,
confidence: f64,
latency: Duration,
) {
if approved {
info!(
operation = %operation,
evidence_type = %evidence_type,
approved = approved,
confidence = confidence,
latency_ms = latency.as_millis(),
"Verification completed"
);
} else {
warn!(
operation = %operation,
evidence_type = %evidence_type,
approved = approved,
confidence = confidence,
latency_ms = latency.as_millis(),
"Verification rejected"
);
}
}
pub fn log_fraud_check(
user_id: &str,
risk_level: &str,
risk_score: f64,
indicators: usize,
latency: Duration,
) {
match risk_level {
"low" | "very_low" => {
debug!(
user_id = %user_id,
risk_level = %risk_level,
risk_score = risk_score,
indicators = indicators,
latency_ms = latency.as_millis(),
"Fraud check completed"
);
}
"medium" => {
info!(
user_id = %user_id,
risk_level = %risk_level,
risk_score = risk_score,
indicators = indicators,
latency_ms = latency.as_millis(),
"Fraud check - medium risk detected"
);
}
_ => {
warn!(
user_id = %user_id,
risk_level = %risk_level,
risk_score = risk_score,
indicators = indicators,
latency_ms = latency.as_millis(),
"Fraud check - high risk detected"
);
}
}
}
pub fn log_cache_hit(key: &str, ttl_remaining: Duration) {
debug!(
key = %key,
ttl_remaining_secs = ttl_remaining.as_secs(),
"Cache hit"
);
}
pub fn log_cache_miss(key: &str) {
debug!(
key = %key,
"Cache miss"
);
}
pub fn log_circuit_breaker_state_change(
circuit: &str,
old_state: &str,
new_state: &str,
reason: &str,
) {
warn!(
circuit = %circuit,
old_state = %old_state,
new_state = %new_state,
reason = %reason,
"Circuit breaker state changed"
);
}
pub fn log_rate_limit_exceeded(tier: &str, limit: f64) {
warn!(
tier = %tier,
limit = limit,
"Rate limit exceeded"
);
}
pub fn log_retry_attempt(operation: &str, attempt: u32, max_attempts: u32, delay: Duration) {
debug!(
operation = %operation,
attempt = attempt,
max_attempts = max_attempts,
delay_ms = delay.as_millis(),
"Retrying operation"
);
}
pub struct PerformanceSpan {
name: String,
start: std::time::Instant,
}
impl PerformanceSpan {
pub fn start(name: impl Into<String>) -> Self {
Self {
name: name.into(),
start: std::time::Instant::now(),
}
}
pub fn end(self) {
let duration = self.start.elapsed();
debug!(
span = %self.name,
duration_ms = duration.as_millis(),
"Performance span completed"
);
}
pub fn end_with_message(self, message: &str) {
let duration = self.start.elapsed();
info!(
span = %self.name,
duration_ms = duration.as_millis(),
message = %message,
"Performance span completed"
);
}
#[must_use]
pub fn elapsed(&self) -> Duration {
self.start.elapsed()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_llm_operation_builder() {
let op = LlmOperation::new("chat", "openai", "gpt-4")
.with_latency(Duration::from_millis(500))
.with_tokens(150)
.success();
assert_eq!(op.operation, "chat");
assert_eq!(op.provider, "openai");
assert_eq!(op.model, "gpt-4");
assert_eq!(op.latency, Some(Duration::from_millis(500)));
assert_eq!(op.tokens, Some(150));
assert!(op.success);
assert!(op.error.is_none());
}
#[test]
fn test_llm_operation_failed() {
let op = LlmOperation::new("chat", "openai", "gpt-4").failed(&AiError::RateLimitExceeded);
assert!(!op.success);
assert!(op.error.is_some());
}
#[test]
fn test_performance_span() {
let span = PerformanceSpan::start("test_operation");
std::thread::sleep(Duration::from_millis(10));
let elapsed = span.elapsed();
assert!(elapsed >= Duration::from_millis(10));
}
}