你的位置:pcMing工作室 >> 资讯 >> 编程开发 >> JAVA/JSP >> 详细内容 在线投稿

Java调试的变迁:从System.out.println到log4j

排行榜 收藏 打印 发给朋友 举报 来源: javaEye   发布者:imain
热度2248票  浏览302次 【共0条评论】【我要评论 时间:2010年11月28日 18:30
jungleford如是说 pcMing工作室jj'|Mr^%z]a

_Wx/Ub0kVh!ca7N0
    用惯了VC的人刚接触Java大概很不习惯代码的调试,的确,在M$的大部分IDE都做得相当出色,包括像VJ++这样一直被Java程序员称为是“垃圾”的类库(记得以前在瀚海星云Java版提有关VJ问题的人是有可能被封的,^_^),它的开发工具在调试上都相当容易。Java也有命令行方式的调试和IDE的调试,但现在的像JB这样的玩意又是个庞然大物,低配置的机器可能就是个奢望,不像VC那样。怎么办呢,高手们说,“我的jdb用得贼熟练”,那我会报以景仰的目光,像我这样的菜鸟基本上就没使过jdb,还是老老实实在代码里面System.out.println(...)。直到1996年一个叫做“欧洲安全电子市场”(E.U.SEMPER)的项目启动,“调试”不再是一件“体力活”,而是一种软件设计的艺术,这个项目组开发的日志管理接口后来成为ApacheJakarta项目中的一员,它就是现在我们所熟悉的log4j。下面的文字将概要介绍与Java日志记录相关的一些技术,目的不是让您放弃老土的System.out.println(...),而是说,在Java的世界里可以有许多种选择,你今天觉得掌握了一件高级武器,明天可能就是“过时”的了,呵呵。

始祖:System.out.println(...)
5Rh9o eJ~xQE0pcMing工作室jjg B P'kv
    为什么还是要一再提到它?毕竟我们的习惯不是那么容易改变的,而且System.out(别忘了还有System.err)是一个直接和控制台打交道的PrintStream对象,是终端显示的基础,高级的Logger要在终端显示日志内容,就必然会用到这个。一个小规模的程序调试,恰当地使用System.out.println(...)我认为仍然是一种最方便最有效的方法,所以我们仍把它放在最开始,以示不能“数典忘祖” :)pcMing工作室 V-F'kW-] cZ)ns Ca

不常用的关键字:assert
oP2K+AZ%A0
[f ^ Bnd!w0
    assert对多数人来讲可能还比较陌生,它也是一个调试工具,好像是J2SE 1.4才加进来的东东,一种常见的用法是:

(?9U9Q0s-{7vy0

 assert(布尔表达式);

Mgy!MBY?3Gd0

pcMing工作室 LW8Ed,a*wOE
    当表达式为true时没有任何反映,如果为false系统将会抛出一个AssertionError。如果你要使用assert,在编译时必须加上“-source 1.4”的选项,在运行时则要加上“-ea”选项。pcMing工作室}A0v-W K-?A

O2pg`6l$`7^+l4t0

后生可畏:Java Logging API一瞥
mJ+Dx!R0
sHU1u-TZ Xc'f0
    System.out.println(...)对于较高要求的用户是远远不够的,它还不是一个日志系统,一个比较完善的日志系统应当有输出媒介、优先级、格式化、日志过滤、日志管理、参数配置等功能。伴随J2SE 1.4一起发布的Java日志包java.util.logging适时地满足了我们的初步需求,在程序中按一定格式显示和记录丰富的调试信息已经是一件相当easy的事情。

L%JB,_WZHbq0

1. 日志记录器:Logger
tv4Z5Sz[Rm@;A0    Logger是一个直接面向用户的日志功能调用接口,从用户的角度上看,它完成大部分日志记录工作,通常你得到一个Logger对象,只需要使用一些简单方法,譬如info,warning,log,logp,logrb等就能完成任务,简单到和System.out.println(...)一样只用一条语句,但后台可能在向控制台,向文件,向数据库,甚至向网络同时输出该信息,而这个过程对用户是完全透明的。
qeg mO t+N'k0    在使用Logger之前,首先需要通过getLogger()或getAnonymousLogger()静态方法得到一个Logger对象(想想看,这里是不是设计模式当中的“工厂方法”的一个实实在在的应用?可以参考一下Logger的源代码,你就明白LogManager是“工厂类”而Logger是“产品类”,凡事都要学以致用嘛,呵呵)。这里我们需要了解的是Logger的“名字空间”(namespace)的概念:通常我们调试时需要清楚地知道某个变量是出现在什么位置,精确到哪个类的哪个方法,namespace就是这么个用处。我们用getLogger()得到Logger时需要指定这个Logger的名字空间,通常是一个包名,譬如“com.jungleford.test”等,如果是指定了namespace,那么将在一个全局对象LogManager中注册这个namespace,Logger会基于namespace形成层次关系,譬如namespace为“com.jungleford”的Logger就是namespace为“com.jungleford.test”的Logger的父,后者调用getParent()方法将返回前者,如果当前没有namespace为“com.jungleford”的Logger,则查找namespace为“com”的Logger,要是按照这个链找不到就返回根Logger,其namespace为"",根Logger的父是null。从理论上说,这个namespace可以是任意的,通常我们是按所调试的对象来定,但如果你是使用getAnonymousLogger()方法产生的Logger,那它就没有namespace,这个“匿名Logger”的父是根Logger。pcMing工作室}*vHz1b@5A5Z
    得到一个Logger对象后就可以记录日志了,下面是一些常用的方法:pcMing工作室:Bh e3L)`

pcMing工作室a+h| K8l-x.mD-|3D;N9e
finestfinerfineinfoconfigwarningsevere:简洁的方法,输出的日志为指定的级别。关于日志级别我们在后面将会详细谈到。
t5s#is8S0
w)KZz^ aK0log:不仅可以指定消息和级别,还可以带一些参数,甚至可以直接是一个LogRecord对象(这些参数是LogRecord对象的重要组成部分)。pcMing工作室BX"S*]/C V
pcMing工作室 ms(kuqjg3sF5o
logp:更加精细了,不但具有log方法的功能,还可以不使用当前的namespace,定义新的类名和方法名。pcMing工作室7l1|3]-HW d cv
pcMing工作室X} ium]x
enteringexiting:这两个方法在调试的时候特别管用,用来观察一个变量变化的情况,就如同我们在VC的调试状态下watch一个变量,然后按F10,呵呵。
pcMing工作室7lV9r,q;F@N/z

M#AiP!e(\{0

2. 输出媒介控制:HandlerpcMing工作室8w|GXK4FE4|B5D
    日志的意义在于它可以以多种形式输出,尤其是像文件这样可以长久保存的媒介,这是System.out.println(...)所无法办到的。Logging API的Handler类提供了一个处理日志记录(LogRecord,它是对一条日志消息的封装对象)的接口,包括几个已实现的API:pcMing工作室v~ Ti2W;k xa

pcMing工作室 zv,x}#@q
ConsoleHandler:向控制台输出。pcMing工作室8H s4k v#Q`l2E%F/@

4TJ)A#p Hkr{-V I0FileHandler:向文件输出。pcMing工作室/R(D&WD!I2h
pcMing工作室uw9{khq0k&u6N
SocketHandler:向网络输出。
pcMing工作室+j6q vX(k

pcMing工作室6zAr ]ZL H
    这三个输出控制器都是StreamHandler的子类,另外Handler还有一个MemoryHandler的子类,它有特殊的用处,我们在后面将会看到。在程序启动时默认的Handler是ConsoleHandler,不过这个是可以配置的,下面会谈到logging配置文件的问题。pcMing工作室6i8LQe~ H
    此外用户还可以定制自己输出控制器,继承Handler即可,通常只需要实现Handler中三个未定义的抽象方法:

5|-usR)m'[ r0

publish:主要方法,把日志记录写入你需要的媒介。
(E|aY.|Tu6| scQ4|Q0pcMing工作室bdY^C}
flush:清除缓冲区并保存数据。
Gbm zq7]1J2s0
zQN4J~ `'J}*P0close:关闭控制器。
pcMing工作室&N9z y5o c0_ Q

publish:主要方法,把日志记录写入你需要的媒介。pcMing工作室 s7DH^)v3Q@U8J

-Q2Dq `!P.v0flush:清除缓冲区并保存数据。pcMing工作室 t%o\:\ P7u4^

