阅读 197

Java日志框架总结

一、日志的概念

日志文件是用于记录系统操作事件的文件集合,可分为事件日志和消息日志。具有处理历史数据、诊断问题的追踪以及理解系统的活动等重要作用。

二、JAVA日志框架

2.1 为什么要用日志框架

因为软件系统发展到今天已经很复杂了,特别是服务器端软件,涉及到的知识,内容,问题太多。在某些方面使用别人成熟的框架,就相当于让别人帮你完成一些基础工作,你只需要集中精力完成系统的业务逻辑设计。而且框架一般是成熟、稳健的,他可以处理系统很多细节问题,比如事务处理、安全性、数据流控制等问题。还有框架一般都经过很多人使用,所以结构很好,所以扩展性也很好,而且它是不断升级的,你可以直接享受别人升级代码带来的好处。

2.2 现有的日志框架

日志实现: JUL(java util logging)、logback、log4j、log4j2
日志门面: JCL(Jakarta Commons Logging)、slf4j( Simple Logging Facade for Java)\

为什么要日志门面?
当我们的系统变的更加复杂的时候,我们的日志就容易发生混乱。随着系统开发的进行,可能会更新不 同的日志框架,造成当前系统中存在不同的日志依赖,让我们难以统一的管理和控制。就算我们强制要 求所有的模块使用相同的日志框架,系统中也难以避免使用其他类似spring,mybatis等其他的第三方框 架,它们依赖于我们规定不同的日志框架,而且他们自身的日志系统就有着不一致性,依然会出来日志 体系的混乱。
所以我们需要借鉴JDBC的思想,为日志系统也提供一套门面,那么我们就可以面向这些接口规范来开 发,避免了直接依赖具体的日志框架。这样我们的系统在日志中,就存在了日志的门面和日志的实现。

三、JUL

JUL全称Java util Logging是java原生的日志框架,使用时不需要另外引用第三方类库,相对其他日志框架使用方便,学习简单,能够在小型应用中灵活使用。

3.1 JUL架构介绍

image.png

  • Loggers:被称为记录器,应用程序通过获取Logger对象,调用其API来来发布日志信息。Logger通常时应用程序访问日志系统的入口程序。
  • Appenders:也被称为Handlers,每个Logger都会关联一组Handlers,Logger会将日志交给关联Handlers处理,由Handlers负责将日志做记录。Handlers在此是一个抽象,其具体的实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等。
  • Layouts:也被称为Formatters,它负责对日志事件中的数据进行转换和格式化。Layouts决定了数据在一条日志记录中的最终形式。
  • Level:每条日志消息都有一个关联的日志级别。该级别粗略指导了日志消息的重要性和紧迫,我可以将Level和Loggers,Appenders做关联以便于我们过滤消息。
  • Filters:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放过。

总结
用户使用Logger来进行日志记录,Logger持有若干个Handler,日志的输出操作是由Handler完成的。在Handler在输出日志前,会经过Filter的过滤,判断哪些日志级别过滤放行哪些拦截,Handler会将日 志内容输出到指定位置(日志文件、控制台等)。Handler在输出日志时会使用Layout,将输出内容进行排版。

3.2 代码案例

/** 入门案例 **/

// 1.获取日志记录器对象
Logger logger = Logger.getLogger("com.example.JULTest");

// 2.日志记录输出
logger.info("hello jul");

// 通用方法进行日志记录
logger.log(Level.INFO,"info msg");

// 通过占位符 方式输出变量值
String name = "itcast";
Integer age = 13;
logger.log(Level.INFO,"用户信息:{0},{1}",new Object[]{name,age});
复制代码
/** 日志级别 **/

// 1.获取日志记录器对象
Logger logger = Logger.getLogger("com.example.JULTest");
// 2.日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info"); // 默认日志输出级别
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
复制代码
/** 自定义日志级别 **/
 
