SpringBoot系列(十三)统一日志处理,logback+slf4j AOP+自定义注解,走起!

往期精彩推荐

SpringBoot系列(一)idea新建Springboot项目

SpringBoot系列(二)入门知识

springBoot系列(三)配置文件详解

SpringBoot系列(四)web静态资源配置详解

SpringBoot系列(五)Mybatis整合完整详细版

SpringBoot系列(六)集成thymeleaf详解版

Springboot系列(七) 集成接口文档swagger,使用,测试

SpringBoot系列(八)分分钟学会Springboot多种解决跨域方式

SpringBoot系列(九)单,多文件上传的正确姿势

SpringBoot系列(十)优雅的处理统一异常处理与统一结果返回

SpringBoot系列(十一)拦截器与拦截器链的配置与使用详解,你知道多少?

SpringBoot系列(十二)过滤器配置详解

本文目录

一、SpringBoot中的日志

 在我们运行项目的时候,你会发现控制台是有日志打印的,这个日志就是SpringBoot默认配置的日志框架处理的。SpringBoot默认是运用logback+slf4j处理日志,slf4j是抽象层,logback是实现层。

 但是不同的框架可能会有不同日志处理方式,如果我们在SpringBoot中集成了不同的框架的话,是不是日志的输出也会混乱呢?很显然,如果你有一点经验的话,你会发现,只要你不修改SpringBoot的默认日志配置,它的日志输出格式是不会变得。这是因为,在SpringBoot管理日志的时候,它都将其他框架的日志通过一些中间包的形式将其他的日志抽象成了slf4j接口,而统一用logback的形式实现。

 本文我们来讲讲怎么来配置日志格式以及运用AOP+自定义注解简化日志的记录。

二、自定义日志常用配置

1. 日志输出级别

 SpringBoot中默认的日志输出级别是info,也就是说我们平常在控制台输出的那些日志都是info级别以及更高级别的日志。我们可以自己定义日志的输出级别,一般有以下几个级别:

trace,debug,info,warn,error  //级别递增

 trace 是追踪日志,debug是调式日志,info一般是自定义日志或者是信息日志,warn是警告日志,error则是错误日志。

 可能这么说你也不知道这个级别有什么用,来看看这个代码:

@RestController
public class TestLogController {

    Logger logger = LoggerFactory.getLogger(TestLogController.class);

    @GetMapping("/testLog")
    public void testLog(){
        logger.trace("这是trace级别的日志");
        logger.debug("这是debug级别的日志");
        logger.info("这是正常自定义日志");
        logger.warn("这是警告日志");
        logger.error("这是错误日志");
    }
}

代码说明:(上面的Logger包这里是使用的org.slf4j.Logger)

 首先我们获取一个日志记录器Logger对象,然后分别在代码中记录不同级别日志的输出。运行项目,然后访问接口。

 你会发现前面的trace日志和debug日志是不会输出的,这你就知道了吧,不同等级的日志有不同的功效,只会在特定的情况下输出。这时候我们也可以自定义日志级别了,在配置文件(yml)

logging:
  level:
    com:
      example: 
        demolog: debug

配置说明:

 这是什么意思呢?我的包名是com.example.demolog,所以说这个配置就是说配置日志所在包的输出级别,是不是很高级。这样就能输出debug日志了。如果你想输出trace日志你就将等级设置为trace就行了。

2. 日志输出到文件

 日志输出到控制台查看起来不是很方便,怎么办?没关系,SpringBoot中还能将日志输出到指定的文件中,yml,添加如下配置。

logging:
  file:
    path: /spring/test/

 这个配置是说将日志输出到指定的目录文件,并且会生成一个spring.log的日志文件用来记录日志(如果你自己指定了文件名,它就会按照你自己设定的名字生成文件。),运行项目你就能直接看到生成的日志所在,这个目录如果你写的和上面一致,那么你的日志文件就会在项目的运行根路径,比如D盘,然后在D盘生成你写的文件目录/spring/test/,最后在文件目录下面生成spring.log的日志文件。这个路径你也可以直接写绝对路径(直接指定这个文件在那个盘,那个文件夹)。

 file下面还有一个配置就是name属性,

