日志框架📕
日志文件是用于记录系统操作事件的文件集合
logfile
是一个记录了发生在运行中的操作系统或其他软件中的事件的文件,或者记录了在网络聊天软件的用户之间发送的消息。
为什么需要日志:
出现日期排序:
Java 日志框架发展了很多年,已经出现了很多个版本~发展过程也比较混乱~
Commons Logging
→ Slf4j → Logback → log4j2log4j 1999
还没有现成的日志框架:
Java 工程师只能使用原始的 System.out.println()
System.err.println() 或者 e.printStackTrace()
把 debug 日志写到 StdOut 流
,错误日志写到 ErrOut 流
,以此记录应用程序的运行状态
Ceki Gülcü 切基·居尔库
创建了 Log4j 项目
JUL 2002
不过被 sun 公司拒绝了
JUL
🙃
sun公司真傲娇,终于知道怎么破产的了~
JCL 2002
日志框架
不同的使用方式,不同日志级别~ 对开发者不是很友好~
2002年 阿帕奇
推出了 JCL日志门面
定义一套接口,具体实现由Log4j 或 JUL 来实现
程序运行时会使用 ClassLoader类加载器
寻找和载入底层的日志库,因此可以自由选择由 log4j 或 JUL 来实现日志功能
增删改查接口, Mysql Oracle...数据库都实现类这些接口
可以做到,无需更换代码切换数据库~
Slf4j 2005
& Logback 2006
日志框架
Ceki Gülcü
大佬,后面离开了阿帕奇,独自开发了一个新的 日志门面 Slf4j
Logback
是其的实现~
大佬牛逼呀!🐂
log4j2 2014
Log4j2
来自老东家的'制裁🙃'
经过上面,我们已经知道常用的日志框架有:Log4j JUL JCL Slf4j Logback Log4j2
这些日志框架可以分为两种类型:门面日志
和日志系统
日志门面:设计模式 外观模式
JCL、slf4j
提供简单实现
提高开发者的使用~
日志系统:
JUL、logback、log4j、log4j2
JUL全称Java.util.Logging
Logger
getLogger();
获取 Logger 对象,调用其 API 来发布日志信息Handler
Console
File
日志记录器logger
有自己默认的,Filter Formatter Level,可以与一个 或 多个Hanlder关联进行
日志输出~
创建一个JUL Maven工程
JUL 是Java 本身提供的,所以,不需要引入任何依赖…
JULTest.Java
import org.junit.Test;
import java.util.logging.Level;
import java.util.logging.Logger;
public class JULTest {
//入门案例:
@Test
public void Test(){
//1.获取日志记录器对象 Logger.getLogger("参数是一个唯一的字符串,一般来说使用类的'全路径名'");
Logger logger = Logger.getLogger("com.wsm.JULTest");
//2.日志记录输出
logger.info("输出日志,info 级别~");
/** 常用方法 */
//通过 .log(); 方法,指定日志的输出级别
logger.log(Level.INFO,"指定输出级别: info~");
logger.log(Level.WARNING,"指定输出级别: WARNING~");
//通过占位符形式,输出日志, 类似于 printf();
String name = "WSM";
Integer age = 3;
logger.log(Level.INFO,"我叫: “{0},今年{1}岁",new Object[]{name,age});
}
}
控制台输出:
十二月 15, 2021 9:43:06 下午 com.wsm.JULTest Test
信息: 输出日志,info 级别~
十二月 15, 2021 9:43:06 下午 com.wsm.JULTest Test
信息: 指定输出级别: info~
十二月 15, 2021 9:43:06 下午 com.wsm.JULTest Test
警告: 指定输出级别: WARNING~
十二月 15, 2021 9:43:06 下午 com.wsm.JULTest Test
信息: 我叫: “WSM,今年3岁
JUL 一个有七个日志级别,两个特殊的 off All
日志级别 | 数值 | 说明 |
---|---|---|
OFF | Integer.MAX_VALUE 最大整数 | 关闭所有消息的日志记录 |
SEVERE中: 色歪啊~ | 1000 | 错误信息最高级的日志级别 |
WARNING | 900 | 警告信息 |
INFO | 800 | 默认信息默认级别 |
CONFIG | 700 | 配置信息 |
FINE | 500 | 详细信息(少) |
FINER | 400 | 详细信息(中) |
FINEST | 300 | 详细信息(多) 最低级的日志级别 |
ALL | Integer.MIN_VALUE最小整数 | 启用所有消息的日志记录 |
数值的意义在于: 设置指定了日志级别,最终展示的信息,必须大于> 指定级别的数值!
全部关闭
全部启动
//检查JUL默认输出级别
@Test
public void Test2(){
// 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");
}
//自定义日志级别1.0
@Test
public void Test3(){
// 1.获取日志记录器对象
Logger logger = Logger.getLogger("com.itheima.JULTest"); //参数,保证唯一即可,一般填写 类路径名;
// 2.设置日志输出级别: 改变Logger 日志对象
// 首先关闭系统默认配置
logger.setUseParentHandlers(false);
// 重新,配置日志具体级别 (你以为这样就行了吗? 执行发现啥也没有~ 因为,现在日志配置还不完整,缺少了 Handler 还有 Formatter )
logger.setLevel(Level.ALL);
// 3.日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
//自定义日志级别2.0
@Test
public void Test4(){
// 1.获取日志记录器对象
Logger logger = Logger.getLogger("com.itheima.JULTest");
// 2.设置日志输出级别: 改变Logger 日志对象
// 首先关闭系统默认配置
logger.setUseParentHandlers(false);
// 重新,配置日志具体级别
logger.setLevel(Level.ALL);
// 创建ConsolHhandler 控制台输出
ConsoleHandler consoleHandler = new ConsoleHandler();
// 创建简单格式转换对象
SimpleFormatter simpleFormatter = new SimpleFormatter();
// 将Handler 和 Formatter进行关联: logger ——关联—— Handler ——关联—— Formatter
consoleHandler.setFormatter(simpleFormatter);
logger.addHandler(consoleHandler);
// 设consoleHandler 控制台输出的级别~
consoleHandler.setLevel(Level.ALL);
// 3.日志记录输出
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
Test2
十二月 15, 2021 9:45:17 下午 com.itheima.JULTest testLogLevel
严重: severe
十二月 15, 2021 9:45:17 下午 com.itheima.JULTest testLogLevel
警告: warning
十二月 15, 2021 9:45:17 下午 com.itheima.JULTest testLogLevel
信息: info
Test3
CMD输出空!因为,日志的配置不完整,缺少了 Handler Formatter
Test4
十二月 15, 2021 10:33:34 下午 com.wsm.JULTest Test4
严重: severe
十二月 15, 2021 10:33:34 下午 com.wsm.JULTest Test4
警告: warning
十二月 15, 2021 10:33:34 下午 com.wsm.JULTest Test4
信息: info
十二月 15, 2021 10:33:34 下午 com.wsm.JULTest Test4
配置: config
十二月 15, 2021 10:33:34 下午 com.wsm.JULTest Test4
详细: fine
十二月 15, 2021 10:33:34 下午 com.wsm.JULTest Test4
较详细: finer
十二月 15, 2021 10:33:34 下午 com.wsm.JULTest Test4
非常详细: finest
Test2
因为,JUL的默认级别就是 info 小于info的级别数值都输出不出来
Test3
因为:日志的配置不完整,缺少了 Handler Formatter
Test4
级别的日志
并进行关联~🔗
最重要的是,设置了Console的输出级别,这个是你控制台输出的日志级别!
consoleHandler.setLevel(Level.ALL);
设置的级别 ,取绝于 logger.setLevel(Level.ALL);
logger 是真正设置输入的日志级别,但它因为配置不完整需要,consoleHandler输出. consoleHandler的级别在大 logger级别放的小,输出的数据也不多~
两者关系就像是两个水桶💧 logger小桶
放在consoleHandler大桶
大桶的出水口在大,小桶一次出一滴水,大桶也只有一滴水流速🌊
小桶出水口很大,每次流很多水,大桶的口小,每次流出的水也不大!
//日志文件的输出:
@Test
public void Test5() throws IOException {
// 1.获取日志记录器对象
Logger logger = Logger.getLogger("com.itheima.JULTest");
// 2.设置日志输出级别: 改变Logger 日志对象
// 首先关闭系统默认配置
logger.setUseParentHandlers(false);
// 重新,配置日志具体级别
logger.setLevel(Level.ALL); //设置小桶流速!
// 创建格式
SimpleFormatter simpleFormatter = new SimpleFormatter();
// 创建ConsolHhandler 控制台输出: 关联 输出级别
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(simpleFormatter);
logger.addHandler(consoleHandler);
consoleHandler.setLevel(Level.ALL);
// 创建文件输出 Handler
FileHandler fileHandler = new FileHandler("C:\\Users\\王斯明\\Desktop\\jul.log"); /** 要确保输出的目录存在! */
fileHandler.setFormatter(simpleFormatter);
logger.addHandler(fileHandler);
// 不设置,日志级别,输出日志~, 发现默认输出了全部, (继承了小桶logger的流量~)
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
Logger 可以持有多个处理器 Handler
FileHandler 来设置,日志的输出文件,地址目录要事先存在!
如果不指定,输出日志级别,则默认,根据logger设置的级别Logger对象的父子关系~
// Logger对象父子关系
@Test
public void Test6(){
Logger logger1 = Logger.getLogger("com.wsm");
Logger logger2 = Logger.getLogger("com");
// 测试
System.out.println("logger1上级 与 logger2比较:"+(logger1.getParent() == logger2));
// 所有日志记录器的顶级父元素 LogManager$RootLogger,name ""
System.out.println("logger2 Parent:"+logger2.getParent() + ",name:" + logger2.getParent().getName());
System.out.println("下级会默认继承上级的配置,改变logger2 logger1也会发生改变~");
// 关闭默认配置
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);
System.out.println();
System.out.println("输出logger1 发现,输出级别变成了 info");
logger1.severe("severe");
logger1.warning("warning");
logger1.info("info");
logger1.config("config");
logger1.fine("fine");
logger1.finer("finer");
logger1.finest("finest");
}
logger1上级 与 logger2比较:true
logger2 Parent:java.util.logging.LogManager$RootLogger@61e4705b,name:
下级会默认继承上级的配置,改变logger2 logger1也会发生改变~
输出logger1 发现,输出级别变成了 info
十二月 16, 2021 12:00:11 上午 com.wsm.JULTest Test6
严重: severe
十二月 16, 2021 12:00:11 上午 com.wsm.JULTest Test6
警告: warning
十二月 16, 2021 12:00:11 上午 com.wsm.JULTest Test6
信息: info
十二月 16, 2021 12:00:11 上午 com.wsm.JULTest Test6
配置: config
十二月 16, 2021 12:00:11 上午 com.wsm.JULTest Test6
详细: fine
十二月 16, 2021 12:00:11 上午 com.wsm.JULTest Test6
较详细: finer
十二月 16, 2021 12:00:11 上午 com.wsm.JULTest Test6
非常详细: finest
父子关系
下降会默认具有,上级的属性配置…`默认的上级是 LogManager$RootLogger@61e4705b,name
上面的,硬编码形式,可以完成简单的 日志记录输出,但是实际开发中,并不方便 开发中使用🙁
logger对象对象具有 父子关系
默认的上级是, LogManager$RootLogger@61e4705b,name
LogManager$RootLogger@61e4705b,name
又是哪里来的,又为什么是默认 info 配置
🧐?Logger.getLogger("");
方法,获取一个日志记录器对象
,ctrl+右击
进入方法~
demandLogger();
进入!,
发现LogManager manager = LogManager.getLogManager();
获取一个LogManager日志管理器对象!
LogManager是一个单例对象,负责记录和管理日志对象
getLogManager();
进入 ensureLogManagerInitialized();
发现:owner.readPrimordialConfiguration();加载配置文件!
**
加载配置文件之后,就会执行 owner.rootLogger = owner.new RootLogger();
设置顶级的,rootLogger 对象
readPrimordialConfiguration();
方法查看,加载配置文件流程:发现readConfiguration();
读取配置文件,继续进入👉
首先:String cname = System.getProperty("java.util.logging.config.class");
当前系统是否存在一个配置类!没有则往下!
判断 String fname = System.getProperty("java.util.logging.config.file");
当然系统下 有没有 自定义的配置文件!
如果没有则:fname = System.getProperty("java.home");
获取Java安装目录,
File f = new File(fname, "lib");
f = new File(f, "logging.properties");
读取,JDK目录中的lib目录下的 logging.properties
👍
logging.properties
# 设置一个控制台输出的 Handler
handlers= java.util.logging.ConsoleHandler
# 设置默认的日志级别 Info
.level= INFO
# 配置默认的 文件处理器
# 指定日志文件默认的输出路径, (%h输出当前设备的用户目录) (%u输出日志的文件后缀1 2 3...)
java.util.logging.FileHandler.pattern = %h/java%u.log
# 当前文件最大存储多少条记录...
java.util.logging.FileHandler.limit = 50000
# 当前文件的数量,输出日志文件的个数~
java.util.logging.FileHandler.count = 1
# 当前 处理器输出文件的格式, 默认 xml格式输出!
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
# 控制台处理器的 默认级别INfo
java.util.logging.ConsoleHandler.level = INFO
# 日志输出的格式
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
# Example to customize the SimpleFormatter output format
com.xyz.foo.level = SEVERE
因此,我们只需要,负责这个配置文件保证名字不变
,到项目的 resources资源目录下
即可通过配置文件形式,配置日志信息!
resources资源目录下,添加 logging.properties
# RootLogger 顶级父元素指定的默认处理器为:ConsoleHandler,FileHandler (设置RootLogger 最高价关联的处理器)
handlers= java.util.logging.ConsoleHandler,java.util.logging.FileHandler
# RootLogger 顶级父元素默认的日志级别为:ALL
.level= ALL
# 关闭默认配置
com.itheima.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
JULConfigTest.Java
/** 配置文件形式使用,JUL */
public class JULConfigTest {
@Test
public void Test() throws Exception{
// 读取配置文件,通过类加载器
InputStream ins = JULTest.class.getClassLoader().getResourceAsStream("logging.properties");
// 创建LogManager
LogManager logManager = LogManager.getLogManager();
// 通过LogManager加载配置文件
logManager.readConfiguration(ins);
// 创建日志记录器
Logger logger = Logger.getLogger("com.wsm");
logger.info("--------------------------------------------------------------------------------------------");
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
System.out.println("");
// // 创建日志记录器2
// Logger logger2 = Logger.getLogger("www");
// logger2.severe("severe");
// logger2.warning("warning");
// logger2.info("info");
// logger2.config("config");
// logger2.fine("fine");
// logger2.finer("finer");
// logger2.finest("finest");
}
}
除了通过 logging.properties
配置文件,设置大众的配置,也可以单独对于某一种的 日志对象进行设置!
## 自定义Logger使用,单独设置自定义的, 日志元素
www.handler = java.util.logging.ConsoleHandler
www.level = INFO
解除上面 Logger.getLogger("www");
注释!
ok, JUL就了解这么多了,现在用的也很少了…几乎没有公司在使用了...
Log4j是Apache下的一款开源的日志框架:官方网站
控制台
、文件
、甚至是数据库中
Log4J 主要由:Loggers日志记录器
Appenders输出端
Layout日志格式化器
控制日志的输出级别与日志是否输出
Logger.getLogger(类的全限定名/类对象~);
有继承机制
com.wsm 会继承,com限定名的日志属性~
root logger可以用Logger.getRootLogger()方法获取
指定日志的输出地方输出到控制台、文件
输出端类型 | 作用 |
---|---|
ConsoleAppender | 将日志输出到控制台 |
FileAppender | 将日志输出到文件中 |
DailyRollingFileAppender | 将日志输出到一个日志文件,并且每天输出到一个新的文件 |
RollingFileAppender | 将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件 |
JDBCAppender | 把日志信息保存到数据库中 |
控制日志信息的输出格式
格式化器类型 | 作用 |
---|---|
HTMLLayout | 格式化日志输出为HTML表格形式 |
SimpleLayout | 简单的日志输出格式化,打印的日志格式为(info - message 输入什么打印什么. |
PatternLayout | 最强大的格式化期,可以根据自定义格式输出日志,如果没有指定转换格式, 就是用默认的转换格式 |
Layout的格式:
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}
%F 输出日志消息产生时所在的文件名称
%L 输出代码中的行号
%% 输出一个 "%" 字符
%l 输出日志时间发生的位置,包括类名%c、线程%t、及在代码中的行数%L 如:Test.main(Test.java:10)
可以在 % 与字符之间加上修饰符来控制最小宽度、最大宽度和文本的对其方式. 如:
%5c 输出category名称,最小宽度是5,category<5,默认的情况下右对齐
%-5c 输出category名称,最小宽度是5,category<5,"-"号指定左对齐,会有空格
%.5c 输出category名称,最大宽度是5,category>5,就会将左边多出的字符截掉,<5不会有空格
%20.30c category名称<20补空格,并且右对齐,>30字符,就从左边交远销出的字符截掉
建立maven工程
添加依赖 因为,不是Java本身提供的需要引入对应的依赖~
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- 为了方便测试,可以导入 JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- log4j 具有操作数据库的功能,引入对应的 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
Log4jTest.Java
//阿帕奇 log4j 的包哦~
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.junit.Test;
public class Log4jTest {
/** 入门案例 */
@Test
public void test(){
//1. 使用前需要初始化配置信息,不然会报错! (实际开发是通过,配置文件; 入门案例需要引入默认的)
BasicConfigurator.configure();
//2. 创建日志对象
Logger logger = Logger.getLogger(Log4jTest.class);//不仅支持 唯一字符 还有 类对象
//3. 输出日志信息!
System.out.println("log4j 和 JUL 的日志级别,有些不同..");
logger.fatal("fatal 严重错误,一般会造成系统崩溃并终止运行");
logger.error("error 错误信息,不会影响系统运行");
logger.warn("warn 警告信息,可能会发生问题");
logger.info("info 追踪信息,记录程序所有的流程信息");
logger.debug("debug 调试信息,一般在开发中使用,记录程序变量参数传递信息等等");
logger.trace("trace 追踪信息,记录程序所有的流程信息");
}
}
源码分析:
Logger.getLogger()
进入,发现Logger 对象是又,一个LogManager对象.getLogger();创建
进入LogManage类
发现 static{ 代码块中 }
url = Loader.getResource("log4j.properties");
类扫描读取log4j.properties 配置文件
,只要 resource 资源目录下存在 log4j.properties文件,就不需要初始化配置信息了!
static{ 代码块中 }
内部有一个OptionConverter.selectAndConfigure(..)
读取配置文件…根据文件类型,动态读取 xml properties
获取Configurator对象
Configurator对象
用于加载,初始化 root 对象,可以通过它来,查看配置文件如何编写
selectAndConfigure方法中
, new PropertyConfigurator();
进入
发现:APPENDER_PREFIX
RENDERER_PREFIX
等很多的配置文件,属性…
日志对象
log4j.properties
resources 资源文件下,添加配置文件;⚙
# 设置默认的日志对象 rootLogger
# 日志级别 和 绑定的appender(可以多个) 逗号,分隔
log4j.rootLogger = trace,console
# 指定控制台日志输出的 appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定控制台日志输出的 layout 输出格式默认 SimpleLayout
log4j.appender.console.layout = org.apache.log4j.SimpleLayout
Log4jTest.Java
/** 配置文件加载: rootlogger对象; */
@Test
public void test1(){
//resources 目录下添加一个, log4j.properties
//这样就不需要 BasicConfigurator.configure(); 加载默认配置文件了;
// 开启 log4j 内置日志记录 (随便,开启日志输出,日志框架的日志信息~
LogLog.setInternalDebugging(true);
//创建日志对象
Logger logger = Logger.getLogger(Log4jTest.class);
//输出日志信息!
System.out.println("log4j 和 JUL 的日志级别,有些不同..");
logger.fatal("fatal 严重错误,一般会造成系统崩溃并终止运行");
logger.error("error 错误信息,不会影响系统运行");
logger.warn("warn 警告信息,可能会发生问题");
logger.info("info 追踪信息,记录程序所有的流程信息");
logger.debug("debug 调试信息,一般在开发中使用,记录程序变量参数传递信息等等");
logger.trace("trace 追踪信息,记录程序所有的流程信息");
}
log4j 和 JUL 的日志级别,有些不同..
FATAL - fatal 严重错误,一般会造成系统崩溃并终止运行
ERROR - error 错误信息,不会影响系统运行
WARN - warn 警告信息,可能会发生问题
INFO - info 追踪信息,记录程序所有的流程信息
DEBUG - debug 调试信息,一般在开发中使用,记录程序变量参数传递信息等等
TRACE - trace 追踪信息,记录程序所有的流程信息
test1()
# 指定控制台日志输出的 layout 输出格式默认 SimpleLayout (HTMLLayout(以HTML展示日志信息) / xml.XMLLayout(以xml形式展示数据) / PatternLayout(自定义常用!)
log4j.appender.console.layout = org.apache.log4j.HTMLLayout
输出的日志就会以,html 形式进行展示📺
log4j.properties
追加
# 设置默认的日志对象 rootLogger
# 日志级别 和 绑定的appender(可以多个) 逗号,分隔
log4j.rootLogger = trace,console
# 指定控制台日志输出的 appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定控制台日志输出的 layout 输出格式默认 SimpleLayout (HTMLLayout / xml.XMLLayout / PatternLayout
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容 layout.conversionPattern
log4j.appender.console.layout.conversionPattern = [%-10p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
# 设置,数据格式:
# [%-10p]: [数据长度10且居左] %r输出自应用启动到输出该log信息耗费的毫秒数
# %r: 输出自应用启动到输出该 log 信息耗费的毫秒数
# %l: 输出日志时间发生的位置,包括类名、线程、及在代码中的行数
# %d: 输出服务器当前时间,默认为 ISO8601,也可以指定格式
后期这样的格式,公司都是有固定的规范的已经配置好了
了解即可!
log4j.properties
追加
设置 log4j.rootLogger = trace,console,file
绑定对应的 appender
# 日志级别 和 绑定的appender(可以多个) 逗号,分隔
log4j.rootLogger = trace,console,file
# 日志文件输出的 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
# 指定日志文件保存路径 (需要手动指定,日志文件保存到磁盘...不然出错,本人这回默认项目的盘符 D盘,没有文件创建文件~
log4j.appender.file.file = /logs/log4j.log
# 指定日志文件的字符集
log4j.appender.file.encoding = UTF-8
运行test1()
log4j 不像JUL ,数据默就追加了~
rollingFile
log4j.properties
追加
设置 log4j.rootLogger = trace,console,rollingFile
绑定对应的 appender
# 日志级别 和 绑定的appender(可以多个) 逗号,分隔
log4j.rootLogger = trace,console,rollingFile
# 按照文件大小拆分的 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
# 指定日志文件内容的大小( 没个文件1mb 产生新的日志文件~
log4j.appender.rollingFile.maxFileSize = 1MB
# 指定日志文件的数量(最多产生 10个日志文件, log4j.log log4j2.log log4j3.log, 超出文件个数,旧的文件会新文件被覆盖
log4j.appender.rollingFile.maxBackupIndex = 10
Log4jTest.Java
循环1w次查看结果~
for (int i = 0; i < 10000; i++) {
logger.fatal("fatal 严重错误,一般会造成系统崩溃并终止运行");
logger.error("error 错误信息,不会影响系统运行");
logger.warn("warn 警告信息,可能会发生问题");
logger.info("info 追踪信息,记录程序所有的流程信息");
logger.debug("debug 调试信息,一般在开发中使用,记录程序变量参数传递信息等等");
logger.trace("trace 追踪信息,记录程序所有的流程信息");
}
dailyFile
log4j.properties
追加
设置 log4j.rootLogger = trace,console,dailyFile
绑定对应的 appender
# 日志级别 和 绑定的appender(可以多个) 逗号,分隔
log4j.rootLogger = trace,console,dailyFile
# 按照时间规则拆分的 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
运行:test1()
创建对应的数据库, 数据库表脚本 .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`)
);
log4j.properties
追加
设置 log4j.rootLogger = trace,console,dailyFile
绑定对应的 appender
# 日志级别 和 绑定的appender(可以多个) 逗号,分隔
log4j.rootLogger = trace,console,logDB
#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
# 设置数据库: 连接信息还有操作的数据库,(本人的数据库的 log~
log4j.appender.logDB.URL=jdbc:mysql://localhost:3306/log
# 数据库 用户名
log4j.appender.logDB.User=root
# 数据库 密码
log4j.appender.logDB.Password=ok
# 设置入库的语句, %? 对应列要存的数据,参考,PatternLayout
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')
运行test1()
作用:对于多个生成环境,需要记录的是不同的日志…可以使用 自定义日志对象类设置~
log4j.properties
# 自定义 logger 对象设置 (输出日志级别更改为了 info, appender还会默认继承 rootlogger
log4j.logger.com.wsm = info,console
Log4jTest.Java
/** 自定义 logger 对象 */@Testpublic void test2(){ //获取自定义日志对象~ Logger logger = Logger.getLogger("com.wsm"); //输出~ logger.fatal("fatal 严重错误,一般会造成系统崩溃并终止运行"); logger.error("error 错误信息,不会影响系统运行"); logger.warn("warn 警告信息,可能会发生问题"); logger.info("info 追踪信息,记录程序所有的流程信息"); logger.debug("debug 调试信息,一般在开发中使用,记录程序变量参数传递信息等等"); logger.trace("trace 追踪信息,记录程序所有的流程信息");}
只展示大于等于 info 的日志级别~
[FATAL ]0 com.wsm.Log4jTest.test2(Log4jTest.java:57) 2021-12-19 22:35:11.558 fatal 严重错误,一般会造成系统崩溃并终止运行
[ERROR ]3 com.wsm.Log4jTest.test2(Log4jTest.java:58) 2021-12-19 22:35:11.561 error 错误信息,不会影响系统运行
[WARN ]3 com.wsm.Log4jTest.test2(Log4jTest.java:59) 2021-12-19 22:35:11.561 warn 警告信息,可能会发生问题
[INFO ]3 com.wsm.Log4jTest.test2(Log4jTest.java:60) 2021-12-19 22:35:11.561 info 追踪信息,记录程序所有的流程信息
全称为Jakarta Commons Logging
,是Apache提供的一个通用日志API,是一种日志门面
SimpleLog
几乎被淘汰了, 一般不会单独使用它
他允许开发人员使用不同的具体日志实现工具: Log4j, Jdk 自带的日志(JUL)
Log(基本记录器)
和 LogFactory(负责创建Log实例)
创建Maven工程
添加 pom.xml
依赖
<dependencies>
<!-- JCL的日志依赖 -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- junit的依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- log4j的依赖! -->
<!-- <dependency>-->
<!-- <groupId>log4j</groupId>-->
<!-- <artifactId>log4j</artifactId>-->
<!-- <version>1.2.17</version>-->
<!-- </dependency>-->
</dependencies>
JCLTest.Java
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
/** JUL入门案例~ */
public class JCLTest {
@Test
public void test(){
// 获取 log日志记录器对象, JCL通过一个日志工厂 获取一个日志对象;
Log log = LogFactory.getLog(com.wsm.JCLTest.class);
// 日志记录输出
log.info("hello jcl");
}
}
输出了和 JUL一样
的日志数据, 当没有引入 log4j依赖时候
JCL默认就通过JUL实现,则使用log4j
十二月 19, 2021 11:02:59 下午 com.wsm.JCLTest test
信息: hello jcl
解开log4j的依赖配置
添加:依赖,配置文件 log4j.properties
# 设置默认的日志对象 rootLogger
# 日志级别 和 绑定的appender(可以多个) 逗号,分隔
log4j.rootLogger = trace,console
# 指定控制台日志输出的 appender
log4j.appender.console = org.apache.log4j.ConsoleAppender
# 指定控制台日志输出的 layout 输出格式默认 SimpleLayout (HTMLLayout / xml.XMLLayout / PatternLayout
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 指定消息格式的内容 layout.conversionPattern
log4j.appender.console.layout.conversionPattern = [%-10p]%r %l %d{yyyy-MM-dd HH:mm:ss.SSS} %m%n
运行test()
,是log4j
的输出方式
[INFO ]1 com.wsm.JCLTest.test(JCLTest.java:13) 2021-12-19 23:17:35.636 hello jcl
JCL 是一个日志门面,随着日志的使用频繁市面上又有很多个不同的日志框架
,开发者需要同时掌握多个日志框架(实在不是很友好~
阿帕奇,就退出了一个日志门面,来统一实现了接口,JUL 和 log4j都对其进行了实现。
好处,开发者只需要学习一个日志框架的使用,即使后面更改日志框架,也不会对代码产生任何影响~👍 只需要替换一个依赖即可!
SimpleLog
,但很少有人使用~
但,JCL 对于不同日志框架的切换实现…
是以一种,硬编码形式实现,之兼容了少量的日志框架
,如果后面需要引入新的日志,还需要更改配置文件~
所以,后面又出现了一种新的日志门面 Slf4j
它可以兼容目前市面上所有/以及未来的 日志框架
简单日志门面Simple Logging Facade For Java
具体的实现可以交由其他日志框架
但是一般很少用到
slf4j-api作为门面
SLF4J日志门面主要提供两大功能:
创建Maven工程:
导入对应的依赖:
<!-- slf4j 日志门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
Slf4jTest.Java
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Slf4j入门案例 */
public class Slf4jTest {
//声明全局的 日志对象
public final static Logger LOGGER = LoggerFactory.getLogger(Slf4jTest.class);
/** 简单入门 */
@Test
public void Test(){
// 日志输出(默认输出至,控制台 info级别;
LOGGER.error("error");
LOGGER.warn("wring");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
// 使用占位符输出日志信息
String name = "wsm";
Integer age = 540;
LOGGER.info("用户:{},{}",name,age);
// 将系统的异常信息输出
try {
int i = 1/0;
} catch (Exception e) {
// e.printStackTrace(); 这样程序就不会,打印Java的 堆栈一推错误了...
LOGGER.error("出现异常:",e);
}
}
}
[main] ERROR com.wsm.Slf4jTest - error
[main] WARN com.wsm.Slf4jTest - wring
[main] INFO com.wsm.Slf4jTest - info
[main] INFO com.wsm.Slf4jTest - 用户:wsm,540
[main] ERROR com.wsm.Slf4jTest - 出现异常:
java.lang.ArithmeticException: / by zero
at com.wsm.Slf4jTest.Test(Slf4jTest.java:29)
#省略下面更多,异常信息~
Slf4j 作为一个强大的日志门面
,可以在不改变代码的情况下,实现切换不同的日志框架…支持不同的日志框架的绑定!
上图,是Slf4j 官方的图片,与各个框架进行绑定的介绍:
application 指定是应用程序 SLF4J API 就是Slf4j日志门面
从左往右——>1-6
项目只是单纯的引入了 日志门面...
logback
slf4j内置实现
log4j2
都是在,slf4j门面之后出现的,因此都对其进行了实现,之间导入 Slf4j日志门面依赖
和 对应实现依赖
即可,自动绑定完成
slf4j门面
所以在,使用之前还需要导入对应的 Adaptation适配器
来进适配.
使用 slf4j门面方法
,内部适配器在调用,JUL
/ log4j
的方法~
接下来,粗略介绍一下各个日志框架的绑定:slf4j内置实现,入门案例就是
因为,已经默认实现类,slf4j 直接引入依赖即可… 别忘记注释,slf4j内置实现依赖
<!--logback 日志实现-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
当我们还不能确定,使用什么日志框架来实现时候,可以引入 slf4j-nop
它是 slf4j的一种空实现,因为如果不引入 slf4j的实现,会报错Defaulting to no-operation (NOP) logger implementation
日志实习依赖
运行程序异常~
slf4j-nop
依赖,运行test();
<!-- nop 日志开关: slf4j的一种空实现~ -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.25</version>
</dependency>
因为,Log4j诞生早于,Slf4j所以并没有直接对其进行实现,slf4j 与 log4j 进行绑定
还需要添加一个 适配器
注释其它实现依赖…
<!--绑定 log4j 日志实现,需要导入适配器 --><dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.12</version></dependency><!-- log4j依赖 --><dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version></dependency>
运行 test()
报错:log4j:WARN No appenders could be found for logger (com.wsm.Slf4jTest).
appenders
需要,添加对应的log4j.properties
配置文件~同上,JUL也需要添加一个适配器
因为JUL 是JDk 自带的,所以,只需要引入一个 适配器依赖即可!
<!--绑定 jul 日志实现,需要导入适配器 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.25</version>
</dependency>
JUL ,JDK内有默认的 配置文件.
使用slf4j的日志绑定流程:
默认使用第一个依赖日志实现
Bridging
假设,突然让你维护一个比较老的项目。老板让你把原先项目的日志框架 log4j 换成 logback
slf4j门面
一个属于 JCL门面
直接替换jar包,肯定不行!移除依赖,修改代码❓
那真的太狗血了🐕!还好,slf4j 提供了桥接,技术 支持开发者引入对应的日志的 桥接器
来完成配置!
Slf4j模块,添加 Log4jTest.Java
import org.apache.log4j.Logger;
import org.junit.Test;
public class Log4jTest {
// 定义 log4j 日志记录器
public static final Logger LOGGER = Logger.getLogger(Log4jTest.class);
// 测试桥接器
@Test
public void test01()throws Exception{
LOGGER.info("hello lgo4j");
}
}
别忘记了 log4j 需要引入配置文件log4j.properties
, 运行 test01();
15:52:04.308 [main] INFO com.itheima.Log4jTest - hello lgo4j
上面是 log4j的代码和输出,下面是,切换的logback的代码输出..
引入依赖:注释log4j的依赖...
<!-- Slf4j的桥接技术 -->
<!-- 将log4j 日志框架,不更改代码清空下,替换为 logback: 注释掉所有的依赖,只保留 JUnit -->
<!-- log4j的所需依赖 -->
<!-- <dependency>-->
<!-- <groupId>log4j</groupId>-->
<!-- <artifactId>log4j</artifactId>-->
<!-- <version>1.2.17</version>-->
<!-- </dependency>-->
<!-- 注释,log4j 的实现依赖; -->
<!--logback 日志实现-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!--配置 log4j 的桥接器-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
十二月 20, 2021 4:48:50 下午 com.wsm.log4jTest test01
信息: hello lgo4j
发现代码不变,输出的方式已经变成了 logback
原因:log4j-over-slf4j桥接器,内部调用了 Slf4j的方法,
相当于进行了一个重定向~
log4j-over-slf4j.jar
和slf4j-log4j12.jar
不能同时出现
jcl-over-slf4j.jar
和 slf4j-jcl.jar
不能同时出现
jul-to-slf4j.jar
和slf4j-jdk14.jar
不能同时出现
日志桥接器,不能和 日志的实现依赖同时出现,
因为,日志桥接器
底层还是去调用Slf4j的接口方法. Slf4j 回去调用日志实现
而如果同时引入了,相同的 桥接器
日志实现
,日志实现 --调用–> 桥接器 --调用–> Slf4j --调用–> 日志实现 陷入死循环,堆栈溢出!
Logback是由log4j创始人设计的另一个开源日志组件:官方地址
Logback主要分为三个模块:
Maven具有继承特性,引入了 classic/access 就默认具有 core了
用于Tomcat/Jetty 日志直接输出到对应的服务器日志中去!
创建Maven 工程
引入 logback 依赖:
<!--slf4j 日志门面-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<!--logback 日志实现-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!--junti 单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
LogbackTest.Java
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** logBack日志入门案例 */
public class LogbackTest {
//创建全局的 logger 对象
public static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);
@Test
public void test(){
//调用 Slf4j 接口实现日志输出;
// logback 一共有五个日志级别,的默认级别是, debug
LOGGER.error("error");
LOGGER.warn("wring");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
}
}
存在,默认的配置 rootLogger
默认:控制台输出, 日志级别debug 😶
10:28:18.657 [main] ERROR com.wsm.LogbackTest - error
10:28:18.662 [main] WARN com.wsm.LogbackTest - wring
10:28:18.662 [main] INFO com.wsm.LogbackTest - info
10:28:18.662 [main] DEBUG com.wsm.LogbackTest - debug
如果均不存在会采用默认配置
在 resources 资源目录下,创建任意一个配置文件即可
xml 格式,方便读取,和操作, 使用也相对比较多,本篇介绍logback.xml
配置文件编写✍~
和其它日志框架,也大致一样,日志对象
记录器
日志输出格式
Logger 日志记录器对象
context环境(配置文件
,LoggerFactory.getLogger()
生成一个 日志记录器对象
,主要用于存放日志对象,定义日志类型、级别
定义于ch.qos.logback.classic.Level
Appender 记录器
Layout 日志输出格式
logback中Layout对象被封 装在encoder中
resources 资源目录下添加: logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- configuration xml的根节点,存在三个属性;
scan: 当此属性设置为true时,配置文件如果发生改变,将会被重新加载,默认值为true
scanPeriod: 设置监测配置文件是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒,当scan为true时,此属性生效;
debug: 当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false;
-->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- contextName:
用来设置上下文名称,每个logger都关联到logger上下文,默认上下文名称为default;
置成其他名字,用于区分不同应用程序的记录;
-->
<contextName>mylog</contextName>
<!-- property:
用来定义变量值,它有两个属性name和value,通过<property>定义的值 可以被配置文件上下文引用, ${name名} 来使用变量
name: 变量的名称
value: 的值时变量定义的值
-->
<!--定义日志文件保存路径属性-->
<property name="log_dir" value="/logs"></property>
<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换行 -->
</configuration>
logback.xml
追加:
<!-- 控制台日志输出的 appender 它有两个必要属性name和class;
name指定appender名称
class指定appender的全限定名,不同的 输出Appender 又有不同的子元素设置:
-->
<!-- ConsoleAppender 把日志输出到控制台;
<encoder>: 对日志进行格式化
<target>: Java控制台输出,字符串System.out(默认)或者System.err(红色字体
-->
<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>
<!-- rootlogger 配置:
level: 设置日志输出级别;
<appender-ref>: 设置rootlogger 绑定的appender,支持绑定多个~
-->
<root level="ALL">
<appender-ref ref="console"/> <!-- 根据 appender 的name 属性进行关联; -->
</root>
控制台:输出对应格式的 system.err 输出的红色字体~
[ERROR] 2021-12-21 16:54:09.008 com.itheima.LogbackTest testQuick 16 [main] error
[WARN ] 2021-12-21 16:54:09.012 com.itheima.LogbackTest testQuick 17 [main] wring
[INFO ] 2021-12-21 16:54:09.012 com.itheima.LogbackTest testQuick 18 [main] info
[DEBUG] 2021-12-21 16:54:09.012 com.itheima.LogbackTest testQuick 19 [main] debug
[TRACE] 2021-12-21 16:54:09.012 com.itheima.LogbackTest testQuick 20 [main] trace
logback.xml
<!-- 日志文件输出的 appender-->
<!-- FileAppender 把日志添加到文件;
<file>: 被写入的文件名,可以是相对目录,也可以是绝对目录,如果上级目录不存在会自动创建,没有默认值;
<append>: 如果是 true 日志被追加到文件结尾 (默认 true;
<encoder>: 对记录事件进行格式化;
<prudent>: 如果是 true,日志会被安全的写入文件,即使其他的FileAppender也在向此文件做写入操作; (效率低默认是 false;
-->
<appender name="file" class="ch.qos.logback.core.FileAppender">
<!--日志文件保存路径: ${log_dir}引入上面的,全局变量-->
<file>${log_dir}/logback.log</file>
<!--日志消息格式配置-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!-- rootlogger 配置:
level: 设置日志输出级别;
<appender-ref>: 设置rootlogger 绑定的appender,支持绑定多个~
-->
<root level="ALL">
<appender-ref ref="console"/> <!-- 根据 appender 的name 属性进行关联; -->
<appender-ref ref="file"/>
</root>
logback.xml
<!--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>
<!-- 设置:rootlogger 配置 -->
logback.xml
<!-- 日志拆分和归档压缩的 appender 对象-->
<!-- RollingFileAppender
省略与 FileAppender 相同的元素;
<rollingPolicy>: 当发生滚动时,决定RollingFileAppender的行为,涉及文件移动和重命名。属性class定义具体的滚动策略类;
<prudent>: 当为true时,不支持FixedWindowRollingPolicy,
-->
<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 按照时间和压缩格式声明拆分的文件名-->
<fileNamePattern>${log_dir}/rolling.%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
<!--maxFileSize 设置按照文件大小拆分: -->
<maxFileSize>1MB</maxFileSize>
<!-- maxHistory 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件; -->
</rollingPolicy>
<!--日志级别过滤器-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--日志过滤规则
<level>: 设置过滤级别
<onMatch>: 用于配置符合过滤条件的操作
<onMismatch>: 用于配置不符合过滤条件的操作
onMatch/onMismatch 执行一个过滤器会有返回个枚举值: DENY,NEUTRAL,ACCEPT
DENY 日志将立即被抛弃不再经过其他过滤器;
NEUTRAL 有序列表里的下个过滤器过接着处理日志;
ACCEPT 日志会被立即处理,不再经过剩余过滤器;
-->
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 异步日志
appender支持通过, appender-ref 直接引用其它的 appender属性~
-->
<!-- AsyncAppender
写出文件,时候会开启一个线程,以异步形式写出日志文件~
-->
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<!--指定某个具体的 appender-->
<appender-ref ref="rollFile"/>
</appender>
<!-- 设置:rootlogger 配置 -->
为了能够看到效果, 循环 1w次日志打印~
logback.xml
<!--自定义 looger 对象
additivity="false" 自定义 logger 对象是否继承 rootLogger
-->
<logger name="com.wsm" level="info" additivity="false">
<appender-ref ref="console"/>
</logger>
name 设置项目的 包名.包名.xxx 该包下,会默认具有 自定义logger对象, additivity可以用于设置是否继承 rootlogger
[ERROR] 2021-12-21 23:20:13.702 com.wsm.LogbackTest test 16 [main] error
[WARN ] 2021-12-21 23:20:13.705 com.wsm.LogbackTest test 17 [main] wring
[INFO ] 2021-12-21 23:20:13.705 com.wsm.LogbackTest test 18 [main] info
输出级别变成了 info
Apache Log4j 2是对Log4j的升级版 [官方地址](Log4j – Apache Log4j 2)
参考了logback的一些优秀的设计,并且修复了一些问题,因此带 来了一些重大的提升:
听说提供了十几倍!
最实用的就是我们在生产 上可以动态的修改日志的级别而不需要重启应用
;
创建一个 Maven 工程:
引入依赖:pom.xml
<dependencies>
<!--log4j2日志门面-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.11.1</version>
</dependency>
<!--log4j2 日志实现-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.11.1</version>
</dependency>
<!--junit 单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
编写入门案例:Log4j2Test.Java
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
/** log4j2 快速入门 */
public class Log4j2Test {
// 定义日志记录器对象 LogManager.getLogger();
public static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class);
/** 快速入门 */
@Test
public void test(){
//log4j2 默认日志级别是 error;
LOGGER.fatal("fatal");
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("inf");
LOGGER.debug("debug");
LOGGER.trace("trace");
}
}
ERROR StatusLogger No Log4j 2 configuration file found. Using default configuration (logging only errors to the console), or user programmatically provided configurations. Set system property 'log4j2.debug' to show Log4j 2 internal initialization logging. See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2
09:38:36.825 [main] FATAL com.wsm.Log4j2Test - fatal
09:38:36.838 [main] ERROR com.wsm.Log4j2Test - error
输出结果,提示没有配置对应的 配置文件!
,可以在 resources资源目录下创建一个 log4j2.xml
的配置文件 log4j2 的配置文件 和 logback大致相同;
虽然输出结果,有警告信息,但是依然正常的打印了日志:log4j2的默认日志级别是 error
当前 resources 资源目录下创建一个 log4j2.xml
与 logback.xml配置文件大致相同~
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
status="warn" 日志框架本身的输出日志级别
monitorInterval="5" 自动加载配置文件的间隔时间,不低于 5 秒,好处是:生产环境下的项目,进行更改,系统会自动重新加载!
-->
<Configuration status="debug" monitorInterval="5">
<!-- 集中配置属性进行管理,想到于: 成员变量 ,使用时通过:${name} -->
<properties>
<property name="LOG_HOME">/logs</property>
</properties>
<!--日志处理,设置appenders 集-->
<Appenders>
<!--控制台输出 appender
target: 控制台输出的方式 System.err / System.out
-->
<Console name="Console" target="SYSTEM_ERR">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L --- %m%n" />
</Console>
<!--日志文件输出 appender-->
<File name="file" fileName="${LOG_HOME}/myfile.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
</File>
<!--使用随机读写流的日志文件输出 appender,性能提高-->
<RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAcclog.log">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %m%n" />
</RandomAccessFile>
<!--按照一定规则拆分的日志文件的 appender
filePattern: 拆分文件名,$${date:yyyy-MM-dd}每天生成一个文件夹, %i 数字排序~
-->
<RollingFile name="rollingFile" fileName="${LOG_HOME}/myrollog.log" filePattern="/logs/$${date:yyyy-MM-dd}/myrollog-%d{yyyy-MM-dd-HH-mm}-%i.log">
<!--日志级别过滤器-->
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY" />
<!--日志消息格式-->
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] %l %c{36} - %msg%n" />
<Policies>
<!--在系统启动时,出发拆分规则,生产一个新的日志文件-->
<OnStartupTriggeringPolicy />
<!--按照文件大小拆分,10MB -->
<SizeBasedTriggeringPolicy size="10 MB" />
<!--按照时间节点拆分,规则根据filePattern定义的-->
<TimeBasedTriggeringPolicy />
</Policies>
<!--在同一个目录下,文件的个数限定为 30 个,超过进行覆盖-->
<DefaultRolloverStrategy max="30" />
</RollingFile>
</Appenders>
<!--logger 定义-->
<Loggers>
<!--使用 rootLogger 配置 日志级别 level="trace"-->
<Root level="trace">
<!--指定日志使用的处理器-->
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
15:41:00.743 [main] [FATAL] com.wsm.Log4j2Test:15 --- fatal
15:41:00.754 [main] [ERROR] com.wsm.Log4j2Test:16 --- error
15:41:00.754 [main] [WARN ] com.wsm.Log4j2Test:17 --- warn
15:41:00.755 [main] [INFO ] com.wsm.Log4j2Test:18 --- inf
15:41:00.755 [main] [DEBUG] com.wsm.Log4j2Test:19 --- debug
15:41:00.755 [main] [TRACE] com.wsm.Log4j2Test:20 --- trace
直接输出就没有,异常信息了, 而且日志级别是 trace
目前市面上最主流的日志门面就是SLF4J,Log4j2也是日志门面 也有自己对应的实现,现功能非常强大,性能优越
log4j2 功能强大!
可最近出的一个bug 估计,也危险了☠
pom.xml
添加依赖:
<!--使用slf4j 作为日志门面-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<!--使用 log4j2 的适配器进行绑定-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.9.1</version>
</dependency>
重新编写 slf4j 的接口调用方法!
Slf4jTest.Java
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** slf4j + log4j2整合 */
public class Slf4jTest {
//slf4j 的获取,日志对象的方法;
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");
}
}
15:52:17.495 [main] [ERROR] com.wsm.Slf4jTest:16 --- error
15:52:17.500 [main] [WARN ] com.wsm.Slf4jTest:17 --- wring
15:52:17.501 [main] [INFO ] com.wsm.Slf4jTest:18 --- info
15:52:17.501 [main] [DEBUG] com.wsm.Slf4jTest:19 --- debug
15:52:17.501 [main] [TRACE] com.wsm.Slf4jTest:20 --- trace
现在输出的就是 Slf4j
的,数据了,但是它底层仍然是 log4j2 所以 log4j2.xml配置文件仍然有效果😀
log4j2最大的特点就是异步日志,其性能的提升主要也是从异步日志中受益 大大的提高了程序运行效率;
Log4j2提供了两种实现异步日志的方式:
效率并没有特别大提升
常用
两者都需要添加 依赖:
<!--异步日志依赖-->
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.3.4</version>
</dependency>
AsyncAppender 方式就直接在 log4j2.xml
添加对应的适配;
<Appenders>
<!-- 异步输出 appender -->
<Async name="Async">
<!-- 指定需要被异步执行的适配器: ref="适配器name" -->
<AppenderRef ref="Console"/>
<!-- <AppenderRef ref="file"/> 可以多个...-->
</Async>
</Appenders>
AsyncLogger 方式, 存在两种方式 全局异步和混合异步
log4j2.component.properties
配置文件;
更加灵活
只需要在 resources 资源目录下,创建一个配置文件 log4j2.component.properties
即可:
Log4jContextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
就是在 log4j2.xml
文件,< Loggers > 中定义,对应的包路径下类对象… 设置成异步执行
<!--logger 定义-->
<Loggers>
<!-- 自定义异步 logger 对象 -->
<AsyncLogger name="com.wsm" level="trace" includeLocation="false" additivity="false">
<AppenderRef ref="Console"/>
</AsyncLogger>
</Loggers>
垃圾收集暂停是延迟峰值的常见原因,并且对于许多系统而言,花费大量精力来控制这些暂停
临时对象
日志事件对象,字符串, 字符数组,字节数组等。这会对垃圾收集器造成压力并增加GC暂停发生的频率;
无垃圾运行模式
尽量的,不使用临时对象
无垃圾模式
log4j2.enableThreadlocals
设置为 true
, 对象存储在 ThreadLocal字段中并重新使用,否则将为每个日志事件创建新对象 非Web应用程序的默认值
log4j2.enableDirectEncoders
设置为 true
日志事件转换为文本,则将此文本转换 为字节而不创建临时对象 注意: 由于使用共享缓冲区上的同步 所以建议使用异步记录器!
所以,只要升级到2.6 版本即可~
log4j2 日志框架性能,当于其它框架高很多,主要在 2.6之后引入了两个概念: 异步日志
无垃圾模式
Idea Spring lnitializr Spring构造器
创建一个 SpringBoot 工程:
SpringBoot 底层默认使用:SLF4J作为日志门面
logback作为日志实现
Maven依赖关系图:
也引入了JUL 和 log4j 的桥接, 可以直接转换!
test 模块下:DemoApplicationTests.Java
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class DemoApplicationTests {
//创建日志记录器 导报: slf4j
public static final Logger LOGGER = LoggerFactory.getLogger(DemoApplicationTests.class);
@Test
void contextLoads() {
// 打印日志信息
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.info("info");
LOGGER.debug("debug");
LOGGER.trace("trace");
}
}
省略… 以上SpringBoot 日志
2021-12-23 15:12:08.771 ERROR 18772 --- [ main] com.wsm.demo.DemoApplicationTests : error
2021-12-23 15:12:08.771 WARN 18772 --- [ main] com.wsm.demo.DemoApplicationTests : warn
2021-12-23 15:12:08.771 INFO 18772 --- [ main] com.wsm.demo.DemoApplicationTests : info
可以看到,SpringBoot 默认集成了 Slf4j
logback
默认日志级别是 info
还可以通过,SpringBoot application.properties/yml
配置进行配置文件的更改!
SpringBoot 可以通过,配置文件,进行简单的修改日志配置:
application.properties
这里使用 pro 可以自行替换为 yml
# 指定自定义 logger 对象日志级别,直接设置根目录级别;
logging.level.com.wsm=trace
# 指定控制台输出消息格式
logging.pattern.console=[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c [%thread]===== %msg %n
# 指定存放日志文件的具体路径
# logging.file 已经过期的,因为这样设置不方便管理..
# logging.file=/logs/springboot.log
# logging.file.path 可以指定日志文件存放的目录,默认的文件名 spring.log 具有更好的辨识度了;
logging.file.path=/logs/springboot/
# 指定日志文件消息格式
logging.pattern.file=[%-5level] %d{yyyy-MM-dd HH:mm:ss} %c [%thread]===== %msg %n
# Springboot 提供的配置文件不够...只能表示基本的日志配置!
Springboot 默认的配置文件,仅支持基本的日志配置
给类路径下放上每个日志框架自己的配置文件;SpringBoot就不使用默认配置的了
日志框架 | 配置文件 |
---|---|
Logback | logback-spring.xml 或 logback.xml |
Log4j2 | log4j2-spring.xml 或 log4j2.xml |
JUL | logging.properties |
以 logback-spring.xml
举例子:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="pattern" value="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] -------- %m %n"></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">
<!-- <springProfile>: SpringBoot 支持使用这样的对日志进行设置不同的场景~ -->
<!-- 开发环境 -->
<springProfile name="dev">
<pattern>${pattern}</pattern>
</springProfile>
<!-- 生产环境 -->
<springProfile name="pro">
<pattern>[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} %c %M %L [%thread] xxxxxxxx %m %n</pattern>
</springProfile>
</encoder>
</appender>
<!--自定义 looger 对象 additivity="false" 自定义 logger 对象是否继承 rootLogger -->
<logger name="com.wsm" level="info" additivity="false">
<appender-ref ref="console"/>
</logger>
</configuration>
application.properties
可以动态调节,生产环境pro 和 开发环境dev
# 指定项目使用的具体环境
spring.profiles.active=pro
来回切换环境, 查看运行效果😀
dev
[ERROR] 2021-12-23 15:51:26.148 com.wsm.demo.DemoApplicationTests contextLoads 15 [main] -------- error
[WARN ] 2021-12-23 15:51:26.149 com.wsm.demo.DemoApplicationTests contextLoads 16 [main] -------- warn
[INFO ] 2021-12-23 15:51:26.149 com.wsm.demo.DemoApplicationTests contextLoads 17 [main] -------- info
[DEBUG] 2021-12-23 15:51:26.149 com.wsm.demo.DemoApplicationTests contextLoads 18 [main] -------- debug
[TRACE] 2021-12-23 15:51:26.149 com.wsm.demo.DemoApplicationTests contextLoads 19 [main] -------- trace
pro
[ERROR] 2021-12-23 15:52:03.623 com.wsm.demo.DemoApplicationTests contextLoads 15 [main] xxxxxxxx error
[WARN ] 2021-12-23 15:52:03.623 com.wsm.demo.DemoApplicationTests contextLoads 16 [main] xxxxxxxx warn
[INFO ] 2021-12-23 15:52:03.623 com.wsm.demo.DemoApplicationTests contextLoads 17 [main] xxxxxxxx info
[DEBUG] 2021-12-23 15:52:03.623 com.wsm.demo.DemoApplicationTests contextLoads 18 [main] xxxxxxxx debug
[TRACE] 2021-12-23 15:52:03.623 com.wsm.demo.DemoApplicationTests contextLoads 19 [main] xxxxxxxx trace
首先,移除logback 实现依赖,添加log4j2 的依赖~
添加 log4j2的依赖配置:pom.xml
<dependencies>
<!-- SpringBoot web starter 的集中依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--idea 执行后,排除 logback 日志实现-->
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- SpringBoot test starter 的集中依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--使用 log4j2 的日志启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
</dependencies>
输出日志信息就是 log4j2 的信息了
[ERROR] 2021-12-23 16:04:54 com.wsm.demo.DemoApplicationTests [main]===== error
[WARN ] 2021-12-23 16:04:54 com.wsm.demo.DemoApplicationTests [main]===== warn
[INFO ] 2021-12-23 16:04:54 com.wsm.demo.DemoApplicationTests [main]===== info
[DEBUG] 2021-12-23 16:04:54 com.wsm.demo.DemoApplicationTests [main]===== debug
[TRACE] 2021-12-23 16:04:54 com.wsm.demo.DemoApplicationTests [main]===== trace
[INFO ] 2021-12-23 16:04:54 com.wsm.demo.DemoApplicationTests [main]===== log4j2 info
添加对应的配置文件: log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
status="warn" 日志框架本身的输出日志级别
monitorInterval="5" 自动加载配置文件的间隔时间,不低于 5 秒
-->
<Configuration status="trace" monitorInterval="5">
<!--日志处理-->
<Appenders>
<!--控制台输出 appender-->
<Console name="Console" target="SYSTEM_ERR">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] [%-5level] %c{36}:%L -------------- %m%n" />
</Console>
</Appenders>
<!--logger 定义-->
<Loggers>
<!--使用 rootLogger 配置 日志级别 level="trace"-->
<Root level="trace">
<!--指定日志使用的处理器-->
<AppenderRef ref="Console" />
</Root>
</Loggers>
</Configuration>
重新执行输出的信息就是, 配置文件中的配置 红色字体, ------------- 间隔;