// 1.获取日志记录器对象
Logger logger = Logger.getLogger("com.example.JULTest");

// 关闭系统默认配置
logger.setUseParentHandlers(false);

// 自定义配置日志级别
// 创建ConsolHhandler 控制台输出
ConsoleHandler consoleHandler = new ConsoleHandler();

// 创建简单格式转换对象
SimpleFormatter simpleFormatter = new SimpleFormatter();

// 进行关联
consoleHandler.setFormatter(simpleFormatter);
logger.addHandler(consoleHandler);

// 配置日志具体级别
logger.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);

// 场景FileHandler  文件输出
FileHandler fileHandler = new FileHandler("/logs/jul.log");

// 进行关联
fileHandler.setFormatter(simpleFormatter);
logger.addHandler(fileHandler);

// 2.日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info"); // 默认日志输出级别
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
复制代码
/** Logger对象父子关系 **/

/**
 * JUL中Logger之间存在父子关系,这种父子关系通过树状结构存储,
 * JUL在初始化时会创建一个顶层RootLogger作为所有Logger父Logger,
 * 存储上作为树状结构的根节点。并父子关系通过路径来关联。
 */

Logger logger1 = Logger.getLogger("com.example");
Logger logger2 = Logger.getLogger("com");

// 测试
System.out.println(logger1.getParent() == logger2);
// 所有日志记录器的顶级父元素 LogManager$RootLogger,name ""
System.out.println("logger2 Parent:"+logger2.getParent() + ",name:" + logger2.getParent().getName());

// 关闭默认配置
logger2.setUseParentHandlers(false);

// 设置logger2日志级别
// 自定义配置日志级别
// 创建ConsolHhandler 控制台输出
ConsoleHandler consoleHandler = new ConsoleHandler();

// 创建简单格式转换对象
SimpleFormatter simpleFormatter = new SimpleFormatter();

// 进行关联
consoleHandler.setFormatter(simpleFormatter);
logger2.addHandler(consoleHandler);

// 配置日志具体级别
logger2.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);

logger1.severe("severe");
logger1.warning("warning");
logger1.info("info");
logger1.config("config");
logger1.fine("fine");
logger1.finer("finer");
logger1.finest("finest");
复制代码

四、LOG4J

Log4j是Apache下的一款开源的日志框架,通过在项目中使用 Log4J,我们可以控制日志信息输出到控制台、文件、甚至是数据库中。我们可以控制每一条日志的输出格式,通过定义日志的输出级别,可以 更灵活的控制日志的输出过程。方便项目的调试。

4.1 日志级别

每个Logger都被了一个日志级别(log level),用来控制日志信息的输出。日志级别从高到低分为:

  • fatal 指出每个严重的错误事件将会导致应用程序的退出。
  • error 指出虽然发生错误事件,但仍然不影响系统的继续运行。
  • warn 表明会出现潜在的错误情形。
  • info 一般和在粗粒度级别上,强调应用程序的运行全程。
  • debug 一般用于细粒度级别上,对调试应用程序非常有帮助。
  • trace 是程序追踪,可以用于输出程序运行中的变量,显示执行的流程。

还有两个特殊的级别:

  • OFF 可用来关闭日志记录。
  • ALL 启用所有消息的日志记录。

4.2 Log4j组件

Log4J 主要由 Loggers (日志记录器)、Appenders(输出端)和Layout(日志格式化器)组成。其中 Loggers 控制日志的输出级别与日志是否输出;Appenders指定日志的输出方式(输出到控制台、文件等);Layout 控制日志信息的输出格式。

Loggers:
日志记录器,负责收集处理日志记录,实例的命名就是类“XX”的full quailied name(类的全限定名), Logger的名字大小写敏感,其命名有继承机制:例如:name为org.apache.commons的logger会继承 name为org.apache的logger。Log4J中有一个特殊的logger叫做“root”,他是所有logger的根,也就意味着其他所有的logger都会直接或者间接地继承自root。rootlogger可以用Logger.getRootLogger()方法获取。

