finagletwitter-finagle

Logging the remote host in Finagle


I'd like to log the called remote host to STDOUT when using Finagle Client. But as far as I see this is not possible via com.twitter.finagle.http.filter.LoggingFilter; Its #format (example see below) method cannot access the actual host:

My first guess is/was that accessing the host is not possible because Finagle's client-side load balancing happens deeper in the stack.

This is the test code I use:

    LoggingFilter<Request> loggingFilter = new LoggingFilter<>(
            new Logger(this.getClass().getSimpleName(), java.util.logging.Logger.getLogger(this.getClass().getSimpleName())),

            new LogFormatter<Request, Response>() {
                @Override
                public String format(Request request, Response reply, Duration replyTime) {
                    return null;
                }

                @Override
                public String formatException(Request request, Throwable throwable, Duration replyTime) {
                    return null;
                }
            });

    Service<Request, Response> service = Http.client().newService("localhost:8090,localhost:8091");
    Future<Response> response = loggingFilter.andThen(service).apply(Request.apply("/profiles/me"));

Solution

  • The actual endpoint to which the request is being sent is decided in the load balancer. So indeed logging the remote host can only be done after the load balancing module.

    The load balancer module makes the parameter Transporter.EndpointAddr available. This parameter contains the actual address. In order to use this param you should add a module to the HTTP client stack, just after the load balancing module.

    An example in Scala:

    Create your logging filter:

    class MyLoggingFilter(addr: Address) extends SimpleFilter[Request, Response] {
      override def apply(request: Request, service: Service[Request, Response]) = {
        println(s"Sending request to $addr")
        service(request)
      }
    }
    

    Define the new module

    def endpointLoggerModule: Stackable[ServiceFactory[Request, Response]] =
      new Stack.Module1[Transporter.EndpointAddr, ServiceFactory[Request, Response]] {
        val role: Role = Role("EndpointLogger")
        val description = "Log remote address"
        def make(_addr: Transporter.EndpointAddr, 
                 next: ServiceFactory[Request, Response]) = {
          val Transporter.EndpointAddr(addr) = _addr
          new MyLoggingFilter(addr).andThen(next)
        }
      }
    

    Create a new Http client with this module in the stack:

    val stackWithLogging = Http.client.stack
      .insertAfter(LoadBalancerFactory.role, endpointLoggerModule)
    val service = Http.client.copy(stack = stackWithLogging)
      .newService("localhost:8090,localhost:8091")
    

    This created service should then log the actual addresses to which the requests are sent.

    See the official Finagle documentation for more information on module composition.