若依源码解析:用LoggingAspect进行日志处理
摘要
本文将深入探讨若依框架中的LoggingAspect切面的作用和重要性。LoggingAspect是若依框架中用于实现日志记录功能的切面组件,它通过切点和切面的概念,将日志记录逻辑与业务逻辑解耦,实现非侵入式的日志记录。文章首先介绍了切面编程和AOP的概念,然后重点讨论了LoggingAspect的作用,包括捕获方法调用、记录日志信息以及提供代码重用和可维护性等优势。此外,文章还解释了@Pointcut注解的作用,它用于定义切点表达式,精确定位目标方法。最后,文章强调了在logback.xml配置文件中为LoggingAspect设置日志记录级别的重要性,以便控制日志输出的详细程度。通过深入理解LoggingAspect,开发人员可以优化日志记录和追踪功能,提高应用程序的可维护性和可读性,从而更好地监测和排查问题。
源代码
@Aspect
@Component
public class LoggingAspect {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private final Environment env;
public LoggingAspect(Environment env) {
this.env = env;
}
/**
* Pointcut that matches all repositories, services and Web REST endpoints.
*/
@Pointcut("within(@org.springframework.stereotype.Repository *)" +
" || within(@org.springframework.stereotype.Service *)" +
" || within(@org.springframework.web.bind.annotation.RestController *)")
public void springBeanPointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
@Pointcut("@annotation(org.springframework.scheduling.annotation.Scheduled)")
public void scheduledJob() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
/**
* Pointcut that matches all repositories, services and Web REST endpoints.
*/
@Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
public void springBeanResourcePointcut() {
// Method is empty as this is just a Pointcut, the implementations are in the advices.
}
/**
* Advice that logs methods throwing exceptions.
*
* @param joinPoint join point for advice
* @param e exception
*/
@AfterThrowing(pointcut = "springBeanPointcut() && scheduledJob()", throwing = "e")
public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
log.error("Exception in {}.{}() with cause = \'{}\' and exception = \'{}\'", joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(), e.getCause() != null ? e.getCause() : "NULL", e.getMessage(), e);
}
/**
* Advice that logs when a method is entered and exited.
*
* @param joinPoint join point for advice
* @return result
* @throws Throwable throws IllegalArgumentException
*/
@Around("scheduledJob()")
public Object scheduledJobAround(ProceedingJoinPoint joinPoint) throws Throwable {
String clazzName = joinPoint.getSignature().getDeclaringTypeName(),
funcName = joinPoint.getSignature().getName();
long start = System.currentTimeMillis();
log.info("执行定时任务 {} {}.{}", start, clazzName, funcName);
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
log.info("执行定时任务 {} {}.{}, host {} ms", start, clazzName, funcName, end - start);
return result;
}
@Around("springBeanResourcePointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
try {
log.info("Enter: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(), convertString(joinPoint.getArgs()));
Object result = joinPoint.proceed();
log.debug("Exit: {}.{}() with result = {}", joinPoint.getSignature().getDeclaringTypeName(),
joinPoint.getSignature().getName(), result);
return result;
} catch (IllegalArgumentException e) {
log.error("Illegal argument: {} in {}.{}()", Arrays.toString(joinPoint.getArgs()),
joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
throw e;
}
}
private String convertString(Object[] args) {
if (args == null || args.length == 0) {
return "[]";
}
String params = Arrays.stream(args).map(it -> {
if (it instanceof ServletResponse) {
return "ServletResponse";
}
if (it instanceof ServletRequest) {
return "ServletRequest";
}
if (it instanceof MultipartFile) {
MultipartFile f = (MultipartFile) it;
return String.format("{fileName: \"%s\", name:\"%s\", contentType: \"%s\", size: \"%d\"}",
f.getOriginalFilename(), f.getName(), f.getContentType(), f.getSize());
}
return JSON.toJSONString(it);
}).collect(Collectors.joining(","));
return "[" + params + "]";
}
}
LoggingAspect的作用
若依中的LoggingAspect是一个切面(Aspect),用于在应用程序中实现日志记录的功能。切面是面向切面编程(AOP)的核心组件之一,它可以在应用程序的不同层和模块中横切关注点(cross-cutting concern),如日志记录、性能监测、事务管理等。
LoggingAspect的作用是捕获应用程序中的方法调用,并记录相关的日志信息,例如方法的名称、参数值、返回值以及执行时间等。通过将日志记录的逻辑从业务逻辑中分离出来,LoggingAspect提供了一种非侵入式的方式来添加日志功能,而不需要在每个方法中显式地编写日志记录代码。
若依的LoggingAspect通常使用切面编程框架(如AspectJ)来实现,它可以通过注解或配置文件的方式定义切点(Pointcut),即要拦截的方法或类的范围。在切点匹配成功后,LoggingAspect会执行相应的日志记录操作,将相关信息输出到日志文件、控制台或其他目标。
通过使用LoggingAspect,开发人员可以方便地添加、修改和管理应用程序中的日志记录行为,提高代码的可维护性和可扩展性。同时,它也可以帮助开发人员快速定位和排查应用程序中的问题,例如追踪方法的调用流程、定位性能瓶颈等。
Pointcut的作用
在LoggingAspect中,@Pointcut注解用于定义切点(Pointcut),即要拦截的方法或类的范围。切点是切面(Aspect)中定义的一种表达式,它指定了在何处应该应用切面逻辑。
@Pointcut注解的作用有以下几个方面:
定义切点表达式:通过@Pointcut注解,开发人员可以使用特定的表达式语言(如AspectJ表达式语言)定义切点表达式,该表达式描述了要匹配的方法或类的模式。例如,可以使用通配符、正则表达式或类名/方法名的完全限定名来匹配相应的目标。
精确定位目标方法:通过切点表达式,可以精确地指定要拦截的方法或类。这使得开发人员可以有选择地将切面逻辑应用于特定的方法或类,而不是整个应用程序。
代码重用:使用@Pointcut注解可以定义多个切点,然后在不同的切面中重用这些切点。这样可以避免在每个切面中重复定义相同的切点表达式,提高代码的可维护性和可重用性。
提高代码可读性:通过使用@Pointcut注解,开发人员可以将切点表达式与切面逻辑分离,使得切面的代码更加清晰和易读。切点表达式描述了何处应该应用切面,而切面逻辑则描述了在切点处要执行的具体操作。
在logback.xml配置文件中添加 logger 元素
<logger name="com.ruoyi.framework.aspectj.LoggingAspect" level="info" >
在logback.xml配置文件中添加 <logger>
元素的作用是指定特定的日志记录级别(level)和对应的日志记录器(logger)。在给定的代码段中,<logger>
元素指定了名为 "com.ruoyi.framework.aspectj.LoggingAspect" 的日志记录器的级别为 "info"。
这段代码的目的是为若依框架中的 LoggingAspect
切面设置日志记录级别。切面是应用程序中实现横切关注点的组件,LoggingAspect 切面负责处理日志记录的逻辑。通过将日志记录级别设置为 "info",指定了该切面记录的日志消息的级别为 "info"。
日志记录级别决定了哪些日志消息会被记录和输出。常见的日志级别包括(从低到高):trace、debug、info、warn、error。设置日志记录级别为 "info" 表示只记录具有 "info" 级别及更高级别的日志消息,即 "info"、"warn" 和 "error" 级别的消息将被记录,而 "trace" 和 "debug" 级别的消息将被忽略。
通过将 LoggingAspect 切面的日志记录级别设置为 "info",可以确保仅记录重要的日志消息,避免过多的冗余日志信息。这有助于提高日志的可读性和过滤掉一些不必要的详细信息,使日志输出更加简洁和有意义。