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_web::cookie::time::Duration;
|
||||
use actix_web::dev::{Service, ServiceRequest, ServiceResponse};
|
||||
use actix_web::middleware::Logger;
|
||||
use actix_web::{cookie::Key, web, App, HttpResponse, HttpServer};
|
||||
use clap::Parser;
|
||||
use db::cache::AppCache;
|
||||
use db::database::AppDatabase;
|
||||
use futures::future::LocalBoxFuture;
|
||||
use observability::{
|
||||
init_tracing_subscriber, install_recorder, prometheus_handler, spawn_http_metrics_poller,
|
||||
HttpMetrics, HttpSnapshotGuard, MetricsMiddleware, TracingSpanMiddleware,
|
||||
@ -14,6 +16,7 @@ use service::AppService;
|
||||
use session::config::{PersistentSession, SessionLifecycle, TtlExtensionPolicy};
|
||||
use session::storage::RedisClusterSessionStore;
|
||||
use session::SessionMiddleware;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
mod args;
|
||||
|
||||
@ -27,6 +30,82 @@ pub struct AppState {
|
||||
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> {
|
||||
if let Some(secret) = cfg.env.get("APP_SESSION_SECRET") {
|
||||
let bytes: Vec<u8> = secret.as_bytes().iter().cycle().take(64).copied().collect();
|
||||
@ -128,7 +207,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
App::new()
|
||||
.wrap(cors)
|
||||
.wrap(session_mw)
|
||||
.wrap(Logger::default().exclude("/health"))
|
||||
.wrap(SkipNoisyPaths::new(Logger::default()))
|
||||
.wrap(metrics_mw)
|
||||
.wrap(TracingSpanMiddleware::new())
|
||||
.app_data(web::Data::new(AppState {
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
use actix_cors::Cors;
|
||||
use actix_files::Files;
|
||||
use actix_web::dev::{Service, ServiceRequest, ServiceResponse};
|
||||
use actix_web::{http::header, middleware::Logger, web, App, HttpResponse, HttpServer};
|
||||
use futures::future::LocalBoxFuture;
|
||||
use std::path::PathBuf;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
/// Static file server for avatar, blob, and other static files
|
||||
/// 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]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||
@ -79,7 +155,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
|
||||
App::new()
|
||||
.wrap(cors)
|
||||
.wrap(Logger::default())
|
||||
.wrap(SkipNoisyPaths::new(Logger::default()))
|
||||
.route("/health", web::get().to(health))
|
||||
.service(
|
||||
Files::new("/avatar", root.join("avatar"))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user