log4j重写JDBCAppender 解决单引号问题

在看看实现的方式,只要在log4j.xml文件中加入如下配置:

Java代码   收藏代码
  1. <appender name="DB_INFO" class="org.apache.log4j.jdbc.JDBCAppender">  
  2.         <param name="Threshold" value="INFO"/>      
  3.         <param name="BufferSize" value="1"/>   
  4.         <!-- 本地 -->  
  5.          <param name="URL" value="jdbc:oracle:thin:@192.168.100.231:1522:mpptest"/>  
  6.         <param name="driver" value="oracle.jdbc.driver.OracleDriver"/>  
  7.         <param name="user" value="gmcc"/>  
  8.         <param name="password" value="skywin"/>  
  9.           
  10.          <!-- 生产机  
  11.         <param name="URL" value="jdbc:oracle:thin:@192.168.101.4:1521:gmcctes"/>  
  12.         <param name="driver" value="oracle.jdbc.driver.OracleDriver"/>  
  13.         <param name="user" value="gmcc"/>  
  14.         <param name="password" value="gmcc"/>  
  15.          -->  
  16.         <param name="sql" value="INSERT INTO RE_GLOBAL_LOG(currtime,currthread,currlevel,currcode,currmsg)    
  17. VALUES ('%d{yyyy-MM-dd HH:mm:ss}''%t''%p''%l''%m')"/>  
  18.         <filter class="org.apache.log4j.varia.LevelRangeFilter">  
  19.             <param name="levelMin" value="INFO" />  
  20.             <param name="levelMax" value="INFO" />         
  21.              <param name="AcceptOnMatch" value="true" />  
  22.         </filter>  
  23.     </appender>    

然后在代码中调用log.info("abcd");等就可以把信息写入数据库了;需要说明的是这样的配置在本地运行(如一般的写个main方法后直接运行)和在tomcat服务器中运行是可以的,是没有问题的,但是当其放到weblogic服务器上的时候,问题出现了,运行了半天日志的信息就是写不进去,而其他功能完全正常,那么问题出现在哪里呢?初步的猜想是执行的sql语句出了问题,没办法只好看看jdbcAppender的源代码,这里首先看看 execute(String sql)方法,在这里建议如果不熟悉的话,直接自己写一个类继承JDBCAppender,尽可能的重写它里面的所有方法即可,这里的重写直接调用父类的对应方法就可以了,不用写那么复杂。在重写的execute(String sql)中把要执行的sql语句打印出来,看了下,果然是这样,要执行的sql语句里面包含了单引号,而我们知道要插入数据库中单引号是要进行转义处理的,在这里出现单引号的是%t参数,也就是获得线程名的时候。问题到了这里,如果说就想为了实现功能,可以直接将得到的sql进行替换,即对所有的单引号进行转义就可以了!但是出于好奇心,我还是往下看下源代码,首先发现上述说的execute(String sql)是在一个flushBuffer()方法里面被执行的,flushBuffer()方法的代码如下

写道
public void flushBuffer()
{
this.removes.ensureCapacity(this.buffer.size());
Iterator i = this.buffer.iterator(); if (i.hasNext());
try {
LoggingEvent logEvent = (LoggingEvent)i.next();
String sql = getLogStatement(logEvent);
execute(sql);
this.removes.add(logEvent);
}
catch (SQLException e) {
while (true) { this.errorHandler.error("Failed to excute sql", e, 2);
}

this.buffer.removeAll(this.removes);

this.removes.clear();
}
}

在自己重写的flushBuffer方法中观察到sql语句(就是很无耻的在各个为位置打印出jdbcAppender的getsql方法)在 getLogStatement(logEvent)的前后发生了变化,到了这里问题已经很明显了 ,就是getLogStatement(logEvent)方法对sql进行了处理,那么到底进行了怎么样的处理呢,接着看这个方法的源代码,代码如下:

Java代码   收藏代码
  1. protected String getLogStatement(LoggingEvent event)  
  2.   {  
  3.     return super.getLayout().format(event);  
  4.   }  

代码在简单不过了,就一句话,就调用了一个logout的format方法,而现在的关键就是这个layout是哪里来的,通过追踪发现在jdbcAppender的setSql方法中对logout进行了赋值,代码如下:

Java代码   收藏代码
  1. public void setSql(String s)  
  2.   {  
  3.     this.sqlStatement = s;  
  4.     if (super.getLayout() == null) {  
  5.       super.setLayout(new PatternLayout(s));  
  6.     }  
  7.     else  
  8.       ((PatternLayout)super.getLayout()).setConversionPattern(s);  
  9.   }  

