shuttle_common/models/
log.rs1use chrono::{DateTime, Utc};
2#[cfg(feature = "display")]
3use crossterm::style::Stylize;
4use serde::{Deserialize, Serialize};
5
6#[derive(Clone, Debug, Deserialize, Serialize)]
7#[typeshare::typeshare]
8pub struct LogItem {
9 pub timestamp: DateTime<Utc>,
10 pub source: String,
12 pub line: String,
13}
14
15impl LogItem {
16 pub fn new(timestamp: DateTime<Utc>, source: String, line: String) -> Self {
17 Self {
18 timestamp,
19 source,
20 line,
21 }
22 }
23}
24
25#[cfg(feature = "display")]
26impl std::fmt::Display for LogItem {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 let datetime: chrono::DateTime<chrono::Local> = DateTime::from(self.timestamp);
29
30 write!(
31 f,
32 "{} [{}] {}",
33 datetime
34 .to_rfc3339_opts(chrono::SecondsFormat::Millis, false)
35 .dim(),
36 self.source,
37 self.line,
38 )
39 }
40}
41
42#[derive(Debug, Serialize, Deserialize)]
43#[typeshare::typeshare]
44pub struct LogsResponse {
45 pub logs: Vec<LogItem>,
46}
47
48#[cfg(test)]
49mod tests {
50 #[cfg_attr(not(feature = "display"), allow(unused_imports))]
51 use super::*;
52
53 #[cfg(feature = "display")]
56 fn with_tz<F: FnOnce()>(tz: &str, f: F) {
57 let prev_tz = std::env::var("TZ").unwrap_or_default();
58 std::env::set_var("TZ", tz);
59 f();
60 std::env::set_var("TZ", prev_tz);
61 }
62
63 #[cfg(feature = "display")]
64 #[rstest::rstest]
65 #[case::utc("utc")]
66 #[case::cest("cest")]
67 fn test_timezone_formatting(#[case] tz: &str) {
68 let item = LogItem::new(
69 Utc::now(),
70 "test".to_string(),
71 r#"{"message": "Building"}"#.to_owned(),
72 );
73
74 with_tz(tz, || {
75 let value = item
76 .timestamp
77 .with_timezone(&chrono::Local)
78 .to_rfc3339_opts(chrono::SecondsFormat::Millis, false);
79
80 let log_line = format!("{}", &item);
81
82 assert!(log_line.contains(&value));
83 });
84 }
85}