Java日志信息存库(log4j篇)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java日志信息存库(log4j篇)相关的知识,希望对你有一定的参考价值。

一、Log4j简介

        在一个完整的J2EE项目开发中,日志是一个非常重要的功能组成部分。它可以记录下系统所产生的所有行为,并按照某种规范表达出来。我们可以通过日志信息为系统进行排错,优化系统的性能,或者根据这些信息调整系统等行为。Log4j是Apache针对于日志信息处理的一个开源项目,其最大特点是通过一个配置文件就可以灵活地控制日志信息的输出方式(控制台、文件和数据库等)、日志输出格式及日志信息打印级别等,而不需要修改应用的代码。

二、编写背景

       作为一名程序猿在开发中总能遇到一些比较奇葩的需求,而这些需求对于身份低微的小编来说又不得不去尽力完成。在接触A公司项目之前,公司项目中使用到的日志信息基本都是写到对应文件中。而A公司客户觉得日志信息存在文件中不方便查看,需要把日志信息记录到数据库中,然后再做个界面供在页面上查询。说实话,日志写库这种低效率的事情我向来是不太赞同去做的。

三、编写目的

       怕年纪大了就会忘了,给自己留个回忆。

四、Java日志信息存库详细解决方案

1.开发环境说明

       Eclipse+Tomcat6+JDK1.6+Oracle+Log4j1.2

2.数据库表创建

            表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文件配置的要保持一致。

 

五、总结

  文中提到的Log4j日志信息存库功能开发仅是Log4j组件的皮毛而已,由于编者水平有限,在很多观点的阐述和代码的处理方式还有存在着很大的争议,望各位提出宝贵的意见和建议。

 



以上是关于Java日志信息存库(log4j篇)的主要内容,如果未能解决你的问题,请参考以下文章

Java优雅的记录日志:log4j实战篇

UTM篇&IPS ❀ 02. 防范Log4j远程代码执行漏洞攻击 ❀ 飞塔 (Fortinet) 防火墙

UTM篇&IPS ❀ 02. 防范Log4j远程代码执行漏洞攻击 ❀ 飞塔 (Fortinet) 防火墙

Java 使用Log4J进行日志操作

java中的日志组件-log4j

Java--log4j