use crate::peer::PeerInfo;
use serde::{Deserialize, Serialize};
use std::fs::OpenOptions;
use std::io::Write;
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoutingLogEntry {
pub timestamp: String,
pub message_id: String,
pub node_id: String,
pub from_peer: Option<String>,
pub selected_peers: Vec<PeerSelection>,
pub available_peers: Vec<PeerMetricsSnapshot>,
pub message_context: MessageContext,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeerSelection {
pub peer_id: String,
pub score: f64,
pub metrics: PeerMetricsSnapshot,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeerMetricsSnapshot {
pub peer_id: String,
pub latency_ms: Option<f64>,
pub uptime_secs: f64,
pub ping_count: u32,
pub ping_failures: u32,
pub reliability_score: f64,
pub is_connected: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageContext {
pub ttl: u8,
pub path_length: usize,
pub is_broadcast: bool,
pub target_peer: Option<String>,
}
impl From<&PeerInfo> for PeerMetricsSnapshot {
fn from(peer: &PeerInfo) -> Self {
Self {
peer_id: peer.node_id.clone(),
latency_ms: peer.metrics.latency.map(|d| d.as_secs_f64() * 1000.0),
uptime_secs: peer.metrics.uptime.as_secs_f64(),
ping_count: peer.metrics.ping_count,
ping_failures: peer.metrics.ping_failures,
reliability_score: peer.metrics.reliability_score() as f64,
is_connected: peer.is_connected(),
}
}
}
pub struct RoutingLogger {
log_file: Arc<RwLock<Option<PathBuf>>>,
}
impl RoutingLogger {
pub fn new() -> Self {
Self {
log_file: Arc::new(RwLock::new(None)),
}
}
pub async fn init(&self, log_dir: Option<PathBuf>) {
let log_path = if let Some(dir) = log_dir {
dir.join("ai_routing_logs.jsonl")
} else {
PathBuf::from("logs").join("ai_routing_logs.jsonl")
};
if let Some(parent) = log_path.parent() {
let _ = std::fs::create_dir_all(parent);
}
let mut file = self.log_file.write().await;
*file = Some(log_path);
}
pub async fn log_routing_decision(&self, entry: RoutingLogEntry) {
let file_path = {
let file = self.log_file.read().await;
file.clone()
};
if let Some(path) = file_path {
if let Ok(mut file) = OpenOptions::new().create(true).append(true).open(&path) {
if let Ok(json) = serde_json::to_string(&entry) {
let _ = writeln!(file, "{}", json);
let _ = file.flush();
}
}
}
}
}
impl Default for RoutingLogger {
fn default() -> Self {
Self::new()
}
}
impl Clone for RoutingLogger {
fn clone(&self) -> Self {
Self {
log_file: self.log_file.clone(),
}
}
}