# 学习目标

  • 日志的作用和目的

  • 日志的框架

  • JUL的使用

  • log4j的使用

  • JCL的使用

# 日志文件

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

在编写代码的时候,通常需要通过日志信息程序运行是否正常,出错时,根据日志信息能快速定位到错误地点。对于软件系统的开发,以及日后的维护工作,都有极其重要的作用。

# 日志框架

# 问题

日志框架需要考虑的问题:

  • 控制日志输出的内容和格式

  • 控制日志输出的位置

  • 日志优化:一部日志,日志文件的归档和压缩

  • 日志系统的维护

  • 面向接口开发——日志门面

# 现有的日志框架

日志分为两个大的部分,日志实现与日志门面。所谓日志门面,就是为各种日志API提供一个统一的接口,类似于操作数据库的JDBC,针对不同的数据库,都是操作同一套API接口。

日志门面:

  • JCL

  • slf4j

日志实现

  • JUL

  • logback

  • log4j

  • log4j2

# JUL

JUL全称Java util Loggingjava原生的日志框架,使用时,不需要引用第三方类库,方便、简单、灵活。

Logger
Logger
Handler
Handler
Application
Application
Outside World
Outside World
FilterFilterFilter
Viewer does not support full SVG 1.1

Loggers:被称为日志记录器,应用程序通过获取Logger对象,调用其API来发布日志信息。Logger通常时应用系统访问日志系统的入口程序。

Appenders:也被称为Handlers,每个Logger都会关联一组HandlersLogger会将日志交给关联Handlers处理,由Handlers负责将日志做记录。Handlers在此是一个抽象,其具体的实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等。

Layouts:也被称为Formatters,它负责对日志事件中的数据进行转换和格式化。Layouts决定了数据在一条日志记录中的最终形式。

Level:每条日志消息都有一个关联的日志级别。该级别展现日志消息的重要性和紧迫性,不同的日志框架该级别会有细微差别。

Filters:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放行。

# 日志级别

severe

当程序运行错误的时候,就可以用severe来记录错误信息。

warning

记录程序运行遇到的问题,一般不会造成程序中止运行,但是也需要注意。

info

记录程序运行中的一些消息,例如数据库连接信息,io的传递信息等等。

config

记录配置信息。

fine

finer

finest

这三者都是记录debug信息。使用时用其中一个就行了。

除上述外,还有两个特殊级别,off(用来关闭日志记录),all(用来开启日志记录)。

# 示例代码

基础类

package org.example.test;
public class JULTest {
    public static void main(String[] args) throws IOException {
    }
}

基本使用

// 获取日志记录器对象
Logger logger = Logger.getLogger("org.example.test.JULTest");
// 日志输出
logger.info("hello jul");
// 通用方法进行日记记录
logger.log(Level.INFO,"info msg");
String name = "小明";
String age = 12;
logger.log(Level.INFO,"用户信息:{0} {1}",new Object[]{name,age});

日志级别

// 获取日志记录器对象
Logger logger = Logger.getLogger("org.example.test.JULTest");
// 日志记录输出
// jul默认的日志级别是info
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");

JUL的默认级别时info,因此上述代码只会输出到info

自定义配置日志级别

// 获取日志记录器对象
Logger logger = Logger.getLogger("org.example.test.JULTest");
// 自定义日志级别
// 关闭JUL默认的日志级别
logger.setUseParentHandlers(false);
//创建ConsoleHandler
ConsoleHandler consoleHandler = new ConsoleHandler();
// 创建格式转换对象
SimpleFormatter simpleFormatter = new SimpleFormatter();
// 进行关联
consoleHandler.setFormatter(simpleFormatter);
logger.addHandler(consoleHandler);
// 配置自定义的日志级别
logger.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
// 日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");

在上述代码中,关闭JUL的默认配置,将日志的输出级别设置为ALL,所有的日志信息都将会输出。

日志输出到文件

// 获取日志记录器对象
Logger logger = Logger.getLogger("org.example.test.JULTest");
// 关闭JUL默认的日志级别
logger.setUseParentHandlers(false);
// 创建FileHandler,这里写日志文件的位置
FileHandler fileHandler = new FileHandler("D:\\jul.log");
// 创建格式转换对象
SimpleFormatter simpleFormatter = new SimpleFormatter();
// 进行关联
fileHandler.setFormatter(simpleFormatter);
logger.addHandler(fileHandler);
// 配置自定义的日志级别
logger.setLevel(Level.ALL);
fileHandler.setLevel(Level.ALL);
// 日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");

