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