Springboot日志记录方案

一、概述

对于一个完整的项目而言,通过日志可以随时观察系统运行情况,日志功能是必不可少的,平时开发项目的时候想知道程序运行情况一般可以使用sysout.print(),打印一些关键的代码或者通过debug查看运行状态,使用sysout.print()会出现代码多余,于是市场上了出现许多记录运行状态的框架。

二、日志框架

日志框架分为3部分:日志门面、日志适配器、日志库

日志门面:JCL、SLF4j、jboss-logging这些框架都是日志门面。门面设计模式是面向对象的一种设计模式,类似JDBC,也就是说这些货本身自己不干活,就是一套接口规范,让调用者不需要关心日志底层具体是什么框架在干活

日志库:也就是真实干活的人,几种常见的日志库

  • jdk 自带的 java.util.logging.Logger都是日志库

java.util.logging.Logger是JDK自带的日志工具类,从1.4版本开始就已经有了。由于log4j等开源的日志组件,这个Logger并没有太多展现机会。但在一些测试性的代码中,JDK自带的Logger比log4j等更方便。参考文章Java自带日志工具java.util.logging.Logger_peterwanghao的博客-CSDN博客

  • log4j、log4j2(Log4j2是Log4j升级版)

Log4j 是 Apache 的一个开源项目,通过使用 Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI 组件,甚至是套接口服务器、NT 的事件记录器、UNIX Syslog 守护进程等;我们也可以控 制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。 Log4j由三个重要的组成构成:日志记录器(Loggers),输出端(Appenders)和日志格式化器(Layout)。 1.Logger:控制要启用或禁用哪些日志记录语句,并对日志信息进行级别限制 2.Appenders : 指定了日志将打印到控制台还是文件中 3.Layout : 控制日志信息的显示格式 Log4j 中将要输出的 Log 信息定义了 5 种级别,依次为 DEBUG、INFO、WARN、ERROR 和 FATAL,当输出时,只有级别高过配置中规定的 级别的信息才能真正的输出,这样就 很方便的来配置不同情况下要输出的内容,而不需要更改代码。

  • logback、JUL(JUL太简陋了基本没人用)

简单地说,Logback 是一个 Java 领域的日志框架。它被认为是 Log4J 的继承人。 Logback 主要由三个模块组成:logback-core,logback-classic。logback-access logback-core 是其它模块的基础设施,其它模块基于它构建,显然,logback-core 提供了一些关键的通用机制。

logback-classic 的地位和作用等同于 Log4J,它也被认为是 Log4J 的一个改进版,并且它实现了简单日志门面 SLF4J; logback-access 主要作为一个与 Servlet 容器交互的模块,比如说 tomcat 或者 jetty,提供一些与HTTP 访问相关的功能。

Logback 优点

  •  同样的代码路径,Logback 执行更快 更充分的测试
  •  原生实现了 SLF4J API(Log4J 还需要有一个中间转换层)
  •  内容更丰富的文档
  • 支持 XML 或者 Groovy 方式配置
  •  配置文件自动热加载
  •  从 IO 错误中优雅恢复
  •  自动删除日志归档
  •  自动压缩日志成为归档文件
  •  支持 Prudent 模式,使多个 JVM 进程能记录同一个日志文件
  •  支持配置文件中加入条件判断来适应不同的环境
  •  更强大的过滤器
  •  支持 SiftingAppender(可筛选 Appender)
  •  异常栈信息带有包信息

日志适配器:它是解决日志门面和日志库接口不兼容的,一般配套的都是兼容的

所以我们的目标是挑选一个日志门面,再挑选一个日志库,搭配使用即可,在Springboot项目中整合日志有两套方案

  • slf4j+Logback
  • slf4j+Log4j2

三、slf4j+Logback

Springboot默认抽象接口层使用slf4j,实现层用logback,创建了一个demo项目当我们引入spring-boot-starter的时候,就默认帮我们引入了logbackslf4j

当我们创建一个boot项目,我们没有配置任何其它配置,就看到在控制台下打印日志,Logback默认打印debug级别日志,但是我们注意到debug级别的日志没有记录下来,那是因为Spring Boot为Logback提供了默认的配置文件,base.xml,另外Spring Boot 提供了两个输出端的配置文件console-appender.xml和file-appender.xml,base.xml引用了这两个配置文件。所以Springboot默认日志级别是info,可以看到base.xml引用两个配置,

