登录关系数据库并扩展日志界面

时间:2018-03-15 14:17:08

标签: java spring logging jdbc log4j2

为了满足我的Spring MVC应用程序的新安全性要求,我需要记录除了正常日志消息之外的额外信息,如userId,并将它们存储到关系数据库中。

我想减少添加appender和日志接口的新实现的更改次数(如果可能)。我使用log4j 2.10并查看文档,可以扩展AbstractAppender以添加一个appender,但我不知道如何扩展Log接口来实现类似的东西:

logger.info(userName, message);

有任何线索吗?

1 个答案:

答案 0 :(得分:0)

一种可能性是在log4j2 xml配置中添加JDBC附加程序,但是Log4j在Spring之前初始化,因此dataSource在运行时将不可用,因此唯一的解决方案是以编程方式添加附加程序。当然,可以使用log4j2 jdbc附加程序,但是这种方法允许使用spring.properties文件根据我的配置文件使用适当的环境设置来覆盖spring应用程序上下文。

这是在应用程序上下文xml上定义的数据源:

<!--  ############ SQLSERVER DATABASE SECTION ############ -->
<bean id="dataSourceMSSqlServer" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
    <property name="url" value="jdbc:sqlserver://${sqlserver.hostname};databaseName=${sqlserver.database};" />
    <property name="username" value="${sqlserver.user}" />
    <property name="password" value="${sqlserver.pass}" />
</bean>

这使我可以为每种环境配置不同的数据库。 这是我要在其中记录条目的表:

[Id] [int] IDENTITY(1,1) NOT NULL,
[CreatedTimeStamp] [datetimeoffset](7) NOT NULL,
[Level] [int] NOT NULL,
[Source] [nvarchar](max) NULL,
[Message] [nvarchar](max) NULL,
[Content] [nvarchar](max) NULL,
[ProductName] [nvarchar](max) NULL,
[Version] [nvarchar](max) NULL,
[LogType] [int] NOT NULL DEFAULT ((0)),
[AuditEventType] [int] NULL,
[UserId] [nvarchar](128) NULL,

计划是创建一个Spring bean,注入DataSource bean,并在 @PostConstruct 方法中动态添加JDBC Appender配置。

package com.afm.web.utility;