logging:
  file:
    name: test.log

 这个是直接指定你的日志的文件名称,默认生成的位置是在项目所在的目录,你也可以自己写绝对路径配置日志文件的位置,但是必须要自己设定文件的名称。

 file的name属性和path属性只能指定一个,如果两个同时指定的话,只有name属性会生效。

3. 自定义日志输出格式

 有时候你可能会觉得这个日志的输出格式太难看了,想自己定义一个日志输出格式,完全ojbk!SpringBoot说:满足你,自己想怎么玩就怎么玩!

logging:
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss}----- 这是全栈学习笔记 [%thread] %-5level %logger{50} - %msg%n"
    file: "%d{yyyy-MM-dd HH:mm:ss}----- 这是全栈学习笔记 [%thread] %-5level %logger{50} - %msg%n"

#  d表示日期时间,        
#  %thread表示线程名,        
#  %‐5level:级别从左显示5个字符宽度        
#  %logger{50} 表示logger名字最长50个字符,否则按照句点分割。  
#  %msg:日志消息,        
#  %n是换行符

 上面的配置分别定义了控制台的日志输出格式与文件的日志输出格式,是不是很方便。输出的格式大概就是这样。

 当然我们还有一个更好的日志配置,利用xml文件进行配置,一步到位就是这么爽。

三、xml文件实现日志配置的方式

 直接上xml文件的内容,建议将文件命名为logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--
scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒当scan为true时,此属性生效。默认的时间间隔为1分钟。
debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。
-->
<configuration scan="false" scanPeriod="60 seconds" debug="false">
    <!-- 定义日志的根目录   建议写绝对路径   如果不写默认在项目运行的根路径( D盘,C盘这种)-->
    <property name="LOG_HOME" value="D:/app/log" />
    <!-- 定义日志文件名称 -->
    <property name="appName" value="testxml"></property>
    <!-- ch.qos.logback.core.ConsoleAppender 表示控制台输出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <!--
        日志输出格式:
			%d表示日期时间,
			%thread表示线程名,
			%-5level:级别从左显示5个字符宽度
			%logger{50} 表示logger名字最长50个字符,否则按照句点分割。
			%msg:日志消息,
			%n是换行符
        -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} --学习笔记--[%thread] %-5level %logger{50} - %msg%n</pattern>
<!--            <springProfile name="dev">-->
<!--                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} == 学习笔记 == [%thread] -&ndash;&gt; %-5level %logger{50} - %msg%n</pattern>-->
<!--            </springProfile>-->
<!--            <springProfile name="prod">-->
<!--                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS}  ==学习笔记==[%thread] %-5level %logger{50} - %msg%n</pattern>-->
<!--            </springProfile>-->

<!--            如果将这个文件的名字改成logback-spring.xml   就可以使用上面的功能,
                上面的功能是说可以根据不同的生产环境做不同的日志打印
-->
        </layout>
    </appender>

    <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
    <appender name="appLogAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 指定日志文件的名称 -->
        <file>${LOG_HOME}/${appName}.log</file>
        <!--
        当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名
        TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。
        -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--
            滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
            %i:当文件大小超过maxFileSize时,按照i进行文件滚动
            -->
            <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <!--
            可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件。假设设置每天滚动,
            且maxHistory是365,则只保存最近365天的文件,删除之前的旧文件。注意,删除旧文件是,
            那些为了归档而创建的目录也会被删除。
            -->
            <MaxHistory>365</MaxHistory>
            <!--
            当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 注意此处配置SizeBasedTriggeringPolicy是无法实现按文件大小进行滚动的,必须配置timeBasedFileNamingAndTriggeringPolicy
            -->
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <!-- 日志输出格式: -->
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%n</pattern>
        </layout>
    </appender>

    <!--
		logger主要用于存放日志对象,也可以定义日志类型、级别
		name:表示匹配的logger类型前缀,也就是包的前半部分
		level:要记录的日志级别,包括 TRACE < DEBUG < INFO < WARN < ERROR
		additivity:作用在于children-logger是否使用 rootLogger配置的appender进行输出,
		false:表示只用当前logger的appender-ref,true:
		表示当前logger的appender-ref和rootLogger的appender-ref都有效
    -->
    <!-- hibernate logger -->
    <logger name="com.example.demolog" level="debug" ></logger>
    <!-- Spring framework logger -->
    <logger name="org.springframework" level="debug" additivity="false"></logger>



    <!--
    root与logger是父子关系,没有特别定义则默认为root,任何一个类只会和一个logger对应,
    要么是定义的logger,要么是root,判断的关键在于找到这个logger,然后判断这个logger的appender和level。
    -->
    <root level="info">
        <appender-ref ref="stdout" />
        <appender-ref ref="appLogAppender" />
    </root>
