Log4j中添加自定义的日志级别


Log4j自带了trace,debug,info.warn,error,fatal几个日志级别。

由于项目需要拆分业务和系统日志,而我又想偷懒,所以决定在log4j日志框架上增加自定义的日志级别专门用于业务日志输出。需要增加的日志级别定为audit,在log4j所有自带日志级别之上。

Log4j官方推荐的增加自定义的日志级别的方法是“继承org.apache.log4j.Level类”,具体的可以参考log4j中的示例XLevel。由于系统开发时不直接使用Log4j,而是通过slf4j调用log4j,官方推荐的方法行不通,所以决定修改log4j和slf4j相关源代码。

Log4j源代码修改点如下:

org.apache.log4j.Logger添加audit方法

public void audit(Object message) {
	if (repository.isDisabled(Level.AUDIT_INT)) {
		return;
	}

	if (Level.AUDIT.isGreaterOrEqual(this.getEffectiveLevel())) {
		forcedLog(FQCN, Level.AUDIT, message, null);
	}
}

org.apache.log4j.Priority类添加AUDIT_INT属性

public final static int AUDIT_INT = 60000; //日志级别大于log4j包含的所有日志级别,便于后期通过Threshold属性过滤日志

org.apache.log4j.Level类添加AUDIT相关属性和方法

public static final Level AUDIT = new Level(AUDIT_INT, "AUDIT", 0);public static Level toLevel(int val, Level defaultLevel) {
	switch(val) {
		case ALL_INT: return ALL;
		case AUDIT_INT: return Level.AUDIT;
		case DEBUG_INT: return Level.DEBUG;
		case INFO_INT: return Level.INFO;
		case WARN_INT: return Level.WARN;
		case ERROR_INT: return Level.ERROR;
		case FATAL_INT: return Level.FATAL;
		case OFF_INT: return OFF;
		case TRACE_INT: return Level.TRACE;
		default: return defaultLevel;
	}
}

至此Log4j修改完成,下面修改slf4j相关源代码。slf4j需要修改slf4j-api和slf4j-log4j12两个包 继续阅读

安装SVNManager注意事项


按照手册一路安装下来,最后碰到两个问题,第一个问题是SVN路径中包含中文,在设置用户和组权限的时候,中文全部乱码,编程\234?\344?这样子的形式。解决这个问题之后,在用户和组的权限管理时碰到了第二个问题,选择了中文目录后,跳转页面出现错误,显示拼接的svn路径在file前多了”\代码,导致svn命令不能正确识别该路径。

第一个问题,网上很多说是编码的问题,我修改为GBK,GB2312都未解决,最后通过分析代码的方式发现,在/usr/share/pear/VersionControl/SVN.php中672行执行exec()函数获得返回结果的时候就已经乱码了,而这个函数是php的,现在本机设置的语言环境是zh_CN.UTF-8,php相应的编码也都设置为UTF-8,apache默认编码也已经设置为UTF-8。

最后通过在将672行的代码修改为exec(“LANG=zh_CN.UTF-8; {$this->prepend_cmd}$cmd 2>&1”, $out, $ret_var);问题解决。

现在不太清楚,为何本地locale已经设置为LANG=zh_CN.UTF-8的情况下,exec命令仍然需要明确指定语言环境。

第二个问题,发现是svnmanager-1.08\svnmanager\RepositoryModule.php代码的Bug,如果svn路径中包含了空格,那么在245行做替换的时候,会在头加上”\,从而导致拼接的svn path错误,这个Bug没有细查了,暂时把svn目录的空格都去掉了。

如果出现PHP Fatal error:  Class ‘PEAR_ErrorStack’ not found错误
可以降版本
pear uninstall VersionControl_SVN-0.5.0卸掉0.5.0
pear install VersionControl_SVN-0.4.0重装0.4.0就没报这个错了

以上是安装SVNManager获得的一些经验,希望对看客有用。另:SVNManager最终配置成功的环境全部是UTF-8编码环境,包括Mysql数据库。

