日志

作者: Coding小聪 | 来源:发表于2018-11-14 23:41 被阅读14次

1. 日志分类

日志能够帮助我们很快的定位出生产上的一些问题,因此程序中日志打印的质量,可以从某种程度中反应出开发者的水平。对于Java开发而言,市面上有很多日志框架可以供我们选择,选择多了有时候并不是一件好事,因为很容易混乱,需要开发者理清它们的关系并进行选用。常见的日志框架有如下几种:

  • JCL(Jakarta Commons Logging),也称为“Apache Commons Logging”;
  • JUL:java.util.logging,JDK自带的日志体系;
  • SLF4J:Simple Logging Facade for Java,日志门面
  • log4j
  • log4j2
  • logback
  • Jboss-logging

这些日志可以分为两类

  1. 日志门面,即实际日志框架的抽象,一般只提供接口API。上面列举的JCL、SLF4J和Jboss-logging都属于日志门面;
  2. 日志实现,日志具体实现框架。

Java开发提倡面向接口编程,所以在我们实际应用当中应该使用日志门面来进行日志编程,这样方便在不同日志实现框架之间进行切换。

Spring框架中,日志默认使用的就是JCL,SpringBoot选用 SLF4j和logback。

2. JCL

使用JCL,只需要引入以下依赖即可

<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>

JCL中存在默认的日志实现,当classpath中没有其他日志jar即会使用其默认的日志。使用默认的日志实现,只要引入上面的jar包即可。

如果要想使用其他的日志实现,除了commons-logging包外,还需要引入相关的依赖包,具体如下
log4j

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

log4j2

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.11.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.11.0</version>
</dependency>

<!-- log4j2与commons-logging集成包 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-jcl</artifactId>
    <version>2.11.0</version>
</dependency>

3. SLF4J

官方使用手册:请点这里

3.1. 简单使用

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.8.0-beta2</version>
</dependency>

程序中引用如下

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

public class HelloWorld {
  public static void main(String[] args) {
    Logger logger = LoggerFactory.getLogger(HelloWorld.class);
    logger.info("Hello World");
  }
}

与JCL不同,slf4j-api中没有提供默认的实现,如果仅添加该依赖而不添加日志实现框架的依赖,在控制台会看到如下输出:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

SLF4J提供一个简单的日志API接口的实现项目slf4j-simple,不过该实现比较简单,一般不在生产项目中使用它。

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.8.0-beta2</version>
    <scope>test</scope>
</dependency>

3.2. SLF4J和其他日志框架

logback由于天然支持SLF4J,所以一般推荐SLF4J+logback的使用组合
应用系统应用SLF4J+具体日志实现框架的关系图如下所示

每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件;
根据不同的日志系统,你可以按如下规则组织配置文件名,就能被正确加载:

Logback:logback-spring.xml, logback-spring.groovy, logback.xml, logback.groovy
Log4j:log4j-spring.properties, log4j-spring.xml, log4j.properties, log4j.xml
Log4j2:log4j2-spring.xml, log4j2.xml
JDK (Java Util Logging):logging.properties

日志名称如果不按照约定的也是可以的,在spring boot中通过logging.config=classpath:logging-config.xml
进行修改。

SLF4J必须要和其他日志实现框架一起使用,才能正常输出日志。不同日志框架引入的依赖有所不同,具体如下:
loggback

<dependency> 
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-classic</artifactId>
  <version>1.0.13</version>
</dependency>

log4j

<dependency> 
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.8.0-beta2</version>
</dependency>

java.util.logging

<dependency> 
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-jdk14</artifactId>
  <version>1.8.0-beta2</version>
</dependency>

上面的这些依赖都会自动引入其所对应日志实现框架的jar包和slf4j-api

3.3. 系统中统一使用SLF4J

SLF4J官方强烈建议我们,在创建公共的类包和框架时只依赖slf4j-api,而不依赖具体的日志实现框架。不过依然会存在一些历史原因导致我们应用引入的Jar包引用了具体的日志实现框架,如果这个时候我们依然想在我们的应用当中统一使用SLF4J+具体日志框架,该如何办呢?

考虑到这种情况,SLF4J附带了几个桥接模块,这些模块重定向对log4j,JCL和java.util.logging API的调用,就好像它们是对SLF4J API一样。

桥接到SLF4J

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

具体案例可以参考:slf4j+log4j+logback总结

