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