关于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来筛选,只查本人关怀的日志。

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理