FWK005 parse may not be called while parsing.


最近在使用javax.xml.parsers.DocumentBuilder解析xml文件的时候偶尔会出错:

org.xml.sax.SAXException: FWK005 parse may not be called while parsing.
        at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(DOMParser.java:263)
        at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(DocumentBuilderImpl.java:284)
        at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:208)
...

跟踪了一下代码,发现这个异常是在com.sun.org.apache.xerces.internal.parsers.DTDConfiguration.parse(DTDConfiguration.java:546)抛出来的。该段代码结构如下:

if(fParseInProgress) {
    throw new XNIException("FWK005 parse may not be called while parsing.");
}

fParseInProgress = true;

// 解析xml文件

finally {
    fParseInProgress = false;
}

从程序逻辑来看,如果当前DocumentBuilder对象正在转换文档,此时再次请求转换文档,那么直接抛出XNIException(“FWK005 parse may not be called while parsing.”);异常。

这个问题也比较好解决,一种是对转换xml文档的方法,增加synchronized关键字,这样子不会有两个线程同时访问方法。

还有一种方法是创建一个DocumentBuilder类型的ThreadLocal变量,这样子每个线程都拥有自己的DocumentBuilder对象,能够同时转换多个xml文件。代码如下:

private static ThreadLocal docBuildeIns = new ThreadLocal() {
    protected DocumentBuilder initialValue() {
        try {
            return DocumentBuilderFactory.newInstance().newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            String msg = "DocumentBuilder 对象初始化失败!";
            log.error(msg, e);
            throw new IllegalStateException(msg, e);
        }
    }
};

解析xml文件时的调用方法:

docBuildIns.get().parse(File);

get()方法返回此线程局部变量的当前线程副本中的值。如果变量没有用于当前线程的值,则先将其初始化为调用 initialValue() 方法返回的值。

跟踪 C3p0 连接池连接泄漏之参数优化与日志分析


上一篇:跟踪 C3p0 连接池连接泄漏 我们有谈到c3p0的debugUnreturnedConnectionStackTraces、unreturnedConnectionTimeout参数的说明,此篇日志进一步介绍应用这两个参数后的一些经验。

首先,为了跟踪问题,debugUnreturnedConnectionStackTraces参数肯定是设置为true的。下面主要说明unreturnedConnectionTimeout参数的设置,unreturnedConnectionTimeout参数是在连接被应用程序checkout后指定时间内未checkin则由连接缓冲池执行kill操作,同时打印堆栈跟踪信息。在我的应用里,maxIdleTime的设置是120秒,所以,我把unreturnedConnectionTimeout设置成150秒,如果达到最大存活时间后,连接还是不能被连接缓冲池正常关闭,那么肯定出现了连接泄漏,此时,再过30秒后,由连接缓冲池主动执行kill。

通过以上设置后,确实收获了一些成果,通过分析日志,找到了连接泄漏问题代码。异常信息如下:

