乐趣区

关于java:Java日志体系一本通

次要内容

1·学习 java 日志体系及日志工具的演进

2·理解日志采集、解决、剖析等各阶段的罕用中间件

3·学会搭建残缺的 elk 日志平台

4·学习日志打点,切面,日志文件等输入伎俩

5·我的项目实战,实现一拜访日志链路的跟踪

1、Java 日志体系

1.1 体系概述

1.1.1 日志接口

  • JCL:Apache 基金会所属的我的项目,是一套 Java 日志接口,之前叫 Jakarta Commons Logging,后更名为 Commons Logging,简称 JCL
  • SLF4J:Simple Logging Facade for Java,缩写 Slf4j,是一套繁难 Java 日志门面,只提供相干接口,和其余日志工具之间须要桥接

1.1.2 日志实现

  • JUL:JDK 中的日志工具,也称为 jdklog、jdk-logging,自 Java1.4 以来 sun 的官网提供。
  • Log4j:隶属于 Apache 基金会的一套日志框架,现已不再保护
  • Log4j2:Log4j 的降级版本,与 Log4j 变化很大,不兼容
  • Logback:一个具体的日志实现框架,和 Slf4j 是同一个作者,性能很好

1.2 倒退历程

1.2.1 上古时代

在 JDK 1.3 及以前,Java 打日志依赖 System.out.println(), System.err.println()或者 e.printStackTrace(),Debug 日志被写到 STDOUT 流,谬误日志被写到 STDERR 流。这样打日志有一个十分大的缺点,十分机械,无奈定制,且日志粒度不够细分。

代码:

System.out.println("123");
System.err.println("456");

1.2.2 创始先驱

于是,Ceki Gülcü 于 2001 年公布了 Log4j,并将其募捐给了 Apache 软件基金会,成为 Apache 基金会的顶级我的项目。起初衍生反对 C, C++, C#, Perl, Python, Ruby 等语言。
Log4j 在设计上十分优良,它定义的 Logger、Appender、Level 等概念对后续的 Java Log 框架有深远的影响,现在的很多日志框架根本沿用了这种思维。Log4j 的性能是个问题,在 Logback 和 Log4j2 进去之后,2015 年 9 月,Apache 软件基金会发表,Log4j 不再保护,倡议所有相干我的项目降级到 Log4j2

pom:

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>apache-log4j-extras</artifactId>
    <version>1.2.17</version>
</dependency>

配置:

log4j.rootLogger=debug

#console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender 
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 
log4j.appender.stdout.layout.ConversionPattern= log4j:[%d{yyyy-MM-dd HH:mm:ss a}]:%p %l%m%n

#dailyfile
log4j.appender.dailyfile=org.apache.log4j.DailyRollingFileAppender 
log4j.appender.dailyfile.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.dailyfile.File=./log4j.log
log4j.appender.dailyfile.Append=true
log4j.appender.dailyfile.Threshold=INFO
log4j.appender.dailyfile.layout=org.apache.log4j.PatternLayout 
log4j.appender.dailyfile.layout.ConversionPattern=log4j:[%d{yyyy-MM-dd HH:mm:ss a}] [Thread: %t][Class:%c >> Method: %l]%n%p:%m%n

代码:

import org.apache.log4j.Logger;

Logger logger = Logger.getLogger(Demo.class);
logger.info("xxxx");

1.2.3 搞事件的 JUL

sun 公司对于 log4j 的呈现心田隐隐示意嫉妒。于是在 jdk1.4 版本后,开始搞事件,减少了一个包为 java.util.logging,简称为 JUL,用以反抗 log4j,然而却给开发造成了麻烦。互相援用的我的项目之间可能应用了不同的日志框架,常常将代码搞得一片凌乱。

代码:

import java.util.logging.Logger;Logger loggger = Logger.getLogger(Demo.class.getName()); 
logger.finest("xxxx");

配置门路:

$JAVA_HOME/jre/lib/logging.properties

JUL 性能远不如 log4j 欠缺,自带的 Handlers 无限,性能和可用性上也个别,JUL 在 Java1.5 当前才有所晋升。

1.2.4 JCL 应运而生