上述代码将日志信息输出到了文件中,日志级别同样是ALL

logger对象父子关系

// 获取日志记录器对象
Logger logger1 = Logger.getLogger("org.example.test.JULTest");
// 获取日志记录器对象
Logger logger2 = Logger.getLogger("org.example.test");
// 测试是否是父子关系
System.out.println(logger1.getParent() == logger2);
// 所有日志记录器的顶级父元素 LogManager$RootLogger
System.out.println("Logger2 Parent:"+logger2.getParent()+",name:"+logger2.getParent().getName());
// 关闭JUL默认的日志级别
logger2.setUseParentHandlers(false);
//创建ConsoleHandler
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");

上述代码展现了父子对象Logger对象关系,子承父业,父对象logger2设置日志级别为ALL,子对象的默认日志级别也变成了ALL

# 日志配置

resource目录下创建logging.properties文件

# RootLogger 顶级父元素制定的默认处理器为: ConsoleHandler
handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler
# RootLogger 顶级父元素默认的日志级别为:ALL
.level= ALL
# 自定义 Logger 使用
org.example.handlers = java.util.logging.ConsoleHandler
org.example.level = CONFIG
org.example.userParentHandlers = false
# 向日志文件文件输出 handler 对象
# 指定日志文件的路径
java.util.logging.FileHandler.pattern= D:/logs/java%u.log
# 指定日志文件内容大小
java.util.logging.FileHandler.limit= 50000
# 指定日志文件级别
java.util.logging.FileHandler.level= ALL
# 指定日志文件数量
java.util.logging.FileHandler.count= 1
# 指定日志文件的输出格式
java.util.logging.FileHandler.formatter= java.util.logging.SimpleFormatter
# 指定文件输出以追加的方式
java.util.logging.FileHandler.append= false
# 向控制台输出的 handler 对象
# 指定 handler 对象的日志级别
java.util.logging.ConsoleHandler.level= ALL
# 指定 handler 对象的日志消息信息格式对象
java.util.logging.ConsoleHandler.formatter= java.util.logging.SimpleFormatter
# 指定 handler 对象的字符集
java.util.logging.ConsoleHandler.encoding= UTF-8

# 日志实现原理

web应用
web应用
Logger
Logger
LogRecord
LogRecord
Handler
Handler
输出
输出
LogManager
LogManager
Level
Level
Filter
Filter
Formatter
Formatter
Viewer does not support full SVG 1.1

1 初始化LogManager

1.1 LogManager加载logging.properties配置 1.2 添加LoggerLogManager

2 从单例LogManager获取Logger

3 设置级别Level,并指定日志记录LogRecord

4 Filter提供了日志级别之外更细粒度的控制

5 Handler是用来处理日志输出位置

6 Formatter是用来格式化LogRecord

# Log4j

Log4jApache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件等,可以定制每一条日志输出的格式,控制日志输出的级别,完成这些操作,仅仅只需要修改配置文件,并不用修改业务逻辑代码。

使用Log4j,首先需要导入Log4jjar包,在maven项目中,只需引用相应坐标即可:

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

# 日志级别

Log4j一共提供有六个日志级别:

fatal

表示严重错误,通常会造成系统崩溃中止运行。

error

表示错误信息,不会影响系统运行。

warn

表示警告信息,可能会发生的问题。

info

程序运行中的信息,数据库连接,io操作等等。

debug

表示调试信息,一般在开发中使用。

trace

表示追踪信息,记录所有的流程信息。

Log4j的默认级别为debug

# 示例代码

基础类

package org.example;
public class App{
    public static void main(String[] args){
    }
}

入门代码

// 初始化配置信息,暂不使用配置文件
BasicConfigurator.configure();
// 获取日志记录器对象
Logger logger = Logger.getLogger(App.class);
// 严重错误,一般会造成系统崩溃,或者不能运行
logger.fatal("fatal");
// 错误信息,不会影响系统的运行
logger.error("error");
// 警告信息,可能会发生问题
logger.warn("warn");
// 运行信息,数据连接,网络连接,IO操作等等
logger.info("info");
// 调试信息,一般在开发中使用,记录程序变化
logger.debug("debug");
// 追踪信息,记录程序所有的流程信息
logger.trace("trace");

基本使用

一般Log4j的配置信息主要就是通过配置文件,因此代码一般不做过多的改动,只需要将上面的默认配置文件注释掉或者删掉,在resource目录下添加配置文件log4j.properties即可。