import java.sql.Connection;
import java.sql.SQLException;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.db.ColumnMapping;
import org.apache.logging.log4j.core.appender.db.jdbc.ColumnConfig;
import org.apache.logging.log4j.core.appender.db.jdbc.ConnectionSource;
import org.apache.logging.log4j.core.appender.db.jdbc.JdbcAppender;
import org.apache.logging.log4j.core.config.AppenderRef;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class JDBCLog {

    @Autowired
    private DataSource dataSourceMSSqlServer;

    // Inner class
    class Connect implements ConnectionSource {

    private DataSource dsource;

    public Connect(DataSource dsource) {
        this.dsource = dsource;
    }

    @Override
    public Connection getConnection() throws SQLException {
        return this.dsource.getConnection();
    }

    }

    public JDBCLog() {}

    @PostConstruct
    private void init(){

    System.out.println("####### JDBCLog init() ########");      
    final LoggerContext ctx = (LoggerContext) LogManager.getContext(false); 
    final Configuration config = ctx.getConfiguration();

    // Here I define the columns I want to log. 
    ColumnConfig[] columnConfigs = new ColumnConfig[] {
        ColumnConfig.newBuilder()
                .setName("CreatedTimeStamp")
                .setPattern(null)
                .setLiteral(null)
                .setEventTimestamp(true)
                .setUnicode(false)
                .setClob(false).build(),
        ColumnConfig.newBuilder()
                .setName("Source")
                .setPattern("%K{className}")
                .setLiteral(null)
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
        ColumnConfig.newBuilder()
                .setName("Level")
                .setPattern("%level")
                .setLiteral(null)
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
        ColumnConfig.newBuilder()
                .setName("Message")
                .setPattern("%K{message}")
                .setLiteral(null)
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
        ColumnConfig.newBuilder()
                .setName("Content")
                .setPattern("%K{exception}")
                .setLiteral(null)
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
        ColumnConfig.newBuilder()
                .setName("ProductName")
                .setPattern(null)
                .setLiteral("'DHC'")
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
        ColumnConfig.newBuilder()
                .setName("Version")
                .setPattern(null)
                .setLiteral("'1.0'")
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
        ColumnConfig.newBuilder()
                .setName("AuditEventType")
                .setPattern("%K{eventId}")
                .setLiteral(null)
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
        ColumnConfig.newBuilder()
                .setName("UserId"
                .setPattern("%K{userId}")
                .setLiteral(null)
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build(),
        ColumnConfig.newBuilder()
                .setName("LogType")
                .setPattern("%K{logType}")
                .setLiteral(null)
                .setEventTimestamp(false)
                .setUnicode(false)
                .setClob(false).build()
        };

    Appender jdbcAppender = JdbcAppender.newBuilder()
        .setBufferSize(0)
                .setColumnConfigs(columnConfigs)
                .setColumnMappings(new ColumnMapping[]{})
                .setConnectionSource(new Connect(dataSourceMSSqlServer))
                .setTableName("dhc.LogItems")
                .withName("databaseAppender")
                .withIgnoreExceptions(true)
                .withFilter(null)
                .build();

    jdbcAppender.start();
    config.addAppender(jdbcAppender);

    // Create an Appender reference.
    // @param ref The name of the Appender.
    // @param level The Level to filter against.
    // @param filter The filter(s) to use.
    // @return The name of the Appender.
    AppenderRef ref= AppenderRef.createAppenderRef("JDBC_Appender", null, null);
        AppenderRef[] refs = new AppenderRef[] {ref};

        /*
         * Factory method to create a LoggerConfig.
         *
         * @param additivity true if additive, false otherwise.
         * @param level The Level to be associated with the Logger.
         * @param loggerName The name of the Logger.
         * @param includeLocation whether location should be passed downstream
         * @param refs An array of Appender names.
         * @param properties Properties to pass to the Logger.
         * @param config The Configuration.
         * @param filter A Filter.
         * @return A new LoggerConfig.
         * @since 2.6
         */
        LoggerConfig loggerConfig = LoggerConfig.createLogger(
                false, Level.DEBUG, "JDBC_Logger", null, refs, null, config, null);        
        loggerConfig.addAppender(jdbcAppender, null, null);

        config.addLogger("JDBC_Logger", loggerConfig);       
        ctx.updateLoggers();  

        System.out.println("####### JDBCLog init() - DONE ########");  

    }

    public DataSource getDataSource() {
    return dataSourceMSSqlServer;
    }

    public void setDataSource(DataSource dataSourceMSSqlServer) {
    this.dataSourceMSSqlServer = dataSourceMSSqlServer;
    }     

}

此时,可以通过以下方式从代码中调用记录器:

Logger jdbcLogger = LogManager.getContext(false).getLogger("JDBC_Logger"); 
jdbcLogger.info(new StringMapMessage()
    .with("eventId", AuditEventType.Logger_General.toString())
    .with("exception", "")
    .with("userId", "TESTUSER")
    .with("message", "TEST!!")
    .with("className", 
        this.getClass().getPackage().toString().replaceAll("package ", "") 
        + "." + this.getClass().getSimpleName() 
        + "." + new Object() {}.getClass().getEnclosingMethod().getName())
);

如果您在Servlet 2.5 Web应用程序中使用Log4j,或者已使用isLog4jAutoInitializationDisabled上下文参数禁用了自动初始化,则必须配置 Log4jServletContextListener Log4jServletFilter 在部署描述符中或以编程方式。过滤器应匹配任何类型的所有请求。侦听器应该是您的应用程序中定义的第一个侦听器,过滤器应该是应用程序中定义和映射的第一个过滤器。使用以下web.xml代码可以轻松完成此操作:

<listener>
    <listener-class>
        org.apache.logging.log4j.web.Log4jServletContextListener
    </listener-class>
</listener>

<filter>
    <filter-name>log4jServletFilter</filter-name>
    <filter-class>
        org.apache.logging.log4j.web.Log4jServletFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>log4jServletFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
    <!-- 
        Servlet 3.0 w/ disabled auto-initialization only; 
        not supported in 2.5 
    -->
    <dispatcher>ASYNC</dispatcher>
</filter-mapping>

此依赖项必须添加到pom.xml:

<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-web</artifactId>
    <version>2.10.0</version>
</dependency>

当然,如果尚未加入,请将其添加到组件扫描中,进入Spring应用程序上下文:

<context:component-scan base-package="com.afm.web.utility" />