Logging not working in web application using Spring AOP - spring

using Spring AOP, I'm trying to put logging in my web application for an object called corelation like below :-
LoggingCorrelationEnrichingAspect.java:-
#Aspect
#Component
public class LoggingCorrelationEnrichingAspect {
private static final Logger logger = getLogger(LoggingCorrelationEnrichingAspect.class);
#Around("#annotation(Correlated)")
public Object wrapWithCorrelationContext(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
logger.info("Entering "+ proceedingJoinPoint.getSignature().getName() +" with Correlation Id:: "
+ ((Map)proceedingJoinPoint.getArgs()[0]).get(CommerceConnectorConstants.HttpHeaders.CORRELATION_ID).get());
return ((Mono<?>) proceedingJoinPoint.proceed());
}
}
Correlated.java:-
#Inherited
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Correlated {}
In my main REST Controller operation, using #Correlated annotation, I'm trying to log this corellation like below :-
#Correlated
#GetMapping(path = "/products}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Mono<ProductBeanResponse> getProducts(
#RequestHeader(name = Test.HttpHeaders.TENANT_ID, required = true) UUID tId,
#RequestHeader(name = Test.HttpHeaders.CORRELATION_ID, required = true) UUID correlationId
----
---
}
However, when I test my service using PostMan tool and see the applicaiton logs, the corelation id is never logged :-
logger.info("Entering "+ proceedingJoinPoint.getSignature().getName() +" with Correlation Id:: "
+ ((Map)proceedingJoinPoint.getArgs()[0]).get(CommerceConnectorConstants.HttpHeaders.CORRELATION_ID).get());
Please advise is this a configuration issue in Spring AOP.
Thanks

This can get working in either of below two ways
Provide fully qualified name of Correlated in the pointcut definition as #Around("#annotation(com.x.y.z.Correlated)")
Update the Aspect method signature to include the Correlated as second argument
#Around("#annotation(correlated)")
public Object wrapWithCorrelationContext(ProceedingJoinPoint proceedingJoinPoint, Correlated correlated ) throws Throwable {
logger.info("Entering "+ proceedingJoinPoint.getSignature().getName() +" with Correlation Id:: "
+ ((Map)proceedingJoinPoint.getArgs()[0]).get(CommerceConnectorConstants.HttpHeaders.CORRELATION_ID).get());
return ((Mono<?>) proceedingJoinPoint.proceed());
}
Let know in comments if anything else is required.
P.S.: Also as pointed out by M. Deinum make sure to remove object cast.

Related

error is prompted when I use #target in spring aop