3.4. logback的配置

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 /app/sv/logs -->
    <property name="LOG_HOME" value="/app/myapp/logs"/>
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>


    <!-- SQL-APPENDER 记录所有sql输出日志 -->
    <appender name="SQL-APPENDER"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名 -->
            <FileNamePattern>${LOG_HOME}/sql.%d{yyyy-MM-dd}-%i.log</FileNamePattern>
            <!--日志文件保留天数 -->
            <MaxHistory>30</MaxHistory>
            <!--日志文件最大的大小 -->
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>500MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <ImmediateFlush>false</ImmediateFlush>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- APPLICATION-APPENDER 当前应用的日志 -->
    <appender name="APPLICATION-APPENDER"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名 -->
            <FileNamePattern>${LOG_HOME}/application.%d{yyyy-MM-dd}-%i.log</FileNamePattern>
            <!--日志文件保留天数 -->
            <MaxHistory>30</MaxHistory>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>500MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <ImmediateFlush>false</ImmediateFlush>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="DUBBO-APPENDER"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名 -->
            <FileNamePattern>${LOG_HOME}/dubbo.%d{yyyy-MM-dd}-%i.log</FileNamePattern>
            <!--日志文件保留天数 -->
            <MaxHistory>30</MaxHistory>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>500MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <ImmediateFlush>false</ImmediateFlush>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="THIRD-PARTY-APPENDER"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名 -->
            <FileNamePattern>${LOG_HOME}/3rd-party.%d{yyyy-MM-dd}-%i.log</FileNamePattern>
            <!--日志文件保留天数 -->
            <MaxHistory>30</MaxHistory>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>500MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <ImmediateFlush>false</ImmediateFlush>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <appender name="THIRD-PART-C3P0"
              class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名 -->
            <FileNamePattern>${LOG_HOME}/3rd-party-c3p0.%d{yyyy-MM-dd}-%i.log</FileNamePattern>
            <!--日志文件保留天数 -->
            <MaxHistory>30</MaxHistory>
            <timeBasedFileNamingAndTriggeringPolicy
                    class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>500MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <ImmediateFlush>false</ImmediateFlush>
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符 -->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 异步输出 -->
    <appender name="ASYNC-APPLICATION" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>8096</queueSize>
        <!-- 添加附加的appender,最多只能添加一个 -->
        <appender-ref ref="APPLICATION-APPENDER"/>
    </appender>

    <!-- 异步输出 -->
    <appender name="ASYNC-SQL" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
        <queueSize>8096</queueSize>
        <!-- 添加附加的appender,最多只能添加一个 -->
        <appender-ref ref="SQL-APPENDER"/>
    </appender>

    <logger name="com.xxx.application" level="INFO" additivity="false">
        <appender-ref ref="ASYNC-APPLICATION"/>
    </logger>

    <logger name="com.xxx.mapper" level="DEBUG" additivity="false">
        <appender-ref ref="ASYNC-SQL"/>
    </logger>

    <logger name="org.springframework" level="INFO" additivity="false">
        <appender-ref ref="THIRD-PARTY-APPENDER"/>
    </logger>
    <logger name="org.apache" level="INFO" additivity="false">
        <appender-ref ref="THIRD-PARTY-APPENDER"/>
    </logger>
    <logger name="com.xxx.dubbo" level="INFO" additivity="false">
        <appender-ref ref="DUBBO-APPENDER"/>
    </logger>

    <logger name="com.mchange" level="TRACE" additivity="false">
        <appender-ref ref="ASYNC-C3P0"/>
    </logger>

    <logger name="com.aliyun" level="INFO" additivity="true">
        <appender-ref ref="ASYNC-APPLICATION"/>
    </logger>


    <!-- 日志输出级别-->
    <root level="INFO">
        <!-- <appender-ref ref="STDOUT"/> -->
    </root>
</configuration>

更多请参考:
logback日志配置
logback 配置详解(一)——logger、root

4. SpringBoot日志关系

SpringBoot中日志starter依赖

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

其底层依赖关系

可以看到
1)、SpringBoot底层是使用slf4j+logback的方式进行日志记录
2)、SpringBoot也把其他的日志都替换成了slf4j;
3)、引入了中间替换包

如果我们在SpringBoot项目中引入其他框架,一定要把这个框架的默认日志依赖移除掉。例如:spring-core依赖了commons-logging我们需要将其排除掉。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <exclusions>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

