Skip to main content

http_request_derive_logging_har/
logger.rs

1// SPDX-FileCopyrightText: OpenTalk GmbH <mail@opentalk.eu>
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5use 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/// A logger for storing har files
16#[derive(Debug)]
17pub struct HarLogger {
18    har: har::v1_3::Log,
19}
20
21impl HarLogger {
22    /// Create a new [`HarLogger`].
23    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    /// Write the collected har data into a file.
57    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    /// Log a HTTP request.
81    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}