Java中的日志框架
需求
使用日志框架有三点需求:
1、期望日志能保存在文件中,方便时候排错。
2、开发环境的日志记录会更多方便调试。
3、生产环境需要记录重要的信息。
Log4j
可用于普通maven项目,也可以用于springboot项目。
Log4j提供了简单的API调用,强大的日志格式定义以及灵活的扩展性。
可以自定义Appender来满足日志输出的需求。
日志级别
从低到高
DEBUG INFO WARN ERROR FATAL
pom依赖
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.8.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.8.2</version>
</dependency>
目录结构
- src
- main
- java
- resources
log4j.properties
配置文件
log4j.properties
log4j.rootLogger=debug, stdout, A, D, E
### Output to the console ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss.SSS} %t [%p] %c{1} (%F:%L) %m%n
### Output to the log file ###
log4j.appender.A=org.apache.log4j.RollingFileAppender
log4j.appender.A.File=D:/logs/firestorm.log
log4j.appender.A.MaxFileSize=1000KB
log4j.appender.A.MaxBackupIndex=1
log4j.appender.A.layout=org.apache.log4j.PatternLayout
log4j.appender.A.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss.SSS} %t [%p] %c{1} (%F:%L) %m%n
### 输出DEBUG 级别以上的日志 ###
log4j.appender.D=org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File=D://logs/debug/debug.log
log4j.appender.D.Append=true
log4j.appender.D.Threshold=DEBUG
log4j.appender.D.layout=org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss.SSS} %t [%p] %c{1} (%F:%L) %m%n
### 输出ERROR 级别以上的日志 ###
log4j.appender.E=org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File=D://logs/error/error.log
log4j.appender.E.Append=true
log4j.appender.E.Threshold=ERROR
log4j.appender.E.layout=org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss.SSS} %t [%p] %c{1} (%F:%L) %m%n
Appender
指定日志输出目的地,可以是控制台也可以是文件。
-
输出到控制台的org.apache.log4j.ConsoleAppender
-
输出到文件的org.apache.log4j.DailyRollingFileAppender
ConversionPattern
指定日志的输出格式
%p:输出日志信息的优先级,即DEBUG,INFO,WARN,ERROR,FATAL。
%d:输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,如:%d{yyyy/MM/dd HH:mm:ss,SSS}。
%r:输出自应用程序启动到输出该log信息耗费的毫秒数。
%t:输出产生该日志事件的线程名。
%l:输出日志事件的发生位置,相当于%c.%M(%F:%L)的组合,包括类全名、方法、文件名以及在代码中的行数。例如:test.TestLog4j.main(TestLog4j.java:10)。
%c:输出日志信息所属的类目,通常就是所在类的全名。
%M:输出产生日志信息的方法名。
%F:输出日志消息产生时所在的文件名称。
%L::输出代码中的行号。
%m::输出代码中指定的具体日志信息。
%n:输出一个回车换行符,Windows平台为"/r/n",Unix平台为"/n"。
%x:输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
%%:输出一个"%"字符。
另外,还可以在%与格式字符之间加上修饰符来控制其最小长度、最大长度、和文本的对齐方式。如:
%20c:指定输出category的名称,最小的长度是20,如果category的名称长度小于20的话,默认的情况下右对齐。
%-20c:"-"号表示左对齐。
%.30c:指定输出category的名称,最大的长度是30,如果category的名称长度大于30的话,就会将左边多出的字符截掉,但小于30的话也不会补空格。
性能问题
Java的IO是阻塞式的,频繁的往文件中写入日志,对系统性能有较大的影响。
性能最差的是ImmediateFlush=true的时候(关闭了缓存),而性能最好的就是开启日志异步AsyncAppender处理的时候
开启缓存
1、好处:提升系统响应性能;
2、不足:当系统因为异常而崩溃,又或者jvm被强行关闭,从而导致缓存中的数据丢失,日志不存在,无法及时确定异常原因。
具体实施
-
当ImmediateFlush=true时候,表示每一条打印日志请求都会被立即输出,也就是立刻同步到磁盘中去。在高并发下,系统性能受到很大的影响,IO和磁盘读写数大大提升。
-
当ImmediateFlush=false时候,与上面正好相反,表示每一条打印日志请求不会被立即输出,会使用java.io.OutputStreamWriter的缓存,缓存大小为1024字节。
-
当ImmediateFlush=false、BufferedIO=true、BufferSize=8192时候,表示使用java.io.BufferedWriter缓存,缓存大小为默认8192字节,每一条打印请求不会立即输出,当缓存达到8192字节后才会输出到文件。这样一来,大大减少了IO和磁盘读写操作,提升了系统的性能。
log4j.appender.HOOKFILE=com.***.***.HookFileAppender
log4j.appender.HOOKFILE.File=D://logs/debug/debug.log
log4j.appender.HOOKFILE.Append=true
log4j.appender.HOOKFILE.Threshold=DEBUG
### 开启缓存 ###
log4j.appender.D.BufferedIO=true
log4j.appender.D.ImmediateFlush=false
log4j.appender.D.BufferSize=8192
### 开启缓存 ###
log4j.appender.HOOKFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.HOOKFILE.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss.SSS} %t [%p] %c{1} (%F:%L) %m%n
钩子
jvm运行结束,日志信息没有保存到磁盘中,还存在于缓存中
以下的程序可以保证数据保存到文件。
public class HookFileAppender extends FileAppender {
public HookFileAppender(){
super();
//添加钩子程序
Runtime.getRuntime().addShutdownHook(new Log4jHockThread());
}
public HookFileAppender(Layout layout, String filename) throws IOException {
super(layout,filename);
//添加钩子程序
Runtime.getRuntime().addShutdownHook(new Log4jHockThread());
}
public HookFileAppender(Layout layout, String filename, boolean append) throws IOException {
super(layout,filename,append);
//添加钩子程序
Runtime.getRuntime().addShutdownHook(new Log4jHockThread());
}
public HookFileAppender(Layout layout, String filename, boolean append, boolean bufferedIO,
int bufferSize) throws IOException {
super(layout,filename,append,bufferedIO,bufferSize);
Runtime.getRuntime().addShutdownHook(new Log4jHockThread());
}
class Log4jHockThread extends Thread{
@Override
public void run() {
//jvm结束之前,运行flush操作,将日志写入磁盘;
if(qw != null){
qw.flush();
}
}
}
}
具体使用
import fabric.edu.sdk.ca.CaManager;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* @author wzm
* @version 1.0.0
* @date 2020/1/24 15:25
**/
public class MyTest {
private static final Log log = LogFactory.getLog(CaManager.class);
public static void main(String[] args) {
log.info("hello");
log.debug("hello");
log.error("hello");
log.warn("hello");
}
}
打印
2020/01/26 15:01:43.005 main [INFO] CaManager (MyTest.java:17) hello
2020/01/26 15:01:43.006 main [DEBUG] CaManager (MyTest.java:18) hello
2020/01/26 15:01:43.006 main [ERROR] CaManager (MyTest.java:19) hello
2020/01/26 15:01:43.007 main [WARN] CaManager (MyTest.java:20) hello
logback
xxx