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

利用log4j的JDBCAppender把日志写入数据库中 log4j提供了多种Appender,通过这些Appender可以把log4j产生的日志写到数据库中,把所用的日志集中起来管理,能够进行日志的分析和集中监控,提升系统的可维护性和系统的可用性,下面是我利用derby嵌入式数据库,写入数据库的配置:在log4j文件中,增加关于JDB 阅读详情
APACHE的log4j是一个非常好用的日志记录管理工具,可以实现到屏幕、文件、远程数据库、自动发送邮件等,功能强大而又简单易用。
 
但是今天在使用经过扩展的JDBCAppender时却碰到一个莫名其妙的问题,描述如下:
1.为了在日志向数据输出时每次都创建新的连接,在原来JDBCAppender的基础上进行扩展,使用自己写的数据库连接池,主要是重写getConnectioin()和closeConnection()两个方法,未改动前的源代码如下:

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

import org.apache.log4j.jdbc.JDBCAppender;
import org.apache.log4j.spi.LoggingEvent;

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) {
   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;// 数据库驱动

 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);
   if (conn != null) {

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

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

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

 /**
  * 从连接池中取出一个可用的连接. 如果空闲池中有,则从空闲池中取,然后把userCount加一,并把此连接
  * 移动使用池中;否则,判断使用池中是否有可用的连接(一个连接上 的使用用户数目还没有达到最大值即为可用的连接),如果有则在此
  * 连接分配给用户使用,如果使用池中没有可用的连接,并连接的总数目 还没有达到系统限制的最大值,则重新创建一个连接供用户使用,否则
  * 等待一段时间再查看是否有可用连接。
  *
  * @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);
       return _conn;
      }

     }
     _conn = null;
    }

    // 如果目前没有可用的连接并且没有达到最大的连接数目,则重新为用户
    // 创建一个连接,否则等待一段时间看是否有连接被释放。
    if (usingConnPool.size() < maxConns) {
     conn = buildConn();
     if (conn != null) {
      _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");这一句,系统就不停的向数据发起连接,至到资源耗尽为止,百思不得其解,程序原来也是一直是正常的,不管是向文件输出还是向远程数据库输出日志信息都没有问题,现在为何会限入死循环呢?

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

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

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

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

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

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

MTK手机传感器驱动开发避坑指南:从SCP代码大小限制到Overlay加载流程详解 本文详细解析了MTK平台传感器驱动开发中的常见问题,包括SCP代码大小限制、Overlay加载流程及客制化传感器集成。通过实战案例和优化策略,帮助开发者高效解决MTK传感器驱动开发中的技术难题,提升开发效率。 阅读详情

相关推荐

解决RocketMQ中@Bean配置DefaultMQProducer时的MQClientException问题

本文针对Spring Boot整合RocketMQ时,因在@Bean配置中手动调用DefaultMQProducer的start()方法而引发的MQClientException问题,深入剖析了其根本原因在于生命周期管理冲突。文章提供了简洁有效的解决方案——移除手动启动,让Spring容器自动管理,并给出了完整的配置示例与生产环境最佳实践。

rnn9storyteller的博客 688

logback不输出至文件_Logback日志使用详解

Logback是由log4j创始人设计的一个开源日志组件。概述Logback建立于三个主要类之上:日志记录器(Logger),输出端(Appender)和日志格式化器(Layout)。这三种组件协同工作,使开发者可以按照消息类型和级别来记录消息,还可以在程序运行期内控制消息的输出格式和输出目的地。1.日志记录器(Logger):控制要输出哪些日志记录语句,对日志信息进行级别限制。2.输出端(App...

weixin_39728909的博客 2808

开源收银系统_大型收银系统源码_OctShop

开源收银系统正成为大型连锁企业数字化转型的关键工具。相比传统闭源系统,开源方案具有成本低(年均降本60%+)、定制灵活(支持多业态协同)、自主可控等优势,能有效解决高并发、多门店数据同步等痛点。核心价值在于:1)分布式架构适配规模化扩张;2)模块化设计实现个性化开发;3)开放接口打通ERP/CRM等系统。典型案例显示,连锁商超通过源码二次开发实现库存实时同步,补货效率提升50%;餐饮集团统一多业态收银流程后会员复购率增长35%。选型需关注源码成熟度、技术兼容性和安全合规,建议采用"开源+服务商&q

OctShop全栈工程师 938

Log日志死循环

Log日志死循环

u013803262的专栏 3447

DFT知识点扫盲——DFT scan chain

实际在项目中synccell一般直接上ULVT,既然是SDF,就是带Scan接口的DFF,SN (set negtive)是低置位,RP(reset positive)是高复位,既没有SN也没有RP的就是无复位寄存器。1:定义:满足可测试性设计(DFT),将设计中所有的触发器连接到一条或者若干条链上,称为scan chain,将一个复杂的时序电路转换为简单的组合电路进行测试,这一步是在逻辑综合(DC)中实现。当SE=1当SI=1时,电路进入scan状态,当SI=0时,电路工作在normal状态。

笨蛋大乌龟的博客 4933

使用下一代的嵌入式IDE-PlatformIO 教程

1.生成工程 1.1 打开STM32CubeMX: 选择你的MCU,这里我用的是STM32F103RC RCC中开启外部HSE时钟,外部时钟比HSI更稳定些 开启DEBUG 4线 打开串口1用于打印调试信息,波特率115200,校验位0,停止位1 打开串口2用于MODBUS协议,波特率9600,校验位0,停止位1 接入RT-thread:参考官方文档 基于 CubeMX 移植 RT-Thread Nano 根据官方文档,需要1.取消生成HardFault_Handler、PendSV_Handler、Sy

jiladahe1997的博客 3万+

Log4j2 JDBCAppender的使用

一.缘由 最近打算重新整理一下项目的日志系统,将系统日志和业务日志区分开,然后采用文件或是数据库来存储日志,于是上网查了一下log4j的相关配置,并将系统日志写入了数据库,但是发生了一个bug: 当写入的信息里存在单引号时,数据库就会报错ORA-00917: 缺失逗号 查了一些资料,发现log4j已经很古老了,它对SQL基本没有做任何处理就执行存储操作,这样SQL里可能存在的一些特殊字符并没

z69183787的专栏 7304

3.5.3以太网ETH TC8-PMA物理层测试

英文原版-OA_Automotive_Ethernet_ECU_TestSpecification_Layer_1_100BASE-T1_v3.0.pdf。PMA测试时各个测试项目所进入的测试模式不同,如TX_01对应测试模式1;TX_04对应测试模式5;例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。TX_07对应测试模式5;物理层测试主要分为两部分:IOP测试(包括链接时间,信号质量,线路诊断),PMA测试。

qq_28664971的博客 7693

【踩坑专栏】无法创建appender: Could not create an Appender of type [net.logstash.logback.appender...

发生这种错误是由于缺少logstash相关依赖,加入依赖即可: <!-- logback --> <dependency> <groupId>net.logstash.logback</groupId> <artifactId>logstash-logback-encoder</artifactId> <version>6

不想睡觉的橘子君的博客 1万+

9 HAL库驱动框架简述(STM32HAL库)

HAL库外设的使用步骤总结如下:1、定义并填充xxx外设句柄结构体。2、如果遵循HAL库规范,通过HAL_xxx_MspInit()函数,实现外设底层资源的初始化,包括但不限于GPIO、时钟、DMA、中断等资源的初始化。3、调用HAL库的对应外设初始化函数,形如:HAL_xxx_Init()。4、初始化完成,开始使用外设。5、使用方法具体查看对应外设的HAL库驱动包中的说明:补充:HAL库使用主线外设初始化外设使用。

qq_25218501的博客 1979

DBC中如何实现报文传输类型属性的配置

AUTOSAR中CAN报文的传输类型主要有三种,周期型,事件型和混合型。周期型:按固定时间间隔自动发送,无需额外的触发条件。事件型:当信号值变化或满足内部条件时立即发送。混合型:默认按周期发送,但事件触发时可提前发送。

sunchengl的博客 303

log4j之重写JDBCAppender

好久没更新博客了,嘿嘿!主要也是因为这段时间比较忙,一直都忙于需求的理解,编码,测试.....反正一系列很操蛋的事情!当然忙的收获就是在强迫的环境中让你学更多的知识,这个我会在后面的文章中提及,今天就先从重写DBCAppender说起!先说下这个问题的背景,现在的项目中要求把日志信息写到文件的同时也把其写入数据库中,以便日后的备查,从而改变了日志文件只能通过简单的文件搜索命令(如在linux中...

qinjingkai的专栏 445

2023年全国青少年信息素养大赛C++编程初中组决赛真题+答案解析

2023年全国青少年信息素养大赛C++编程初中组决赛真题+答案解析

伶俐角少儿编程 818

log4j

ITeye上一篇很详细的介绍log4j的文章: http://www.iteye.com/topic/378077 log4j.properties模板: log4j.rootLogger=info,fileLog  log4j.appender.fileLog = org.apache.log4j.FileAppender log4j.appender.fileLog.File =

nimeijian的专栏 633

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

将Logger的数据导入到数据库详解

昨天公司说要在系统中添加日志查询统计功能,刚开始第一个念头就是用IO流读取日志文件,然后持久化,最后在系统中查询。结果看了一些LOG4J的说明文档后发现,原来LOG4J本身就有将日志信息存入数据库功能,这样就大大节省了开发的时间。现在把方法记录下来,希望对有需要的朋友有所帮助。 一、前提条件       系统必须是使用LOG4J进行日志管理,否则方法无效。       系统必须包含commo

qq_25843323的博客 3万+

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

解决log4net error:“Could not create Appender”

&lt;appender name="uiLogAppender" type="MainAppGree.Log.UiLogAppender,MainAppGree"&gt; &lt;layout type="log4net.Layout.PatternLayout"&gt; &lt;conversionPattern value="%date{HH:mm:ss,fff} .

ZHENG 8727

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Log4j的AsyncAppender能否提升性能?

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;近日在写多线程方面的东西,需要对多线程进行日志监控就重新翻出了log4j,看到了log4j下居然还有一个AsyncAppender,异步记日志?觉得挺不错,网上搜了一下也有一些讨论,JavaEye上也有很多讨论。但是这个AsyncAppender到底能否提升日志性能呢?我们还是先看看log4j本身文档里的性能测试说明吧,详细测...

Java企业应用 1230

log4j 滚动日志 及 实现操作日志

log4j.xml 用户操作日志 系统滚动日志 Druid 连接池配置

TT_DUL的博客 6929

logback异步日志AsyncAppender配置

日志记录会消耗性能,但当出现问题的时候,日志又能够帮助我们快速解决问题。那么如何提高打日志的性能呢?在使用logback的时候,推荐使用AsyncAppender异步记录日志。 1)logback设置AsyncAppender 要注意AsyncAppender异步记录ILoggingEvents,它仅充当事件分派器,因此必须引用另一个appender才能执行任何有用的操作。 <con...

zhibin的程序日记 2万+

使用log4j失误导致系统假死,记录一下

最近有一个用了挺长时间的老项目出现了故障,系统卡死了,一个后台线程停止了运行。 项目的功能是一个接收短信的功能,在一个后台线程中一直循环,每隔5秒中去读取一下是否有短信,不管是否有短信都会往一个监控平台写一个状态,表示当前系统在运行。结果监控平台发现系统隔了好几天都没有收到状态了。不清楚具体原因。 咋一看,程序也在运行中,没有发现什么异常。于是使用jstack打印了所有的线程栈

rumlee的专栏 2770

jstack简单使用,定位死循环、线程阻塞、死锁等问题

阅读目录 死循环 Object.wait()情况 死锁 等待IO 其它 当我们运行java程序时,发现程序不动,但又不知道是哪里出问题时,可以使用JDK自带的jstack工具去定位; 废话不说,直接上例子吧,在window平台上的; 死循环 写个死循环的程序如下: package concurrency; public class Test { pub...

xiongyouqiang的博客 488
上一篇: 关于JAVA中线程同步的性能测试
下一篇: 单源点最短路径Dijkstra算法的JAVA实现
sinboy
博客等级 码龄25年 239粉丝 65原创
评论 7
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值