"Q8y6^8a#K0close:关闭控制器。

pcMing工作室#Jz_K5S-tKG
    通过重写以上三个方法我们可以很容易就实现一个把日志写入数据库的控制器。pcMing工作室(r|%Q1MC

9g8G u8zH+l7B4z0

3. 自定义输出格式:FormatterpcMing工作室7mY.i"Y-Ki/Q ]
    除了可以指定输出媒介之外,我们可能还希望有多种输出格式,譬如可以是普通文本、HTML表格、XML等等,以满足不同的查看需求。Logging API中的Formatter就是这样一个提供日志记录格式化方法接口的类。默认提供了两种Formatter:pcMing工作室&p]4Dzr%]
pcMing工作室7w;p1k^M{ |)wm
SimpleFormatter:标准日志格式,就是我们通常在启动一些诸如TomcatJBoss之类的服务器的时候经常能在控制台下看到的那种形式,就像这样:pcMing工作室%|7ws}2Y_6|"_)R


2vbA {?02004-12-20 23:08:52 org.apache.coyote.http11.Http11Protocol initpcMing工作室#|*It-eu
信息: Initializing Coyote HTTP/1.1 on http-8080pcMing工作室4F(o%p;zs6X&[

u4S uy5| T,EOx02004-12-20 23:08:56 org.apache.coyote.http11.Http11Protocol init
w'uE;E/P Sq0信息: Initializing Coyote HTTP/1.1 on http-8443
pcMing工作室S4@9J#|"C Rv M5h:]

pcMing工作室,?0o&lz \pE
XMLFormatter:XML形式的日志格式,你的Logger如果add了一个new XMLFormatter(),那么在控制台下就会看到下面这样的形式,不过更常用的是使用上面介绍的FileHandler输出到XML文件中:pcMing工作室-SlaK4}!p q&]X1Y

<?xml version="1.0" encoding="GBK" standalone="no"?>
+s)C}-K*x*g A5x0<!DOCTYPE log SYSTEM "logger.dtd">
I3b^-\but7H0<log>pcMing工作室TYgNe1TJ
<record>pcMing工作室2S3`P.@"a{Dq;h`
  <date>2004-12-20T23:47:56</date>pcMing工作室8Q5q%FXUF
  <millis>1103557676224</millis>pcMing工作室(Gv B)h$_4U.Be"T3N.Y
  <sequence>0</sequence>pcMing工作室3~ ?}G8~6Z4L;J
  <logger>Test</logger>pcMing工作室'K#B O;B A#w#m"?3j
  <level>WARNING</level>pcMing工作室6]Xjy!i)J
  <class>Test</class>
