在关系数据库上记录并扩展日志接口



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

我想减少更改的数量(如果可能的话),添加一个追加器和日志接口的新实现。我正在使用log4j 2.10并查看文档,可以扩展AbstractAppender以添加追加器,但我不知道如何扩展Log接口以实现以下内容:

logger.info(userName, message);

有什么线索吗?

一种可能性是在 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,

计划是创建一个春豆,注入数据源 bean,并在@PostConstruct方法中动态添加 JDBC 追加器配置。

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" />

最新更新