目录
6、以输出到控制台为例,编写异步日志(当然对于其他类型的日志也是一样的)
8、在maven/ssh环境中将日志输出到数据库(这里是mysql,用到了数据库主键的自增)
9、日志保存到数据库(必看)的最佳实践-log4j2的启动在spring加载配置文件之前
11、查看 Spring 日志最佳时间-关键是开启 debug模式
前言
本文将展示在maven/spring+hibernate环境中实现log4j2日志的输出,包括分级输出到控制台、日志(按日志文件大小、日期分割、限定文件个数)、数据库。
Log4j2可以实现对log4j、slf4j、java自带日志的兼容。
Log4j2的官方文档地址如下
http://logging.apache.org/log4j/2.x/manual/appenders.html#FailoverAppender
SpringBoot 配置
参考:Springboot整合log4j2日志全解 - 上帝爱吃苹果-Soochow - 博客园
* 去除springboot默认的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions><!-- 去掉springboot默认配置 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
* 增加独立的pom依赖
<dependency> <!-- 引入log4j2依赖 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
但是问题没有这么简单,有些简介依赖的项目需要 log4j,比如 durid,那么需要加上下面这段,在不修改代码的情况下 更多可参考 Slf4j与log4j及log4j2的关系及使用方法
<!-- 添加log4j2依赖 - start-->
<!-- log4j-1.2-api为log4j和log4j2的连接包。 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-1.2-api</artifactId>
<version>2.3</version>
</dependency>
<!--log4j2核心jar包-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.3</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.3</version>
<!-- <scope>provided</scope> -->
</dependency>
* 配置log4j2
参考本文
指定路径和级别 logging.config
logging :
level :
org :
springframework : INFO
config: classpath:log4j2.xml
*引入 lombok
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
使用 @slfj4 注解
import lombok.extern.slf4j.Slf4j;
@Slf4j
log.info("111");
log.error("111{}","测试");
普通项目配置
1、maven的pom.xml配置Log4j2的依赖
<!-- 日志框架声明:http://blog.csdn.net/edward0830ly/article/details/8250412 http://blog.csdn.net/ziruobing/article/details/3919501 http://blog.csdn.net/autfish/article/details/51203709-->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.7</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.7</version>
</dependency>
<!-- log4j2-链接数据库 -->
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
<version>1.6</version>
</dependency>
2、创建log4j2.xml
3、编写log4j2.xml的一般形式
在log4j2.xml文件中我们需要做出这样一些配置。
·日志的名字是什么,比如root日志是根日志,可以在调用的时候直接声明调用根日志,也可以任意命名一个日志去调用,当然,跟日志也可以引用自定义日志。
·日志的级别是什么,比如是info或者warn,也可以使用过滤器,让不同的级别的日志输出到不同的日志中去。
·日志输出到什么地方,比如是控制台,日志文件或者是数据库。
·同步输出日志还是异步输出日志。
·输出日志的格式,比如仅仅显示日期和信息还是类名和方法名等。
·在配置文件中配置常量。
·高级用法,以及以json格式描述的log4j.xml(本文不涉及,更多介绍请参考官方文档)
4、将日志以某种格式输出到控制台及测试方法
log4j2.xml内容,<Appenders>决定日志以哪种格式输出到什么地方,<Loggers>则约定了被具体代码应用的日志的名字,通过 <AppenderRef>和<Appenders>联系起来
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="300"><!-- 每隔300秒重新读取配置文件,对web应用很实用 -->
<Appenders>
<!-- 日志输出格式 :<Console是控制台,<File是文件,<RollingRandomAccessFile按时间和文件大小生成多个日志,<JDBC是数据库,<Async异步-->
<!-- Console 输出到控制台及格式-->
<Console name="toConsole" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %F %M %l- %-5level %logger{36} %msg%n" />
</Console>
</Appenders>
<Loggers><!-- 日志类别 -->
<!-- 定义根日志类别 查看level http://blog.csdn.net/techq/article/details/6636287-->
<Root level="trace">
<AppenderRef ref="toConsole" />
</Root>
<!-- 输出到控制台 -->
<Logger name="mylog" additivity="false" level="TRACE"><!--additivity="false" 不再输出父级日志 -->
<AppenderRef ref="toConsole" />
</Logger>
</Loggers>
</Configuration>
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
测试方法-根日志输出到控制台
/**
* 测试root日志打印到控制台
*/
@Test
public void testLog4j2(){
Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
logger.trace("trace level");
logger.debug("debug level");
logger.info("info level");
logger.warn("warn level");
logger.error("error level");
logger.fatal("fatal level");
}
测试方法-name为“mylog”的Logger输出到控制台-additivity=“false”则跟日志不再输出,否则根日志默认会再次输出一次
/**
* 测试自定义日志打印到控制台
*/
@Test
public void testLog4j2SC(){
Logger logger = LogManager.getLogger("mylog");
logger.trace("trace level");
logger.debug("debug level");
logger.info("info level");
logger.warn("warn level");
logger.error("error level");
logger.fatal("fatal level");
}
日志格式和输出内容比对如下
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %F %M %l- %-5level %logger{36} %msg%n" />
2017-01-16 22:20:11.224 [main] TestForAll.java testLog4j2 com.bestcxx.mavenstu.mavenssh.util.TestForAll.testLog4j2(TestForAll.java:14)- TRACE trace level
5、将日志输出到文件
输出到文件有2种应用模式,1是直接输出到一个文件,2是限定日志文件大小、名字和总数来滚动输出,第2种也是使用较为广泛的
·直接输出到一个文件
log4j2.xml内容
<!-- 日志输出格式 :<Console是控制台,<File是文件,<RollingRandomAccessFile按时间和文件大小生成多个日志,<JDBC是数据库,<Async异步-->
<!-- File 输出到文件及格式 -->
<File name="toFile" fileName="D://a/log/mavenssh.log">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</File>
</Appenders>
<Loggers><!-- 日志类别 -->
<!-- 定义根日志类别 查看level http://blog.csdn.net/techq/article/details/6636287-->
<Root level="trace">
<AppenderRef ref="toFile" />
</Root>
<!-- 输出到文件 -->
<Logger name="mylogtofile" additivity="false" level="TRACE"><!--additivity="false" 不再输出父级日志 -->
<AppenderRef ref="toFile" />
</Logger>
</Loggers>
</Configuration>
测试代码
/**
* 自动以日志输出到文件
*/
@Test
public void testLog4j2SF(){
Logger logger = LogManager.getLogger("mylogtofile");
logger.trace("trace level");
logger.debug("debug level");
logger.info("info level");
logger.warn("warn level");
logger.error("error level");
logger.fatal("fatal level");
}
·将日志按照一定的命名格式、限定日志大小和日志数量滚动输出到日志文件中
log4j2.xml内容,这里我们增加了常量,而且在调用测试的代码中,使用的不是root或者logger名字,而是类名.class,你会发现系统会在找不到对应的Logger时自动使用
Root这个日志配置
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="300"><!-- 每隔300秒重新读取配置文件,对web应用很实用 -->
<properties><!-- 定义常量 -->
<property name="LOG_HOME">D://a/log</property>
<property name="FILE_NAME">mavenssh2</property>
</properties>
<Appenders>
<!-- 日志输出格式 :<Console是控制台,<File是文件,<RollingRandomAccessFile按时间和文件大小生成多个日志,<JDBC是数据库,<Async异步-->
<!-- RollingRandomAccessFile
TimeBasedTriggeringPolicy interval="1"一个最小时间单位生成一个文件
SizeBasedTriggeringPolicy size="1 MB" 当文件大小超过1MB生成一个日志文件,你可以写成0.001MB实验一下效果
DefaultRolloverStrategy max="2" 同一个时间节点最多生成2个日志文件,否则后面的覆盖前面的,如1分钟10MB,允许2个,则最终是第9MB和第10MB的日志存在 时间节点-index(1,2)
-->
<RollingRandomAccessFile name="toFileByRoll"
fileName="${LOG_HOME}/${FILE_NAME}.log"
filePattern="${LOG_HOME}/${date:yyyy-MM}/${FILE_NAME}-%d{yyyy-MM-dd HH-mm}-%i.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
<Policies>
<TimeBasedTriggeringPolicy interval="1" />
<SizeBasedTriggeringPolicy size="0.001 MB" />
</Policies>
<DefaultRolloverStrategy max="2" />
</RollingRandomAccessFile>
</Appenders>
<Loggers><!-- 日志类别 -->
<!-- 定义根日志类别 查看level http://blog.csdn.net/techq/article/details/6636287-->
<Root level="trace">
<AppenderRef ref="toFileByRoll" />
</Root>
<!-- 按照时间节点和文件大小滚动生成日志 -->
<Logger name="mylogtofileroll" additivity="false" level="TRACE"><!--additivity="false" 不再输出父级日志 -->
<AppenderRef ref="toFileByRoll" />
</Logger>
</Loggers>
</Configuration>
测试代码
/**
* 自动以日志输出到文件
* 滚动输出
*/
@Test
public void testLog4j2SFR(){
//Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);//根日志
//Logger logger = LogManager.getLogger("mylogtofileroll");//自定义日志
Logger logger = LogManager.getLogger(TestForAll.class);//以类名作为日志的名字
logger.trace("trace level");
logger.debug("debug level");
logger.info("info level");
logger.warn("warn level");
logger.error("error level");
logger.fatal("fatal level");
}
效果
和
6、以输出到控制台为例,编写异步日志(当然对于其他类型的日志也是一样的)
原理就是,如果有多个日志需要输出,则可以将这多个日志统一于<Appenders>下的<Async,然后被Root日志引用或者被自定义日志引用
例子为两个输出到控制台的格式定义被异步输出
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="300"><!-- 每隔300秒重新读取配置文件,对web应用很实用 -->
<Appenders>
<!-- Console 输出到控制台及格式-->
<Console name="toConsole" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %F %M %l- %-5level %logger{36} %msg%n" />
</Console>
<Console name="toConsole2" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS}%msg[2]%n" />
</Console>
<!-- 异步日志 -->
<Async name="Async">
<AppenderRef ref="toConsole" />
<AppenderRef ref="toConsole2"/>
</Async>
</Appenders>
<Loggers><!-- 日志类别 -->
<!-- 定义根日志类别 查看level http://blog.csdn.net/techq/article/details/6636287-->
<Root level="trace">
<AppenderRef ref="Async" />
</Root>
<!-- 使用异步 -->
<Logger name="asyncConsole" additivity="false" level="trace">
<AppenderRef ref="Async"/>
</Logger>
</Loggers>
</Configuration>
测试代码
/**
* 异步输出日志
*
*/
@Test
public void testLog4j2asyncConsole(){
Logger logger = LogManager.getLogger("asyncConsole");
logger.trace("trace level");
logger.debug("debug level");
logger.info("info level");
logger.warn("warn level");
logger.error("error level");
logger.fatal("fatal level");
}
效果
7、使用过滤器输出日志
好处是,将制定级别的日志保留到指定位置,而不是全部的
log4j2.xml内容
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="300"><!-- 每隔300秒重新读取配置文件,对web应用很实用 -->
<Appenders>
<!-- Console 输出到控制台及格式-并使用过滤器 <Filters-->
<Console name="toConsoleFilter" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %F %M %l- %-5level %logger{36} %msg%n" />
<Filters>
<ThresholdFilter level="fatal" onMatch="DENY" onMismatch="NEUTRAL" /><!-- 高级别优先设置,NEUTRAL不影响低等级设置 -->
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY" /> <!-- 低级别及以后遵循同样的标准-已经声明的高级别除外 -->
</Filters>
</Console>
</Appenders>
<Loggers><!-- 日志类别 -->
<!-- 定义根日志类别 查看level http://blog.csdn.net/techq/article/details/6636287-->
<Root level="trace">
<AppenderRef ref="toConsoleFilter" />
</Root>
<!-- 使用过滤器 -->
<Logger name="toConsoleFilter" level="trace" additivity="false">
<AppenderRef ref="toConsoleFilter" />
</Logger>
</Loggers>
</Configuration>
测试代码
/**
* 自动以日志输出到控制台
* 过滤器
* 日志级别是
* trace->debug->info->warn->error->fatal
* 本例中是
* 1、fatal 级别不输出
* 2、warn级别以及之后输出
* 综合是 warn之后以及fatal(不含fatal)级别之前输出
*
*/
@Test
public void testLog4j2ErrorNoFatal(){
Logger logger = LogManager.getLogger("toConsoleFilter");
logger.trace("trace level");
logger.debug("debug level");
logger.info("info level");
logger.warn("warn level");
logger.error("error level");
logger.fatal("fatal level");
}
* 自动以日志输出到控制台
* 过滤器
* 日志级别是
* trace->debug->info->warn->error->fatal
* 本例中是
* 1、fatal 级别不输出
* 2、warn级别以及之后输出
* 综合是 warn之后以及fatal(不含fatal)级别之前输出
*
*/
@Test
public void testLog4j2ErrorNoFatal(){
Logger logger = LogManager.getLogger("toConsoleFilter");
logger.trace("trace level");
logger.debug("debug level");
logger.info("info level");
logger.warn("warn level");
logger.error("error level");
logger.fatal("fatal level");
}
效果是仅输出了warn和error级别的
8、在maven/ssh环境中将日志输出到数据库(这里是mysql,用到了数据库主键的自增)
首先,本文是在maven/ssh环境中运行的,所以需要在测试的时候需要加载配置文件的内容
需要读者具备ssh的基本知识,可以参考文章
http://blog.csdn.net/bestcxx/article/details/52975675
8.1 首先需要新建日志表
这里我使用的是自动建表,所以只新建了实体
package com.bestcxx.mavenstu.mavenssh.model;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.GenericGenerator;
@SuppressWarnings("serial")
@Table(name="LOGGER")
@Entity(name="LOGGER")
public class LoggerModel implements Serializable{
private long eventId;//EVENT_ID 日志id
private Date eventDate;//EVENT_DATE -注意这里必须使用sqlDate
private String level;//LEVEL 日志的级别
private String logger;//LOGGER 日志内容
private String message;//MESSAGE 其他信息
private String throwable;//THROWABLE 异常抛出
@Id
@GeneratedValue(strategy = GenerationType.AUTO)//主键自增-mysql
//@GeneratedValue(strategy = GenerationType.IDENTITY)
//@GenericGenerator(name = "persistenceGenerator", strategy = "increment")
@Column(name="EVENT_ID",unique=true,nullable=false)
public long getEventId() {
return eventId;
}
public void setEventId(long eventId) {
this.eventId = eventId;
}
@Column(name="EVENT_DATE")
@Temporal(TemporalType.TIMESTAMP)
public Date getEventDate() {
return eventDate;
}
public void setEventDate(Date eventDate) {
this.eventDate = eventDate;
}
@Column(name="LEVEL",nullable=false,length=10)
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
@Column(name="LOGGER",nullable=true,length=100)
public String getLogger() {
return logger;
}
public void setLogger(String logger) {
this.logger = logger;
}
@Column(name="MESSAGE",nullable=true,length=100)
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Column(name="THROWABLE",nullable=true,length=100)
public String getThrowable() {
return throwable;
}
public void setThrowable(String throwable) {
this.throwable = throwable;
}
}
8.2 编写log4j2,xml调用的方法类
下面的方法是直接从官网上复制的,不同之处在于原文中数据库 url、用户名和密码是写在代码中的,这不符合我们的实际,因而稍微做了修改,使得该类可以直接从配置文件中获取相关的值
ConnectionFactory.java
package com.bestcxx.mavenstu.mavenssh.util;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnection;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.pool.impl.GenericObjectPool;
public class ConnectionFactory {
final private static String jdbcurl=CustomizedPropertyPlaceholderConfigurer.getContextProperty("jdbc.url");
final private static String jdbcusername=CustomizedPropertyPlaceholderConfigurer.getContextProperty("jdbc.username");
final private static String jdbcpassword=CustomizedPropertyPlaceholderConfigurer.getContextProperty("jdbc.password");
private static interface Singleton {
final ConnectionFactory INSTANCE = new ConnectionFactory();
}
private final DataSource dataSource;
private ConnectionFactory() {
Properties properties = new Properties();
properties.setProperty("user", jdbcusername);
properties.setProperty("password", jdbcpassword); // or get properties from some configuration file
GenericObjectPool<PoolableConnection> pool = new GenericObjectPool<PoolableConnection>();
DriverManagerConnectionFactory connectionFactory = new DriverManagerConnectionFactory(
jdbcurl, properties
);
new PoolableConnectionFactory(
connectionFactory, pool, null, "SELECT 1", 3, false, false, Connection.TRANSACTION_READ_COMMITTED
);
this.dataSource = new PoolingDataSource(pool);
}
public static Connection getDatabaseConnection() throws SQLException {
return Singleton.INSTANCE.dataSource.getConnection();
}
}
这样就需要新写一个类CustomizedPropertyPlaceholderConfigurer.java
这个类继承自PropertyPlaceholderConfigurer,而后者熟悉Spring的话一定可以认出来,其就是Spring用于加载常量配置文件的方法
可以参考Spring 加载标准属性值配置文件application.properties的两种方式
CustomizedPropertyPlaceholderConfigurer.java的内容
package com.bestcxx.mavenstu.mavenssh.util;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
/**
* 自定义PropertyPlaceholderConfigurer返回properties内容
* Spring的applicationContext.xml的
* <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
* 需要改变为
* <bean class="本类路径.CustomizedPropertyPlaceholderConfigurer">
*/
public class CustomizedPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
private static Map<String, String> ctxPropertiesMap;
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,Properties props) throws BeansException {
super.processProperties(beanFactoryToProcess, props);
ctxPropertiesMap = new HashMap<String, String>();
for (Object key : props.keySet()) {
String keyStr = key.toString();
String value = props.getProperty(keyStr);
ctxPropertiesMap.put(keyStr, value);
}
}
public static String getContextProperty(String name) {
return ctxPropertiesMap.get(name);
}
}
然后,正如该类中的注释中声明的那样,需要修改一下 applicationContext.xml的获取配置*.properties文件的那部分代码
<!-- 定义受环境影响易变的变量 -->
<!-- <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> -->
<bean class="com.bestcxx.mavenstu.mavenssh.util.CustomizedPropertyPlaceholderConfigurer"><!-- 覆盖重写这个类,可以在java代码中获取加载文件的值key-value形式 -->
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="ignoreResourceNotFound" value="true" />
<property name="locations">
<list>
<!-- 数据库配置 -->
<value>classpath:config/jdbc.properties</value>
</list>
</property>
</bean>
jdbc.properties的内容也贴一下
#jdbc settings
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=*
jdbc.password=*
jdbc.initialSize=5
jdbc.maxActive=10
#hibernate settings
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.show_sql=false
hibernate.format_sql=false
hibernate.hbm2ddl.auto=update
这里涉及到 hibernaye.hbm2ddl.auto=update 表不存在则创建表,表已存在若存在改变则更新表,其应用也是在applicationContext.xml中
<!-- 声明Hibernate 的 Session 工厂-->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- model里含有具体的实体类若干 -->
<property name="packagesToScan" value="com.bestcxx.mavenstu.mavenssh.model"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop><!--自动建表 http://www.cnblogs.com/feilong3540717/archive/2011/12/19/2293038.html -->
</props>
</property>
</bean>
当然,如果你觉得这样很麻烦,可以手动建表,也可以把数据库url、用户名和密码写在代码中
8.3、然后是log4j2.xml的配置
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN" monitorInterval="300"><!-- 每隔300秒重新读取配置文件,对web应用很实用 -->
<Appenders>
<!-- 日志输入到数据库 -->
<!-- 必须先创建表结构,所以建议保留日志表实体,在项目启动时自动创建或更新表 -->
<JDBC name="databaseAppender" tableName="LOGGER">
<ConnectionFactory class="com.bestcxx.mavenstu.mavenssh.util.ConnectionFactory" method="getDatabaseConnection" />
<!-- mysql 设置主键自增策略,对于数据库不支持的则开启下面这字段
<Column name="EVENT_ID" literal="LOGGER_SEQUENCE.NEXTVAL" />
-->
<Column name="EVENT_DATE" isEventTimestamp="true" />
<Column name="LEVEL" pattern="%level" />
<Column name="LOGGER" pattern="%logger" />
<Column name="MESSAGE" pattern="%message" />
<Column name="THROWABLE" pattern="%ex{full}" />
</JDBC>
</Appenders>
<Loggers><!-- 日志类别 -->
<!-- 定义根日志类别 查看level http://blog.csdn.net/techq/article/details/6636287-->
<Root level="trace">
<AppenderRef ref="databaseAppender" />
</Root>
<!-- 保存到数据库 -->
<Logger name="databaseAppender" additivity="false" level="INFO"><!--additivity="false" 不再输出父级日志 -->
<AppenderRef ref="databaseAppender" />
</Logger>
</Loggers>
</Configuration>
8.4、测试代码
package com.bestcxx.mavenstu.mavenssh.util;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@DirtiesContext
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring/applicationContext.xml"})
//@TransactionConfiguration(transactionManager = "defaultTransactionManager",defaultRollback=false)//事务管理
@Rollback(false)
public class LoggerDaoImplTest {
@Test
public void testAddLogger(){
// Logger logger = LogManager.getLogger(LoggerDaoImplTest.class);
Logger logger = LogManager.getLogger("databaseAppender");
logger.trace("trace level");
logger.debug("debug level");
logger.info("info level");
logger.warn("warn level");
logger.error("error level");
logger.fatal("fatal level");
}
}
效果
9、日志保存到数据库(必看)的最佳实践-log4j2的启动在spring加载配置文件之前
在上面的日志保存到数据库的例子中,由于测试的缘故,我们把日志的声明写在了方法体中,类似于下面的
@Test
public void testLog4j2(){
Logger logger = LogManager.getLogger(LogManager.ROOT_LOGGER_NAME);
但是在实际的运用中,我们总是习惯于把日志对象声明为所在类的全局变量,也就是下面这种
@Controller
@SuppressWarnings("serial")
public class PersonAction extends BaseAction<Person> {
private Logger logger=LogManager.getLogger(PersonAction.class);
但是以目前的项目配置会出问题,会报空指针,经排查是由于log4j2在这种情况下,使用spring加载的配置文件来获取参数的时候,获取的参数为null,更直白的说就是log4j2要连接数据库的时候,spring还没有加载或者没有加载完配置文件,所以需要对代码进行修改,改动也非常小,java获取spring中配置文件 的工具类还是可以留着的,这不过log4j2连接数据库的用户名、密码、url采取直接从配置文件读取了,绕过了spring。修改ConnectionFactory.java 为如下即可
package com.bestcxx.mavenstu.mavenssh.util;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnection;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.pool.impl.GenericObjectPool;
public class ConnectionFactory {
/*final private static String jdbcurl=CustomizedPropertyPlaceholderConfigurer.getContextProperty("jdbc.url");
final private static String jdbcusername=CustomizedPropertyPlaceholderConfigurer.getContextProperty("jdbc.username");
final private static String jdbcpassword=CustomizedPropertyPlaceholderConfigurer.getContextProperty("jdbc.password");
*/
private static interface Singleton {
final ConnectionFactory INSTANCE = new ConnectionFactory();
}
private final DataSource dataSource;
private static InputStream inStream = ConnectionFactory.class.getClassLoader().getResourceAsStream("config/jdbc.properties");
private static Properties prop = new Properties();
private ConnectionFactory() {
try {
prop.load(inStream);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String jdbcusername = prop.getProperty("jdbc.username");//数据库连接用户名
String jdbcpassword = prop.getProperty("jdbc.password");//数据库连接密码
String jdbcurl = prop.getProperty("jdbc.url");//数据库连接url
Properties properties = new Properties();
properties.setProperty("user", jdbcusername);
properties.setProperty("password", jdbcpassword); // or get properties from some configuration file
GenericObjectPool<PoolableConnection> pool = new GenericObjectPool<PoolableConnection>();
DriverManagerConnectionFactory connectionFactory = new DriverManagerConnectionFactory(
jdbcurl, properties
);
new PoolableConnectionFactory(
connectionFactory, pool, null, "SELECT 1", 3, false, false, Connection.TRANSACTION_READ_COMMITTED
);
this.dataSource = new PoolingDataSource(pool);
}
public static Connection getDatabaseConnection() throws SQLException {
return Singleton.INSTANCE.dataSource.getConnection();
}
}
jdbc.properties 的位置
config/jdbc.properties 作为常量其实可以保存到枚举类中
private static InputStream inStream = ConnectionFactory.class.getClassLoader().getResourceAsStream(EnumUtil.COMMON_DATABASE_PROPERTIES.toString());
完整的ssh代码请查看 GitHub - Bestcxy/SSH-ajax-axis2-maven-log4j-redis: spring+hibernate+struts2+ajax+axis2+log4j2+redis 组合起来的项目
如果你还没有加入github 请先阅读 Eclipse 使用 github_bestcxx的博客-CSDN博客
10、类中声明日志的最佳实践
一般声明一个静态、final日志对象
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
下民两种写法从输出来看没有区别
private Logger logger=LogManager.getLogger(类名.class);
private Logger logger=LogManager.getLogger(this.getClass());
和其他类目结合,请参考 Slf4j与log4j及log4j2的关系及使用方法_Andrew_Yuan的博客-CSDN博客
11、查看 Spring 日志最佳时间-关键是开启 debug模式
> 由于 Spring 代码的日志都是有一个 debug模式判断,所有配置为debug才可以看到 Spring日志
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="debug" monitorInterval="300"><!-- 每隔300秒重新读取配置文件,对web应用很实用 -->
<Appenders>
<!-- 日志输出格式 :<Console是控制台,<File是文件,<RollingRandomAccessFile按时间和文件大小生成多个日志,<JDBC是数据库,<Async异步 -->
<!-- Console 输出到控制台及格式 -->
<Console name="toConsole" target="SYSTEM_OUT">
<PatternLayout
pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %F %M %l- %-5level %logger{36} %msg%n" />
</Console>
</Appenders>
<Loggers><!-- 日志类别 -->
<!-- 定义根日志类别 查看level http://blog.csdn.net/techq/article/details/6636287 -->
<Root level="debug">
<AppenderRef ref="toConsole" />
</Root>
</Loggers>
</Configuration>