从下面能够看出,JUL 的 api 与 log4j 是齐全不同的(参数只承受 string)。因为日志零碎相互没有关联,彼此没有约定,不同人的代码应用不同日志,替换和对立也就变成了比拟辣手的一件事。如果你的利用应用 log4j,而后我的项目援用了一个其余团队的库,他们应用了 JUL,你的利用就得应用两个日志零碎了,而后其余团队又应用了 simplelog……这个时候如果要调整日志的输入级别,用于跟踪某个信息,几乎就是一场劫难。

那这个情况该如何解决呢?答案就是进行形象,形象出一个接口层,对每个日志实现都适配或者转接,这样这些提供给他人的库都间接应用形象层即可,当前调用的时候,就调用这些接口。(面向接口思维)

于是,JCL(Jakarta Commons Logging)应运而生,也就是 commons-logging-xx.jar 组件。JCL 只提供 log 接口,具体的实现则在运行时动静寻找。这样一来组件开发者只须要针对 JCL 接口开发,而调用组件的应用程序则能够在运行时搭配本人爱好的日志实际工具。

那接口下实在的日志是谁呢?参考下图:


JCL 会在 ClassLoader 中进行查找,如果能找到 Log4j 则默认应用 log4j 实现,如果没有则应用 JUL(jdk 自带的) 实现,再没有则应用 JCL 外部提供的 SimpleLog 实现。(代码验证)

pom:

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

代码:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

Log log =LogFactory.getLog(Demo.class);
log.info('xxx');

JCL 毛病也很显著,一是效率较低,二是容易引发凌乱,三是 JCL 的机制有很大的可能会引发内存泄露。

同时,JCL 的书写存在一个不太优雅的中央,典型的场景如下:

如果要输入一条 debug 日志,而个别状况下,生产环境 log 级别都会设到 info 或者以上,那这条 log 是不会被输入的。于是,在代码里就呈现了

logger.debug("this is a debug info , message :" + msg);

这个有什么问题呢?尽管生产不会打出日志,然而这其中都会做一个字符串连贯操作,而后生成一个新的字符串。如果这条语句在循环或者被调用很屡次的函数中,就会多做很多无用的字符串连贯,影响性能。

所以,JCL 举荐的写法如下:

if (logger.isDebugEnabled()) {logger.debug("this is a debug info , message :" + msg);
}

尽管解决了问题,然而这个代码切实看上去不怎么难受 …

1.2.5 再起波澜

于是,针对以上状况,log4j 的作者再次出手,他感觉 JCL 不好用,本人又写了一个新的接口 api,就是 slf4j,并且为了谋求更极致的性能,新增了一套日志的实现,就是 logback,一时间烽火又起……

坐标:

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.30</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

配置:

<?xml version="1.0" encoding="UTF-8"?>

<configuration scan="true" scanPeriod="60 seconds" debug="false">
    
    <property name="logPattern" value="logback:[%-5level] [%date{yyyy-MM-dd HH:mm:ss.SSS}] %logger{96} [%line] [%thread]- %msg%n"></property>

    <!-- 控制台的规范输入 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>UTF-8</charset>
            <pattern>${logPattern}</pattern>
        </encoder>
    </appender>

    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
    </root>

</configuration>

logback-core 提供根底形象,logback-classic 提供日志实现,并且间接就是基于 Slf4j API。所以 slf4j 配合 logback 来实现日志时,不须要像其余的日志框架一样提供适配器。

slf4j 自身并没有理论的日志输入能力,它底层还是须要去调用具体的日志框架 API,也就是它须要跟具体的日志框架联合应用。因为具体日志框架比拟多,而且相互也大都不兼容,日志门面接口要想实现与任意日志框架联合就须要额定对应的桥接器。

有了新的 slf4j 后,下面的字符串拼接问题,被以下代码所取代,而 logback 也提供了更高级的个性,如异步 logger,Filter 等。

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

final Logger logger = LoggerFactory.getLogger(Demo.class);

logger.debug("this is a debug info , message : {}", msg);

事件完结了吗?没有,log4j 的粉丝们并不开心,于是下一代诞生了……

1.2.6 再度青春

后面提到,log4j 由 apache 发表,2015 年后,不再保护。举荐大家降级到 log4j2,尽管 log4j2 因循了 log4j 的思维,然而 log4j2 和 log4j 齐全是两码事,并不兼容。

log4j2 以性能著称,它比其前身 Log4j 1.x 提供了重大改良,同时类比 logback,它提供了 Logback 中可用的许多改良,同时修复了 Logback 架构中的一些固有问题。性能上,它有着和 Logback 雷同的基本操作,同时又有本人独特的局部,比方:插件式构造、配置文件优化、异步日志等。


