好久没更新博客了,嘿嘿!主要也是因为这段时间比较忙,一直都忙于需求的理解,编码,测试.....反正一系列很操蛋的事情!当然忙的收获就是在强迫的环境中让你学更多的知识,这个我会在后面的文章中提及,今天就先从重写DBCAppender说起!先说下这个问题的背景,现在的项目中要求把日志信息写到文件的同时也把其写入数据库中,以便日后的备查,从而改变了日志文件只能通过简单的文件搜索命令(如在linux中的less命令等)来查找的状况,好了,可以说最高目的是很好的。在看看实现的方式,只要在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()方法的代码如下
写道
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进行了处理,那么到底进行了怎么样的处理呢,接着看这个方法的源代码,代码如下:
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方法,代码也很少,大体如下:
写道
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!最终问题得到解决!
分享到:
相关推荐
以JDBCAppender为例,我们需要在Log4j的配置文件(通常是log4j.properties或log4j.xml)中进行以下设置: ```properties # 配置JDBCAppender log4j.appender.jdbc=org.apache.log4j.jdbc.JDBCAppender 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.appender.DBAppender=org.apache.log4j.jdbc.JDBCAppender log4j.appender.DBAppender.URL=jdbc:mysql://localhost:3306/mydb log4j.appender.DBAppender.driver=com.mysql.jdbc.Driver log4j.appender....
这些级别可以自定义,但 Log4j 建议只使用四个级别:ERROR、WARN、INFO 和 DEBUG。 四、Appenders Appenders 是 Log4j 的输出目的地,用于将日志信息输出到不同的目的地。Log4j 提供了多种 Appenders,包括: 1. ...
- `org.apache.log4j.jdbc.JDBCAppender`:通过 JDBC 将日志记录到数据库。 4. **Layout 类型**: - `org.apache.log4j.PatternLayout`:自定义输出格式。 - `org.apache.log4j.HTMLLayout`:HTML 输出格式。 -...
log4j.appender.DB=org.apache.log4j.jdbc.JDBCAppender log4j.appender.DB.URL=jdbc:mysql://localhost:3306/mydatabase log4j.appender.DB.Driver=com.mysql.jdbc.Driver log4j.appender.DB.User=myuser log4...
- `log4j.appender.appender2=org.apache.log4j.jdbc.JDBCAppender`: 创建了一个 JDBCAppender 实例,用于将日志信息存储到数据库。 - `log4j.appender.appender2.driver=com.mysql.jdbc.Driver`: 指定了数据库...
总结来说,Log4j写入数据库的配置涉及到创建适应的数据库表结构、配置`log4j.properties`文件以指定数据库连接信息和日志格式,并通过`JDBCAppender`将日志数据持久化到数据库中。这样的配置有助于收集和分析大量...
《深入学习log4J》是一本专注于Java日志框架Log4J的专业书籍,旨在帮助开发者深入理解并熟练运用Log4J进行系统日志管理和分析。Log4J是Apache组织开发的一个开源日志记录工具,广泛应用于Java应用程序中,提供灵活的...
log4j.appender.db=org.apache.log4j.jdbc.JDBCAppender log4j.appender.db.URL=jdbc:mssql://localhost:1433/WdzLog log4j.appender.db.driver=com.microsoft.sqlserver.jdbc.SQLServerDriver log4j.appender.db....
log4j.appender.DB=org.apache.log4j.jdbc.JDBCAppender log4j.appender.DB.URL=jdbc:mysql://localhost:3306/testdb log4j.appender.DB.Driver=com.mysql.jdbc.Driver log4j.appender.DB.Username=root log4j....
3. **JDBC Appender**:Log4j提供了JDBCAppender类,它可以将日志事件转换为SQL语句并执行。你需要自定义SQL语句,通常是一个INSERT语句,用于向日志表中插入数据。 4. **数据库表设计**:创建一个合适的日志表结构...
本工程用于研究log4j日志输出目的地org.apache.log4j.jdbc.JDBCAppender的使用方法 本工程编码方式:UTF-8 本工程开发工具:MyEclipse 本工程需要执行的SQL语句: CREATE DATABASE `test`; CREATE TABLE `...
Log4j Appender 配置详解 Log4j 是一个 Java 语言下的日志记录工具库,它提供了灵活的日志记录机制,可以将日志信息写入到控制台、文件、数据库等多种目标中。在 Log4j 中,Appender 是一种输出目标,它负责将日志...
<appender name="DB" class="org.apache.log4j.jdbc.JDBCAppender"> <layout class="org.apache.log4j.PatternLayout"> ``` 在这个配置中,我们创建了一个名为"DB"的JDBCAppender,设置...
log4j.appender.db=org.apache.log4j.jdbc.JDBCAppender log4j.appender.db.URL=jdbc:mysql://localhost:3306/testdb log4j.appender.db.Driver=com.mysql.jdbc.Driver log4j.appender.db.User=root log4j....
Log4j支持多种Appender类型,包括但不限于FileAppender(文件)、ConsoleAppender(控制台)、JDBCAppender(数据库)。每种Appender都有其特定的配置选项,如日志文件路径、最大日志文件大小等。 Appender的**添加...
<appender name="DBAppender" class="org.apache.log4j.jdbc.JDBCAppender"> <param name="URL" value="jdbc:mysql://localhost:3306/logdb" /> <layout class="org.apache.log4j.PatternLayout"> ``` ...
log4j.appender.JDBC=org.apache.log4j.jdbc.JDBCAppender log4j.appender.JDBC.dataSource=com.example.DataSource log4j.appender.JDBC.sql=INSERT INTO logs (logger, level, message) VALUES (?, ?, ?) log4...