+SrdBY6Q4o)t A0  <method>main</method>pcMing工作室A7I ` m7n
  <thread>10</thread>pcMing工作室o,m%h#LR~W1I
  <message>warning message</message>
f3L/[#g;W$k`0</record>
pcMing工作室 i!k^T5qR

<?xml version="1.0" encoding="GBK" standalone="no"?>pcMing工作室*Q!H%U:uA%k x
<!DOCTYPE log SYSTEM "logger.dtd">pcMing工作室Z#Tt!|'E
<log>pcMing工作室F0V-S#kM"F
<record>pcMing工作室 gY7pZ6^/R:^n#f
  <date>2004-12-20T23:47:56</date>
m5\jK Uo0  <millis>1103557676224</millis>pcMing工作室$KI }3N@5hI
  <sequence>0</sequence>pcMing工作室8L\hb7guG
  <logger>Test</logger>
b1I` Cc+Q p%S'Jhf0  <level>WARNING</level>pcMing工作室T+vP*B9_ }(@ ]v
  <class>Test</class>
?~zW:b!a#t]*i*n]0  <method>main</method>pcMing工作室p3C"Cb}Dl
  <thread>10</thread>
.u$xCsYO0  <message>warning message</message>
X1A,`/w!cH^/U0</record>


i2@{~N Fw0    与Handler类似,我们也可以编写自己的格式化处理器,譬如API里没有将日志输出为我们可通过浏览器查看的HTML表格形式的Formatter,我们只需要重写3个方法:pcMing工作室 Ny@m.dQ#o$C

format:格式化LogRecord中包含的信息。
%P.R-J |6`'[/qtCMe0pcMing工作室9A k3H)R#G/Xv+N K
getHead:输出信息的头部。pcMing工作室o5YD*d)J2~ w
pcMing工作室? p2\]/s2O
getTail:输出信息的尾部。
pcMing工作室vK z.J9\C'Z,DM
pcMing工作室rB]1MA

format:格式化LogRecord中包含的信息。
n J7Q%e.[0
L;AIC&XRR`0getHead:输出信息的头部。pcMing工作室W!S0kdq%m/C1~
pcMing工作室 V-o.Vcv7M
getTail:输出信息的尾部。
pcMing工作室k6S|#T,g,p)RJ7~

4. 定义日志级别:Level
*K*\"`s%{&?9k0    大家可能都知道Windows的“事件查看器”,里面有三种事件类型:“信息”、“警告”、“错误”。这其实就是日志级别的一种描述。Java日志级别用Level类表示,一个日志级别对应的是一个整数值,范围和整型值的范围是一致的,该整数值愈大,说明警戒级别愈高。Level有9个内置的级别,分别是:

[/tr^"K.emc&[Id0

类型         对应的整数
.bU9n;Ke|#CH0
OFF         最大整数(Integer.MAX_VALUE
E&sp+B1^0
SEVERE     1000
F"h|Ly#~g3`0
WARNING  900
smj5p,`c.QN5LS0
INFO        800
%}4lX OT&` J4m;Jd0
CONFIG     700pcMing工作室 A-K_,ynR
FINE          500pcMing工作室E$\,Df(J
FINER        400
WG {+JR^0
FINEST     300
jA5nN5gN]'A:M0
ALL           最小整数(Integer.MIN_VALUE

sk4k,fr4S-JL.K0

pcMing工作室(_x\A@$Lg
    你也可以定义自己的日志级别,但要注意的是,不是直接创建Level的对象(因为它的构造函数是protected的),而是通过继承Level的方式,譬如:

Lo/fssCVkg0

classAlertLevelextendsjava.util.logging.Level
(T j%W9x m%m W0{
z+I$M,|AzMV0  publicAlertLevel()
+N {.`)|;aP;p0  {
t+x lS6Y9j8?(h0    super("ALERT",950);
5?Q c ~3RY*x*d&c'f0  }pcMing工作室)B7`;t:Mh'L riOn
}pcMing工作室_+kZ#Z'b.wkZ\
...
r W+IMsL3p0Loggerlogger =Logger.getAnonymousLogger();
\N!^$_ @v:~q!x L-m0logger.log(newAlertLevel(),"A dangerous action!");

O0mU/a'DFw6l0
classAlertLevelextendsjava.util.logging.LevelpcMing工作室X)[?X5SlE4N/VR
{pcMing工作室+Y5k"wao#E1b-p
  publicAlertLevel()pcMing工作室 u"^u+w$Bm;f
  {pcMing工作室b:F |L)Z7xANj
    super("ALERT",950);
m h8n4g9SFhl}"E0  }
Yj{8~%l*_ ZhF0}
n1N.X ~ p6|8z[E0...
/bC5w{y x0Loggerlogger =Logger.getAnonymousLogger();pcMing工作室6sdIM2Bo*~~A#|
logger.log(newAlertLevel(),"A dangerous action!");

pcMing工作室~8g5Zb+Ls6M0d
    上面定义了一个高于WARNING但低于SEVERE的日志级别。
Fy4Ditw0    于是可能有朋友会兴冲冲地用以下的语句来记录一个事件:pcMing工作室-s0oD[d4KO,a

Loggerlogger =Logger.getAnonymousLogger();
L1M1Z@wH7Yh0logger.fine("Everything seems ok.");pcMing工作室|clS?_*v"xZ
//或者是pcMing工作室 x7vF.` C3?d4j
//logger.log(Level.FINE, "Everything seems ok.");
pcMing工作室x"~"Y'foG\I

Loggerlogger =Logger.getAnonymousLogger();pcMing工作室 Y:u:WY"g Y pi
logger.fine("Everything seems ok.");
K#a+Fc A2m0//或者是
8R)D(w&e2t}0//logger.log(Level.FINE, "Everything seems ok.");

pcMing工作室QP$po4f6x |
    但是一程序运行,奇怪了,怎么没有打印出任何消息呢?下一小节我们就来谈这个问题。
pcMing工作室k;I)b1d y"q ?8okk

5. 日志过滤器:FilterpcMing工作室&Xw O*py1t5B L
    所谓过滤器是控制哪些日志该输出哪些不该输出的一种组件。上面你写的那条日志没有能在控制台显示出来,是因为logging API预先设定的缺省级别是INFO,也就是说只有级别不低于INFO(即其整数值不小于800)的日志才会被输出,这个就是Filter的功能。所以我们可以看到SEVERE、WARNING、INFO以及上面我们定义的ALERT消息,但看不到FINE、FINER和FINEST消息。当然,你尽可以用Logger的setLevel方法或者修改配置文件的方法(什么是配置文件,我们后面将会看到)来重新定义Logger输出的最低级别。
LZg]*v7M,n"aB0    Filter不仅仅可以按日志级别过滤,你也可以定义自己的Filter,实现其中的isLoggable方法,随便按照LogRecord携带的任何信息进行过滤,譬如(顺便复习一下匿名类,呵呵):
pcMing工作室 _\ F|h.nY'X

Loggerlogger =Logger.getAnonymousLogger();pcMing工作室B_$x.O*L-RB
logger.setFilter(newFilter()
U Jk$|!e0{pcMing工作室,tUuf"o |
  publicbooleanisLoggable(LogRecordrec)pcMing工作室Y"A IK6M?%c(W)} SI
  {
#a.\[qb#C$|`Qsu0    //从LogRecord里得到过滤信息
^(gkOUT5V0w0
  }pcMing工作室4Dv&RK Ra1n8{
});

CB.E*D,^e;n/{0

pcMing工作室.f2}+h$V8[-ct
6. 预定义参数pcMing工作室*z8IE)d6@
    LogManager是一个实现了Singleton模式的全局对象(由于是一个唯一的对象,LogManager需要是线程安全的),它管理着程序启动以后所有已注册(包层次)或匿名的Logger,以及相关配置信息。这里的配置信息通常是从<JAVA_HOME>\jre\lib\logging.properties文件得到的。logging.properties对于logging API来说是一个很重要的文件,它的内容一般是:pcMing工作室:rf1H-Qe:]MiO

lu;^kM1Gi0

############################################################
e"P(c`M,O0# Default Logging Configuration FilepcMing工作室-I&g&W)O%J+G]#C"_~
#pcMing工作室4E2T8jK*gB)K0j
# You can use a different file by specifying a filename
d%E'`wv7GhT0# with the java.util.logging.config.file system property.pcMing工作室a6l2@ R5u!j U5F;]
# For example java -Djava.util.logging.config.file=myfile
X~"q3pic L eE0############################################################

q+X?2Y'Iy2HgKF0

############################################################pcMing工作室qrbEUiy p
# Global propertiespcMing工作室i:u(s(a Ba {
############################################################pcMing工作室,[.F Fl/qo1J

# "handlers" specifies a comma separated list of log HandlerpcMing工作室v/K+P`#U;{.o
# classes. These handlers will be installed during VM startup.pcMing工作室HPB0O1x3MKp9X
# Note that these classes must be on the system classpath.pcMing工作室M,cZgg#Vi
# By default we only configure a ConsoleHandler, which will only
Zn;t}Q7Tt0# show messages at the INFO and above levels.pcMing工作室2lDdr5J C7t
handlers= java.util.logging.ConsoleHandlerpcMing工作室 x#Fhs)],W

# To also add the FileHandler, use the following line instead.pcMing工作室p? A5gEb$o
#handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandlerpcMing工作室d%X{w'B$Tjf;l

# Default global logging level.
d#m1Cd P\^0# This specifies which kinds of events are logged acrosspcMing工作室)ys~ UG#`
# all loggers. For any given facility this global level
3r0x8t#`n,g&k A%o0# can be overriden by a facility specific level
d0@/R/}/y ^S0# Note that the ConsoleHandler also has a separate levelpcMing工作室7b4g8O O@7F F
# setting to limit messages printed to the console.pcMing工作室c!dC4e9l;B,y
.level= INFOpcMing工作室l4]^+?iJ r d%M

############################################################pcMing工作室e$cY(E6u6K N
# Handler specific properties.pcMing工作室 Uz#RAO!_~
# Describes specific configuration info for Handlers.pcMing工作室"[0Eh/G6m4J
############################################################pcMing工作室Sg |kobx*@

# default file output is in user's home directory.
!|9E$v h g~r?0java.util.logging.FileHandler.pattern = %h/java%u.log
F5UzE-M0java.util.logging.FileHandler.limit = 50000
}ep0vq N;ga0java.util.logging.FileHandler.count = 1
w4BXN$G0java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatterpcMing工作室Gi%B3Y9h9L)iBm

# Limit the message that are printed on the console to INFO and above.pcMing工作室 Uw1gyr p?|;z
java.util.logging.ConsoleHandler.level = INFOpcMing工作室UFf'G3vGS byF{[
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatterpcMing工作室c^ ?:k$L&S:j*\E9n

v&E!sW,AW"t x0

############################################################
O6S#@m:]1I$c0# Facility specific properties.
0}4tj+l~6\]m0# Provides extra control for each logger.pcMing工作室8RA)n0k&nSo
############################################################pcMing工作室+aT }J~ ?$@

# For example, set the com.xyz.foo logger to only log SEVERE
g8g(es4n)H s0# messages:
/yEES"w? _D0com.xyz.foo.level = SEVEREpcMing工作室+v.K r_Bt7O


P UZi5jv0    你可以通过修改这个配置文件来改变运行时Logger的行为,譬如:.level定义的是上面所说的默认输出的最低日志级别;XXXHandler相关属性定义了各种输出媒介等等。pcMing工作室C{ O;OZq2OUH$H[w
    这里比较有意思的是关于日志文件,也就是FileHandler,当然,你可以在程序中创建一个FileHandler,然后添加到logger中:
 
FileHandlerfhd =newFileHandler("%h/java%u.log",5000,1,true);pcMing工作室W C d$GXvx%r
fhd.setLevel(Level.ALL);pcMing工作室&Q U$L4He7oJn&hP
fhd.setFormatter(newXMLFormatter());pcMing工作室eof0E$ZIR9E
logger.addHandler(fhd);
FileHandlerfhd =newFileHandler("%h/java%u.log",5000,1,true);
A!j0U u0B*k0fhd.setLevel(Level.ALL);
Sl F,g)Uj@n Xo-H8z9@0fhd.setFormatter(newXMLFormatter());pcMing工作室z&NWr^ i(fp ~#`
logger.addHandler(fhd);

?+M,h"WZ3t+l,t0    这段代码等价于上面logging.properties中的文字段:
 
java.util.logging.FileHandler.pattern = %h/java%u.log
CDPe%QO0java.util.logging.FileHandler.limit = 50000pcMing工作室 Gf9\1yev{[
java.util.logging.FileHandler.count = 1
5MeU CJ#D1p5e0java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter
java.util.logging.FileHandler.pattern = %h/java%u.logpcMing工作室r g,e4Nm9I_
java.util.logging.FileHandler.limit = 50000pcMing工作室4}&b _:v2Qs$q
java.util.logging.FileHandler.count = 1
;yu(~+rv2|&P|0java.util.logging.FileHandler.formatter = java.util.logging.XMLFormatter

7_:t8^mp3R Q0    这里的pattern代表用转义字符定义的一个日志文件名:转义字符串含义%t临时目录%h用户目录,即系统属性“user.home”对应的值%g一个随机生成的数字,可以重复%u一个随机生成的非重复数字
vw"V.HPE3b0    以上面的“%h/java%u.log”为例,在Windows 2000下代表日志文件可能就是:C:\Documents and Settings\Administrator\javax.log。这里x代表一个不重复的数字,如果是第一次,那么就是java0.log;如果在该目录下已经存在了一个java0.log的文件,那么logger就产生一个java1.log的新的日志文件。
A&E#r9Rsi7Z3I$fp0    当然,你可以在别的地方使用自己写的配置文件,不过在启动程序时候需要指定java.logging.config.file属性:
转义字符串含义%t临时目录%h用户目录,即系统属性“user.home”对应的值%g一个随机生成的数字,可以重复%u一个随机生成的非重复数字pcMing工作室#};[W"an1R
    以上面的“%h/java%u.log”为例,在Windows 2000下代表日志文件可能就是:C:\Documents and Settings\Administrator\javax.log。这里x代表一个不重复的数字,如果是第一次,那么就是java0.log;如果在该目录下已经存在了一个java0.log的文件,那么logger就产生一个java1.log的新的日志文件。
H7O-U5dttm)n_0    当然,你可以在别的地方使用自己写的配置文件,不过在启动程序时候需要指定java.logging.config.file属性:
 
java -Djava.logging.config.file=...

a:Vr#a2R3c Y07. 资源与本地化pcMing工作室$r Ip#p?#CKQ
    Logger里还有个方法叫logrb,可能初学者不太会用到。如果你安装的JDK是国际版的,那么你将会看到在中文Windows平台下日志输出的INFO、WARNING显示的是“信息”、“警告”等中文字样。因为logrb是一个和Java i18n/l10n相关的方法,你可以定义自己的“资源包”(Resource Bundle),然后在logrb方法中指定相应的资源名称,那么在输出日志中你就能看到用自己定义的本地语言、时间等显示的信息。如果你对i18n/l10n感兴趣,可以参考Java Localization文档pcMing工作室'l:cV \c;jQ

6M-s/CjlU0pcMing工作室-bK.gG?
    了解以上组件后,我们回顾一个完整的日志处理的工作过程:
T@S"t?0N-a$]&JB0    程序启动日志服务,创建Logger对象,LogManager按照namespace的层次结构组织Logger,在同一个namespace里子Logger将继承父Logger的属性;同时,LogManager从logging.properties中读取相应的属性对Logger进行初始化,如果在程序中设置了属性则使用新的配置。当应用程序产生一条日志,Logger将创建一个LogRecord对象,该对象封装了一条日志的全部信息。Logger需要根据当前设置的Filter来判断这条日志是否需要输出,并将有用的日志传给相应的Handler处理,而Handler根据当前设置的Formatter和Resource Bundle将日志消息转换成一定的显示格式,然后输出到预定的媒介(控制台、文件等)中去。整个过程大致如图1所示:
D;p$p@ }&?,M0

 

7h? G _"L&V`g)A2o-\0

oA1W_$R*P0

                                                  图1
q.Y"XCr*y0pcMing工作室$Mc&k6V#d@4M+@
    前面我们在介绍Handler的时候提到过一个特殊的类叫MemoryHandler,这里我们要了解一下“Handler链”的概念,日志在输出之前可能经过多个Handler的处理,MemoryHandler在这种情况下就是一个中间角色,它维持一个内存中的日志缓冲区,当日志没有填满缓冲区时就将全部日志送到下一个Handler,否则新进来的日志将会覆盖最老的那些日志,因此,使用MemoryHandler可以维护一定容量的日志,另外,MemoryHandler也可以不需要使用Formatter来进行格式化,从而具有较高的效率。一个使用Handler链的例子如图2所示:

j6ys+v%q!?@{"h0

 

2S,nH `9ons0
pcMing工作室I$n8UMY
图2

 pcMing工作室t LZ$B4L\/h#pQAI*B

青出于蓝:Apache Jakarta log4j日志工具包
?&Mdpa$xB \0
J3]&OjT5?0
    应付日常的日志需求,J2SE的Logging API可以说已经做得相当出色了,但追求完美的开发人员可能需要可扩展性更好的专业日志处理工具,log4j正是当前比较流行的一个工具包,它提供更多的输出媒介、输出格式和配置选择,你会发现原来在J2SE里一些仍需要自己手工构建的功能在log4j当中都已经为你实现了。关于log4j我可能谈得不会太多,可以看看文后所附的“参考资料”,网上也有很详细的介绍,我在这里做的是一个对比,因为log4j和J2SE 1.4 Logging API的用法是很相似的,一些名称不同的组件你会发现他们所处的地位其实是一样的:
8Xn8M5T^$f0pcMing工作室 ^"H,Meno(ADHF
                 J2SE 1.4中的类      log4j中的类
#pL8j;J1A4c0
日志记录器    Logger                  Logger
k"Jh H5b-S)l?0
日志管理器    LogManager          LogManager
1vX(F$Z;t:m0日志对象       LogRecord            LoggingEvent
6A3I|y0d0输出媒介控制Handler                 AppenderpcMing工作室Q6^ ^.R-UB%n6S
格式化        Formatter              Layout
8NW ];A^^gU iW0
级别           Level                    Level
@FzWO0过滤器         Filter                     Filter
V+i!MV|;TkX\0
pcMing工作室ldYD8siLD
    log4j可以做到更精细更完善的控制,譬如J2SE里没有现成向数据库里写日志的方法,但log4j却有JDBCAppender,它甚至还能向GUI图形界面(LF5Appender,一种以JTree方式显示的层次结构)、Windows NT事件查看器(NTEventLogAppender)、UNIX的syslogd服务(SyslogAppender)、电子邮箱(SMTPAppender)、Telnet终端(TelnetAppender)、JMS消息(JMSAppender)输出日志,牛吧;J2SE里默认只能用%JAVA_HOME%\jre\lib\logging.properties做配置文件,但log4j却可以在代码中设置其它路径下的properties文件或XML格式的配置文件。log4j的其它方面同样很丰富,总之,log4j的最大的特点就是“灵活”,无论是Appender、Layout还是Configurator,你可以把日志轻松地弄成几乎任何你想要的形式。

!t?N)[8XkH?e5q3k0

框架与标准:JSR议案
R4SrA{0p/K,r U0
d$v6x*].z@+e0
    从时间顺序上讲,log4j要比J2SE Logging API来得早,很多概念都是log4j先有的,但成为一个标准,则是在JSR 47的形成。可能有人还不太了解JSR,这还要谈到JCP,即“Java Community Process”,它是一个于1998年成立的旨在为Java技术制定民间标准的开放组织,你可以通过http://www.jcp.org/en/participation/membership申请成为它的付费或免费会员,JCP的主要工作就是制定和发布JSR(Java Specification Requests),JSR对于Java的意义就相当于RFC对于网络技术的意义,由于JCP会员们的集思广益,使得JSR成为Java界的一个重要标准。JSR 47即“Logging API Specification”,制定了调试和日志框架,J2SE Logging API正是该框架的一个实现。由于种种原因,在JSR 47出来以前,log4j就已经成为一项成熟的技术,使得log4j在选择上占据了一定的优势,但不能因此就说JSR 47是过时的规范,标准总是在发展的嘛!pcMing工作室!o!FT v STdQ

并不是全部:其它日志处理工具
VL k BF1C ~0pcMing工作室4D5H'CVZuPp
    除了J2SE Logging API和log4j,日志处理方面还有别的技术:Jakarta的commons组件项目中的JCL(Jakarta Commons Logging)是一个不错的选择,它有点类似于GSS-API(通用安全服务接口)中的思想,其日志服务机制是可以替换的,也就是说既可以用J2SE Logging API也可以用log4j,但JCL对开发人员提供一致的接口,这一点相当重要,组件可重用正是Jakarta Commons项目追求的一个目标;IBMJLog也是在J2SE Logging API之前推出的一个工具包,但JLog是一个商业产品。
.R$IplhbU0    至于日志API的应用那可就多了,现在哪个大一点的工具或平台不用到日志模块呢?Tomcat、JBoss……
*E+fDWOYd*TeU0

{$G e{.N X0

    说了这么多,我们无非需要知道的一件事就是,“调试”也是一门学问。在我们一个劲地用System.out.println(...)而且用得很爽的时候,也应该想想看,如何让这样一条菜鸟语句也能变得人性化和丰富多彩。

(h$BSZ5\7?u0p0

参考资料pcMing工作室Zp,[h0YU _ \ Y@

  • Java Logging Documentation
  • Java Logging APIs
  • J2SE进阶, bywww.javaresearch.org
  • Short introduction to log4j, byCeki Gülcü
  • log4j APIs
  • FAQ about log4j

始祖:System.out.println(...)pcMing工作室$yRS*Mo*j8A-v

;r(H1g [ Gn,Lw4K X0
    为什么还是要一再提到它?毕竟我们的习惯不是那么容易改变的,而且System.out(别忘了还有System.err)是一个直接和控制台打交道的PrintStream对象,是终端显示的基础,高级的Logger要在终端显示日志内容,就必然会用到这个。一个小规模的程序调试,恰当地使用System.out.println(...)我认为仍然是一种最方便最有效的方法,所以我们仍把它放在最开始,以示不能“数典忘祖” :)pcMing工作室te }.SBJ

不常用的关键字:assert
:q#`9JWc{c~jZ F0pcMing工作室VNaV-L&Ni
    assert对多数人来讲可能还比较陌生,它也是一个调试工具,好像是J2SE 1.4才加进来的东东,一种常见的用法是:pcMing工作室 w{8}%M(q4A

 assert(布尔表达式);

l0LKr u,x0

pcMing工作室I4ree"~,T
    当表达式为true时没有任何反映,如果为false系统将会抛出一个AssertionError。如果你要使用assert,在编译时必须加上“-source 1.4”的选项,在运行时则要加上“-ea”选项。pcMing工作室8D hTL1k
pcMing工作室6y.gd SW%o8]8Jk6P

后生可畏:Java Logging API一瞥pcMing工作室A(J%l M0C+du)d.s'M
pcMing工作室3xv7hn+^'V,QR8@
    System.out.println(...)对于较高要求的用户是远远不够的,它还不是一个日志系统,一个比较完善的日志系统应当有输出媒介、优先级、格式化、日志过滤、日志管理、参数配置等功能。伴随J2SE 1.4一起发布的Java日志包java.util.logging适时地满足了我们的初步需求,在程序中按一定格式显示和记录丰富的调试信息已经是一件相当easy的事情。

fvnI'j7c,q'qt0

1. 日志记录器:LoggerpcMing工作室n)U\*Y3Y5u.C
    Logger是一个直接面向用户的日志功能调用接口,从用户的角度上看,它完成大部分日志记录工作,通常你得到一个Logger对象,只需要使用一些简单方法,譬如info,warning,log,logp,logrb等就能完成任务,简单到和System.out.println(...)一样只用一条语句,但后台可能在向控制台,向文件,向数据库,甚至向网络同时输出该信息,而这个过程对用户是完全透明的。
!D NT8Y3Ou0    在使用Logger之前,首先需要通过getLogger()或getAnonymousLogger()静态方法得到一个Logger对象(想想看,这里是不是设计模式当中的“工厂方法”的一个实实在在的应用?可以参考一下Logger的源代码,你就明白LogManager是“工厂类”而Logger是“产品类”,凡事都要学以致用嘛,呵呵)。这里我们需要了解的是Logger的“名字空间”(namespace)的概念:通常我们调试时需要清楚地知道某个变量是出现在什么位置,精确到哪个类的哪个方法,namespace就是这么个用处。我们用getLogger()得到Logger时需要指定这个Logger的名字空间,通常是一个包名,譬如“com.jungleford.test”等,如果是指定了namespace,那么将在一个全局对象LogManager中注册这个namespace,Logger会基于namespace形成层次关系,譬如namespace为“com.jungleford”的Logger就是namespace为“com.jungleford.test”的Logger的父,后者调用getParent()方法将返回前者,如果当前没有namespace为“com.jungleford”的Logger,则查找namespace为“com”的Logger,要是按照这个链找不到就返回根Logger,其namespace为"",根Logger的父是null。从理论上说,这个namespace可以是任意的,通常我们是按所调试的对象来定,但如果你是使用getAnonymousLogger()方法产生的Logger,那它就没有namespace,这个“匿名Logger”的父是根Logger。
h)Ob,{;a(uq Gc*_N2[*D0    得到一个Logger对象后就可以记录日志了,下面是一些常用的方法:

p,aM:tz3g2i0


&G'BjM tT0finestfinerfineinfoconfigwarningsevere:简洁的方法,输出的日志为指定的级别。关于日志级别我们在后面将会详细谈到。pcMing工作室L6qGHz$h+~

s9_4q"BP0log:不仅可以指定消息和级别,还可以带一些参数,甚至可以直接是一个LogRecord对象(这些参数是LogRecord对象的重要组成部分)。
S6~!}uS.C#}|1L0
:^2K4ML]U+uU;a0logp:更加精细了,不但具有log方法的功能,还可以不使用当前的namespace,定义新的类名和方法名。
3|B$PP5@$z^6L0pcMing工作室 Ok&T:W4F0E
enteringexiting:这两个方法在调试的时候特别管用,用来观察一个变量变化的情况,就如同我们在VC的调试状态下watch一个变量,然后按F10,呵呵。
pcMing工作室kHT ^J"Q+G,b
pcMing工作室 o'Zj2V7@^sh!yr9N

2. 输出媒介控制:Handler
R t_l/_0    日志的意义在于它可以以多种形式输出,尤其是像文件这样可以长久保存的媒介,这是System.out.println(...)所无法办到的。Logging API的Handler类提供了一个处理日志记录(LogRecord,它是对一条日志消息的封装对象)的接口,包括几个已实现的API:pcMing工作室&ryWg'h!~


arGq?4\0ConsoleHandler:向控制台输出。
+dn"GS.}"y6nQ0
4U_utq3L0FileHandler:向文件输出。pcMing工作室F"e5BN rP @]8u
pcMing工作室$^XY3J cwLI/]
SocketHandler:向网络输出。
pcMing工作室*w7mP4Za j]


T`1m^o r%y7_0    这三个输出控制器都是StreamHandler的子类,另外Handler还有一个MemoryHandler的子类,它有特殊的用处,我们在后面将会看到。在程序启动时默认的Handler是ConsoleHandler,不过这个是可以配置的,下面会谈到logging配置文件的问题。
ra+B'm-q9@N'qC0    此外用户还可以定制自己输出控制器,继承Handler即可,通常只需要实现Handler中三个未定义的抽象方法:pcMing工作室;H6Z[L1x F3g

publish:主要方法,把日志记录写入你需要的媒介。
A;F6QxU4PMJ0pcMing工作室%w-O:Q4d$GO
flush:清除缓冲区并保存数据。pcMing工作室i@5sUJ7pr1j
pcMing工作室H@KI(qU n?Tc
close:关闭控制器。
pcMing工作室b ce1O?9FaN

publish:主要方法,把日志记录写入你需要的媒介。pcMing工作室pr!ay/Sy wWC
pcMing工作室"j{~kD_b
flush:清除缓冲区并保存数据。
;pPi-AJa2[B9_0
i)_u#rf(C0close:关闭控制器。


I lV_4V#g0zd0    通过重写以上三个方法我们可以很容易就实现一个把日志写入数据库的控制器。
J3~+F/a+\} j0

d a0lu9_+x&wV0

3. 自定义输出格式:FormatterpcMing工作室LvS-C:m6_
    除了可以指定输出媒介之外,我们可能还希望有多种输出格式,譬如可以是普通文本、HTML表格、XML等等,以满足不同的查看需求。Logging API中的Formatter就是这样一个提供日志记录格式化方法接口的类。默认提供了两种Formatter:
ic ^bLe0pcMing工作室+s0W9r(?9Z @7l6@
SimpleFormatter:标准日志格式,就是我们通常在启动一些诸如TomcatJBoss之类的服务器的时候经常能在控制台下看到的那种形式,就像这样:pcMing工作室Y'U2[0Z*h,~3C6Ge

pcMing工作室$GYw+_._3I/A
2004-12-20 23:08:52 org.apache.coyote.http11.Http11Protocol initpcMing工作室9ZBw&}X%N:}S]
信息: Initializing Coyote HTTP/1.1 on http-8080pcMing工作室 Qem0P"KzX
pcMing工作室:U:pT%@$W!}-C!|9L
2004-12-20 23:08:56 org.apache.coyote.http11.Http11Protocol init
zt.Mt{r&A{0信息: Initializing Coyote HTTP/1.1 on http-8443
pcMing工作室;sW-Yo,Wj$E


Ay!Uq5P`K0XMLFormatter:XML形式的日志格式,你的Logger如果add了一个new XMLFormatter(),那么在控制台下就会看到下面这样的形式,不过更常用的是使用上面介绍的FileHandler输出到XML文件中:

!k R7HP4bd(s:QJ5r4j0

<?xml version="1.0" encoding="GBK" standalone="no"?>pcMing工作室J4[9p^)CaFT
<!DOCTYPE log SYSTEM "logger.dtd">pcMing工作室 \hpR;b
<log>pcMing工作室 q dj*Pk*o1O3}
<record>
6P,Q'v4cz0  <date>2004-12-20T23:47:56</date>pcMing工作室B`.xv-g(N? yVJ
  <millis>1103557676224</millis>
.Bu'z)?cm._0  <sequence>0</sequence>
J1m;?0z7]/@5gH0A7l }*e0  <logger>Test</logger>
;{wo&J Ju T0  <level>WARNING</level>pcMing工作室_)h6j[,W5w v'Dh X
  <class>Test</class>
#nh7AJ.I0  <method>main</method>
yQHy9m/WJB0  <...

J'r'~Z#Wm0
TAG: log4j
顶:146 踩:118
对本文中的事件或人物打分:
当前平均分:-0.36 (681次打分)
对本篇资讯内容的质量打分:
当前平均分:-0.28 (665次打分)
【已经有638人表态】
104票
感动
74票
路过
75票
高兴
69票
难过
78票
搞笑
66票
愤怒
86票
无聊
86票
同情
上一篇 下一篇
发表评论
换一张

网友评论仅供网友表达个人看法,并不表明本网同意其观点或证实其描述。

查看全部回复【已有0位网友发表了看法】

网络资源