pom:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.13.0</version>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.13.0</version>
</dependency>
<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.4.1</version>
</dependency>

代码:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

Logger logger = LogManager.getLogger(Demo.class);

logger.debug("debug Msg");

配置:

<?xml version="1.0" encoding="UTF-8"?>

<configuration status="info" monitorInterval="30">
    <Properties>
        <Property name="pattern">log4j2:[%-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n</Property>
    </Properties>

    <appenders>
        <!--console : 控制台输入的配置 -->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${pattern}"/>
        </Console>
    </appenders>
    
    <loggers>
        <logger name="org.springframework" level="INFO"></logger>
        <root level="info">
            <appender-ref ref="Console"/>
        </root>
    </loggers>
</configuration>

到 log4j2,轰轰烈烈的 java log 战斗根本就完结了。上面的章节,咱们将进入日志工具的具体配置阶段。

1.3 配置解说

1.3.1 概述

1)日志级别

一个残缺的日志组件都要具备日志级别的概念,每种日志组件级别定义不同,日常编码最常常用到的支流分级如下(由低到高):

  • trace:门路跟踪
  • debug:个别用于日常调式
  • info:打印重要信息
  • warn:给出正告
  • error:呈现谬误或问题

每个日志组件的具体级别划分稍有不同,参考下文各章节。

2)日志组件

  • appender:日志输入目的地,负责日志的输入(输入到什么 中央)
  • logger:日志记录器,负责收集解决日志记录(如何解决日志)
  • layout:日志格式化,负责对输入的日志格式化(以什么模式展示)

1.3.2 jul

1)配置文件:

默认状况下配置文件门路为 $JAVAHOME\jre\lib\logging.properties

能够指定配置文件:

static {
    System.setProperty("java.util.logging.config.file",
            Demo.class.getClassLoader().getResource("logging.properties").getPath());
}

代码实际:配置文件地位,日志输入级别,如何输入低于 INFO 级别的信息

2)级别:

  • SEVERE(最高值)
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST(最低值)
  • OFF,敞开日志。
  • ALL,启用所有日志。

3)处理器:

  • StreamHandler:日志记录写入 OutputStream。
  • ConsoleHandler:日志记录写入 System.err。
  • FileHandler:日志记录写入单个文件或一组滚动日志文件。
  • SocketHandler:日志记录写入近程 TCP 端口的处理程序。
  • MemoryHandler:缓冲内存中日志记录。

4)格式化:

  • SimpleFormatter:格式化为简短的日志记录摘要。
  • XMLFormatter:格式化为具体的 XML 构造信息。
  • 可自定输入格局,继承抽象类 java.util.logging.Formatter 即可。

5) 代码实际:

  • Appender:Console,File
  • Layout:Xml,Simple

1.3.3 log4j

1)配置文件:

  • 启动时,默认会寻找 source folder 下的 log4j.xml
  • 若没有,会寻找 log4j.properties

2)级别:

  • FATAL(最高)
  • ERROR
  • WARN
  • INFO
  • DEBUG(最低)
  • OFF,敞开日志。
  • ALL,启用所有日志。

3)处理器:

  • org.apache.log4j.ConsoleAppender(控制台)
  • org.apache.log4j.FileAppender(文件)
  • org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
  • org.apache.log4j.RollingFileAppender(文件大小达到指定尺寸的时候产生一个新的文件)
  • org.apache.log4j.WriterAppender(将日志信息以流格局发送到任意指定的中央)

4)格式化:

  • org.apache.log4j.HTMLLayout(以 HTML 表格模式布局)
  • org.apache.log4j.PatternLayout(能够灵便地指定布局模式)
  • org.apache.log4j.SimpleLayout(蕴含日志信息的级别和信息字符串)
  • org.apache.log4j.TTCCLayout(蕴含日志产生的工夫、线程、类别等等信息)

5) 代码实际:

  • Appender:Console,DailyRollingFile
  • Layout:Pattern
log4j.rootLogger=debug,stdout,dailyfile 

#console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender 
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 
log4j.appender.stdout.layout.ConversionPattern= [%d{yyyy-MM-dd HH:mm:ss a}]:%p %l%m%n

