log4j重写JDBCAppender 解决单引号问题
来源:互联网 发布:嵌入式软件 大公司 编辑:程序博客网 时间:2024/06/05 11:14
在看看实现的方式,只要在log4j.xml文件中加入如下配置:
- <appender name="DB_INFO" class="org.apache.log4j.jdbc.JDBCAppender">
- <param name="Threshold" value="INFO"/>
- <param name="BufferSize" value="1"/>
- <!-- 本地 -->
- <param name="URL" value="jdbc:oracle:thin:@192.168.100.231:1522:mpptest"/>
- <param name="driver" value="oracle.jdbc.driver.OracleDriver"/>
- <param name="user" value="gmcc"/>
- <param name="password" value="skywin"/>
- <!-- 生产机
- <param name="URL" value="jdbc:oracle:thin:@192.168.101.4:1521:gmcctes"/>
- <param name="driver" value="oracle.jdbc.driver.OracleDriver"/>
- <param name="user" value="gmcc"/>
- <param name="password" value="gmcc"/>
- -->
- <param name="sql" value="INSERT INTO RE_GLOBAL_LOG(currtime,currthread,currlevel,currcode,currmsg)
- VALUES ('%d{yyyy-MM-dd HH:mm:ss}', '%t', '%p', '%l', '%m')"/>
- <filter class="org.apache.log4j.varia.LevelRangeFilter">
- <param name="levelMin" value="INFO" />
- <param name="levelMax" value="INFO" />
- <param name="AcceptOnMatch" value="true" />
- </filter>
- </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()方法的代码如下
{
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进行了处理,那么到底进行了怎么样的处理呢,接着看这个方法的源代码,代码如下:
- protected String getLogStatement(LoggingEvent event)
- {
- return super.getLayout().format(event);
- }
代码在简单不过了,就一句话,就调用了一个logout的format方法,而现在的关键就是这个layout是哪里来的,通过追踪发现在jdbcAppender的setSql方法中对logout进行了赋值,代码如下:
- public void setSql(String s)
- {
- this.sqlStatement = s;
- if (super.getLayout() == null) {
- super.setLayout(new PatternLayout(s));
- }
- else
- ((PatternLayout)super.getLayout()).setConversionPattern(s);
- }
在这里意思就是说如果在log4j.xml文件中没有为jdbcAppender配置patterLayout那么会自动扔一个PatternLayout给jdbdAppender,好了,到了这里不用说接下来就看PatternLayout的format方法了,代码如下:
- public String format(LoggingEvent event)
- {
- if (this.sbuf.capacity() > 1024)
- this.sbuf = new StringBuffer(256);
- else {
- this.sbuf.setLength(0);
- }
- PatternConverter c = this.head;
- while (c != null) {
- c.format(this.sbuf, event);
- c = c.next;
- }
- return this.sbuf.toString();
- }
代码也不难,最核心的地方就是一个while循环,然后不断调用一个c.format方法就完事了,这下就要关注这个c是啥东西了,首先它就是this.head,而搜索下jdbdAppend的源代码,终于发现this.head是在哪里被赋值了,其实也是粗心了一点,如果刚刚在看setsql方法的时候细心的往下看就会发现在
- super.setLayout(new PatternLayout(s));
中PatterLayout的构造方法是带参数的,而现在看下这个带参数的方法做了怎么?看下代码
- public PatternLayout(String pattern)
- {
- this.BUF_SIZE = 256;
- this.MAX_CAPACITY = 1024;
- this.sbuf = new StringBuffer(256);
- this.pattern = pattern;
- this.head = createPatternParser(pattern).parse();
- }
呵呵,终于发现给this.head赋值的地方了,不用说 直接看createPatternParser(pattern).parse()方法,代码很长,如下:
- public PatternConverter parse()
- {
- char c;
- this.i = 0;
- while (true) { while (true) { if (this.i >= this.patternLength) break label572;
- c = this.pattern.charAt(this.i++);
- switch (this.state)
- {
- case 0:
- if (this.i != this.patternLength) break; this.currentLiteral.append(c);
- case 1:
- case 4:
- case 3:
- case 5:
- case 2: } } if (c == '%')
- {
- switch (this.pattern.charAt(this.i))
- {
- case '%':
- this.currentLiteral.append(c);
- this.i += 1;
- break;
- case 'n':
- this.currentLiteral.append(Layout.LINE_SEP);
- this.i += 1;
- break;
- default:
- if (this.currentLiteral.length() != 0) {
- addToList(new LiteralPatternConverter(this.currentLiteral.toString()));
- }
- this.currentLiteral.setLength(0);
- this.currentLiteral.append(c);
- this.state = 1;
- this.formattingInfo.reset(); continue;
- this.currentLiteral.append(c);
- continue;
- this.currentLiteral.append(c);
- switch (c)
- {
- case '-':
- this.formattingInfo.leftAlign = true;
- break;
- case '.':
- this.state = 3;
- break;
- default:
- if ((c >= '0') && (c <= '9')) {
- this.formattingInfo.min = (c - '0');
- this.state = 4;
- }
- else {
- finalizeConverter(c);
- continue;
- this.currentLiteral.append(c);
- if ((c >= '0') && (c <= '9')) {
- this.formattingInfo.min = (this.formattingInfo.min * 10 + c - '0');
- } else if (c == '.') {
- this.state = 3;
- } else {
- finalizeConverter(c);
- continue;
- this.currentLiteral.append(c);
- if ((c >= '0') && (c <= '9')) {
- this.formattingInfo.max = (c - '0');
- this.state = 5;
- }
- else {
- LogLog.error("Error occured in position " + this.i + ".\n Was expecting digit, instead got char \"" + c + "\".");
- this.state = 0;
- continue;
- this.currentLiteral.append(c);
- if ((c >= '0') && (c <= '9')) {
- this.formattingInfo.max = (this.formattingInfo.max * 10 + c - '0');
- } else {
- finalizeConverter(c);
- this.state = 0; } } } }
- }
- }
- }
- }
- if (this.currentLiteral.length() != 0) {
- label572: addToList(new LiteralPatternConverter(this.currentLiteral.toString()));
- }
- return this.head;
- }
上面的代码是比较长,但是功能其实不难,大概的意思就是把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,代码如下
- case 't':
- pc = new BasicPatternConverter(this.formattingInfo, 2001);
- this.currentLiteral.setLength(0);
- break;
再看2001到底干了啥,看下代码
- case 2001:
- return event.getThreadName();
很简单把,就是获得线程的名字,我的本意是在return event.getThreadName()返回前对单引号进行替换,但是发现BasicPatternConverter这个是私有的内部类(private),看来还是挺难搞的,看来没得搞了,只好重写event的getThreadName方法,就是自己写一个类扩展LoggingEvent,重写它的getThreadName方法,代码也不难了,在这里就简单的写下把:
- public class BPSLoggingEvent extends LoggingEvent {
- private static final long serialVersionUID = -1405129465403337629L;
- public BPSLoggingEvent(String fqnOfCategoryClass, Category logger, Priority level, Object message, Throwable throwable) {
- super(fqnOfCategoryClass, logger, level, message, throwable);
- // TODO Auto-generated constructor stub
- }
- public String getThreadName() {
- // TODO Auto-generated method stub
- String thrdName=super.getThreadName();
- if(thrdName.indexOf("'")!=-1){
- thrdName=thrdName.replaceAll("'", "''");
- }
- return thrdName;
- }
- public String getRenderedMessage() {
- String msg=super.getRenderedMessage();
- if(msg.indexOf("'")!=-1){
- msg=msg.replaceAll("'", "''");
- }
- return msg;
- }
- }
好了,到这里大功告成了,就只剩下一小步了,那就是刚刚重写的方法如何被调用,因为按照类jdbdAppend的流程是不会执行我重写的getThreadName的?呵呵,在多写一个类,让它按照执行就是了,这里写的类就是要扩展JDBCAPPend了,覆盖里面的getLogStatement方法,代码也很少,大体如下:
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!最终问题得到解决!
- log4j重写JDBCAppender 解决单引号问题
- log4j JDBCAppender 消息中有单引号 BUG
- 解决单引号问题
- 关于在Log4j中使用JDBCAppender时出现死循环的问题
- 关于在Log4j中使用JDBCAppender时出现死循环的问题
- Log4j之DailyRollingFileAppender,SMTPAppender,JDBCAppender篇
- 利用log4j的JDBCAppender把日志写入数据库中
- 利用log4j的JDBCAppender把日志写入数据库中
- 使用cfqueryparam解决cfquery的单引号问题。
- JS中单引号双引号问题的解决
- log4j是什么,log4j解决了什么问题,log4j怎么用
- sql server存储过程中解决单引号的问题
- js中参数带单引号和双引号问题!已解决
- 解决 jquery find查询中含有单引号的问题
- 解决java中显示单引号和双引号的问题
- 解决win7不能识别cURL中的单引号问题
- URL重写解决Session不能使用问题
- rc资源文件重写问题的解决
- CSS 背景图片和背景颜色融合,以及多张背景图片融合显示
- [bigdata-63] ssh连接超时问题解决
- Guava官方文档-RateLimiter类
- 在线编辑器
- 深度学习检测方法梳理
- log4j重写JDBCAppender 解决单引号问题
- The SDK Build Tools revision (23.0.3) is too low for project ':app'. Minimum required is 25.0.0
- hzau1201——Friends(树形DP)
- YOLO: Real-Time Object Detection
- java字符串与二进制的相互转化
- 字符串匹配
- 从零开始学习音视频编程技术(二十) 录屏软件开发之录屏生成MP4
- 深度学习算法之YOLOv2
- No ip domain-lookup和Logging synchronous和Exec-timeout 0 0