1use std::fmt;
43use std::fs::{File, OpenOptions};
44use std::io::{self, Write};
45use std::path::Path;
46use std::sync::{Arc, Mutex};
47use std::time::SystemTime;
48
49use async_trait::async_trait;
50use chrono::{DateTime, Utc};
51use serde_json::json;
52use tracing::{debug, error, info, trace, warn};
53
54use crate::error::{Error, Result};
55use crate::event::{EventSubscriber, NodeEvent};
56
57#[derive(Clone)]
59pub enum LogDestination {
60 Console,
62
63 File {
65 path: String,
67
68 max_size: Option<usize>,
70
71 rotate: bool,
73 },
74
75 Custom(Arc<dyn Fn(&str) + Send + Sync>),
77}
78
79impl fmt::Debug for LogDestination {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 match self {
83 LogDestination::Console => write!(f, "LogDestination::Console"),
84 LogDestination::File {
85 path,
86 max_size,
87 rotate,
88 } => f
89 .debug_struct("LogDestination::File")
90 .field("path", path)
91 .field("max_size", max_size)
92 .field("rotate", rotate)
93 .finish(),
94 LogDestination::Custom(_) => write!(f, "LogDestination::Custom(<function>)"),
95 }
96 }
97}
98
99#[derive(Debug, Clone)]
101pub struct EventLoggerConfig {
102 pub destination: LogDestination,
104
105 pub structured: bool,
107
108 pub log_level: log::Level,
110}
111
112impl Default for EventLoggerConfig {
113 fn default() -> Self {
114 Self {
115 destination: LogDestination::Console,
116 structured: false,
117 log_level: log::Level::Info,
118 }
119 }
120}
121
122pub struct EventLogger {
128 config: EventLoggerConfig,
130
131 file: Option<Arc<Mutex<File>>>,
133}
134
135impl EventLogger {
136 pub fn new(config: EventLoggerConfig) -> Self {
138 let file = match &config.destination {
139 LogDestination::File { path, .. } => match Self::open_log_file(path) {
140 Ok(file) => Some(Arc::new(Mutex::new(file))),
141 Err(err) => {
142 error!("Failed to open log file {}: {}", path, err);
143 None
144 }
145 },
146 _ => None,
147 };
148
149 Self { config, file }
150 }
151
152 fn open_log_file(path: &str) -> io::Result<File> {
154 if let Some(parent) = Path::new(path).parent() {
156 std::fs::create_dir_all(parent)?;
157 }
158
159 OpenOptions::new().create(true).append(true).open(path)
161 }
162
163 fn log_event(&self, event: &NodeEvent) -> Result<()> {
165 let log_message = if self.config.structured {
166 self.format_structured_log(event)?
167 } else {
168 self.format_plain_log(event)
169 };
170
171 match &self.config.destination {
172 LogDestination::Console => {
173 match self.config.log_level {
175 log::Level::Error => error!("{}", log_message),
176 log::Level::Warn => warn!("{}", log_message),
177 log::Level::Info => info!("{}", log_message),
178 log::Level::Debug => debug!("{}", log_message),
179 log::Level::Trace => trace!("{}", log_message),
180 }
181 Ok(())
182 }
183 LogDestination::File { .. } => {
184 if let Some(file) = &self.file {
185 let mut file_guard = file.lock().map_err(|_| {
186 Error::Configuration("Failed to acquire log file lock".to_string())
187 })?;
188
189 writeln!(file_guard, "{}", log_message).map_err(|err| {
191 Error::Configuration(format!("Failed to write to log file: {}", err))
192 })?;
193
194 file_guard.flush().map_err(|err| {
196 Error::Configuration(format!("Failed to flush log file: {}", err))
197 })?;
198
199 Ok(())
200 } else {
201 error!("{}", log_message);
203 Ok(())
204 }
205 }
206 LogDestination::Custom(func) => {
207 func(&log_message);
209 Ok(())
210 }
211 }
212 }
213
214 fn format_plain_log(&self, event: &NodeEvent) -> String {
216 let timestamp = DateTime::<Utc>::from(SystemTime::now()).format("%Y-%m-%dT%H:%M:%S%.3fZ");
217
218 match event {
219 NodeEvent::PlainMessageReceived { message } => {
220 format!("[{}] MESSAGE RECEIVED: {}", timestamp, message)
221 }
222 NodeEvent::PlainMessageSent { message, from, to } => {
223 format!(
224 "[{}] MESSAGE SENT: from={}, to={}, message={}",
225 timestamp, from, to, message
226 )
227 }
228 NodeEvent::AgentRegistered { did } => {
229 format!("[{}] AGENT REGISTERED: {}", timestamp, did)
230 }
231 NodeEvent::AgentUnregistered { did } => {
232 format!("[{}] AGENT UNREGISTERED: {}", timestamp, did)
233 }
234 NodeEvent::DidResolved { did, success } => {
235 format!(
236 "[{}] DID RESOLVED: did={}, success={}",
237 timestamp, did, success
238 )
239 }
240 NodeEvent::AgentPlainMessage { did, message } => {
241 format!(
242 "[{}] AGENT MESSAGE: did={}, message_length={}",
243 timestamp,
244 did,
245 message.len()
246 )
247 }
248 }
249 }
250
251 fn format_structured_log(&self, event: &NodeEvent) -> Result<String> {
253 let timestamp = DateTime::<Utc>::from(SystemTime::now()).to_rfc3339();
255
256 let (event_type, event_data) = match event {
258 NodeEvent::PlainMessageReceived { message } => (
259 "message_received",
260 json!({
261 "message": message,
262 }),
263 ),
264 NodeEvent::PlainMessageSent { message, from, to } => (
265 "message_sent",
266 json!({
267 "from": from,
268 "to": to,
269 "message": message,
270 }),
271 ),
272 NodeEvent::AgentRegistered { did } => (
273 "agent_registered",
274 json!({
275 "did": did,
276 }),
277 ),
278 NodeEvent::AgentUnregistered { did } => (
279 "agent_unregistered",
280 json!({
281 "did": did,
282 }),
283 ),
284 NodeEvent::DidResolved { did, success } => (
285 "did_resolved",
286 json!({
287 "did": did,
288 "success": success,
289 }),
290 ),
291 NodeEvent::AgentPlainMessage { did, message } => (
292 "agent_message",
293 json!({
294 "did": did,
295 "message_length": message.len(),
296 }),
297 ),
298 };
299
300 let log_entry = json!({
302 "timestamp": timestamp,
303 "event_type": event_type,
304 "data": event_data,
305 });
306
307 serde_json::to_string(&log_entry).map_err(Error::Serialization)
309 }
310}
311
312#[async_trait]
313impl EventSubscriber for EventLogger {
314 async fn handle_event(&self, event: NodeEvent) {
315 if let Err(err) = self.log_event(&event) {
316 error!("Failed to log event: {}", err);
317 }
318 }
319}
320
321impl fmt::Debug for EventLogger {
322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323 f.debug_struct("EventLogger")
324 .field("config", &self.config)
325 .field("file", &self.file.is_some())
326 .finish()
327 }
328}