techiall / Blog

🍋 [My Blog] See discussions
https://github.com/techiall/Blog/discussions
MIT License
8 stars 1 forks source link

Spring 日志切面(Aspect) #64

Open techiall opened 4 years ago

techiall commented 4 years ago

背景

生产环境上出现了问题,但线上日志的等级一般是 INFO ,错误日志没有打印出来。

不知道内部是怎么调用的,这个时候,怎么办?

发个版本,把所有函数的入口加上日志,返回值也打印出来。

发布后,让测试再次测试,打开日志,发现问题,改完重新发布 ……

日志统一打印怎么解决,不可能要求项目里面所有人都在在函数的输入和返回值打印出来结果,怎么办?利用 Spring Aspect 即可。

依赖

先添加 Spring Aop 依赖。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

代码

假设我们的包为 top.techial.knowledge

我们需要查看这个包下加了 @Repository@Service@RestController 注解的类内函数 参数返回值

使用 @Around("applicationPackagePointcut() && springBeanPointcut()") 进行环绕。

@Log4j2 是 lombok 中的一个注解。

设置打印出来参数的日志等级为 DEBUG


import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Aspect
@Log4j2
@Component
public class LoggingAspect {

    @Pointcut("within(@org.springframework.stereotype.Repository *)" +
        " || within(@org.springframework.stereotype.Service *)" +
        " || within(@org.springframework.web.bind.annotation.RestController *)")
    public void springBeanPointcut() {
    }

    @Pointcut("within(top.techial.knowledge..*)")
    public void applicationPackagePointcut() {
    }

    @AfterThrowing(pointcut = "applicationPackagePointcut() && springBeanPointcut()", throwing = "e")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
        if (log.isErrorEnabled()) {
            log.error(
                "Exception in {}.{}() with cause = '{}' and exception = '{}'",
                joinPoint.getSignature().getDeclaringTypeName(),
                joinPoint.getSignature().getName(), e.getCause() != null ? e.getCause() : "NULL", e.getMessage(),
                e
            );
        }
    }

    @Around("applicationPackagePointcut() && springBeanPointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        if (log.isDebugEnabled()) {
            log.debug("Enter: {}.{}() with argument[s] = {}", joinPoint.getSignature().getDeclaringTypeName(),
                joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs()));
        }
        try {
            Object result = joinPoint.proceed();
            if (log.isDebugEnabled()) {
                log.debug("Exit: {}.{}() with result = {}", joinPoint.getSignature().getDeclaringTypeName(),
                    joinPoint.getSignature().getName(), result);
            }
            return result;
        } catch (IllegalArgumentException e) {
            if (log.isErrorEnabled()) {
                log.error("Illegal argument: {} in {}.{}()", Arrays.toString(joinPoint.getArgs()),
                    joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
            }
            throw e;
        }
    }
}

线上错误定位

有了这个 日志切面,我们再配合 Spring Actuator,actuator 可以在不重启的情况下修改日志的等级。

依赖如下:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

application.yml 添加如下配置:

# spring actuator
management:
  server:
    port: 8080
  endpoint:
    health:
      show-details: always
  endpoints:
    enabled-by-default: true
    web:
      base-path: /actuator
      exposure:
        include:
          - loggers
          - health
          - info

我们就可以通过发送请求 查看 / 修改 日志输出等级,以 curl 为例,其他的以此类推。

再也不用线上出问题,打包加日志!!!

真香。

techiall commented 4 years ago

其实有更香的东西,推荐 https://alibaba.github.io/arthas/