Skip to main content

qubit_http/options/
http_logging_options.rs

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