日志技术——早期
一、日志概念
记录信息,方便查错
- 调试日志:开发时调试使用
- 系统日志:生产环境找错使用
二、Java日志技术
1. 门面概念
日志框架层出不穷,如果没有统一接口,将大大增加用户的学习成本,扩展性也不好。所以现有日志框架都可以用门面来做到一套编码,任意更换,程序员面向接口编程,不直接使用日志的实现。
2. 现有日志框架
- 日志门面
- JCL、slf4j
- 日志实现
- JUL(java自带)、logback、log4j、log4j2(log4j的升级版)
三、JUL 学习
JUL —— java util logging,java的原生框架
1. 架构介绍
- Loggers:产生日志
- Appenders(Handlers):决定日志输出到哪
- Layouts(Formatters): 决定日志输出的形式,xml还是其他的格式
- Level:标记日志消息的重要性的紧迫性
- Filters:根据不同的日志的重要性(级别),决定哪些日志会被过滤掉
2. 快速入门
- 产生方式
net.logger_JUL.test.TestJUL
@Test
public void testQuick() {
// 1. 创建日志记录器对象
Logger logger = Logger.getLogger("net.logger_JUL.test.TestJUL");
// 2. 产生日志记录(默认输出到控制台)
logger.info("我是info信息");
logger.log(Level.INFO, "我也是info信息");
// 日志的占位参数
logger.log(Level.INFO, "用户信息:{0},{1}", new Object[]{"leo", "18"});
}
/****************************** 输出 ******************************
七月 23, 2021 11:51:47 上午 net.logger_JUL.test.TestJUL testQuick
信息: 我是info信息
七月 23, 2021 11:51:47 上午 net.logger_JUL.test.TestJUL testQuick
信息: 我也是info信息
七月 23, 2021 11:51:47 上午 net.logger_JUL.test.TestJUL testQuick
信息: 用户信息:leo,18
******************************************************************/
- 默认日志级别
net.logger_JUL.test.TestJUL
@Test
public void testLevelA() {
/**
* 一条日志他会有level属性,只有他的leve大于等于设置的level等级,它才会被输出
* <ul>
* <li>OFF (value = Integer.MAX_VALUE),用来关闭所有日志输出
* <li>SEVERE (highest value)
* <li>WARNING
* <li>INFO
* <li>CONFIG
* <li>FINE
* <li>FINER
* <li>FINEST (lowest value)
* <li>All (value = Integer.MIN_VALUE),用来开启所有日志输出
* </ul>
*/
// 默认Level是info,所以只有info以上才会被输出
Logger logger = Logger.getLogger("net.logger_JUL.test.TestJUL");
logger.log(Level.SEVERE, "severe");
logger.log(Level.WARNING, "warning");
logger.log(Level.INFO, "info");
logger.log(Level.CONFIG, "config");
logger.log(Level.FINE, "fine");
logger.log(Level.FINER, "finer");
logger.log(Level.FINEST, "finest");
}
/****************************** 输出 ******************************
七月 23, 2021 11:56:35 上午 net.logger_JUL.test.TestJUL testLevelA
严重: severe
七月 23, 2021 11:56:35 上午 net.logger_JUL.test.TestJUL testLevelA
警告: warning
七月 23, 2021 11:56:35 上午 net.logger_JUL.test.TestJUL testLevelA
信息: info
******************************************************************/
- 自定义级别
net.logger_JUL.test.TestJUL
@Test
public void testLevelB() {
Logger logger = Logger.getLogger("net.logger_JUL.test.TestJUL");
// 1.关闭系统默认配置
// 不关闭的话logger会把日志也发一份给它的父亲,所以表现为info级别以上的日志打印了两遍
logger.setUseParentHandlers(false);
// 2.创建handler对象
ConsoleHandler consoleHandler = new ConsoleHandler();
// 3.创建formatter对象
SimpleFormatter simpleFormatter = new SimpleFormatter();
// 4.进行关联
consoleHandler.setFormatter(simpleFormatter);
logger.addHandler(consoleHandler);
// 5.设置日志级别
logger.setLevel(Level.ALL);
consoleHandler.setLevel(Level.ALL);
logger.log(Level.SEVERE, "severe");
logger.log(Level.WARNING, "warning");
logger.log(Level.INFO, "info");
logger.log(Level.CONFIG, "config");
logger.log(Level.FINE, "fine");
logger.log(Level.FINER, "finer");
logger.log(Level.FINEST, "finest");
}
/****************************** 输出 ******************************
七月 23, 2021 1:52:46 下午 net.logger_JUL.test.TestJUL testLevelB
严重: severe
七月 23, 2021 1:52:46 下午 net.logger_JUL.test.TestJUL testLevelB
警告: warning
七月 23, 2021 1:52:46 下午 net.logger_JUL.test.TestJUL testLevelB
信息: info
七月 23, 2021 1:52:46 下午 net.logger_JUL.test.TestJUL testLevelB
配置: config
七月 23, 2021 1:52:46 下午 net.logger_JUL.test.TestJUL testLevelB
详细: fine
七月 23, 2021 1:52:46 下午 net.logger_JUL.test.TestJUL testLevelB
较详细: finer
七月 23, 2021 1:52:46 下午 net.logger_JUL.test.TestJUL testLevelB
非常详细: finest
******************************************************************/
3. 父子关系
JUL中的Logger之间存在父子关系,JUL初始时会创建一个顶层RootLogger作为所有Logger的父亲。子Logger默认会向父Logger的Handlers发送日志拷贝,从而子Logger的日志通过父Logger之手输出。
net.logger_JUL.test.TestJUL
@Test
public void testInherit() {
Logger logger_f = Logger.getLogger("net.logger_JUL");
Logger logger_s = Logger.getLogger("net.logger_JUL.test");
// 谁是父亲谁是儿子,在命名的那刻就决定了
System.out.println(logger_s.getParent() == logger_f);
// logger_f 的父亲就是 RootLogger,而不是叫“net”的家伙,并且 RootLogger 无名
System.out.println("logger_f's father: " + logger_f.getParent());
System.out.println("logger_f's fatherName: " + logger_f.getParent().getName());
// 现对 logger_f 自定义,看 logger_s 是否同父亲一样也自定义了
// 其实子 Logger 就是啥都不做,全交给父 Logger 了
logger_f.setUseParentHandlers(false);
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(new SimpleFormatter());
consoleHandler.setLevel(Level.ALL);
logger_f.setLevel(Level.ALL);
logger_f.addHandler(consoleHandler);
logger_s.log(Level.SEVERE, "severe");
logger_s.log(Level.WARNING, "warning");
logger_s.log(Level.INFO, "info");
logger_s.log(Level.CONFIG, "config");
logger_s.log(Level.FINE, "fine");
logger_s.log(Level.FINER, "finer");
logger_s.log(Level.FINEST, "finest");
}
/****************************** 输出 ******************************
true
logger_f's father: java.util.logging.LogManager$RootLogger@61e717c2
logger_f's fatherName:
七月 23, 2021 2:55:07 下午 net.logger_JUL.test.TestJUL testInherit
严重: severe
七月 23, 2021 2:55:07 下午 net.logger_JUL.test.TestJUL testInherit
警告: warning
七月 23, 2021 2:55:07 下午 net.logger_JUL.test.TestJUL testInherit
信息: info
七月 23, 2021 2:55:07 下午 net.logger_JUL.test.TestJUL testInherit
配置: config
七月 23, 2021 2:55:07 下午 net.logger_JUL.test.TestJUL testInherit
详细: fine
七月 23, 2021 2:55:07 下午 net.logger_JUL.test.TestJUL testInherit
较详细: finer
七月 23, 2021 2:55:07 下午 net.logger_JUL.test.TestJUL testInherit
非常详细: finest
******************************************************************/
4. 配置文件
默认配置文件路径:$JAVAHOME\jre\lib\logging.properties
logging.properties
# 不向父亲发送日志
net.logger_JUL.test.TestJUL.useParentHandlers = false
# 指定handler
net.logger_JUL.test.TestJUL.handlers = java.util.logging.ConsoleHandler,java.util.logging.FileHandler
# 设置日志级别
net.logger_JUL.test.TestJUL.level = ALL
# 设置ConsoleHandler的级别
java.util.logging.ConsoleHandler.level = ALL
# 设置ConsoleHandler的formatter
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# 设置FileHandler的输出目录,这里的 / 是用户目录
java.util.logging.FileHandler.pattern = java%u.log
java.util.logging.FileHandler.level = INFO
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 设置formatter输出格式
java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n
# 设置一个日志文件的大小
java.util.logging.FileHandler.limit = 50000
# 输出日志文件限制个数
java.util.logging.FileHandler.count = 10
# 输出日志文件 是否是追加
java.util.logging.FileHandler.append = true
java.util.logging.FileHandler.encoding = utf-8
net.logger_JUL.test.TestJUL
@Test
public void testConfigFile() throws IOException {
InputStream inputStream = TestJUL.class.getClassLoader().getResourceAsStream("logging.properties");
LogManager logManager = LogManager.getLogManager();
logManager.readConfiguration(inputStream);
Logger logger = Logger.getLogger("net.logger_JUL.test.TestJUL");
logger.log(Level.SEVERE, "severe");
logger.log(Level.WARNING, "warning");
logger.log(Level.INFO, "info");
logger.log(Level.CONFIG, "config");
logger.log(Level.FINE, "fine");
logger.log(Level.FINER, "finer");
logger.log(Level.FINEST, "finest");
}
/****************************** 输出 ******************************
七月 23, 2021 2:53:25 下午 net.logger_JUL.test.TestJUL testConfigFile
严重: severe
七月 23, 2021 2:53:25 下午 net.logger_JUL.test.TestJUL testConfigFile
警告: warning
七月 23, 2021 2:53:25 下午 net.logger_JUL.test.TestJUL testConfigFile
信息: info
七月 23, 2021 2:53:25 下午 net.logger_JUL.test.TestJUL testConfigFile
配置: config
七月 23, 2021 2:53:25 下午 net.logger_JUL.test.TestJUL testConfigFile
详细: fine
七月 23, 2021 2:53:25 下午 net.logger_JUL.test.TestJUL testConfigFile
较详细: finer
七月 23, 2021 2:53:25 下午 net.logger_JUL.test.TestJUL testConfigFile
非常详细: finest
日志文件内容略
******************************************************************/
5. 日志原理解析
略
四、LOG4J 学习
Apache 下的日志框架。
1. 快速入门
net.logger_log4j.test.TestLog4j
@Test
public void testQuick() {
// 1. 初始化配置,不需要配置文件
BasicConfigurator.configure();
// 2. 创建日志记录器对象
Logger logger = Logger.getLogger(TestLog4j.class);
// 3. 日志记录输出
logger.fatal("fatal");
// 以下四个为常用级别
logger.error("error");
logger.warn("warn");
logger.info("info");
logger.debug("debug"); // 默认 debug 级别
logger.trace("trace");
// 和 JUL 一样,OFF 和 All 用来关闭和打开日志
}
2. Log4j 组件
-
Loggers
- 收集处理日志记录
- 命名是类的全限定类名,和JUL一样根据名称决定logger之间的继承关系
- 例如:net.logger_log4j.test 是 net.logger_log4j.test.TestLog4j 父亲
- 所有 logger 顶层父亲是 root,用 getRootLogger() 来获取
- 自定义的 logger 默认没有 appender,所以用的是父亲的 appender
-
Appenders
- 用来指定日志输出到哪里
输出端类型 作用 ConsoleAppender 输出到控制台 FileAppender 输出到文件中 RollingFileAppender FileAppender的子类,根据文件大小拆分日志文件 DailyRollingFileAppender FileAppender的子类,每天输出到一个新的文件 JDBCAppender 将日志保存到数据库 -
Layouts
- 用来控制日志的输出格式
输出格式类 作用 HTMLLayout 输出为html格式 SimpleLayout 输出为简单文本格式 PatternLayout 自定输出格式
3. Layout 格式
占位符 含义 %m 输出日志信息 %p 输出日志信息的等级 %n 输出换行 %r 输出自应用启动到输出该 log 信息耗费的毫秒数 %c 输出打印语句所属的类的全名 %t 输出产生该日志的线程全名 %d 输出服务器当前时间,默认为 ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日 HH:mm:ss} %% 输出% %F 输出日志消息产生时所在的文件名称 %L 输出代码中的行号 %l 输出日志时间发生的位置,包括类名、及在代码中的行数。
4. Appender 输出
classpath:log4j.properties
# 这里root没有指定appender,所以即使子logger把日志传给它,也不会有任何输出
# 级别,appender的名字,另一个appender的名字...
log4j.rootLogger=info
# 自定义logger
log4j.logger.net.logger_log4j.test = trace, Console
## 输出到控制台配置
log4j.appender.Console=org.apache.log4j.ConsoleAppender
# 指定layout
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.conversionPattern=%l %p %r %d %F %m%n
## 输出到文件配置
log4j.appender.A = org.apache.log4j.DailyRollingFileAppender
# 指定日志输出路径
log4j.appender.A.file = D:/log.txt
# 设置为追加模式
log4j.appender.A.append = true
# 指定layout
log4j.appender.A.layout = org.apache.log4j.PatternLayout
log4j.appender.A.layout.conversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%t:%r] -[%p] %m%n
log4j.appender.A.encoding = utf-8
## 输出到数据库设置
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/log?useUnicode=true&characterEncoding=utf8
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('log4j_test','%d{yyyy-MM-dd HH:mm:ss}','%p','%c','%F','%t','%L','%l','%m')
sql 语句
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) DEFAULT NULL COMMENT '号行',
`all_category` varchar(255) DEFAULT NULL COMMENT '日志事件的发生位置',
`message` varchar(4000) DEFAULT NULL COMMENT '输出代码中指定的消息',
PRIMARY KEY (`log_id`)
);
net.logger_log4j.test.TestLog4j
@Test
public void testLayout() {
Logger logger = Logger.getLogger(TestLog4j.class);
// 输出试试
logger.fatal("fatal");
logger.error("error");
logger.warn("warn");
logger.info("info");
logger.debug("debug");
logger.trace("trace");
}
- 配置文件总结
- 即使 RootLogger,它初始也没任何appenders,所以需要配置
- 定义 logger,指定等级,appenders
- 定义 appenders,指定layout
五、JCL 学习
1. JCL 是什么
- JCL —— Jakarta Commons Logging,Apache 提供的通用日志API
- 为 “所有” Java 日志实现提供统一接口,自身也提供一个日志实现 —— SimpleLog。
- 上面的 “所有”,指的是:Log4j、JUL、SimpleLog
- 两个基本抽象类:Log(日志记录) 和 LogFactory(创建Log实例)
2. 快速入门
net.logger_JCL.test.TestJCL
@Test
public void testQuick() {
Log log = LogFactory.getLog(TestJCL.class);
log.fatal("fatal");
log.error("error");
log.warn("warn");
log.info("info");
log.debug("debug");
}
3. JCL 原理
-
LogFactory 动态加载Log实现类
-
日志门面支持的日志实现数组:
-
org.apache.commons.logging.impl.LogFactoryImpl 类
-
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"};
-
-
获取具体日志的实现
-
org.apache.commons.logging.impl.LogFactoryImpl 类
-
private Log discoverLogImplementation(String logCategory)
方法 -
for(int i = 0; i < classesToDiscover.length && result == null; ++i) { result = this.createLogFromClass(classesToDiscover[i], logCategory, true); }
-