LogBack入数据库重写

项目需要:将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();

        }