以下是从git上找到spring-boot用的base.xml




	
	
	
	
	
		
		
	

在这里, 您可以看到 Spring boot已通过将根记录器设置为INFO来覆盖 Logback 的默认日志记录级别, 这是我们在上面的示例中没有看到调试消息的原因。

Springboot日志框架配置就相当讲究了,就比如我们公司要求,所有日志必须保存180天,每个文件最大50mb,超过的另外取一个文件。infowarnerror必须分开。毕竟跟学生项目不同,公司是有审计要求的,不能胡来,那么我就按这个标准配置一下

第一种:简单配置

小项目可以直接在application.properties中对logback简单配置,如下


#修改指定包下的日志输出级别
logging.level.com.javayihao=trace
#当前项目下生成mylog.log日志记录输出的日志
#logging.file=mylog.log
#在指定的路径下输出日志
#logging.file=f:/mylog.log
#当前的磁盘根路径下创建spring文件家和里面的log文件夹,以spring.log作为日志文件名字
logging.path=/spring/log
#指定控制台输出日志的格式
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS}+++[%thread] %-5level %logger{50} - %msg%n
#执行日志文件中输出日志的格式
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS}===[%thread] %-5level %logger{50} - %msg%n

第二种:通过logback专有的xml配置文件详细配置

通过application.properties文件配置Logback,对于大多数Spring Boot应用来说已经足够了,但是对于一些大型的企业应用来说似乎有一些相对复杂的日志需求。在Spring Boot中你可以在logback.xml或者在logback-spring.xml中对Logback进行配置,相对于logback.xml,logback-spring.xml更加被偏爱。如果是其他名字,只需在application.propertions

中配置logging.config=classpath:logback-boot.xml使用自己的日志配置文件即可

logback.xml结构

  
     
    ${glmapper-name} 
    
    
    
        //xxxx
       
    
    
        //xxxx
    
    
                 
       //xxxx
      
  

属性

  • scan:当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true。
  • scanPeriod:设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。当scan为true时,此属性生效。默认的时间间隔为1分钟。
  • debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。

property

用来定义变量值的标签,property标签有两个属性,namevalue;其中name的值是变量的名称,value的值时变量定义的值。通过property定义的值会被插入到logger上下文中。定义变量后,可以使“${name}”来使用变量。如上面的xml所示。

contextName

每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用contextName标签设置成其他名字,用于区分不同应用程序的记录

appender负责写日志的组件


    true
    
        ${logging.level}
    
    
        ${logging.path}/glmapper-spring-boot/glmapper-loggerone.log
    
    
        ${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd}
        30
    
    
        %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
        UTF-8
    

appender 有两个属性 nameclass;name指定appender名称,class指定appender的全限定名。上面声明的是名为GLMAPPER-LOGGERONEclassch.qos.logback.core.rolling.RollingFileAppender的一个appender

appender 的种类

  • ConsoleAppender:把日志添加到控制台
  • FileAppender:把日志添加到文件
  • RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件。它是FileAppender的子类

append 子标签

true

如果是 true,日志被追加到文件结尾,如果是false,清空现存文件,默认是true

filter 子标签

filter其实是appender里面的子元素。它作为过滤器存在,执行一个过滤器会有返回DENY,NEUTRAL,ACCEPT三个枚举值中的一个。appender 有多个过滤器时,按照配置顺序执行。

  • DENY:日志将立即被抛弃不再经过其他过滤器
  • NEUTRAL:有序列表里的下个过滤器过接着处理日志
  • ACCEPT:日志会被立即处理,不再经过剩余过滤器

filter的class有ThresholdFilter以及LevelFilter

ThresholdFilter

临界值过滤器,过滤掉低于指定临界值的日志。当日志级别等于或高于临界值时,过滤器返回NEUTRAL;当日志级别低于临界值时,日志会被拒绝。


    INFO

LevelFilter

级别过滤器,根据日志级别进行过滤。如果日志级别等于配置级别,过滤器会根据onMath(用于配置符合过滤条件的操作) 和 onMismatch(用于配置不符合过滤条件的操作)接收或拒绝日志。

   
  INFO   
  ACCEPT   
  DENY   
 