#dailyfile
log4j.appender.dailyfile=org.apache.log4j.DailyRollingFileAppender 
log4j.appender.dailyfile.DatePattern='_'yyyy-MM-dd'.log'
log4j.appender.dailyfile.File=./log4j.log
log4j.appender.dailyfile.Append=true
log4j.appender.dailyfile.Threshold=INFO
log4j.appender.dailyfile.layout=org.apache.log4j.PatternLayout 
log4j.appender.dailyfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][Class:%c >> Method: %l]%n%p:%m%n
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration debug="true" xmlns:log4j='http://jakarta.apache.org/log4j/'>

    <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
        <param name="Target" value="System.out"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern"
                   value=""/>
        </layout>
        <filter class="org.apache.log4j.varia.LevelRangeFilter">
            <param name="LevelMin" value="DEBUG"/>
            <param name="LevelMax" value="DEBUG"/>
        </filter>
    </appender>
    <appender name="DAILYROLLINGFILE" class="org.apache.log4j.DailyRollingFileAppender">
        <param name="File" value="log4j.log"/>
        <param name="DatePattern" value="yyyy-MM-dd"/>
        <param name="Append" value="true"/>
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern"
                   value="%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][Class:%c Method: %l]%n%p:%m%n"/>
        </layout>
    </appender>


    <root>
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="DAILYROLLINGFILE"/>
    </root>
</log4j:configuration>
语法 阐明
%c a.b.c
%c{2} b.c
%20c (若名字空间长度小于 20,则右边用空格填充)
%-20c (若名字空间长度小于 20,则左边用空格填充)
%.30c (若名字空间长度超过 30,截去多余字符)
%20.30c (若名字空间长度小于 20,则右边用空格填充;若名字空间长度超过 30,截去多余字符)
%-20.30c (若名字空间长度小于 20,则左边用空格填充;若名字空间长度超过 30,截去多余字符)
%C org.apache.xyz.SomeClass
%C{1} SomeClass
%d{yyyy/MM/dd HH:mm:ss,SSS} 2000/10/12 11:22:33,117
%d{ABSOLUTE} 11:22:33,117
%d{DATE} 12 Oct 2000 11:22:33,117
%d{ISO8601} 2000-10-12 11:22:33,117
%F MyClass.java
%l MyClass.main(MyClass.java:123)
%L 123
%m This is a message for debug.
%M main
%n Windows 平台下示意 rn,UNIX 平台下示意 n
%p INFO
%r 1215
%t MyClass
%% %

1.3.4 logback

http://logback.qos.ch/manual/…

1)配置文件:

  • Logback tries to find a file called logback-test.xml in the classpath.
  • If no such file is found, logback tries to find a file called logback.groovy in the classpath.
  • If no such file is found, it checks for the file logback.xml in the classpath..
  • If no such file is found, service-provider loading facility (introduced in JDK 1.6) is used to resolve the implementation of com.qos.logback.classic.spi.Configurator interface by looking up the file META-INF\services\ch.qos.logback.classic.spi.Configurator in the class path. Its contents should specify the fully qualified class name of the desired Configurator implementation.
  • If none of the above succeeds, logback configures itself automatically using the BasicConfigurator which will cause logging output to be directed to the console.

2)级别:

  • 日志打印级别 ALL > TRACE > FATAL > DEBUG > INFO > WARN > ERROR > OFF
  • 日志输入级别 TRACE > DEBUG > INFO > WARN > ERROR

3)处理器:

http://logback.qos.ch/manual/…


4)格式化:

http://logback.qos.ch/manual/…

