zoukankan      html  css  js  c++  java
  • 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 + ".  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!最终问题得到解决!

  • 相关阅读:
    14_部署LNMP环境、构建LNMP平台、地址重写
    13_搭建Nginx服务器、配置网页认证、基于域名的虚拟主机、ssl虚拟主机
    12_rsync+SSH同步
    11_DNS子域授权、分离解析、缓存DNS服务器
    10_自定义yum仓库、源码编译安装
    09_parted分区工具、交换分区、链路聚合
    08_简单MariaDB数据库的管理
    bzoj1396
    bzoj4154
    bzoj3489
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13317754.html
Copyright © 2011-2022 走看看