4

I'm trying to use Spring AOP to intercept Feign.Client calls and log request and response to my Splunk Server. All methods in my project package are intercepted as I expected but Feign.Client doesn't.

This is my AOP class:

@Component
@Aspect
public class MyAspect {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Pointcut("execution(* com.example.demo.*.*(..))")
    public void pointCutDemo(){}

    @Pointcut("execution(* feign.Client+.*(..))")
    public void pointCutFeign(){}

    @Around("pointCutDemo()")
    public void myAroundDemo(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("calling joinpoint "+joinPoint.getSignature().getName());
        joinPoint.proceed();
    }

    @Around("pointCutFeign()")
    public void myAroundFeign(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("calling feign joinpoint "+joinPoint.getSignature().getName());
        joinPoint.proceed();
    }
}

The method myAroundDemo is called multiple times, as I expected, but myAroundFeign is never called.

I have a simple Controller that call my interface (Feign API), this is the controller:

@RestController
public class Controller {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private ExternalAPI externalAPI;

    @GetMapping
    public String get(){
        logger.info("calling get method");
        logger.info(String.valueOf(externalAPI.listUsers()));
        return "I'm here";
    }
}

And this is my Feign Interface:

@FeignClient(url = "http://localhost:3000", name = "feign", configuration = FeignConfig.class)
public interface ExternalAPI {
    @GetMapping(value = "/menu")
    String listUsers();
}
kriegaex
  • 63,017
  • 15
  • 111
  • 202
Ronaldo Lanhellas
  • 2,975
  • 5
  • 46
  • 92

5 Answers5

2

Spring AOP only applies to Spring components. My guess would be it is not working because Feign is not a Spring component and thus out of scope for Spring AOP. If you need to apply aspects to non-Spring classes, just use full AspectJ. The Spring manual explains how to configure it via LTW (load-time weaving).

kriegaex
  • 63,017
  • 15
  • 111
  • 202
2

@kriegaex is correct and I can't apply AOP in a non-Spring Component. But I prefer not to use pure AspectJ, so I fixed with another solution. Here are my steps to solve the problem:

1) Enabling Spring Cloud Ribbon I got the class LoadBalancerFeignClient managed by spring that implements feign.Client, so I added the dependency in pom.xml and changed my application.yml.

application.yml

myfeign:
  ribbon:
    listOfServers: localhost:3000

pom.xml

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

2) In MyAspect class I intercepted the LoadBalancerFeignClient class:

@Pointcut("execution(* org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient.execute(..))")
public void pointCutFeign(){}

@Around("pointCutFeign()")
public Object myAroundFeign(ProceedingJoinPoint joinPoint) throws Throwable {
    if (joinPoint.getArgs().length > 0) {
        Request request = (Request) joinPoint.getArgs()[0];
        logger.info("REQUEST >>>>>>>>>>>>");
        logger.info("URL = "+request.url());
        logger.info("METHOD = "+request.httpMethod().name());
        logger.info("BODY = "+request.requestBody().asString());
        logger.info("HEADERS = "+request.headers().toString());
    }

    Response response = (Response) joinPoint.proceed();

    logger.info("RESPONSE <<<<<<<<<<<<<<");
    logger.info("STATUS = "+response.status());
    logger.info("HEADERS = "+response.headers().toString());
    logger.info("BODY = " + IOUtils.toString(response.body().asInputStream(), "UTF-8"));
    return response;
}

Now it works very well, i got all information that i need.

kriegaex
  • 63,017
  • 15
  • 111
  • 202
Ronaldo Lanhellas
  • 2,975
  • 5
  • 46
  • 92
  • Very nice that you found this easy solution for yourself. In general we can say that if you want to avoid AspectJ you have the option of writing a Spring component wrapper around whatever 3rd class objects you want to intercept. In your case there was a prefabricated component already, making it even easier. :-) BTW, you should accept your own answer in order to close this question. – kriegaex Jul 04 '19 at 04:01
  • Following your answer, I worked around the same issue for logging. However when I try to use `listUsers()` method's return value from your example, I am getting null even though advice is getting the response object. Any insights on this? @Ronaldo – bibliophilsagar Mar 09 '20 at 17:41
1

Feign has built in logging that you can use without AOP. If you create a feign.Logger instance and register it, it will log the request, response, and headers.

@Bean
public feign.Logger logger() {
    /* requires feign-slf4j */
    return new Slf4jLogger();
}

Logger instances provide the following features:

  • Requests are logged before they are sent to the client.
  • Responses are logged regardless of status, once they are received.
  • Retries are logged if they are triggered.

This may be a better solution if logging is all you need.

Kevin Davis
  • 1,193
  • 8
  • 14
0

I also face this question . but I can not intercepted the LoadBalancerFeignClient class. i use the same code to test ,but it does not work.enter image description here

when i debug the funcution ,i find (this) point to the TraceLoadBalanceFeignClient.The LoadBalancerFeignClient ’s subclass.finally i find when i use sleuth. feign client would be create by sleuth 's feignbuilder.The pointcut will invalid

basic_seed
  • 23
  • 4
0

I have a way to get useful information when intercepting any feign client, I hope it helps someone.

@Before("@within(org.springframework.cloud.openfeign.FeignClient)")
public void logBeforeService(JoinPoint joinPoint) {
    final MethodMetadata methodMetadata = new SpringMvcContract().parseAndValidateMetadata(
                                   joinPoint.getSignature().getDeclaringType(),
                                   ((MethodSignature) joinPoint.getSignature()).getMethod());

    String requestUrl = methodMetadata.template().url();
    String methodName = methodMetadata.template().method();
    log.info("RequestUrl: {}", requestUrl);
    log.info("HttpMethod: {}", methodName);

}
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community May 05 '22 at 23:28