学习目标
-
日志的作用和目的
-
日志的框架
-
JUL的使用
-
log4j的使用
-
JCL的使用
日志文件
日志文件是用于记录系统操作事件的记录文件或文件集合,具有处理历史数据、诊断问题的追踪以及理解系统的活动等重要作用。
在编写代码的时候,通常需要通过日志信息程序运行是否正常,出错时,根据日志信息能快速定位到错误地点。对于软件系统的开发,以及日后的维护工作,都有极其重要的作用。
日志框架
问题
日志框架需要考虑的问题:
-
控制日志输出的内容和格式
-
控制日志输出的位置
-
日志优化:一部日志,日志文件的归档和压缩
-
日志系统的维护
-
面向接口开发——日志门面
现有的日志框架
日志分为两个大的部分,日志实现与日志门面。所谓日志门面,就是为各种日志API
提供一个统一的接口,类似于操作数据库的JDBC
,针对不同的数据库,都是操作同一套API
接口。
日志门面:
日志实现
JUL
JUL
全称Java util Logging
是java
原生的日志框架,使用时,不需要引用第三方类库,方便、简单、灵活。
LoggerLoggerHandlerHandlerApplicationApplicationOutside WorldOutside WorldFilterFilterFilterViewer does not support full SVG 1.1
Loggers
:被称为日志记录器,应用程序通过获取Logger
对象,调用其API
来发布日志信息。Logger
通常时应用系统访问日志系统的入口程序。
Appenders
:也被称为Handlers
,每个Logger
都会关联一组Handlers
,Logger
会将日志交给关联Handlers
处理,由Handlers
负责将日志做记录。Handlers
在此是一个抽象,其具体的实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志等。
Layouts
:也被称为Formatters
,它负责对日志事件中的数据进行转换和格式化。Layouts
决定了数据在一条日志记录中的最终形式。
Level
:每条日志消息都有一个关联的日志级别。该级别展现日志消息的重要性和紧迫性,不同的日志框架该级别会有细微差别。
Filters
:过滤器,根据需要定制哪些信息会被记录,哪些信息会被放行。
日志级别
severe
当程序运行错误的时候,就可以用severe
来记录错误信息。
warning
记录程序运行遇到的问题,一般不会造成程序中止运行,但是也需要注意。
info
记录程序运行中的一些消息,例如数据库连接信息,io
的传递信息等等。
config
记录配置信息。
fine
finer
finest
这三者都是记录debug
信息。使用时用其中一个就行了。
除上述外,还有两个特殊级别,off
(用来关闭日志记录),all
(用来开启日志记录)。
示例代码
基础类
1
2
3
4
5
6
|
package org.example.test;
public class JULTest {
public static void main(String[] args) throws IOException {
}
}
|
基本使用
1
2
3
4
5
6
7
8
9
|
// 获取日志记录器对象
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});
|
日志级别
1
2
3
4
5
6
7
8
9
10
11
|
// 获取日志记录器对象
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
。
自定义配置日志级别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// 获取日志记录器对象
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
,所有的日志信息都将会输出。
日志输出到文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
// 获取日志记录器对象
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对象父子关系
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
// 获取日志记录器对象
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
文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
# 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应用LoggerLoggerLogRecordLogRecordHandlerHandler输出输出LogManagerLogManagerLevelLevelFilterFilterFormatterFormatterViewer does not support full SVG 1.1
1 初始化LogManager
1.1 LogManager
加载logging.properties
配置
1.2 添加Logger
到LogManager
2 从单例LogManager
获取Logger
3 设置级别Level
,并指定日志记录LogRecord
4 Filter
提供了日志级别之外更细粒度的控制
5 Handler
是用来处理日志输出位置
6 Formatter
是用来格式化LogRecord
的
Log4j
Log4j
是Apache
的一个开源项目,通过使用Log4j
,我们可以控制日志信息输送的目的地是控制台、文件等,可以定制每一条日志输出的格式,控制日志输出的级别,完成这些操作,仅仅只需要修改配置文件,并不用修改业务逻辑代码。
使用Log4j
,首先需要导入Log4j
的jar
包,在maven
项目中,只需引用相应坐标即可:
1
2
3
4
5
|
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
|
日志级别
Log4j
一共提供有六个日志级别:
fatal
表示严重错误,通常会造成系统崩溃中止运行。
error
表示错误信息,不会影响系统运行。
warn
表示警告信息,可能会发生的问题。
info
程序运行中的信息,数据库连接,io操作等等。
debug
表示调试信息,一般在开发中使用。
trace
表示追踪信息,记录所有的流程信息。
Log4j
的默认级别为debug
。
示例代码
基础类
1
2
3
4
5
6
|
package org.example;
public class App{
public static void main(String[] args){
}
}
|
入门代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 初始化配置信息,暂不使用配置文件
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
即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 获取日志记录器对象
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
文件
基本配置
最简单的配置,仅仅只配置了,日志级别,输出位置,输出格式。
1
2
3
4
5
6
7
|
# 指定 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内置日志记录
在要记录的类代码中添加如下代码。
1
2
|
// 开启 log4j 内置日志记录
LogLog.setInternalDebugging(true);
|
开启后可以观察到log4j
自身执行的日志信息。
日志输出格式
日志输出的格式,log4j
提供了以下几种。
日志输出格式 |
作用 |
HTMLLayout |
以html表格形式布局 |
PatternLayout |
灵活指定布局模式 |
SimpleLayout |
简单日志布局 |
TTCCLayout |
包含产生时间、线程、类别等 |
通过修改,以下配置信息,就可实现简单的日志输出格式。
1
2
|
# 指定日志的输出格式 layout
log4j.appender.console.layout = org.apache.log4j.SimpleLayout
|
若是想实现自定义的日志输出格式,则需要指定日志的布局为PatternLayout
,然后添加以下代码,就实现了自定义日志格式。
1
2
|
# 指定日志格式的内容
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 |
输出代码中的行号 |
%% |
输出一个"%“字符 |
日志输出到文件
1
2
3
4
5
6
7
8
9
10
|
# 日志文件输出的 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
1
2
|
# 指定日志级别=trace,使用的 appender 为console和file
log4j.rootLogger = trace,console,file
|
按大小日志文件拆分
当日志输出到文件的时候,需要考虑到一个问题,能否将所有的日志记录都输出到一个文件中,这是不友好的,当日志记录众多,查找起来相当复杂,所以要使用到文件拆分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# 按照文件大小拆分的 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
1
|
log4j.rootLogger = trace,rollingFile
|
按时间拆分日志文件
1
2
3
4
5
6
7
8
9
10
11
12
|
# 按照时间规则拆分的 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
1
|
log4j.rootLogger = trace,dailyFile
|
将日志文件输出到数据库
首先需要在数据库中创建一个表
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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
数据库的连接器。
1
2
3
4
5
|
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
|
然后在配置文件中添加数据库的信息。针对不同的数据库有不同的实现,这里使用的是mysql
。
1
2
3
4
5
6
7
8
|
# 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&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
1
|
log4j.rootLogger = trace,logDB
|
自定义logger配置
这个是仅仅针对这个org.example
包下的类文件有用
1
|
log4j.logger.org.example = info,file
|
对于父对象rootLogger
,则会进行继承,日志的级别将进行覆盖,日志输出的位置将进行合并。
1
|
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
,允许开发人员使用Log4j
,JUL
日志实现。
JCL
有两个基本的抽象类,Log
(基本记录器)和LogFactory
(创建Log
对象)。
App(Java应用)App(Java应用)JCLJCLLog4jLog4jJULJULSimpleLogSimpleLogViewer does not support full SVG 1.1
项目初始化
在pom.xml
中导入JCL
的相应的坐标。
1
2
3
4
5
|
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
|
基础类
1
2
3
4
5
6
7
|
package org.example;
public class App
{
public static void main( String[] args ){
}
}
|
JUL实现
入门代码
1
2
3
4
|
// 获取 log 日志记录器对象
Log log = LogFactory.getLog(App.class);
// 日志记录输出
log.info("hello jcl");
|
Log4j实现
在pom.xml
中插入log4j
的坐标
1
2
3
4
5
|
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
|
在resource
目录中创建log4j.properties
文件,写入如下内容。
1
2
3
4
5
6
7
|
# 指定 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原理
内置有日志实现的一个顺序,优先级高的执行了,优先级低的就会被屏蔽。
1
2
3
4
5
6
|
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»…Jdk14LoggerJdk14LoggerLog4JLoggerLog4JLoggerSimpleLogSimpleLogJdk13LumberjackLoggerJdk13LumberjackLoggerViewer does not support full SVG 1.1
灵魂拷问,优先级低的如何让它实现呢?
参考链接
全面学习多种日志框架