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