1use std::time::Duration;
2
3use bytes::Bytes;
4
5use super::{CURRENT, Ctx, ms};
6
7#[derive(Debug)]
8#[non_exhaustive]
9pub enum BodyOutcome<'a> {
10 Complete,
11 Failed(&'a std::io::Error),
12 Dropped,
13}
14
15#[derive(Debug)]
16#[non_exhaustive]
17pub struct BodyEnd<'a> {
18 pub id: u64,
19 pub bytes: u64,
20 pub elapsed: Duration,
21 pub outcome: BodyOutcome<'a>,
22}
23
24pub(crate) struct BodyWatch {
25 ctx: Ctx,
26 bytes: u64,
27 ended: bool,
28}
29
30impl BodyWatch {
31 pub(crate) fn begin() -> Option<Self> {
32 CURRENT
33 .try_with(|ctx| Self {
34 ctx: ctx.clone(),
35 bytes: 0,
36 ended: false,
37 })
38 .ok()
39 }
40
41 pub(crate) fn observe(&mut self, item: Option<&std::io::Result<Bytes>>) {
42 match item {
43 Some(Ok(chunk)) => self.bytes += chunk.len() as u64,
44 Some(Err(err)) => self.end(BodyOutcome::Failed(err)),
45 None => self.end(BodyOutcome::Complete),
46 }
47 }
48
49 fn end(&mut self, outcome: BodyOutcome<'_>) {
50 if self.ended {
51 return;
52 }
53 self.ended = true;
54 let (id, start) = self.ctx.root;
55 self.ctx.hook.body(&BodyEnd {
56 id,
57 bytes: self.bytes,
58 elapsed: start.elapsed(),
59 outcome,
60 });
61 }
62}
63
64impl Drop for BodyWatch {
65 fn drop(&mut self) {
66 if !std::thread::panicking() {
67 self.end(BodyOutcome::Dropped);
68 }
69 }
70}
71
72pub(super) fn render(ev: &BodyEnd<'_>) {
73 let elapsed_ms = ms(ev.elapsed);
74 match ev.outcome {
75 BodyOutcome::Complete => {
76 tracing::info!(target: "leyline::trace", id = ev.id, bytes = ev.bytes, elapsed_ms, outcome = "complete", "body")
77 }
78 BodyOutcome::Failed(err) => {
79 tracing::info!(target: "leyline::trace", id = ev.id, bytes = ev.bytes, elapsed_ms, outcome = "error", kind = ?err.kind(), error = %err, "body")
80 }
81 BodyOutcome::Dropped => {
82 tracing::info!(target: "leyline::trace", id = ev.id, bytes = ev.bytes, elapsed_ms, outcome = "dropped", "body")
83 }
84 }
85}