2011-04-06 15:49:42,599 INFO : com.mchange.v2.resourcepool.BasicResourcePool.removeResource(BasicResourcePool.java:1395) – Logging the stack trace by which the overdue resource was checked-out.
java.lang.Exception: DEBUG ONLY: Overdue resource check-out stack trace.
at com.mchange.v2.resourcepool.BasicResourcePool.checkoutResource(BasicResourcePool.java:506)
at com.mchange.v2.c3p0.impl.C3P0PooledConnectionPool.checkoutPooledConnection(C3P0PooledConnectionPool.java:525)
at com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource.getConnection(AbstractPoolBackedDataSource.java:128)
at org.springframework.orm.hibernate3.LocalDataSourceConnectionProvider.getConnection(LocalDataSourceConnectionProvider.java:82)
at org.hibernate.jdbc.ConnectionManager.openConnection(ConnectionManager.java:423)
at org.hibernate.jdbc.ConnectionManager.getConnection(ConnectionManager.java:144)
at org.hibernate.jdbc.AbstractBatcher.prepareQueryStatement(AbstractBatcher.java:139)
at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1547)
at org.hibernate.loader.Loader.doQuery(Loader.java:673)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:236)
at org.hibernate.loader.Loader.doList(Loader.java:2220)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2104)
at org.hibernate.loader.Loader.list(Loader.java:2099)
at org.hibernate.loader.custom.CustomLoader.list(CustomLoader.java:289)
at org.hibernate.impl.SessionImpl.listCustomQuery(SessionImpl.java:1695)
at org.hibernate.impl.AbstractSessionImpl.list(AbstractSessionImpl.java:142)
at org.hibernate.impl.SQLQueryImpl.list(SQLQueryImpl.java:152)
at com.leo.dao.XXXDAO.queryXXX(XXXDAO.java:20)
at jsp_servlet._keyareas._country.__taskcdb_add._jspService(__taskcdb_add.java:195)
at weblogic.servlet.jsp.JspBase.service(JspBase.java:34)
at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:227)
at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:125)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:300)
at weblogic.servlet.internal.ServletStubImpl.onAddToMapException(ServletStubImpl.java:416)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:326)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:183)
at weblogic.servlet.internal.RequestDispatcherImpl.invokeServlet(RequestDispatcherImpl.java:526)
at weblogic.servlet.internal.RequestDispatcherImpl.forward(RequestDispatcherImpl.java:253)

通过堆栈信息,很容易就确认了问题点。这个问题点是由于DAO使用不规范导致的,XXXDAO继承了HibernateDaoSupport,在方法中使用了getHibernateTemplate().getSessionFactory().openSession()获得session,然后通过session.createSQLQuery(),随后却并没有执行session.close()。

关于日志大小:在目前maxIdleTime=120,maxPoolSize=18,minPoolSize=5,idleConnectionTestPeriod=30的情况下,日志PatternLayout %d %-5p: %l – %m%n,一天的日志记录情况大约是12M,在硬盘空间允许的情况下建议按天存储日志。

使用JRockit JVM运行eclipse


来源:Running Eclipse on JRockit JVM

Oracle收购Sun之后,曾有媒体爆出Oracle考虑整合Sun JVM和JRockit JVM,要知道JRockit JVM也不是Oracle自己开发的,来自于之前的BEA公司。其实作为开发人员,真心希望两个JVM整合,以提供更好的性能。最近就在折腾把eclipse以及tomcat的开发环境从Sun JVM切换到JRockit JVM。

虽然JRockit JVM是着眼与服务器端的高性能JVM,但是也能运行客户端程序,像eclipse就能在JRockit JVM上很好的运行。

一、最简单的配置方法

-showsplash
org.eclipse.platform
-vm
/opt/Oracle/jrockit-jdk1.6.0_22-R28.1.1-4.0.1/bin/java

二、指定堆内存大小

-showsplash
org.eclipse.platform
-vm
/opt/Oracle/jrockit-jdk1.6.0_22-R28.1.1-4.0.1/bin/java
-vmargs -Xms384m -Xmx384m

三、配置deterministic GC,提高响应速度

-showsplash
org.eclipse.platform
-vm
/opt/Oracle/jrockit-jdk1.6.0_22-R28.1.1-4.0.1/bin/java
-vmargs
-Xms384m
-Xmx384m
-XgcPrio:deterministic
-XpauseTarget:20
-XXcompactratio:1

安装完成后,从主观感受上来看,eclipse响应速度确实快了不少。客观上,使用384内存出来的效果和Sun JVM服务器模式运行512内存出来的效果一样,也就是说相比调整后Sun JVM(Eclipse JVM 性能调优),内存占用有所降低。

此外,JRockit JVM还提供了Mission Control for eclipse插件,可以在eclipse通过Mission Control perspective视图分析JVM工作情况。这个插件的安装方法也很简单。找到安装目录下的missioncontrol目录,将里面的featrues和plugins文件夹复制到eclipse中即可。

对于tomcat,我们在Server Runtime Environment中选择jrockit jvm即可实现使用jrockit jvm运行tomcat,启动速度那是飕飕的!!!有木有!!!

