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_);