chore(app): exclude health/metrics/WS from access logger output
Skip terminal access logs for noisy K8s probe and monitoring endpoints: /health, /metrics, and /ws path prefix. Applied to both the main app and static file server.
This commit is contained in:
parent
61210da7a1
commit
b00d42ee8d
@ -1,10 +1,12 @@
|
|||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
use actix_web::cookie::time::Duration;
|
use actix_web::cookie::time::Duration;
|
||||||
|
use actix_web::dev::{Service, ServiceRequest, ServiceResponse};
|
||||||
use actix_web::middleware::Logger;
|
use actix_web::middleware::Logger;
|
||||||
use actix_web::{cookie::Key, web, App, HttpResponse, HttpServer};
|
use actix_web::{cookie::Key, web, App, HttpResponse, HttpServer};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use db::cache::AppCache;
|
use db::cache::AppCache;
|
||||||
use db::database::AppDatabase;
|
use db::database::AppDatabase;
|
||||||
|
use futures::future::LocalBoxFuture;
|
||||||
use observability::{
|
use observability::{
|
||||||
init_tracing_subscriber, install_recorder, prometheus_handler, spawn_http_metrics_poller,
|
init_tracing_subscriber, install_recorder, prometheus_handler, spawn_http_metrics_poller,
|
||||||
HttpMetrics, HttpSnapshotGuard, MetricsMiddleware, TracingSpanMiddleware,
|
HttpMetrics, HttpSnapshotGuard, MetricsMiddleware, TracingSpanMiddleware,
|
||||||
@ -14,6 +16,7 @@ use service::AppService;
|
|||||||
use session::config::{PersistentSession, SessionLifecycle, TtlExtensionPolicy};
|
use session::config::{PersistentSession, SessionLifecycle, TtlExtensionPolicy};
|
||||||
use session::storage::RedisClusterSessionStore;
|
use session::storage::RedisClusterSessionStore;
|
||||||
use session::SessionMiddleware;
|
use session::SessionMiddleware;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
|
|
||||||
@ -27,6 +30,82 @@ pub struct AppState {
|
|||||||
pub cache: AppCache,
|
pub cache: AppCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Skips Logger terminal output for noisy health/monitor/WS endpoints while still
|
||||||
|
/// passing the request through to the inner service (so metrics are still recorded).
|
||||||
|
struct SkipNoisyPaths<S>(S);
|
||||||
|
|
||||||
|
impl<S> SkipNoisyPaths<S> {
|
||||||
|
fn new(logger: S) -> Self {
|
||||||
|
Self(logger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, B> actix_web::dev::Transform<S, ServiceRequest> for SkipNoisyPaths<S>
|
||||||
|
where
|
||||||
|
S: actix_web::dev::Transform<
|
||||||
|
ServiceRequest,
|
||||||
|
Response = ServiceResponse<B>,
|
||||||
|
Error = actix_web::Error,
|
||||||
|
InitError = (),
|
||||||
|
>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: 'static,
|
||||||
|
{
|
||||||
|
type Response = ServiceResponse<B>;
|
||||||
|
type Error = actix_web::Error;
|
||||||
|
type Transform = SkipNoisyPathsService<S::Transform>;
|
||||||
|
type InitError = ();
|
||||||
|
|
||||||
|
fn new_transform(&self, service: S::Transform) -> <Self::Transform as actix_web::dev::Transform<
|
||||||
|
ServiceRequest,
|
||||||
|
Response = ServiceResponse<B>,
|
||||||
|
Error = actix_web::Error,
|
||||||
|
InitError = (),
|
||||||
|
>>::Future {
|
||||||
|
futures::future::ok(SkipNoisyPathsService {
|
||||||
|
service,
|
||||||
|
_marker: std::marker::PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SkipNoisyPathsService<S> {
|
||||||
|
service: S,
|
||||||
|
_marker: std::marker::PhantomData<fn(ServiceRequest)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, B> actix_web::dev::Service<ServiceRequest> for SkipNoisyPathsService<S>
|
||||||
|
where
|
||||||
|
S: actix_web::dev::Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: 'static,
|
||||||
|
{
|
||||||
|
type Response = ServiceResponse<B>;
|
||||||
|
type Error = actix_web::Error;
|
||||||
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.service.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
|
let path = req.path().to_string();
|
||||||
|
let should_skip = path == "/health"
|
||||||
|
|| path == "/metrics"
|
||||||
|
|| path.starts_with("/ws");
|
||||||
|
if should_skip {
|
||||||
|
let fut = self.service.call(req);
|
||||||
|
Box::pin(async move {
|
||||||
|
let res = fut.await?;
|
||||||
|
// Replace body so Logger doesn't print the line for this request.
|
||||||
|
Ok(res.map_body(|_, _| actix_web::body::Empty::new()))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.service.call(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn build_session_key(cfg: &AppConfig) -> anyhow::Result<Key> {
|
fn build_session_key(cfg: &AppConfig) -> anyhow::Result<Key> {
|
||||||
if let Some(secret) = cfg.env.get("APP_SESSION_SECRET") {
|
if let Some(secret) = cfg.env.get("APP_SESSION_SECRET") {
|
||||||
let bytes: Vec<u8> = secret.as_bytes().iter().cycle().take(64).copied().collect();
|
let bytes: Vec<u8> = secret.as_bytes().iter().cycle().take(64).copied().collect();
|
||||||
@ -128,7 +207,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
App::new()
|
App::new()
|
||||||
.wrap(cors)
|
.wrap(cors)
|
||||||
.wrap(session_mw)
|
.wrap(session_mw)
|
||||||
.wrap(Logger::default().exclude("/health"))
|
.wrap(SkipNoisyPaths::new(Logger::default()))
|
||||||
.wrap(metrics_mw)
|
.wrap(metrics_mw)
|
||||||
.wrap(TracingSpanMiddleware::new())
|
.wrap(TracingSpanMiddleware::new())
|
||||||
.app_data(web::Data::new(AppState {
|
.app_data(web::Data::new(AppState {
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
use actix_files::Files;
|
use actix_files::Files;
|
||||||
|
use actix_web::dev::{Service, ServiceRequest, ServiceResponse};
|
||||||
use actix_web::{http::header, middleware::Logger, web, App, HttpResponse, HttpServer};
|
use actix_web::{http::header, middleware::Logger, web, App, HttpResponse, HttpServer};
|
||||||
|
use futures::future::LocalBoxFuture;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
/// Static file server for avatar, blob, and other static files
|
/// Static file server for avatar, blob, and other static files
|
||||||
/// Serves files from /data/{type} directories
|
/// Serves files from /data/{type} directories
|
||||||
@ -39,6 +42,79 @@ async fn health() -> HttpResponse {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Skips Logger terminal output for noisy health/WS endpoints.
|
||||||
|
struct SkipNoisyPaths<S>(S);
|
||||||
|
|
||||||
|
impl<S> SkipNoisyPaths<S> {
|
||||||
|
fn new(logger: S) -> Self {
|
||||||
|
Self(logger)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, B> actix_web::dev::Transform<S, ServiceRequest> for SkipNoisyPaths<S>
|
||||||
|
where
|
||||||
|
S: actix_web::dev::Transform<
|
||||||
|
ServiceRequest,
|
||||||
|
Response = ServiceResponse<B>,
|
||||||
|
Error = actix_web::Error,
|
||||||
|
InitError = (),
|
||||||
|
>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: 'static,
|
||||||
|
{
|
||||||
|
type Response = ServiceResponse<B>;
|
||||||
|
type Error = actix_web::Error;
|
||||||
|
type Transform = SkipNoisyPathsService<S::Transform>;
|
||||||
|
type InitError = ();
|
||||||
|
|
||||||
|
fn new_transform(&self, service: S::Transform) -> <Self::Transform as actix_web::dev::Transform<
|
||||||
|
ServiceRequest,
|
||||||
|
Response = ServiceResponse<B>,
|
||||||
|
Error = actix_web::Error,
|
||||||
|
InitError = (),
|
||||||
|
>>::Future {
|
||||||
|
futures::future::ok(SkipNoisyPathsService {
|
||||||
|
service,
|
||||||
|
_marker: std::marker::PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SkipNoisyPathsService<S> {
|
||||||
|
service: S,
|
||||||
|
_marker: std::marker::PhantomData<fn(ServiceRequest)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, B> Service<ServiceRequest> for SkipNoisyPathsService<S>
|
||||||
|
where
|
||||||
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: 'static,
|
||||||
|
{
|
||||||
|
type Response = ServiceResponse<B>;
|
||||||
|
type Error = actix_web::Error;
|
||||||
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.service.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
|
let path = req.path().to_string();
|
||||||
|
let should_skip =
|
||||||
|
path == "/health" || path == "/metrics" || path.starts_with("/ws");
|
||||||
|
if should_skip {
|
||||||
|
let fut = self.service.call(req);
|
||||||
|
Box::pin(async move {
|
||||||
|
let res = fut.await?;
|
||||||
|
Ok(res.map_body(|_, _| actix_web::body::Empty::new()))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.service.call(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
@ -79,7 +155,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(cors)
|
.wrap(cors)
|
||||||
.wrap(Logger::default())
|
.wrap(SkipNoisyPaths::new(Logger::default()))
|
||||||
.route("/health", web::get().to(health))
|
.route("/health", web::get().to(health))
|
||||||
.service(
|
.service(
|
||||||
Files::new("/avatar", root.join("avatar"))
|
Files::new("/avatar", root.join("avatar"))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user