Skip to content

Instantly share code, notes, and snippets.

@dimfeld
Last active August 27, 2021 04:50
Show Gist options
  • Save dimfeld/189053f1307682524739df8387636daa to your computer and use it in GitHub Desktop.
Save dimfeld/189053f1307682524739df8387636daa to your computer and use it in GitHub Desktop.
Simple actix-web middleware for custom KV logging with slog
use actix_service::{Service, Transform};
use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error};
use futures::future::{ok, FutureResult};
use futures::{Future, Poll};
use slog::info;
// There are two step in middleware processing.
// 1. Middleware initialization, middleware factory get called with
// next service in chain as parameter.
// 2. Middleware's call method get called with normal request.
pub struct Logging {
logger: slog::Logger,
}
impl Logging {
pub fn new(logger: slog::Logger) -> Logging {
Logging { logger }
}
}
// Middleware factory is `Transform` trait from actix-service crate
// `S` - type of the next service
// `B` - type of response's body
impl<S, B> Transform<S> for Logging
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = LoggingMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
ok(LoggingMiddleware {
service,
logger: self.logger.clone(),
})
}
}
pub struct LoggingMiddleware<S> {
service: S,
logger: slog::Logger,
}
impl<S, B> Service for LoggingMiddleware<S>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
let start_time = chrono::Utc::now();
let logger = self.logger.clone();
Box::new(self.service.call(req).and_then(move |res| {
let req = res.request();
let end_time = chrono::Utc::now();
let duration = end_time - start_time;
info!(logger, "handled request";
"responseTime" => duration.num_milliseconds(),
"url" => %req.uri(),
"route" => req.path(),
"method" => %req.method(),
"statusCode" => res.status().as_u16()
);
Ok(res)
}))
}
}
@DrBluefall
Copy link

Just a note, this doesn't work with actix-web >= 4.0.0-beta.8.

@dimfeld
Copy link
Author

dimfeld commented Aug 27, 2021

@DrBluefall yeah not too surprised there. The middleware traits have changed a bit since I originally wrote this. FWIW nowadays I prefer the tracing crate along with tracing-actix-web for my logging needs.

@DrBluefall
Copy link

Not too much a fan of tracing, generally prefer slog. Especially when using with journald.

I'm sure I could hack something up based on this, though. Maybe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment