Logback和Log4j详解

日志框架

前言

​ 在程序中写日志是一件非常重要,但是很容易被开发人员忽视的事情。程序中好的日志可以帮助我们大大减轻后期维护压力。在实际的工作中,开发人员往往迫于巨大时间压力,而写日志又是一个非常繁琐的事情,往往没有引起足够的重视。开发人员应在一开始就养成良好的日志撰写习惯,并且应在实际的开发工作中为写日志预留足够的时间。

一、日志简介

1、什么是日志?

简单的说,日志就是记录程序的运行轨迹,方便查找关键信息,也方便快速定位解决问题。

2、日志的用途?

  1. 记录用户操作的审计日志,甚至有的时候就是监管部门的要求

  2. 快速定位问题的根源

  3. 追踪程序执行的过程

  4. 追踪数据的变化

  5. 数据统计和性能分析

  6. 采集运行环境数据

    一般在程序上线之后,一旦发生异常,第一件事就是要弄清楚当时发生了什么。用户当时做了什么操作,环境有无影响,数据有什么变化,是不是反复发生等,然后再进一步的确定大致是哪个方面的问题。确定是程序的问题之后再交由开发人员去重现、研究、提出解决方案。这时,日志就给我们提供了第一手的资料。

二、常用的日志框架

1、日志门面

日志门面其实就是日志框架的接口,不同的日志框架可以实现同一个框架接口,在我们更换日志框架的时候只要换了jar包就行。遵循了设计原则中的依赖倒置原则。

常见的日志门面:slf4j(主流)、commons-logging

slf4j的实现框架:log4j和logback

日志级别:TRACE, DEBUG, INFO, WARN, ERROR,遵循就近原则。

  • 注:我们在项目中导入包的时候一定要导入日志门面对应的接口类,不要使用实现类。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0nCr01BZ-1603758455711)(C:\Users\smilevers\AppData\Roaming\Typora\typora-user-images\image-20201009095946899.png)]

2、logback(更快的执行速度使其成为主流)

推荐阅读官方文档:[](http://www.logback.cn/02%E7%AC%AC%E4%BA%8C%E7%AB%A0%E6%9E%B6%E6%9E%84.html

2.1 引入依赖
 		<!--slf4j统一日志接口依赖包-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.5</version>
        </dependency>
            
        <!--logback依赖包-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.1.7</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.7</version>
        </dependency>
2.2 编写xml文件
  • logback的xml文件初始化过程(寻找顺序吧)
  1. logback 会在类路径下寻找名为 logback-test.xml 的文件。
  2. 如果没有找到,logback 会继续寻找名为 logback.groovy 的文件。
  3. 如果没有找到,logback 会继续寻找名为 logback.xml 的文件。
  4. 如果没有找到,将会通过 JDK 提供的 ServiceLoader 工具在类路径下寻找文件 META-INFO/services/ch.qos.logback.classic.spi.Configurator,该文件的内容为实现了 Configurator 接口的实现类的全限定类名。
  5. 如果以上都没有成功,logback 会通过 BasicConfigurator 为自己进行配置,并且日志将会全部在控制台打印出来。

我们一般会命名为logback.xml

<configuration debug="false">
    <!--appender必须定义在logger的前面-->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">     
        <encoder>         
            <pattern>IM ROOT [%p]%d %c: %m%n</pattern>
            <!--            <outputPatternAsHeader>true</outputPatternAsHeader>-->
        </encoder>
    </appender>

    <appender name="my_info" class="ch.qos.logback.core.ConsoleAppender">     
        <encoder>         
            <pattern>IM INFO [%p]%d %c: %m%n</pattern>
        </encoder>
    </appender>

    <!--输出到指定文件-->
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
        <file>test.log</file>
        <immediateFlush>true</immediateFlush>
        <encoder>         
            <pattern>IM ROOT [%p]%d %c: %m%n</pattern>
        </encoder>
    </appender>

    <!--按日期输出到指定文件(基于时间的轮转策略)-->
    <!--    <appender name="ROLL" class="ch.qos.logback.core.rolling.RollingFileAppender">-->
    <!--        &lt;!&ndash;        <file>testROll.log</file>&ndash;&gt;-->
    <!--        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">-->
    <!--            <fileNamePattern>log/logFile.%d{yyyy-MM-dd}.log</fileNamePattern>-->
    <!--保存30填记录,最大保存日志为3G-->
    <!--            <maxHistory>30</maxHistory>-->
    <!--            <totalSizeCap>3GB</totalSizeCap>-->
    <!--        </rollingPolicy>-->
    <!--        <encoder>         -->
    <!--            <pattern>IM ROLL [%p]%d %c: %m%n</pattern>-->
    <!--        </encoder>-->
    <!--    </appender>-->

    <!--按日期输出到指定文件(基于时间及大小的轮转策略)-->
    <!--    <appender name="ROLL_SIZEANDTIME" class="ch.qos.logback.core.rolling.RollingFileAppender">-->
    <!--        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">-->
    <!--            <fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>-->
    <!--每个文件最大100M-->
    <!--            <maxFileSize>100MB</maxFileSize>-->
    <!--            <maxHistory>60</maxHistory>-->
    <!--            <totalSizeCap>20GB</totalSizeCap>-->
    <!--        </rollingPolicy>-->
    <!--        <encoder>         -->
    <!--            <pattern>IM ROLL_SIZEANDTIME [%p]%d %c: %m%n</pattern>-->
    <!--        </encoder>-->
    <!--    </appender>-->

    <!--输出到数据库-->
    <!--    <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">-->
    <!--        <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">-->
    <!--            <dataSource class="com.mysql.jdbc.jdbc2.optional.MysqlDataSource">-->
    <!--                <serverName>cdb-6j6jktxk.bj.tencentcdb.com</serverName>-->
    <!--                <port>10175</port>-->
    <!--                <databaseName>logging</databaseName>-->
    <!--                <user>root</user>-->
    <!--                <password>@bai880607</password>-->
    <!--            </dataSource>-->
    <!--        </connectionSource>-->
    <!--    </appender>-->

    <!--帅选策略-->
    <!--    <appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender">-->
    <!-- 在缺少 class 属性的情况下,默认的 discriminator 类型为ch.qos.logback.classic.sift.MDCBasedDiscriminator -->
    <!--        <discriminator>-->
    <!--            <key>userid</key>-->
    <!--            <defaultValue>unknown</defaultValue>-->
    <!--        </discriminator>-->
    <!--        <sift>-->
    <!--            <appender name="FILE-${userid}"-->
    <!--                      class="ch.qos.logback.core.FileAppender">-->
    <!--                <file>${userid}_${FILE_NAME}.log</file>-->
    <!--                <append>false</append>-->
    <!--                <layout class="ch.qos.logback.classic.PatternLayout">-->
    <!--                    <pattern>%d [%thread] %level %mdc %logger{35} - %msg%n</pattern>-->
    <!--                </layout>-->
    <!--            </appender>-->
    <!--        </sift>-->
    <!--    </appender>-->

    <!--ROOT顶级logger-->
    <root level="ERROR">      
        <appender-ref ref="STDOUT"/>   
    </root>
    <!--自定义子logger-my_info-->
    <logger name="my_info" level="INFO" additivity="false">
        <appender-ref ref="my_info"/>  
    </logger>
    <!--自定义子logger-my_info-->
    <logger name="my_warn" level="DEBUG">
        <appender-ref ref="FILE"/>  
    </logger>
</configuration>
2.3 测试用例
public class Log4jTest{
    // 创建不同的logger
    private static final Logger logger = LoggerFactory.getLogger(Log4jTest.class);
    private static final Logger my_info = LoggerFactory.getLogger("my_info");
    private static final Logger my_warn = LoggerFactory.getLogger("my_warn");
    private static final Logger my_error = LoggerFactory.getLogger("my_error");
    java.util.logging.Logger log = java.util.logging.Logger.getLogger(this.getClass().getName());

    @Test
    public void test1() {
        int count = 10;
        // my_warn.info("日志输出次数:{}",count);
        my_warn.debug("日志输出次数:{}", count);
        // MDC.put("userid", "Alice");
        // logger.debug("Alice says hello");
        // 打印内部的状态
        // LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
        // StatusPrinter.print(lc);
    }
2.4组件详解

常用的2大组件looger和appender,

2.4.1 appender

appender可以控制日志输出的路径和输出格式

日志输出路径:控制台、本地文件、远程文件、数据库。

encoder组件用来控制输出格式。(不建议使用layout)

这里只介绍3种:

1、ConsoleAppender:输出到控制台
<appender name="my_info" class="ch.qos.logback.core.ConsoleAppender">     
    <encoder>         
        <pattern>IM INFO [%p]%d %c: %m%n</pattern>
    </encoder>
</appender>
2、FileAppender:输出到文件
<!--输出到指定文件-->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>test.log</file>
    <immediateFlush>true</immediateFlush>
    <encoder>         
        <pattern>IM ROOT [%p]%d %c: %m%n</pattern>
    </encoder>
</appender>
3、RollingFileAppender:轮转输出到文件
  • 基于时间的轮转策略
   <!--按日期输出到指定文件(基于时间的轮转策略)-->
    <appender name="ROLL" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--指定当前活动的文件名称-->
        <file>testROll.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--指定归档的文件名称-->
            <fileNamePattern>log/logFile.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!--保存30天记录,最大保存日志为3G-->
            <maxHistory>30</maxHistory>
            <totalSizeCap>3GB</totalSizeCap>
        </rollingPolicy>
        <encoder>         
            <pattern>IM ROLL [%p]%d %c: %m%n</pattern>
        </encoder>
    </appender>
  • 基于时间和大小的轮转策略
<!--按日期输出到指定文件(基于时间及大小的轮转策略)-->
<appender name="ROLL_SIZEANDTIME" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
        <fileNamePattern>mylog-%d{yyyy-MM-dd}.%i.txt</fileNamePattern>
        <!--每个文件最大100M-->
        <maxFileSize>100MB</maxFileSize>
        <maxHistory>60</maxHistory>
        <totalSizeCap>20GB</totalSizeCap>
    </rollingPolicy>
    <encoder>         
        <pattern>IM ROLL_SIZEANDTIME [%p]%d %c: %m%n</pattern>
    </encoder>
</appender>
4、DBAppender:输出到数据库
<!--输出到数据库-->
<appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
    <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">
        <dataSource class="com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
            <serverName>数据库url</serverName>
            <port>10175</port>
            <databaseName>logging</databaseName>
            <user>root</user>
            <password>********</password>
        </dataSource>
    </connectionSource>
</appender>
  • 需要建立的表及字段
BEGIN;
DROP TABLE IF EXISTS logging_event_property;
DROP TABLE IF EXISTS logging_event_exception;
DROP TABLE IF EXISTS logging_event;
COMMIT;
 
 
BEGIN;
CREATE TABLE logging_event 
  (
    timestmp         BIGINT NOT NULL,
    formatted_message  TEXT NOT NULL,
    logger_name       VARCHAR(254) NOT NULL,
    level_string      VARCHAR(254) NOT NULL,
    thread_name       VARCHAR(254),
    reference_flag    SMALLINT,
    arg0              VARCHAR(254),
    arg1              VARCHAR(254),
    arg2              VARCHAR(254),
    arg3              VARCHAR(254),
    caller_filename   VARCHAR(254) NOT NULL,
    caller_class      VARCHAR(254) NOT NULL,
    caller_method     VARCHAR(254) NOT NULL,
    caller_line       CHAR(4) NOT NULL,
    event_id          BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY
  );
COMMIT;
 
BEGIN;
CREATE TABLE logging_event_property
  (
    event_id          BIGINT NOT NULL,
    mapped_key        VARCHAR(254) NOT NULL,
    mapped_value      TEXT,
    PRIMARY KEY(event_id, mapped_key),
    FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
  );
COMMIT;
 
BEGIN;
CREATE TABLE logging_event_exception
  (
    event_id         BIGINT NOT NULL,
    i                SMALLINT NOT NULL,
    trace_line       VARCHAR(254) NOT NULL,
    PRIMARY KEY(event_id, i),
    FOREIGN KEY (event_id) REFERENCES logging_event(event_id)
  );
COMMIT;
2.4.2 logger

1、logger就是我们代码中创建的logger对象,可以控制输出级别引用appender是否继承父类logger的appender

private static final Logger logger = LoggerFactory.getLogger(Log4jTest.class);
private static final Logger my_info = LoggerFactory.getLogger("my_info");
private static final Logger my_warn = LoggerFactory.getLogger("my_warn");
private static final Logger my_error = LoggerFactory.getLogger("my_error");

2、一个logger可以引用多个appender,通过符号 . 来区分

<!--自定义子logger-my_info.my_warn-->
<logger name="my_info.my_warn" level="DEBUG">
    <appender-ref ref="STDOUT"/>  
</logger>

3、所有logger都默认继承root对象

<!--ROOT顶级logger-->
<root level="ERROR">      
    <appender-ref ref="STDOUT"/>   
</root>

不设置也行,root对象有默认属性

4、logger的名字可以自定义,一般我们通过当前类来获取logger

5、可以通过包名或类名来指定输出级别

<!--通过包来控制输出级别-->
<logger name="com.smilevers" level="DEBUG">
</logger>

3、log4j

Log4j 是 Apache 的一个开源日志框架,也是市场占有率最多的一个框架。大多数没用过 Java Logging, 但没人敢说没用过 Log4j 吧,反正从我接触 Java 开始就是这种情况,做 Java 项目必有 Log4j 日志框架。

注意:log4j 在 2015/08/05 这一天被 Apache 宣布停止维护了,用户需要切换到 Log4j2上面去。

#ERROR To CONSOLE
log4j.rootLogger=ERROR,CONSOLE

###################
# Console Appender
###################
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=[%p] %d %c - %m%n


log4j.logger.mdp_root =INFO,CONSOLE
#log4j.logger.mdp_root =INFO,mdp_root
log4j.appender.mdp_root=org.apache.log4j.RollingFileAppender
log4j.appender.mdp_root.File=${catalina.base}/logs/mdp/mdp_root.log
log4j.appender.mdp_root.Append=true
log4j.appender.mdp_root.MaxFileSize=50000KB
log4j.appender.mdp_root.MaxBackupIndex=100
log4j.appender.mdp_root.layout=org.apache.log4j.PatternLayout
log4j.appender.mdp_root.layout.ConversionPattern=[%p] %d %c - %m%n

log4j.logger.mdp_system =INFO,CONSOLE
#log4j.logger.mdp_system =INFO,mdp_system
log4j.appender.mdp_system=org.apache.log4j.RollingFileAppender
log4j.appender.mdp_system.File=${catalina.base}/logs/mdp/mdp_system.log
log4j.appender.mdp_system.Append=true
log4j.appender.mdp_system.MaxFileSize=50000KB
log4j.appender.mdp_system.MaxBackupIndex=100
log4j.appender.mdp_system.layout=org.apache.log4j.PatternLayout
log4j.appender.mdp_system.layout.ConversionPattern=[%p] %d %c - %m%n

log4j.logger.mdp_exception =INFO,CONSOLE
#log4j.logger.mdp_exception =INFO,mdp_exception
log4j.appender.mdp_exception=org.apache.log4j.RollingFileAppender
log4j.appender.mdp_exception.File=${catalina.base}/logs/mdp/mdp_exception.log
log4j.appender.mdp_exception.Append=true
log4j.appender.mdp_exception.MaxFileSize=50000KB
log4j.appender.mdp_exception.MaxBackupIndex=100
log4j.appender.mdp_exception.layout=org.apache.log4j.PatternLayout
log4j.appender.mdp_exception.layout.ConversionPattern=[%p] %d %c - %m%n

log4j.logger.mdp_debug =INFO,CONSOLE
#log4j.logger.mdp_debug =INFO,mdp_debug
log4j.appender.mdp_debug=org.apache.log4j.RollingFileAppender
log4j.appender.mdp_debug.File=${catalina.base}/logs/mdp/mdp_debug.log
log4j.appender.mdp_debug.Append=true
log4j.appender.mdp_debug.MaxFileSize=50000KB
log4j.appender.mdp_debug.MaxBackupIndex=100
log4j.appender.mdp_debug.layout=org.apache.log4j.PatternLayout
log4j.appender.mdp_debug.layout.ConversionPattern=[%p] %d %c - %m%n

log4j.logger.mdp_handle =INFO,CONSOLE
#log4j.logger.mdp_handle =INFO,mdp_handle
log4j.appender.mdp_handle=org.apache.log4j.RollingFileAppender
log4j.appender.mdp_handle.File=${catalina.base}/logs/mdp/mdp_handle.log
log4j.appender.mdp_handle.Append=true
log4j.appender.mdp_handle.MaxFileSize=50000KB
log4j.appender.mdp_handle.MaxBackupIndex=100
log4j.appender.mdp_handle.layout=org.apache.log4j.PatternLayout
log4j.appender.mdp_handle.layout.ConversionPattern=[%p] %d %c - %m%n

log4j.logger.mdp_info =INFO,CONSOLE
#log4j.logger.mdp_info =INFO,mdp_info
log4j.additivity.mdp_info= false
log4j.appender.mdp_info=org.apache.log4j.RollingFileAppender
log4j.appender.mdp_info.File=${catalina.base}/logs/mdp/mdp_info.log
log4j.appender.mdp_info.Append=true
log4j.appender.mdp_info.MaxFileSize=50000KB
log4j.appender.mdp_info.MaxBackupIndex=100
log4j.appender.mdp_info.layout=org.apache.log4j.PatternLayout
log4j.appender.mdp_info.layout.ConversionPattern=[%p] %d %c - %m%n

#framework
log4j.logger.org.springframework=INFO
#log4j.logger.org.springframework.web=TRACE
log4j.logger.com.midea=DEBUG
log4j.logger.com.cttq=DEBUG
log4j.logger.org.apache.ibatis=DEBUG
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.com.alibaba.dubbo=ERROR

三、项目实战

1、项目日志打印2次的问题

在使用项目工具LogUtils工具打印日志的时候,会出现控制台打印2次日志的问题。

分析:日志打印2次,应该是继承的父类的appender,那我们只要添加additivity属性,并将其值设置为false即可。

2、通过类创建的logger对象打印不出日志

分析:日志打印不出,一般是级别设置的不对,通过查看配置文件可以看到,配置中指定了具体包的日志级别为DEBUG,而我们打印日志的包并没有指定,就会继承root对象的级别为ERROR,因此我们使用logger.debbug或logger.info都不能打印输出日志。我们再将所在包添加即可。

3、按级别分文件输出

<configuration>
    <property name="APP_NAME" value="APPNAME"/>
    <property name="LOG_HOME" value="./log/APPNAME" />
    <contextName>${APP_NAME}</contextName>
    <!-- 控制台日志输出 -->
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <encoder charset="UTF-8">
            <pattern>%d{yyyy-MM-dd HH:mm:ss} %5p %c:%L %m%n</pattern>
        </encoder>
    </appender>
    <!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 指定日志文件的名称 -->
        <file>${LOG_HOME}/${APP_NAME}.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--
            滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
            %i:当文件大小超过maxFileSize时,按照i进行文件滚动
            -->
            <fileNamePattern>${LOG_HOME}/logbacks/${APP_NAME}-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <MaxHistory>180</MaxHistory>
            <!--
            当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 -->
            <maxFileSize>30MB</maxFileSize>
        </rollingPolicy>
        <!--
        日志输出格式:%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} : %line ] - %msg%n</pattern>
        </layout>
    </appender>
 
    <!-- root是所有logger的父级,当logger的additivity=false时,logger管理的日志不再父级传递 -->
    <!--root的level尽量高一些,logger的级别必须低于root的级别才能输出-->
    <!--ALL > TRACE > FATAL > DEBUG > INFO > WARN > ERROR > OFF-->
    <root level="DEBUG">
        <appender-ref ref="stdout"/>
        <appender-ref ref="FILE"/>
    </root>
 
    <!--appender和logger一起使用-->
 
    <!--appender的name可以随意指定,被logger的appender-ref标签引用-->
    <appender name="errorAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 指定日志文件的名称 -->
        <file>${LOG_HOME}/${APP_NAME}-error.log</file>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--只匹配ERROR级别日志-->-->
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--
            滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
            %i:当文件大小超过maxFileSize时,按照i进行文件滚动
            -->
            <fileNamePattern>${LOG_HOME}/logbacks/${APP_NAME}-error-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <MaxHistory>180</MaxHistory>
            <!-- 当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 -->
            <maxFileSize>30MB</maxFileSize>
        </rollingPolicy>
        <!--
        日志输出格式:%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} : %line ] - %msg%n</pattern>
        </layout>
    </appender>
    <appender name="infoAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!-- 指定日志文件的名称 -->
        <file>${LOG_HOME}/${APP_NAME}-info.log</file>
        <!--只输出INFO-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--只匹配INFO级别日志-->
            <level>INFO</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--
            滚动时产生的文件的存放位置及文件名称 %d{yyyy-MM-dd}:按天进行日志滚动
            %i:当文件大小超过maxFileSize时,按照i进行文件滚动
            -->
            <fileNamePattern>${LOG_HOME}/logbacks/${APP_NAME}-info-%d{yyyy-MM-dd}-%i.log</fileNamePattern>
            <MaxHistory>180</MaxHistory>
            <!-- 当日志文件超过maxFileSize指定的大小是,根据上面提到的%i进行日志文件滚动 -->
            <maxFileSize>30MB</maxFileSize>
        </rollingPolicy>
        <!--
        日志输出格式:%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} : %line ] - %msg%n</pattern>
        </layout>
    </appender>
 
    <!--logger的name作为匹配包名的作用-->
    <!--当java代码中LoggerFactory.getLogger(XX.class);匹配该XX的类名-->
    <!--当java代码中LoggerFactory.getLogger("loggername");时,name属性直接写loggername-->
    <logger name="com.example" additivity="false">
        <appender-ref ref="infoAppender"/>
        <appender-ref ref="errorAppender"/>
    </logger>
     
</configuration>

4、 日志框架的迁移

log4j和logback可以无缝切换,不需要改变任何代码,只需要把对应的jar包换掉即可。

四、日志规范

1、Logger对象的声明和初始化

//推荐,注意导入的包一定是slf4j的
private static final Logger logger = LoggerFactory.getLogger(Xxx.class);

2、 不得使用System.out, System.err进行日志记录,请改使用logger.debug、logger.error

3、 使用参数化形式{}占位

LOG.debug("Save order with order no:{}, and order amount:{}");

为使用占位符的,必须使用isXxxEnabled() 判断

if (logger.isDebugEnabled()) {
    logger.debug(test());
}

4、 输出不同级别的日志

  • ERROR(错误)

    一般用来记录程序中发生的任何异常错误信息(Throwable),或者是记录业务逻辑出错。

  • WARN(警告)

    一般用来记录一些用户输入参数错误

  • INFO(信息)

    默认的日志级别,用来记录程序运行中的一些有用的信息。如程序运行开始、结束、耗时、重要参数等信息,需要注意有选择性的有意义的输出,到时候自己找问题看一堆日志却找不到关键日志就没意义了

  • DEBUG(调试)

    这个级别一般记录一些运行中的中间参数信息,只允许在测试环境开启,选择性在开发环境开启。

5、没有输出全部错误信息

看以下代码,这样不会记录详细的堆栈异常信息,只会记录错误基本描述信息,不利于排查问题。

try {
    // ...
} catch (Exception e) {
    // 错误
    LOG.error('XX 发生异常', e.getMessage());
 
    // 正确
    LOG.error('XX 发生异常', e);

6、不要在千层循环中打印日志

这个是什么意思,如果你的框架使用了性能不高的 Log4j 框架,那就不要在上千个 for循环中打印日志,这样可能会拖垮你的应用程序,如果你的程序响应时间变慢,那要考虑是不是日志打印的过多了

// 错误
for(int i=0; i<2000; i++){
    LOG.info("XX");
}

最好的办法是在循环中记录要点,在循环外面总结打印出来。

7、 什么时候输出日志

7.1 异常捕获处

在捕获异常处输出日志,大家在基本都能做到,唯一需要注意的是怎么输出一个简单明了的日志信息。这在后面的问题问题中有进一步说明。

7.2 调用外部接口处

在调用外部系统时,我们需要输出日志记录返回的结果。

7.3 关键操作

关键操作的日志一般是INFO级别,如果数量、频度很高,可以考虑使用DEBUG级别。以下是一些关键操作的举例,实际的关键操作肯定不止这么多。

n 删除:删除一个文件、删除一组重要数据库记录……

n 添加:和外系统交互时,收到了一个文件、收到了一个任务……

n 处理:开始、结束一条任务……

n ……

五、日志分析、存储、展示

1、ELK简介

ELK是一套完整的日志解决方案,由ElasticSearch、Logstash、 Kibana
这三款开源软件组成。

  • EastiSearch是基于Lucene开发的分布式存储检引擎,用来存储各类日志;
  • Logstash对日志进行收集、分析,并将其存储供以后使用:
  • Kibana 是基于Node.js开发的展示工具,为Logstah和ElasticSearch提供用于日志展示的Web界面,还用于帮助汇总、分析和搜索重要日志数据。

ELK官网https://www.elastic.co/products 分别提供包进行下载安装。

2、ELK工作原理

  • 在所有需要收集日志的服务上部署Logstash,作为署Logstash agent用于监控并过滤所收集的日志,将过滤后的内容整合在一起,最终全部交给EastiSearch检索引擎;
  • 用EastiSearch进行自定义检索;
  • 再通过Kibana通过结合自定义检索内容生成图表,进行日志数据展示。

详细参考:https://blog.51cto.com/11134648/2163789
logback官方文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值