Skip to main content

qubit_http/options/
http_logging_options.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9
10use qubit_config::{ConfigReader, ConfigResult};
11
12use super::HttpConfigError;
13use crate::constants::DEFAULT_LOG_BODY_SIZE_LIMIT_BYTES;
14
15/// Controls TRACE-level HTTP request/response logging in [`crate::HttpLogger`].
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct HttpLoggingOptions {
18    /// Whether logging is enabled.
19    pub enabled: bool,
20    /// Whether request headers are logged.
21    pub log_request_header: bool,
22    /// Whether request body is logged.
23    pub log_request_body: bool,
24    /// Whether response headers are logged.
25    pub log_response_header: bool,
26    /// Whether response body is logged.
27    pub log_response_body: bool,
28    /// Maximum body bytes to print in logs.
29    pub body_size_limit: usize,
30}
31
32impl Default for HttpLoggingOptions {
33    /// Enables logging of headers and bodies; body preview size is
34    /// [`crate::constants::DEFAULT_LOG_BODY_SIZE_LIMIT_BYTES`].
35    ///
36    /// # Returns
37    /// Default [`HttpLoggingOptions`].
38    fn default() -> Self {
39        Self {
40            enabled: true,
41            log_request_header: true,
42            log_request_body: true,
43            log_response_header: true,
44            log_response_body: true,
45            body_size_limit: DEFAULT_LOG_BODY_SIZE_LIMIT_BYTES,
46        }
47    }
48}
49
50/// Raw optional fields read from config before merging into [`HttpLoggingOptions`] defaults.
51struct LoggingConfigInput {
52    enabled: Option<bool>,
53    log_request_header: Option<bool>,
54    log_request_body: Option<bool>,
55    log_response_header: Option<bool>,
56    log_response_body: Option<bool>,
57    body_size_limit: Option<usize>,
58}
59
60fn read_logging_config<R>(config: &R) -> ConfigResult<LoggingConfigInput>
61where
62    R: ConfigReader + ?Sized,
63{
64    Ok(LoggingConfigInput {
65        enabled: config.get_optional("enabled")?,
66        log_request_header: config.get_optional("log_request_header")?,
67        log_request_body: config.get_optional("log_request_body")?,
68        log_response_header: config.get_optional("log_response_header")?,
69        log_response_body: config.get_optional("log_response_body")?,
70        body_size_limit: config.get_optional("body_size_limit")?,
71    })
72}
73
74impl HttpLoggingOptions {
75    /// Reads logging settings from `config` using **relative** keys.
76    ///
77    /// # Parameters
78    /// - `config`: Any [`ConfigReader`] (e.g. `config.prefix_view("logging")`).
79    ///
80    /// Keys read (all optional; missing keys keep their defaults):
81    /// - `enabled`
82    /// - `log_request_header`
83    /// - `log_request_body`
84    /// - `log_response_header`
85    /// - `log_response_body`
86    /// - `body_size_limit`
87    ///
88    /// # Returns
89    /// Populated [`HttpLoggingOptions`] or [`HttpConfigError`].
90    pub fn from_config<R>(config: &R) -> Result<Self, HttpConfigError>
91    where
92        R: ConfigReader + ?Sized,
93    {
94        let raw = read_logging_config(config).map_err(HttpConfigError::from)?;
95
96        let mut opts = HttpLoggingOptions::default();
97        if let Some(v) = raw.enabled {
98            opts.enabled = v;
99        }
100        if let Some(v) = raw.log_request_header {
101            opts.log_request_header = v;
102        }
103        if let Some(v) = raw.log_request_body {
104            opts.log_request_body = v;
105        }
106        if let Some(v) = raw.log_response_header {
107            opts.log_response_header = v;
108        }
109        if let Some(v) = raw.log_response_body {
110            opts.log_response_body = v;
111        }
112        if let Some(v) = raw.body_size_limit {
113            opts.body_size_limit = v;
114        }
115
116        Ok(opts)
117    }
118
119    /// Ensures `body_size_limit` is non-zero when any body logging flag is enabled.
120    ///
121    /// # Returns
122    /// `Ok(())` or [`HttpConfigError::invalid_value`] for `logging.body_size_limit`.
123    pub fn validate(&self) -> Result<(), HttpConfigError> {
124        if (self.log_request_body || self.log_response_body) && self.body_size_limit == 0 {
125            return Err(HttpConfigError::invalid_value(
126                "logging.body_size_limit",
127                "body_size_limit must be greater than 0 when body logging is enabled",
128            ));
129        }
130        Ok(())
131    }
132}