跳至主要內容

若依源码解析:用LoggingAspect进行日志处理

程序员诚哥大约 6 分钟若依若依后端源码解析

摘要

本文将深入探讨若依框架中的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注解的作用有以下几个方面:

  1. 定义切点表达式:通过@Pointcut注解,开发人员可以使用特定的表达式语言(如AspectJ表达式语言)定义切点表达式,该表达式描述了要匹配的方法或类的模式。例如,可以使用通配符、正则表达式或类名/方法名的完全限定名来匹配相应的目标。

  2. 精确定位目标方法:通过切点表达式,可以精确地指定要拦截的方法或类。这使得开发人员可以有选择地将切面逻辑应用于特定的方法或类,而不是整个应用程序。

  3. 代码重用:使用@Pointcut注解可以定义多个切点,然后在不同的切面中重用这些切点。这样可以避免在每个切面中重复定义相同的切点表达式,提高代码的可维护性和可重用性。

  4. 提高代码可读性:通过使用@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",可以确保仅记录重要的日志消息,避免过多的冗余日志信息。这有助于提高日志的可读性和过滤掉一些不必要的详细信息,使日志输出更加简洁和有意义。

上次编辑于:
贡献者: zccbbg