5) 代码实战:

  • Appender:Console,Rollingfile,DB
  • Layout:Xml,Pattern,Html , 自定义
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="3 seconds" debug="false">
    <!-- lOGGER  PATTERN 依据集体爱好抉择匹配  -->
    <property name="logPattern" value="logback:[%-5level] [%date{yyyy-MM-dd HH:mm:ss.SSS}] %logger{96} [%line] [%thread]- %msg%n"></property>

    <!-- 动静日志级别 -->
    <jmxConfigurator/>

    <!-- 控制台的规范输入 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <charset>UTF-8</charset>
            <pattern>${logPattern}</pattern>
        </encoder>
    </appender>

    <!-- 滚动文件  -->
    <appender name="ROLLING_FILE"  class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUG</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <file>./logback.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>./logback.log.%d{yyyy-MM-dd}.zip</fileNamePattern>
            <!-- 最大保留工夫 -->
            <maxHistory>2</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${logPattern}</pattern>
        </encoder>
    </appender>

    <!-- DB  -->
    <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
        <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
            <driverClass>com.mysql.jdbc.Driver</driverClass>
            <url>jdbc:mysql://172.17.0.203:3306/log?useSSL=false</url>
            <user>root</user>
            <password>root</password>
        </connectionSource>
    </appender>
    
    <!-- ASYNC_LOG  -->
    <appender name="ASYNC_LOG" class="ch.qos.logback.classic.AsyncAppender">
        <!-- 不失落日志. 默认的, 如果队列的 80% 已满, 则会抛弃 TRACT、DEBUG、INFO 级别的日志 -->
        <discardingThreshold>0</discardingThreshold>
        <!-- 更改默认的队列的深度, 该值会影响性能. 默认值为 256 -->
        <queueSize>3</queueSize>
        <appender-ref ref="STDOUT"/>
    </appender>


    <!-- 日志的记录级别 -->
    <!-- 在定义后援用 APPENDER -->
    <root level="DEBUG">
        <!--  控制台  -->
        <appender-ref ref="STDOUT"/>
        <!--  ROLLING_FILE  -->
        <appender-ref ref="ROLLING_FILE"/>
        <!-- ASYNC_LOG -->
        <appender-ref ref="ASYNC_LOG"/>
    </root>

</configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        <!--<charset>UTF-8</charset>-->
        <!--<pattern>${logPattern}</pattern>-->

        <!--<layout class="com.itheima.logback.MySampleLayout" />-->

        <layout class="ch.qos.logback.classic.html.HTMLLayout">
            <pattern>%relative%thread%mdc%level%logger%msg</pattern>
        </layout>

        <!--<layout class="ch.qos.logback.classic.log4j.XMLLayout">-->
            <!--<locationInfo>false</locationInfo>-->
        <!--</layout>-->
    </encoder>
</appender>

1.3.5 jcl

1)配置文件:

  • 首先在 classpath 下寻找 commons-logging.properties 文件。如果找到,则应用其中定义的 Log 实现类;如果找不到,则在查找是否已定义零碎环境变量 org.apache.commons.logging.Log,找到则应用其定义的 Log 实现类;
  • 查看 classpath 中是否有 Log4j 的包,如果发现,则主动应用 Log4j 作为日志实现类;
  • 否则,应用 JDK 本身的日志实现类(JDK1.4 当前才有日志实现类);
  • 否则,应用 commons-logging 本人提供的一个简略的日志实现类 SimpleLog;

2)级别:

  • jcl 有 5 个级别:trace < debug < info < warn < error

3)代码实战:

  • 日志查找程序:log4j-jul-slog
  • commons-logging.properties 配置
# 指定日志对象:#org.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
#org.apache.commons.logging.Log=org.apache.commons.logging.impl.Jdk14Logger
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger

#指定日志工厂:org.apache.commons.logging.LogFactory = org.apache.commons.logging.impl.LogFactoryImpl

1.3.6 log4j2

1)配置文件:

  • Log4j will inspect the log4j.configurationFile system property and, if set, will attempt to load the configuration using the ConfigurationFactory that matches the file extension.
  • If no system property is set the YAML ConfigurationFactory will look for log4j2-test.yaml or log4j2-test.yml in the classpath.
  • If no such file is found the JSON ConfigurationFactory will look for log4j2-test.json or log4j2-test.jsn in the classpath.
  • If no such file is found the XML ConfigurationFactory will look for log4j2-test.xml in the classpath.
  • If a test file cannot be located the YAML ConfigurationFactory will look for log4j2.yaml or log4j2.yml on the classpath.
  • If a YAML file cannot be located the JSON ConfigurationFactory will look for log4j2.json or log4j2.jsn on the classpath.
  • If a JSON file cannot be located the XML ConfigurationFactory will try to locate log4j2.xml on the classpath.
  • If no configuration file could be located the DefaultConfiguration will be used. This will cause logging output to go to the console.

2)级别:

  • 从低到高为:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF

3)处理器:

