1.log4j配置
<appender name="DATABASE" class="com.sf.core.log4j.DataSourceAppender"> <filter class="org.apache.log4j.varia.LevelRangeFilter" > <param name="levelMin" value="error"/> </filter> </appender> <root> <level value="INFO"/> <appender-ref ref="CONSOLE"/> <appender-ref ref="DATABASE"/> </root>
2.自己定义Appender类,这里使用spring提供的JdbcTemplate批量插入日志.
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sf.core.log4j;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import org.apache.log4j.Level;
import org.apache.log4j.MDC;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.spi.LoggingEvent;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import com.sf.core.util.AppUtil;
import com.sf.core.util.UniqueIdUtil;
public class DataSourceAppender extends org.apache.log4j.AppenderSkeleton
implements org.apache.log4j.Appender {
protected String sqlStatement = "";
/**
* size of LoggingEvent buffer before writting to the database. Default is
* 1.
*/
protected int bufferSize = 1;
protected int maxBuffSize = 10000;
/**
* ArrayList holding the buffer of Logging Events.
*/
protected ArrayList buffer;
/**
* Helper object for clearing out the buffer
*/
protected ArrayList removes;
private boolean locationInfo = false;
public DataSourceAppender() {
super();
buffer = new ArrayList(bufferSize);
removes = new ArrayList(bufferSize);
}
/**
* Gets whether the location of the logging request call should be captured.
*
* @since 1.2.16
* @return the current value of the <b>LocationInfo</b> option.
*/
public boolean getLocationInfo() {
return locationInfo;
}
/**
* The <b>LocationInfo</b> option takes a boolean value. By default, it is
* set to false which means there will be no effort to extract the location
* information related to the event. As a result, the event that will be
* ultimately logged will likely to contain the wrong location information
* (if present in the log format).
* <p/>
* <p/>
* Location information extraction is comparatively very slow and should be
* avoided unless performance is not a concern.
* </p>
*
* @since 1.2.16
* @param flag
* true if location information should be extracted.
*/
public void setLocationInfo(final boolean flag) {
locationInfo = flag;
}
/**
* Adds the event to the buffer. When full the buffer is flushed.
*/
public void append(LoggingEvent event) {
event.getNDC();
event.getThreadName();
// Get a copy of this thread's MDC.
event.getMDCCopy();
if (locationInfo) {
event.getLocationInformation();
}
event.getRenderedMessage();
event.getThrowableStrRep();
buffer.add(event);
if (buffer.size() >= bufferSize)
flushBuffer();
}
/**
* By default getLogStatement sends the event to the required Layout object.
* The layout will format the given pattern into a workable SQL string.
*
* Overriding this provides direct access to the LoggingEvent when
* constructing the logging statement.
*
*/
protected String getLogStatement(LoggingEvent event) {
String statement = getLayout().format(event);
return statement;
}
/**
* Closes the appender, flushing the buffer first then closing the default
* connection if it is open.
*/
public void close() {
flushBuffer();
this.closed = true;
}
/**
* loops through the buffer of LoggingEvents, gets a sql string from
* getLogStatement() and sends it to execute(). Errors are sent to the
* errorHandler.
*
* If a statement fails the LoggingEvent stays in the buffer!
*/
@SuppressWarnings("unchecked")
public void flushBuffer() {
removes.ensureCapacity(buffer.size());
try {
if (AppUtil.getContext() != null && AppUtil.getServletContext()!=null) {
removes.addAll(buffer);
JdbcTemplate jdbcTemplate = (JdbcTemplate) AppUtil.getBean("jdbcTemplateLog4j");
try{
String sql = "INSERT INTO SYS_LOG4J_MSG("+
"ID_,"+
"CLAZZ," +
"PRIORITY," +
"MESSAGE," +
"LOG_DATE," +
"USER_ID," +
"USER_NAME," +
"USER_ACCOUNT) "
+"values(?," +
"?," +
"?," +
"?," +
"?," +
"?," +
"?," +
"?)";
jdbcTemplate.batchUpdate(sql,new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
LoggingEvent logEvent = (LoggingEvent) buffer.get(i);
String clazz = logEvent.getLocationInformation().getClassName();
String level = getLogLevelH(logEvent.getLevel());
String message = logEvent.getMessage().toString();
Timestamp time = new Timestamp(logEvent.getTimeStamp());
Long userId = (Long) MDC.get("current_user_id");
String userName = (String) MDC.get("current_user_name");
String userAccount = (String) MDC.get("current_user_account");
ps.setLong(1, UniqueIdUtil.genId());
ps.setString(2, clazz);
ps.setString(3, level);
ps.setString(4, message);
ps.setTimestamp(5,time);
ps.setLong(6, userId==null?0L:userId);
ps.setString(7, userName);
ps.setString(8, userAccount);
}
@Override
public int getBatchSize() {
return buffer.size();
}
});
}catch (Exception e) {
e.printStackTrace();
}
}else{
if(buffer.size()>maxBuffSize){
removes.addAll(buffer.subList(0, maxBuffSize/10));
}
}
} catch (Exception e) {
e.printStackTrace();
}
buffer.removeAll(removes);
removes.clear();
}
private String getLogLevelH(Level l){
String ls="";
if(l.toInt()==Level.ALL.toInt()){
ls="ALL";
}else if(l.toInt()==Level.DEBUG.toInt()){
ls="DEBUG";
}else if(l.toInt()==Level.ERROR.toInt()){
ls="ERROR";
}else if(l.toInt()==Level.FATAL.toInt()){
ls="FATAL";
}else if(l.toInt()==Level.INFO.toInt()){
ls="INFO";
}else if(l.toInt()==Level.OFF.toInt()){
ls="OFF";
}else if(l.toInt()==Level.TRACE.toInt()){
ls="TRACE";
}else if(l.toInt()==Level.WARN.toInt()){
ls="WARN";
}
return ls;
}
/** closes the appender before disposal */
public void finalize() {
close();
}
/**
* JDBCAppender requires a layout.
* */
public boolean requiresLayout() {
return true;
}
/**
*
*/
public void setSql(String s) {
sqlStatement = s;
if (getLayout() == null) {
this.setLayout(new PatternLayout(s));
} else {
((PatternLayout) getLayout()).setConversionPattern(s);
}
}
/**
* Returns pre-formated statement eg: insert into LogTable (msg) values
* ("%m")
*/
public String getSql() {
return sqlStatement;
}
public void setBufferSize(int newBufferSize) {
bufferSize = newBufferSize;
buffer.ensureCapacity(bufferSize);
removes.ensureCapacity(bufferSize);
}
public int getBufferSize() {
return bufferSize;
}
}
3.建立数据表
-- Create table
create table SYS_LOG4J_MSG
(
CLAZZ VARCHAR2(512),
PRIORITY VARCHAR2(64),
LOG_DATE TIMESTAMP(6),
MESSAGE VARCHAR2(2000),
USER_ID NUMBER(18),
USER_NAME VARCHAR2(256),
USER_ACCOUNT VARCHAR2(256),
ID_ NUMBER(18) not null
);
-- Add comments to the table
comment on table SYS_LOG4J_MSG
is 'log4j消息';
-- Add comments to the columns
comment on column SYS_LOG4J_MSG.CLAZZ
is '类型名';
comment on column SYS_LOG4J_MSG.PRIORITY
is '级别';
comment on column SYS_LOG4J_MSG.LOG_DATE
is '时间截';
comment on column SYS_LOG4J_MSG.MESSAGE
is '消息内容';
comment on column SYS_LOG4J_MSG.USER_ID
is '用户ID';
comment on column SYS_LOG4J_MSG.USER_NAME
is '用户名';
comment on column SYS_LOG4J_MSG.USER_ACCOUNT
is '账号';
comment on column SYS_LOG4J_MSG.ID_
is 'ID';
-- Create/Recreate primary, unique and foreign key constraints
alter table SYS_LOG4J_MSG
add constraint PK_LOG4J_MSG primary key (ID_);