5. 日志规约

《阿里巴巴Java开发手册》中关于日志方面的规定如下

1.【强制】应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架
SLF4J中的API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
privatestaticfinalLoggerlogger=LoggerFactory.getLogger(Abc.class);
2.【强制】日志文件推荐至少保存15天,因为有些异常具备以“周”为频次发生的特点。
3.【强制】应用中的扩展日志(如打点、临时监控、访问日志等)命名方式:
appName_logType_logName.log。logType:日志类型,推荐分类有
stats/desc/monitor/visit等;logName:日志描述。这种命名的好处:通过文件名就可知
道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找。
正例:mppserver应用中单独监控时区转换异常,如:
mppserver_monitor_timeZoneConvert.log
说明:推荐对日志进行分类,如将错误日志和业务日志分开存放,便于开发人员查看,也便于
通过日志对系统进行及时监控。
4.【强制】对trace/debug/info级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
说明:logger.debug("Processing trade with id:"+id+" and symbol:"+symbol);
如果日志级别是warn,上述日志不会打印,但是会执行字符串拼接操作,如果symbol是对象,会执行toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。
正例:(条件)
if(logger.isDebugEnabled()){
logger.debug("Processingtradewithid:"+id+"andsymbol:"+symbol);
}
正例:(占位符)
logger.debug("Processingtradewithid:{}andsymbol:{}",id,symbol);
5.【强制】避免重复打印日志,浪费磁盘空间,务必在log4j.xml中设置additivity=false。
正例:<loggername="com.taobao.dubbo.config"additivity="false">
6.【强制】异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字throws往上抛出。
正例:logger.error(各类参数或者对象toString+"_"+e.getMessage(),e);
7.【推荐】谨慎地记录日志。生产环境禁止输出debug日志;有选择地输出info日志;如果使用warn来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志。
说明:大量地输出无效日志,不利于系统性能提升,也不利于快速定位错误点。记录日志时请
思考:这些日志真的有人看吗?看到这条日志你能做什么?能不能给问题排查带来好处?
8.【参考】可以使用warn日志级别来记录用户输入参数错误的情况,避免用户投诉时,无所适从。注意日志输出的级别,error级别只记录系统逻辑出错、异常等重要的错误信息。如非必要,请不要在此场景打出error级别。

参考文档

Bridging legacy APIs
SLF4J user manual
Spring Boot干货系列:(七)默认日志框架配置
《阿里巴巴Java开发手册》

相关文章

  • Funboot开发:系统日志组件

    系统日志组件 目录 系统日志 使用MongoDb存储日志 系统日志 系统日志分为操作日志、错误日志、登录日志、数据...

  • Mysql之日志

    mysql的日志种类:通用查询日志、慢查询日志、错误日志、二进制日志、中继日志、重做日志、回滚日志。 1、通用查询...

  • 27-日志管理

    本章内容 ◆ 日志介绍◆ 日志配置◆ 日志管理◆ 远程日志◆ 基于MYSQL的日志 日志介绍 rsyslog 启用...

  • 日志基础知识

    1. 日志 系统日志 应用日志 安全日志 2. 日志框架 vs 日志门面 日志框架JULLog4jLogbackL...

  • 20171012 日志管理

    日志介绍rsyslog日志管理journalctlMySQL管理日志 一、日志介绍 (一)日志的基本概念 日志:将...

  • mysql日志系统

    mysql有如下几种不同的日志: 错误日志 二进制日志(Binlog日志) 查询日志 慢查询日志 事务日志(inn...

  • 8. 日志

    1. 日志 日志事务日志 transaction log中继日志 reley log错误日志 error ...

  • hadoop 3.x 案例7: hadoop大数据平台日志

    一. Hadoop日志 日志分类: namenode日志 datanode日志 secondarynamenode...

  • MySQL主要日志的基本操作与简单解析

    MySQL主要有以下几种日志: 错误日志 通用查询日志 慢查询日志 二进制日志 DDL日志 日志是mysql数据库...

  • httpd常用配置:日志设定

    日志设定 日志类型:访问日志和错误日志 访问日志:  LogFormat "%h %l %u %t "%r" %>...

网友评论

    本文标题:日志

    本文链接:https://www.haomeiwen.com/subject/ykzkfqtx.html