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.
Diff
src/server.rs | 5 +-
src/settings/mod.rs | 517 +++++++++++++++++++++++++++--------------------------
src/winservice.rs | 6 +-
3 files changed, 271 insertions(+), 257 deletions(-)
@@ -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};
@@ -58,9 +58,6 @@ impl Server {
pub fn run_standalone(self) -> Result {
logger::init(&self.opts.general.log_level)?;
self.run_server_on_rt(None, || {})
}
@@ -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};
pub struct Headers {
@@ -71,7 +72,9 @@ pub struct Settings {
}
impl Settings {
pub fn get() -> Result<Settings> {
let opts = General::parse();
@@ -143,274 +146,269 @@ impl Settings {
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);
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);
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
}
#[cfg(windows)]
if let Some(v) = general.windows_service {
windows_service = v
}
#[cfg(windows)]
if let Some(v) = general.windows_service {
windows_service = v
}
}
logger::init(log_level.as_str())?;
if let Some(advanced) = settings.advanced {
let headers_entries = match advanced.headers {
Some(headers_entries) => {
let mut headers_vec: Vec<Headers> = Vec::new();
if let Some(advanced) = settings.advanced {
let headers_entries = match advanced.headers {
Some(headers_entries) => {
let mut headers_vec: Vec<Headers> = Vec::new();
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)
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,
};
let rewrites_entries = match advanced.rewrites {
Some(rewrites_entries) => {
let mut rewrites_vec: Vec<Rewrites> = Vec::new();
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,
};
let rewrites_entries = match advanced.rewrites {
Some(rewrites_entries) => {
let mut rewrites_vec: Vec<Rewrites> = Vec::new();
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,
};
let redirects_entries = match advanced.redirects {
Some(redirects_entries) => {
let mut redirects_vec: Vec<Redirects> = Vec::new();
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,
};
let redirects_entries = match advanced.redirects {
Some(redirects_entries) => {
let mut redirects_vec: Vec<Redirects> = Vec::new();
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 {
logger::init(log_level.as_str())?;
}
Ok(Settings {
@@ -474,3 +472,24 @@ impl Settings {
})
}
}
fn get_file_settings(file_path_opt: Option<PathBuf>) -> Result<Option<(FileSettings, PathBuf)>> {
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)
}
@@ -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");