http://logging.apache.org/log…

  • FileAppender 一般地输入到本地文件
  • KafkaAppender 输入到 kafka 队列
  • FlumeAppender 将几个不同源的日志会集、集中到一处。
  • JMSQueueAppender,JMSTopicAppender 与 JMS 相干的日志输入
  • RewriteAppender 对日志事件进行掩码或注入信息
  • RollingFileAppender 对日志文件进行封存(具体)
  • RoutingAppender 在输出地之间进行筛选路由
  • SMTPAppender 将 LogEvent 发送到指定邮件列表
  • SocketAppender 将 LogEvent 以一般格局发送到近程主机
  • SyslogAppender 将 LogEvent 以 RFC 5424 格局发送到近程主机
  • AsynchAppender 将一个 LogEvent 异步地写入多个不同输出地
  • ConsoleAppender 将 LogEvent 输入到命令行
  • FailoverAppender 保护一个队列,零碎将尝试向队列中的 Appender 顺次输入 LogEvent,直到有一个胜利为止

4)格式化:

http://logging.apache.org/log…

5)代码实战:

  • Appender:Console,File,RollingFile
  • Layout:csv,json,Xml,Pattern,Html
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--status="WARN" : 用于设置 log4j2 本身外部日志的信息输入级别,默认是 OFF-->
<!--monitorInterval="30"  : 距离秒数, 自动检测配置文件的变更和重新配置自身 -->
<configuration status="info" monitorInterval="30">
    <Properties>
        <!-- 自定义一些常量,之后应用 ${变量名}援用 -->
        <Property name="pattern">log4j2:[%-5p]:%d{YYYY-MM-dd HH:mm:ss} [%t] %c{1}:%L - %msg%n</Property>
    </Properties>
    <!--appenders: 定义输入内容, 输入格局, 输入形式, 日志保留策略等, 罕用其下三种标签[console,File,RollingFile]-->
    <appenders>
        <!--console : 控制台输入的配置 -->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="${pattern}"/>
        </Console>
        <!--File : 同步输入日志到本地文件 -->
        <!--append="false" : 依据其下日志策略, 每次清空文件从新输出日志, 可用于测试 -->
        <File name="File" fileName="./log4j2-file.log" append="false">
            <PatternLayout pattern="${pattern}"/>
        </File>
        <RollingFile name="RollingFile" fileName="./log4j2-rollingfile.log"
                     filePattern="./$${date:yyyy-MM}/log4j2-%d{yyyy-MM-dd}-%i.log">
            <!--ThresholdFilter : 日志输入过滤 -->
            <!--level="info" : 日志级别,onMatch="ACCEPT" : 级别在 info 之上则承受,onMismatch="DENY" : 级别在 info 之下则回绝 -->
            <ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${pattern}"/>
            <!-- Policies : 日志滚动策略 -->
            <Policies>
                <!-- TimeBasedTriggeringPolicy : 工夫滚动策略,
                默认 0 点产生新的文件,
                interval="6" : 自定义文件滚动工夫距离, 每隔 6 小时产生新文件,
                modulate="true" : 产生文件是否以 0 点偏移工夫, 即 6 点,12 点,18 点,0 点 -->
                <TimeBasedTriggeringPolicy interval="6" modulate="true"/>
                <!-- SizeBasedTriggeringPolicy : 文件大小滚动策略 -->
                <SizeBasedTriggeringPolicy size="1 MB"/>
            </Policies>
            <!-- DefaultRolloverStrategy 属性如不设置,则默认为最多同一文件夹下 7 个文件,这里设置了 20 -->
            <DefaultRolloverStrategy max="20"/>
        </RollingFile>

    </appenders>
    <!-- 而后定义 logger,只有定义了 logger 并引入的 appender,appender 才会失效 -->
    <loggers>
        <!-- 过滤掉 spring 和 mybatis 的一些无用的 DEBUG 信息 -->
        <!--Logger 节点用来独自指定日志的模式,name 为包门路, 比方要为 org.springframework 包下所有日志指定为 INFO 级别等。-->
        <logger name="org.springframework" level="INFO"></logger>
        <logger name="org.mybatis" level="INFO"></logger>

        <!--AsyncLogger : 异步日志,LOG4J 有三种日志模式, 全异步日志, 混合模式, 同步日志, 性能从高到底, 线程越多效率越高, 也能够防止日志卡死线程状况产生 -->
        <!--additivity="false" : additivity 设置事件是否在 root logger 输入,为了防止反复输入,能够在 Logger 标签下设置 additivity 为”false”-->
        <AsyncLogger name="AsyncLogger" level="trace" includeLocation="true" additivity="true">
            <appender-ref ref="Console"/>
        </AsyncLogger>

        <logger name="Kafka" additivity="false" level="debug">
            <appender-ref ref="Kafka"/>
            <appender-ref ref="Console"/>
        </logger>

        <!-- Root 节点用来指定我的项目的根日志,如果没有独自指定 Logger,那么就会默认应用该 Root 日志输入 -->
        <root level="info">
            <appender-ref ref="Console"/>
            <!--<appender-ref ref="File"/>-->
            <!--<appender-ref ref="RollingFile"/>-->
            <!--<appender-ref ref="Kafka"/>-->
        </root>
    </loggers>
