log4j2.xml配置日志写入数据库

时间:2021-09-12 23:58:42

之前写过通过logback.xml配置将日志写入数据库的文章,本章中公司项目中使用的则是log4j2.xml,本来以为很容易实现,结果费了一下午时间才搞定,记录一下。

一、在本地新建库auge_log,在该库中新建表error_log

DROP TABLE IF EXISTS `error_log`;
CREATE TABLE `error_log` (
  `log_id` int(20) NOT NULL AUTO_INCREMENT,
  `class` varchar(200) DEFAULT NULL,
  `method` varchar(100) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `log_level` varchar(50) DEFAULT NULL,
  `msg` varchar(4000) DEFAULT NULL,
  PRIMARY KEY (`log_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

二、在log4j2.xml配置写入数据库的信息:JDBC部分

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="TRACE">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"/>
        </Console>
        <!-- 按天第天备份一个日志 -->
        <RollingFile name="saveFile" fileName="${sys:catalina.home}/logs/beanspark/auge.log"
                     filePattern="${sys:catalina.home}/logs/beanspark/auge_%d{yyyy-MM-dd}_%i.log">
            <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"/>
            <Policies>
                <!-- 每天更新一次 每个文件最大活跃大小 512MB 最多备份20个文件 -->
                <TimeBasedTriggeringPolicy modulate="true" interval="1" />
                <SizeBasedTriggeringPolicy size="512MB" />
            </Policies>
            <!-- 最多备份20个 -->
            <DefaultRolloverStrategy max="20" />
        </RollingFile>
        <!-- 按天第天备份一个日志 -->
        <RollingFile name="XdyjError" fileName="${sys:catalina.home}/logs/beanspark/auge_error.log"
                     filePattern="${sys:catalina.home}/logs/beanspark/auge_error_%d{yyyy-MM-dd}_%i.log">
            <PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"/>
            <Policies>
                <!-- 每天更新一次 每个文件最大活跃大小 128MB 最多备份20个文件 -->
                <TimeBasedTriggeringPolicy modulate="true" interval="1" />
                <SizeBasedTriggeringPolicy size="128MB" />
            </Policies>
            <!-- 最多备份20个 -->
            <DefaultRolloverStrategy max="20" />
        </RollingFile>
        <!--写入数据库配置,在Logger中设置日志级别为error-->
        <JDBC name="databaseAppender" tableName="error_log">
            <ConnectionFactory class="com.xiaodou.park.util.ConnectionFactory" method="getDataSourceConnection" />
            <Column name="class" pattern="%c" />
            <Column name="method" pattern="%M" />
            <Column name="log_level" pattern="%p" />
            <Column name="msg" pattern="%msg"/>
            <Column name="create_time" pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}"/>
        </JDBC>
    </Appenders>

    <Loggers>
        <logger name="com.xiaodou.park" level="trace" additivity="false">
            <appender-ref ref="XdyjError" level="error"/>
            <appender-ref ref="saveFile" level="info"/>
            <appender-ref ref="Console" level="debug"/>
            <appender-ref ref="databaseAppender" level="error"/>
        </logger>
        <Root level="error">
            <!-- Only events at DIAG level or more specific are sent to the console. -->
            <appenderRef ref="XdyjError"/>
            <appenderRef ref="saveFile"/>
            <appender-ref ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

三、新建数据库连接类,使用的是druid数据源(主要是项目中与数据库的连接使用的就是druid数据源)

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

import com.google.common.collect.MapConstraints;
import org.apache.log4j.jdbc.JDBCAppender;
import org.apache.log4j.spi.ErrorCode;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;

public class ConnectionFactory extends JDBCAppender {
    /* Druid数据源 */
    private DruidDataSource dataSource;
    private static ConnectionFactory connectionFactory;
    public ConnectionFactory() {
        super();
    }

    @Override
    protected void closeConnection(Connection con) {
        try {
            /* 如果数据库连接对象不为空和没有被关闭的话,关闭数据库连接 */
            if (con != null && !con.isClosed())
                con.close();
        } catch (SQLException e) {
            errorHandler.error("Error closing MyJDBCAppender.closeConnection() 's connection", e, ErrorCode.GENERIC_FAILURE);
        }
    }

    @Override
    protected Connection getConnection() throws SQLException {
        String className = "com.mysql.jdbc.Driver";
        String connUrl = "jdbc:mysql://localhost:3306/auge_log?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true";
        String uname = "root";
        String psw = "123456";
        Properties result = new Properties();
        result.put("driverClassName", className);
        result.put("url", connUrl);
        result.put("username", uname);
        result.put("password", psw);

        /* 其他配置 自然你也可以自己写property 然后获取set */
        result.put("maxActive", "30");
        result.put("minIdle", "3");

        try {
            dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(result);
        } catch (Exception e) {
            /* Druid数据库源对象产生失败后,取消初始化 */
            try {
                uninitialize();
            } catch (Exception e2) {
            }
        }

        return dataSource.getConnection();
    }

    /* 取消初始化 */
    public void uninitialize() {
        try {
            if (dataSource != null)
                dataSource.close();
        } catch (Exception e) {
        } finally {
            super.close();
        }
    }
    public static Connection getDataSourceConnection() throws SQLException {
        if (connectionFactory==null){
            connectionFactory = new ConnectionFactory();
        }
        return connectionFactory.getConnection();
    }

}

四、在需要记录的方法里添加日志记录。

代码部分省略,若录入数据库为error级别,则只有logger.error的日志会被存储到数据库。

@Controller
@RequestMapping("/admin")
public class AdminController {
    private static Logger logger = LogManager.getLogger(AdminController.class);
    @RequestMapping(value = "/login", method = {RequestMethod.POST}, produces = MEDIATYPE_CHARSET_JSON_UTF8)
    @ResponseBody
    public String login(HttpServletRequest request,String name,String password) {
        logger.info("管理员尝试登陆: name = " + name);
        try {
            if (Strings.isNullOrEmpty(name) || Strings.isNullOrEmpty(password)) {
                logger.info("用户名或密码不能为空!");
            }
            AugeAdmin admin = adminService.loginByUesrnameAndPassword(name, md5Pwd);
            if (admin == null) {
                logger.error("用户名或密码错误");
            }
        } catch (Exception e) {
	    logger.error(e.getMessage());
            e.printStackTrace();	
        }
        return "";
    }
}

五、运行代码,密码故意输错,查看数据库。

log4j2.xml配置日志写入数据库

log4j2.xml配置日志写入数据库

附:log4j2.xml配置解析

配置文件节点解析:
1.根节点Configuration有两个属性:status和monitorinterval,有两个子节点:Appenders和Loggers(表明可以定义多个Appender和Logger).
status用来指定log4j本身的打印日志的级别。monitorinterval用于指定log4j自动重新配置的监测间隔时间,单位是s,最小是5s。
2.Appenders节点,常见的有三种子节点:Console、RollingFile、File,本章中还使用了JDBC节点。
Console节点用来定义输出到控制台的Appender。
属性
name:指定Appender的名字。
target:SYSTEM_OUT 或 SYSTEM_ERR,一般只设置默认:SYSTEM_OUT。
子节点
PatternLayout:输出格式,不设置默认为:%m%n。
格式举例:<PatternLayout pattern="[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"/>
File节点用来定义输出到指定位置的文件的Appender。
属性
name:指定Appender的名字。
fileName:指定输出日志的目的文件带全路径的文件名。
子节点
PatternLayout:输出格式,不设置默认为:%m%n。
RollingFile节点用来定义超过指定大小自动删除旧的创建新的的Appender。
属性
name:指定Appender的名字。
fileName:指定输出日志的目的文件带全路径的文件名。
filePattern:指定新建日志文件的名称格式。
子节点
PatternLayout:输出格式,不设置默认为:%m%n。
Policies:指定滚动日志的策略,就是什么时候进行新建日志文件输出日志。基于时间的触发策略。该策略主要是完成周期性的log文件封存工作。
有两个参数:interval,integer型,指定两次封存动作之间的时间间隔。单位:以日志的命名精度来确定单位,比如yyyy-MM-dd-HH 单位为小时,yyyy-MM-dd-HH-mm 单位为分钟
modulate,boolean型,说明是否对封存时间进行调制。若modulate=true,则封存时间将以0点为边界进行偏移计算。
比如,modulate=true,interval=4hours,那么假设上次封存日志的时间为03:00,则下次封存日志的时间为04:00,之后的封存时间依次为08:00,12:00,16:00
Policies的子节点
TimeBasedTriggeringPolicy:基于时间的滚动策略,interval属性用来指定多久滚动一次,默认是1 hour。modulate=true用来调整时间:比如现在是早上3am,interval是4,那么第一次滚动是在4am,接着是8am,12am...而不是7am.
SizeBasedTriggeringPolicy:基于指定文件大小的滚动策略,size属性用来定义每个日志文件的大小.
DefaultRolloverStrategy:用来指定同一个文件夹下最多有几个日志文件时开始删除最旧的,创建新的(通过max属性)。
JDBC节点用来配置日志输出到数据库
属性
name:指定Appender的名字。
tableName:日志存储表的名称。
子节点
ConnectionFactory:两个属性class:连接库的类,method:类中的连接方法。
Column:数据库表字段。 name:字段名称。pattern:写入值。isEventTimestamp:日期格式是否使用时间戳,不和pattern一起出现。
3.Loggers节点,常见的有两种:Root和Logger.
Root节点用来指定项目的根日志,如果没有单独指定Logger,那么就会默认使用该Root日志输出
level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.
AppenderRef:Root的子节点,用来指定该日志输出到哪个Appender.
Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。
level:日志输出级别,共有8个级别,按照从低到高为:All < Trace < Debug < Info < Warn < Error < Fatal < OFF.
name:用来指定该Logger所适用的类或者类所在的包全路径,继承自Root节点.
AppenderRef:Logger的子节点,用来指定该日志输出到哪个Appender,如果没有指定,就会默认继承自Root.如果指定了,那么会在指定的这个Appender和Root的Appender中都会输出,此时我们可以设置Logger的additivity="false"只在自定义的Appender中进行输出。

日志level级别定义:
All:最低等级的,用于打开所有日志记录.
Trace:是追踪,就是程序推进以下,你就可以写个trace输出,所以trace应该会特别多,不过没关系,我们可以设置最低日志级别不让他输出.
Debug:指出细粒度信息事件对调试应用程序是非常有帮助的.
Info:消息在粗粒度级别上突出强调应用程序的运行过程.
Warn:输出警告及warn以下级别的日志.
Error:输出错误信息日志.
Fatal:输出每个严重的错误事件将会导致应用程序的退出的日志.
OFF:最高等级的,用于关闭所有日志记录.

自定义格式:
%t:线程名称
%p:日志级别
%c:日志消息所在类名
%m:消息内容
%M:输出执行方法
%d:发生时间,%d{yyyy-MM-dd HH:mm:ss.SSS}
%x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
%L:代码中的行数
%n:换行