暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

SpringBoot:自动打印日志

风尘博客 2019-08-24
266


在项目开发中,日志系统是必不可少的,用 AOP
在Web的请求做入参和出参的参数打印,同时对异常进行日志打印,避免重复的手写日志,完整案例见文末源码。

一、 SpringAOP

AOP
(Aspect-Oriented Programming,面向切面编程),它利用一种"横切"的技术,将那些多个类的共同行为封装到一个可重用的模块。便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

AOP
中有以下概念:

  • Aspect
    (切面):声明类似于 Java
    中的类声明,在 Aspect
    中会包含一些Pointcut及相应的Advice。

  • Jointpoint
    (连接点):表示在程序中明确定义的点。包括方法的调用、对类成员的访问等。

  • Pointcut
    (切入点):表示一个组 Jointpoint
    ,如方法名、参数类型、返回类型等等。

  • Advice
    (通知): Advice
    定义了在 Pointcut
    里面定义的程序点具体要做的操作,它通过( before
    、 around
    、 after
    return
    、 throw
    )、 finally
    来区别是在每个 Joint
     point
    之前、之后还是执行 前后要调用的代码。

  • Before
    :在执行方法前调用 Advice
    ,比如请求接口之前的登录验证。

  • Around
    :在执行方法前后调用 Advice
    ,这是最常用的方法。

  • After
    :在执行方法后调用 Advice
    , after
    、 return
    是方法正常返回后调用, after\throw
    是方法抛出异常后调用。

  • Finally
    :方法调用后执行 Advice
    ,无论是否抛出异常还是正常返回。

  • AOP proxy
    : AOP proxy
    也是 Java
    对象,是由 AOP
    框架创建,用来完成上述动作, AOP
    对象通常可以通过 JDKdynamicproxy
    完成,或者使用 CGLIb
    完成。

  • Weaving
    :实现上述切面编程的代码织入,可以在编译时刻,也可以在运行时刻, Spring
    和其它大多数Java框架都是在运行时刻生成代理。

二、项目示例

当然,在使用该案例之前,如果需要了解日志配置相关,可参考 SpringBoot 异步输出 Logback 日志, 本文就不再概述了。

2.1 在pom引入依赖

  1. <dependencies>

  2. <dependency>

  3. <groupId>org.springframework.boot</groupId>

  4. <artifactId>spring-boot-starter-web</artifactId>

  5. </dependency>


  6. <dependency>

  7. <groupId>org.springframework.boot</groupId>

  8. <artifactId>spring-boot-starter-aop</artifactId>

  9. </dependency>

  10. <!-- 分析客户端信息的工具类-->

  11. <dependency>

  12. <groupId>eu.bitwalker</groupId>

  13. <artifactId>UserAgentUtils</artifactId>

  14. <version>1.20</version>

  15. </dependency>

  16. <!-- lombok -->

  17. <dependency>

  18. <groupId>org.projectlombok</groupId>

  19. <artifactId>lombok</artifactId>

  20. <scope>1.8.4</scope>

  21. </dependency>

  22. </dependencies>

