diff --git a/src/config/commandline.rs b/src/config/commandline.rs index 8d8f565..f5f47b1 100644 --- a/src/config/commandline.rs +++ b/src/config/commandline.rs @@ -18,6 +18,7 @@ impl Args { log_format, log_targets, log_spans, + log_requests, no_log_ansi, console_address, console_buffer_capacity, @@ -40,6 +41,7 @@ impl Args { targets: log_targets.map(Serde::new), log_spans, no_ansi: no_log_ansi, + log_requests, }, console: Console { address: console_address, @@ -584,6 +586,8 @@ struct Logging { #[serde(skip_serializing_if = "std::ops::Not::not")] log_spans: bool, #[serde(skip_serializing_if = "std::ops::Not::not")] + log_requests: bool, + #[serde(skip_serializing_if = "std::ops::Not::not")] no_ansi: bool, } @@ -928,6 +932,9 @@ pub(super) struct Args { /// Whether to log openning and closing of tracing spans to stdout #[arg(long)] log_spans: bool, + /// Whether to log request completions at an INFO level + #[arg(long)] + log_requests: bool, #[arg(long)] /// Whether to disable color-codes in log output diff --git a/src/config/defaults.rs b/src/config/defaults.rs index 2029512..020b9e9 100644 --- a/src/config/defaults.rs +++ b/src/config/defaults.rs @@ -55,6 +55,7 @@ struct LoggingDefaults { format: LogFormat, targets: Serde, log_spans: bool, + log_requests: bool, no_ansi: bool, } @@ -236,6 +237,7 @@ impl Default for LoggingDefaults { format: LogFormat::Normal, targets: "info".parse().expect("Valid targets string"), log_spans: false, + log_requests: false, no_ansi: false, } } diff --git a/src/config/file.rs b/src/config/file.rs index 1b809e0..87d0809 100644 --- a/src/config/file.rs +++ b/src/config/file.rs @@ -165,6 +165,8 @@ pub(crate) struct Logging { pub(crate) log_spans: bool, pub(crate) no_ansi: bool, + + pub(crate) log_requests: bool, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] diff --git a/src/lib.rs b/src/lib.rs index dbf54fe..d2bb644 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1741,7 +1741,7 @@ async fn launch< spawn_workers(state.clone()); App::new() - .wrap(Log) + .wrap(Log::new(state.config.tracing.logging.log_requests)) .wrap(TracingLogger::default()) .wrap(Deadline) .wrap(Metrics) diff --git a/src/middleware/log.rs b/src/middleware/log.rs index bfd4b92..905916c 100644 --- a/src/middleware/log.rs +++ b/src/middleware/log.rs @@ -7,16 +7,30 @@ use actix_web::{ ResponseError, }; -pub(crate) struct Log; +pub(crate) struct Log { + info: bool, +} pub(crate) struct LogMiddleware { + info: bool, inner: S, } +impl Log { + pub(crate) fn new(info: bool) -> Self { + Self { info } + } +} + #[derive(Debug)] -pub(crate) struct LogError(actix_web::Error); +pub(crate) struct LogError { + info: bool, + error: actix_web::Error, +} pin_project_lite::pin_project! { pub(crate) struct LogFuture { + info: bool, + #[pin] inner: F, } @@ -24,6 +38,8 @@ pin_project_lite::pin_project! { pin_project_lite::pin_project! { pub(crate) struct LogBody { + info: bool, + status: Option, #[pin] @@ -45,7 +61,10 @@ where type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { - ready(Ok(LogMiddleware { inner: service })) + ready(Ok(LogMiddleware { + info: self.info, + inner: service, + })) } } @@ -64,13 +83,20 @@ where &self, ctx: &mut core::task::Context<'_>, ) -> std::task::Poll> { - self.inner - .poll_ready(ctx) - .map(|res| res.map_err(|e| LogError(e.into()).into())) + self.inner.poll_ready(ctx).map(|res| { + res.map_err(|e| { + LogError { + info: self.info, + error: e.into(), + } + .into() + }) + }) } fn call(&self, req: ServiceRequest) -> Self::Future { LogFuture { + info: self.info, inner: self.inner.call(req), } } @@ -88,6 +114,7 @@ where self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>, ) -> std::task::Poll { + let info = self.info; let this = self.project(); std::task::Poll::Ready(match std::task::ready!(this.inner.poll(cx)) { @@ -95,15 +122,23 @@ where let status = response.status(); let status = if response.response().body().size().is_eof() { - emit(status); + emit(status, info); None } else { Some(status) }; - Ok(response.map_body(|_, inner| LogBody { status, inner })) + Ok(response.map_body(|_, inner| LogBody { + info, + status, + inner, + })) } - Err(e) => Err(LogError(e.into()).into()), + Err(e) => Err(LogError { + info, + error: e.into(), + } + .into()), }) } } @@ -128,7 +163,7 @@ where if opt.is_none() { if let Some(status) = this.status.take() { - emit(status); + emit(status, *this.info); } } @@ -138,31 +173,32 @@ where impl std::fmt::Display for LogError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) + self.error.fmt(f) } } impl std::error::Error for LogError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - self.0.source() + self.error.source() } } impl ResponseError for LogError { fn status_code(&self) -> actix_web::http::StatusCode { - self.0.as_response_error().status_code() + self.error.as_response_error().status_code() } fn error_response(&self) -> actix_web::HttpResponse { - let response = self.0.error_response(); + let response = self.error.error_response(); let status = response.status(); if response.body().size().is_eof() { - emit(status); + emit(status, self.info); response } else { response.map_body(|_, inner| { LogBody { + info: self.info, status: Some(status), inner, } @@ -172,14 +208,16 @@ impl ResponseError for LogError { } } -fn emit(status: StatusCode) { +fn emit(status: StatusCode, info: bool) { if status.is_server_error() { tracing::error!("server error"); } else if status.is_client_error() { tracing::warn!("client error"); } else if status.is_redirection() { tracing::info!("redirected"); - } else { + } else if info { tracing::info!("completed"); + } else { + tracing::debug!("completed"); } }