` 需要自定义数据库连接工厂类,并提供获取DataSource或者Connection的静态方法。
### 2.4 编写LoggerConnectionFactory类实现逻辑
`LoggerConnectionFactory`类的代码如下:
```java
package com.yonyou.esn.palmyy.common;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 日志配置所使用的数据库连接工厂类
* Created by followtry on 2017/4/20.
*/
@Component
public class LoggerConnectionFactory {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggerConnectionFactory.class);
private static final LoggerConnectionFactory loggerConnectionFactory = new LoggerConnectionFactory();
@Autowired
private DataSource dataSource;
//通过内部单例接口实例化当前工厂类并提供Connection连接
private interface Singleton {
LoggerConnectionFactory INSTANCE = new LoggerConnectionFactory();
}
@PostConstruct
void init(){
Properties prop = new Properties() {
{
put("username","abc");
put("password","abc");
put("url","jdbc:mysql://172.20.19.200:3306/xx_test?useUnicode=true&characterEncoding=UTF-8");
put("driverClassName","com.mysql.jdbc.Driver");
}
};
try {
dataSource = DruidDataSourceFactory.createDataSource(prop);
} catch (Exception e) {
LOGGER.error("获取数据源出错",e);
}
}
private LoggerConnectionFactory() {}
public static Connection getDatabaseConnection() throws SQLException {
return Singleton.INSTANCE.dataSource.getConnection();
}
}
```
这样的方式是可以获取到Mysql数据库的`Connection`并且也可以将日志写入到数据库中,但是很明显这样有两个很大的缺点:
- 自己控制对数据库的链接,没有应用业界比较好的数据源库也没有使用线程池。
- 没有应用到Spring的IOC和DI。
### 2.5 Log4j2设置解惑
**有人会问:**
对于连接属性的配置直接调用Spring管理的数据源不行吗?答案是现在这样的配置不行,因为log4j2等日志是在Spring初始化前加载并初始化的,因此log4j2在当前配置下是拿不到Spring的bean信息的。
那这个问题就无解了吗?当然不是,要不然干嘛要引入`log4j-web`包呢。引入该包就是为了在web应用中使用Log4j2的,具体可以参考
**要说明的是:Log4j2仅支持Servlet3.0及以上版本,Tomcat7.0及以上**
Log4j2会在WEB容器启动和销毁时自动启动和关闭。是通过`Log4jServletContainerInitializer`继承自`ServletContainerInitializer`达到自动启动的目的。在Tomcat7.0.43之前,基于性能原因,web容器会忽略名为log4j*.jar包,阻止其获取随web容器启动的特性。但在之后的版本中已经修复。
如果禁止Log4j2的自动初始化,那么需要在web.xml中加入配置:
```xml
isLog4jAutoInitializationDisabled
true
```
但是在Servlet2.5需要在其他应用代码执行前初始化Log4j。
如果使用的是**Servlet2.5**,那么需要在web.xml文件中加入配置:
```xml
org.apache.logging.log4j.web.Log4jServletContextListener
log4jServletFilter
org.apache.logging.log4j.web.Log4jServletFilter
log4jServletFilter
/*
REQUEST
FORWARD
INCLUDE
ERROR
```
详情请看,接下来就说明下怎样通过在Log4j2中使用Spring管理的bean。
### 2.6 重新配置Log4j2支持JDBC
这次配置是基于Spring的,使用了Spring容器的功能,对配置文件中Log4j2的配置进行增加。
代码如下:
```java
package com.yonyou.esn.palmyy.common;
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.LoggerContext;
import org.apache.logging.log4j.core.appender.AsyncAppender;
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;
/**
* 配置log4j2的JDBC写数据库的配置类
*
* Created by followtry on 2017/4/20.
*/
@Component
public class LoggerBean {
@Autowired
private DataSource dataSource;
/**
* 初始化日志写数据库配置,在构造方法后执行
*/
@PostConstruct
void init(){
LoggerContext context = (LoggerContext)LogManager.getContext(false);
//
Configuration cfg = context.getConfiguration();
/**
* 该配置对应columnConfig
*
*
*/
ColumnConfig[] columnConfig = {
ColumnConfig.newBuilder().setConfiguration(cfg).setName("thread_name")
.setPattern("%t").build(),
ColumnConfig.newBuilder().setConfiguration(cfg).setName("class_name")
.setPattern("%C").build(),
ColumnConfig.newBuilder().setConfiguration(cfg).setName("method_name")
.setPattern("%method").build(),
ColumnConfig.newBuilder().setConfiguration(cfg).setName("beginTime")
.setEventTimestamp(true).build(),
ColumnConfig.newBuilder().setConfiguration(cfg).setName("logger_level")
.setPattern("%level").build(),
ColumnConfig.newBuilder().setConfiguration(cfg).setName("logger_name")
.setPattern("%c").build(),
ColumnConfig.newBuilder().setConfiguration(cfg).setName("logger_message")
.setPattern("%m").build(),
ColumnConfig.newBuilder().setConfiguration(cfg).setName("logger_throwable")
.setPattern("%throwable{3}").build(),
ColumnConfig.newBuilder().setConfiguration(cfg).setName("createTime")
.setEventTimestamp(true).build(),
};
JdbcAppender jdbcAppender = JdbcAppender.newBuilder()
.setConfiguration(cfg)
.withName("databaseAppender")
.withIgnoreExceptions(false)
.setConnectionSource(new Connect(dataSource))
.setTableName("logger_info")
.setColumnConfigs(columnConfig)
//ColumnMapping设置为空,是因为如果不设置或设置为null会报空指针异常
.setColumnMappings(new ColumnMapping[]{}).build();
jdbcAppender.start();
//将添加到
cfg.addAppender(jdbcAppender);
/**
*
*/
AsyncAppender asyncDbAppender = AsyncAppender.newBuilder().setConfiguration(cfg).setName
("asyncDatabaseAppender").setAppenderRefs(new AppenderRef[] { AppenderRef
.createAppenderRef(jdbcAppender.getName(),Level.ERROR,null) }).build();
asyncDbAppender.start();
//将添加到
cfg.addAppender(asyncDbAppender);
//将添加到
LoggerConfig loggerConfig = new LoggerConfig();
loggerConfig.setAdditive(false);
//设置为jdbcAppender为同步写库,配置为asyncDbAppender为异步写库,但是异步写库会导致类名和方法名找不到
loggerConfig.addAppender(jdbcAppender,Level.ERROR,null);
cfg.addLogger("asyncDBLogger",loggerConfig);
context.updateLoggers();
}
//内部类
class Connect implements ConnectionSource {
private DataSource dsource;
public Connect(DataSource dsource) {
this.dsource = dsource;
}
@Override
public Connection getConnection() throws SQLException {
return this.dsource.getConnection();
}
}
private LoggerBean() {}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
```
以上代码中的注释已经比较详细了,概括来说就是在当前LoggerBean被IOC实例化之后获取到已经从配置文件中读取的Log4j2的配置,并在init()方法中用代码将JDBC部分的逻辑补充上,更新下Log4j2的上下文即可。一个类内就把Log4j2的JDBC功能实现了,不需要在任何地方做log4j2对JBDC支持的配置。
### 2.7 Log4j2配置发邮件功能
log4j2.xml中``内的配置就是控制日志发送邮件的功能了,默认使用的Lauout是``,对应类`org.apache.logging.log4j.core.layout.HtmlLayout`,比较蛋疼的是该类是final型的,不能被继承也就是说不能通过继承该类实现自定义Html布局。唉没办法,使用自定义的Layout吧!
自定义的layout需要继承`org.apache.logging.log4j.core.layout.AbstractStringLayout`,并且要在类上加上注解:
```java
@Plugin(
name = "HtmlLayout",
category = "Core",
elementType = "layout",
printObject = true
)
```
其中name处value是配置文件`log4j2.xml`中使用的标签,如``,写者没有对其原理进行分析,直接强行复制其代码新建了个类,并修改了关键的位置`@Plugin`的name作为自己的布局模板。
自定义的布局模板关键代码如下:
```java
@Plugin(
//此处name值即在Log4j2.xml中配置的标签名,如
name = "customHtmlLayout",
category = "Core",
elementType = "layout",
printObject = true)
/**
* Created by followtry on 2017/4/26.
*/
public class CustomHtmlLayout extends AbstractStringLayout {
public byte[] getHeader() {
//实现布局Header部分的逻辑
}
public String toSerializable(LogEvent event){
//内部实现布局body的逻辑
}
public byte[] getFooter() {
//实现布局footer的逻辑
}
}
```
通过将上面解释的逻辑调整为自己想要的就实现了自定义布局。
### 2.8 Log4j2支持Mongo写日志
此处不涉及Mongo的安装和使用
对于NoSql的配置就比较简单了,以Mongo为例:
```xml
```
相信Mongo配置的属性不用解释也能看明白了,就这点配置,只要能连上Mongo就可以将日志写入到Mongo中,当然前提是要在``或者``标签中引用`noSqlDbAppender`。
Mongo存储结构如下:

## 3 注意事项
log4j2 中配置``标签异步存储日志会使得写Mysql库和Mongo库时,调用Logger的ClassName和MethodName都为空,写者当前在项目中暂时没有使用异步写日志操作。