// 获取日志记录器对象
Logger logger = Logger.getLogger(App.class);
// 严重错误,一般会造成系统崩溃,或者不能运行
logger.fatal("fatal");
// 错误信息,不会影响系统的运行
logger.error("error");
// 警告信息,可能会发生问题
logger.warn("warn");
// 运行信息,数据连接,网络连接,IO操作等等
logger.info("info");
// 调试信息,一般在开发中使用,记录程序变化
logger.debug("debug");
// 追踪信息,记录程序所有的流程信息
logger.trace("trace");

# 日志配置

resource目录中创建log4j.properties文件

基本配置

最简单的配置,仅仅只配置了,日志级别,输出位置,输出格式。

# 指定 RootLogger 顶级父元素默认配置
# 指定日志级别=trace,使用的 appender 为console
log4j.rootLogger = trace,console
# 指定控制台日志输出的位置
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定日志的输出格式 layout
log4j.appender.console.layout = org.apache.log4j.SimpleLayout

开启log4j内置日志记录

在要记录的类代码中添加如下代码。

// 开启 log4j 内置日志记录
LogLog.setInternalDebugging(true);

开启后可以观察到log4j自身执行的日志信息。

日志输出格式

日志输出的格式,log4j提供了以下几种。

日志输出格式 作用
HTMLLayout 以html表格形式布局
PatternLayout 灵活指定布局模式
SimpleLayout 简单日志布局
TTCCLayout 包含产生时间、线程、类别等

通过修改,以下配置信息,就可实现简单的日志输出格式。

# 指定日志的输出格式 layout
log4j.appender.console.layout = org.apache.log4j.SimpleLayout

若是想实现自定义的日志输出格式,则需要指定日志的布局为PatternLayout,然后添加以下代码,就实现了自定义日志格式。

# 指定日志格式的内容
log4j.appender.console.layout.conversionPattern = [%-5p] %r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n

上述的一些代码定制日志输出格式,可在下面表格查找其含义。

符号 作用
%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 输出代码中的行号
%% 输出一个"%"字符

日志输出到文件

# 日志文件输出的 appender 对象
log4j.appender.file = org.apache.log4j.FileAppender
# 指定日志的的格式 layout
log4j.appender.file.layout = org.apache.log4j.PatternLayout
# 指定日志格式的内容
log4j.appender.file.layout.conversionPattern = [%-5p] %r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件的保存路径
log4j.appender.file.file = D:/logs/log4j.log
# 指定日志文件的字符集
log4j.appender.file.encoding = UTF-8

同时需要更改rootLogger

# 指定日志级别=trace,使用的 appender 为console和file
log4j.rootLogger = trace,console,file

按大小日志文件拆分

当日志输出到文件的时候,需要考虑到一个问题,能否将所有的日志记录都输出到一个文件中,这是不友好的,当日志记录众多,查找起来相当复杂,所以要使用到文件拆分。

# 按照文件大小拆分的 appender 对象
# 日志文件输出的 appender 对象
log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
# 指定日志的的格式 layout
log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
# 指定日志格式的内容
log4j.appender.rollingFile.layout.conversionPattern = [%-5p] %r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件的保存路径
log4j.appender.rollingFile.file = D:/logs/log4j.log
# 指定日志文件的字符集
log4j.appender.rollingFile.encode = UTF-8
# 指定日志文件内容的大小
log4j.appender.rollingFile.maxFileSize = 1MB
# 指定日志文件的数量
log4j.appender.rollingFile.maxBackupIndex = 10

同业需要修改rootLogger

log4j.rootLogger = trace,rollingFile

按时间拆分日志文件

# 按照时间规则拆分的 appender 对象
log4j.appender.dailyFile = org.apache.log4j.DailyRollingFileAppender
# 指定日志的的格式 layout
log4j.appender.dailyFile.layout = org.apache.log4j.PatternLayout
# 指定日志格式的内容
log4j.appender.dailyFile.layout.conversionPattern = [%-5p] %r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件的保存路径
log4j.appender.dailyFile.file = D:/logs/log4j.log
# 指定日志文件的字符集
log4j.appender.dailyFile.encode = UTF-8
# 指定日期拆分规则
log4j.appender.dailyFile.datePattern = '.'yyyy-MM-dd-HH-mm-ss

修改rootLogger

log4j.rootLogger = trace,dailyFile

将日志文件输出到数据库

首先需要在数据库中创建一个表

