12- JavaLogs
日志图谱日志门面:类似与JDBC,使用一套接口,操作不同实现。
日志框架出现的顺序:log4j>>jul>>jcl>>slf4j>>logback>>log4j2
log4j2:即使日志门面也是日志实现
一、JUL
全称Java Util Logging 是JAVA原生的日志框架,使用时不需要引用三方Jar类库。
-
Loggers:被称为记录器,应用程序通过获取Logger对象,调用其API来发布日志信息。
-
Appenders:也被称为Handlers,每个Logger都会关联一组Handlers,Logger会将日志交给关联Handlers处理,由Handlers负责将日志做记录。Handlers在此是一个抽象,其具体实现决定了日志记录的位置可以是控制台、文件、网络上的其他日志服务或操作系统日志。
-
Layouts:也被称为Formatters,它负责对日志事件中的数据进行转换和格式化。Layouts决定了数据在一条日志记录中的最终形式。
-
Level:每条日志消息都有一个关联的日志级别。我们可以将Level和Loggers、Appenders做关联以便于我们过滤消息。
-
JUL中Logger之间存在父子关系,这种父子关系通过树状结构存储,JUL在初始化时会创建一个顶层;RootLogger作为所有Logger父Logger,存储上作为树状结构的根节点。并父子关系通过路径来关联。所有日志记录器对象的顶级父元素 class为
流程图java.util.logging.LogManager$RootLogger
1.1 JUL入门
public class JULTest {
public void test01() {
Logger logger = Logger.getLogger("com.examples.logs");
logger.info("你好");
logger.warning("警告!!!!");
//通用的日志方法
logger.log(Level.INFO,"HELLO WORLD !!");
String name ="z";
String age = "0";
//使用占位符的通用方法
logger.log(Level.INFO,"{0},{1}",new Object[]{name,age});
//日志级别
//logger.setLevel(Level.ALL);
//logger.setLevel(Level.OFF);
logger.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>");
logger.severe("severe");
logger.warning("warning");
//默认级别
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
public static void main(String[] args) throws Exception {
JULTest jul = new JULTest();
jul.test01();
}
}
1.2 自定义日志
import java.util.logging.*;
public class JULTest {
public void test02() throws Exception{
SimpleFormatter simpleFormatter = new SimpleFormatter();
Logger logger = Logger.getLogger("com.examples.logs");//儿子
Logger logger1 = Logger.getLogger("com.examples");//爸爸
Logger logger0 = logger1.getParent();//爷爷 无名氏
System.out.println(logger1==logger.getParent());
System.out.println(logger0+"--->"+logger0.getName());
// 0. 爷爷的默认
// 默认INFO,需要使用consoleHandler0修改默认级别才能输出全部级别日志
ConsoleHandler consoleHandler0 = new ConsoleHandler();
logger0.addHandler(consoleHandler0);
consoleHandler0.setLevel(Level.ALL);
logger0.setLevel(Level.ALL);
logger0.severe("爷爷的severe");
logger0.warning("爷爷的warning");
logger0.info("爷爷的info");
logger0.config("爷爷的config");
logger0.fine("爷爷的fine");
logger0.finer("爷爷的finer");
logger0.finest("爷爷的finest");
// 1.爸爸在控制台:输出所有日志,使用自定义的日志级别
// 爸爸关闭爷爷日志级别配置
logger1.setUseParentHandlers(false);
ConsoleHandler consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(simpleFormatter);
logger1.addHandler(consoleHandler);
consoleHandler.setLevel(Level.WARNING);
logger1.setLevel(Level.ALL);
logger1.severe("爸爸的severe");
logger1.warning("爸爸的warning");
logger1.info("爸爸的info");
logger1.config("爸爸的config");
logger1.fine("爸爸的fine");
logger1.finer("爸爸的finer");
logger1.finest("爸爸的finest");
// 2.儿子在文件中输出日志:输出错误日志,使用自定义的日志级别
logger.setUseParentHandlers(false);
FileHandler fileHandler = new FileHandler("C:\\Users\\Administrator\\Desktop\\logs.txt");
fileHandler.setFormatter(simpleFormatter);
logger.addHandler(fileHandler);
fileHandler.setLevel(Level.SEVERE);
logger.setLevel(Level.ALL);
logger.severe("severe");
logger.warning("warning");
logger.info("info");
logger.config("config");
logger.fine("fine");
logger.finer("finer");
logger.finest("finest");
}
public static void main(String[] args) throws Exception {
JULTest jul = new JULTest();
jul.test02();
}
}
结果:
控制台 文件1.3 使用配置文件
JDK默认的日志文件在
$JAVA_HOME/jre/lib
实例:
#指定顶级父元素RootLogger默认处理器
handlers= java.util.logging.ConsoleHandler
.level= ALL
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
#自定义Logger
com.simple.handlers = java.util.logging.ConsoleHandler
com.simple.level=ALL
#关闭RootLogger配置
com.simple.ueParentHandlers = false
客户端:
package com.examples.logs;
import java.io.InputStream;
import java.util.logging.*;
public class JULTest {
public void test02() throws Exception{
InputStream ins = JULTest.class.getClassLoader().getResourceAsStream("logging.properties");
LogManager logManager = LogManager.getLogManager();
logManager.readConfiguration(ins);
Logger logger1 = Logger.getLogger("com.examples");//爸爸
Logger logger0 = logger1.getParent();//爷爷 无名氏
logger0.severe("爷爷的severe");
logger0.warning("爷爷的warning");
logger0.info("爷爷的info");
logger0.config("爷爷的config");
logger0.fine("爷爷的fine");
logger0.finer("爷爷的finer");
logger0.finest("爷爷的finest");
}
public static void main(String[] args) throws Exception {
JULTest jul = new JULTest();
jul.test02();
}
}
结果
二、Log4J
是Apache的一款开源日志框架,通过在项目中使用log4j,我们可以控制日志信息输出到控制台、文件甚至是数据库中。
Log4j主要有Loggers(日志记录器,是category的别名)、Appenders(输出端)和Layout(日志格式化器)组成。其中Loggers控制日志的输出级别与日志是否输出;Appenders指定日志的输出方式;控制日志的输出格式。
自定义日志使用场景:第三方日志。
Loggers:
Appenders:
- ConsoleAppender:将日志输出到控制台。
- FileAppender:将日志输出到文件中。
- DailyRollingFileAppender:将日志输出到一个日志文件,并且每天输出到一个新的文件。
- RollingFileAppender:将日志信息输出到一个日志文件,并且指定文件的尺寸,当文件大小达到指定尺寸时,会自动把文件改名,同时产生一个新的文件。
- JDBCAppenders:把日志信息保存到数据库中。
Layout:
- HTMLLayout:格式化日志输出为HTML表格形式。
- SimpleLayout:简单的日志输出格式化,打印的日志格式为(info-message)。
- PatternLayout:最强大的格式化器,可以根据自定义格式输出日志,如果没有指定转换格式,就是默认的转换格式。
2.1 入门案例
pom.xml
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
客户端:
package com.examples.logs;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
public class Log4jTest {
public void test1() throws Exception{
//不适应配置文件进行初始化
BasicConfigurator.configure();
//获取日志记录
Logger logger = Logger.getLogger(Log4jTest.class);
//默认日志级别是debug
logger.fatal("fatal");//验证错误,一般会造成系统崩溃并终止运行
logger.error("error");//错误信息,不会影响系统运行
logger.warn("warn");//警告信息,可能会发生的问题
logger.info("info");//运行信息
logger.debug("debug");//调试信息
logger.trace("trace");//追踪信息,记录程序所有流程信息
}
public static void main(String[] args) throws Exception{
Log4jTest log4jTest = new Log4jTest();
log4jTest.test1();
}
}
结果
2.2 使用配置文件
log4j.properties
log4j.rootLogger=info,console,fileAppender,rollingFile
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
log4j.appender.fileAppender = org.apache.log4j.FileAppender
log4j.appender.fileAppender.File = D:/xzv.log
log4j.appender.fileAppender.Append = true
log4j.appender.fileAppender.Threshold = DEBUG
log4j.appender.fileAppender.layout = org.apache.log4j.PatternLayout
log4j.appender.fileAppender.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m\n
log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender
log4j.appender.rollingFile.Threshold=ERROR
log4j.appender.rollingFile.File=D:/xzb.log
log4j.appender.rollingFile.MaxFileSize=10KB
log4j.appender.rollingFile.Append = true
log4j.appender.rollingFile.MaxBackupIndex=10
log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.rollingFile.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss.SSS} [ %t:%r ] - [ %p ] %m\n
# 配置文件中layout的pattern的格式:
# %m:输出代码中指定的日志信息。
# %p:输出优先级及DEBUG、INFO等。
# %n:换行符Windows:"\n";Unix:"%n"。
# %r:输出自应用启动到输出该LOG信息耗费的毫秒数
# %c:输出打印语句所属的类的全名。
# %t:输出产生该日志的线程全名。
# %d:输出服务器当前时间,默认ISO8601,也可以指定格式,如:%d{yyyy年MM月dd日 HH:mm:ss}。
# %l:输出日志时间发生的位置,包括类名、线程和代码中的行数。
# %F:输出日志消息产生时所在的文件名称。
# %L:输出代码中的行号
# %%输出一个“%”字符。
# %5c:输出category名称,最小宽度是5,category<5,默认的情况下右对齐。
# %-5c:输出category名称,最小宽度是5,“-”号指定左对齐,会有空格。
# .5c:输出category名称,最大宽度是5,category>5,就会将左边多出的字符截掉,<5不会有空格。
# %20.30c category名称<20补空格,并且右对齐,>30字符,就从左边将多出的字符截掉。
Log4jTest.java
public class Log4jTest {
public void test1() throws Exception{
//开启LOG4J内置日志
LogLog.setInternalDebugging(false);
Logger logger = Logger.getLogger(Log4jTest.class);
for (int i = 0; i < 100; i++) {
logger.fatal("fatal");//验证错误,一般会造成系统崩溃并终止运行
logger.error("error");//错误信息,不会影响系统运行
logger.warn("warn");//警告信息,可能会发生的问题
logger.info("info");//运行信息
logger.debug("debug");//调试信息
logger.trace("trace");//追踪信息,记录程序所有流程信息
}
}
public static void main(String[] args) throws Exception{
Log4jTest log4jTest = new Log4jTest();
log4jTest.test1();
}
}
结果:
结果三、 JCL日志门面
当项目从JUL转变为Log4J的时候,由于接口不同,代价是巨大的。JCL就是为解决此问题的,所有日志的实现将提供统一的接口,改变日志的实现而不需要更改程序。
JCL
JCL原理
日志门面支持的日志实现数组
private static final String[] classesToDiscover = {
"org.apache.commons.logging.impl.Log4JLogger",
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"
}
获取具体的日志实现:
for(int i = 0; i < classesToDiscover.length && result == null; ++i)
{
result = this.createLogFromClass(classesToDiscover[i], logCategory, true);
}
3.1 入门案例
客户端:
package com.examples.logs;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class JCLTest {
public void test1() {
Log log = LogFactory.getLog(JCLTest.class);
log.info("大家好!!!");
}
public static void main(String[] args) {
JCLTest jcl = new JCLTest();
jcl.test1();
}
}
3.1.1 只引入commons-logging包
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
只引入commons-logging
3.1.2 引入log4j不引入配置文件
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
引入log4j不引入配置文件
3.1.3 引入log4j和配置文件
引入log4j和配置文件四、SLF4J日志门面
原理:
- SLF4J通过LoggerFactory加载日志具体实现对象。
- LoggerFactory在初始化的过程中,会通过performInitialization()绑定具体的日志实现。
- 在绑定具体实现的时候,通过类加载器,加载org.slf4j.impl.StaticLoggerBinder.class
- 所以只要是一个日志实现框架,在org.slf4j.impl包中提供一个自己的StaticLoggerBinder类,在其中提供具体日志实现的LoggerFactory就可以被SLF4J所加载。
客户端:
package examples.logs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SLF4JTest {
public static final Logger LOGGER = LoggerFactory.getLogger(SLF4JTest.class);
public void test1() {
LOGGER.error("错误");
LOGGER.warn("警告");
LOGGER.info("信息");
LOGGER.debug("BUG");
//使用占位符
String name ="xzv";
int age = 30;
LOGGER.info("name:{},{}",name,age);
//将系统异常信息输出
try {
int i =1/0;
} catch (Exception e) {
e.printStackTrace();
LOGGER.warn("警告",e);
}
}
public static void main(String[] args) {
SLF4JTest jcl = new SLF4JTest();
jcl.test1();
}
}
4.1 入门案例
pom.xml: 自带的日志实现
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
</dependencies>
自带的日志实现
4.2 日志绑定
image.png - 添加slf4j-api的依赖。
- 使用slf4j的API在项目中进行统一的日志记录。
- 绑定具体的日志实现框架
- 1 绑定已经实现了slf4j的日志框架,直接添加对应的依赖。
- 2 绑定没有实现slf4j的日志框架,先添加日志的适配器,再添加实现类的依赖
- slf4j有且仅有一个日志实现框架的绑定。(如果出现多个默认使用第一个依赖日志实现)
4.2.1 绑定logback
pom.xml
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
绑定logback
4.2.2 绑定nop
pom.xml
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
结果:没有输出日志框架的日志。
绑定nop4.2.3 绑定 log4j
pom.xml
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<!--绑定log4j 日志实现,导入适配器-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
绑定 log4j-未引入配置文件
绑定 log4j-引入配置文件
4.2.4 绑定 JUL
pom.xml
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<!--绑定JUL日志实现,导入适配器,JUL是JDK内置的,不需要添加实现-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.31</version>
</dependency>
绑定 JUL
4.2.5 slf4j桥接器
应用场景:当旧项目的日志框架log4j需要更换为slf4j+logback的时候,又不想修改源代码。使用桥接可以处理
原项目的pom.xml
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
新项目的pom.xml
<!---1. 先导入日志门面--->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
<!---2. 导入需要更换的logback;仅仅导入logback源代码是会报错的--->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!---3. 导入桥接器--->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.25</version>
</dependency>
桥接器注意事项:
1.jcl-over-slf4j.jar和slf4j-jcl.jar不能同时部署。
2.log4j-over-slf4j.jar和slf4j-log4j12.jar不能同时出现。
3.jul-to-slf4j.jar和slf4j-jdk14.jar不能同时出现。
log4j-over-slf4j.jar和slf4j-log4j12.jar
五、Logback
Logback是由log4j创始人设计的另一个开源日志组件。
Logback主要分为三个模块:
- logback-core:其他两个模块的基础模块。
- logback-classic:是log4j的一个改良版本,同时它完整实现了slf4j API。
- logback-access:访问模块与Servlet容器集成提供通过http来访问日志的功能。
5.1 logback入门
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>Theme</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Logback</artifactId>
<dependencies>
<!-- maven传递依赖,不需要导入slf4j-api
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.26</version>
</dependency>
-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
</dependencies>
</project>
客户端:
package com.theme.logback;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
public class LogbackTest {
public static final Logger LOGGER = LoggerFactory.getLogger(LogbackTest.class);
public void test1() {
LOGGER.error("错误");
LOGGER.warn("警告");
LOGGER.info("信息");
LOGGER.debug("BUG");
}
public static void main(String[] args) {
LogbackTest logbackTest = new LogbackTest();
logbackTest.test1();
}
}
结果:
image.png5.2. logback配置
logback会依次读取以下类型的配置文件logback.groovy
、logback-test.xml
、logback.xml
如果均不存在会采用默认配置。
logback组件之间的关系:
Logger:日志的记录器,把它关联到应用对应的context上后,主要用于存放日志对象,也可以定义日志类型、级别。
Appender:用于指定日志输出的目的地。
Layout:负责把事件转换成字符串,格式化的日志信息的输出。在logback对象被封装在encoder中。
配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度,%msg:日志消息,%n是换行符-->
<property name="pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="/logggs"/>
<!--控制台日志, 控制台输出 -->
<appender name="STDOUT" 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>
<!--日志文件-->
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_HOME}/logback.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${pattern}</pattern>
</encoder>
</appender>
<!--HTML日志文件-->
<appender name="HTMLFILE" class="ch.qos.logback.core.FileAppender">
<file>${LOG_HOME}/logback.html</file>
<encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
<layout class="ch.qos.logback.classic.html.HTMLLayout">
<pattern>${pattern}</pattern>
</layout>
</encoder>
</appender>
<!--日志文件 拆分-->
<appender name="ROLLFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/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>${LOG_HOME}/roll.%d{yyyy-MM-dd HH:mm:ss.SSS}log%i.gz</fileNamePattern>
<!--按照文件大小拆分-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
<!--日志级别过滤器-->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!--日志过滤级别-->
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMisMatch>DENY</onMisMatch>
</filter>
</appender>
<!--异步日志-->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<!--指定某个具体的appender-->
<appender-ref ref="ROLLFILE"/>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
<appender-ref ref="HTMLFILE"/>
<appender-ref ref="ASYNC"/>
</root>
<!--自定义日志:表示com.theme.logback包下的日志走自定义的配置-->
<logger name="com.theme.logback" level="info" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
</configuration>
5.3.logback-access
logback-access模块与Servlet容器集成,以提供HTTP访问日志的功能。我们可以使用logback-access模块来替代tomcat的访问日志。
-
1 将logback-access.jar与logback-core.jar复制到
$TOMCAT_HOME/lib/
目录下 -
2 修改
$TOMCAT_HOME/conf/server.xml
中的Host元素,添加
<value className="ch.qos.logback.access.tomcat.LogbackValue"/>
- 3 logback默认会在
$TOMCAT_HOME/conf下
查找logback-access.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<statusLister class="ch.qos.logback.core.status.OnConsolesListener"/>
<property name="LOG_DIR" value="${catalina.base}/logs/">
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/access.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/roll.%d{yyyy-MM-dd HH:mm:ss.SSS}log%i.gz</fileNamePattern>
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
<encoder>
<pattern>combined</pattern>
</encoder>
</appender>
<appender-ref ref="FILE">
</configuration>
六、Log4j2
Log4j2是对Log4j的升级,参考了logback的优点;
- 异常处理,在logback中Appender中的异常不会被应用感知到,但是在Log4j2中提供一些异常处理机制。
- 自动重载配置,参考logback的设计,生产环境上可以动态的修改日志的级别而不需要重启应用。
- 无垃圾机制,Log4j2在大部分情况下,都可以使用其设计的一套无垃圾机制,避免频繁的日志收集导致的JVM GC。
6.1 Log4j2入门
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<dependencies>
<!--第一种门面-->
<!--log4j2日志门面-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.12.1</version>
</dependency>
<!--第二种门面-->
<!--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>
<!--log4j2日志实现 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.1</version>
</dependency>
</dependencies>
客户端:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Log4j2Test {
public static final Logger LOGGER = LogManager.getLogger(Log4j2Test.class);
public void test1() {
LOGGER.fatal("fatal");
LOGGER.error("error");
LOGGER.warn("warn");
LOGGER.debug("debug");
LOGGER.trace("trace");
}
public static void main(String[] args) {
Log4j2Test logbackTest = new Log4j2Test();
logbackTest.test1();
}
}
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
status:日志框架本身日志的级别
monitorInterval:自动加载配置文件的间隔时间,不低于5S
-->
<configuration status="OFF" monitorInterval="5">
<properties>
<property name="LOG_HOME">/logs</property>
</properties>
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<!--只接受程序中DEBUG级别的日志进行处理-->
<ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="[%d{HH:mm:ss.SSS}] %-5level %class{36} %L %M - %msg%xEx%n"/>
</Console>
<file name="File" fileName="${LOG_HOME}/mylog.log">
<PatternLayout pattern="[%d{HH:mm:ss.SSS}] %-5level %class{36} %L %M - %msg%xEx%n"/>
</file>
<!--随机读写流-->
<RandomAccessFile name="accessFile" fileName="${LOG_HOME}/myAccessFile.log">
<PatternLayout pattern="[%d{HH:mm:ss.SSS}] %-5level %class{36} %L %M - %msg%xEx%n"/>
</RandomAccessFile>
<!--处理INFO级别的日志,并把该日志放到logs/info.log文件中-->
<RollingFile name="RollingFileInfo" fileName="${LOG_HOME}/logs/info.log"
filePattern="logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
<Filters>
<!--只接受INFO级别的日志,其余的全部拒绝处理-->
<ThresholdFilter level="INFO"/>
<ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
</Filters>
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %class{36} %L %M - %msg%xEx%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="500 MB"/>
<TimeBasedTriggeringPolicy/>
<OnStartupTriggeringPolicy/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
<!--druid的日志记录追加器-->
<RollingFile name="druidSqlRollingFile" fileName="./logs/druid-sql.log"
filePattern="logs/$${date:yyyy-MM}/api-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss}] %-5level %L %M - %msg%xEx%n"/>
<Policies>
<SizeBasedTriggeringPolicy size="500 MB"/>
<TimeBasedTriggeringPolicy/>
</Policies>
</RollingFile>
</appenders>
<!-- 然后定义logger,只有定义了logger并引入的appender,appender才会生效 -->
<loggers>
<!--默认的root的logger-->
<root level="DEBUG">
<appender-ref ref="Console"/>
<appender-ref ref="RollingFileInfo"/>
</root>
<!--额外配置的logger-->
<!--记录druid-sql的记录-->
<logger name="druid.sql.Statement" level="debug" additivity="false">
<appender-ref ref="druidSqlRollingFile"/>
</logger>
<!--log4j2 自带过滤日志-->
<Logger name="org.apache.catalina.startup.DigesterFactory" level="error" />
<Logger name="org.apache.catalina.util.LifecycleBase" level="error" />
<Logger name="org.apache.coyote.http11.Http11NioProtocol" level="warn" />
<logger name="org.apache.sshd.common.util.SecurityUtils" level="warn"/>
<Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="warn" />
<Logger name="org.crsh.plugin" level="warn" />
<logger name="org.crsh.ssh" level="warn"/>
<Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
<Logger name="org.hibernate.validator.internal.util.Version" level="warn" />
<logger name="org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration" level="warn"/>
<logger name="org.springframework.boot.actuate.endpoint.jmx" level="warn"/>
<logger name="org.thymeleaf" level="warn"/>
</loggers>
</configuration>
6.2 Log4j2异步日志
摘自:https://blog.csdn.net/jek123456/article/details/100123570
pom.xml
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>3.4.3</version>
</dependency>
6.2.1 AsyncAppender方式
AsyncAppender是通过引用别的Appender来实现的,当有日志事件到达时,会开启另外一个线程来处理它们。需要注意的是,如果在Appender的时候出现异常,对应用来说是无法感知的。 AsyncAppender应该在它引用的Appender之后配置,默认使用 java.util.concurrent.ArrayBlockingQueue实现而不需要其它外部的类库。 当使用此Appender的时候,在多线程的环境下需要注意,阻塞队列容易受到锁争用的影响,这可能会对性能产生影响。这时候,我们应该考虑使用无所的异步记录器(AsyncLogger)。
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<File name="MyFile" fileName="logs/app.log">
<PatternLayout>
<Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
</PatternLayout>
</File>
<Async name="Async">
<AppenderRef ref="MyFile"/>
</Async>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="Async"/>
</Root>
</Loggers>
</Configuration>
6.2.2 AsyncLogger方式
AsyncLogger才是log4j2 的重头戏,也是官方推荐的异步方式。它可以使得调用Logger.log返回的更快。
你可以有两种选择:全局异步和混合异步。
- 全局异步就是,所有的日志都异步的记录,在配置文件上不用做任何改动,只需要在jvm启动的时候增加一个参数;
- 混合异步就是,你可以在应用中同时使用同步日志和异步日志,这使得日志的配置方式更加灵活。因为Log4j文档中也说了,虽然Log4j2提供以一套异常处理机制,可以覆盖大部分的状态,但是还是会有一小部分的特殊情况是无法完全处理的,比如我们如果是记录审计日志,那么官方就推荐使用同步日志的方式,而对于其他的一些仅仅是记录一个程序日志的地方,使用异步日志将大幅提升性能,减少对应用本身的影响。混合异步的方式需要通过修改配置文件来实现,使用AsyncLogger标记配置。
全局异步
不修改配置文件,在系统初始化的时候,增加全局参数配置:
System.setProperty("log4j2.contextSelector, "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
你可以在你第一次获取Logger之前设置,也可以加载JVM启动参数里,类似
java -Dog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector
混合异步
log4j2.xml
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<!-- Async Loggers will auto-flush in batches, so switch off immediateFlush. -->
<RandomAccessFile name="RandomAccessFile" fileName="asyncWithLocation.log"
immediateFlush="false" append="false">
<PatternLayout>
<Pattern>%d %p %class{1.} [%t] %location %m %ex%n</Pattern>
</PatternLayout>
</RandomAccessFile>
</Appenders>
<Loggers>
<!-- pattern layout actually uses location, so we need to include it -->
<AsyncLogger name="com.foo.Bar" level="trace" includeLocation="true">
<AppenderRef ref="RandomAccessFile"/>
</AsyncLogger>
<Root level="info" includeLocation="true">
<AppenderRef ref="RandomAccessFile"/>
</Root>
</Loggers>
</Configuration>
在使用异步日志的时候需要注意一些事项,如下:
- 不要同时使用AsyncAppender和AsyncLogger,也就是在配置中不要在配置Appender的时候,使用Async标识的同时,又配置AsyncLogger,这不会报错,但是对于性能提升没有任何好处。
- 不要在开启了全局同步的情况下,仍然使用AsyncAppender和AsyncLogger。这和上一条是同一个意思,也就是说,如果使用异步日志,AsyncAppender、AsyncLogger和全局日志,不要同时出现。
- 如果不是十分必须,不管是同步异步,都设置immediateFlush为false,这会对性能提升有很大帮助。
- 如果不是确实需要,不要打印location信息,比如HTML的location,或者pattern模式里的%C or $class, %F or %file, %l or %location, %L or %line, %M or %method, 等,因为Log4j需要在打印日志的时候做一次栈的快照才能获取这些信息,这对于性能来说是个极大的损耗。