上周老大让我拓展下apache的log4j,让其可以把日志内容当做事件,发送到消息队列当中,然后有专门的事件处理来订阅消息,同时事件处理程序和socket关联在一块。客户端通过socket请求日志信息,请求参数构成规则,来决定是否通过socket连接发送日志到客户端。
现在我们把焦点放在“如何在打印日志的时候发消息”,至于事件处理程序暂且不表。
这是一条基本日志信息:
2017-09-05 22:59:00 DEBUG [org.apache.activemq.util.ThreadPoolUtils] Forcing shutdown of ExecutorService: java.util.concurr
我最开始的想法是,将日志内容分为分为4部分:日期,级别,所属类,日志内容。用这4个内容作为规则判断。创建一个Log类:
public class Log implements Serializable{
protected Date date;//log日期
protected Level level;//log的级别
protected String clazz;//log所属的类全称
protected Object message;//log的内容
那么如何在打印日志的时候,获取到日志的这4个部分呢?我说一下我的思考过程。最开始打算包装Logger,在日志打印的最外层发送log事件;后来又尝试从打印的日志结果截取log对象发送;这些想法都比较蠢,没有从根本解决问题。昨天了解了log4j的执行流程,还有配置文件的规则和作用,找到了一种非常简单有效的实现方式,直达问题的本质。
###一.简单的包装Logger类,在最外层发送log事件
public class NewLogger extends Logger {
private Logger logger;
public NewLogger(Logger logger){
super(logger.getName());
this.logger = logger;
}
public static NewLogger getNewLogger(Class clazz){
Logger log = getLogger(clazz);
//TODO 可能还要检查NewLogger对象是否存在,存在就获取
return new NewLogger(log);
}
@Override
public void trace(Object message) {
logger.trace(message);
sendLogEvent("TRACE", message);
}
@Override
public void debug(Object message){
logger.debug(message);
sendLogEvent("DEBUG", message);
}
@Override
public void info(Object message) {
logger.info(message);
sendLogEvent("INFO", message);
}
@Override
public void warn(Object message) {
logger.warn(message);
sendLogEvent("WARN", message);
}
@Override
public void error(Object message) {
logger.error(message);
sendLogEvent("ERROR", message);
}
@Override
public void fatal(Object message) {
logger.fatal(message);
sendLogEvent("FATAL", message);
}
/**
* 载入发事件的动作
*
*/
protected void sendLogEvent(String level, Object message) {
String className = logger.getName();
Level level_ = Level.toLevel(level);
Log log = new Log(new Date(),level_,className,message);
//发送事件
//sendEvent(log);
}
这是最基本的思路,但是有明显的缺陷。第一,用NewLog构建的对象才能发消息,其他如框架,组件,库等使用Logger,或LoggerFactory的地方任然无法发送事件;第二NewLog是使用Logger对象完成日志的处理,Logger对象可以保证唯一性,同一个class多次构建Logger对象(Logger.getLogger(this.getClass());),也只会生成一个Logger对象,但是NewLogger还需要自己做校验,比较麻烦;第三,时间不准,上面的处理中是打印log之后,new了一个date对象作为时间,虽然有毫秒级的误差,但是终究不是正确的。
二、监听控制台Appender,得到一行行日志信息,将其格式化
看一下log4j.properties配置
log4j.rootLogger=debug, logFile, stdout
log4j.appender.logFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logFile.DatePattern='.'yyyy-MM-dd
log4j.appender.logFile.File=C://log/spring.log
log4j.appender.logFile.layout=org.apache.log4j.PatternLayout
log4j.appender.logFile.layout.ConversionPattern=%d - %m%n
log4j.appender.logFile.Append=true
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} %p [%c] %m%n
我们获取stdout的Appender,监听它的输出,从管道中读出来
public void getLogInfo(){
Logger root = LogManager.getRootLogger();
if(root==null) return ;
// 获取子记录器的输出源
Appender appender = root.getAppender("stdout");
// 定义一个未连接的输入流管道
PipedReader reader = new PipedReader();
// 定义一个已连接的输出流管理,并连接到reader
Writer writer = null;
try {
writer = new PipedWriter(reader);
} catch (IOException e) {
e.printStackTrace();
}
// 设置 appender 输出流
((WriterAppender) appender).setWriter(writer);
Scanner scanner = new Scanner(reader);
while ( scanner.hasNextLine()) {
try {
//睡眠
Thread.sleep(100);
String line = scanner.nextLine();
System.out.println("[+]================"+line);
//TODO 格式化line得到Log对象
;
} catch (Exception ex) {
//异常信息不作处理
}
}
}
这是一种本末倒置的做法,强烈不推荐。
这篇文章就到这里,下篇我会将一种聪明的做法。