CREATE TABLE `log` (
  `log_id` int(11) NOT NULL AUTO_INCREMENT,
  `project_name` varchar(255) DEFAULT NULL COMMENT '项目名',
  `create_date` varchar(255) DEFAULT NULL COMMENT '创建时间',
  `level` varchar(255) DEFAULT NULL COMMENT '优先级',
  `category` varchar(255) DEFAULT NULL COMMENT '所在类的全类名',
  `file_name` varchar(255) DEFAULT NULL COMMENT '输出日志消息产生时所在的文件名称',
  `thread_name` varchar(255) DEFAULT NULL COMMENT '日志事件的线程名',
  `line` varchar(255) NOT NULL COMMENT '行号',
  `all_category` varchar(255) DEFAULT NULL COMMENT '日志事件的发生位置',
  `message` varchar(255) DEFAULT NULL COMMENT '输出代码中指定的消息',
  PRIMARY KEY (`log_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6005 DEFAULT CHARSET=utf8;

pom.xml中添加mysql数据库的连接器。

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.19</version>
</dependency>

然后在配置文件中添加数据库的信息。针对不同的数据库有不同的实现,这里使用的是mysql

# mysql
log4j.appender.logDB = org.apache.log4j.jdbc.JDBCAppender
log4j.appender.logDB.layout = org.apache.log4j.PatternLayout
log4j.appender.logDB.Driver = com.mysql.jdbc.Driver
log4j.appender.logDB.URL = jdbc:mysql://localhost:3306/srcrs?useSSL=false&amp;serverTimezone=UTC
log4j.appender.logDB.User = root
log4j.appender.logDB.Password = 123456
log4j.appender.logDB.Sql = INSERT INTO log(project_name,create_date,level,category,file_name,thread_name,line,all_category,message) values('itcast','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')

更改rootLogger

log4j.rootLogger = trace,logDB

自定义logger配置

这个是仅仅针对这个org.example包下的类文件有用

log4j.logger.org.example = info,file

对于父对象rootLogger,则会进行继承,日志的级别将进行覆盖,日志输出的位置将进行合并。

log4j.rootLogger = trace,console

# 日志实现原理

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

日志输出位置 作用
ConsoleAppender 输出到控制台
FileAppender 输出到文件
DailyRollingFileAppender 按时间产生日志文件
RollingFileAppender 按文件大小产生新文件
WriterAppender 将日志按流的方式输出到任何位置

# JCL

全名叫做Jakarta Commons Logging,是Apache提供的一个通用日志API。JCL为日志门面,为所有的java日志实现提供一个统一的接口,自身也提供了一个日志实现SimpleLog,允许开发人员使用Log4jJUL日志实现。

JCL有两个基本的抽象类,Log(基本记录器)和LogFactory(创建Log对象)。

App(Java应用)
App(Java应用)
JCL
JCL
Log4j
Log4j
JUL
JUL
SimpleLog
SimpleLog
Viewer does not support full SVG 1.1

# 项目初始化

pom.xml中导入JCL的相应的坐标。

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

基础类

package org.example;
public class App
{
    public static void main( String[] args ){
    }
}

# JUL实现

入门代码

// 获取 log 日志记录器对象
Log log = LogFactory.getLog(App.class);
// 日志记录输出
log.info("hello jcl");

# Log4j实现

pom.xml中插入log4j的坐标

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

resource目录中创建log4j.properties文件,写入如下内容。

# 指定 RootLogger 顶级父元素默认配置
# 指定日志级别=trace,使用的 appender 为console
log4j.rootLogger = trace,console
# 指定控制台日志输出的位置
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定日志的输出格式 layout
log4j.appender.console.layout = org.apache.log4j.SimpleLayout

# JCL原理

内置有日志实现的一个顺序,优先级高的执行了,优先级低的就会被屏蔽。

private static final String[] classesToDiscover = new String[]{
    "org.apache.commons.logging.impl.Log4JLogger",
    "org.apache.commons.logging.impl.Jdk14Logger",
    "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
    "org.apache.commons.logging.impl.SimpleLog"
};

«interface»
Log
«interface»...
Jdk14Logger
Jdk14Logger
Log4JLogger
Log4JLogger
SimpleLog
SimpleLog
Jdk13LumberjackLogger
Jdk13LumberjackLogger
Viewer does not support full SVG 1.1

灵魂拷问,优先级低的如何让它实现呢?

# 参考链接

全面学习多种日志框架 (opens new window)