始祖QSystem.out.println(...)
Z么还是要一再提到它Q毕竟我们的习惯不是那么Ҏ改变的,而且System.outQ别忘了q有System.errQ是一个直接和控制台打交道?a target=_blank>PrintStream对象Q是l端昄的基Q高U的Logger要在l端昄日志内容Q就必然会用到这个。一个小规模的程序调试,恰当C用System.out.println(...)我认Z然是一U最方便最有效的方法,所以我们仍把它攑֜最开始,以示不能“数典忘祖” :)
不常用的关键字:assert
assert对多Ch来讲可能q比较陌生,它也是一个调试工P好像?a target=_blank>J2SE 1.4才加q来的东东,一U常见的用法是:
assert (布尔表达?;
当表辑ּ为true时没有Q何反映,如果为falsepȝ会抛出一?a target=_blank>AssertionError。如果你要用assertQ在~译时必d?#8220;-source 1.4”的选项Q在q行时则要加?#8220;-ea”选项?br>
后生可畏QJava Logging API一?/font>
System.out.println(...)对于较高要求的用hq远不够的,它还不是一个日志系l,一个比较完善的日志pȝ应当有输出媒介、优先、格式化、日志过滤、日志管理、参数配|等功能。伴随J2SE 1.4一起发布的Java日志?a target=_blank>java.util.logging适时地满了我们的初步需求,在程序中按一定格式显C和记录丰富的调试信息已l是一件相当easy的事情?/p>
1. 日志记录器:Logger
Logger是一个直接面向用L日志功能调用接口Q从用户的角度上看,它完成大部分日志记录工作Q通常你得C个Logger对象Q只需要用一些简单方法,譬如infoQwarningQlogQlogpQlogrb{就能完成Q务,单到和System.out.println(...)一样只用一条语句,但后台可能在向控制台Q向文gQ向数据库,甚至向网l同时输信息Q而这个过E对用户是完全透明的?br> 在用Logger之前Q首先需要通过getLogger()?a target=_blank>getAnonymousLogger()静态方法得C个Logger对象Q想想看Q这里是不是设计模式当中?#8220;工厂Ҏ”的一个实实在在的应用Q可以参考一下Logger的源代码Q你明?a target=_blank>LogManager?#8220;工厂c?#8221;而Logger?#8220;产品c?#8221;Q凡事都要学以致用嘛Q呵呵)。这里我们需要了解的是Logger?#8220;名字I间”Q?strong>namespaceQ的概念Q通常我们调试旉要清楚地知道某个变量是出现在什么位|,_到哪个类的哪个方法,namespace是q么个用处。我们用getLogger()得到Logger旉要指定这个Logger的名字空_通常是一个包名,譬如“com.jungleford.test”{,如果是指定了namespaceQ那么将在一个全局对象LogManager中注册这个namespaceQLogger会基于namespace形成层次关系Q譬如namespace?#8220;com.jungleford”的Logger是namespace?#8220;com.jungleford.test”的Logger的父Q后者调?a target=_blank>getParent()Ҏ返回前者,如果当前没有namespace?#8220;com.jungleford”的LoggerQ则查找namespace?#8220;com”的LoggerQ要是按照这个链找不到就q回根LoggerQ其namespace?"Q根Logger的父是null。从理论上说Q这个namespace可以是Q意的Q通常我们是按所调试的对象来定,但如果你是用getAnonymousLogger()Ҏ产生的LoggerQ那它就没有namespaceQ这?#8220;匿名Logger”的父是根Logger?br> 得到一个Logger对象后就可以记录日志了,下面是一些常用的ҎQ?
finest?a target=_blank>finer?a target=_blank>fine?a target=_blank>info?a target=_blank>config?a target=_blank>warning?a target=_blank>severeQ简z的ҎQ输出的日志为指定的U别。关于日志别我们在后面会详细谈到?br>
logQ不仅可以指定消息和U别Q还可以带一些参敎ͼ甚至可以直接是一个LogRecord对象Q这些参数是LogRecord对象的重要组成部分)?br>
logpQ更加精l了Q不但具有logҎ的功能,q可以不使用当前的namespaceQ定义新的类名和Ҏ名?br>
entering?a href="http://java.sun.com/j2se/1.4.2/docs/api/java/util/logging/Logger.html#exiting(java.lang.String, java.lang.String)" target=_blank>exitingQ这两个Ҏ在调试的时候特别管用,用来观察一个变量变化的情况Q就如同我们在VC的调试状态下watch一个变量,然后按F10Q呵c?/font>
2. 输出媒介控制QHandler
日志的意义在于它可以以多UŞ式输出,其是像文gq样可以长久保存的媒介,q是System.out.println(...)所无法办到的。Logging API?a target=_blank>HandlercL供了一个处理日志记录(LogRecordQ它是对一条日志消息的装对象Q的接口Q包括几个已实现的APIQ?/p>
ConsoleHandlerQ向控制台输出?br>
FileHandlerQ向文g输出?br>
SocketHandlerQ向|络输出?/font>
q三个输出控制器都是StreamHandler的子c,另外Handlerq有一个MemoryHandler的子c,它有Ҏ的用处,我们在后面将会看到。在E序启动旉认的Handler是ConsoleHandlerQ不q这个是可以配置的,下面会谈到logging配置文g的问题?br> 此外用户q可以定制自p出控制器Q承Handler卛_Q通常只需要实现Handler中三个未定义的抽象方法:
publishQ主要方法,把日志记录写入你需要的媒介?br>
flushQ清除缓冲区q保存数据?br>
closeQ关闭控制器?/font>
通过重写以上三个Ҏ我们可以很容易就实现一个把日志写入数据库的控制器?br>
3. 自定义输出格式:Formatter
除了可以指定输出媒介之外Q我们可能还希望有多U输出格式,譬如可以是普通文本、HTML表格、XML{等Q以满不同的查看需求。Logging API中的Formatter是q样一个提供日志记录格式化Ҏ接口的类。默认提供了两种FormatterQ?br>
SimpleFormatterQ标准日志格式,是我们通常在启动一些诸?a target=_blank>Tomcat?a target=_blank>JBoss之类的服务器的时候经常能在控制台下看到的那种形式Q就像这P
2004-12-20 23:08:52 org.apache.coyote.http11.Http11Protocol init
信息: Initializing Coyote HTTP/1.1 on http-8080
2004-12-20 23:08:56 org.apache.coyote.http11.Http11Protocol init
信息: Initializing Coyote HTTP/1.1 on http-8443
XMLFormatterQXML形式的日志格式,你的Logger如果add了一个new XMLFormatter()Q那么在控制C׃看到下面q样的Ş式,不过更常用的是用上面介l的FileHandler输出到XML文g中:
<?xml version="1.0" encoding="GBK" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2004-12-20T23:47:56</date>
<millis>1103557676224</millis>
<sequence>0</sequence>
<logger>Test</logger>
<level>WARNING</level>
<class>Test</class>
<method>main</method>
<thread>10</thread>
<message>warning message</message>
</record>
与HandlercMQ我们也可以~写自己的格式化处理器,譬如API里没有将日志输出为我们可通过览器查看的HTML表格形式的FormatterQ我们只需要重?个方法:
formatQ格式化LogRecord中包含的信息?br>
getHeadQ输Z息的头部?br>
getTailQ输Z息的N?/font>
4. 定义日志U别QLevel
大家可能都知道Windows?#8220;事g查看?#8221;Q里面有三种事gcdQ?#8220;信息”?#8220;警告”?#8220;错误”。这其实是日志U别的一U描q。Java日志U别?a target=_blank>Levelc表C,一个日志别对应的是一个整数|范围和整型值的范围是一致的Q该整数值愈大,说明警戒U别愈高。Level?个内|的U别Q分别是Q?
cd 对应的整?/strong>
OFF 最大整敎ͼInteger.MAX_VALUEQ?br>SEVERE 1000
WARNING 900
INFO 800
CONFIG 700
FINE 500
FINER 400
FINEST 300
ALL 最整敎ͼInteger.MIN_VALUEQ?/font>
你也可以定义自己的日志别,但要注意的是Q不是直接创建Level的对象(因ؓ它的构造函数是protected的)Q而是通过l承Level的方式,譬如Q?
class AlertLevel extends java.util.logging.Level
{
public AlertLevel()
{
super("ALERT", 950);
}
}
...
Logger logger = Logger.getAnonymousLogger();
logger.log(new AlertLevel(), "A dangerous action!");
上面定义了一个高于WARNING但低于SEVERE的日志别?br> 于是可能有朋友会兴冲冲地用以下的语句来记录一个事Ӟ
Logger logger = Logger.getAnonymousLogger();
logger.fine("Everything seems ok.");
//或者是
//logger.log(Level.FINE, "Everything seems ok.");
但是一E序q行Q奇怪了Q怎么没有打印ZQ何消息呢Q下一节我们来谈这个问题?/font>
5. 日志qo器:Filter
所谓过滤器是控制哪些日志该输出哪些不该输出的一U组件。上面你写的那条日志没有能在控制台显C出来,是因为logging API预先讑֮的缺省别是INFOQ也是说只有别不低于INFOQ即其整数g于800Q的日志才会被输出,q个是Filter的功能。所以我们可以看到SEVERE、WARNING、INFO以及上面我们定义的ALERT消息Q但看不到FINE、FINER和FINEST消息。当Ӟ你尽可以用Logger?a target=_blank>setLevelҎ或者修攚w|文件的ҎQ什么是配置文gQ我们后面将会看刎ͼ来重新定义Logger输出的最低别?br> Filter不仅仅可以按日志U别qoQ你也可以定义自qFilterQ实现其中的isLoggableҎQ随便按照LogRecord携带的Q何信息进行过滤,譬如Q顺便复习一下匿名类Q呵呵)Q?
Logger logger = Logger.getAnonymousLogger();
logger.setFilter(new Filter()
{
public boolean isLoggable(LogRecord rec)
{
//从LogRecord里得到过滤信?br> }
});
6. 预定义参?br> LogManager是一个实CSingleton模式的全局对象Q由于是一个唯一的对象,LogManager需要是U程安全的)Q它理着E序启动以后所有已注册Q包层次Q或匿名的LoggerQ以及相关配|信息。这里的配置信息通常是从<JAVA_HOME>\jre\lib\logging.properties文g得到的。logging.properties对于logging API来说是一个很重要的文Ӟ它的内容一般是Q?br>
############################################################
# Default Logging Configuration File
#
# You can use a different file by specifying a filename
# with the java.util.logging.config.file system property.
# For example java -Djava.util.logging.config.file=myfile
############################################################
############################################################
# Global properties
############################################################
# "handlers" specifies a comma separated list of log Handler
# classes. These handlers will be installed during VM startup.
# Note that these classes must be on the system classpath.
# By default we only configure a ConsoleHandler, which will only
# show messages at the INFO and above levels.
handlers= java.util.logging.ConsoleHandler
# To also add the FileHandler, use the following line instead.
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
# Default global logging level.
# This specifies which kinds of events are logged across
# all loggers. For any given facility this global level
# can be overriden by a facility specific level
# Note that the ConsoleHandler also has a separate level
# setting to limit messages printed to the console.
.level= INFO
############################################################
# Handler specific properties.
# Describes specific configuration info for Handlers.
############################################################
# default file output is in user's home directory.
java.util.logging.FileHandler.pattern = %h/java%u.log
java.util.logging.FileHandler.limit = 50000
java.util.logging.FileHandler.count = 1
java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
# Limit the message that are printed on the console to INFO and above.
java.util.logging.ConsoleHandler.level = INFO
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
############################################################
# Facility specific properties.
# Provides extra control for each logger.
############################################################
# For example, set the com.xyz.foo logger to only log SEVERE
# messages:
com.xyz.foo.level = SEVERE
?
前面我们在介lHandler的时候提到过一个特D的cdMemoryHandlerQ这里我们要了解一?#8220;Handler?#8221;的概念,日志在输Z前可能经q多个Handler的处理,MemoryHandler在这U情况下是一个中间角Ԍ它维持一个内存中的日志缓冲区Q当日志没有填满~冲区时将全部日志送到下一个HandlerQ否则新q来的日志将会覆盖最老的那些日志Q因此,使用MemoryHandler可以l护一定容量的日志Q另外,MemoryHandler也可以不需要用Formatter来进行格式化Q从而具有较高的效率。一个用Handler铄例子如图2所C:
青出于蓝QApache Jakarta log4j日志工具?/font>
应付日常的日志需求,J2SE的Logging API可以说已l做得相当出色了Q但q求完美的开发h员可能需要可扩展性更好的专业日志处理工具Qlog4j正是当前比较行的一个工具包Q它提供更多的输出媒介、输出格式和配置选择Q你会发现原来在J2SE里一些仍需要自己手工构建的功能在log4j当中都已lؓ你实C。关于log4j我可能谈得不会太多,可以看看文后所附的“参考资?/u>”Q网上也有很详细的介l,我在q里做的是一个对比,因ؓlog4j和J2SE 1.4 Logging API的用法是很相似的Q一些名UC同的lg你会发现他们所处的C其实是一LQ?
J2SE 1.4中的c?/strong> log4j中的c?/strong>
日志记录?/font> Logger Logger
日志理?/strong> LogManager LogManager
日志对象 LogRecord LoggingEvent
输出媒介控制 Handler Appender
格式?/strong> Formatter Layout
U别 Level Level
qo?/strong> Filter Filter
log4j可以做到更精l更完善的控Ӟ譬如J2SE里没有现成向数据库里写日志的ҎQ但log4j却有JDBCAppenderQ它甚至q能向GUI囑Ş界面Q?a target=_blank>LF5AppenderQ一U以JTree方式昄的层ơ结构)、Windows NT事g查看器(NTEventLogAppenderQ、UNIX的syslogd服务Q?a target=_blank>SyslogAppenderQ、电子邮(SMTPAppenderQ、Telnetl端Q?a target=_blank>TelnetAppenderQ、JMS消息Q?a target=_blank>JMSAppenderQ输出日志,牛吧QJ2SE里默认只能用%JAVA_HOME%\jre\lib\logging.properties做配|文Ӟ但log4j却可以在代码中设|其它\径下的properties文g或XML格式的配|文件。log4j的其它方面同样很丰富QMQlog4j的最大的特点是“灉|”Q无论是Appender、Layoutq是ConfiguratorQ你可以把日志轻村֜弄成几乎M你想要的形式?/font>
框架与标准:JSR议案
从时间顺序上Ԍlog4j要比J2SE Logging API来得早,很多概念都是log4j先有的,但成Z个标准,则是?a target=_blank>JSR 47的Ş成。可能有不太了解JSRQ这q要谈到JCPQ即“Java Community Process”Q它是一个于1998q成立的旨在为Java技术制定民间标准的开攄l,你可以通过http://www.jcp.org/en/participation/membership甌成ؓ它的付费或免费会员,JCP的主要工作就是制定和发布JSRQJava Specification RequestsQ,JSR对于Java的意义就相当于RFC对于|络技术的意义Q由于JCP会员们的集思广益,使得JSR成ؓJava界的一个重要标准。JSR 47?#8220;Logging API Specification”Q制定了调试和日志框ӞJ2SE Logging API正是该框架的一个实现。由于种U原因,在JSR 47出来以前Qlog4j已l成ZҎ熟的技术,使得log4j在选择上占据了一定的优势Q但不能因此pJSR 47是过时的规范Q标准L在发展的嘛!
q不是全部:其它日志处理工具
除了J2SE Logging API和log4jQ日志处理方面还有别的技术:Jakarta?a target=_blank>commonslg目中的JCLQJakarta Commons LoggingQ是一个不错的选择Q它有点cM于GSS-APIQ通用安全服务接口Q中的思想Q其日志服务机制是可以替换的Q也是说既可以用J2SE Logging API也可以用log4jQ但JCL对开发h员提供一致的接口Q这一点相当重要,lg可重用正是Jakarta Commons目q求的一个目标;IBM?a target=_blank>JLog也是在J2SE Logging API之前推出的一个工具包Q但JLog是一个商业品?br> 至于日志API的应用那可就多了Q现在哪个大一点的工具或^C用到日志模块呢?Tomcat、JBoss……
说了q么多,我们无非需要知道的一件事是Q?#8220;调试”也是一门学问。在我们一个劲地用System.out.println(...)而且用得很爽的时候,也应该想想看Q如何让q样一条菜鸟语句也能变得h性化和丰富多彩?/p>
Tomcat启动的时候出C面这L提示Q?/p>
2006-1-26 19:44:11 org.apache.catalina.core.AprLifecycleListener lifecycleEvent
信息: The Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: D:\Java\jdk1.5.0_05\bin;.;C:\WINDOWS\system32;C:\WINDOWS;d:\ruby\bin;.;..;D:\Java\jrockit-R26.0.0-jdk
实际q是使用apache的apr?/p>
如果想用apr的话Q?实际操作很简单,
下蝲 http://tomcat.heanet.ie/native/1.1.1/binaries/win32/tcnative-1.dll
按照说明要求Q将q个文g攑ֈTomcat下的bin目录?/p>
然后重新启动tomcatQ就会发现tomcat 的控制台信息为:
2006-1-26 19:48:42 org.apache.coyote.http11.Http11AprProtocol init
信息: Initializing Coyote HTTP/1.1 on http-9080
详情?http://tomcat.apache.org/tomcat-5.5-doc/apr.html