2.2 Controller
 切面: WebLogAspect

  1. @Aspect

  2. @Component

  3. @Slf4j

  4. public class WebLogAspect {


  5. /**

  6. * 进入方法时间戳

  7. */

  8. private Long startTime;

  9. /**

  10. * 方法结束时间戳(计时)

  11. */

  12. private Long endTime;


  13. public WebLogAspect() {

  14. }



  15. /**

  16. * 定义请求日志切入点,其切入点表达式有多种匹配方式,这里是指定路径

  17. */

  18. @Pointcut("execution(public * cn.van.log.aop.controller.*.*(..))")

  19. public void webLogPointcut() {

  20. }


  21. /**

  22. * 前置通知:

  23. * 1. 在执行目标方法之前执行,比如请求接口之前的登录验证;

  24. * 2. 在前置通知中设置请求日志信息,如开始时间,请求参数,注解内容等

  25. *

  26. * @param joinPoint

  27. * @throws Throwable

  28. */

  29. @Before("webLogPointcut()")

  30. public void doBefore(JoinPoint joinPoint) {


  31. // 接收到请求,记录请求内容

  32. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

  33. HttpServletRequest request = attributes.getRequest();

  34. //获取请求头中的User-Agent

  35. UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));

  36. //打印请求的内容

  37. startTime = System.currentTimeMillis();

  38. log.info("请求开始时间:{}" + LocalDateTime.now());

  39. log.info("请求Url : {}" + request.getRequestURL().toString());

  40. log.info("请求方式 : {}" + request.getMethod());

  41. log.info("请求ip : {}" + request.getRemoteAddr());

  42. log.info("请求方法 : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());

  43. log.info("请求参数 : {}" + Arrays.toString(joinPoint.getArgs()));

  44. // 系统信息

  45. log.info("浏览器:{}", userAgent.getBrowser().toString());

  46. log.info("浏览器版本:{}", userAgent.getBrowserVersion());

  47. log.info("操作系统: {}", userAgent.getOperatingSystem().toString());

  48. }


  49. /**

  50. * 返回通知:

  51. * 1. 在目标方法正常结束之后执行

  52. * 1. 在返回通知中补充请求日志信息,如返回时间,方法耗时,返回值,并且保存日志信息

  53. *

  54. * @param ret

  55. * @throws Throwable

  56. */

  57. @AfterReturning(returning = "ret", pointcut = "webLogPointcut()")

  58. public void doAfterReturning(Object ret) throws Throwable {

  59. endTime = System.currentTimeMillis();

  60. log.info("请求结束时间:{}" + LocalDateTime.now());

  61. log.info("请求耗时:{}" + (endTime - startTime));

  62. // 处理完请求,返回内容

  63. log.info("请求返回 : {}" + ret);

  64. }


  65. /**

  66. * 异常通知:

  67. * 1. 在目标方法非正常结束,发生异常或者抛出异常时执行

  68. * 1. 在异常通知中设置异常信息,并将其保存

  69. *

  70. * @param throwable

  71. */

  72. @AfterThrowing(value = "webLogPointcut()", throwing = "throwable")

  73. public void doAfterThrowing(Throwable throwable) {

  74. // 保存异常日志记录

  75. log.error("发生异常时间:{}" + LocalDateTime.now());

  76. log.error("抛出异常:{}" + throwable.getMessage());

  77. }

  78. }

2.3 编写测试

  1. @RestController

  2. @RequestMapping("/log")

  3. public class LogbackController {


  4. /**

  5. * 测试正常请求

  6. * @param msg

  7. * @return

  8. */

  9. @GetMapping("/{msg}")

  10. public String getMsg(@PathVariable String msg) {

  11. return "request msg : " + msg;

  12. }


  13. /**

  14. * 测试抛异常

  15. * @return

  16. */

  17. @GetMapping("/test")

  18. public String getException(){

  19. // 故意造出一个异常

  20. Integer.parseInt("abc123");

  21. return "success";

  22. }

  23. }

2.4 @Before
和 @AfterReturning
部分也可使用以下代码替代

  1. /**

  2. * 在执行方法前后调用Advice,这是最常用的方法,相当于@Before和@AfterReturning全部做的事儿

  3. * @param pjp

  4. * @return

  5. * @throws Throwable

  6. */

  7. @Around("webLogPointcut()")

  8. public Object doAround(ProceedingJoinPoint pjp) throws Throwable {

  9. // 接收到请求,记录请求内容

  10. ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

  11. HttpServletRequest request = attributes.getRequest();

  12. //获取请求头中的User-Agent

  13. UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));

  14. //打印请求的内容

  15. startTime = System.currentTimeMillis();

  16. log.info("请求Url : {}" , request.getRequestURL().toString());

  17. log.info("请求方式 : {}" , request.getMethod());

  18. log.info("请求ip : {}" , request.getRemoteAddr());

  19. log.info("请求方法 : " , pjp.getSignature().getDeclaringTypeName() , "." , pjp.getSignature().getName());

  20. log.info("请求参数 : {}" , Arrays.toString(pjp.getArgs()));

  21. // 系统信息

  22. log.info("浏览器:{}", userAgent.getBrowser().toString());

  23. log.info("浏览器版本:{}",userAgent.getBrowserVersion());

  24. log.info("操作系统: {}", userAgent.getOperatingSystem().toString());

  25. // pjp.proceed():当我们执行完切面代码之后,还有继续处理业务相关的代码。proceed()方法会继续执行业务代码,并且其返回值,就是业务处理完成之后的返回值。

  26. Object ret = pjp.proceed();

  27. log.info("请求结束时间:"+ LocalDateTime.now());

  28. log.info("请求耗时:{}" , (System.currentTimeMillis() - startTime));

  29. // 处理完请求,返回内容

  30. log.info("请求返回 : " , ret);

  31. return ret;

  32. }

