From 949c539f50e8a82dcec395cd7deb59b6b76c1d50 Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Wed, 19 Jul 2023 22:58:14 +0200 Subject: [PATCH] refactor: logging system initialization at config level the logging system is now initialized once the `general` settings are determined cli/env & config-file based. this means that we can display more tracing entries that are useful for example when debugging advanced features usage (config-file based) like URL rewrites/redirects, etc. --- src/server.rs | 5 +---- src/settings/mod.rs | 517 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- src/winservice.rs | 6 ++---- 3 files changed, 271 insertions(+), 257 deletions(-) diff --git a/src/server.rs b/src/server.rs index bcc8cbd..855509f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -24,7 +24,7 @@ use { hyper::service::{make_service_fn, service_fn}, }; -use crate::{cors, helpers, logger, Settings}; +use crate::{cors, helpers, Settings}; use crate::{service::RouterService, Context, Result}; /// Define a multi-thread HTTP or HTTP/2 web server. @@ -58,9 +58,6 @@ impl Server { /// Run the multi-thread `Server` as standalone. /// It is a top-level function of [run_server_on_rt](#method.run_server_on_rt). pub fn run_standalone(self) -> Result { - // Logging system initialization - logger::init(&self.opts.general.log_level)?; - self.run_server_on_rt(None, || {}) } diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 71ab5ea..8719f54 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -11,8 +11,9 @@ use globset::{Glob, GlobMatcher}; use headers::HeaderMap; use hyper::StatusCode; use regex::Regex; +use std::path::PathBuf; -use crate::{Context, Result}; +use crate::{logger, Context, Result}; pub mod cli; pub mod file; @@ -22,7 +23,7 @@ pub use cli::Commands; use cli::General; -use self::file::RedirectsKind; +use self::file::{RedirectsKind, Settings as FileSettings}; /// The `headers` file options. pub struct Headers { @@ -71,7 +72,9 @@ pub struct Settings { } impl Settings { - /// Handles CLI and config file options and converging them into one. + /// Reads CLI/Env and config file options returning the server settings. + /// It also takes care to initialize the logging system with its level + /// once the `general` settings are determined. pub fn get() -> Result { let opts = General::parse(); @@ -143,274 +146,269 @@ impl Settings { // Handle "config file options" and set them when available // NOTE: All config file based options shouldn't be mandatory, therefore `Some()` wrapped - if let Some(ref p) = opts.config_file { - if p.is_file() { - let path_resolved = p - .canonicalize() - .with_context(|| "error resolving toml config file path")?; - - let settings = file::Settings::read(&path_resolved) - .with_context(|| { - "can not read toml config file because has invalid or unsupported format/options" - })?; - - config_file = Some(path_resolved); - - // File-based "general" options - if let Some(general) = settings.general { - if let Some(v) = general.host { - host = v - } - if let Some(v) = general.port { - port = v - } - if let Some(v) = general.root { - root = v - } - if let Some(ref v) = general.log_level { - log_level = v.name().to_lowercase(); - } - if let Some(v) = general.cache_control_headers { - cache_control_headers = v - } - #[cfg(feature = "compression")] - if let Some(v) = general.compression { - compression = v - } - #[cfg(feature = "compression")] - if let Some(v) = general.compression_static { - compression_static = v - } - if let Some(v) = general.page404 { - page404 = v - } - if let Some(v) = general.page50x { - page50x = v - } - #[cfg(feature = "http2")] - if let Some(v) = general.http2 { - http2 = v - } - #[cfg(feature = "http2")] - if let Some(v) = general.http2_tls_cert { - http2_tls_cert = Some(v) - } - #[cfg(feature = "http2")] - if let Some(v) = general.http2_tls_key { - http2_tls_key = Some(v) - } - #[cfg(feature = "http2")] - if let Some(v) = general.https_redirect { - https_redirect = v - } - #[cfg(feature = "http2")] - if let Some(v) = general.https_redirect_host { - https_redirect_host = v - } - #[cfg(feature = "http2")] - if let Some(v) = general.https_redirect_from_port { - https_redirect_from_port = v - } - #[cfg(feature = "http2")] - if let Some(v) = general.https_redirect_from_hosts { - https_redirect_from_hosts = v - } - #[cfg(feature = "http2")] - match general.security_headers { - Some(v) => security_headers = v, - _ => { - if http2 { - security_headers = true; - } + if let Some((settings, path_resolved)) = get_file_settings(opts.config_file)? { + config_file = Some(path_resolved); + + // File-based "general" options + if let Some(general) = settings.general { + if let Some(v) = general.host { + host = v + } + if let Some(v) = general.port { + port = v + } + if let Some(v) = general.root { + root = v + } + if let Some(ref v) = general.log_level { + log_level = v.name().to_lowercase(); + } + if let Some(v) = general.cache_control_headers { + cache_control_headers = v + } + #[cfg(feature = "compression")] + if let Some(v) = general.compression { + compression = v + } + #[cfg(feature = "compression")] + if let Some(v) = general.compression_static { + compression_static = v + } + if let Some(v) = general.page404 { + page404 = v + } + if let Some(v) = general.page50x { + page50x = v + } + #[cfg(feature = "http2")] + if let Some(v) = general.http2 { + http2 = v + } + #[cfg(feature = "http2")] + if let Some(v) = general.http2_tls_cert { + http2_tls_cert = Some(v) + } + #[cfg(feature = "http2")] + if let Some(v) = general.http2_tls_key { + http2_tls_key = Some(v) + } + #[cfg(feature = "http2")] + if let Some(v) = general.https_redirect { + https_redirect = v + } + #[cfg(feature = "http2")] + if let Some(v) = general.https_redirect_host { + https_redirect_host = v + } + #[cfg(feature = "http2")] + if let Some(v) = general.https_redirect_from_port { + https_redirect_from_port = v + } + #[cfg(feature = "http2")] + if let Some(v) = general.https_redirect_from_hosts { + https_redirect_from_hosts = v + } + #[cfg(feature = "http2")] + match general.security_headers { + Some(v) => security_headers = v, + _ => { + if http2 { + security_headers = true; } } - #[cfg(not(feature = "http2"))] - if let Some(v) = general.security_headers { - security_headers = v - } - if let Some(ref v) = general.cors_allow_origins { - cors_allow_origins = v.to_owned() - } - if let Some(ref v) = general.cors_allow_headers { - cors_allow_headers = v.to_owned() - } - if let Some(ref v) = general.cors_expose_headers { - cors_expose_headers = v.to_owned() - } - #[cfg(feature = "directory-listing")] - if let Some(v) = general.directory_listing { - directory_listing = v - } - #[cfg(feature = "directory-listing")] - if let Some(v) = general.directory_listing_order { - directory_listing_order = v - } - #[cfg(feature = "directory-listing")] - if let Some(v) = general.directory_listing_format { - directory_listing_format = v - } - #[cfg(feature = "basic-auth")] - if let Some(ref v) = general.basic_auth { - basic_auth = v.to_owned() - } - if let Some(v) = general.fd { - fd = Some(v) - } - if let Some(v) = general.threads_multiplier { - threads_multiplier = v - } - if let Some(v) = general.max_blocking_threads { - max_blocking_threads = v - } - if let Some(v) = general.grace_period { - grace_period = v - } - #[cfg(feature = "fallback-page")] - if let Some(v) = general.page_fallback { - page_fallback = v - } - if let Some(v) = general.log_remote_address { - log_remote_address = v - } - if let Some(v) = general.redirect_trailing_slash { - redirect_trailing_slash = v - } - if let Some(v) = general.ignore_hidden_files { - ignore_hidden_files = v - } - if let Some(v) = general.health { - health = v - } + } + #[cfg(not(feature = "http2"))] + if let Some(v) = general.security_headers { + security_headers = v + } + if let Some(ref v) = general.cors_allow_origins { + cors_allow_origins = v.to_owned() + } + if let Some(ref v) = general.cors_allow_headers { + cors_allow_headers = v.to_owned() + } + if let Some(ref v) = general.cors_expose_headers { + cors_expose_headers = v.to_owned() + } + #[cfg(feature = "directory-listing")] + if let Some(v) = general.directory_listing { + directory_listing = v + } + #[cfg(feature = "directory-listing")] + if let Some(v) = general.directory_listing_order { + directory_listing_order = v + } + #[cfg(feature = "directory-listing")] + if let Some(v) = general.directory_listing_format { + directory_listing_format = v + } + #[cfg(feature = "basic-auth")] + if let Some(ref v) = general.basic_auth { + basic_auth = v.to_owned() + } + if let Some(v) = general.fd { + fd = Some(v) + } + if let Some(v) = general.threads_multiplier { + threads_multiplier = v + } + if let Some(v) = general.max_blocking_threads { + max_blocking_threads = v + } + if let Some(v) = general.grace_period { + grace_period = v + } + #[cfg(feature = "fallback-page")] + if let Some(v) = general.page_fallback { + page_fallback = v + } + if let Some(v) = general.log_remote_address { + log_remote_address = v + } + if let Some(v) = general.redirect_trailing_slash { + redirect_trailing_slash = v + } + if let Some(v) = general.ignore_hidden_files { + ignore_hidden_files = v + } + if let Some(v) = general.health { + health = v + } - // Windows-only options - #[cfg(windows)] - if let Some(v) = general.windows_service { - windows_service = v - } + // Windows-only options + #[cfg(windows)] + if let Some(v) = general.windows_service { + windows_service = v } + } + + // Logging system initialization + logger::init(log_level.as_str())?; + + // File-based "advanced" options + if let Some(advanced) = settings.advanced { + // 1. Custom HTTP headers assignment + let headers_entries = match advanced.headers { + Some(headers_entries) => { + let mut headers_vec: Vec = Vec::new(); - // File-based "advanced" options - if let Some(advanced) = settings.advanced { - // 1. Custom HTTP headers assignment - let headers_entries = match advanced.headers { - Some(headers_entries) => { - let mut headers_vec: Vec = Vec::new(); - - // Compile a glob pattern for each header sources entry - for headers_entry in headers_entries.iter() { - let source = Glob::new(&headers_entry.source) - .with_context(|| { - format!( - "can not compile glob pattern for header source: {}", - &headers_entry.source - ) - })? - .compile_matcher(); - - headers_vec.push(Headers { - source, - headers: headers_entry.headers.to_owned(), - }); - } - Some(headers_vec) + // Compile a glob pattern for each header sources entry + for headers_entry in headers_entries.iter() { + let source = Glob::new(&headers_entry.source) + .with_context(|| { + format!( + "can not compile glob pattern for header source: {}", + &headers_entry.source + ) + })? + .compile_matcher(); + + headers_vec.push(Headers { + source, + headers: headers_entry.headers.to_owned(), + }); } - _ => None, - }; - - // 2. Rewrites assignment - let rewrites_entries = match advanced.rewrites { - Some(rewrites_entries) => { - let mut rewrites_vec: Vec = Vec::new(); - - // Compile a glob pattern for each rewrite sources entry - for rewrites_entry in rewrites_entries.iter() { - let source = Glob::new(&rewrites_entry.source) - .with_context(|| { - format!( - "can not compile glob pattern for rewrite source: {}", - &rewrites_entry.source - ) - })? - .compile_matcher(); - - let pattern = - source.glob().regex().trim_start_matches("(?-u)").to_owned(); - tracing::debug!( - "url rewrites glob pattern: {}", - &rewrites_entry.source - ); - tracing::debug!("url rewrites regex equivalent: {}", pattern); - - let source = Regex::new(&pattern).with_context(|| { + Some(headers_vec) + } + _ => None, + }; + + // 2. Rewrites assignment + let rewrites_entries = match advanced.rewrites { + Some(rewrites_entries) => { + let mut rewrites_vec: Vec = Vec::new(); + + // Compile a glob pattern for each rewrite sources entry + for rewrites_entry in rewrites_entries.iter() { + let source = Glob::new(&rewrites_entry.source) + .with_context(|| { + format!( + "can not compile glob pattern for rewrite source: {}", + &rewrites_entry.source + ) + })? + .compile_matcher(); + + let pattern = + source.glob().regex().trim_start_matches("(?-u)").to_owned(); + tracing::debug!( + "url rewrites glob pattern: {}", + &rewrites_entry.source + ); + tracing::debug!("url rewrites regex equivalent: {}", pattern); + + let source = Regex::new(&pattern).with_context(|| { format!( "can not compile regex pattern equivalent for rewrite source: {}", &pattern ) })?; - rewrites_vec.push(Rewrites { - source, - destination: rewrites_entry.destination.to_owned(), - redirect: rewrites_entry.redirect.to_owned(), - }); - } - Some(rewrites_vec) + rewrites_vec.push(Rewrites { + source, + destination: rewrites_entry.destination.to_owned(), + redirect: rewrites_entry.redirect.to_owned(), + }); } - _ => None, - }; - - // 3. Redirects assignment - let redirects_entries = match advanced.redirects { - Some(redirects_entries) => { - let mut redirects_vec: Vec = Vec::new(); - - // Compile a glob pattern for each redirect sources entry - for redirects_entry in redirects_entries.iter() { - let source = Glob::new(&redirects_entry.source) - .with_context(|| { - format!( - "can not compile glob pattern for redirect source: {}", - &redirects_entry.source - ) - })? - .compile_matcher(); - - let pattern = - source.glob().regex().trim_start_matches("(?-u)").to_owned(); - tracing::debug!( - "url rewrites glob pattern: {}", - &redirects_entry.source - ); - tracing::debug!("url rewrites regex equivalent: {}", pattern); - - let source = Regex::new(&pattern).with_context(|| { + Some(rewrites_vec) + } + _ => None, + }; + + // 3. Redirects assignment + let redirects_entries = match advanced.redirects { + Some(redirects_entries) => { + let mut redirects_vec: Vec = Vec::new(); + + // Compile a glob pattern for each redirect sources entry + for redirects_entry in redirects_entries.iter() { + let source = Glob::new(&redirects_entry.source) + .with_context(|| { + format!( + "can not compile glob pattern for redirect source: {}", + &redirects_entry.source + ) + })? + .compile_matcher(); + + let pattern = + source.glob().regex().trim_start_matches("(?-u)").to_owned(); + tracing::debug!( + "url rewrites glob pattern: {}", + &redirects_entry.source + ); + tracing::debug!("url rewrites regex equivalent: {}", pattern); + + let source = Regex::new(&pattern).with_context(|| { format!( "can not compile regex pattern equivalent for rewrite source: {}", &pattern ) })?; - let status_code = redirects_entry.kind.to_owned() as u16; - redirects_vec.push(Redirects { - source, - destination: redirects_entry.destination.to_owned(), - kind: StatusCode::from_u16(status_code).with_context(|| { - format!("invalid redirect status code: {status_code}") - })?, - }); - } - Some(redirects_vec) + let status_code = redirects_entry.kind.to_owned() as u16; + redirects_vec.push(Redirects { + source, + destination: redirects_entry.destination.to_owned(), + kind: StatusCode::from_u16(status_code).with_context(|| { + format!("invalid redirect status code: {status_code}") + })?, + }); } - _ => None, - }; + Some(redirects_vec) + } + _ => None, + }; - settings_advanced = Some(Advanced { - headers: headers_entries, - rewrites: rewrites_entries, - redirects: redirects_entries, - }); - } + settings_advanced = Some(Advanced { + headers: headers_entries, + rewrites: rewrites_entries, + redirects: redirects_entries, + }); } + } else { + // Logging system initialization + logger::init(log_level.as_str())?; } Ok(Settings { @@ -474,3 +472,24 @@ impl Settings { }) } } + +fn get_file_settings(file_path_opt: Option) -> Result> { + if let Some(ref file_path) = file_path_opt { + if file_path.is_file() { + let file_path_resolved = file_path + .canonicalize() + .with_context(|| "error resolving toml config file path")?; + let settings = FileSettings::read(&file_path_resolved).with_context(|| { + "can not read toml config file because has invalid or unsupported format/options" + })?; + return Ok(Some((settings, file_path_resolved))); + } + tracing::warn!( + "config file `{}` is not a regular toml file, ignored!", + file_path.display() + ); + } else { + tracing::warn!("config file was not determined, ignored!"); + } + Ok(None) +} diff --git a/src/winservice.rs b/src/winservice.rs index ff7a81f..063ba0d 100644 --- a/src/winservice.rs +++ b/src/winservice.rs @@ -22,7 +22,7 @@ use windows_service::{ service_manager::{ServiceManager, ServiceManagerAccess}, }; -use crate::{helpers, logger, Context, Result, Server, Settings}; +use crate::{helpers, Context, Result, Server, Settings}; const SERVICE_NAME: &str = "static-web-server"; const SERVICE_TYPE: ServiceType = ServiceType::OWN_PROCESS; @@ -72,9 +72,7 @@ fn set_service_state( } fn run_service() -> Result { - let opts = Settings::get()?; - - logger::init(&opts.general.log_level)?; + let _ = Settings::get()?; tracing::info!("windows service: starting service setup"); -- libgit2 1.7.2