Skip to main content

better_fetch/plugins/
logger.rs

1use async_trait::async_trait;
2use tracing::{error, info, info_span, warn};
3
4use crate::hooks::{ErrorContext, Hooks};
5use crate::plugin::Plugin;
6
7/// Tracing-based logger plugin (request, response, retry, error).
8#[derive(Debug, Clone)]
9pub struct LoggerPlugin {
10    /// When `false`, hooks are registered but do not log.
11    pub enabled: bool,
12    /// When `true`, includes method and URL on each line.
13    pub verbose: bool,
14}
15
16impl LoggerPlugin {
17    /// Creates a plugin with logging enabled.
18    pub fn new() -> Self {
19        Self {
20            enabled: true,
21            verbose: false,
22        }
23    }
24
25    /// Enables or disables log output.
26    pub fn enabled(mut self, enabled: bool) -> Self {
27        self.enabled = enabled;
28        self
29    }
30
31    /// Enables verbose log fields.
32    pub fn verbose(mut self, verbose: bool) -> Self {
33        self.verbose = verbose;
34        self
35    }
36}
37
38impl Default for LoggerPlugin {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44#[async_trait]
45impl Plugin for LoggerPlugin {
46    /// Plugin id: `"logger"`.
47    fn id(&self) -> &'static str {
48        "logger"
49    }
50
51    fn hooks(&self) -> Hooks {
52        let enabled = self.enabled;
53        let verbose = self.verbose;
54
55        Hooks::new()
56            .on_request(move |ctx| {
57                let enabled = enabled;
58                let verbose = verbose;
59                async move {
60                    if enabled {
61                        let span = info_span!(
62                            "http.request",
63                            method = %ctx.method,
64                            url = %ctx.url,
65                            retry_attempt = ctx.retry_attempt,
66                        );
67                        let _guard = span.enter();
68                        if verbose {
69                            info!(header_count = ctx.headers.len(), "better-fetch request");
70                        } else {
71                            info!("better-fetch request");
72                        }
73                    }
74                    Ok(ctx)
75                }
76            })
77            .on_response_stream({
78                let enabled = self.enabled;
79                let verbose = self.verbose;
80                move |ctx| {
81                    let enabled = enabled;
82                    let verbose = verbose;
83                    async move {
84                        if enabled {
85                            let span = info_span!(
86                                "http.response",
87                                status = %ctx.status,
88                                url = %ctx.request.url,
89                                streaming = true,
90                            );
91                            let _guard = span.enter();
92                            if verbose {
93                                info!(
94                                    header_count = ctx.headers.len(),
95                                    "better-fetch stream response"
96                                );
97                            } else {
98                                info!("better-fetch stream response");
99                            }
100                        }
101                        Ok(crate::hooks::StreamingResponseMeta {
102                            status: ctx.status,
103                            headers: ctx.headers,
104                        })
105                    }
106                }
107            })
108            .on_response({
109                let enabled = self.enabled;
110                let verbose = self.verbose;
111                move |ctx| {
112                    let enabled = enabled;
113                    let verbose = verbose;
114                    async move {
115                        if enabled {
116                            let status = ctx.response.status();
117                            let span = info_span!(
118                                "http.response",
119                                status = %status,
120                                url = %ctx.request.url,
121                                streaming = false,
122                            );
123                            let _guard = span.enter();
124                            if verbose {
125                                info!(
126                                    header_count = ctx.response.headers().len(),
127                                    "better-fetch response"
128                                );
129                            } else {
130                                info!("better-fetch response");
131                            }
132                        }
133                        Ok(ctx.response)
134                    }
135                }
136            })
137            .on_retry({
138                let enabled = self.enabled;
139                move |ctx| {
140                    let enabled = enabled;
141                    async move {
142                        if enabled {
143                            warn!(
144                                retry_attempt = ctx.request.retry_attempt,
145                                next_attempt = ctx.request.retry_attempt + 1,
146                                status = %ctx.response.status(),
147                                url = %ctx.request.url,
148                                "better-fetch retry"
149                            );
150                        }
151                    }
152                }
153            })
154            .on_error({
155                let enabled = self.enabled;
156                move |ctx: ErrorContext| {
157                    let enabled = enabled;
158                    async move {
159                        if enabled {
160                            let status = ctx.response.as_ref().map(|r| r.status().as_u16());
161                            let body_preview = ctx.response_body_preview(256);
162                            error!(
163                                error = %ctx.error,
164                                url = %ctx.request.url,
165                                ?status,
166                                body_preview = body_preview.as_deref(),
167                                retry_attempt = ctx.request.retry_attempt,
168                                "better-fetch error"
169                            );
170                        }
171                    }
172                }
173            })
174    }
175}