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 NodeEvent::MessageRejected {
249 message_id,
250 reason,
251 from,
252 to,
253 } => {
254 format!(
255 "[{}] MESSAGE REJECTED: id={}, from={}, to={}, reason={}",
256 timestamp, message_id, from, to, reason
257 )
258 }
259 NodeEvent::MessageAccepted {
260 message_id,
261 message_type,
262 from,
263 to,
264 } => {
265 format!(
266 "[{}] MESSAGE ACCEPTED: id={}, type={}, from={}, to={}",
267 timestamp, message_id, message_type, from, to
268 )
269 }
270 NodeEvent::ReplyReceived {
271 original_message_id,
272 ..
273 } => {
274 format!(
275 "[{}] REPLY RECEIVED: original_id={}",
276 timestamp, original_message_id
277 )
278 }
279 NodeEvent::TransactionStateChanged {
280 transaction_id,
281 old_state,
282 new_state,
283 agent_did,
284 } => match agent_did {
285 Some(did) => format!(
286 "[{}] TRANSACTION STATE CHANGED: id={}, {} -> {} (by {})",
287 timestamp, transaction_id, old_state, new_state, did
288 ),
289 None => format!(
290 "[{}] TRANSACTION STATE CHANGED: id={}, {} -> {}",
291 timestamp, transaction_id, old_state, new_state
292 ),
293 },
294 }
295 }
296
297 fn format_structured_log(&self, event: &NodeEvent) -> Result<String> {
299 let timestamp = DateTime::<Utc>::from(SystemTime::now()).to_rfc3339();
301
302 let (event_type, event_data) = match event {
304 NodeEvent::PlainMessageReceived { message } => (
305 "message_received",
306 json!({
307 "message": message,
308 }),
309 ),
310 NodeEvent::PlainMessageSent { message, from, to } => (
311 "message_sent",
312 json!({
313 "from": from,
314 "to": to,
315 "message": message,
316 }),
317 ),
318 NodeEvent::AgentRegistered { did } => (
319 "agent_registered",
320 json!({
321 "did": did,
322 }),
323 ),
324 NodeEvent::AgentUnregistered { did } => (
325 "agent_unregistered",
326 json!({
327 "did": did,
328 }),
329 ),
330 NodeEvent::DidResolved { did, success } => (
331 "did_resolved",
332 json!({
333 "did": did,
334 "success": success,
335 }),
336 ),
337 NodeEvent::AgentPlainMessage { did, message } => (
338 "agent_message",
339 json!({
340 "did": did,
341 "message_length": message.len(),
342 }),
343 ),
344 NodeEvent::MessageRejected {
345 message_id,
346 reason,
347 from,
348 to,
349 } => (
350 "message_rejected",
351 json!({
352 "message_id": message_id,
353 "reason": reason,
354 "from": from,
355 "to": to,
356 }),
357 ),
358 NodeEvent::MessageAccepted {
359 message_id,
360 message_type,
361 from,
362 to,
363 } => (
364 "message_accepted",
365 json!({
366 "message_id": message_id,
367 "message_type": message_type,
368 "from": from,
369 "to": to,
370 }),
371 ),
372 NodeEvent::ReplyReceived {
373 original_message_id,
374 reply_message,
375 original_message,
376 } => (
377 "reply_received",
378 json!({
379 "original_message_id": original_message_id,
380 "reply_message": serde_json::to_value(reply_message).unwrap_or(json!(null)),
381 "original_message": serde_json::to_value(original_message).unwrap_or(json!(null)),
382 }),
383 ),
384 NodeEvent::TransactionStateChanged {
385 transaction_id,
386 old_state,
387 new_state,
388 agent_did,
389 } => (
390 "transaction_state_changed",
391 json!({
392 "transaction_id": transaction_id,
393 "old_state": old_state,
394 "new_state": new_state,
395 "agent_did": agent_did,
396 }),
397 ),
398 };
399
400 let log_entry = json!({
402 "timestamp": timestamp,
403 "event_type": event_type,
404 "data": event_data,
405 });
406
407 serde_json::to_string(&log_entry).map_err(|e| Error::Serialization(e.to_string()))
409 }
410}
411
412#[async_trait]
413impl EventSubscriber for EventLogger {
414 async fn handle_event(&self, event: NodeEvent) {
415 if let Err(err) = self.log_event(&event) {
416 error!("Failed to log event: {}", err);
417 }
418 }
419}
420
421impl fmt::Debug for EventLogger {
422 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423 f.debug_struct("EventLogger")
424 .field("config", &self.config)
425 .field("file", &self.file.is_some())
426 .finish()
427 }
428}