Appenders
用来指定日志输出到哪个地方,可以同时指定日志的输出目的地。Log4j 常用的输出目的地有以下几种:

输出端类型作用
ConsoleAppender将日志输出到控制台
FileAppender将日志输出到控制台
DailyRollingFileAppender将日志输出到一个日志文件,并且每天输出到一个新的文件
RollingFileAppender将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件
JDBCAppender把日志信息保存到数据库中

Layouts
布局器 Layouts用于控制日志输出内容的格式,让我们可以使用各种需要的格式输出日志。Log4j常用的Layouts:

输出端类型作用
HTMLLayout格式化日志输出为HTML表格形式
SimpleLayout简单的日志输出格式化,打印的日志格式为(info - message)
PatternLayout最强大的格式化期,可以根据自定义格式输出日志,如果没有指定转换格式,就是用默认的转换格式

log4j 采用类似 C 语言的 printf 函数的打印格式格式化日志信息,具体的占位符及其含义如下:

%m 输出代码中指定的日志信息
%p 输出优先级,及 DEBUG、INFO 等
%n 换行符(Windows平台的换行符为 "\n",Unix 平台为 "\n")
%r 输出自应用启动到输出该 log 信息耗费的毫秒数
%c 输出打印语句所属的类的全名
%t 输出产生该日志的线程全名
%d 输出服务器当前时间,默认为 ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日HH:mm:ss}
%l 输出日志时间发生的位置,包括类名、线程、及在代码中的行数。如:Test.main(Test.java:10)
%F 输出日志消息产生时所在的文件名称
%L 输出代码中的行号 %% 输出一个 "%" 字符
复制代码

四、SLF4J

简单日志门面(Simple Logging Facade For Java) SLF4J主要是为了给Java日志访问提供一套标准、规范的API框架,其主要意义在于提供接口,具体的实现可以交由其他日志框架,例如log4j和logback等。当然slf4j自己也提供了功能较为简单的实现,但是一般很少用到。对于一般的Java项目而言,日志框架会选择slf4j-api作为门面,配上具体的实现框架(log4j、logback等),中间使用桥接器完成桥接。

4.1为什么使用SLF4J

  • 使用SLF4J框架,可以在部署时迁移到所需的日志记录框架。
  • SLF4J提供了对所有流行的日志框架的绑定,例如log4j,JUL,Simple logging和NOP。因此可以在部署时切换到任何这些流行的框架。
  • 无论使用哪种绑定,SLF4J都支持参数化日志记录消息。由于SLF4J将应用程序和日志记录框架分离, 因此可以轻松编写独立于日志记录框架的应用程序。而无需担心用于编写应用程序的日志记录框架。
  • SLF4J提供了一个简单的Java工具,称为迁移器。使用此工具,可以迁移现有项目,这些项目使用日志 框架(如Jakarta Commons Logging(JCL)或log4j或Java.util.logging(JUL))到SLF4J。

4.2 代码案例

public static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);

// 快速入门
@Test
public void testQuick()throws Exception{
    for (int i = 0; i < 10000; i++) {

        // 日志输出
        LOGGER.error("error");
        LOGGER.warn("wring");
        LOGGER.info("info");
        LOGGER.debug("debug");// 默认级别
        LOGGER.trace("trace");
    }
}
复制代码

4.3 SLF4J原理解析

  1. SLF4J通过LoggerFactory加载日志具体的实现对象;
  2. LoggerFactory在初始化的过程中,会通过performInitialization()方法绑定具体的日志实现;
  3. 在绑定具体实现的时候,通过类加载器,加载org/slf4j/impl/StaticLoggerBinder.class;
  4. 所以,只要是一个日志实现框架,在org.slf4j.impl包中提供一个自己的StaticLoggerBinder类,在其中提供具体日志实现的LoggerFactory就可以被SLF4J所加载。

