1、Log4j簡介java
在一個完整的J2EE項目開發中,日誌是一個很是重要的功能組成部分。它能夠記錄下系統所產生的全部行爲,並按照某種規範表達出來。咱們能夠經過日誌信息爲系統進行排錯,優化系統的性能,或者根據這些信息調整系統等行爲。Log4j是Apache針對於日誌信息處理的一個開源項目,其最大特色是經過一個配置文件就能夠靈活地控制日誌信息的輸出方式(控制檯、文件和數據庫等)、日誌輸出格式及日誌信息打印級別等,而不須要修改應用的代碼。web
2、編寫背景spring
做爲一名程序猿在開發中總能遇到一些比較奇葩的需求,而這些需求對於身份低微的小編來講又不得不去盡力完成。在接觸A公司項目以前,公司項目中使用到的日誌信息基本都是寫到對應文件中。而A公司客戶以爲日誌信息存在文件中不方便查看,須要把日誌信息記錄到數據庫中,而後再作個界面供在頁面上查詢。說實話,日誌寫庫這種低效率的事情我向來是不太贊同去作的。sql
3、編寫目的數據庫
怕年紀大了就會忘了,給本身留個回憶。apache
4、Java日誌信息存庫詳細解決方案服務器
1.開發環境說明oracle
Eclipse+Tomcat6+JDK1.6+Oracle+Log4j1.2app
2.數據庫表建立ide
表1.日誌信息表(LOGGING_EVENT)
字段名 |
中文說明 |
類型 |
爲空 |
TIMESTMP |
記錄時間 |
NUMBER(20) |
N |
FORMATTED_MESSAGE |
格式化後的日誌信息 |
CLOB |
N |
LOGGER_NAME |
執行記錄請求的logger |
VARCHAR2(256) |
N |
LEVEL_STRING |
日誌級別 |
VARCHAR2(256) |
N |
THREAD_NAME |
日誌線程名 |
VARCHAR2(256) |
Y |
REFERENCE_FLAG |
包含標識:1-MDC或上下文屬性;2-異常;3-均包含 |
INTEGER |
Y |
ARG0 |
參數1 |
VARCHAR2(256) |
Y |
ARG1 |
參數2 |
VARCHAR2(256) |
Y |
ARG2 |
參數3 |
VARCHAR2(256) |
Y |
ARG3 |
參數4 |
VARCHAR2(256) |
Y |
CALLER_FILENAME |
文件名 |
VARCHAR2(256) |
N |
CALLER_CLASS |
類 |
VARCHAR2(256) |
N |
CALLER_METHOD |
方法名 |
VARCHAR2(256) |
N |
CALLER_LINE |
行號 |
VARCHAR2(256) |
N |
EVENT_ID |
主鍵ID |
NUMBER(10) |
N |
注: 建這個表主要是由於項目中同時使用了logback和log4j兩種組件記錄日誌信息,該表的表結構使用的是logback-classic-1.1.3.jar中提供的oracle.sql文件建立的。
3.實現方案
(1)直接配置log4j.properties文件
使用log4j原生態JDBCAppender最大的缺陷就是無法使用JNDI,還有比較棘手的就是無法把超過4000字符的日誌信息插入到數據庫表(即使使用CLOB類型來存儲亦如此)
#配置將INFO級別及以上級別的日誌存到數據庫中 log4j.rootLogger=INFO,db #使用log4j默認的JDBCAppender將日誌存到數據庫 log4j.appender.db = org.apache.log4j.jdbc.JDBCAppender #配置產生多少條日誌的時候再去插入到數據庫,默認爲1 log4j.appender.db.BufferSize=5 #配置數據庫驅動 log4j.appender.db.driver=oracle.jdbc.driver.OracleDriver #配置數據庫鏈接地址 log4j.appender.db.URL=jdbc:oracle:thin:@<ip>:<port>:<sid> #配置數據庫鏈接用戶名 log4j.appender.db.user=XXX #配置數據庫鏈接密碼 log4j.appender.db.password=XXX #配置日誌存數庫執行的sql,支持log4j格式化參數,LOGGING_EVENT_ID_SEQ是創建的索引,用於生成主鍵 log4j.appender.db.sql=insert into LOGGING_EVENT (timestmp,formatted_message,logger_name,level_string,thread_name,caller_filename,caller_class,caller_method,caller_line,event_id)
values((SYSDATE - TO_DATE('1970-1-1 8', 'YYYY-MM-DD HH24')) * 86400000 + TO_NUMBER(TO_CHAR(SYSTIMESTAMP(3), 'FF')),'%m','atsws','%p','%t','%F','%C','%M','%L',LOGGING_EVENT_ID_SEQ.nextval) #配置對應的layout log4j.appender.db.layout=org.apache.log4j.PatternLayout
(2)自定義JDBCAppender類
1)自定義Appender類(ATSDBAppender.java)
該ATSDBAppender是基於log4j-1.2.17.jar中原有的JDBCAppender改造而來,同時支持JDBC及JNDI鏈接數據庫操做,具備比較好的擴展性,且很好的解決了日誌信息超長沒法存庫的問題。
1 package com.hundsun.util.loggingevent; 2 3 import java.io.StringReader; 4 import java.sql.Connection; 5 import java.sql.DriverManager; 6 import java.sql.PreparedStatement; 7 import java.sql.SQLException; 8 import java.util.ArrayList; 9 import java.util.Iterator; 10 11 import javax.naming.InitialContext; 12 import javax.naming.NamingException; 13 import javax.sql.DataSource; 14 15 import org.apache.commons.lang3.StringUtils; 16 import org.apache.log4j.Appender; 17 import org.apache.log4j.AppenderSkeleton; 18 import org.apache.log4j.PatternLayout; 19 import org.apache.log4j.spi.ErrorCode; 20 import org.apache.log4j.spi.LocationInfo; 21 import org.apache.log4j.spi.LoggingEvent; 22 23 public class ATSDBAppender extends AppenderSkeleton implements Appender{ 24 protected String databaseURL; 25 protected String databaseUser; 26 protected String databasePassword; 27 protected Connection connection; 28 protected String sqlStatement; 29 protected int bufferSize = 1; 30 protected ArrayList<LoggingEvent> buffer; 31 protected ArrayList<LoggingEvent> removes; 32 private boolean locationInfo = false; 33 34 protected DataSource ds = null; 35 protected String jndiName;//JNDI名 36 37 public ATSDBAppender(){ 38 this.buffer = new ArrayList<LoggingEvent>(this.bufferSize); 39 this.removes = new ArrayList<LoggingEvent>(this.bufferSize); 40 } 41 @Override 42 public void close() { 43 flushBuffer(); 44 try{ 45 if((this.connection!=null)&&(!this.connection.isClosed())){ 46 this.connection.close(); 47 } 48 }catch(SQLException e){ 49 this.errorHandler.error("關閉鏈接失敗",e,0); 50 } 51 this.closed=true; 52 } 53 public void flushBuffer(){ 54 this.removes.ensureCapacity(this.buffer.size()); 55 for(Iterator<LoggingEvent> i=this.buffer.iterator();i.hasNext();){ 56 LoggingEvent logEvent=(LoggingEvent)i.next(); 57 try{ 58 String sql=" insert into logging_event (timestmp,formatted_message,logger_name,level_string,thread_name,caller_filename,caller_class,caller_method,caller_line,event_id) values(?,?,?,?,?,?,?,?,?,LOGGING_EVENT_ID_SEQ.nextval) "; 59 execute(sql, logEvent); 60 }catch(SQLException e){ 61 this.errorHandler.error("執行sql出錯", e, 2); 62 }finally{ 63 this.removes.add(logEvent); 64 } 65 } 66 this.buffer.removeAll(this.removes); 67 this.removes.clear(); 68 } 69 public void finalize(){ 70 close(); 71 } 72 @Override 73 public boolean requiresLayout() { 74 return true; 75 } 76 77 @Override 78 public synchronized void doAppend(LoggingEvent event) { 79 if(!StringUtils.isEmpty(name)&&"db".equals(name)&&closed){ 80 closed=false; 81 } 82 super.doAppend(event); 83 } 84 @Override 85 protected void append(LoggingEvent event) { 86 event.getTimeStamp(); 87 event.getThreadName(); 88 event.getMDCCopy(); 89 if(this.locationInfo){ 90 event.getLocationInformation(); 91 } 92 event.getRenderedMessage(); 93 event.getThrowableStrRep(); 94 this.buffer.add(event); 95 if(this.buffer.size()>=this.bufferSize) 96 flushBuffer(); 97 } 98 protected void execute(String sql,LoggingEvent logEvent) throws SQLException{ 99 Connection con=null; 100 PreparedStatement stmt=null; 101 try{ 102 con=getConnection(); 103 stmt=con.prepareStatement(sql); 104 stmt.setLong(1, logEvent.getTimeStamp()); 105 String largeText=logEvent.getRenderedMessage(); 106 StringReader reader=new StringReader(largeText); 107 stmt.setCharacterStream(2, reader,largeText==null?0:largeText.length()); 108 stmt.setString(3, "atsws"); 109 stmt.setString(4, logEvent.getLevel().toString()); 110 stmt.setString(5, logEvent.getThreadName()); 111 LocationInfo locationInfo = logEvent.getLocationInformation(); 112 stmt.setString(6, locationInfo.getFileName()); 113 stmt.setString(7, locationInfo.getClassName()); 114 stmt.setString(8, locationInfo.getMethodName()); 115 stmt.setString(9, locationInfo.getLineNumber()); 116 stmt.executeUpdate(); 117 }finally{ 118 if(stmt!=null){ 119 stmt.close(); 120 } 121 closeConnection(con); 122 } 123 } 124 protected void closeConnection(Connection con){ 125 try{ 126 if(connection!=null&&!connection.isClosed()) 127 connection.close(); 128 }catch(SQLException e){ 129 errorHandler.error("關閉鏈接失敗!",e,ErrorCode.GENERIC_FAILURE); 130 } 131 } 132 protected Connection getConnection() throws SQLException{ 133 if(!DriverManager.getDrivers().hasMoreElements()){ 134 setDriver("oracle.jdbc.driver.OracleDriver"); 135 } 136 if(databaseURL!=null&&databaseUser!=null&&databasePassword!=null){ 137 if(this.connection==null){ 138 this.connection=DriverManager.getConnection(this.databaseURL, this.databaseUser, this.databasePassword); 139 } 140 }else{ 141 while(ds==null){ 142 try{ 143 InitialContext context=new InitialContext(); 144 ds=(DataSource)context.lookup(jndiName); 145 }catch(NamingException e){ 146 this.errorHandler.error(e.getMessage()); 147 } 148 } 149 this.connection=ds.getConnection(); 150 connection.setAutoCommit(true); 151 } 152 return this.connection; 153 } 154 public boolean isLocationInfo() { 155 return locationInfo; 156 } 157 public void setLocationInfo(boolean flag) { 158 this.locationInfo = flag; 159 } 160 public void setJndiName(String jndiName) { 161 this.jndiName = jndiName; 162 } 163 public void setSql(String s) 164 { 165 this.sqlStatement = s; 166 if (getLayout() == null) { 167 setLayout(new PatternLayout(s)); 168 }else{ 169 ((PatternLayout)getLayout()).setConversionPattern(s); 170 } 171 } 172 173 public String getSql() { 174 return this.sqlStatement; 175 } 176 177 public void setUser(String user) { 178 this.databaseUser = user; 179 } 180 181 public void setURL(String url) { 182 this.databaseURL = url; 183 } 184 185 public void setPassword(String password) { 186 this.databasePassword = password; 187 } 188 189 public void setBufferSize(int newBufferSize) { 190 this.bufferSize = newBufferSize; 191 this.buffer.ensureCapacity(this.bufferSize); 192 this.removes.ensureCapacity(this.bufferSize); 193 } 194 195 public String getUser() { 196 return this.databaseUser; 197 } 198 199 public String getURL() { 200 return this.databaseURL; 201 } 202 203 public String getPassword() { 204 return this.databasePassword; 205 } 206 207 public int getBufferSize() { 208 return this.bufferSize; 209 } 210 211 public void setDriver(String driverClass) { 212 try { 213 Class.forName(driverClass); 214 } catch (Exception e) { 215 this.errorHandler.error("加載數據庫驅動失敗", e, 0); 216 } 217 } 218 }
2)配置自定義ATSDBAppender,將日誌信息存入Oracle數據庫
Ⅰ.使用JDBC方式配置log4j.properties文件
#配置INFO級別的日誌存入數據庫 log4j.rootLogger=INFO,db #使用自定義的ATSDBAppender類來將日誌信息存庫 log4j.appender.db=com.hundsun.util.loggingevent.ATSDBAppender #設置有多少條日誌數據時再進行存庫操做,默認爲1,即日誌信息每產生一條就新增進數據庫 log4j.appender.db.BufferSize=5 #配置數據庫驅動 log4j.appender.db.driver=oracle.jdbc.driver.OracleDriver #配置數據庫鏈接地址 log4j.appender.db.URL=jdbc:oracle:thin:@127.0.0.1:1521:orcl #配置數據庫鏈接用戶名 log4j.appender.db.user=tiger #配置數據庫鏈接密碼 log4j.appender.db.password=123456 #配置使用的Layout log4j.appender.db.layout=org.apache.log4j.PatternLayout
注:此處並無配置sql語句,主要是由於在log4j-1.2.17版本中sql語句處理timestmp字段值使用時間戳方式比較繁瑣,且日誌信息超4000字符時會報字段超長錯誤。
Ⅱ.使用JNDI方式配置
A.Tomcat安裝目錄/config/server.xml文件配置JNDI
<Context debug="0" docBase="E:\prj_abic\src\trunk\fundats\ats-modules-webservice\target\ats-modules-webservice" path="/webservice" reloadable="true"> <Resource auth="Container" driverClassName="oracle.jdbc.driver.OracleDriver" maxActive="30" maxIdle="30" name="jdbc/logging" password="123456" type="javax.sql.DataSource" url="jdbc:oracle:thin:@127.0.0.1:1521:orcl" username="tiger"/> </Context>
注:該配置爲Tomcat下配置JNDI鏈接比較經常使用的方式,若不太清楚這塊的配置規則可去查閱相關書籍,此時定義的jndi名稱爲"jdbc/logging".
B.applicationContext.xml數據源配置
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName"> <value>java:comp/env/jdbc/logging</value> </property>
</bean>
注:因爲使用的是Tomcat服務器,因此在配置數據源的時候得加上前綴[java:comp/env/],weblogic服務器則無需添加前綴,此時jndi名稱與前面server.xml文件配置的要保持一致。
C.log4j.properties文件配置
#配置將INFO級別的日誌信息存儲到數據庫中
log4j.rootLogger=INFO,db
#使用自定義的Appender實現數據庫的存庫操做
log4j.appender.db=com.hundsun.util.loggingevent.ATSDBAppender
#設置一次性將多少條日誌信息存入數據庫,默認爲1,但效率低
log4j.appender.db.BufferSize=5
#配置使用到的JNDI的名稱,該值與Tomcat服務器配置的JNDI名稱保持一致
log4j.appender.db.jndiName=java:comp/env/jdbc/logging
#配置日誌使用的Layout
log4j.appender.db.layout=org.apache.log4j.PatternLayout
注:因爲使用的是Tomcat服務器,因此jndiName的值需加上前綴[java:comp/env/],weblogic服務器則無需添加前綴,此時jndi名稱與前面server.xml文件配置的要保持一致。
5、總結
文中提到的Log4j日誌信息存庫功能開發僅是Log4j組件的皮毛而已,因爲編者水平有限,在不少觀點的闡述和代碼的處理方式還有存在着很大的爭議,望各位提出寶貴的意見和建議。