`

在LOG4J中把日志写入远程数据库

阅读更多
LOG4J强大的功能让我爱不释手,为了不用跑到机房看日志,我打算把日志写入远程数据库,这样操作起来就方便了,同时又可以按时间、按关键词搜索,一举两得。

LOG4J 提花了一个JDBCAppender的远程数据库输出方案,使用也很简单,在配置文件里写好驱动名、URL及远程数据库的登陆账号、密码,再加一个布局&SQL语句全部搞定,真当是方便极了。在我本机上测试没有问题,但欢天喜地移植到服务器上问题就来了,发现每次数据库连接特别慢,严重影响了程序的正常运行,这还得了,赶紧恢复到原来的状态,思索如何改进。

了解它的原理之后,问题的关键部分就很清楚了,JDBCAppender在向远程数据库写日志时,用的是短连接,虽然定义了一个BufferSize,但好像不起什么作用,这就相当于每次写日志都要重新建立一次数据连接,而建连接往往最耗时间的啦,能不能把我原来写的数据库连接池和LOG4J和JDBCAppender结合起来使用呢?

大的方向应该是没有问题,也搜索了一下别人实现的数据库连接池,但总觉得不是很满意,还是用自己的连接池放心。如何扩展原来的JDBCAppender、把数据库连接池传递进去,一开始没有搞明白,想了老半天,后来终于整明白了。我只需要继承原来的JDBCAppender,把getConnection()和 closeConnection()两个方法重写即可,其它的都不用变,示例如下:
package com.gftech.log4j;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;

import org.apache.log4j.jdbc.JDBCAppender;

import com.gftech.common.GFConn;
import com.gftech.common.GFDB;

public class JDBCExtAppender extends JDBCAppender ...{
    protected String driver;

    public static GFDB gfdb;

    private ArrayList<GFConn> tempList;

    public JDBCExtAppender() ...{
        super();
        tempList = new ArrayList<GFConn>();
    }

    /** *//**
     * Override this to return the connection to a pool, or to clean up the
     * resource.
     *
     * The default behavior holds a single connection open until the appender is
     * closed (typically when garbage collected).
     */
    protected void closeConnection(Connection con) ...{
        if (con != null && tempList != null) ...{
            for (int i = 0; i < tempList.size(); i++) ...{
                GFConn gfconn = tempList.get(i);
                if (con==gfconn.getConn()) ...{
                    gfconn.close();
                    tempList.remove(i);
//                    System.err.println("remove conn:"+con);
                    break;
                }
            }
        }
    }

    /** *//**
     * Override this to link with your connection pooling system.
     *
     * By default this creates a single connection which is held open until the
     * object is garbage collected.
     */
    protected Connection getConnection() throws SQLException ...{
        if (gfdb == null)
            gfdb = new GFDB("log4jDB", driver, databaseURL, databaseUser, databasePassword);

        if (gfdb != null) ...{
            GFConn gfconn = gfdb.getConn();
            if (gfconn != null) ...{
                connection = gfconn.getConn();
                tempList.add(gfconn);
            }
        }

        return connection;
    }

    public void setDriver(String driverClass) ...{
        driver = driverClass;

    }

}

虽然用到了数据库连接池,但我在实际测试中发现,在多线程应用程序中,很快就提示使用的连接无效,但该连接明明在刚开始还可以用,难道连接被主动关闭掉了? 既然用的是数据库连接池,我当然希望所有的连接在连接池内部进行动态管理,外面不需要干涉,这个现象说明JDBCAppender里面一定是显式关闭了连接.分析出原因后,很容易找到JDBCAppender里面有个close()方法,里面显式关闭了连接:
/** *//**
   * Closes the appender, flushing the buffer first then closing the default
   * connection if it is open.
   */
  public void close()
  ...{
    flushBuffer();

    try ...{
      if (connection != null && !connection.isClosed())
          connection.close();
    } catch (SQLException e) ...{
        errorHandler.error("Error closing connection", e, ErrorCode.GENERIC_FAILURE);
    }
    this.closed = true;
  }

为了保证连接不被关闭,让连接池自己管理,我们需要重写该方法,去掉连接关闭的代码,修改如下:
public void close() ...{
        flushBuffer();

        this.closed = true;
    }
   

再一次测试,发现连接已不会再断开,达到预期效果。同时又发现另外一个问题,就是堆栈信息无法写入到数据库当中,而ConsoleAppender、 FileAppender、SMTPAppender都可以显示异常时的详细堆栈信息,为何JDBCAppender不可以呢?通过查看源代码得知,其它 Appender在最后生成%m对应的信息时都做了一下判断,如果异常被忽略了,则把异常的堆栈信息重新添加进去,而JDBCAppender并没有做这一步,这是导致它与众不同的原因。搞清楚问题所在之后,我们重写getLogStatement()方法并且JDBCAppender里面也建议重写该方法,自己对信息做一下处理并为每句添加一个回车换行使信息看起来更清晰,如下所示:
public String getLogStatement(LoggingEvent event)...{
        StringBuffer sbuf=new StringBuffer();
        sbuf.append(layout.format(event));
        if (layout.ignoresThrowable()) ...{
            sbuf.delete(sbuf.length()-2,sbuf.length() );
            String[] s = event.getThrowableStrRep();
            if (s != null) ...{
                for (int j = 0; j < s.length; j++) ...{
                    sbuf.append("\r\n ");
                    sbuf.append(s[j]);
                }
            }
            sbuf.append("')");
        }
       
        return sbuf.toString() ;
    }



在配置文件中做如下设置:
#A5 send log info to remote mysql database
log4j.appender.A5 = com.gftech.log4j.JDBCExtAppender
log4j.appender.A5.Driver = com.mysql.jdbc.Driver
log4j.appender.A5.URL = jdbc:mysql://192.168.10.1:3306/log
log4j.appender.A5.User = root
log4j.appender.A5.Password = plus
log4j.appender.A5.layout = org.apache.log4j.PatternLayout
log4j.appender.A5.sql = INSERT INTO app_log(machine,occur_date,thread_name,cat,level,info) values('DP','%d{yyyy-MM-dd HH:mm:ss}','%t','%c','%p','%m')

指定LOG4J用自定义的扩展JDBC Appender,这样一来大大减少数据库连接建立的次数,提示程序的执行效率

说明:

也可用开源的一些数据库连接池技术,原理都大同小异,同样是修改这两个处理数据库连接的方法可以了
分享到:
评论
2 楼 zheng12tian 2012-07-03  
代码有全的不?
只贴一部分,,,,
1 楼 ajie1986 2010-04-27  
lz这种发现问题,解决问题,不断改进的做法和想法很赞

相关推荐

    mysql数据库my.cnf配置文件

    # 主线程中每秒会将重做日志缓冲写入磁盘的重做日志文件(REDO LOG)中。不论事务是否已经提交)默认的日志文件是ib_logfile0,ib_logfile1 # 1:当设为默认值1的时候,每次提交事务的时候,都会将log buffer刷写到日志...

    git-sqlite:用于将git log commit数据转换为sqlite的数据管道

    gitlog→sqlite数据管道 该数据管道从github仓库中获取最新更改,然后将其git log输出转换为json数据,然后将其加载到pandas中并导出到sqlite... 日志会保存在平面文件和数据库中,并且可以设置Airflow以写入远程日志

    数据库操作语句大全(sql)

    EXCEPT 运算符通过包括所有在 TABLE1 中但不在 TABLE2 中的行并消除所有重复行而派生出一个结果表。当 ALL 随 EXCEPT 一起使用时 (EXCEPT ALL),不消除重复行。 C: INTERSECT 运算符 INTERSECT 运算符通过只包括 ...

    Oracle9i的init.ora参数中文说明

    说明: 用于指定数据库为远程 PL/SQL 存储的过程处理被依赖对象的方式。如果设置为 TIMESTAMP, 只有在服务器与本地时间戳相匹配的情况下, 才能执行该过程。如果设置为 SIGNATURE, 在签名安全的情况下即可执行该过程。...

    Occursions:快速可自定义的时间序列Web数据库,用于存储大数据(如日志文件)-开源

    当每行写入磁盘时,事件会异步拖尾日志文件并为每个日志文件中的各行建立索引,因此您甚至不必在事件搜索到它后再等待一秒钟。 Occursions使用自定义磁盘支持的数据结构来创建和搜索其索引,因此在使用CPU,内存和...

    RTBH:在Linux上使用BIRD的ipv4和ipv6 RBL阻止

    我们将建立一个数据库,以阻止来自多个来源的“不良” IP: proftpd身份验证日志到公用存储F5 ASM日志远程发送到graylog2和elasticsearch 数百个Apache日志发送到单独的graylog2服务器场要求: 带有Apache和...

    ORACLE9i_优化设计与系统调整

    §2.4.1.5 在运行控制实用程序中设置日志参数 50 §2.4.1.6 理解监听日志中信息 50 §2.4.1.7 理解连接管理器信息 53 §2.4.2 跟踪文件( Trace File ) 53 §2.4.2.1 跟踪文件的命名: 54 §2.4.2.2 参数设置与初始化...

    C#程序开发范例宝典(第2版).part13

    实例119 在图片中写入文字 176 实例120 局部图片的复制 178 实例121 波形图的绘制 179 4.2 图形转换 180 实例122 BMP转换成JPG格式 181 实例123 JPG转换成BMP格式 182 实例124 位图转化为WMF格式 183 实例125...

    C#程序开发范例宝典(第2版).part08

    实例119 在图片中写入文字 176 实例120 局部图片的复制 178 实例121 波形图的绘制 179 4.2 图形转换 180 实例122 BMP转换成JPG格式 181 实例123 JPG转换成BMP格式 182 实例124 位图转化为WMF格式 183 实例125...

    C#程序开发范例宝典(第2版).part02

    实例119 在图片中写入文字 176 实例120 局部图片的复制 178 实例121 波形图的绘制 179 4.2 图形转换 180 实例122 BMP转换成JPG格式 181 实例123 JPG转换成BMP格式 182 实例124 位图转化为WMF格式 183 实例125...

    C#程序开发范例宝典(第2版).part12

    实例119 在图片中写入文字 176 实例120 局部图片的复制 178 实例121 波形图的绘制 179 4.2 图形转换 180 实例122 BMP转换成JPG格式 181 实例123 JPG转换成BMP格式 182 实例124 位图转化为WMF格式 183 实例125...

    IIS6.0 IIS,互联网信息服务

    四、在Vista系统中安装IIS7.0相对于早先的版本,IIS 7.0 带来了许多引人注目的新特色新功能,比如基于 Microsoft .NET Framework 的全局配置文件,可简单地通过文本编辑器或 Microsoft Visual Studio 编辑;...

Global site tag (gtag.js) - Google Analytics