file 子标签

file 标签用于指定被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值。


    ${logging.path}/glmapper-spring-boot/glmapper-loggerone.log

这个表示当前appender将会将日志写入到${logging.path}/glmapper-spring-boot/glmapper-loggerone.log这个目录下。

rollingPolicy 子标签

这个子标签用来描述滚动策略的。这个只有appenderclassRollingFileAppender时才需要配置。这个也会涉及文件的移动和重命名(a.log->a.log.2018.07.22)。class有TimeBasedRollingPolicy和FixedWindowRollingPolicy

TimeBasedRollingPolicy

最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动。这个下面又包括了两个属性:

  • FileNamePattern
  • maxHistory

    
    
        ${logging.path}/glmapper-spring-boot/glmapper-loggerone.log.%d{yyyy-MM-dd}
    
    
    30

复制代码

上面的这段配置表明每天生成一个日志文件,保存30天的日志文件

FixedWindowRollingPolicy

根据固定窗口算法重命名文件的滚动策略。

encoder 子标签

对记录事件进行格式化。它干了两件事:

  • 把日志信息转换成字节数组
  • 把字节数组写入到输出流

    %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}
    - %msg%n
    UTF-8

目前encoder只有PatternLayoutEncoder一种类型。

appender案例

定义一个只打印error级别日志的appdener

 

    true
    
    
        error
    
    
    ${logging.path}/glmapper-spring-boot/glmapper-error.log
    
    
        
        ${logging.path}/glmapper-spring-boot/glmapper-error.log.%d{yyyy-MM-dd}
        
        30
    
    
        
        %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
        
        UTF-8
    

定义一个输出到控制台的appender



    
        %d{HH:mm:ss.SSS} %-5level %logger{80} - %msg%n
    

logger

用来设置某一个包或者具体的某一个类的日志打印级别以及指定appender

root

根logger,也是一种logger,且只有一个level属性

案例logback.xml

a):统一配置



    
    
    
    
        
            %d %p (%file:%line\)- %m%n
            
            UTF-8
        
    
    
    
    
    
    
        
        log/sys.log
        
        
        
            
            
            log/sys.%d.%i.log
            
            30
            
                
                10MB
            
        
        
            
            
                %d %p (%file:%line\)- %m%n
            
            
            UTF-8 
        
    
    
    
        
    
    
    
    
    
        
    

b):生产和测试环境分开配置

可以在application-dev.ymlapplication-pro.yml中分别指明不同环境的logback配置文件的目录,即可分开,也开使用一个xml文件



    
 
    
        
        
    
 
    
    
        
            
        
        
    
 
    
    
        
            
        
        
    

c:)提示信息和异常记录分开记录





    

    


    
    
        
            ${CONSOLE_LOG_PATTERN}
            
        
    


    
    
    
        
            INFO
            ACCEPT
            DENY
        
        
            ${CONSOLE_LOG_PATTERN}
        
        ${LOG_HOME}/info.log
        
            
            ${LOG_HOME}/achiveLog/info-%d{yyyy-MM-dd}_%i.log
            50MB
            
            180
        
    


    
    
        
            WARN
            ACCEPT
            DENY
        
        
            ${CONSOLE_LOG_PATTERN}
        
        ${LOG_HOME}/warn.log
        
            ${LOG_HOME}/achiveLog/warn-%d{yyyy-MM-dd}_%i.log
            50MB
            180
        
    

    
    
        
            ERROR
            ACCEPT
            DENY
        
        
            ${CONSOLE_LOG_PATTERN}
        
        ${LOG_HOME}/error.log
        
            ${LOG_HOME}/achiveLog/error-%d{yyyy-MM-dd}_%i.log
            50MB
            180
        
    

    
    
        
        
        
        
    

首先看一下根节点root,这个level="INFO"指的是日志登记,按日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,我们这边指定了INFO,也就是说DEBUG的日志是不会被输出出来的

根节点有4个直接点,挑一个做一下解释

    
    
        
            WARN
            ACCEPT
            DENY
        
        
            ${CONSOLE_LOG_PATTERN}
        
        ${LOG_HOME}/warn.log
        
            ${LOG_HOME}/achiveLog/warn-%d{yyyy-MM-dd}_%i.log
            50MB
            180
        
    

  • filter:用于过滤日志
  • level:匹配等级是WARN
  • onMatch:当匹配时ACCEPT
  • onMismatch:不匹配时DENY,不匹配的过滤掉

