解决多线程log4j日志输出混乱的问题,每个线程输出独立的日志
最近项目中遇到一个问题:多线程场景批量执行任务的时候,所有任务的日志输出到同一个文件中,非常的混乱,根本没有办法查看任务运行情况。
由此衍生出新的需求:多线程场景下实现每个线程日志独立输出。因为任务运行时可能会有数百个任务同时执行,所以不能采用传统的log4j配置文件解决。
解决思路:调用log4j的源码,创建新的实现了log4j接口的类,为每个线程创建logger实例,根据logger实例以变量形式动态设置输出文件。
解决过程:
通过log4j日志文件可以知道,appender主要控制日志输出到什么地方(控制台、文件),layout主要控制日志打印格式。查看org.apache.log4j.RollingFileAppender的源码了解到,构造实例时需要传入layout,filename,datepattern。
创建继承了DailyRollingFileAppender类的ThreadFileAppender类,传入指定的layout,filename,datepattern调用父类的构造函数构建appender对象。
解决了关键的问题点之后,封装一个静态方法返回logger实例,以供业务系统调用。
关键代码如下:
ThreadLogger.java封装了实现代码。
package com.niukl.log4j;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.log4j.DailyRollingFileAppender;
import org.apache.log4j.Layout;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
public class ThreadLogger {
public static Logger getLogger(String logName) {
Logger logger = null;
logger = Logger.getLogger(logName);
PatternLayout layout = new PatternLayout("[%d{MM-dd HH:mm:ss}] %-5p %-8t %m%n");
// 日志文件按照每天分文件夹存放
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String logPath = "D://logs/log4jTest/" + sdf.format(new Date()) + "/";
// 文件输出
ThreadLogger.ThreadFileAppender fileAppender = null;
try {
fileAppender = new ThreadFileAppender(layout, logPath, logName, "yyyy-MM-dd");
} catch (IOException e) {
e.printStackTrace();
}
fileAppender.setAppend(false);
fileAppender.setImmediateFlush(true);
fileAppender.setThreshold(Level.DEBUG);
// 绑定到logger
logger.setLevel(Level.DEBUG);
logger.addAppender(fileAppender);
return logger;
}
/*
* 继承了log4j类的内部类
*/
public static class ThreadFileAppender extends DailyRollingFileAppender {
public ThreadFileAppender(Layout layout, String filePath, String fileName, String datePattern)
throws IOException {
super(layout, filePath + fileName + ".log", datePattern);
}
}
}
MyThread.java是用于执行任务线程的业务类:
package com.niukl.log4j;
import org.apache.log4j.Logger;
public class MyThread implements Runnable {
String logName;
public MyThread(String logName) {
this.logName = logName;
}
public void run() {
// 在run方法内实现线程独立的logger实例
Logger logger = ThreadLogger.getLogger(logName);
logger.info(logName + "_" + Thread.currentThread().getName() + " started!");
logger.error("this is error!");
logger.info(logName + "_" + Thread.currentThread().getName() + " finished!");
}
}
TestLog.java用于管理线程:
package com.niukl.log4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.log4j.Logger;
public class TestLog {
// 这是主线程的Logger,这些不需独立日志的类也可以创建为普通的Logger,通过log4j配置文件配置参数
static Logger logger = ThreadLogger.getLogger("main_log");
public static void main(String[] args) {
try {
ExecutorService executor = Executors.newCachedThreadPool();
logger.info("任务开始执行");
for (int i = 0; i < 200; i++) {
MyThread thread = new MyThread(String.valueOf(i));
executor.submit(thread);
}
executor.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
logger.info("任务结束!");
}
}
运行效果:
每个线程打印的日志都是独立的,由于任务需要周期性执行,所以采取以天为单位分文件夹存放。
ps:程序中引用的log4j的jar包是log4j-1.2.16.jar