项目需要:将info以及error的日志信息写入到数据库中;同时所有的日志都要写入到日志文件中。
可以封装一下,在基类的logError/logInfo中调用了log.error()以及log.info之后在调用一次LoggerDBService进行写入;但是这样就意味着"不美",日志还需要调用两次;而且因为早期设计问题,并不是所有的日志都采用基类的logError/logInfo。
看了一下logback源码,分析了一下其机制,于是决定采用重写DBAppender并结合AsyncAppender进行异步调用的方式进行实现。对于日志类操作,如果写入数据库这种比较消耗资源和时间的事情进行同步,很不值,于是才决定通过异步方式进行处理。
AsyncAppender(AA)是一个独立的Appender,放置到它下面的appender(通过appender-ref属性进行设定)也就不需要在放置到root节点下面,因为AsyncAppender设计的逻辑就是:在root下面引用该Appender,然后通过AA进行调度此appender进行日志输出。
<appender name="asyncLog" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>10000</queueSize>
<appender-ref ref="dbLog"/>
</appender>
<root level="debug">
<appender-ref ref="stdout" />
<appender-ref ref="txtLog" />
<appender-ref ref="asyncLog"/>
</root>
至于调度的逻辑,首先要明白一个概念: LoggingEvent(日志事件),任何一次logback的输出动作,都是一个LogEvent,logEvent里面包括了很多信息,包括要写入的内容,级别等等一系列信息。AA的调度逻辑就是将每次的输出动作放到内置的BlockingQueue中;然后再从BlockingQueue中取出来交给关联的Appender进行处理。LogEvent和Appender是无关的,前者是内容;后者是处理内容。
ILoggingEvent是每次传入append方法的入参。
重写的DB继承自UnsynchronizedAppenderBase<ILoggingEvent>,重写了append方法,里面使用自己的DBManager来进行数据库处理;我没有继承DBAppenderBase,是因为里面固化了一些内容,很多都是不需要的;而且采用它,就必须要指定数据库的连接字符串,用户名密码,这意味着同样的数据库要在两个地方进行配置:应用级别的配置文件以及logback的配置文件。于是我索性就直接继承了UnsynchronizedAppenderBase<ILoggingEvent>,没有重用DBAppender相关内容。
public class TransportDBLoggerAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {
@Override
public void append(ILoggingEvent eventObject) {
try {
String content = eventObject.getFormattedMessage();
System.out.println("content内容是: " + content);
Map<String, String> map = new HashMap<String, String>();
map.put("LOG_LEVEL", eventObject.getLevel().levelStr);
map.put("CONTENT", content.replace("'", "''"));
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
map.put("CREATE_DATE", sdf.format(new Date()));
// 拼接SQL语句,然后执行
… …
} catch (Throwable sqle) {
String errorMsg = CommonUtil.getTrace(sqle);
System.out.println(errorMsg);
}
}
}
在配置文件中,还需要指定过滤级别,因为只需要info和error需要写入到数据库中;在过来级别logback提供了两种方式来进行处理,分别是LevelFilter以及ThresholderFilter,前者只能指定过来特定的级别的操作(ACCEPT,NEUTRAL,DENY),后者则是过滤指定级别,之下的将会被拒绝(DENY)。
<appender name="dbLog" class="test.MyDBLoggerAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
</appender>
调用测试代码,发现并没有走入库逻辑,后来才发现原来是因为测试代码走完后,整个应用退出,于是logback也退出了;换言之,放置到Queue里面的内容根本就没有被处理,logback的线程也就消亡了。于是尝试让测试线程阻塞10秒钟,至此,才看到入库的动作以及数据。
Logger logger = LoggerFactory.getLogger(this.getClass());
logger.info("test transDbLogger INFO");
logger.error("test transDbLogger ERROR");
logger.debug("test transDbLogger DEBUG");
System.out.println("OK, complete!");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}