</configuration>

1.3.7 slf4j

1)配置文件:

具体日志输入内容取决于失效的具体日志实现

2)级别:

slf4j 日志级别有五种:ERROR、WARN、INFO、DEBUG、TRACE,级别从高到底

3)slf 日志实现:

<!--slf 转其余日志 -->
<!--slf - jcl-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jcl</artifactId>
    <version>1.7.30</version>
    <exclusions>
        <exclusion>
            <groupId>*</groupId>
            <artifactId>*</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- slf - log4j -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.30</version>
    <exclusions>
        <exclusion>
            <groupId>*</groupId>
            <artifactId>*</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!-- slf4j - log4j2 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.13.0</version>
    <scope>runtime</scope>
    <optional>true</optional>
    <exclusions>
        <exclusion>
            <groupId>*</groupId>
            <artifactId>*</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<!--slf - jul-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-jdk14</artifactId>
    <version>1.7.30</version>
</dependency>

<!--slf - simplelog-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.30</version>
</dependency>

<!--slf - logback-->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-core</artifactId>
    <version>1.2.3</version>
</dependency>
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

问题:多个桥接器的话,slf4j 怎么解决呢?

应用在 class path 中较早呈现的那个,如在 maven 中,会应用在 pom.xml 中定义较靠前的桥接器(代码验证)

小常识:

桥接器会传递依赖到对应的上游日志组件,比方 slf4j-log4j12 会附带 log4j 的 jar 包依赖(代码验证)

4)其余日志转 slf

<!-- 其余日志转 slf-->
<!--jul - slf-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jul-to-slf4j</artifactId>
    <version>1.7.30</version>
</dependency>

<!--jcl - slf-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.30</version>
</dependency>

<!--log4j - slf-->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>log4j-over-slf4j</artifactId>
    <version>1.7.30</version>
</dependency>

<!--log4j2 - slf-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-to-slf4j</artifactId>
    <version>2.13.0</version>
</dependency>

5)slf4j 日志环

    <!-- 演示实例:slf4j - log4j - slf4j -->
    <!--log4j-->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>apache-log4j-extras</artifactId>
        <version>1.2.17</version>
    </dependency>

    <!--slf4j-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.30</version>
    </dependency>

    <!--log4j - slf-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>log4j-over-slf4j</artifactId>
        <version>1.7.30</version>
    </dependency>

    <!-- slf - log4j -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.30</version>
        <exclusions>
            <exclusion>
                <groupId>*</groupId>
                <artifactId>*</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

依赖展现:


报错后果:

SLF4J: Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError. 
SLF4J: See also http://www.slf4j.org/codes.html#log4jDelegationLoop for more details.
java.lang.ExceptionInInitializerError
    at org.slf4j.impl.StaticLoggerBinder.<init>(StaticLoggerBinder.java:72)
    at org.slf4j.impl.StaticLoggerBinder.<clinit>(StaticLoggerBinder.java:45)
    at org.slf4j.LoggerFactory.bind(LoggerFactory.java:150)
    at org.slf4j.LoggerFactory.performInitialization(LoggerFactory.java:124)
    at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:417)
    at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:362)
    at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:388)
    at com.itheima.slf4j.Demo.<clinit>(Demo.java:8)
Caused by: java.lang.IllegalStateException: Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError. See also http://www.slf4j.org/codes.html#log4jDelegationLoop for more details.
    at org.slf4j.impl.Log4jLoggerFactory.<clinit>(Log4jLoggerFactory.java:54)
    ... 8 more
Exception in thread "main" 
Process finished with exit code 1

教训:应用 slf 桥接和绑定的时候要留心,不要自圆其说造成环

1.4 日志倡议

1.4.1 门面束缚

应用门面,而不是具体实现

