use crate::llm::actions::protocol_trait::ActionResult;
use crate::protocol::log_template::{LogLevel, LogTemplate};
use crate::protocol::Event;
use crate::server::connection::ConnectionId;
use crate::state::ServerId;
use crate::utils::clock::Instant;
use serde_json::Value;
use std::net::SocketAddr;
use tokio::sync::mpsc::UnboundedSender;
use tracing::{debug, info, trace};
pub struct EventLogContext<'a> {
pub event: &'a Event,
pub start_time: Instant,
pub server_id: ServerId,
pub connection_id: Option<ConnectionId>,
pub client_addr: Option<SocketAddr>,
pub protocol_name: String,
}
impl<'a> EventLogContext<'a> {
pub fn new(
event: &'a Event,
server_id: ServerId,
connection_id: Option<ConnectionId>,
client_addr: Option<SocketAddr>,
protocol_name: impl Into<String>,
) -> Self {
Self {
event,
start_time: Instant::now(),
server_id,
connection_id,
client_addr,
protocol_name: protocol_name.into(),
}
}
pub fn build_enriched_data(&self) -> Value {
let mut data = self.event.data.clone();
if let Some(obj) = data.as_object_mut() {
if let Some(addr) = self.client_addr {
obj.insert(
"client_ip".to_string(),
serde_json::json!(addr.ip().to_string()),
);
obj.insert("client_port".to_string(), serde_json::json!(addr.port()));
}
obj.insert(
"server_id".to_string(),
serde_json::json!(self.server_id.as_u32()),
);
if let Some(conn_id) = self.connection_id {
obj.insert(
"connection_id".to_string(),
serde_json::json!(conn_id.as_u32()),
);
}
obj.insert(
"protocol".to_string(),
serde_json::json!(self.protocol_name.clone()),
);
obj.insert("event_id".to_string(), serde_json::json!(self.event.id()));
}
data
}
pub fn log_start(&self, status_tx: Option<&UnboundedSender<String>>) {
let data = self.build_enriched_data();
if let Some(ref template) = self.event.event_type.log_template {
if let Some(msg) = template.render(LogLevel::Debug, &data) {
debug!("{}", msg);
if let Some(tx) = status_tx {
let _ = tx.send(format!("[DEBUG] {}", msg));
}
return;
}
}
let fallback_msg = format!(
"{} event '{}' from {}",
self.protocol_name,
self.event.id(),
self.client_addr
.map(|a| a.to_string())
.unwrap_or_else(|| "unknown".to_string())
);
debug!("{}", fallback_msg);
if let Some(tx) = status_tx {
let _ = tx.send(format!("[DEBUG] {}", fallback_msg));
}
}
pub fn log_complete(
&self,
status_tx: Option<&UnboundedSender<String>>,
action_results: &[ActionResult],
) {
let duration = self.start_time.elapsed();
let mut data = self.build_enriched_data();
if let Some(obj) = data.as_object_mut() {
obj.insert(
"duration_ms".to_string(),
serde_json::json!(duration.as_millis() as u64),
);
obj.insert(
"action_count".to_string(),
serde_json::json!(action_results.len()),
);
let response_bytes: usize = action_results
.iter()
.flat_map(|r| r.get_all_output())
.map(|o| o.len())
.sum();
obj.insert(
"response_bytes".to_string(),
serde_json::json!(response_bytes),
);
for result in action_results {
if let ActionResult::Custom {
name,
data: custom_data,
} = result
{
if let Some(status) = custom_data.get("status") {
obj.insert("status".to_string(), status.clone());
}
obj.insert("action_name".to_string(), serde_json::json!(name));
break;
}
}
}
if let Some(ref template) = self.event.event_type.log_template {
if let Some(msg) = template.render(LogLevel::Trace, &data) {
trace!("{}", msg);
if let Some(tx) = status_tx {
let _ = tx.send(format!("[TRACE] {}", msg));
}
}
if let Some(msg) = template.render(LogLevel::Info, &data) {
info!("{}", msg);
if let Some(tx) = status_tx {
let _ = tx.send(format!("[INFO] {}", msg));
}
}
if let Some(msg) = template.render(LogLevel::Debug, &data) {
let msg_with_timing = format!("{} ({}ms)", msg, duration.as_millis());
debug!("{}", msg_with_timing);
if let Some(tx) = status_tx {
let _ = tx.send(format!("[DEBUG] {}", msg_with_timing));
}
} else {
let fallback_msg = format!(
"{} '{}' completed in {}ms, {} action(s), {} bytes",
self.protocol_name,
self.event.id(),
duration.as_millis(),
action_results.len(),
data.get("response_bytes")
.and_then(|v| v.as_u64())
.unwrap_or(0)
);
debug!("{}", fallback_msg);
if let Some(tx) = status_tx {
let _ = tx.send(format!("[DEBUG] {}", fallback_msg));
}
}
} else {
let fallback_msg = format!(
"{} '{}' completed in {}ms, {} action(s)",
self.protocol_name,
self.event.id(),
duration.as_millis(),
action_results.len()
);
debug!("{}", fallback_msg);
if let Some(tx) = status_tx {
let _ = tx.send(format!("[DEBUG] {}", fallback_msg));
}
}
}
}
pub fn log_action_result(
action_name: &str,
action_data: &Value,
result: &ActionResult,
template: Option<&LogTemplate>,
status_tx: Option<&UnboundedSender<String>>,
) {
let mut enriched_data = action_data.clone();
if let Some(obj) = enriched_data.as_object_mut() {
let output_bytes: usize = result.get_all_output().iter().map(|o| o.len()).sum();
obj.insert("output_bytes".to_string(), serde_json::json!(output_bytes));
obj.insert(
"closes_connection".to_string(),
serde_json::json!(result.closes_connection()),
);
obj.insert(
"waits_for_more".to_string(),
serde_json::json!(result.waits_for_more()),
);
obj.insert("action_name".to_string(), serde_json::json!(action_name));
}
if let Some(template) = template {
if let Some(msg) = template.render(LogLevel::Trace, &enriched_data) {
trace!("Action {}: {}", action_name, msg);
if let Some(tx) = status_tx {
let _ = tx.send(format!("[TRACE] Action {}: {}", action_name, msg));
}
}
if let Some(msg) = template.render(LogLevel::Debug, &enriched_data) {
debug!("Action {}: {}", action_name, msg);
if let Some(tx) = status_tx {
let _ = tx.send(format!("[DEBUG] Action {}: {}", action_name, msg));
}
}
if let Some(msg) = template.render(LogLevel::Info, &enriched_data) {
info!("Action {}: {}", action_name, msg);
if let Some(tx) = status_tx {
let _ = tx.send(format!("[INFO] Action {}: {}", action_name, msg));
}
}
} else {
let summary = crate::llm::actions::summarize_action(action_data);
debug!("Action executed: {}", summary);
if let Some(tx) = status_tx {
let _ = tx.send(format!("[DEBUG] Action: {}", summary));
}
}
}