項目需要:將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();
}