应用 Log Facade 能够不便的切换具体的日志实现。而且,如果依赖多个我的项目,应用了不同的 Log Facade,还能够不便的通过 Adapter 转接到同一个实现上。如果依赖我的项目间接应用了多个不同的日志实现,会十分蹩脚。

经验之谈:日志门面,个别当初举荐应用 Log4j-API 或者 SLF4j,不举荐持续应用 JCL。

1.4.2 繁多准则

只增加一个日志实现

我的项目中应该只应用一个具体的 Log Implementation,如果在依赖的我的项目中,应用的 Log Facade 不反对以后 Log Implementation,就增加适合的桥接器。

经验之谈:jul 性能个别,log4j 性能也有问题而且不再保护,倡议应用 Logback 或者 Log4j2。

1.4.3 依赖束缚

日志实现坐标应该设置为 optional 并应用 runtime scope

在我的项目中,Log Implementation 的依赖强烈建议设置为 runtime scope,并且设置为 optional。例如我的项目中应用了 SLF4J 作为 Log Facade,而后想应用 Log4j2 作为 Implementation,那么应用 maven 增加依赖的时候这样设置:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>${log4j.version}</version>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>${log4j.version}</version>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>

设为 optional,依赖不会传递,这样如果你是个 lib 我的项目,而后别的我的项目应用了你这个 lib,不会被引入不想要的 Log Implementation 依赖;

Scope 设置为 runtime,是为了避免开发人员在我的项目中间接应用 Log Implementation 中的类,强制束缚开发人员应用 Facade 接口。

1.4.4 防止传递

尽量用 exclusion 排除依赖的第三方库中的日志坐标

同上一个话题,第三方库的开发者却未必会把具体的日志实现或者桥接器的依赖设置为 optional,而后你的我的项目就会被迫传递引入这些依赖,而这些日志实现未必是你想要的,比方他依赖了 Log4j,你想应用 Logback,这时就很难堪。另外,如果不同的第三方依赖应用了不同的桥接器和 Log 实现,极有可能会造成环。

这种状况下,举荐的解决办法,是应用 exclude 来排除所有的这些 Log 实现和桥接器的依赖,只保留第三方库里面对 Log Facade 的依赖。

实例:依赖 jstorm 会引入 Logback 和 log4j-over-slf4j,如果你在本人的我的项目中应用 Log4j 或其余 Log 实现的话,就须要加上 exclusion:

<dependency>
    <groupId>com.alibaba.jstorm</groupId>
    <artifactId>jstorm-core</artifactId>
    <version>2.1.1</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
        </exclusion>
        <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </exclusion>
    </exclusions>
</dependency>

1.4.5 留神写法

防止为不会输入的 log 买单

Log 库都能够灵便的设置输入级别,所以每一条程序中的 log,都是有可能不会被输入的。这时候要留神不要额定的付出代价。实例如下:

logger.debug("this is debug:" + message);
logger.debug("this is json msg: {}", toJson(message));

后面讲到,第一条的字符串拼接,即便日志级别高于 debug 不会打印,仍然会做字符串连贯操作;第二条尽管用了 SLF4J/Log4j2 中的懒求值形式,然而 toJson()这个函数却是总会被调用并且开销更大。举荐的写法如下:

// SLF4J/LOG4J2
logger.debug("this is debug:{}", message); 

// LOG4J2
logger.debug("this is json msg: {}", () -> toJson(message)); 

// SLF4J/LOG4J2
if (logger.isDebugEnabled()) {logger.debug("this is debug:" + message); 
}

1.4.6 缩小剖析

输入的日志中尽量不要应用行号,函数名等信息

起因是,为了获取语句所在的函数名,或者行号,log 库的实现都是获取以后的 stacktrace,而后剖析取出这些信息,而获取 stacktrace 的代价是很低廉的。如果有很多的日志输入,就会占用大量的 CPU。在没有非凡须要的状况下,倡议不要在日志中输入这些这些字段。

1.4.7 精简至上

log 中尽量不要输入稀奇古怪的字符,这是个习惯和束缚问题。有的同学习惯用这种语句:

logger.debug("=====================================:{}",message);

输入了大量无关字符,尽管本人一时畅快,然而如果所有人都这样做的话,那 log 输入就没法看了!正确的做法是日志只输入必要信息,如果要过滤,前期应用 grep 来筛选,只查本人关怀的日志。

本文由传智教育博学谷 – 狂野架构师教研团队公布
转载请注明出处!

退出移动版