因此只过滤出levelwarn级别的日志,其他的日志信息经过这个节点会被过滤掉,也就不会进入到后续流程了。如果你不写onMatchonMismatch标签,那么error级别的因为大于warn,也会进入到后续流程

而我们说的后续流程就是下面的标签。这些标签指明过滤出来的数据应该放到控制台还是放到文件

  • file:这些过滤出来的数据需要被写入到文件中
  • fileNamePattern:文件的目录和命名格式warn-yyyy-mm-dd_1.log
  • maxFileSize:文件最大50MB
  • MaxHistory:保留18天

四、slf4j+Log4j2

1、引入pom



    org.springframework.boot
    spring-boot-starter-web
    
        
        
            org.springframework.boot
            spring-boot-starter-logging
        
    



    org.springframework.boot
    spring-boot-starter-log4j2
    2.2.6.RELEASE



    com.lmax
    disruptor
    3.4.2






    org.springframework.boot
    spring-boot-starter
    
        
        
            org.springframework.boot
            spring-boot-starter-logging
        
    



    org.springframework.boot
    spring-boot-starter-log4j2
    2.2.6.RELEASE



    com.lmax
    disruptor
    3.4.2

2、application.yml

# 引入日志配置文件
logging:
  config: classpath:log4j2.xml

3、log4j2.xml 放在目录 resources 下即可





    

    
    
        
        
        
        
        
        
    

    

        
            
            
        

        
        
            
            
                
                
                
            
            
            
                
                
                    
                    
                    
                    
                    
                
            
        
    

    
    
    
        
        
        
            
            
        
        
            
            
        
        
            
            
        
    

4、声明 logger 变量

package com.songo.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class TestService {
    private static final Logger logger = LoggerFactory.getLogger(TestService.class);
    
    public void Test() {
        logger.info("test...");
    }
}

5、附自己使用配置文件



    
        ${sys:LOG_PATH}/apps/manager
        ${sys:LOG_PATH}/apps/manager
    

    
        
        
            
        

        
            
                %d [%t] %-5p [%c] - %m%n
            
            
            
                
                
            
        

        
        
            
            
            
                %d [%t] %-5p [%c] - %m%n
            
            
                
                
            
        
        
        
            
                %m%n
            
            
                
                
            
        
    

    
        
        
        
        
        
        
        
        



        

        
            
            

            
            
        
    

 注意:对于AsyncLogger的name属性可以是指定的类,也可以是字符串

//指定类 需要打日志地方直接调用即可
public class LogUitl{
    public static final Logger log = LogManager.getLogger(LogUitl.class);
    /**
     * 记录
     */
    public static void record(String str) {
        log.info(str);
    }
}


//直接使用字符串  
public class Test{
//AsyncLogger的name属性也为testLog
    public static final Logger log = LogManager.getLogger("testLog");
    /**
     * 记录
     */
    public void test(String str) {
        log.info(str);
    }
}

五、Springboot记录日志方式

1、使用记录器logger记录

  1. 考虑好异常属于什么级别的异常,再打日志。想好是log.error还是log.warn

比如业务异常往往通过用户引导就可以恢复的,那么就只打WARN级别。而ERROR级别的日志一旦出现,就需要人工介入,因此要定期检查ERROR日志排查问题。这段是《阿里Java开发手册》写的

public class TestController{
   //记录器
   Logger logger = LoggerFactory.getLogger(getClass());
   @Test
   public void contextLoads() {
      //日志级别,由低到高,调整日志级别,只输出高级别日志,
      logger.trace("这是trace跟踪信息");
      logger.debug("这是debug调试信息");
      //springboot默认输出info以后的级别,也就是root级别
      logger.info("这是info自定义信息");
      logger.warn("这是warn警告信息");
      logger.error("这是error错误信息");
   }
}

看下logger.error代码就知道error有2个重载方法

public void error(String msg);
public void error(String msg, Throwable t);

上面的代码只有一个参数,因此都会被认为是调用第一种方法,这样造成的结果就是e将会被自动转成String类型,从而丢失的许多错误信息、堆栈信息,即不知道哪个方法调用了service产生的错误,也不知道错误的原因,更不知道代码抛出异常的行数。生产一旦出现问题,根本无从排起,

