In our code we create many "finagle pipelines" like so:
val f1 = new Filter[A,B,C,D](...)
val f2 = new SimpleFilter[C,D](...)
val f3 = new Filter[C,D,E,F](...)
val s = new Service[E,F](...)
val pipeline: Service[A,B] = f1 andThen f2 andThen f3 andThen s
I would now like the ability to "insert" loggers anywhere in such a chain. The logger would only log the fact that a request came in and a response was received. Something like this:
class LoggerFilter[Req, Resp](customLog: String) extends SimpleFilter[Req, Resp] with LazyLogging{
override def apply(request: Req, service: Service[Req, Resp]): Future[Resp] = {
logger.info(s"$customLog => Request: ${request.getClass.getName} -> ${service.toString}")
service(request).map{resp =>
logger.info(s"$customLog => Response: ${resp.getClass.getName} -> ${request.getClass.getName}")
resp
}
}
}
With this approach we have to keep declaring multiple loggers so that the types can align correctly and then we insert at the "right location".
val logger1 = new LoggerFilter[A,B]("A->B Logger")
val logger2 = new LoggerFilter[C,D]("C->D Logger")
val logger3 = new LoggerFilter[E,F]("E->F Logger")
val pipeline = logger1 andThen f1 andThen f2 andThen logger2 andThen f3 andThen logger3 andThen s
Is there a way this can be avoided? Is it possible to just have a single logger that can infer the Req/Resp
types automagically and be "insertable anywhere" in the chain?
E.g.:
val logger = getTypeAgnosticLogger // What's the implementation?
val pipeline = logger andThen f1 andThen f2 andThen logger andThen f3 andThen logger andThen s
// Is this possible - params for logger to print?
val pipeline = logger("f1") andThen f1 andThen f2 andThen logger("f3") andThen f3 andThen logger("s") andThen s
I couldn't work out a way of defining an automagic logger. My first idea was relying on compiler type inference as per @Krzysztof's suggestion, but that ended up with a type error due to a logger with parameters [Nothing, Nothing]
so it seems compiler inference triggered too early there. Given that, I'm not sure it's possible in the way you describe without explicit types on each logger.
However, what you can do to get something very similar is to extend the Filter
and Service
classes with a withLogging
method that attaches the Logger prior to running. At that point, you've got enough type information to construct the logger explicitly, and it also lets you pass in your param:
implicit class FilterLogging[ReqIn, RepOut, ReqOut, RepIn](filter: Filter[ReqIn, RepOut, ReqOut, RepIn]) {
def withLogging(param: String) : Filter[ReqIn, RepOut, ReqOut, RepIn] = new LoggerFilter[ReqIn, RepOut](param).andThen(filter)
}
implicit class ServiceLogging[Req, Rep](service: Service[Req, Rep]) {
def withLogging(param: String) : Service[Req, Rep] = new LoggerFilter[Req, Rep](param).andThen(service)
}
val pipeline = f1.withLogging("f1") andThen f2 andThen f3.withLogging("f3") andThen service.withLogging("s")
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With