日志框架从入门到重新入门
1.日志框架分类日志框架分类1.日志门面:操作日志的一套APIJCLappache推出的slf4j2.日志实现JULjdk自带的logbackspringboot默认log4jappache推出的老牌的日志框架,停止维护更新log4j2是log4j的升级版,提升性能2.日志框架1. JULJUL1.使用jdk自带的不
1.日志框架分类
日志框架分类
1.日志门面:操作日志的一套API
JCL appache推出的
slf4j
2.日志实现
JUL jdk自带的
logback springboot默认
log4j appache推出的老牌的日志框架,停止维护更新
log4j2 是log4j的升级版,提升性能
2.日志框架
1. JUL
JUL
1.使用
jdk自带的不须导包
package com.zzhua;
import org.junit.Test;
import java.util.logging.*;
public class JULTest {
// 快速入门
@Test
public void testQuick()throws Exception{
// 1.获取日志记录器对象
Logger logger = Logger.getLogger("com.itheima.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});
}
// 日志级别
@Test
public void testLogLevel()throws Exception{
// 1.获取日志记录器对象
Logger logger = Logger.getLogger("com.itheima.JULTest");
// 2.日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info"); // 默认日志输出级别
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
// 自定义日志级别
@Test
public void testLogConfig()throws Exception{
// 1.获取日志记录器对象
Logger logger = Logger.getLogger("com.itheima.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对象父子关系
@Test
public void testLogParent()throws Exception{
Logger logger1 = Logger.getLogger("com.itheima");
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");
}
// 加载自定义配置文件
@Test
public void testLogProperties()throws Exception{
// 读取配置文件,通过类加载器
InputStream ins = JULTest.class.getClassLoader().getResourceAsStream("logging.properties");
// 创建LogManager
LogManager logManager = LogManager.getLogManager();
// 通过LogManager加载配置文件
logManager.readConfiguration(ins);
// 创建日志记录器
Logger logger = Logger.getLogger("com.itheima");
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
Logger logger2 = Logger.getLogger("test");
logger2.severe("severe test");
logger2.warning("warning test");
logger2.info("info test");
logger2.config("config test");
logger2.fine("fine test");
logger2.finer("finer test");
logger2.finest("finest test");
}
}
logging.properties
# RootLogger 顶级父元素指定的默认处理器为:ConsoleHandler
handlers= java.util.logging.FileHandler
# RootLogger 顶级父元素默认的日志级别为:ALL
.level= ALL
# 自定义 Logger 使用
com.zzhua.handlers = java.util.logging.ConsoleHandler
com.zzhua.level = CONFIG
# 关闭默认配置
com.zzhua.useParentHanlders = false
# 向日志文件输出的 handler 对象
# 指定日志文件路径 /logs/java0.log
java.util.logging.FileHandler.pattern = ./java%u.log
# 指定日志文件内容大小
java.util.logging.FileHandler.limit = 50000
# 指定日志文件数量
java.util.logging.FileHandler.count = 1
# 指定 handler 对象日志消息格式对象
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
# 指定以追加方式添加日志内容
java.util.logging.FileHandler.append = true
# 向控制台输出的 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
# 指定日志消息格式
java.util.logging.SimpleFormatter.format = %4$s: %5$s [%1$tc]%n
2. LOG4J
1. 引入log4j的依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
2. 使用
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.apache.log4j.helpers.LogLog;
@Test
public void test01(){
// 需要加上下面这句,否则什么也没有输出,还报log4j的警告信息,因为没有配置文件
BasicConfigurator.configure(); // 代码配置
Logger logger = Logger.getLogger(Log4j_Test.class);
logger.trace("hello trace ~");
logger.debug("hello debug ~");
logger.info("hello info ~");
logger.warn("hello warn ~");
logger.error("hello error ~");
logger.fatal("hello fatal ~");
}
3. log4j.properties
属性配置文件配置: LOG4J会默认读取类路径下的log4j.properties文件
#参考log4j包下的PropertyConfigurator这个类查看配置
#第一个参数是 日志级别,第二个和之后都是appender并且用‘,’隔开
log4j.rootLogger = trace,console #使用下面定义的appender
log4j.appender.console = org.apache.log4j.ConsoleAppender #定义appender
log4j.appender.console.layout = org.apache.log4j.SimpleLayout #设置appender的layout
@Test
public void test01(){
LogLog.setInternalDebugging(true); // 使用这句可以开启Log4j自身的日志信息,查看log4j自身的的执行日志信息
Logger logger = Logger.getLogger(Log4j_Test.class);
logger.trace("hello trace ~");
logger.debug("hello debug ~"); // 默认日志级别
logger.info("hello info ~");
logger.warn("hello warn ~");
logger.error("hello error ~");
logger.fatal("hello fatal ~");
}
4. 配置文件详解
# 指定 RootLogger 顶级父元素默认配置信息
# 指定日志级别=trace,使用的 apeender 为=console
log4j.rootLogger = trace,console
# 自定义 logger 对象设置
# 自定义logger的级别是info,使用的appender是console,console指向下面的console配置
# 这里的com.zzhua是代码调用getLogger(类名.class)时,这个字节码对象所在的包的包名.因此该方法返回的logger就会找到对应的
# Logger,如果没有自定义,那么将会使用rootLogger的配置
log4j.logger.com.zzhua = info,console
# 自定义另外一个logger的级别是error,使用的appender是空,那么默认继承rootLogger的console
log4j.logger.org.apache = error
# 指定控制台日志输出的 appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定消息格式 layout
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
# conversionPattern查看PatternLayout的setConversionPattern可以设置
log4j.appender.console.layout.conversionPattern = [%-10p]%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 = [%-10p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.file.file = /logs/log4j.log
# 指定日志文件的字符集
log4j.appender.file.encoding = UTF-8
# 按照文件大小拆分的 appender 对象
# 日志文件输出的 appender 对象
log4j.appender.rollingFile = org.apache.log4j.RollingFileAppender
# 指定消息格式 layout
log4j.appender.rollingFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.rollingFile.layout.conversionPattern = [%-10p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.rollingFile.file = /logs/log4j.log
# 指定日志文件的字符集
log4j.appender.rollingFile.encoding = UTF-8
# 指定日志文件内容的大小 ,查看RollingFileAppender的setMaxFileSize方法设置
log4j.appender.rollingFile.maxFileSize = 1MB
# 指定日志文件的数量
log4j.appender.rollingFile.maxBackupIndex = 10
# 按照时间规则拆分的 appender 对象
log4j.appender.dailyFile = org.apache.log4j.DailyRollingFileAppender
# 指定消息格式 layout
log4j.appender.dailyFile.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容
log4j.appender.dailyFile.layout.conversionPattern = [%-10p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 指定日志文件保存路径
log4j.appender.dailyFile.file = /logs/log4j.log
# 指定日志文件的字符集
log4j.appender.dailyFile.encoding = UTF-8
# 指定日期拆分规则
log4j.appender.dailyFile.datePattern = '.'yyyy-MM-dd-HH-mm-ss
#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/test
log4j.appender.logDB.User=root
log4j.appender.logDB.Password=root
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')
3. JCL
日志门面,appache出的,仅支持JUL和log4j
提供了两个抽象类: Log接口 和 LoggerFactory抽象类 ,别导错包了 -> org.apache.commons.logging
1. 导入commons-logging的包
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
2.使用
// 如果导入了log4j的依赖,那么下面代码用的就是log4j的实现
如果没有导入,那么用的就是org.apache.commons.logging.impl.Jdk14Logger
import org.apache.commons.logging.Log; // 注意导包
import org.apache.commons.logging.LogFactory; // 注意导包
import org.junit.Test;
public class JCL_test {
@Test
public void test01(){
Log log = LogFactory.getLog(JCL_test.class);
log.info("info - msg");
}
}
3.原理
Log log = LogFactory.getLog(JCL_test.class);
#当使用LogFactory获取Log对象实例的时候,
return getFactory().getInstance(clazz);
它会先获取到LogFactory的实例,默认没有配置的情况下就是LogFactoryImpl,然后调用getInstance方法
instance = discoverLogImplementation(name); // 寻找实现
for(int i=0; i<classesToDiscover.length && result == null; ++i) { // 依次遍历classesToDiscover
result = createLogFromClass(classesToDiscover[i], logCategory, true);
}
依次遍历的
private static final String[] classesToDiscover = {
"org.apache.commons.logging.impl.Log4JLogger", // 首先的就是Log4JLogger,注意包名
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"
};
我们首先看看这个类
import java.io.Serializable;
import org.apache.commons.logging.Log;
import org.apache.log4j.Logger; // 这个类导入了log4j包的类,
import org.apache.log4j.Priority; // 因此如果没有导入log4j包的类的话,那么就会抛异常,所以会按顺序使用下面的类
import org.apache.log4j.Level; // 而不会使用log4jlogger了
public class Log4JLogger implements Log, Serializable {...}
4. slf4j
1. 引入依赖
<!-- slf4j 日志门面,仅提供一套接口,须导入具体实现,才能使用 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<!-- slf4j 内置的简单实现,后期可依照下面的适配图所示的依赖替换掉-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
2. 使用
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Slf4jTest {
public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);
// 快速入门
@Test
public void test01()throws Exception{
// 日志输出
LOGGER.error("error");
LOGGER.warn("wring");
LOGGER.info("info"); // 默认级别
LOGGER.debug("debug");
LOGGER.trace("trace");
// 使用占位符输出日志信息
String name = "itheima";
Integer age = 14;
LOGGER.info("用户:{},{}",name,age);
// 将系统的异常信息输出
try {
int i = 1/0;
} catch (Exception e) {
// e.printStackTrace();
LOGGER.error("出现异常:",e);
}
}
}
3.功能
1.slf4j绑定日志的实现
单独导入slf4j-api的依赖并不能直接使用,需要导入其它日志实现依赖的包,才能使用,具体适配可以查看下图。
如果绑定多个日志实现,那么会默认选择第一个。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rfgP6lhu-1595081403718)(assets/concrete-bindings.png)]
#绑定原理:
public static final Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class); // 获取LOGGER
#来到slf4j的获取logger的静态方法
->Logger logger = getLogger(clazz.getName()); // 根据名字获取logger
->public static Logger getLogger(String name) { // 被调用
ILoggerFactory iLoggerFactory = getILoggerFactory(); // 先获取ILoggerFactory
return iLoggerFactory.getLogger(name);
}
getIloggerFactory调用performInitilization方法
->performInitialization(){
bind();
}
->private static final void bind() { // 开始绑定
String msg;
try {
Set<URL> staticLoggerBinderPathSet = null;
if (!isAndroid()) {
// 在类路径下寻找org/slf4j/impl/StaticLoggerBinder.class这个文件,并返回路径,可能有多个
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
// 判断是否返回了多个,打印相关信息
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// 获取到StaticLoggerBinder实例
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = 3; // 为3
reportActualBinding(staticLoggerBinderPathSet);
fixSubstituteLoggers();
replayEvents();
SUBST_FACTORY.clear();
} catch (NoClassDefFoundError var2) {
msg = var2.getMessage();
if (!messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
failedBinding(var2);
throw var2;
}
INITIALIZATION_STATE = 4;
Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.");
} catch (NoSuchMethodError var3) {
msg = var3.getMessage();
if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
INITIALIZATION_STATE = 2;
Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
Util.report("Your binding is version 1.5.5 or earlier.");
Util.report("Upgrade your binding to version 1.6.x.");
}
throw var3;
} catch (Exception var4) {
failedBinding(var4);
throw new IllegalStateException("Unexpected initialization failure", var4);
}
}
->跳回到getILoggerFactory()这个方法,INITIALIZATION_STATE状态是3
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == 0) {
Class var0 = LoggerFactory.class;
synchronized(LoggerFactory.class) {
if (INITIALIZATION_STATE == 0) {
INITIALIZATION_STATE = 1;
performInitialization();
}
}
}
switch(INITIALIZATION_STATE) {
case 1:
return SUBST_FACTORY;
case 2:
throw new IllegalStateException("org.slf4j.LoggerFactory in failed state. Original exception was thrown EARLIER. See also http://www.slf4j.org/codes.html#unsuccessfulInit");
case 3:
// 调用绑定成功的类的 单例对象的getLoggerFactory()方法,
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case 4:
return NOP_FALLBACK_FACTORY;
default:
throw new IllegalStateException("Unreachable code");
}
}
// 如果导入多个依赖与slf4j绑定,也就是说有多个StaticLoggerBinder[不同依赖,但同包同类名],
// 那么按顺序只会有一个能够绑定成功
public class StaticLoggerBinder implements LoggerFactoryBinder {
private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
public static String REQUESTED_API_VERSION = "1.6.99";
private static final String loggerFactoryClassStr = Log4jLoggerFactory.class.getName();
// 如果这个类绑定成功,那么用的就是Log4jLoggerFactory了
private final ILoggerFactory loggerFactory = new Log4jLoggerFactory();
public static final StaticLoggerBinder getSingleton() { // 获取到当前类的单例对象
return SINGLETON;
}
private StaticLoggerBinder() {
try {
Level var1 = Level.TRACE;
} catch (NoSuchFieldError var2) {
Util.report("This version of SLF4J requires log4j version 1.2.12 or later. +
"See also http://www.slf4j.org/codes.html#log4j_version");
}
}
public ILoggerFactory getLoggerFactory() {
return this.loggerFactory;
}
public String getLoggerFactoryClassStr() {
return loggerFactoryClassStr;
}
}
// 再来看看slf4j-jdk14的依赖中的StaticLoggerBinder类
public class StaticLoggerBinder implements LoggerFactoryBinder {
private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
public static String REQUESTED_API_VERSION = "1.6.99";
private static final String loggerFactoryClassStr = JDK14LoggerFactory.class.getName();
// 这里用的就是JDK14LoggerFactory了
private final ILoggerFactory loggerFactory = new JDK14LoggerFactory();
public static final StaticLoggerBinder getSingleton() {
return SINGLETON;
}
private StaticLoggerBinder() {
}
public ILoggerFactory getLoggerFactory() {
return this.loggerFactory;
}
public String getLoggerFactoryClassStr() {
return loggerFactoryClassStr;
}
}
2.桥接旧的日志框架
项目原本用的是log4j作为日志框架,但须改成logback。
这个时候,就可以引进slf4j-api和logback-classic、logback-core的依赖。
并且删除掉log4j的依赖,删除的话,原来的代码就会报错,找不到类。通过再引入桥接的log4j-over-slf4j依赖,替换掉原来log4j包中的类(同包同类名),而替换的类会转而调用slf4j-api门面的接口,这样原来的代码就不报错并且不用改动,就完成了修改。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pe0bWx9y-1595081403720)(assets/legacy.png)]
3.注意事项
桥接器和适配器不能同时使用。
适配器是为了让slf4j能够顺利的使用老牌的日志框架,比如log4j。而桥接器是为了修改掉老牌的日志框架,使原有调用老牌的日志框架的代码不用改动,但是调用的是桥接器中代码,转而调用slf4j门面,从而不使用老牌的日志框架。所以如果同时使用会出现死循环,造成栈内存溢出。所以两者不能共存。
更多推荐
所有评论(0)