1use std::fmt;
2use std::time::Duration;
3
4use http::StatusCode;
5use url::Url;
6
7use super::ms;
8use crate::profile::Browser;
9use crate::{Error, HttpVersion};
10
11#[non_exhaustive]
12pub struct Summary<'a> {
13 pub id: u64,
14 pub method: &'a str,
15 pub url: Option<&'a Url>,
16 pub original_url: &'a str,
17 pub redirects: usize,
18 pub status: Option<StatusCode>,
19 pub version: Option<HttpVersion>,
20 pub attempts: u32,
21 pub elapsed: Duration,
22 pub outcome: Result<(), &'a Error>,
23 pub streamed: bool,
24 pub tag: Option<&'a str>,
25 pub proxy: Option<String>,
26 pub browser: Option<Browser>,
27}
28
29impl fmt::Debug for Summary<'_> {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 f.debug_struct("Summary")
32 .field("id", &self.id)
33 .field("method", &self.method)
34 .field("url", &self.url.map(|u| crate::util::redact(u.as_str())))
35 .field("original_url", &crate::util::redact(self.original_url))
36 .field("redirects", &self.redirects)
37 .field("status", &self.status)
38 .field("version", &self.version)
39 .field("attempts", &self.attempts)
40 .field("elapsed", &self.elapsed)
41 .field("outcome", &self.outcome)
42 .field("streamed", &self.streamed)
43 .field("tag", &self.tag)
44 .field("proxy", &self.proxy)
45 .field("browser", &self.browser)
46 .finish()
47 }
48}
49
50pub(crate) struct Finish<'a> {
51 pub(crate) method: &'a str,
52 pub(crate) original_url: &'a str,
53 pub(crate) attempts: u32,
54 pub(crate) streamed: bool,
55}
56
57pub(crate) fn summary(finish: &Finish<'_>, out: Result<&crate::Response, &Error>) {
58 super::with(|ctx| {
59 let notes = super::notes::take(&ctx.notes);
60 let parsed;
61 let (url, redirects, status, version, outcome) = match out {
62 Ok(r) => (
63 Some(r.url()),
64 r.redirect_chain().len(),
65 Some(r.status()),
66 Some(r.version()),
67 Ok(()),
68 ),
69 Err(e) => {
70 parsed = Url::parse(finish.original_url).ok();
71 (parsed.as_ref(), 0, None, None, Err(e))
72 }
73 };
74 ctx.hook.summary(&Summary {
75 id: ctx.id,
76 method: finish.method,
77 url,
78 original_url: finish.original_url,
79 redirects,
80 status,
81 version,
82 attempts: finish.attempts,
83 elapsed: ctx.start.elapsed(),
84 outcome,
85 streamed: finish.streamed,
86 tag: notes.tag.as_deref(),
87 proxy: notes.proxy,
88 browser: notes.browser,
89 });
90 });
91}
92
93pub(super) fn render(ev: &Summary<'_>) {
94 let url = crate::util::redact(ev.url.map_or(ev.original_url, Url::as_str));
95 let status = ev.status.map(|s| s.as_u16());
96 let version = ev.version.map(tracing::field::debug);
97 let elapsed_ms = ms(ev.elapsed);
98 let tag = ev.tag;
99 let proxy = ev.proxy.as_deref();
100 let browser = ev.browser.map(tracing::field::debug);
101 match ev.outcome {
102 Ok(()) => {
103 tracing::info!(target: "leyline::trace", id = ev.id, method = ev.method, url = %url, redirects = ev.redirects, status, version, attempts = ev.attempts, elapsed_ms, streamed = ev.streamed, tag, proxy, browser, outcome = "ok", "request")
104 }
105 Err(e) => {
106 tracing::info!(target: "leyline::trace", id = ev.id, method = ev.method, url = %url, redirects = ev.redirects, status, version, attempts = ev.attempts, elapsed_ms, streamed = ev.streamed, tag, proxy, browser, outcome = "error", kind = ?e.kind(), error = %e, "request")
107 }
108 }
109}