java - 登录关系型数据库并扩展日志接口(interface)

标签 java spring logging jdbc log4j2

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

我想减少添加附加程序和日志接口(interface)的新实现的更改数量(如果可能)。我正在使用 log4j 2.10 并查看文档,可以扩展 AbstractAppender 来添加附加程序,但我不知道如何扩展 Log 接口(interface)来实现类似的功能:

logger.info(userName, message);

有什么线索吗?

最佳答案

一种可能性是在 log4j2 xml 配置中添加 JDBC 附加程序,但是 Log4j 在 Spring 之前初始化,因此数据源在运行时不可用,因此唯一的解决方案是以编程方式添加附加程序。当然,可以使用 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,注入(inject) 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 上下文参数禁用自动初始化,则必须在部署描述符中或以编程方式配置 Log4jServletContextListenerLog4jServletFilter。过滤器应该匹配任何类型的所有请求。监听器应该是应用程序中定义的第一个监听器,并且过滤器应该是应用程序中定义和映射的第一个过滤器。使用以下 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" />

关于java - 登录关系型数据库并扩展日志接口(interface),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49301846/

相关文章:

java - Apache Spark 网络端口配置

java - Android Studio 数组值到 TextView

java - Spring 是否提供 JpaRepositories 的 stub 实现?

java - SpringMVC Controller 不工作

spring - 在 MongoDB 支持的 Spring Data REST 存储库中使用自定义 ID

android - 如何在 android 中启用 ALOGD?

从 HashMap 中删除元素时出现 java.util.ConcurrentModificationException

java - Java中使用printf显示信息

logging - 如何使用 IIS 7 自定义日志记录模块和 ETW 跟踪模块

c# - 无法在 Azure 应用服务上生成日志 NLog 内部日志记录文件