而正确的日志,我们即可以知道发生错误的原因,抛出异常的行数,同时也能获悉堆栈调用的关系,这样排查生产问题才有解决的可能性。

logger.error("x部分出错", e);

2、通过AOP思想记录日志

代码1:

@Aspect//切面
@Component//组件
public class LogAspect {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 这里把切点切再controller(web)层下的每个方法
     */
    @Pointcut("execution(* com.javayihao.myweb.controller.*.*(..))")
    public void log() {
    }


    /**
     * 执行com.javayihao.myweb.controller下所有的方法之前执行这个方法
     *  获取要记录的url ip 请求的方法 以及请求时候所带的参数
     * @param joinPoint
     */
    @Before("log()")
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURL().toString();
        String ip = request.getRemoteAddr();
        //类名.方法
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        //参数
        Object[] args = joinPoint.getArgs();
        //调用自己封装的对象
        RequestLog requestLog = new RequestLog(url, ip, classMethod, args);
        logger.info("Request : {}", requestLog);
    }

    @After("log()")
    public void doAfter() {
//        logger.info("--------doAfter--------");
    }

    //执行com.javayihao.myweb.controller下所有的方法之后要执行的方法
    @AfterReturning(returning = "result",pointcut = "log()")
    public void doAfterRuturn(Object result) {
        logger.info("Result : {}", result);
    }

    private class RequestLog {
        private String url;
        private String ip;
        private String classMethod;
        private Object[] args;

        public RequestLog(String url, String ip, String classMethod, Object[] args) {
            this.url = url;
            this.ip = ip;
            this.classMethod = classMethod;
            this.args = args;
        }

        @Override
        public String toString() {
            return "{" +
                    "url='" + url + '\'' +
                    ", ip='" + ip + '\'' +
                    ", classMethod='" + classMethod + '\'' +
                    ", args=" + Arrays.toString(args) +
                    '}';
        }
    }

}

代码2:

@Aspect
@Component
public class LogAopAspect {

    private static final Logger logger = LoggerFactory.getLogger(LogAopAspect.class);


    @Around("execution(* company.controller.*.*(..))")
    public Object process(ProceedingJoinPoint pjp) throws Throwable {

        Class currentClass = pjp.getTarget().getClass();
        MethodSignature signature = (MethodSignature) (pjp.getSignature());
        String ClassName = currentClass.getSimpleName();
        String methodName = currentClass.getMethod(signature.getName(), signature.getParameterTypes()).getName();
        logger.info("======= 开始执行:" + ClassName + " — " + methodName + " ========");
        Object obj = pjp.proceed();
        logger.info("======= 执行结束:" + ClassName + " — " + methodName + " ========");
        return obj;
    }
}

六、如何使用其他日志框架 

项目中如果要记录日志,不应该直接使用一个日志实现类记录,而是通过一个日志抽象层+一个日志抽象实现类,然后调用抽象层里面的接口记录接口,如下使用日志抽象类SLF4j

Springboot日志记录方案_第1张图片

boot就是这么做的,这里主要总结一下日志抽象类SLF4j和其他日志实现类的使用

官网:SLF4J

使用方法,官网的一张图说的很明白

Springboot日志记录方案_第2张图片

问题:x系统使用Spring(commons-logging)、Hibernate(jboss-logging)、MyBatis、xxxx框架实现,每个框架有不同的日志记录框架,如何统一使用都使用slf4j

Springboot日志记录方案_第3张图片

解决方法

1、将系统中其他日志框架先排除出去;
2、用中间包来替换原有的日志框架;
3、我们导入slf4j其他的实现

比如我们要使用slf4j+log4j的方式来记录日志,依赖如下

再如我们要使用slf4j+log4j2的方式来记录日志,依赖如下


org.springframework.boot
spring‐boot‐starter‐web


spring‐boot‐starter‐logging
org.springframework.boot




org.springframework.boot
spring‐boot‐starter‐log4j2

但是在boot项目中不推荐slf4j+log4j2或者slf4j+log4j组合来记录日志,springboot官方推荐slf4+logback

你可能感兴趣的:(#,Springboot企业级开发,spring,boot,java,apache)