</configuration>

 上面的xml配置文件配置就不细说了,里面都有详细的注释说明。配置文件默认位置应该是直接放在resources下面,和yml,properties文件同级,当然你也可以自己配置文件位置的。

logging
  config: classpath:static/logback.xml

 这样就将xml配置文件放在static路径下面时能自动识别了。也可以设置为绝对路径。

 上面我们建议将日志文件设置为logback-spring.xml,如果我们的xml文件的名称是logback.xml,它就会直接被日志框架识别,如果你的xml文件是用logback-spring.xml命名,那么他会被SpringBoot来识别并解析日志配置,可以使用SpringBoot的高级Profile功能。这个高级功能我在xml文件中有注释说明。往上看。你也可以去看Spring的官网,有详细的配置说明。

四、AOP + 自定义注解实现统一日志处理

 引入aop依赖:

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

 自定义注解,还不会注解的,看这里,注解详细介绍注解干货

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
    String value() default "";
}

 自定义切面类:


@Aspect
@Component
public class LogAspect {
    private Logger logger = LoggerFactory.getLogger(LogAspect.class);

    @Pointcut("@annotation(com.example.demolog.annotation.MyLog)")
    public void myPointCut(){
        //签名,可以理解成这个切入点的一个名称
    }
    @Before("myPointCut()")
    public void doBefore(JoinPoint joinPoint){
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //获取url,请求方法,ip地址,类名以及方法名,参数
        logger.info("url={},method={},ip={},class_method={},args={}", request.getRequestURI(),request.getMethod(),request.getRemoteAddr(),joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName(),joinPoint.getArgs());

    }
    @AfterReturning(pointcut = "myPointCut()")
    public void printLog(JoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        MyLog myLog = method.getAnnotation(MyLog.class);
        String value = null;
        if (myLog!=null){
            value = myLog.value();
        }
        logger.info(new Date()+"-----"+value);
    }
}

 上面配置完成之后再去controller的方法之上添加一个自定义的 @Mylog注解

@GetMapping("/testLog")
@MyLog("测试一个日志")
public void testLog(){
    //和上面的一致
}

代码说明:

  • @Aspect:标明这是一个切面类

  • @Component:标明这是一个bean

  • @Pointcut("@annotation(com.example.demolog.annotation.MyLog)") 定义切入点为自定义的注解,也可以是一个类或者是一个包,包的写法如下:

@Pointcut("execution(public * com.example.demolog.*(..))") 

 上面的意思是切入点是 所有在com.example,demolog包下面的以public为修饰,不限制返回值(*),不限制参数不限制名称的类。

扩展知识:

  • @befor:前置通知,在一个方法执行之前被调用。
  • @after:在方法执行之后调用的通知,无论方法执行是否成功。
  • @after-returning:仅当方法成功完成之后通知。
  • @after-throwing:在方法抛出异常退出时执行的通知。
  • @around:在方法执行之前和之后调用的通知。

 然后我们再来测试一下接口:localhost:8098/testLog

本期分享到此结束,总结一下下!

五、总结

 本文先讲解SpringBoot的默认日志配置,然后自己在配置文件配置日志的输出等级,输出格式,将日志输出到文件中,然后通过xml文件来配置日志。最后我们引出了利用aop,简化日志的输出,并且统一日志的输出格式。如果你觉得本文有用的话,点个赞吧!另外需要源码的看下面。

posted @ 2020-05-07 09:30  全栈学习笔记  阅读(...)  评论(...编辑  收藏