Java日誌信息存庫(log4j篇)

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組件的皮毛而已,因爲編者水平有限,在不少觀點的闡述和代碼的處理方式還有存在着很大的爭議,望各位提出寶貴的意見和建議。

相關文章
相關標籤/搜索