I find the same question in there but didn`t find a useful answer, so I support more details. My code is the following.
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface DS {
String value();
}
public class AnnotationAspect {
#Around("#target(com.yh.application.DS)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String dsName = getDataSourceAnnotation(joinPoint).value();
System.out.println("enter in aspect:" + dsName);
return joinPoint.proceed();
}
here is a demo,
just run the application you can see the error stack trace
Unable to proxy interface-implementing method
[public final void org.springframework.boot.web.servlet.RegistrationBean.onStartup
(javax.servlet.ServletContext) throws javax.servlet.ServletException]
because it is marked as final: Consider using interface-based JDK proxies instead!
seems I need to change the aop proxy type to JDK, but when I did this, another error is prompted.
The bean 'dispatcherServlet' could not be injected as a 'org.springframework.web.servlet.DispatcherServlet' because it is a JDK dynamic proxy
Does anyone help me? thank you!
R.G's solution is correct, you ought to limit the pointcut scope. BTW, looking at your aspect code, I noticed this contrived way of getting the annotation value:
private DS getDataSourceAnnotation(ProceedingJoinPoint joinPoint) {
Class<?> targetClass = joinPoint.getTarget().getClass();
DS dsAnnotation = targetClass.getAnnotation(DS.class);
if (Objects.nonNull(dsAnnotation)) {
return dsAnnotation;
}
else {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
return methodSignature.getMethod().getAnnotation(DS.class);
}
}
I suggest you just bind the annotation to an advice method parameter like this:
package com.yh.application;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class AnnotationAspect {
#Before("#target(ds) && within(com.yh..*)")
public void interceptDS(JoinPoint joinPoint, DS ds) {
System.out.println(joinPoint + " -> DS value = " + ds.value());
}
}
Update:
I forgot to explain why you were getting the error in the first place: Pointcuts like this(), target(), #this(), #target() can only be determined dynamically during runtime because they access active object instances. Hence, all possible Spring components (also internal ones) are being aspect-woven, which is also the reason why the workaround to limit the aspect scope by using statically evaluated pointcut designators like within() help you avoid the problem.
But actually, using a statically evaluated pointcut designator in the first place, if it is a viable alternative, is the best idea. It is also faster than weaving the world, creating dozens or hundreds of proxies, and then to dynamically evaluate pointcuts over and over again. Luckily, in this case such an alternative exists: #within().
#Aspect
#Component
public class AnnotationAspect {
#Before("#within(ds)")
public void interceptDS(JoinPoint joinPoint, DS ds) {
System.out.println(joinPoint + " -> DS value = " + ds.value());
}
}

Order of processing REST API calls

I have a strage(for me) question to ask. I have created synchronized Service which is called by Controller:
#Controller
public class WebAppApiController {
private final WebAppService webApService;
#Autowired
WebAppApiController(WebAppService webApService){
this.webApService= webApService;
}
#Transactional
#PreAuthorize("hasAuthority('ROLE_API')")
#PostMapping(value = "/api/webapp/{projectId}")
public ResponseEntity<Status> getWebApp(#PathVariable(value = "projectId") Long id, #RequestBody WebAppRequestModel req) {
return webApService.processWebAppRequest(id, req);
}
}
Service layer is just checking if there is no duplicate in request and store it in database. Because client which is using this endpoint is making MANY requests continously it happened that before one request was validated agnist duplicate other the same was put in database - that is why I am trying to do synchronized block.
#Service
public class WebAppService {
private final static String UUID_PATTERN_TO = "[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}";
private final WebAppRepository waRepository;
#Autowired
public WebAppService(WebAppRepository waRepository){
this.waRepository= waRepository;
}
#Transactional(rollbackOn = Exception.class)
public ResponseEntity<Status> processScanWebAppRequest(Long id, WebAppScanModel webAppScanModel){
try{
synchronized (this){
Optional<WebApp> webApp=verifyForDuplicates(webAppScanModel);
if(!webApp.isPresent()){
WebApp webApp=new WebApp(webAppScanModel.getUrl())
webApp=waRepository.save(webApp);
processpropertiesOfWebApp(webApp);
return new ResonseEntity<>(HttpStatus.CREATED);
}
return new ResonseEntity<>(HttpStatus.CONFLICT);
}
} catch (NonUniqueResultException ex){
return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
} catch (IncorrectResultSizeDataAccessException ex){
return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
}
}
}
Optional<WebApp> verifyForDuplicates(WebAppScanModel webAppScanModel){
return waRepository.getWebAppByRegex(webAppScanModel.getUrl().replaceAll(UUID_PATTERN_TO,UUID_PATTERN_TO)+"$");
}
And JPA method:
#Query(value="select * from webapp wa where wa.url ~ :url", nativeQuery = true)
Optional<WebApp> getWebAppByRegex(#Param("url") String url);
processpropertiesOfWebApp method is doing further processing for given webapp which at this point should be unique.
Intended behaviour is:
when client post request contains multiple urls like:
https://testdomain.com/user/7e1c44e4-821b-4d05-bdc3-ebd43dfeae5f
https://testdomain.com/user/d398316e-fd60-45a3-b036-6d55049b44d8
https://testdomain.com/user/c604b551-101f-44c4-9eeb-d9adca2b2fe9
Only first one will be stored within database but at this moment this is not what is happening. Select from my database:
select inserted,url from webapp where url ~ 'https://testdomain.com/users/[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}$';
2019-11-07 08:53:05 | https://testdomain.com/users/d398316e-fd60-45a3-b036-6d55049b44d8
2019-11-07 08:53:05 | https://testdomain.com/users/d398316e-fd60-45a3-b036-6d55049b44d8
2019-11-07 08:53:05 | https://testdomain.com/users/d398316e-fd60-45a3-b036-6d55049b44d8
(3 rows)
I will try to add unique constraint on url column but I can't imagine this will solve the problem while when UUID changes new url will be unique
Could anyone give me a hint what I am doing wrong?
Question is related with the one I asked before but not found proper solution, so I simplified my method but still no success

Error casting object MethodSignature. Spring AOP

Thanks in advance for your support.
Currently I´m stuck in the next problem. I developed an Aspect class to validate my input JSON from al the pkg of RestController.
Complying with certain characteristics.
Each method of my controllers returns a different DTO object.
I created a new generic object to return it from my aspect, when my logic is not fulfilled. When I do tests, I get an error of CannotCastClass "xxxxDTO" to newErrorResponseDTO.
Currently I already can obtain the method signature or the object type. My idea is to cast the return type (from methodSignature) to my new DTOResponse. The object response is always different.
I mention that the architecture and design of the total project was already developed. I only did the aspect
At the moment, I have not succeeded.
I attach evidence. Thanks
I tried ResponseAdvice, and multiple ways to cast objects.
I prefer to stay in the aspect. I get the solution changing all the response DTO in controller to Object generic. Asumming that doing is bad practice, i prefer real solution
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
// Other imports missing...
#Aspect
#Component("validateParameterAspect")
public class ValidatorParameterAspect {
public static final Logger logger = Logger.getLogger(ValidatorParameterAspect.class);
#Autowired
ServiciosRest servicio;
#Pointcut("execution(* com.actinver.rest.*.* (..))")
public void executeController() {}
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void logRequestMapping() {}
#Around("logRequestMapping() && executeController() && args(..,#RequestBody requestBody) ")
public Object logRequestBody(ProceedingJoinPoint joinPoint, Object requestBody) throws Throwable {
String vlDataDecrypt = "";
try {
// output = joinPoint.proceed();
System.out.println("--------------123------------");
logger.warn("Entering in Method : " + joinPoint.getSignature().getName());
logger.warn("Class Name : " + joinPoint.getSignature().getDeclaringTypeName());
logger.warn("Arguments : " + Arrays.toString(joinPoint.getArgs()));
logger.warn("Target class : " + joinPoint.getTarget().getClass().getName());
SimpleJSONDataContainer args = (SimpleJSONDataContainer) joinPoint.getArgs()[0];
MethodSignature sign = (MethodSignature) joinPoint.getSignature();
Class<?> ret = sign.getReturnType();
String returnString = sign.getReturnType().getName();
logger.warn("Signature : " + ret);
vlDataDecrypt = AESHelper.decrypt(servicio.getSeedWord(), args.getData());
logger.info(" Decrypt -> " + vlDataDecrypt);
logger.info("args " + args.getData());
ErrorDataResponse res = validDataEmpty(args.getData());
if (res.getResult() == "2") {
return res; // or cast Class<?>
//return ret.cast(res);
}
} catch (Exception e) {
logger.error("Stack trace -> ", e);
}
return joinPoint.proceed();
}
public ErrorDataResponse validDataEmpty(String vlDataDecrypt) {
ErrorDataResponse errorDto = new ErrorDataResponse();
if (vlDataDecrypt == null || vlDataDecrypt.hashCode() == "77631826690E45839D7B49B932CBC81B".hashCode()
&& vlDataDecrypt.equalsIgnoreCase("77631826690E45839D7B49B932CBC81B")) {
errorDto.setResult("2");
errorDto.setMensaje(RestValidatorUtil.EnumErrors.ERROR_INPUT.getMsg());
logger.info("JSON null" + errorDto.getResult());
return errorDto;
}
return errorDto;
}
}
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
// Other imports missing...
#RestController
#RequestMapping("inicio")
public class Bursanet {
public final static Logger logger = Logger.getLogger(Bursanet.class);
#RequestMapping(
value = "cashByDate",
method = { RequestMethod.GET, RequestMethod.POST },
consumes = "application/json",
produces = "application/json"
)
public CashByDateDTO cashByDate(
#RequestBody SimpleJSONDataContainer simpleJSONDataContainer,
Authentication authentication
) {
String vlDataDecrypt = "";
CashByDateDTO outJson = new CashByDateDTO();
CashByDateRequest request = null;
try {
UsernamePasswordAuthenticationToken userPasswordAuthenticationToken =
(UsernamePasswordAuthenticationToken)
((OAuth2Authentication) authentication).getUserAuthentication();
//////example
return outJson;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
It is very difficult to analyse your code because you are not providing an MCVE:
There are no package names in your classes.
There are no imports either.
You use several project-specific classes (not part of the Spring Framework) the code of which you also don't share here.
There is no Spring configuration either.
So I have to make some educated guesses here. From what I can see, I can tell you this:
If you expect ValidatorParameterAspect.logRequestBody(..) to intercept execution of Bursanet.cashByDate(..), it should not work because
in args(.., #RequestBody requestBody) you are expecting that parameter to be the last one in the target method's signature, but actually in Bursanet.cashByDate(..) it is the first one. So the pointcut should never match.
Again in args(.., #RequestBody requestBody) you ought to use a fully qualified class name, i.e. args(.., #org.springframework.web.bind.annotation.RequestBody requestBody).
Please also note that execution(* com.actinver.rest.*.* (..)) only matches methods in classes residing directly in the com.actinver.rest package, not in any subpackages. If you want to include those too, you need to change the pointcut to execution(* com.actinver.rest..* (..)).
In your question you mention you only want to intercept REST controllers, but you do not limit pointcut matching to classes with a #RestController annotation. You could do that via #within(org.springframework.web.bind.annotation.RestController). Right now you are doing it indirectly by only relying on methods with #annotation(org.springframework.web.bind.annotation.RequestMapping), which will also work as long as those methods only occur in #RequestController classes. Probably this is the case in your application, I am just mentioning it as a detail.
Instead of SimpleJSONDataContainer args = (SimpleJSONDataContainer) joinPoint.getArgs()[0];, why don't you bind the first argument to a SimpleJSONDataContainer parameter via args() and then just use the currently unused requestBody advice method parameter in your code? Something like this:
#Around("logRequestMapping() && executeController() && args(#org.springframework.web.bind.annotation.RequestBody requestBody, ..)")
public Object logRequestBody(ProceedingJoinPoint joinPoint, SimpleJSONDataContainer requestBody) throws Throwable {
// (...)
vlDataDecrypt = AESHelper.decrypt(servicio.getSeedWord(), requestBody.getData());
logger.info(" Decrypt -> " + vlDataDecrypt);
logger.info("args " + requestBody.getData());
ErrorDataResponse res = validDataEmpty(requestBody.getData());
// (...)
}
You define MethodSignature sign = (MethodSignature) joinPoint.getSignature(); but don't use it above several times where you repeatedly call joinPoint.getSignature(), too. Instead you could just reorganise the code like this:
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
System.out.println("--------------123------------");
logger.warn("Entering in Method : " + methodSignature.getName());
logger.warn("Class Name : " + methodSignature.getDeclaringTypeName());
logger.warn("Arguments : " + Arrays.toString(joinPoint.getArgs()));
logger.warn("Target class : " + joinPoint.getTarget().getClass().getName());
Class<?> ret = methodSignature.getReturnType();
String returnString = methodSignature.getReturnType().getName();
I never understood why so many people call many JoinPoint methods in order to extract details for logging if instead they could simply log the joinpoint instance. This would show the type of pointcut (e.g. execution()) as well as the target method signature. Okay, if you want to list all method arguments, you can do this additionally, but how about this, wouldn't that be enough?
logger.warn(joinPoint);
// logger.warn("Entering in Method : " + methodSignature.getName());
// logger.warn("Class Name : " + methodSignature.getDeclaringTypeName());
logger.warn("Arguments : " + Arrays.toString(joinPoint.getArgs()));
// logger.warn("Target class : " + joinPoint.getTarget().getClass().getName());
This whole code block I guess you can also remove. It even prints wrong information and calls the return type "signature":
Class<?> ret = methodSignature.getReturnType();
String returnString = methodSignature.getReturnType().getName();
logger.warn("Signature : " + ret);
Now for the part which is probably your problem:
ErrorDataResponse res = validDataEmpty(requestBody.getData());
if (res.getResult() == "2") {
return res; // or cast Class<?>
//return ret.cast(res);
}
Here you are making the aspect advice skip the joinPoint.proceed() call and return another object instead. The method you intercept has the signature public CashByDateDTO cashByDate(..), i.e. it returns a specific DTO type. If you want to return an ErrorDataResponse instead, this would only work if ErrorDataResponse was a subtype of CashByDateDTO, which probably it is not. From the class names I would even say that a *Response and a *DTO are completely different object types. Your advice cannot just change or ignore the method signature. You have to return a CashByDateDTO object, no matter what. If you cannot do that here, maybe you are intercepting the wrong method or trying to do the wrong thing in your aspect.
Sorry for the lengthy reply, but there is so much chaos in your code, I had to point out some details.

Validating Spring Kafka payloads

I am trying to set up a service that has both a REST (POST) endpoint and a Kafka endpoint, both of which should take a JSON representation of the request object (let's call it Foo). I would want to make sure that the Foo object is valid (via JSR-303 or whatever). So Foo might look like:
public class Foo {
#Max(10)
private int bar;
// Getter and setter boilerplate
}
Setting up the REST endpoint is easy:
#PostMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<String> restEndpoint(#Valid #RequestBody Foo foo) {
// Do stuff here
}
and if I POST, { "bar": 9 } it processes the request, but if I post: { "bar": 99 } I get a BAD REQUEST. All good so far!
The Kafka endpoint is easy to create (along with adding a StringJsonMessageConverter() to my KafkaListenerContainerFactory so that I get JSON->Object conversion:
#KafkaListener(topics = "fooTopic")
public void kafkaEndpoint(#Valid #Payload Foo foo) {
// I shouldn't get here with an invalid object!!!
logger.debug("Successfully processed the object" + foo);
// But just to make sure, let's see if hand-validating it works
Validator validator = localValidatorFactoryBean.getValidator();
Set<ConstraintViolation<SlackMessage>> errors = validator.validate(foo);
if (errors.size() > 0) {
logger.debug("But there were validation errors!" + errors);
}
}
But no matter what I try, I can still pass invalid requests in and they process without error.
I've tried both #Valid and #Validated. I've tried adding a MethodValidationPostProcessor bean. I've tried adding a Validator to the KafkaListenerEndpointRegistrar (a la the EnableKafka javadoc):
#Configuration
public class MiscellaneousConfiguration implements KafkaListenerConfigurer {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Autowired
LocalValidatorFactoryBean validatorFactory;
#Override
public void configureKafkaListeners(KafkaListenerEndpointRegistrar registrar) {
logger.debug("Configuring " + registrar);
registrar.setMessageHandlerMethodFactory(kafkaHandlerMethodFactory());
}
#Bean
public MessageHandlerMethodFactory kafkaHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory();
factory.setValidator(validatorFactory);
return factory;
}
}
I've now spent a few days on this, and I'm running out of other ideas. Is this even possible (without writing validation into every one of my kakfa endpoints)?
Sorry for the delay; we are at SpringOne Platform this week.
The infrastructure currently does not pass a Validator into the payload argument resolver. Please open an issue on GitHub.
Spring kafka listener by default do not scan for #Valid for non Rest controller classes. For more details please refer this answer
https://stackoverflow.com/a/71859991/13898185

logging at package level using spring AOP

I am trying to implement logging and exception framework for my application using spring AOP. I have defined beans for classes in ApplicationContext and using interceptors to log entry, exit and exceptions if any.
My query is : Do I need to create bean for every class in my ApplicationContext or is it possible to generalise this at a package level. If I have 30 classes in my package, I should create 30 beans in my AppContext, rather I would prefer doing it at a package level if it is possible. Kindly help me in this regard.
You need to use #Around advice. Here define the pointcut such that each class and method of the package are covered / intercepted. See sample below -
#Aspect
public class AllMethodLogging {
#Around(execution("* com.x.y..*.*(..)"))
public Object intercept(ProceddingJoinPoint pjp){
String name = pjp.getSignature().getName();
try {
logger.info("started " + name);
Object obj = pjp.proceed();
logger.info("finished " + name + " successfully");
return obj;
} catch (Throwable t) {
logger.error(name + " finished with Exception - " + t.getMessage());
throw t;
} finally {
// do some more logging if required else skip
}
}
}
The pointcut execution("* com.x.y..*.*(..)") means that any method within com.x.y package, and its sub-package, with all visibility should be intercepted by this advice.
See docs here for more examples to customize your pointcut as per your needs.

Resources