在这里意思就是说如果在log4j.xml文件中没有为jdbcAppender配置patterLayout那么会自动扔一个PatternLayout给jdbdAppender,好了,到了这里不用说接下来就看PatternLayout的format方法了,代码如下:

Java代码   收藏代码
  1. public String format(LoggingEvent event)  
  2.   {  
  3.     if (this.sbuf.capacity() > 1024)  
  4.       this.sbuf = new StringBuffer(256);  
  5.     else {  
  6.       this.sbuf.setLength(0);  
  7.     }  
  8.   
  9.     PatternConverter c = this.head;  
  10.   
  11.     while (c != null) {  
  12.       c.format(this.sbuf, event);  
  13.       c = c.next;  
  14.     }  
  15.     return this.sbuf.toString();  
  16.   }  

代码也不难,最核心的地方就是一个while循环,然后不断调用一个c.format方法就完事了,这下就要关注这个c是啥东西了,首先它就是this.head,而搜索下jdbdAppend的源代码,终于发现this.head是在哪里被赋值了,其实也是粗心了一点,如果刚刚在看setsql方法的时候细心的往下看就会发现在

Java代码   收藏代码
  1. super.setLayout(new PatternLayout(s));  

中PatterLayout的构造方法是带参数的,而现在看下这个带参数的方法做了怎么?看下代码

Java代码   收藏代码
  1. public PatternLayout(String pattern)  
  2.   {  
  3.     this.BUF_SIZE = 256;  
  4.     this.MAX_CAPACITY = 1024;  
  5.   
  6.     this.sbuf = new StringBuffer(256);  
  7.   
  8.     this.pattern = pattern;  
  9.     this.head = createPatternParser(pattern).parse();  
  10.   }  

呵呵,终于发现给this.head赋值的地方了,不用说 直接看createPatternParser(pattern).parse()方法,代码很长,如下:

Java代码   收藏代码
  1. public PatternConverter parse()  
  2.  {  
  3.    char c;  
  4.    this.i = 0;  
  5.    while (true) { while (true) { if (this.i >= this.patternLength) break label572;  
  6.        c = this.pattern.charAt(this.i++);  
  7.        switch (this.state)  
  8.        {  
  9.        case 0:  
  10.          if (this.i != this.patternLength) breakthis.currentLiteral.append(c);  
  11.        case 1:  
  12.        case 4:  
  13.        case 3:  
  14.        case 5:  
  15.        case 2: }  } if (c == '%')  
  16.      {  
  17.        switch (this.pattern.charAt(this.i))  
  18.        {  
  19.        case '%':  
  20.          this.currentLiteral.append(c);  
  21.          this.i += 1;  
  22.          break;  
  23.        case 'n':  
  24.          this.currentLiteral.append(Layout.LINE_SEP);  
  25.          this.i += 1;  
  26.          break;  
  27.        default:  
  28.          if (this.currentLiteral.length() != 0) {  
  29.            addToList(new LiteralPatternConverter(this.currentLiteral.toString()));  
  30.          }  
  31.   
  32.          this.currentLiteral.setLength(0);  
  33.          this.currentLiteral.append(c);  
  34.          this.state = 1;  
  35.          this.formattingInfo.reset(); continue;  
  36.   
  37.          this.currentLiteral.append(c);  
  38.   
  39.          continue;  
  40.   
  41.          this.currentLiteral.append(c);  
  42.          switch (c)  
  43.          {  
  44.          case '-':  
  45.            this.formattingInfo.leftAlign = true;  
  46.            break;  
  47.          case '.':  
  48.            this.state = 3;  
  49.            break;  
  50.          default:  
  51.            if ((c >= '0') && (c <= '9')) {  
  52.              this.formattingInfo.min = (c - '0');  
  53.              this.state = 4;  
  54.            }  
  55.            else {  
  56.              finalizeConverter(c);  
  57.   
  58.              continue;  
  59.   
  60.              this.currentLiteral.append(c);  
  61.              if ((c >= '0') && (c <= '9')) {  
  62.                this.formattingInfo.min = (this.formattingInfo.min * 10 + c - '0');  
  63.              } else if (c == '.') {  
  64.                this.state = 3;  
  65.              } else {  
  66.                finalizeConverter(c);  
  67.   
  68.                continue;  
  69.   
  70.                this.currentLiteral.append(c);  
  71.                if ((c >= '0') && (c <= '9')) {  
  72.                  this.formattingInfo.max = (c - '0');  
  73.                  this.state = 5;  
  74.                }  
  75.                else {  
  76.                  LogLog.error("Error occured in position " + this.i + ".\n Was expecting digit, instead got char \"" + c + "\".");  
  77.   
  78.                  this.state = 0;  
  79.   
  80.                  continue;  
  81.   
  82.                  this.currentLiteral.append(c);  
  83.                  if ((c >= '0') && (c <= '9')) {  
  84.                    this.formattingInfo.max = (this.formattingInfo.max * 10 + c - '0');  
  85.                  } else {  
  86.                    finalizeConverter(c);  
  87.                    this.state = 0; } } } }  
  88.          }  
  89.        }  
  90.      }  
  91.    }  
  92.    if (this.currentLiteral.length() != 0) {  
  93.      label572: addToList(new LiteralPatternConverter(this.currentLiteral.toString()));  
  94.    }  
  95.   
  96.    return this.head;  
  97.  }  

