Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to have a generic logging filter in finagle that can be "inserted anywhere" in a chain of andThens?

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
like image 925
PhD Avatar asked Jun 10 '19 18:06

PhD


1 Answers

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")
like image 192
Astrid Avatar answered Nov 10 '22 00:11

Astrid