`
zengbo0710
  • 浏览: 414535 次
社区版块
存档分类
最新评论

关于在Log4j中使用JDBCAppender时出现死循环的问题

阅读更多
APACHE的log4j是一个非常好用的日志记录管理工具<wbr></wbr>,可以实现到屏幕、文件、远程数据库、自动发送邮件等<wbr></wbr>,功能强大而又简单易用。
 
但是今天在使用经过扩展的JDBCAppender时却碰到一个莫<wbr></wbr>名其妙的问题,描述如下:
1.为了在日志向数据输出时每次都创建新的连接,在原来JDBCAppender的基础上进行扩展<wbr></wbr>,使用自己写的数据库连接池,主要是重写getConnectio<wbr></wbr>in()和closeConnection()两个方法<wbr></wbr>,未改动前的源代码如下:

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

import org.apache.log4j.jdbc.JDBCAppen<wbr></wbr>der;
import org.apache.log4j.spi.LoggingEve<wbr></wbr>nt;

import com.gftech.util.GFConn;
import com.gftech.util.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) {<script type="text/javascript"><!----></script>
   gfdb = new GFDB("db9", driver, databaseURL, databaseUser, databasePassword);
   
  }
  if (gfdb != null) {
   GFConn gfconn = gfdb.getConn();
   if (gfconn != null) {
    connection = gfconn.getConn();
    tempList.add(gfconn);
   } 
  }  
  return connection;
 }

 public void close() {
  flushBuffer();

  this.closed = true;
 }
 
 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() ;
 }

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

 }

数据库连接池的主要源代码如下:

public class GFDB {
 private String dbName;

 private ArrayList<GFConn> idleConnPool;// 空闲池

 private ArrayList<GFConn> usingConnPool;// 使用池

 private final int maxConns = 150;// 最大连接数目

 private final int maxUserCount = 149;// 每个连接允许的最大用户数目

 private final int timeout = 60000;// 连接的最大空闲时间

 private final int waitTime = 30000;// 用户的最大等待时间

 private String dbUrl = null;// 连接地址

 private String dbDriver = null;// 数据库驱动<script type="text/javascript"><!----></script>

 private String dbUser = null;// 登陆用户名

 private String dbPwd = null;// 登陆密码

 private ThreadGroup group = null;
 

 static Logger logger=Logger.getLogger(GFDB.class); 

 public GFDB(String dbName, String driver, String url, String user,
   String pwd) {

  this.dbName = dbName;
  dbDriver = driver;
  dbUrl = url;
  dbUser = user;
  dbPwd = pwd;
  if (driver != null && url != null && user != null && pwd != null)
   init();
 }

 private void init() {

  Connection conn = null;
  GFConn _conn = null;

  // init the connection pool
  idleConnPool = new ArrayList<GFConn>(0);
  usingConnPool = new ArrayList<GFConn>(0);

  conn = buildConn();
  if (conn != null) {
   _conn = new GFConn();
   _conn.setConn(conn);
   idleConnPool.add(_conn);
  } else {

   try {
    logger.error("\n\n和数据库的连接被重置,试图重新建立连接。。。");
    Thread.sleep(10000);
    init();
   } catch (InterruptedException ex) {
    ex.printStackTrace();
   }
  }

  if (group == null)
   group = new ThreadGroup("ThreadGroup");
  // if the manager thread is not active ,then run it
  if (!isActive(group, "manager"))
   manager();
 }

 /**
  * 连接远程数据库
  *
  * @return 连接成功返回TRUE
  */
 private Connection buildConn() {
  Connection conn = null;
  try {
   Class.forName(dbDriver);
   conn = DriverManager.getConnection (dbUrl, dbUser, dbPwd);<script type="text/javascript"><!----></script>
   if (conn != null) {

    String str = "建立和远程数据库" + dbUrl + "的连接:" + conn;
    logger.info(str);
   }

  } catch (SQLException e) {
   e.printStackTrace();

  } catch (ClassNotFoundException e) {
   e.printStackTrace();
  }
  return conn;
 }

 /**
  * 从连接池中取出一个可用的连接. 如果空闲池中有,则从空闲池中取,然后把userCount加一<wbr></wbr>,并把此连接
  * 移动使用池中;否则,判断使用池中是否有可用的连接(一个连接上 的使用用户数目还没有达到最大值即为可用的连接),如果有则在此
  * 连接分配给用户使用,如果使用池中没有可用的连接,并连接的总数目 还没有达到系统限制的最大值,则重新创建一个连接供用户使用,否则
  * 等待一段时间再查看是否有可用连接。
  *
  * @param timeout
  *            最大等待时间,超出此时间则返回NULL
  * @return
  */
 public synchronized GFConn getConn() {
  Connection conn = null;
  GFConn _conn = null;

  if (idleConnPool != null && idleConnPool.size() > 0) {
   // 这个地方可能会出数组越界异常. 估计还是同步问题
   _conn = (GFConn) idleConnPool.remove(0);
   // System.out.println("remove from idleConnPool:"+_conn);
   _conn.setLastAccessTime();
   _conn.changeUserCount(1);
   usingConnPool.add(_conn);
   return _conn;
  } else {
   if (usingConnPool != null) {
    if (usingConnPool.size() > 0) {
     // System.out.println ("usingConnPool.size():"+usingConnPool.size());
     for (int i = 0; i < usingConnPool.size(); i++) {
      _conn = (GFConn) usingConnPool.get(i);
      if (_conn.getUserCount() < maxUserCount) {
       _conn.changeUserCount(1<wbr></wbr>);
       return _conn;
      }

     }
     _conn = null;
    }

    // 如果目前没有可用的连接并且没有达到最大的连接数目,则重新为用户
    // 创建一个连接,否则等待一段时间看是否有连接被释放。
    if (usingConnPool.size() < maxConns) {
     conn = buildConn();
     if (conn != null) {<script type="text/javascript"><!----></script>
      _conn = new GFConn();
      _conn.setConn(conn);
      _conn.changeUserCount(1);
      usingConnPool.add(_conn);

      return _conn;
     }
    } else {
     try {
      Thread.sleep(waitTime);
      System.out.println("No usable connection,sleep...");
     } catch (InterruptedException e) {
      e.printStackTrace();
     }

     getConn();
    }
   }
  }
  return null;
 }

。。。。

主类里的调用代码:

PropertyConfigurator.configure(ConfParam.LOG4J_PROP_CONF);

logger.info("load dict");

问题出来了,在程序启动后,只要一执行完logger.info("load dict");这一句,系统就不停的向数据发起连接,至到资源耗尽为止,百思不得其解,程序原来也是一直是正常的<wbr></wbr>,不管是向文件输出还是向远程数据库输出日志信息都没有问题<wbr></wbr>,现在为何会限入死循环呢?

仔细分析源代码可以发现,程序在执行logger.info("load dict");后,为了按照LOG4J。properties中定义的数据库输出执行,log4j接口会自<wbr></wbr>动调用JDBCExtAppender当中的getConnect<wbr></wbr>ion()方法,在此方法中才会创建数据库实例并创建数据库连接<wbr></wbr>。在此方法中执行了这么一句

gfdb = new GFDB("db9", driver, databaseURL, databaseUser, databasePassword);

我在调试中发现,只要执行到这一句之后就开始陷入死循环<wbr></wbr>,程序不往下执行获取连接那一步,那说明问题是出在new GFDB()这个地方,可是奇怪的是我以前是正常的呀<wbr></wbr>,并且如果不起用JDBCExtAppender也不会出现这个的<wbr></wbr>问题。再跳到GFDB类里分析,构造函数里面调用了一个init<wbr></wbr>()方法,而init()里面又调用了buildConn(<wbr></wbr>)方法,问题就表现在buildConn()里面。

经过研究发现,在创建连接成功后我调用logger.info()输出了一个创建成功的提示,见上面红色代码,把它屏蔽结果就好<wbr></wbr>了。再仔细琢磨这个方法,又对照执行时打印出来的异常信息是个死循<wbr></wbr>环的引用恍然大悟,死循环就出现在这里。

原因很简单,我在创建连接成功后,调用了logger。info<wbr></wbr>()方法,而一调用此方法logger为了把日志输出到数据库它就<wbr></wbr>立即又去调用JDBCExtAppender当中的getConn<script type="text/javascript"><!----></script><wbr></wbr>ection()方法,而此时GFDB的实例还没有被创建成功<wbr></wbr>,所以gfdb=null,于是该方法又去调用new GFDB去创建连接,创建成功后又会调用logger。info<wbr></wbr>()。。。

如此反复,就彻底进入了死循环的怪圈,至到系统资源耗尽<wbr></wbr>。解决的办法很简单,就是在数据库连接池类里面不要用logger<wbr></wbr>来输出日志就行了,全部改成System.out.println()输出,其实原来我就是这样,所以一直挺正常。前几天为了把输出<wbr></wbr>统一起来,也为了能够记录数据库创建的信息,就把标准输出改成了l<wbr></wbr>ogger输出,导致了现在这一情况的出现。不过有了这样的教训<wbr></wbr>,也有了一个教训,不然自己引用自己,象小猫玩自己抓自己尾巴的游<wbr></wbr>戏,总也抓不住,累都累死。  



分享到:
评论

相关推荐

    log4j日志写入数据库实例

    以JDBCAppender为例,我们需要在Log4j的配置文件(通常是log4j.properties或log4j.xml)中进行以下设置: ```properties # 配置JDBCAppender log4j.appender.jdbc=org.apache.log4j.jdbc.JDBCAppender log4j....

    log4j使用与java中log4j记录日志如何写入数据库

    本文将详细介绍如何在Java中使用Log4j来记录日志,并将其写入数据库。 首先,我们需要了解Log4j的基本结构。一个简单的Log4j项目通常包含以下几个部分: 1. **配置文件**:Log4j的配置文件通常是`log4j.properties...

    log4j使用笔记

    1. **引入 log4j 库**:首先需要在项目中添加 log4j 的 JAR 包(例如 `log4j-xxx.jar`)。 2. **配置 log4j**: - 配置文件可以是 `log4j.properties` 或 `log4j.xml`,通常放在项目的根目录或类路径下。 - 可以...

    log4j与log4j.properties的配置.doc

    1. 在应用程序中使用 Log4j,需要将 Log4j 库文件(log4j-*.jar)添加到 CLASSPATH 变量中。 2. 新建一个配置文件 log4j.properties,放于 bin 文件下。 二、log4j.properties 配置文件 log4j.properties 配置文件...

    log4j日志驱动包

    log4j.appender.DATABASE=org.apache.log4j.jdbc.JDBCAppender log4j.appender.DATABASE.URL=jdbc:mysql://localhost:3306/test log4j.appender.DATABASE.driver=com.mysql.jdbc.Driver log4j.appender.DATABASE....

    log4j添加日志到数据库和文件中

    1. **配置文件**:在Log4j中,通常通过配置文件(通常是log4j.properties或log4j.xml)来设置日志的行为。对于文件日志,我们需要配置一个FileAppender,指定日志文件的路径和文件名。例如: ``` log4j.appender....

    Log4j 日志信息存储到数据库中

    ### Log4j 日志信息存储到数据库中的配置与使用 #### 概述 在软件开发过程中,日志记录是至关重要的环节之一。它不仅能够帮助开发者追踪程序运行时的状态,还可以在系统出现故障时提供诊断信息。Apache Log4j 是一...

    log4j写入数据库配置

    Log4j是一个广泛使用的日志记录框架,它允许开发者在应用程序中轻松地记录各种日志信息。对于大型系统和分布式环境来说,将日志写入数据库可以提供更强大的查询、分析和故障排查能力。本文将详细阐述如何配置Log4j以...

    深入学习log4J

    《深入学习log4J》是一本专注于Java日志...通过学习,读者不仅可以掌握Log4J的基本操作,还能理解如何在实际项目中有效地应用Log4J,提升系统的可维护性和稳定性。对于Java开发者来说,这是一份非常有价值的参考资料。

    Log4j写入数据库详解

    在Log4j中,通过`JDBCAppender`组件可以轻松实现将日志信息写入数据库的功能。`JDBCAppender`利用JDBC技术将日志消息异步写入数据库表中。 ##### 1. 实现步骤: **步骤一:添加依赖** 确保项目中有log4j和commons...

    log4j常用Appender配置

    在 Log4j 中,Appender 是一种输出目标,它负责将日志信息写入到指定的目标中。本文将对 Log4j 中的常用 Appender 配置进行详细介绍。 WriterAppender WriterAppender 是 Log4j 中的一种基本 Appender,它可以将...

    使用log4j 记录日志到数据库

    这篇博客“使用log4j记录日志到数据库”将介绍如何配置和使用Log4j,以便将日志信息存储到数据库中,而非传统的文本文件。数据库存储的日志便于进行结构化查询,有助于进行长期的数据分析和管理。 首先,理解Log4j...

    log4j输出日志到数据库表中

    **标题解析:** "log4j输出日志到数据库表中" 指的是使用Log4j这个流行的Java日志框架,将日志记录存储在数据库的特定表中,而不是默认的文本文件或控制台。这通常是出于日志管理、分析和长期存储的需求。 **描述...

    Log4j手册

    对于遇到的任何问题,Log4j社区和论坛也是一个宝贵的资源库,其中包含了大量关于常见问题和最佳实践的信息。 #### 日志乱码的解决 日志乱码通常是由于字符编码不匹配造成的。解决这一问题的方法是在配置文件中明确...

    log4j 配置mysql 数据库 demo

    在代码中使用Log4j进行日志记录,例如: ```java import org.apache.log4j.Logger; public class TestLogging { private static final Logger logger = Logger.getLogger(TestLogging.class); public ...

    如何借助log4j把日志写入数据库中

    然后,在Java代码中,我们可以创建并使用Logger对象,Log4j会自动将日志信息写入数据库。例如: ```java import org.apache.log4j.Logger; public class LogDemo { private static final Logger logger = Logger....

    log4jToJDBCAppender.zip

    本工程用于研究log4j日志输出目的地org.apache.log4j.jdbc.JDBCAppender的使用方法 本工程编码方式:UTF-8 本工程开发工具:MyEclipse 本工程需要执行的SQL语句: CREATE DATABASE `test`; CREATE TABLE `...

    log4j 数据库,邮件,html 等配置

    Log4j允许我们直接将日志信息存储到数据库中,这对于需要长期保存和分析大量日志数据的系统来说尤其有用。首先,你需要配置一个Appender(输出端),例如使用JDBCAppender。在配置文件中,定义数据库连接参数,包括...

Global site tag (gtag.js) - Google Analytics