三、 测试

3.1 请求入口 LogbackController.java

  1. @RestController

  2. @RequestMapping("/log")

  3. public class LogbackController {


  4. /**

  5. * 测试正常请求

  6. * @param msg

  7. * @return

  8. */

  9. @GetMapping("/normal/{msg}")

  10. public String getMsg(@PathVariable String msg) {

  11. return msg;

  12. }


  13. /**

  14. * 测试抛异常

  15. * @return

  16. */

  17. @GetMapping("/exception/{msg}")

  18. public String getException(@PathVariable String msg){

  19. // 故意造出一个异常

  20. Integer.parseInt("abc123");

  21. return msg;

  22. }

  23. }

3.2 测试正常请求

打开浏览器,访问http://localhost:8082/log/normal/hello

日志打印如下:

  1. [2019-02-24 22:37:50.050] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-1] [65] [INFO ] 请求开始时间:2019-02-24T22:37:50.892

  2. [2019-02-24 22:37:50.050] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-1] [66] [INFO ] 请求Url : http://localhost:8082/log/normal/hello

  3. [2019-02-24 22:37:50.050] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-1] [67] [INFO ] 请求方式 : GET

  4. [2019-02-24 22:37:50.050] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-1] [68] [INFO ] 请求ip : 0:0:0:0:0:0:0:1

  5. [2019-02-24 22:37:50.050] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-1] [69] [INFO ] 请求方法 :

  6. [2019-02-24 22:37:50.050] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-1] [70] [INFO ] 请求参数 : [hello]

  7. [2019-02-24 22:37:50.050] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-1] [72] [INFO ] 浏览器:CHROME

  8. [2019-02-24 22:37:50.050] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-1] [73] [INFO ] 浏览器版本:76.0.3809.100

  9. [2019-02-24 22:37:50.050] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-1] [74] [INFO ] 操作系统: MAC_OS_X

  10. [2019-02-24 22:37:50.050] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-1] [88] [INFO ] 请求结束时间:2019-02-24T22:37:50.901

  11. [2019-02-24 22:37:50.050] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-1] [89] [INFO ] 请求耗时:14

  12. [2019-02-24 22:37:50.050] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-1] [91] [INFO ] 请求返回 : hello

3.3 测试异常情况

访问:http://localhost:8082/log/exception/hello

  1. [2019-02-24 22:39:57.057] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-9] [65] [INFO ] 请求开始时间:2019-02-24T22:39:57.728

  2. [2019-02-24 22:39:57.057] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-9] [66] [INFO ] 请求Url : http://localhost:8082/log/exception/hello

  3. [2019-02-24 22:39:57.057] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-9] [67] [INFO ] 请求方式 : GET

  4. [2019-02-24 22:39:57.057] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-9] [68] [INFO ] 请求ip : 0:0:0:0:0:0:0:1

  5. [2019-02-24 22:39:57.057] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-9] [69] [INFO ] 请求方法 :

  6. [2019-02-24 22:39:57.057] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-9] [70] [INFO ] 请求参数 : [hello]

  7. [2019-02-24 22:39:57.057] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-9] [72] [INFO ] 浏览器:CHROME

  8. [2019-02-24 22:39:57.057] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-9] [73] [INFO ] 浏览器版本:76.0.3809.100

  9. [2019-02-24 22:39:57.057] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-9] [74] [INFO ] 操作系统: MAC_OS_X

  10. [2019-02-24 22:39:57.057] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-9] [104] [ERROR] 发生异常时间:2019-02-24T22:39:57.731

  11. [2019-02-24 22:39:57.057] [cn.van.log.aop.aspect.WebLogAspect] [http-nio-8082-exec-9] [105] [ERROR] 抛出异常:For input string: "abc123"

  12. [2019-02-24 22:39:57.057] [org.apache.juli.logging.DirectJDKLog] [http-nio-8082-exec-9] [175] [ERROR] Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NumberFormatException: For input string: "abc123"] with root cause

  13. java.lang.NumberFormatException: For input string: "abc123"

四、源码

4.1 示例代码

https://github.com/vanDusty/SpringBoot-Home/tree/master/springboot-demo-logback/log-aop

4.2 技术交流

  1. 更多内容,欢迎关注风尘博客 - https://www.dustyblog.cn

  2. 技术交流,扫一扫



文章转载自风尘博客,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论