资源:
Oracle JRockit Online Documentation

跟踪 c3p0 连接池连接泄漏


最近的项目碰到了连接泄漏的问题。 项目用的是Spring + Hibernate + c3p0,具体表现为,系统运行一段时间后,用户打开登录页后,执行登录无响应,查看后台日志,提示Could not open Connection。将c3p0日志级别调成debug之后,发现c3p0报告连接数已经达到设置的上限。 之前检查连接泄漏时,使用的是极低效的办法,目测代码,所以问题解决起来颇费周折,效果还不好。 今天查了一下c3p0官方手册,c3p0官方提供了两个参数,能够很方便的检查没有正确返回连接池的连接。

debugUnreturnedConnectionStackTraces
默认为false,如果此参数设置为true,当所有连接用完的时候,会以堆栈信息显示哪些代码使用了连接。由于跟踪连接状态会产生额外的消耗,如果程序稳定,应该将此参数恢复为默认值false。

unreturnedConnectionTimeout
设置连接被checkout后,经过多长时间还未返还连接池,则连接缓冲池直接Kill该连接,此时,结合debugUnretrunedConnectionStackTraces,我们可以知道是什么程序持有了连接,并且没有返回。这个方法很暴力,有可能造成应用程序不稳定,所以,如果真的出现了连接泄漏,一时半会又找不到问题点所在,可以暂时使用这个方法让应用程序跑起来。

参考:c3p0官方手册

解决JasperReport 导出 Html 时图片显示问题


JasperReport导出为Html时,图片显示不正常的问题,从项目一开始就困扰着我,今天终于解决了。太爽了!o(∩∩)o…

首先自己犯了一个错误,Html报表的图片输出没有使用net.sf.jasperreports.j2ee.servlets.ImageServlet,导致新打开的报表总是使用的之前报表的图片,且第一次输出报表时,图片不加载。

JasperReport导出Html报表的正确配置应该是

  1. 在web.xml配置net.sf.jaasperreports.j2ee.servlets.ImageServlet
  2. 设置JRHtmlExporterParameter.IMAGES_DIR_NAME,用于存放图片
  3. 导出报表时设置JRHtmlExporterParameter.IMAGES_URI变量,内容和web.xml中配置的ImageServlet的url-pattern一致
  4. 在Session中设置print对象
  5. 然后就可以执行导出了

下面是代码

String jasperFilePath = “WEB-INF/reports/test.jasper”;
// 获得报表模板文件对象
File reportFile = getApplicationContext().getResource(jasperFilePath).getFile();

JasperPrint print = JasperFillManager.fillReport(reportFile.getAbsolutePath(), map, dataSource.getConnection());

JRHtmlExporter exporter = new JRHtmlExporter();
// 组装图片请求URI,增加time参数,防止同一个报表的图片缓存。
String imageURI = request.getContextPath() + “/service/reportImages?time=” + System.currentTimeMillis() + “&image=” ;

exporter.setParameter(JRHtmlExporterParameter.IMAGES_URI, imageURI);
request.getSession().setAttribute(ImageServlet.DEFAULT_JASPER_PRINT_SESSION_ATTRIBUTE, print);
exporter.setParameter(JRHtmlExporterParameter.IS_USING_IMAGES_TO_ALIGN, Boolean.TRUE);
exporter.setParameter(JRExporterParameter. JASPER_PRINT, print);

exporter.setParameter(JRExporterParameter. OUTPUT_WRITER, response.getWriter());
exporter.setParameter(JRHtmlExporterParameter.FLUSH_OUTPUT, Boolean.TRUE);

exporter.exportReport();

参考:Chart caching issues with JasperReports web output? A solution!

Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”.


今天碰到了一个很头疼的问题,自定义的一个XML文档,在eclipse中做测试的时候能够正常解析,但是一旦部署到服务器上就会出现下面的错误:

