1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fmt;
5
6use crate::context;
7use crate::level::LogLevel;
8
9type RecordFormatter = Box<dyn Fn(&Record) -> String + Send + Sync>;
12
13pub struct Record {
19 level: LogLevel,
21 message: String,
23 module: String,
25 file: String,
27 line: u32,
29 timestamp: DateTime<Utc>,
31 metadata: HashMap<String, String>,
33 context: HashMap<String, serde_json::Value>,
35 format_fn: Option<RecordFormatter>,
37}
38
39impl Clone for Record {
40 fn clone(&self) -> Self {
41 Self {
42 level: self.level,
43 message: self.message.clone(),
44 module: self.module.clone(),
45 file: self.file.clone(),
46 line: self.line,
47 timestamp: self.timestamp,
48 metadata: self.metadata.clone(),
49 context: self.context.clone(),
50 format_fn: None, }
52 }
53}
54
55impl fmt::Debug for Record {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 f.debug_struct("Record")
58 .field("level", &self.level)
59 .field("message", &self.message)
60 .field("module", &self.module)
61 .field("file", &self.file)
62 .field("line", &self.line)
63 .field("timestamp", &self.timestamp)
64 .field("metadata", &self.metadata)
65 .field("context", &self.context)
66 .field("format_fn", &format_args!("<function>"))
67 .finish()
68 }
69}
70
71impl Record {
72 pub fn new(
74 level: LogLevel,
75 message: impl Into<String>,
76 module: Option<String>,
77 file: Option<String>,
78 line: Option<u32>,
79 ) -> Self {
80 let mut context_map = HashMap::new();
81 for (k, v) in context::current_context().into_iter() {
82 context_map.insert(k, context_value_to_json(v));
83 }
84 Self {
85 level,
86 message: message.into(),
87 module: module.unwrap_or_else(|| String::from("unknown")),
88 file: file.unwrap_or_else(|| String::from("unknown")),
89 line: line.unwrap_or(0),
90 timestamp: Utc::now(),
91 metadata: HashMap::new(),
92 context: context_map,
93 format_fn: None,
94 }
95 }
96
97 pub fn level(&self) -> LogLevel {
99 self.level
100 }
101
102 pub fn message(&self) -> &str {
104 &self.message
105 }
106
107 pub fn module(&self) -> &str {
109 &self.module
110 }
111
112 pub fn file(&self) -> &str {
114 &self.file
115 }
116
117 pub fn line(&self) -> u32 {
119 self.line
120 }
121
122 pub fn timestamp(&self) -> DateTime<Utc> {
124 self.timestamp
125 }
126
127 pub fn metadata(&self) -> &HashMap<String, String> {
129 &self.metadata
130 }
131
132 pub fn context(&self) -> &HashMap<String, serde_json::Value> {
134 &self.context
135 }
136
137 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
139 self.metadata.insert(key.into(), value.into());
140 self
141 }
142
143 pub fn with_structured_data<T: serde::Serialize + ?Sized>(
162 mut self,
163 key: &str,
164 value: &T,
165 ) -> Result<Self, serde_json::Error> {
166 let json_value = serde_json::to_string(value)?;
167 self.metadata.insert(key.to_string(), json_value);
168 Ok(self)
169 }
170
171 pub fn with_context(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
189 self.context.insert(key.into(), value);
190 self
191 }
192
193 pub fn with_deferred_format<F>(mut self, format_fn: F) -> Self
209 where
210 F: Fn(&Record) -> String + Send + Sync + 'static,
211 {
212 self.format_fn = Some(Box::new(format_fn));
213 self
214 }
215
216 pub fn get_metadata(&self, key: &str) -> Option<&str> {
218 self.metadata.get(key).map(String::as_str)
219 }
220
221 pub fn get_context(&self, key: &str) -> Option<&serde_json::Value> {
223 self.context.get(key)
224 }
225
226 pub fn has_context(&self) -> bool {
228 !self.context.is_empty()
229 }
230
231 pub fn has_metadata(&self) -> bool {
233 !self.metadata.is_empty()
234 }
235
236 pub fn has_formatter(&self) -> bool {
238 self.format_fn.is_some()
239 }
240
241 pub fn context_len(&self) -> usize {
243 self.context.len()
244 }
245
246 pub fn metadata_len(&self) -> usize {
248 self.metadata.len()
249 }
250}
251
252impl fmt::Display for Record {
253 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254 if let Some(format_fn) = &self.format_fn {
255 write!(f, "{}", format_fn(self))
256 } else {
257 write!(
258 f,
259 "[{}] {} {}:{} - {}",
260 self.timestamp.format("%Y-%m-%d %H:%M:%S%.3f"),
261 self.level,
262 self.file,
263 self.line,
264 self.message
265 )
266 }
267 }
268}
269
270impl Serialize for Record {
271 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
272 where
273 S: serde::Serializer,
274 {
275 use serde::ser::SerializeStruct;
276 let mut state = serializer.serialize_struct("Record", 7)?;
277 state.serialize_field("level", &self.level)?;
278 state.serialize_field("message", &self.message)?;
279 state.serialize_field("module", &self.module)?;
280 state.serialize_field("file", &self.file)?;
281 state.serialize_field("line", &self.line)?;
282 state.serialize_field("timestamp", &self.timestamp.to_rfc3339())?;
283 state.serialize_field("metadata", &self.metadata)?;
284 state.end()
285 }
286}
287
288impl<'de> Deserialize<'de> for Record {
289 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
290 where
291 D: serde::Deserializer<'de>,
292 {
293 #[derive(Deserialize)]
294 #[serde(field_identifier, rename_all = "lowercase")]
295 enum Field {
296 Level,
297 Message,
298 Module,
299 File,
300 Line,
301 Timestamp,
302 Metadata,
303 }
304
305 struct RecordVisitor;
306
307 impl<'de> serde::de::Visitor<'de> for RecordVisitor {
308 type Value = Record;
309
310 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
311 formatter.write_str("struct Record")
312 }
313
314 fn visit_map<V>(self, mut map: V) -> Result<Record, V::Error>
315 where
316 V: serde::de::MapAccess<'de>,
317 {
318 let mut level = None;
319 let mut message = None;
320 let mut module = None;
321 let mut file = None;
322 let mut line = None;
323 let mut timestamp = None;
324 let mut metadata = None;
325
326 while let Some(key) = map.next_key()? {
327 match key {
328 Field::Level => {
329 if level.is_some() {
330 return Err(serde::de::Error::duplicate_field("level"));
331 }
332 level = Some(map.next_value()?);
333 }
334 Field::Message => {
335 if message.is_some() {
336 return Err(serde::de::Error::duplicate_field("message"));
337 }
338 message = Some(map.next_value()?);
339 }
340 Field::Module => {
341 if module.is_some() {
342 return Err(serde::de::Error::duplicate_field("module"));
343 }
344 module = Some(map.next_value()?);
345 }
346 Field::File => {
347 if file.is_some() {
348 return Err(serde::de::Error::duplicate_field("file"));
349 }
350 file = Some(map.next_value()?);
351 }
352 Field::Line => {
353 if line.is_some() {
354 return Err(serde::de::Error::duplicate_field("line"));
355 }
356 line = Some(map.next_value()?);
357 }
358 Field::Timestamp => {
359 if timestamp.is_some() {
360 return Err(serde::de::Error::duplicate_field("timestamp"));
361 }
362 let ts_str: String = map.next_value()?;
363 timestamp = Some(
364 DateTime::parse_from_rfc3339(&ts_str)
365 .map_err(serde::de::Error::custom)?
366 .with_timezone(&Utc),
367 );
368 }
369 Field::Metadata => {
370 if metadata.is_some() {
371 return Err(serde::de::Error::duplicate_field("metadata"));
372 }
373 metadata = Some(map.next_value()?);
374 }
375 }
376 }
377
378 let level = level.ok_or_else(|| serde::de::Error::missing_field("level"))?;
379 let message = message.ok_or_else(|| serde::de::Error::missing_field("message"))?;
380 let module = module.ok_or_else(|| serde::de::Error::missing_field("module"))?;
381 let file = file.ok_or_else(|| serde::de::Error::missing_field("file"))?;
382 let line = line.ok_or_else(|| serde::de::Error::missing_field("line"))?;
383 let timestamp =
384 timestamp.ok_or_else(|| serde::de::Error::missing_field("timestamp"))?;
385 let metadata = metadata.unwrap_or_default();
386
387 Ok(Record {
388 level,
389 message,
390 module,
391 file,
392 line,
393 timestamp,
394 metadata,
395 context: HashMap::new(),
396 format_fn: None,
397 })
398 }
399 }
400
401 deserializer.deserialize_struct(
402 "Record",
403 &[
404 "level",
405 "message",
406 "module",
407 "file",
408 "line",
409 "timestamp",
410 "metadata",
411 ],
412 RecordVisitor,
413 )
414 }
415}
416
417fn context_value_to_json(val: context::ContextValue) -> serde_json::Value {
418 match val {
419 context::ContextValue::String(s) => serde_json::Value::String(s),
420 context::ContextValue::Integer(i) => serde_json::Value::Number(i.into()),
421 context::ContextValue::Float(f) => match serde_json::Number::from_f64(f) {
422 Some(num) => serde_json::Value::Number(num),
423 None => serde_json::Value::Null,
424 },
425 context::ContextValue::Bool(b) => serde_json::Value::Bool(b),
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 use super::*;
432 use serde_json::json;
433
434 #[test]
435 fn test_record_creation() {
436 let record = Record::new(LogLevel::Info, "test message", None, None, None);
437 assert_eq!(record.level(), LogLevel::Info);
438 assert_eq!(record.message(), "test message");
439 assert_eq!(record.module(), "unknown");
440 assert_eq!(record.file(), "unknown");
441 assert_eq!(record.line(), 0);
442 }
443
444 #[test]
445 fn test_record_with_metadata() {
446 let record = Record::new(LogLevel::Info, "test message", None, None, None)
447 .with_metadata("key", "value");
448 assert_eq!(record.metadata().get("key").unwrap(), "value");
449 }
450
451 #[test]
452 fn test_record_with_all_fields() {
453 let record = Record::new(
454 LogLevel::Info,
455 "test message",
456 Some("test_module".to_string()),
457 Some("test_file.rs".to_string()),
458 Some(42),
459 );
460 assert_eq!(record.module(), "test_module");
461 assert_eq!(record.file(), "test_file.rs");
462 assert_eq!(record.line(), 42);
463 }
464
465 #[test]
466 fn test_record_display() {
467 let record = Record::new(LogLevel::Error, "Test error message", None, None, None);
468
469 let display = format!("{}", record);
470 assert!(display.contains("ERROR"));
471 assert!(display.contains("unknown:0"));
472 assert!(display.contains("Test error message"));
473 }
474
475 #[test]
476 fn test_record_metadata_overwrite() {
477 let record = Record::new(
478 LogLevel::Info,
479 "Test message",
480 Some("test_module".to_string()),
481 Some("test.rs".to_string()),
482 Some(42),
483 )
484 .with_metadata("key", "value1")
485 .with_metadata("key", "value2");
486 assert_eq!(record.metadata().get("key"), Some(&"value2".to_string()));
487 }
488
489 #[test]
490 fn test_record_structured_data() {
491 let record = Record::new(
492 LogLevel::Info,
493 "test message",
494 Some("test_module".to_string()),
495 Some("test.rs".to_string()),
496 Some(42),
497 );
498 let record = record.with_structured_data("key", &"value").unwrap();
499 assert_eq!(record.metadata().get("key"), Some(&"\"value\"".to_string()));
500 }
501
502 #[test]
503 fn test_record_timestamp() {
504 let record = Record::new(
505 LogLevel::Info,
506 "Test message",
507 Some("test_module".to_string()),
508 Some("test.rs".to_string()),
509 Some(42),
510 );
511 let now = chrono::Utc::now();
512 assert!(record.timestamp() <= now);
513 }
514
515 #[test]
516 fn test_record_context() {
517 let record = Record::new(LogLevel::Info, "test message", None, None, None).with_context(
518 "user",
519 json!({
520 "id": 123,
521 "name": "test"
522 }),
523 );
524
525 let user = record.get_context("user").unwrap();
526 assert_eq!(user["id"], 123);
527 assert_eq!(user["name"], "test");
528 }
529
530 #[test]
531 fn test_record_deferred_format() {
532 let record = Record::new(LogLevel::Info, "test message", None, None, None)
533 .with_deferred_format(|r| {
534 format!("[{}] {} - {}", r.timestamp(), r.level(), r.message())
535 });
536
537 let display = format!("{}", record);
538 assert!(display.contains("INFO"));
539 assert!(display.contains("test message"));
540 }
541
542 #[test]
543 fn test_record_serialization() {
544 let record = Record::new(
545 LogLevel::Info,
546 "test message",
547 Some("test_module".to_string()),
548 Some("test.rs".to_string()),
549 Some(42),
550 );
551
552 let serialized = serde_json::to_string(&record).unwrap();
553 let deserialized: Record = serde_json::from_str(&serialized).unwrap();
554
555 assert_eq!(deserialized.level(), record.level());
556 assert_eq!(deserialized.message(), record.message());
557 assert_eq!(deserialized.module(), record.module());
558 assert_eq!(deserialized.file(), record.file());
559 assert_eq!(deserialized.line(), record.line());
560 }
561
562 #[test]
563 fn test_record_state_checks() {
564 let record = Record::new(LogLevel::Info, "test message", None, None, None);
565 assert!(!record.has_context());
566 assert!(!record.has_metadata());
567 assert!(!record.has_formatter());
568 assert_eq!(record.context_len(), 0);
569 assert_eq!(record.metadata_len(), 0);
570
571 let record = record
572 .with_metadata("key", "value")
573 .with_context("user", json!({"id": 1}))
574 .with_deferred_format(|r| format!("{}", r.message()));
575
576 assert!(record.has_context());
577 assert!(record.has_metadata());
578 assert!(record.has_formatter());
579 assert_eq!(record.context_len(), 1);
580 assert_eq!(record.metadata_len(), 1);
581 }
582}