五、Logback

Logback主要分为三个模块:

  • logback-core:其它两个模块的基础模块
  • logback-classic:它是log4j的一个改良版本,同时它完整实现了slf4j API
  • logback-access:访问模块与Servlet容器集成提供通过Http来访问日志的功能

logback会依次读取以下类型配置文件:

  • logback.groovy
  • logback-test.xml
  • logback.xml

如果均不存在会采用默认配置

5.1 logback组件之间的关系

  • Logger:日志的记录器,把它关联到应用的对应的context上后,主要用于存放日志对象,也 可以定义日志类型、级别。
  • Appender:用于指定日志输出的目的地,目的地可以是控制台、文件、数据库等等。
  • Layout:负责把事件转换成字符串,格式化的日志信息的输出。在logback中Layout对象被封装在encoder中。

5.2 代码案例

/**快速入门**/

public static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);

@Test
public void testQuick()throws Exception{
    for (int i = 0; i < 10000; i++) {
        // 日志输出
        LOGGER.error("error");
        LOGGER.warn("wring");
        LOGGER.info("info");
        LOGGER.debug("debug");// 默认级别
        LOGGER.trace("trace");
    }
}
复制代码

5.3 配置案例

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

    <!--
        配置集中管理属性
        我们可以直接改属性的 value 值
        格式:${name}
    -->
    <property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n"></property>
    <!--
    日志输出格式:
        %-5level
        %d{yyyy-MM-dd HH:mm:ss.SSS}日期
        %c类的完整名称
        %M为method
        %L为行号
        %thread线程名称
        %m或者%msg为信息
        %n换行
      -->
    <!--定义日志文件保存路径属性-->
    <property name="log_dir" value="/logs"></property>


    <!--控制台日志输出的 appender-->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!--控制输出流对象 默认 System.out 改为 System.err-->
        <target>System.err</target>
        <!--日志消息格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <!--日志文件输出的 appender-->
    <appender name="file" class="ch.qos.logback.core.FileAppender">
        <!--日志文件保存路径-->
        <file>${log_dir}/logback.log</file>
        <!--日志消息格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>

    <!--html 格式日志文件输出 appender-->
    <appender name="htmlFile" class="ch.qos.logback.core.FileAppender">
        <!--日志文件保存路径-->
        <file>${log_dir}/logback.html</file>
        <!--html 消息格式配置-->
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="ch.qos.logback.classic.html.HTMLLayout">
                <pattern>%-5level%d{yyyy-MM-dd HH:mm:ss.SSS}%c%M%L%thread%m</pattern>
            </layout>
        </encoder>
    </appender>


    <!--日志拆分和归档压缩的 appender 对象-->
    <appender name="rollFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <!--日志文件保存路径-->
        <file>${log_dir}/roll_logback.log</file>
        <!--日志消息格式配置-->
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${pattern}</pattern>
        </encoder>
        <!--指定拆分规则-->
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <!--按照时间和压缩格式声明拆分的文件名-->
            <fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
            <!--按照文件大小拆分-->
            <maxFileSize>1MB</maxFileSize>
        </rollingPolicy>
        <!--日志级别过滤器-->
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!--日志过滤规则-->
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--异步日志-->
    <appender name="async" class="ch.qos.logback.classic.AsyncAppender">
        <!--指定某个具体的 appender-->
        <appender-ref ref="rollFile"/>
    </appender>


    <!--root logger 配置-->
    <root level="ALL">
        <appender-ref ref="console"/>
        <appender-ref ref="async"/>
    </root>

    <!--自定义 looger 对象
        additivity="false" 自定义 logger 对象是否继承 rootLogger
     -->
    <logger name="com.example" level="info" additivity="false">
        <appender-ref ref="console"/>
    </logger>
</configuration>
复制代码
文章分类
后端
文章标签