[Fatal Error] :1:1476: Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”.
net.sf.json.JSONException: nu.xom.ParsingException: Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”. at line 1, column 1476
at net.sf.json.xml.XMLSerializer.read(XMLSerializer.java:331)
at net.sf.json.xml.XMLSerializer.FromStream(XMLSerializer.java:391)
at net.sf.json.xml.XMLSerializer.FromFile(XMLSerializer.java:355)
……
Caused by: nu.xom.ParsingException: Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”. at line 1, column 1476
at nu.xom.Builder.build(Unknown Source)
at nu.xom.Builder.build(Unknown Source)
at net.sf.json.xml.XMLSerializer.read(XMLSerializer.java:309)
… 62 more
Caused by: org.xml.sax.SAXParsingException: Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”.

从错误提示来看,是有标签没有结束,清空文件内容,一点点的添加,逐步排查,发现所有的标签都正常结束了阿。按照异常提示,一层层阅读源代码,从json-lib.jar到xom.jar,最后恍然大悟,应该直接拿

org.xml.sax.SAXParsingException: Element type “nodeName” must be followed by either attribute specifications, “>” or “/>”.

作为搜索条件,果不其然,使用这个关键字搜索的结果比net.sf.json.JSONException和nu.xom.ParsingException的结果多多了。

最后在关于dom4j解析编码的问题,org.xml.sax.SAXParseException: Invalid byte 1 of 1-byte UTF-8 sequence找到了关联答案,在二楼有人说

UTF-8编码中中文解析有问题
将编码格式改成“GB2312”后就可以正常解析了。<?xml version=”1.0″ encoding=”GB2312″?>

马上联想,这么奇怪的问题是不是也是编码问题导致的呢?最后把encoding从UTF-8修改为GB18030,问题解决。

疑问:为什么在eclipse中测试的时候没有出现问题,部署到tomcat或者weblogic都会出现这样的问题呢?

问题的根本原因找到了: 继续阅读

Spring 3.0配置多个DispatcherServlet的方法及注意事项


项目中需要同时用到两个视图解析器,一个报表的,一个jsp的。这就产生了标题所述的需求。通过阅读Spring Framework参考手册以及示例,解决了这个问题,中间也走了不少弯路,碰到了不少问题,特记录下来。

配置多个DispatcherServlet有多种方法,一种是在DispatcherServlet中直接指定此DispatcherServlet对应的配置文件。例如:

<servlet>
<servlet-name>app</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/app-config.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>app</servlet-name>
<url-pattern>/app/*</url-pattern>
</servlet-mapping>

但是这种配置方法存在一个问题:由于DispatcherServlet对应的Controller需要访问Service并且使用dataSource,而DispatcherServlet之间的上下文是分离的,势必会出现多个dataSource的情况,降低了数据库连接使用的效率。

后来找到了第二种方法,目前正在使用的:DispatcherServlet不指定具体的配置文件,并且DispatcherServlet只初始化自己需要使用到的Controller类,然后由org.springframework.web.context.ContextLoaderListener初始化除Controller以外的全部对象。这样子多个dataSource的问题解决了。配置如下: 继续阅读

Weblogic 解析JSP出现This attribute is not recognized.错误


最近碰到一个奇怪的问题,在tomcat中能够正常显示的页面,跑到weblogic中解析的时候出错,提示This attribute is not recognized.

发现weblogic对嵌套标签的使用要求更加严格。对于在标签属性中嵌套jspscriptlet代码时,引号必须单、双引号配对使用。

例如:

<c:out value=”<%=node.findAttribute(“name”) %>”/>代码在tomcat中能够正常解析,但是在weblogic中就会出错。

正确的写法应该是:<c:out value='<%=node.findAttribute(“name”) %>’/>,在外面使用单引号

出现这样的情况,主要还是开发的时候为了便利,使用tomcat,在服务器上做每日集成的时候也没有使用了tomcat,但是客户部署时使用的是weblogic,由于开发环境和最终环境不一致,但是问题直到部署时才发现。为了避免这样的问题再次出现,至少需要在每日集成时使用最终部署相同的环境,尽早暴露问题。