上面的代码是比较长,但是功能其实不难,大概的意思就是把log4j.xml中写到的sql语句,即"INSERT INTO RE_GLOBAL_LOG(currtime,currthread,currlevel,currcode,currmsg) VALUES ('%d{yyyy-MM-dd HH:mm:ss}', '%t', '%p', '%l', '%m')"中的t,p,l,m等等都搞成一个PatternConverter,并返回第一个PatternConverter,也就是说到底就是一个链表,而this.head是这个链表的头元素,哈哈,到了这里终于明白数据结构等那些基础知识的重要性了!到了这里,主要就是关注各个PatternConverter的format方法了 ,很显然是这些PatternConverter的format方法里面出现了单引号等特殊字符,最后发现当遇到sql中%t的时候被解成了BasicPatternConverter,代码如下

Java代码   收藏代码
  1. case 't':  
  2.       pc = new BasicPatternConverter(this.formattingInfo, 2001);  
  3.   
  4.       this.currentLiteral.setLength(0);  
  5.       break;  

再看2001到底干了啥,看下代码

Java代码   收藏代码
  1. case 2001:  
  2.        return event.getThreadName();  

很简单把,就是获得线程的名字,我的本意是在return event.getThreadName()返回前对单引号进行替换,但是发现BasicPatternConverter这个是私有的内部类(private),看来还是挺难搞的,看来没得搞了,只好重写event的getThreadName方法,就是自己写一个类扩展LoggingEvent,重写它的getThreadName方法,代码也不难了,在这里就简单的写下把:

Java代码   收藏代码
  1. public class BPSLoggingEvent extends LoggingEvent {  
  2.     private static final long serialVersionUID = -1405129465403337629L;  
  3.   
  4.     public BPSLoggingEvent(String fqnOfCategoryClass, Category logger, Priority level, Object message, Throwable throwable) {  
  5.         super(fqnOfCategoryClass, logger, level, message, throwable);  
  6.         // TODO Auto-generated constructor stub  
  7.     }  
  8.       
  9.       
  10.     public String getThreadName() {  
  11.         // TODO Auto-generated method stub  
  12.         String thrdName=super.getThreadName();  
  13.         if(thrdName.indexOf("'")!=-1){  
  14.             thrdName=thrdName.replaceAll("'""''");  
  15.         }  
  16.         return thrdName;  
  17.     }  
  18.   
  19.     public String getRenderedMessage() {  
  20.         String msg=super.getRenderedMessage();  
  21.         if(msg.indexOf("'")!=-1){  
  22.             msg=msg.replaceAll("'""''");  
  23.         }  
  24.         return msg;  
  25.     }  
  26.       
  27.       
  28.       
  29.   
  30. }  

好了,到这里大功告成了,就只剩下一小步了,那就是刚刚重写的方法如何被调用,因为按照类jdbdAppend的流程是不会执行我重写的getThreadName的?呵呵,在多写一个类,让它按照执行就是了,这里写的类就是要扩展JDBCAPPend了,覆盖里面的getLogStatement方法,代码也很少,大体如下:

写道
public class BPSJDBCAppender extends JDBCAppender {

protected String getLogStatement(LoggingEvent event) {
String fqnOfCategoryClass=event.fqnOfCategoryClass;
Category logger=Category.getRoot();
Priority level=event.level;
Object message=event.getMessage();
Throwable throwable=null;
BPSLoggingEvent bEvent=new BPSLoggingEvent(fqnOfCategoryClass,logger,level,message,throwable);
return super.getLogStatement(bEvent);
}

到了这里只需要把log4j.xml中的jdbdappender换成我们刚刚写的类就可以看,也就是BPSJDBCAppender!最终问题得到解决!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值