http_request_derive_logging_har/
logger.rs1use std::{fs::create_dir_all, path::Path};
6
7use bytes::Bytes;
8use http_request_derive_logging::HttpLoggerBackend;
9use jiff::{Zoned, fmt::strtime};
10use log::info;
11use snafu::{ResultExt as _, Snafu};
12
13use crate::{request::http_request_to_har_request, response::http_response_to_har_response};
14
15#[derive(Debug)]
17pub struct HarLogger {
18 har: har::v1_3::Log,
19}
20
21impl HarLogger {
22 pub fn new(name: String, version: String) -> Self {
24 Self {
25 har: har::v1_3::Log {
26 creator: har::v1_3::Creator {
27 name,
28 version,
29 comment: None,
30 },
31 pages: Some(Vec::new()),
32 ..Default::default()
33 },
34 }
35 }
36}
37
38#[derive(Debug, Snafu)]
39pub enum HarLoggerDumpError {
40 #[snafu(display("Directory {path:?} could not be created"))]
41 CreateDirectory {
42 path: std::path::PathBuf,
43 source: std::io::Error,
44 },
45 HarExport {
46 source: har::Error,
47 },
48 #[snafu(display("File {path:?} could not be written"))]
49 WriteFile {
50 path: std::path::PathBuf,
51 source: std::io::Error,
52 },
53}
54
55impl HarLogger {
56 pub fn write_to_file(&self, path: &Path) -> Result<(), HarLoggerDumpError> {
58 info!("Dumping har file to {path:?}");
59
60 if let Some(parent) = path.parent() {
61 create_dir_all(parent).context(CreateDirectorySnafu {
62 path: parent.to_path_buf(),
63 })?;
64 }
65
66 let har = har::Har {
67 log: har::Spec::V1_3(self.har.clone()),
68 };
69
70 let dump = har::to_json(&har).context(HarExportSnafu)?;
71 std::fs::write(path, dump).context(WriteFileSnafu {
72 path: path.to_path_buf(),
73 })?;
74 Ok(())
75 }
76}
77
78#[async_trait::async_trait(?Send)]
79impl HttpLoggerBackend for HarLogger {
80 async fn log_request(
82 &mut self,
83 start_time: std::time::SystemTime,
84 request: &http::Request<Vec<u8>>,
85 response: Option<&http::Response<Bytes>>,
86 ) {
87 let start = Zoned::try_from(start_time).expect("valid start time expected");
88 let duration = start.duration_until(&Zoned::now());
89
90 let har_request = http_request_to_har_request(request);
91 let har_response = response
92 .map(http_response_to_har_response)
93 .unwrap_or_default();
94
95 self.har.entries.push(har::v1_3::Entries {
96 pageref: None,
97 started_date_time: strtime::format("%Y-%m-%dT%H:%M:%S%.f%:z", &start)
98 .expect("valid time format required"),
99 time: duration.as_millis_f64(),
100 request: har_request,
101 response: har_response,
102 cache: har::v1_3::Cache::default(),
103 timings: har::v1_3::Timings::default(),
104 server_ip_address: None,
105 connection: None,
106 comment: None,
107 });
108 }
109}