- 浏览: 5956 次
-
最新评论
3.断言的使用
assertion(断言)是Java1.4引入的一个新特性,该特性的引入的目的是为了辅助开发人员调试和测试,是一种比较常用的调试、测试方案。assertion在软件开发过程中是一种比较常用的调试方法;不仅仅如此,使用assertion可以在开发过程中证明程序的正确性,只是这种用法会对系统的整体设计存在很大的挑战,而且目前很少投入到实用里面,所以一般情况下使用assertion的目的是为了调试和测试。
i.assertion概念以及基本用法
在代码实现的时候,需要使用关键字assert,而assertion本身在程序里面就是一条语句,它的作用是对boolean表达式进行检查,正确保证这个boolean表达式在程序运行到此刻的时候为true;一旦这个boolean表达式为false的话,就说明该程序已经处于了不正确的执行状态了,系统在断言开启的情况下会根据相关情况给出警告或者退出。
当在程序开发过程中,一般情况下使用assertion来保证整个应用程序里面最基本的、关键的正确性,而在操作过程中一般是开发和测试的时候开启该功能,一旦等软件开发完成过后,为了提高程序性能,发布的时候就将断言关闭。
1)语法:
Java里面使用assert关键字来支持assertion,其本身包括了两种表达方式:
[1]assert 表达式1;
[2]assert 表达式1:表达式2;
以上两种语法里面,表达式1表示一个boolean表达式,而表达式2一般是一个基本类型或者对象,这里需要说明的是在开发过程一般表达式2写的都是字符串以提供该断言失败的信息,但是真正在使用的时候应该理解的是表达式2也可以是某个对象或者基本类型,这里通过一个简单的例子来初次接触断言:
/**
*断言使用的概念说明代码
**/
public class AssertionDriver {
public static void main(String args[]){
Employee employee = new Employee();
employee.setName("Lang Yu");
employee.setEmail("silentbalanceyh@126.com");
businessProcess(employee);
}
public static void businessProcess(Employee employee){
try{
assert employee.getName() != null &&
employee.getEmail() != null &&
employee.getPassword() != null:
employee;
}catch(AssertionError error){
System.out.println(error);
}
}
}
class Employee{
private String name;
private String email;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString(){
return "\nName:" + name + "\n" + "Email:" + email + "\n" + "Password:" + password;
}
}
上边代码段使用了第二种表达式的方式,但是这里复杂的地方在于表达式2不是一个字符串,而是定义的Employee类的一个对象的实例,也就是说这里表达式2是一个Object实例,然后编译该代码,运行的时候打开断言,就可以得到下边的输出:
java.lang.AssertionError:
Name:Lang Yu
Email:silentbalanceyh@126.com
Password:null
【*:当断言中表达式1返回false的时候,try块里面就抛出了AssertionError类型的断言错误,然后在catch块里面会将该错误打印出来,这种错误的格式为:java.lang.AssertionError:object.toString(),因为这里重写了Employee的toString方法,根据输出结果可以知道,返回false的boolean表达式为子表达式:(employee.getPassword() != null)】
通常,在对某个对象执行关键操作时会需要对它创建断言。这有助于增强代码的健壮性,比如如果在程序中出现了某种错误,可以更方便地调试程序。这样做要比程序在某处执行失败造成不良后果来发现错误要好得多。当知道程序失败是由于它违反了假设而引起的时候,跟踪失败的原因要简单得多。
2)语义:
在运行的时候,如果关闭了assertion功能,这些语句将不会起任何作用,JVM默认assertion的功能是关闭的,如果要上边这段代码输出该结果还需要一定的操作。如果assertion功能被打开,那么JVM会先计算表达式1的值,如果它为false,该语句会抛出一个AssertionError异常。若assertion语句包括了表达式2参数,程序将计算表达式2的结果,然后将这个结果作为AssertionError的构造函数的参数,用来创建AssertionError对象,并抛出该对象,若表达式1值为true,表达式2将不被计算。
这里简单看看AssertionError的API文档说明构造函数的定义:
AssertionError()
AssertionError(boolean detailMessage)
AssertionError(char detailMessage)
AssertionError(double detailMessage)
AssertionError(float detailMessage)
AssertionError(int detailMessage)
AssertionError(long detailMessage)
AssertionError(Object detailMessage)
在讲的断言里面,当表达式1为false的时候,就需要构造AssertionError对象,构造的时候,传入的就是表达式2,也就是说在使用assertion的时候,AssertionError构造函数的实参就是真正在运行的表达式2,这样也可以理解表达式2为什么可以是基础类型,也可以是Object。
这里再提供几个简单的代码段,加深印象:
assert 0 < value;
assert 0 < value:"value = " + value;
assert ref != null:"ref doesn't equal null";
assert isValid();
【*:再提醒一点,既然表达式1是一个boolean表达式,那么可以是一个返回值为boolean的函数。】
3)编译和运行:
【编译】
由于assert是JDK 1.4才出来的关键字,使用老版本的JDK是无法编译带有assert的程序的,因此在使用javac命令编译该代码的时候,必须使用JDK 1.4或者更新的Java编译器,如果编译的时候因为无法识别assert关键字报错,那么需要加上编译参数-source 1.4这里的版本号至少是1.4或者以上的。直接使用javac命令编译的时候-source 1.4表示使用JDK 1.4版本的方式来编译源代码,版本太低带有assert关键字的代码就无法通过编译。关于javac和java命令的内容后边会有专程的章节介绍
【运行】
在运行带有assert语句的程序时,使用了新的ClassLoader的Class类,因此必须保证程序在JDK 1.4以及以上的版本或者JRE 1.4以及以上的版本环境里面运行。而在运行的时候,因为JVM默认是关闭了assertion功能的,所以要使用assertion功能的话必须显式使用加入参数来选择启用或者禁用断言。另外,断言的参数可以使得java应用程序可以开启一部分类或包的assertion功能,所以运行相对编译而言,比较复杂,这里有两类参数需要说明:
[1]参数-esa和-dsa:
该含义为开启(关闭)系统类的assertion功能。由于新版的Java的系统类中,也使用了assertion语句,如果用户需要观察它们本身的运行情况就需要打开assertion功能,可以使用参数-esa参数打开,使用-dsa参数关闭。 -esa和-dsa的全名为-enablesystemassertions和-disenablesystemassertions,全名和缩写名具有同样的效果。
[2]参数-ea和-da:
它们的含义为开启(关闭)用户类的assertion功能:通过使用该参数,用户可以打开某些类或者包的assertion功能,同样用户也可以关闭某些类和包的assertion功能。打开assertion功能的参数为-ea;如果不带任何参数,表示打开所有用户类;如果带有包名称或者类名称,就表示打开这些类或包的assertion功能。-ea和-da的全名为-enableassertions和-disenableassertions
这里提供一个表格来说明运行时断言参数的用法
参数 例子 说明
-ea java -ea 打开所有用户类的assertion
-da java -da 关闭所有用户类的assertion
-ea:<classname> java -ea:AssertionDriver 开打AssertionDriver类的assertion
-da:<classname> java -da:AssertionDriver 关闭AssertionDriver类的assertion
-ea:<packagename> java -ea:packagename 打开packagename包的assertion
-da:<packagename> java -da:packagename 关闭packagename包的assertion
-ea:... java -ea:... 打开缺省包(无名包)的assertion
-da:... java -da:... 关闭缺省包(无名包)的assertion
-ea:<packagename>... java -ea:packagename... 打开packagename包以及其子包的assertion
-da:<packagename>... java -da:packagename... 关闭packagename包以及其子包的assertion
-esa java -esa 打开系统类的assertion
-dsa java -dsa 关闭系统类的assertion
综合使用 java -dsa:ClassOne:pkgOne 关闭ClassOne类和pkgOne包的assertion
在上边的表格里面,需要说明的是:
[1]...代表该包和该包对应的子包,如果系统有两个包分别为pkgOne和pkgOne.subpkg,则pkgOne...就代表这两个包
[2]可是使用编程的方式来禁用或者启用assertion,下边提供一段代码来说明该功能:编程方式的assertion
/**
*使用程序开启断言的代码段
**/
class Loaded
{
public void go()
{
try
{
assert false:"Loaded.go()";
}
catch(AssertionError error){
System.out.println(error);
}
}
}
public class LoaderAssertions
{
public static void main(String args[])
{
ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true);
new Loaded().go();
}
}
直接运行上边的代码会有下边的输出:
java.lang.AssertionError: Loaded.go()
除了上边的代码使用的setDefaultAssertionStatus方法以外,ClassLoader的API里面还有以下几种方法:
setDefaultAssertionStatus:用于开启/关闭assertion功能
setPackageAssertionStatus:用于开启/关闭某些包的assertion功能
setClassAssertionStatus: 用于开启/关闭某些类的assertion功能
clearAssertionStatus:用于关闭assertion功能
ii.关于断言的设计和使用
1)assertion的设计:
assertion在开发中是必要的,如果没有统一的assertion机制,Java程序通常使用if-else或者switch-case语句来进行assertion的检查,而且检查的数据类型有可能不相同,但是一旦有了assertion机制过后,Java程序员就可以使用统一的方式来处理assertion的问题,而不是按照每个人不同的习惯以及方式来处理。而且还有一点,断言在未开启的情况下,发布代码的过程是不起任何作用的,只是在调试和测试阶段发生作用,一般情况在发布过程就关闭assertion功能,如果用户按照自己的方式处理进行相关检查,这些代码在发布过后不可能像assertion一样失去作用,就使得用户自己方式处理的assertion代码部分会影响程序本身的性能。Java提供的assertion统一机制,从语言层次上讲,使得assertion对系统性能的影响减小到最小。
Java通过增加一个关键字assert来实现assertion机制,而不是使用一个函数来支持,这说明java已经将assertion机制内置到语言内部称为了语言特性,本身在设计上java语言认为assertion机制是很重要的。
Java中的assertion的开启和C语言不太一样:
在C语言里面,assertion的开启是编译时决定的,可以在debug版本里面开启而直接在release版本中自动关闭;在Java里面,assertion的开启和关闭是在运行时决定的。两种方式各有优缺点。如果采取编译时决定,开发人员将处理两种类型的目标文件,就debug版本和release版本,这种方式加大了文档的管理难度,但是提高了代码的运行效率。而运行时的方式,所有的assertion都会放到目标代码里面,统一目标代码可以使用不同的方式运行,增加了一部分代码灵活性,但是牺牲了一部分性能。但是从语言发展角度,既然从纯面向过程的C的设计演变到C++的过程和对象共存的设计再到Java的纯面向对象的设计就知道,实际上Java里面这种性能的损失是可以忽略不计的,所以java这中使用运行时决定assertion的机制的方式是真正在开发过程中的首选。
另外,有一点我在异常章节里面已经提及过的,在上边的代码里面,AssertionError类作为了Error的子类,而不是RuntimeException。这一点一致都没有想过为什么,实际上所有Error的子类里面,只有这一个类可以使用catch块进行捕捉,那么按照对异常的理解,AssertionError本应该是属于一个RuntimeException而不应该是一个Error,那么回到上边关于Error和RuntimeException的定义:
Error通常代表一些错误,不可以恢复
RuntimeException强调了该错误是在运行时才发生的
那么AssertionError通常情况下是在调试以及测试过程打开了断言开关才会遇到的关键性错误,这里遇到的错误,往往是整个程序关键性的缺陷,这一点在后边使用assertion的时候会介绍assert关键字的使用场合。从这点来讲,与AssertionError有关的错误往往都是没有办法恢复的,而且assertion机制也不鼓励程序员通过编程或者其他手段来恢复折中类型的错误。因此,为了强调Assertion机制的概念以及定义,AssertionError作为了一个Error的子类进行设计,并且它的运行原理类似RuntimeException,在运行过程中是可以直接被catch块捕捉到的。
2)assertion和继承
那么在assertion机制里面,父类和子类在assertion的开启和关闭的时候会发生什么现象呢,这里提供一段代码:
/**
*讨论assertion和继承的关系
**/
class SuperClass{
public void superMethod()
{
assert false:"Assert:This is Super Class";
System.out.println("Super Method");
}
}
public class SubClass extends SuperClass{
public void subMethod()
{
assert false:"Assert:This is Sub Class";
System.out.println("Sub Method");
}
public static void main(String[] args)
{
try
{
SubClass assertClass = new SubClass();
assertClass.superMethod();
assertClass.subMethod();
}
catch(AssertionError error)
{
System.out.println(error);
}
}
}
这里使用几种情况来运行:【*:确保编译的时候使用的是1.4和1.4以上的JDK版本】
[1]直接运行的输出:java SubClass(这种方式和全关闭断言的方式一样的输出:java -da SubClass)
Super Method
Sub Method
[2]全开断言的输出:java -ea SubClass
java.lang.AssertionError: Assert:This is Super Class
[3]仅仅打开父类断言输出:java -ea:SuperClass SubClass
java.lang.AssertionError: Assert:This is Super Class
[4]仅仅打开子类断言的输出:java -ea:SubClass SubClass
Super Method
java.lang.AssertionError: Assert:This is Sub Class
仔细分析上边的输入输出可以知道:当父类的assertion只有在父类的assert开启的时候会起作用,如果仅仅打开子类的assert,父类的assert不会运行,比如上边[4]的输出,SuperClass的assert没有起任何作用,由此可以认为,assert语句不具有继承功能。
【*:其实仔细想想,这一点从语言本身设计上是合理的,assert机制本身的存在是辅助开发、调试、测试,因为有这样的一个assert存在,使得程序的数据流能够在合法的范围内在程序里面运行,而assert机制本身是类似踩点方式,如果觉得什么地方出现了程序的关键性问题,这里置放一个assertion用来确保数据的准确性,使得调试的时候不用去考虑数据在这个地方是否合法,因为一旦不合法可以通过assert进行记录,以及很容易发现assert监控的数据出问题的地方,如果assert支持继承的话,在一个设计得比较好的继承树系统里面,本身初衷是为了查询某个点的assert标记进行调试,如果它支持了继承,那么assert会形成一个不必要的链式结构,这种方式反而不利于找到程序的关键部分,所以个人觉得从概念上讲,assert本身不支持继承的设计也是蛮合理的。】
3)assertion的使用:
assertion既然能够辅助调试、测试以及开发,那么在使用assertion的时候,问题会变得相对复杂,因为这里会涉及到不同的程序员在使用assertion的目标、程序的性质以及各自不同的代码风格问题。
一般来讲,assertion主要用来检查一些程序的关键性问题,并且对整个程序或者局部功能的完成起到很大的影响,这种错误不是数据的非法性,往往这些部分可能导致整个程序出现不可恢复的问题。这里提供几种简单的应用:
[1]检查控制流:
在这个例子中,不应该发生的控制流上边使用assert false
public class AssertionUseOne
{
public static void main(String args[])
{
int a = 1;// 1,2,3,4
switch(a)
{
case 1:...;break;
case 2:...;break;
case 3:...;break;
default:assert false:"a is not valid";
}
}
}
[2]检查私有函数的输入参数:
对于一些私有函数,要求输入满足一定的特殊条件,那么可以在函数开始对参数进行assert检查,这个和公有函数不一样,公有函数一般都是由外部和内部调用,一旦输入有什么问题就可以知道,因为共有函数本应该检查无效的参数,而且必须对无效的参数进行处理,但是私有函数是可以直接使用的,而且不能由外部调用,那么在使用过程可以使用assert来检查私有函数的参数输入合法性。【*:确保在运行时断言是打开的】
public class AssertionUseTwo
{
public static void main(String args[])
{
System.out.println(AssertionUseTwo.test(10,5));
}
private static int test(int a,int b)
{
assert b != 0:"b can not be zero!";
return a / b;
}
}
[3]检查函数结果:
在函数计算过后,检查函数的结果是否有效,对于一些做运算的函数,在函数返回某个结果的时候,要确保这些结果具有一定的性质,因此可以通过使用assert来检查该值。所以这种情况可以在return语句之前检查函数的结果是否复合某种性质
[4]检查程序不变量:
有些程序在运行的整个周期里面存在一定的不变量,而这些量在整个生命周期都是不会改变的,但是有时候这个不变量有可能是表达式。对于一些关键不变量,可以通过assert进行检查,这种情况就可以利用下边的方式来书写:
public class AssertionUseFour
{
public boolean flag= true;
public boolean isValid()
{
return flag;
}
public static void main(String args[])
{
AssertionUseFour assertion = new AssertionUseFour();
assert assertion.isValid():"Flag must be true!";
}
}
【*:上边的四种方式都是概念说明代码,而在实际运用中只是一个规则说明,这里特别说明第四种情况,这种情况一般是根据业务走的,比如某个银行,在某个人进行某项操作的时候必须要符合这个人的一些自身条件,而这些条件本身不会影响到整个系统的运行,而且这个条件本来就属于该对象的不变条件,也就是说即使这个人条件不满足的情况下,业务就越轨了,但是为了使得某种业务不会出现越轨的操作,在这个的Object做某个操作之前,使用assert检测一下他是否复合了这种要求就是一个比较不错的方式。也就是说在最后一个例子里面,必须理解不变量在整个程序里面的含义,只有对不变量的检测才符合断言的使用,如果在设计系统的时候用不好这种情况就不使用。】
4)关于断言的思考:
Java断言机制的出现,其主要目的是为了辅助程序员开发中的调试、测试、开发等操作,既然如此,在正规的业务流程中不能滥用,用不好有可能导致很严重的问题,有三点需要说明:
【1】请开发人员牢记,断言默认是关闭的,所有主流的IDE包括Eclipse和NetBeans在调试代码过程都没有将断言打开,如果要打开断言,需要进行相关的设置,所以有时候加入了assert在代码里面了过后,在寻找系统关键环节的时候,必须记得打开断言功能
【2】不可以使用断言去替代正常的业务逻辑:这种情况对比地说就是assert和if语句,这两者在程序开发过程中有着本质性的概念差异。if最典型的解释就是,条件满足就执行,条件不满足就不用执行;而assert一般放在系统的关键位置,assert是确保条件表达式是正确的,如果不正确表示系统有Error,所以二者在概念上有本质的差别,所以开发人员必须记得不能使用assert去替代本来的if语句。而且有一点,在发布最终代码版本的时候,断言一般会关闭的,如果使用了这样的替代方式,就意味着未发布版本和发布版本的流程不一样使得在工业生产中
【3】assert的出现,意味着如果系统在此处出现AssertionError的话证明系统已经出现了不可恢复的错误,所以assert是不能用于正规的业务逻辑的,只能用于开发和调试,这种不可恢复错误在工业生产和工程项目里面有可能导致严重的问题,所以这种情况一定要仔细考虑assert的使用和位置
4.Java中的日志(JDK 1.4 Logging Framework)
日志系统是一种不可或缺的跟踪调试工具,特别是在任何无人职守的后台程序以及那些没有跟踪调试环境的系统中有着广泛的应用。长期以来,日志系统作为一种应用程序服务,对于跟踪调试、程序状态记录、崩溃数据恢复都有非常现实的意义。而这种应用程序服务一般以两种方式存在:
[1]日志系统作为一种服务进程存在
一般服务进程存在于系统背后,如Windows本身提供的Windows事件日志服务这种,这种情况日志的发送端和接收端是异步调用方式,而这种日志系统最常用的功能就是作为系统日志存在,它记录了系统每隔一段时间的运行状况以及系统本身的一些配置记录,这种日志存在的目的是为了管理员能够更加方便地监控系统运行状态。
[2]日志系统作为系统调用存在
Java中的日志系统大部分就属于这种类型,日志系统的代码作为系统调用被编译进了日志的发送端,在书写代码的时候,遇到一些比较重要的信息的时候就将东西记录下来,这种情况下,日志的发送端就是系统本身运行的业务它们,而且这种记录方式和前者不一样的就是这种运行方式属于同步进行,在日志记录过程中,应用程序可能会等到该日志记录完成过后才会继续运行,因为日志记录的程序和业务程序本身就在同一个进程里面。
这里所讲到的日志记录就是第二种方式
i.Java世界常用的日志系统
1)Log4j:最早的Java日志记录框架之一,Apache的一个开放源代码项目,通过使用Log4j,可以控制日志信息输出的目的地是控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器等,也可以控制每一条日志输出的格式;通过定义每一条日志的级别,能够更加细致地控制日志的最终生成过程,而在这些特性的修改的时候,使用的不是编码的方式,而是通过配置文件来配置该日志记录器的一些相关特性和行为。
2)JDK1.4 Logging Framework
继Log4j之后,JDK标准委员会将Log4j的基本思想吸收到了JDK当中,在JDK1.4中发布了第一个日志框架接口,并且提供了很简单的实现。
3)Commons Logging Framework
该框架同样是Apache基金会的项目,其出现的目的是为了整合Java项目中的JDK1.4 Logging Framework框架以及Log4j,使得应用程序可以在这两种框架上进行随意切换,因此该框架提供了比较统一的调用接口和配置方法。
ii.日志系统特性:
如果需要自己来设计一个完备的日志系统,就需要考虑到日志系统的特性问题,一般情况下,日志记录的特性如下:
[1]日志的输出可以按照分类归档,这样方便在调试的时候针对不同模块的日志进行相关查询;举个例子:使用过Windows事件查看器的用户都知道平时Windows里面记录了Application日志,System日志和Security日志,如果是一个开发环境有可能还记录了单独的Office日志以及其他分类的日志,所以日志分类是一个日志系统设计的时候应该考虑的内容。
[2]其次是日志的分级,在某一类的日志里面,按照不同的级别对日志记录的信息进行区分,同样的例子:Windows事件查看器里面有Information、Warning、Error三种不同级别的日志记录
[3]支持多线程:日志记录一定是一个多线程的记录方式,而且本身应该是线程安全的,因为有可能在一个多线程程序里面不同的线程会记录不同的日志
[4]支持不同的介质:日志系统的记录介质一般包括文件系统、控制台、服务器、时间记录器、数据库等,这些相关日志记录都应该是该日志记录器可以支持的,这点特性使得日志记录器可以将日志记录在不同的地方以方便查询,常见的就是文件后缀使用.log的文本文件
[5]高性能。日志系统通常要提供高速的日志记录功能以应对大系统下大请求流量下系统的正常运转。
[6]稳定性。日志系统必须是保持高度的稳定性,不能因为日志系统内部错误导致主要业务代码的崩溃。
iii.JDK 1.4 Logging Framework:
JDK1.4引入了Logger对象,该对象用来记录特定系统或者应用程序组件的日志信息,一般情况下使用圆点分割的层次空间名称来命名Logger,Logger的名称可以是任意字符串,但是最好是基于被记录组件的包名、类名或者类全名,例如java.net或javax.swing这种格式。不仅仅如此,在使用Logger的时候也可以创建“匿名”的Logger,该名称是存储在日志记录器的名空间里面的。
先看一段代码,初次接触一下JDK 1.4 Logging Framework:【参考Java Logging规范】
package org.susan.java.logging
import java.util.logging.Filter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
public class Nose {
private static Logger logger = Logger.getLogger("org.susan.java.logging.Nose");
static class Duck{};
static class Wombat{};
static void sendLogMessages(){
logger.log(Level.WARNING,"A duck in the house!",new Duck());
logger.log(Level.WARNING,"A Wombat at large!",new Wombat());
}
public static void main(String args[]){
sendLogMessages();
logger.setFilter(new Filter(){
public boolean isLoggable(LogRecord record){
Object[] params = record.getParameters();
if( params == null ){
return true;
}
if( record.getParameters()[0] instanceof Duck){
return true;
}
return false;
}
});
logger.info("After setting filter...");
sendLogMessages();
}
}
运行以上代码,就可以在控制台里面看到该段代码的输出【Logger开始区分System.out和System.err,err用红色】:
Sep 14, 2009 10:10:41 AM org.susan.java.logging.Nose sendLogMessages
WARNING: A duck in the house!
Sep 14, 2009 10:10:41 AM org.susan.java.logging.Nose sendLogMessages
WARNING: A Wombat at large!
Sep 14, 2009 10:10:41 AM org.susan.java.logging.Nose main
INFO: After setting filter...
Sep 14, 2009 10:10:41 AM org.susan.java.logging.Nose sendLogMessages
WARNING: A duck in the house!
上边只是JDK 1.4 Logging Framework的一段简单的应用代码,主要是让初学者有个简单的印象。先根据上边代码段简单了解一下JDK里面的Logger:
Logger对象用来记录特定系统或者应用程序组件的日志消息,一般用圆点分割的层次名称空间来命名Logger,Logger名称可以是任意字符串,但是它们一般应该基于被记录组件的包名或者类名。一般情况下,可以直接使用getLogger工厂方法来获取Logger对象,Logger.getLogger(String name)方法使用的是工厂模式,如果传入的name原来的系统里面没有这样的一个Logger,那么就直接创建一个Logger的新实例,如果该Logger已经存在了,那么就直接返回该存在的实例。如上边这段代码里面的:
Logger.getLogger("org.susan.java.logging.Nose"); //在这里org.susan.java.logging.Nose就是该日志记录器的名称。
每个Logger对象都是分等级的,所以都有一个与之相关的“Level”,上边的代码可以说明:
logger.log(Level.WARNING,"A duck in the house!",new Duck()); //这里设置了日志的等级Level.WARNING
上边代码还出现的一个关键概念就是Filter,Logger日志框架里面Logger对象和Handler都有对应的关联过滤器,其作用就是对日志记录进行过滤,Filter为一个接口,当该接口的isLoggable方法返回为fales的时候,则丢弃LogRecord也就是不做任何日志记录,仔细分析上边的代码就可以发现:
WARNING: A Wombat at large!
这句输出没有出现第二次,主要是因为isLoggable方法返回了false,所以在实例化Wombat对象的时候并没有记录下来,而且是在Logger进行了相关设置过后没有输出,设置前后就是在原来的Logger基础上添加了一个Filter接口对实例的类型进行了判断。而且需要注意一点的是:Filter的创建在这个地方是作为了一个匿名内部类来实现的!
1)JDK 1.4 Logger Framework关键组件:
了解这些元素之前,先看看它们相互之间存在一个什么关系。一般情况下,应用程序把日志称为Logger对象,而在JDK 1.4 Logging Framework里面,Logger对象本身是存在一定的层次结构的,也就是说Logger对象本身就是跟踪了某个父对象“Logger”。而在整个日志记录过程里面,传入的是什么?传入的就是LogRecord对象,Logger对象会将LogRecord对象传给Handler对象发布出来,而LogRecord对象里面就记录了日志的详细信息。在整个结构里面,Logger对象本身和Handler对象都会用到Level和Filter两个对象,用这两个对象的目的是为了一些特殊的需求,或者根据不同的情况定制特殊的日志记录器来进行日志记录。而在记录过程中,如果有需要额外的需求,可以选择使用Formatter对象来进行消息的格式化,在输出的时候进行,而Formatter只有Handler可操作,Logger本身是不能操作的,通过下边的图,这个结构就一目了然了:
上图就是JDK 1.4 Logging Framework结构的整体流程,有时候会出现一些例外情况,一些Handler对象有可能直接把日志记录发布给其他的Handler对象,形成一个Handler Chain,这种情况下的流程如下:
【*:思考一个问题:为什么说Formatter只有Handler可以操作,Logger不能操作?这句话我自己是这样理解的,JDK 1.4 Logging Framework在进行日志记录的过程里面,总是通过Logger把日志记录的信息发布给Handler,而Handler做了该日志记录的统一的出口,也就是说,在做日志记录的时候,Handler至少有一个。而且Formatter是在最终发布日志的时候才会出现,也就是说Farmatter是在最后一个Handler里面才会使用到,而Formatter的使用不是必须的,是可选的,因为Formatter本质上没有做其他操作,就是对日志进行了格式化,可以这样理解,那么这样看来,在拥有Chain结构的Handler链中,Formatter一般出现的位置应该是Chain的末端,也就是Chain的最后一个Handler可能会调用Formatter对象。】
[1]Logger:
Logger对象是日志框架里面的主体对象,我们在编程过程大多数时间遇到的就是Logger对象。每个Logger对象都有一个相关的Level【下边会介绍】,而Logger的级别如何设置为null的话,那么它就会继承其父类的Logger,这一点会一直向上递归得到,而Logger的默认级别是INFO。在开发过程我们可以根据日志配置文件的属性来配置日志的级别,而默认级别就是在配置文件里面体现的,在LogManager对象里面也针对此级别有所描述,如果在运行过程有所改变,可以通过调用Logger.setLevel方法来动态的改变,如果日志级别改变了,这种变化会向下递归,其子类也会受相对的影响。对于每次日志记录调用,最初Logger都会依据有效日志级别对请求级别进行对应的检查,如果请求级别低于日志级别,则不作记录,直接返回。
Logger一般情况下通过工厂方法Logger.getLogger来获取,一旦获取过后,它将分配一个LogRecord来描述日志记录的消息,接着调用Filter【如果存在的话】进行更加详细的检查,以确定是否发布。如果检查通过,则将LogRecord发布到对应的输出Handler,默认情况会沿着递推发布到其父Handler。每个Logger还存在一个与其关联的ResourceBundle名称,用于本地化日志消息,若一个Logger没有自己的ResourceBundle名称,同样会通过递归的方式向上寻找。
Logger的很多输出方法里面都带有“msg”参数,该参数可以是一个原始值,也可以是一个本地化的键。进行Formatter操作的时候,若Logger具有一个本地化的ResourceBundle,并且里面包含了msg字符串的映射关系,那么直接用本地化的值来替换msg字符串,否则使用原有的字符串输出。将ResourceBundle名称映射到ResourceBundle时,Logger首先调用ContextClassLoader,如果为null,Logger尝试调用SystemClassLoader,若两者都为空,Logger会继续在ClassLoader中寻找ResourceBundle,试图找到一个匹配的ResourceBundle。
Logger本身的方法主要分五个类别:【这个跟Level不是一个概念】
一系列的“log”方法,这种方法带有日志级别、消息字符串以及可选的字符串参数;
一些列的“logp”方法,与“log”方法类似,但是带有显示的源类名称和方法名称;
一些列的“logrb”方法,与“logp”方法类似,但是带有显示的本地化日志消息使用的资源包名称;
还有跟踪方法(“entering”方法)、方法返回(“exiting”方法)和抛出方法(“throwing”方法);
一些便捷方法,这些方法按照日志记录的等级命名(“severe”,“warning”,“info”等),仅仅带一个参数,即消息字符串
【*:日志记录里面所有的这些方法都是线程安全的】
[2]Level:
Level类定义了一组可用来控制日志输出的标准日志级别,日志Level对象是有序的,并且是通过有序的整数来指定,在给定的级别上启用日志也就启用了所有较高级别的日志记录,而我们编程的时候不应该使用它的值常量格式,也就是Level里面的常量的真实整数值格式,应该直接使用预定义常量。(如Level.SEVERE)
其级别分为以下几种:
SEVERE(最高级)
WARNING
INFO
CONFIG
FINE
FINER
FINEST(最低值)
此外有一个OFF级别是用来关闭日志记录,另外还有个ALL启用所有消息的日志记录。若要自定义日志级别,需要子类化Level,也就是我们在开发过程中使用继承来实现Level的子类化
[3]LogRecord
LogRecord用于在日志框架和单个日志Handler之间传递日志请求。将LogRecord传递到日志框架中过后,它在逻辑上是属于此框架的,客户端应用程序不可以再使用或者更新它的记录,可以这样理解:大多数情况下LogRecord都是只读的。
【*:如果客户端应用程序尚未显示指定源方法和源类名,则LogRecord类将在第一次访问它们的时候通过解析调用堆栈来自动推导。因此如果使用RMI的方式传输LogRecord的时候,如果希望它能够在后续操作里面获取方法名和类名,应该调用getSourceClassName或者getSourceMethodName其中之一来强制填入某些信息。】
LogRecord在序列化过程需要注意:
LogRecord类是可序列化的
因为参数数组中的对象可能不可序列化,所以在序列化过程,应该写入参数数组中所有对象的相应String
ResourceBundle不作为序列化的一部分传输,而在接受该对象的时候readObject方法自己会尝试查找合适的资源包
[4]LoggingPermission
当SecurityManager运行的代码调用某个日志记录方法的时候,SecurityManager将会检查其权限。当前只有一个指定LoggingPermission,就是“control”,此权限授予了控制日志记录配置的能力,如添加、移除Handler或者Filter,设置日志记录级别。不过牵涉到安全部分一般情况我们不会直接通过编程的方式来创建LoggingPermission对象,这些对象一般都是由安全celue代码根据读取的安全策略文件创建的。
[5]LogManager
在整个日志框架里面,存在一个单一的全局LogManager对象,它可以用于维护Logger和日志服务的一组共享状态。LogManager的作用在于:
管理Logger对象的层次结构名称空间,所有指定的Logger均存储在此名称空间中
管理一组日志控制属性,这些事提供Handler以及其他日志对象用于自我配置的键值对
编程过程可是使用LoggerManager.getLogManager()获取全局的LogManager对象,LogManager对象是在类初始化过程中创建的,过后便不可以更改。在启动的时候,使用java.util.logging.manager系统属性定位LogManager类。默认情况下,LogManager会从JRE目录的属性文件“lib/logging.properties”中读取其初始配置,如果编辑该属性文件,则可更改此JRE的所有用户的默认日志配置。另外LogManager还有两个可选的允许更好地控制初始配置读取的系统属性:
“java.util.logging.config.class”
“java.util.logging.config.file”
这两个属性也可以通过JPA类配置,既作为“java”命令的命令行属性定义,也可以作为传递到JNI_CreateJavaVM的系统属性定义。若设置了“java.util.logging.config.class”属性,则会把属性值当做类名,LogManager将会去加载该类,并且根据该类的定义去实例化一个对象,该对象的构造方法会去读取初始配置。此类可以作为系统的备用配置类,备用配置类可以使用readConfiguretion(InputStream)来定义LogManager中的属性。若“java.util.logging.config.class”没有设置,将会使用“java.util.logging.config.file”系统属性来制定一个属性文件【.properties格式】,从此文件读取对应的配置。若两个都没定义,则从默认的JRE目录的属性文件“lib/logging.properties”中读取其初始配置。
这里看一段代码,简单说明一下LogManager如何进行Logger的管理:
package org.susan.java.logging;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.logging.XMLFormatter;
/**
*关于LogManager最简单的用法,同时引入Handler的概念说明代码
**/
public class LoggingManager {
public static void main(String args[]){
try{
// 这里通过LogManager的静态方法获取全局的LogManager,而且还创建了一个FileHandler
LogManager lManager = LogManager.getLogManager();
Logger logger;
FileHandler fileHandler = new FileHandler("log_test.xml");
// 这一段代码设置相互之间的关系,FileHandler设置了输出格式为一个XML格式的
logger = Logger.getLogger("org.susan.java.logging.LoggingManager");
lManager.addLogger(logger);
logger.setLevel(Level.INFO);
fileHandler.setFormatter(new XMLFormatter());
logger.addHandler(fileHandler);
// 这里开始进行对应的日志记录
logger.log(Level.INFO,"test 1");
logger.log(Level.INFO,"test 2");
logger.log(Level.INFO,"test 3");
fileHandler.close();
}
catch(Exception ex){
System.out.println("Exception thrown:" + ex);
ex.printStackTrace();
}
}
}
这段代码有些特殊就在于会有两处输出,在控制台里面可以看到以下输出:
Sep 14, 2009 2:55:24 PM org.susan.java.logging.LoggingManager main
INFO: test 1
Sep 14, 2009 2:55:24 PM org.susan.java.logging.LoggingManager main
INFO: test 2
Sep 14, 2009 2:55:24 PM org.susan.java.logging.LoggingManager main
INFO: test 3
而且在运行目录下还会输出一个文件log_text.xml,这个文件的内容如下:
<?xml version="1.0" encoding="windows-1252" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2009-09-14T14:55:24</date>
<millis>1252911324403</millis>
<sequence>0</sequence>
<logger>org.susan.java.logging.LoggingManager</logger>
<level>INFO</level>
<class>org.susan.java.logging.LoggingManager</class>
<method>main</method>
<thread>10</thread>
<message>test 1</message>
</record>
<record>
<date>2009-09-14T14:55:24</date>
<millis>1252911324416</millis>
<sequence>1</sequence>
<logger>org.susan.java.logging.LoggingManager</logger>
<level>INFO</level>
<class>org.susan.java.logging.LoggingManager</class>
<method>main</method>
<thread>10</thread>
<message>test 2</message>
</record>
<record>
<date>2009-09-14T14:55:24</date>
<millis>1252911324417</millis>
<sequence>2</sequence>
<logger>org.susan.java.logging.LoggingManager</logger>
<level>INFO</level>
<class>org.susan.java.logging.LoggingManager</class>
<method>main</method>
<thread>10</thread>
<message>test 3</message>
</record>
</log>
[6]Filter和Formatter
Filter:
Filter本身属于一个接口,前边已经讲得很清楚了,其作用就是对日志记录进行过滤,Filter为一个接口,当该接口的isLoggable方法返回为false的时候,则丢弃LogRecord也就是不做任何日志记录。这里再提供一个例子可以让我们自定义Filter:
package org.susan.java.logging;
import java.util.logging.Filter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
class Person{
private String name = null;
private int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class AgeFilter implements Filter{
public boolean isLoggable(LogRecord record){
boolean result = false;
Object[] objs = record.getParameters();
Person person = (Person)objs[0];
if( person != null ){
int age = person.getAge();
if( age > 30 )
result = true;
else
result = false;
}
return result;
}
}
public class CustomFilter {
public static void main(String args[]){
Logger logger = Logger.getLogger("org.susan.java.logging.CustomFilter");
AgeFilter filterAgeFilter = new AgeFilter();
logger.setFilter(filterAgeFilter);
Person person = new Person("LangYu",32);
logger.log(Level.INFO, "Person has age " + person.getAge(), person);
}
}
运行上边代码可以看到输出为:
Sep 14, 2009 3:15:03 PM org.susan.java.logging.CustomFilter main
INFO: Person has age 32
上边代码定义了一个Filter,是对Person类的对象年龄进行过滤然后记录的,这段代码初始化的时候person的年龄是32,如果传入一个小于30的数,那么该Filter里面的isLoggable方法就会返回false,什么输出都不会有,而且需要留意代码:class AgeFilter implements Filter,记住Filter是一个接口并不是一个类。
Formatter:
Formatter为格式化LogRecords提供支持。一般来说,每个日志记录Handler都有关联的Formatter。Formatter接受LogRecord,并将它转换为一个字符串。有些Formatter(如 XMLFormatter)需要围绕一组格式化记录来包装头部和尾部字符串。可以使用getHeader和getTail方法来获得这些字符串。同样这里给一个使用了LogRecord类的XMLFormatter类的例子:
package org.susan.java.logging;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.XMLFormatter;
public class CustomFormatter {
public static void main(String args[]) throws Exception{
XMLFormatter formatter = new XMLFormatter();
LogRecord record = new LogRecord(Level.INFO,"XML message...");
FileHandler handler = new FileHandler("formatter.xml");
handler.setFormatter(formatter);
handler.publish(record);
handler.flush();
}
}
这段代码控制台不会有任何输出,但是生成的formatter.xml文件的内容如下:
<?xml version="1.0" encoding="windows-1252" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2009-09-14T15:28:33</date>
<millis>1252913313104</millis>
<sequence>0</sequence>
<level>INFO</level>
<thread>10</thread>
<message>XML message...</message>
</record>
上边是XMLFormatter的一个简单的应用。
关于Filter和Formatter确实在整个日志系统框架里面没有太多的重难点,只要做几个简单的例子就能够理解Formatter和Filter的用法,下一小节会深入日志框架里面核心部分,之所以说比较核心是因为它的子类比较多,而且在整个包的API里面占了很大的比重。不过Formatter有两个子类,一个是SimpleFormatter,记录日志的格式如同我们平时启动Tomcat服务器的时候服务器上的输出;另外一种就是XMLFormatter,其格式就如同上边我们看到的代码
[7]Handler:
Handler对象从Logger中获取日志信息,并且可以将这些信息导出,它可以将这些信息写入控制台或者文件,也可以将这些信息发送到网络日志服务器上,或者转发到其他操作系统日志中。如果要禁用Handler,就直接使用setLevel(Level.OFF)来操作,并且可以根据适当的级别来重新启用。在java.util.logging包里面,先看看整个Handler的继承树快照:
java.util.logging.Handler
|—java.util.logging.MemoryHandler
|—java.util.logging.StreamHandler
|—java.util.logging.ConsoleHandler
|—java.util.logging.FileHandler
|—java.util.logging.SocketHandler
MemoryHandler:
MemoryHandler在内存中的循环缓冲区中对请求进行缓冲处理:通常情况下,该Handler只将传入的LogRecord存储到内存缓冲区,并且丢弃原来的记录。此缓冲很经济,为避免格式化开销,在一定的触发条件下,MemoryHandler将其当前的缓冲区内容push到目标Handler中,通常情况下此Handler将内容发布到外界。该触发条件包括:
传入的LogRecord类型大于预定义的pushLevel级别
外部类显示调用push方法
如果记录符合某些标准,则子类重写log方法,并扫描每个传入的LogRecord,调用push
配置:默认情况下,使用LogManager配置属性初始化每个MemoryHandler,如果没有定义,则使用指定的默认值。当默认值读取不到的时候会抛出RuntimeException。
java.util.logging.MemoryHandler.level指定Handler的级别(默认为Level.ALL)
java.util.logging.MemoryHandler.filter指定要使用的Filter类的名称(默认为无Filter)
java.util.logging.MemoryHandler.size定义缓冲区的大小(默认为1000)
java.util.logging.MemoryHandler.push定义pushLevel(默认为Level.SEVERE)
java.util.logging.MemoryHandler.target制定目标Handler类的名称(无默认值)
StreamHandler:
该Handler是基于流的日志Handler,此类主要作为基类,或支持实现其他日志Handlers所用的类,直接将LogRecord发布到给定的java.io.OutputStream
配置:
java.util.logging.StreamHandler.level指定Handler的默认级别(默认值为 Level.INFO)
java.util.logging.StreamHandler.filter指定要使用的Filter类的名称(默认值非 Filter)
java.util.logging.StreamHandler.formatter指定要使用的Formatter(默认值为java.util.logging.SimpleFormatter)
java.util.logging.StreamHandler.encoding要使用的字符集编码的名称(默认值为默认平台编码)
ConsoleHandler:
此Handler用于向控制台发布日志记录,是StreamHandler的子类,默认情况下直接使用SimpleFormatter生成简短摘要,但是注意一点发布到控制台的时候是向System.err发布,也就是说在IDE环境下很可能输出为红色字体
java.util.logging.ConsoleHandler.level为Handler指定默认的级别(默认为 Level.INFO)
java.util.logging.ConsoleHandler.filter指定要使用的Filter类的名称(默认为无 Filter)
java.util.logging.ConsoleHandler.formatter指定要使用的Formatter类的名称(默认为java.util.logging.SimpleFormatter)
java.util.logging.ConsoleHandler.encoding指定要使用的字符集编码的名称(默认为使用默认平台的编码)
SocketHander:
此Handler用于将LogRecord发布到网络日志,默认格式化为XMLFormatter,而且输入IO是缓冲的,每次写入LogRecord的时候都会刷新
java.util.logging.SocketHandler.level指定Handler的默认级别(默认值为 Level.ALL)
java.util.logging.SocketHandler.filter指定要使用的Filter类的名称(默认值非 Filter)
java.util.logging.SocketHandler.formatter指定要使用的Formatter(默认值为java.util.logging.XMLFormatter)
java.util.logging.SocketHandler.encoding要使用的字符集编码的名称(默认值为默认平台编码)
java.util.logging.SocketHandler.host指定要连接到的目标主机名(无默认值)
java.util.logging.SocketHandler.port指定要使用的目标TCP端口(无默认值)
FileHandler:
FileHandler可与将日志写入指定的文件,也可以写入文件轮换集。对于文件轮换集而言,到达每个文件的给定大小限制后,就关闭该文件,将其轮换出去,并打开新的文件。通过在基本文件中添加“0”,“1”,“2”等来依次命名旧文件。默认情况下,IO库中启用了缓冲,当缓冲完成的时候,每个记录都会被刷新,默认情况下,FileHandler的格式化输出使用的也是XMLFormatter。
java.util.logging.FileHandler.level为Handler指定默认的级别(默认为 Level.ALL)
java.util.logging.FileHandler.filter指定要使用的Filter类的名称(默认为无 Filter)
java.util.logging.FileHandler.formatter指定要使用的Formatter类的名称(默认为java.util.logging.XMLFormatter)
java.util.logging.FileHandler.encoding指定要使用的字符集编码的名称(默认使用默认的平台编码)
java.util.logging.FileHandler.limit指定要写入到任意文件的近似最大量(以字节为单位)如果该数为0,则没有限制(默认为无限制)
java.util.logging.FileHandler.count指定有多少输出文件参与循环(默认为1)
java.util.logging.FileHandler.pattern为生成的输出文件名称指定一个模式。有关细节请参见以下内容(默认为 "%h/java%u.log")
java.util.logging.FileHandler.append指定是否应该将FileHandler追加到任何现有文件上(默认为 false)
文件作日志记录的时候会遇到模式匹配问题,模式包括了以下特殊组件的字符串,则运行时需要替换这些组件:
"/" 本地路径名分隔符
"%t" 系统临时目录
"%h" "user.home"系统属性的值
"%g" 区分循环日志的生成号
"%u" 解决冲突的唯一号码
"%%" 转换为单个百分数符号"%"
上边出现过的日志里面的模式匹配会在日志记录的时候去读取相对应的值
2)关于配置文件:
[1]配置相关说明:
有一点需要说明,该日志记录框架是JDK 1.4 Logger Framework,那么我们在使用该框架的时候对JDK的要求是1.4以及以上的版本。默认情况下logging.properties的位置位于JAVA_HOME/jre/lib/logging.properties,在我们使用命令行执行某个程序需要使用自定义的配置文件的时候,需要按照下边这中方式来执行:
java -Djava.util.logging.config.file=D:\logging.properties LoggingTester
配置文件的记录格式说明如下:
Logger和Handler的属性名称是以圆点分隔的Logger或Handler的名称开头。
全局日志属性可以包括:
属性"handlers"。该属性为handler类定义类名的空白或逗号分隔列表,以便作为处理程序在根Logger(该Logger名为"")中加载和注册。每个类名必须用于具有默认构造方法的Handler类。注意,刚开始使用这些Handler时,它们可能是以延迟方式创建的。
属性"<logger>.handlers"。该属性为handler类定义空白分隔或逗号分隔的列表,以便作为处理程序加载和注册到指定的logger。每个类名必须用于一个具有默认构造方法的Handler类。注意,刚开始使用这些Handler时,它们可能是以延迟方式创建的。
属性"<logger>.useParentHandlers"。该属性定义一个boolean值。默认情况下,每个logger除了自己处理日志消息外,还可能调用其父级来处理,这往往也会导致根logger来处理消息。将此属性设置为false时,需要为此logger配置Handler,否则不传递任何消息。
属性"config"。此属性允许运行任意配置代码。该属性定义类名的空白或逗号分隔的列表。为每个指定类创建新实例。每个类的默认构造方法都可以执行任意代码来更新日志配置,如设置logger级别、添加处理程序、添加过滤器,等等。
注意,在LogManager配置期间加载的所有类,其搜索顺序是先从系统类路径中搜索,然后才从用户类中搜索。这包括LogManager类、任何config类和任何handler类。Logger是按其圆点分隔的名称被组织到命名层次结构中的。因此,"a.b.c"是"a.b"的子级,但"a.b1"和a.b2"属于同一级。假定所有以".level"结尾的名称的属性为Logger定义日志级别。因此,"foo.level"就为名称为"foo"的logger定义了日志级别,进而为指定层次结构中它的所有子级也逐个定义了日志级别。日志级别是按其在属性文件中的定义顺序应用的。因此,树中子节点的级别设置应该迟于其父级设置。属性名".level"可用于设置树的根级。
[2]配置文件例子:
# handlers
handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler
# general level
.level=INFO
# file handler
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
# console handler
java.util.logging.ConsoleHandler.level = FINEST
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
test.de.jayefem.log4e.logkits.JDK1_4_Logging.level = FINEST
[3]这里再提供一段读取远程配置文件的代码:
package org.susan.java.logging;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.logging.LogManager;
public class RemoteConfigReader {
private String urlString = "http://localhost/logging.properties";
private URL url;
private URLConnection urlConn = null;
private InputStream inputStream = null;
private LogManager logManager = null;
public RemoteConfigReader(){
try{
url = new URL(urlString);
urlConn = url.openConnection();
inputStream = urlConn.getInputStream();
logManager = LogManager.getLogManager();
// 如果是从本地文件读取,该原理也是一样的,多调用一次readConfiguration
logManager.readConfiguration(inputStream);
}catch(MalformedURLException mue){
System.err.println("Could not open url:" + urlString);
}catch(IOException ioe){
System.err.println("IOException occured in reading:" + urlString);
}catch(SecurityException se){
System.err.println("Security exception occured in class RemoteConfigReader");
}
}
public static void main(String args[]){
new RemoteConfigReader();
}
}
根据这段代码就可以知道,从本地非默认配置文件进行日志记录,只需要传入一个InputStream到readConfiguration方法就可以了……
本文来自CSDN博客:http://blog.csdn.net/silentbalanceyh/archive/2009/09/18/4564884.aspx
assertion(断言)是Java1.4引入的一个新特性,该特性的引入的目的是为了辅助开发人员调试和测试,是一种比较常用的调试、测试方案。assertion在软件开发过程中是一种比较常用的调试方法;不仅仅如此,使用assertion可以在开发过程中证明程序的正确性,只是这种用法会对系统的整体设计存在很大的挑战,而且目前很少投入到实用里面,所以一般情况下使用assertion的目的是为了调试和测试。
i.assertion概念以及基本用法
在代码实现的时候,需要使用关键字assert,而assertion本身在程序里面就是一条语句,它的作用是对boolean表达式进行检查,正确保证这个boolean表达式在程序运行到此刻的时候为true;一旦这个boolean表达式为false的话,就说明该程序已经处于了不正确的执行状态了,系统在断言开启的情况下会根据相关情况给出警告或者退出。
当在程序开发过程中,一般情况下使用assertion来保证整个应用程序里面最基本的、关键的正确性,而在操作过程中一般是开发和测试的时候开启该功能,一旦等软件开发完成过后,为了提高程序性能,发布的时候就将断言关闭。
1)语法:
Java里面使用assert关键字来支持assertion,其本身包括了两种表达方式:
[1]assert 表达式1;
[2]assert 表达式1:表达式2;
以上两种语法里面,表达式1表示一个boolean表达式,而表达式2一般是一个基本类型或者对象,这里需要说明的是在开发过程一般表达式2写的都是字符串以提供该断言失败的信息,但是真正在使用的时候应该理解的是表达式2也可以是某个对象或者基本类型,这里通过一个简单的例子来初次接触断言:
/**
*断言使用的概念说明代码
**/
public class AssertionDriver {
public static void main(String args[]){
Employee employee = new Employee();
employee.setName("Lang Yu");
employee.setEmail("silentbalanceyh@126.com");
businessProcess(employee);
}
public static void businessProcess(Employee employee){
try{
assert employee.getName() != null &&
employee.getEmail() != null &&
employee.getPassword() != null:
employee;
}catch(AssertionError error){
System.out.println(error);
}
}
}
class Employee{
private String name;
private String email;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString(){
return "\nName:" + name + "\n" + "Email:" + email + "\n" + "Password:" + password;
}
}
上边代码段使用了第二种表达式的方式,但是这里复杂的地方在于表达式2不是一个字符串,而是定义的Employee类的一个对象的实例,也就是说这里表达式2是一个Object实例,然后编译该代码,运行的时候打开断言,就可以得到下边的输出:
java.lang.AssertionError:
Name:Lang Yu
Email:silentbalanceyh@126.com
Password:null
【*:当断言中表达式1返回false的时候,try块里面就抛出了AssertionError类型的断言错误,然后在catch块里面会将该错误打印出来,这种错误的格式为:java.lang.AssertionError:object.toString(),因为这里重写了Employee的toString方法,根据输出结果可以知道,返回false的boolean表达式为子表达式:(employee.getPassword() != null)】
通常,在对某个对象执行关键操作时会需要对它创建断言。这有助于增强代码的健壮性,比如如果在程序中出现了某种错误,可以更方便地调试程序。这样做要比程序在某处执行失败造成不良后果来发现错误要好得多。当知道程序失败是由于它违反了假设而引起的时候,跟踪失败的原因要简单得多。
2)语义:
在运行的时候,如果关闭了assertion功能,这些语句将不会起任何作用,JVM默认assertion的功能是关闭的,如果要上边这段代码输出该结果还需要一定的操作。如果assertion功能被打开,那么JVM会先计算表达式1的值,如果它为false,该语句会抛出一个AssertionError异常。若assertion语句包括了表达式2参数,程序将计算表达式2的结果,然后将这个结果作为AssertionError的构造函数的参数,用来创建AssertionError对象,并抛出该对象,若表达式1值为true,表达式2将不被计算。
这里简单看看AssertionError的API文档说明构造函数的定义:
AssertionError()
AssertionError(boolean detailMessage)
AssertionError(char detailMessage)
AssertionError(double detailMessage)
AssertionError(float detailMessage)
AssertionError(int detailMessage)
AssertionError(long detailMessage)
AssertionError(Object detailMessage)
在讲的断言里面,当表达式1为false的时候,就需要构造AssertionError对象,构造的时候,传入的就是表达式2,也就是说在使用assertion的时候,AssertionError构造函数的实参就是真正在运行的表达式2,这样也可以理解表达式2为什么可以是基础类型,也可以是Object。
这里再提供几个简单的代码段,加深印象:
assert 0 < value;
assert 0 < value:"value = " + value;
assert ref != null:"ref doesn't equal null";
assert isValid();
【*:再提醒一点,既然表达式1是一个boolean表达式,那么可以是一个返回值为boolean的函数。】
3)编译和运行:
【编译】
由于assert是JDK 1.4才出来的关键字,使用老版本的JDK是无法编译带有assert的程序的,因此在使用javac命令编译该代码的时候,必须使用JDK 1.4或者更新的Java编译器,如果编译的时候因为无法识别assert关键字报错,那么需要加上编译参数-source 1.4这里的版本号至少是1.4或者以上的。直接使用javac命令编译的时候-source 1.4表示使用JDK 1.4版本的方式来编译源代码,版本太低带有assert关键字的代码就无法通过编译。关于javac和java命令的内容后边会有专程的章节介绍
【运行】
在运行带有assert语句的程序时,使用了新的ClassLoader的Class类,因此必须保证程序在JDK 1.4以及以上的版本或者JRE 1.4以及以上的版本环境里面运行。而在运行的时候,因为JVM默认是关闭了assertion功能的,所以要使用assertion功能的话必须显式使用加入参数来选择启用或者禁用断言。另外,断言的参数可以使得java应用程序可以开启一部分类或包的assertion功能,所以运行相对编译而言,比较复杂,这里有两类参数需要说明:
[1]参数-esa和-dsa:
该含义为开启(关闭)系统类的assertion功能。由于新版的Java的系统类中,也使用了assertion语句,如果用户需要观察它们本身的运行情况就需要打开assertion功能,可以使用参数-esa参数打开,使用-dsa参数关闭。 -esa和-dsa的全名为-enablesystemassertions和-disenablesystemassertions,全名和缩写名具有同样的效果。
[2]参数-ea和-da:
它们的含义为开启(关闭)用户类的assertion功能:通过使用该参数,用户可以打开某些类或者包的assertion功能,同样用户也可以关闭某些类和包的assertion功能。打开assertion功能的参数为-ea;如果不带任何参数,表示打开所有用户类;如果带有包名称或者类名称,就表示打开这些类或包的assertion功能。-ea和-da的全名为-enableassertions和-disenableassertions
这里提供一个表格来说明运行时断言参数的用法
参数 例子 说明
-ea java -ea 打开所有用户类的assertion
-da java -da 关闭所有用户类的assertion
-ea:<classname> java -ea:AssertionDriver 开打AssertionDriver类的assertion
-da:<classname> java -da:AssertionDriver 关闭AssertionDriver类的assertion
-ea:<packagename> java -ea:packagename 打开packagename包的assertion
-da:<packagename> java -da:packagename 关闭packagename包的assertion
-ea:... java -ea:... 打开缺省包(无名包)的assertion
-da:... java -da:... 关闭缺省包(无名包)的assertion
-ea:<packagename>... java -ea:packagename... 打开packagename包以及其子包的assertion
-da:<packagename>... java -da:packagename... 关闭packagename包以及其子包的assertion
-esa java -esa 打开系统类的assertion
-dsa java -dsa 关闭系统类的assertion
综合使用 java -dsa:ClassOne:pkgOne 关闭ClassOne类和pkgOne包的assertion
在上边的表格里面,需要说明的是:
[1]...代表该包和该包对应的子包,如果系统有两个包分别为pkgOne和pkgOne.subpkg,则pkgOne...就代表这两个包
[2]可是使用编程的方式来禁用或者启用assertion,下边提供一段代码来说明该功能:编程方式的assertion
/**
*使用程序开启断言的代码段
**/
class Loaded
{
public void go()
{
try
{
assert false:"Loaded.go()";
}
catch(AssertionError error){
System.out.println(error);
}
}
}
public class LoaderAssertions
{
public static void main(String args[])
{
ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true);
new Loaded().go();
}
}
直接运行上边的代码会有下边的输出:
java.lang.AssertionError: Loaded.go()
除了上边的代码使用的setDefaultAssertionStatus方法以外,ClassLoader的API里面还有以下几种方法:
setDefaultAssertionStatus:用于开启/关闭assertion功能
setPackageAssertionStatus:用于开启/关闭某些包的assertion功能
setClassAssertionStatus: 用于开启/关闭某些类的assertion功能
clearAssertionStatus:用于关闭assertion功能
ii.关于断言的设计和使用
1)assertion的设计:
assertion在开发中是必要的,如果没有统一的assertion机制,Java程序通常使用if-else或者switch-case语句来进行assertion的检查,而且检查的数据类型有可能不相同,但是一旦有了assertion机制过后,Java程序员就可以使用统一的方式来处理assertion的问题,而不是按照每个人不同的习惯以及方式来处理。而且还有一点,断言在未开启的情况下,发布代码的过程是不起任何作用的,只是在调试和测试阶段发生作用,一般情况在发布过程就关闭assertion功能,如果用户按照自己的方式处理进行相关检查,这些代码在发布过后不可能像assertion一样失去作用,就使得用户自己方式处理的assertion代码部分会影响程序本身的性能。Java提供的assertion统一机制,从语言层次上讲,使得assertion对系统性能的影响减小到最小。
Java通过增加一个关键字assert来实现assertion机制,而不是使用一个函数来支持,这说明java已经将assertion机制内置到语言内部称为了语言特性,本身在设计上java语言认为assertion机制是很重要的。
Java中的assertion的开启和C语言不太一样:
在C语言里面,assertion的开启是编译时决定的,可以在debug版本里面开启而直接在release版本中自动关闭;在Java里面,assertion的开启和关闭是在运行时决定的。两种方式各有优缺点。如果采取编译时决定,开发人员将处理两种类型的目标文件,就debug版本和release版本,这种方式加大了文档的管理难度,但是提高了代码的运行效率。而运行时的方式,所有的assertion都会放到目标代码里面,统一目标代码可以使用不同的方式运行,增加了一部分代码灵活性,但是牺牲了一部分性能。但是从语言发展角度,既然从纯面向过程的C的设计演变到C++的过程和对象共存的设计再到Java的纯面向对象的设计就知道,实际上Java里面这种性能的损失是可以忽略不计的,所以java这中使用运行时决定assertion的机制的方式是真正在开发过程中的首选。
另外,有一点我在异常章节里面已经提及过的,在上边的代码里面,AssertionError类作为了Error的子类,而不是RuntimeException。这一点一致都没有想过为什么,实际上所有Error的子类里面,只有这一个类可以使用catch块进行捕捉,那么按照对异常的理解,AssertionError本应该是属于一个RuntimeException而不应该是一个Error,那么回到上边关于Error和RuntimeException的定义:
Error通常代表一些错误,不可以恢复
RuntimeException强调了该错误是在运行时才发生的
那么AssertionError通常情况下是在调试以及测试过程打开了断言开关才会遇到的关键性错误,这里遇到的错误,往往是整个程序关键性的缺陷,这一点在后边使用assertion的时候会介绍assert关键字的使用场合。从这点来讲,与AssertionError有关的错误往往都是没有办法恢复的,而且assertion机制也不鼓励程序员通过编程或者其他手段来恢复折中类型的错误。因此,为了强调Assertion机制的概念以及定义,AssertionError作为了一个Error的子类进行设计,并且它的运行原理类似RuntimeException,在运行过程中是可以直接被catch块捕捉到的。
2)assertion和继承
那么在assertion机制里面,父类和子类在assertion的开启和关闭的时候会发生什么现象呢,这里提供一段代码:
/**
*讨论assertion和继承的关系
**/
class SuperClass{
public void superMethod()
{
assert false:"Assert:This is Super Class";
System.out.println("Super Method");
}
}
public class SubClass extends SuperClass{
public void subMethod()
{
assert false:"Assert:This is Sub Class";
System.out.println("Sub Method");
}
public static void main(String[] args)
{
try
{
SubClass assertClass = new SubClass();
assertClass.superMethod();
assertClass.subMethod();
}
catch(AssertionError error)
{
System.out.println(error);
}
}
}
这里使用几种情况来运行:【*:确保编译的时候使用的是1.4和1.4以上的JDK版本】
[1]直接运行的输出:java SubClass(这种方式和全关闭断言的方式一样的输出:java -da SubClass)
Super Method
Sub Method
[2]全开断言的输出:java -ea SubClass
java.lang.AssertionError: Assert:This is Super Class
[3]仅仅打开父类断言输出:java -ea:SuperClass SubClass
java.lang.AssertionError: Assert:This is Super Class
[4]仅仅打开子类断言的输出:java -ea:SubClass SubClass
Super Method
java.lang.AssertionError: Assert:This is Sub Class
仔细分析上边的输入输出可以知道:当父类的assertion只有在父类的assert开启的时候会起作用,如果仅仅打开子类的assert,父类的assert不会运行,比如上边[4]的输出,SuperClass的assert没有起任何作用,由此可以认为,assert语句不具有继承功能。
【*:其实仔细想想,这一点从语言本身设计上是合理的,assert机制本身的存在是辅助开发、调试、测试,因为有这样的一个assert存在,使得程序的数据流能够在合法的范围内在程序里面运行,而assert机制本身是类似踩点方式,如果觉得什么地方出现了程序的关键性问题,这里置放一个assertion用来确保数据的准确性,使得调试的时候不用去考虑数据在这个地方是否合法,因为一旦不合法可以通过assert进行记录,以及很容易发现assert监控的数据出问题的地方,如果assert支持继承的话,在一个设计得比较好的继承树系统里面,本身初衷是为了查询某个点的assert标记进行调试,如果它支持了继承,那么assert会形成一个不必要的链式结构,这种方式反而不利于找到程序的关键部分,所以个人觉得从概念上讲,assert本身不支持继承的设计也是蛮合理的。】
3)assertion的使用:
assertion既然能够辅助调试、测试以及开发,那么在使用assertion的时候,问题会变得相对复杂,因为这里会涉及到不同的程序员在使用assertion的目标、程序的性质以及各自不同的代码风格问题。
一般来讲,assertion主要用来检查一些程序的关键性问题,并且对整个程序或者局部功能的完成起到很大的影响,这种错误不是数据的非法性,往往这些部分可能导致整个程序出现不可恢复的问题。这里提供几种简单的应用:
[1]检查控制流:
在这个例子中,不应该发生的控制流上边使用assert false
public class AssertionUseOne
{
public static void main(String args[])
{
int a = 1;// 1,2,3,4
switch(a)
{
case 1:...;break;
case 2:...;break;
case 3:...;break;
default:assert false:"a is not valid";
}
}
}
[2]检查私有函数的输入参数:
对于一些私有函数,要求输入满足一定的特殊条件,那么可以在函数开始对参数进行assert检查,这个和公有函数不一样,公有函数一般都是由外部和内部调用,一旦输入有什么问题就可以知道,因为共有函数本应该检查无效的参数,而且必须对无效的参数进行处理,但是私有函数是可以直接使用的,而且不能由外部调用,那么在使用过程可以使用assert来检查私有函数的参数输入合法性。【*:确保在运行时断言是打开的】
public class AssertionUseTwo
{
public static void main(String args[])
{
System.out.println(AssertionUseTwo.test(10,5));
}
private static int test(int a,int b)
{
assert b != 0:"b can not be zero!";
return a / b;
}
}
[3]检查函数结果:
在函数计算过后,检查函数的结果是否有效,对于一些做运算的函数,在函数返回某个结果的时候,要确保这些结果具有一定的性质,因此可以通过使用assert来检查该值。所以这种情况可以在return语句之前检查函数的结果是否复合某种性质
[4]检查程序不变量:
有些程序在运行的整个周期里面存在一定的不变量,而这些量在整个生命周期都是不会改变的,但是有时候这个不变量有可能是表达式。对于一些关键不变量,可以通过assert进行检查,这种情况就可以利用下边的方式来书写:
public class AssertionUseFour
{
public boolean flag= true;
public boolean isValid()
{
return flag;
}
public static void main(String args[])
{
AssertionUseFour assertion = new AssertionUseFour();
assert assertion.isValid():"Flag must be true!";
}
}
【*:上边的四种方式都是概念说明代码,而在实际运用中只是一个规则说明,这里特别说明第四种情况,这种情况一般是根据业务走的,比如某个银行,在某个人进行某项操作的时候必须要符合这个人的一些自身条件,而这些条件本身不会影响到整个系统的运行,而且这个条件本来就属于该对象的不变条件,也就是说即使这个人条件不满足的情况下,业务就越轨了,但是为了使得某种业务不会出现越轨的操作,在这个的Object做某个操作之前,使用assert检测一下他是否复合了这种要求就是一个比较不错的方式。也就是说在最后一个例子里面,必须理解不变量在整个程序里面的含义,只有对不变量的检测才符合断言的使用,如果在设计系统的时候用不好这种情况就不使用。】
4)关于断言的思考:
Java断言机制的出现,其主要目的是为了辅助程序员开发中的调试、测试、开发等操作,既然如此,在正规的业务流程中不能滥用,用不好有可能导致很严重的问题,有三点需要说明:
【1】请开发人员牢记,断言默认是关闭的,所有主流的IDE包括Eclipse和NetBeans在调试代码过程都没有将断言打开,如果要打开断言,需要进行相关的设置,所以有时候加入了assert在代码里面了过后,在寻找系统关键环节的时候,必须记得打开断言功能
【2】不可以使用断言去替代正常的业务逻辑:这种情况对比地说就是assert和if语句,这两者在程序开发过程中有着本质性的概念差异。if最典型的解释就是,条件满足就执行,条件不满足就不用执行;而assert一般放在系统的关键位置,assert是确保条件表达式是正确的,如果不正确表示系统有Error,所以二者在概念上有本质的差别,所以开发人员必须记得不能使用assert去替代本来的if语句。而且有一点,在发布最终代码版本的时候,断言一般会关闭的,如果使用了这样的替代方式,就意味着未发布版本和发布版本的流程不一样使得在工业生产中
【3】assert的出现,意味着如果系统在此处出现AssertionError的话证明系统已经出现了不可恢复的错误,所以assert是不能用于正规的业务逻辑的,只能用于开发和调试,这种不可恢复错误在工业生产和工程项目里面有可能导致严重的问题,所以这种情况一定要仔细考虑assert的使用和位置
4.Java中的日志(JDK 1.4 Logging Framework)
日志系统是一种不可或缺的跟踪调试工具,特别是在任何无人职守的后台程序以及那些没有跟踪调试环境的系统中有着广泛的应用。长期以来,日志系统作为一种应用程序服务,对于跟踪调试、程序状态记录、崩溃数据恢复都有非常现实的意义。而这种应用程序服务一般以两种方式存在:
[1]日志系统作为一种服务进程存在
一般服务进程存在于系统背后,如Windows本身提供的Windows事件日志服务这种,这种情况日志的发送端和接收端是异步调用方式,而这种日志系统最常用的功能就是作为系统日志存在,它记录了系统每隔一段时间的运行状况以及系统本身的一些配置记录,这种日志存在的目的是为了管理员能够更加方便地监控系统运行状态。
[2]日志系统作为系统调用存在
Java中的日志系统大部分就属于这种类型,日志系统的代码作为系统调用被编译进了日志的发送端,在书写代码的时候,遇到一些比较重要的信息的时候就将东西记录下来,这种情况下,日志的发送端就是系统本身运行的业务它们,而且这种记录方式和前者不一样的就是这种运行方式属于同步进行,在日志记录过程中,应用程序可能会等到该日志记录完成过后才会继续运行,因为日志记录的程序和业务程序本身就在同一个进程里面。
这里所讲到的日志记录就是第二种方式
i.Java世界常用的日志系统
1)Log4j:最早的Java日志记录框架之一,Apache的一个开放源代码项目,通过使用Log4j,可以控制日志信息输出的目的地是控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器等,也可以控制每一条日志输出的格式;通过定义每一条日志的级别,能够更加细致地控制日志的最终生成过程,而在这些特性的修改的时候,使用的不是编码的方式,而是通过配置文件来配置该日志记录器的一些相关特性和行为。
2)JDK1.4 Logging Framework
继Log4j之后,JDK标准委员会将Log4j的基本思想吸收到了JDK当中,在JDK1.4中发布了第一个日志框架接口,并且提供了很简单的实现。
3)Commons Logging Framework
该框架同样是Apache基金会的项目,其出现的目的是为了整合Java项目中的JDK1.4 Logging Framework框架以及Log4j,使得应用程序可以在这两种框架上进行随意切换,因此该框架提供了比较统一的调用接口和配置方法。
ii.日志系统特性:
如果需要自己来设计一个完备的日志系统,就需要考虑到日志系统的特性问题,一般情况下,日志记录的特性如下:
[1]日志的输出可以按照分类归档,这样方便在调试的时候针对不同模块的日志进行相关查询;举个例子:使用过Windows事件查看器的用户都知道平时Windows里面记录了Application日志,System日志和Security日志,如果是一个开发环境有可能还记录了单独的Office日志以及其他分类的日志,所以日志分类是一个日志系统设计的时候应该考虑的内容。
[2]其次是日志的分级,在某一类的日志里面,按照不同的级别对日志记录的信息进行区分,同样的例子:Windows事件查看器里面有Information、Warning、Error三种不同级别的日志记录
[3]支持多线程:日志记录一定是一个多线程的记录方式,而且本身应该是线程安全的,因为有可能在一个多线程程序里面不同的线程会记录不同的日志
[4]支持不同的介质:日志系统的记录介质一般包括文件系统、控制台、服务器、时间记录器、数据库等,这些相关日志记录都应该是该日志记录器可以支持的,这点特性使得日志记录器可以将日志记录在不同的地方以方便查询,常见的就是文件后缀使用.log的文本文件
[5]高性能。日志系统通常要提供高速的日志记录功能以应对大系统下大请求流量下系统的正常运转。
[6]稳定性。日志系统必须是保持高度的稳定性,不能因为日志系统内部错误导致主要业务代码的崩溃。
iii.JDK 1.4 Logging Framework:
JDK1.4引入了Logger对象,该对象用来记录特定系统或者应用程序组件的日志信息,一般情况下使用圆点分割的层次空间名称来命名Logger,Logger的名称可以是任意字符串,但是最好是基于被记录组件的包名、类名或者类全名,例如java.net或javax.swing这种格式。不仅仅如此,在使用Logger的时候也可以创建“匿名”的Logger,该名称是存储在日志记录器的名空间里面的。
先看一段代码,初次接触一下JDK 1.4 Logging Framework:【参考Java Logging规范】
package org.susan.java.logging
import java.util.logging.Filter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
public class Nose {
private static Logger logger = Logger.getLogger("org.susan.java.logging.Nose");
static class Duck{};
static class Wombat{};
static void sendLogMessages(){
logger.log(Level.WARNING,"A duck in the house!",new Duck());
logger.log(Level.WARNING,"A Wombat at large!",new Wombat());
}
public static void main(String args[]){
sendLogMessages();
logger.setFilter(new Filter(){
public boolean isLoggable(LogRecord record){
Object[] params = record.getParameters();
if( params == null ){
return true;
}
if( record.getParameters()[0] instanceof Duck){
return true;
}
return false;
}
});
logger.info("After setting filter...");
sendLogMessages();
}
}
运行以上代码,就可以在控制台里面看到该段代码的输出【Logger开始区分System.out和System.err,err用红色】:
Sep 14, 2009 10:10:41 AM org.susan.java.logging.Nose sendLogMessages
WARNING: A duck in the house!
Sep 14, 2009 10:10:41 AM org.susan.java.logging.Nose sendLogMessages
WARNING: A Wombat at large!
Sep 14, 2009 10:10:41 AM org.susan.java.logging.Nose main
INFO: After setting filter...
Sep 14, 2009 10:10:41 AM org.susan.java.logging.Nose sendLogMessages
WARNING: A duck in the house!
上边只是JDK 1.4 Logging Framework的一段简单的应用代码,主要是让初学者有个简单的印象。先根据上边代码段简单了解一下JDK里面的Logger:
Logger对象用来记录特定系统或者应用程序组件的日志消息,一般用圆点分割的层次名称空间来命名Logger,Logger名称可以是任意字符串,但是它们一般应该基于被记录组件的包名或者类名。一般情况下,可以直接使用getLogger工厂方法来获取Logger对象,Logger.getLogger(String name)方法使用的是工厂模式,如果传入的name原来的系统里面没有这样的一个Logger,那么就直接创建一个Logger的新实例,如果该Logger已经存在了,那么就直接返回该存在的实例。如上边这段代码里面的:
Logger.getLogger("org.susan.java.logging.Nose"); //在这里org.susan.java.logging.Nose就是该日志记录器的名称。
每个Logger对象都是分等级的,所以都有一个与之相关的“Level”,上边的代码可以说明:
logger.log(Level.WARNING,"A duck in the house!",new Duck()); //这里设置了日志的等级Level.WARNING
上边代码还出现的一个关键概念就是Filter,Logger日志框架里面Logger对象和Handler都有对应的关联过滤器,其作用就是对日志记录进行过滤,Filter为一个接口,当该接口的isLoggable方法返回为fales的时候,则丢弃LogRecord也就是不做任何日志记录,仔细分析上边的代码就可以发现:
WARNING: A Wombat at large!
这句输出没有出现第二次,主要是因为isLoggable方法返回了false,所以在实例化Wombat对象的时候并没有记录下来,而且是在Logger进行了相关设置过后没有输出,设置前后就是在原来的Logger基础上添加了一个Filter接口对实例的类型进行了判断。而且需要注意一点的是:Filter的创建在这个地方是作为了一个匿名内部类来实现的!
1)JDK 1.4 Logger Framework关键组件:
了解这些元素之前,先看看它们相互之间存在一个什么关系。一般情况下,应用程序把日志称为Logger对象,而在JDK 1.4 Logging Framework里面,Logger对象本身是存在一定的层次结构的,也就是说Logger对象本身就是跟踪了某个父对象“Logger”。而在整个日志记录过程里面,传入的是什么?传入的就是LogRecord对象,Logger对象会将LogRecord对象传给Handler对象发布出来,而LogRecord对象里面就记录了日志的详细信息。在整个结构里面,Logger对象本身和Handler对象都会用到Level和Filter两个对象,用这两个对象的目的是为了一些特殊的需求,或者根据不同的情况定制特殊的日志记录器来进行日志记录。而在记录过程中,如果有需要额外的需求,可以选择使用Formatter对象来进行消息的格式化,在输出的时候进行,而Formatter只有Handler可操作,Logger本身是不能操作的,通过下边的图,这个结构就一目了然了:
上图就是JDK 1.4 Logging Framework结构的整体流程,有时候会出现一些例外情况,一些Handler对象有可能直接把日志记录发布给其他的Handler对象,形成一个Handler Chain,这种情况下的流程如下:
【*:思考一个问题:为什么说Formatter只有Handler可以操作,Logger不能操作?这句话我自己是这样理解的,JDK 1.4 Logging Framework在进行日志记录的过程里面,总是通过Logger把日志记录的信息发布给Handler,而Handler做了该日志记录的统一的出口,也就是说,在做日志记录的时候,Handler至少有一个。而且Formatter是在最终发布日志的时候才会出现,也就是说Farmatter是在最后一个Handler里面才会使用到,而Formatter的使用不是必须的,是可选的,因为Formatter本质上没有做其他操作,就是对日志进行了格式化,可以这样理解,那么这样看来,在拥有Chain结构的Handler链中,Formatter一般出现的位置应该是Chain的末端,也就是Chain的最后一个Handler可能会调用Formatter对象。】
[1]Logger:
Logger对象是日志框架里面的主体对象,我们在编程过程大多数时间遇到的就是Logger对象。每个Logger对象都有一个相关的Level【下边会介绍】,而Logger的级别如何设置为null的话,那么它就会继承其父类的Logger,这一点会一直向上递归得到,而Logger的默认级别是INFO。在开发过程我们可以根据日志配置文件的属性来配置日志的级别,而默认级别就是在配置文件里面体现的,在LogManager对象里面也针对此级别有所描述,如果在运行过程有所改变,可以通过调用Logger.setLevel方法来动态的改变,如果日志级别改变了,这种变化会向下递归,其子类也会受相对的影响。对于每次日志记录调用,最初Logger都会依据有效日志级别对请求级别进行对应的检查,如果请求级别低于日志级别,则不作记录,直接返回。
Logger一般情况下通过工厂方法Logger.getLogger来获取,一旦获取过后,它将分配一个LogRecord来描述日志记录的消息,接着调用Filter【如果存在的话】进行更加详细的检查,以确定是否发布。如果检查通过,则将LogRecord发布到对应的输出Handler,默认情况会沿着递推发布到其父Handler。每个Logger还存在一个与其关联的ResourceBundle名称,用于本地化日志消息,若一个Logger没有自己的ResourceBundle名称,同样会通过递归的方式向上寻找。
Logger的很多输出方法里面都带有“msg”参数,该参数可以是一个原始值,也可以是一个本地化的键。进行Formatter操作的时候,若Logger具有一个本地化的ResourceBundle,并且里面包含了msg字符串的映射关系,那么直接用本地化的值来替换msg字符串,否则使用原有的字符串输出。将ResourceBundle名称映射到ResourceBundle时,Logger首先调用ContextClassLoader,如果为null,Logger尝试调用SystemClassLoader,若两者都为空,Logger会继续在ClassLoader中寻找ResourceBundle,试图找到一个匹配的ResourceBundle。
Logger本身的方法主要分五个类别:【这个跟Level不是一个概念】
一系列的“log”方法,这种方法带有日志级别、消息字符串以及可选的字符串参数;
一些列的“logp”方法,与“log”方法类似,但是带有显示的源类名称和方法名称;
一些列的“logrb”方法,与“logp”方法类似,但是带有显示的本地化日志消息使用的资源包名称;
还有跟踪方法(“entering”方法)、方法返回(“exiting”方法)和抛出方法(“throwing”方法);
一些便捷方法,这些方法按照日志记录的等级命名(“severe”,“warning”,“info”等),仅仅带一个参数,即消息字符串
【*:日志记录里面所有的这些方法都是线程安全的】
[2]Level:
Level类定义了一组可用来控制日志输出的标准日志级别,日志Level对象是有序的,并且是通过有序的整数来指定,在给定的级别上启用日志也就启用了所有较高级别的日志记录,而我们编程的时候不应该使用它的值常量格式,也就是Level里面的常量的真实整数值格式,应该直接使用预定义常量。(如Level.SEVERE)
其级别分为以下几种:
SEVERE(最高级)
WARNING
INFO
CONFIG
FINE
FINER
FINEST(最低值)
此外有一个OFF级别是用来关闭日志记录,另外还有个ALL启用所有消息的日志记录。若要自定义日志级别,需要子类化Level,也就是我们在开发过程中使用继承来实现Level的子类化
[3]LogRecord
LogRecord用于在日志框架和单个日志Handler之间传递日志请求。将LogRecord传递到日志框架中过后,它在逻辑上是属于此框架的,客户端应用程序不可以再使用或者更新它的记录,可以这样理解:大多数情况下LogRecord都是只读的。
【*:如果客户端应用程序尚未显示指定源方法和源类名,则LogRecord类将在第一次访问它们的时候通过解析调用堆栈来自动推导。因此如果使用RMI的方式传输LogRecord的时候,如果希望它能够在后续操作里面获取方法名和类名,应该调用getSourceClassName或者getSourceMethodName其中之一来强制填入某些信息。】
LogRecord在序列化过程需要注意:
LogRecord类是可序列化的
因为参数数组中的对象可能不可序列化,所以在序列化过程,应该写入参数数组中所有对象的相应String
ResourceBundle不作为序列化的一部分传输,而在接受该对象的时候readObject方法自己会尝试查找合适的资源包
[4]LoggingPermission
当SecurityManager运行的代码调用某个日志记录方法的时候,SecurityManager将会检查其权限。当前只有一个指定LoggingPermission,就是“control”,此权限授予了控制日志记录配置的能力,如添加、移除Handler或者Filter,设置日志记录级别。不过牵涉到安全部分一般情况我们不会直接通过编程的方式来创建LoggingPermission对象,这些对象一般都是由安全celue代码根据读取的安全策略文件创建的。
[5]LogManager
在整个日志框架里面,存在一个单一的全局LogManager对象,它可以用于维护Logger和日志服务的一组共享状态。LogManager的作用在于:
管理Logger对象的层次结构名称空间,所有指定的Logger均存储在此名称空间中
管理一组日志控制属性,这些事提供Handler以及其他日志对象用于自我配置的键值对
编程过程可是使用LoggerManager.getLogManager()获取全局的LogManager对象,LogManager对象是在类初始化过程中创建的,过后便不可以更改。在启动的时候,使用java.util.logging.manager系统属性定位LogManager类。默认情况下,LogManager会从JRE目录的属性文件“lib/logging.properties”中读取其初始配置,如果编辑该属性文件,则可更改此JRE的所有用户的默认日志配置。另外LogManager还有两个可选的允许更好地控制初始配置读取的系统属性:
“java.util.logging.config.class”
“java.util.logging.config.file”
这两个属性也可以通过JPA类配置,既作为“java”命令的命令行属性定义,也可以作为传递到JNI_CreateJavaVM的系统属性定义。若设置了“java.util.logging.config.class”属性,则会把属性值当做类名,LogManager将会去加载该类,并且根据该类的定义去实例化一个对象,该对象的构造方法会去读取初始配置。此类可以作为系统的备用配置类,备用配置类可以使用readConfiguretion(InputStream)来定义LogManager中的属性。若“java.util.logging.config.class”没有设置,将会使用“java.util.logging.config.file”系统属性来制定一个属性文件【.properties格式】,从此文件读取对应的配置。若两个都没定义,则从默认的JRE目录的属性文件“lib/logging.properties”中读取其初始配置。
这里看一段代码,简单说明一下LogManager如何进行Logger的管理:
package org.susan.java.logging;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.logging.XMLFormatter;
/**
*关于LogManager最简单的用法,同时引入Handler的概念说明代码
**/
public class LoggingManager {
public static void main(String args[]){
try{
// 这里通过LogManager的静态方法获取全局的LogManager,而且还创建了一个FileHandler
LogManager lManager = LogManager.getLogManager();
Logger logger;
FileHandler fileHandler = new FileHandler("log_test.xml");
// 这一段代码设置相互之间的关系,FileHandler设置了输出格式为一个XML格式的
logger = Logger.getLogger("org.susan.java.logging.LoggingManager");
lManager.addLogger(logger);
logger.setLevel(Level.INFO);
fileHandler.setFormatter(new XMLFormatter());
logger.addHandler(fileHandler);
// 这里开始进行对应的日志记录
logger.log(Level.INFO,"test 1");
logger.log(Level.INFO,"test 2");
logger.log(Level.INFO,"test 3");
fileHandler.close();
}
catch(Exception ex){
System.out.println("Exception thrown:" + ex);
ex.printStackTrace();
}
}
}
这段代码有些特殊就在于会有两处输出,在控制台里面可以看到以下输出:
Sep 14, 2009 2:55:24 PM org.susan.java.logging.LoggingManager main
INFO: test 1
Sep 14, 2009 2:55:24 PM org.susan.java.logging.LoggingManager main
INFO: test 2
Sep 14, 2009 2:55:24 PM org.susan.java.logging.LoggingManager main
INFO: test 3
而且在运行目录下还会输出一个文件log_text.xml,这个文件的内容如下:
<?xml version="1.0" encoding="windows-1252" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2009-09-14T14:55:24</date>
<millis>1252911324403</millis>
<sequence>0</sequence>
<logger>org.susan.java.logging.LoggingManager</logger>
<level>INFO</level>
<class>org.susan.java.logging.LoggingManager</class>
<method>main</method>
<thread>10</thread>
<message>test 1</message>
</record>
<record>
<date>2009-09-14T14:55:24</date>
<millis>1252911324416</millis>
<sequence>1</sequence>
<logger>org.susan.java.logging.LoggingManager</logger>
<level>INFO</level>
<class>org.susan.java.logging.LoggingManager</class>
<method>main</method>
<thread>10</thread>
<message>test 2</message>
</record>
<record>
<date>2009-09-14T14:55:24</date>
<millis>1252911324417</millis>
<sequence>2</sequence>
<logger>org.susan.java.logging.LoggingManager</logger>
<level>INFO</level>
<class>org.susan.java.logging.LoggingManager</class>
<method>main</method>
<thread>10</thread>
<message>test 3</message>
</record>
</log>
[6]Filter和Formatter
Filter:
Filter本身属于一个接口,前边已经讲得很清楚了,其作用就是对日志记录进行过滤,Filter为一个接口,当该接口的isLoggable方法返回为false的时候,则丢弃LogRecord也就是不做任何日志记录。这里再提供一个例子可以让我们自定义Filter:
package org.susan.java.logging;
import java.util.logging.Filter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
class Person{
private String name = null;
private int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class AgeFilter implements Filter{
public boolean isLoggable(LogRecord record){
boolean result = false;
Object[] objs = record.getParameters();
Person person = (Person)objs[0];
if( person != null ){
int age = person.getAge();
if( age > 30 )
result = true;
else
result = false;
}
return result;
}
}
public class CustomFilter {
public static void main(String args[]){
Logger logger = Logger.getLogger("org.susan.java.logging.CustomFilter");
AgeFilter filterAgeFilter = new AgeFilter();
logger.setFilter(filterAgeFilter);
Person person = new Person("LangYu",32);
logger.log(Level.INFO, "Person has age " + person.getAge(), person);
}
}
运行上边代码可以看到输出为:
Sep 14, 2009 3:15:03 PM org.susan.java.logging.CustomFilter main
INFO: Person has age 32
上边代码定义了一个Filter,是对Person类的对象年龄进行过滤然后记录的,这段代码初始化的时候person的年龄是32,如果传入一个小于30的数,那么该Filter里面的isLoggable方法就会返回false,什么输出都不会有,而且需要留意代码:class AgeFilter implements Filter,记住Filter是一个接口并不是一个类。
Formatter:
Formatter为格式化LogRecords提供支持。一般来说,每个日志记录Handler都有关联的Formatter。Formatter接受LogRecord,并将它转换为一个字符串。有些Formatter(如 XMLFormatter)需要围绕一组格式化记录来包装头部和尾部字符串。可以使用getHeader和getTail方法来获得这些字符串。同样这里给一个使用了LogRecord类的XMLFormatter类的例子:
package org.susan.java.logging;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.XMLFormatter;
public class CustomFormatter {
public static void main(String args[]) throws Exception{
XMLFormatter formatter = new XMLFormatter();
LogRecord record = new LogRecord(Level.INFO,"XML message...");
FileHandler handler = new FileHandler("formatter.xml");
handler.setFormatter(formatter);
handler.publish(record);
handler.flush();
}
}
这段代码控制台不会有任何输出,但是生成的formatter.xml文件的内容如下:
<?xml version="1.0" encoding="windows-1252" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2009-09-14T15:28:33</date>
<millis>1252913313104</millis>
<sequence>0</sequence>
<level>INFO</level>
<thread>10</thread>
<message>XML message...</message>
</record>
上边是XMLFormatter的一个简单的应用。
关于Filter和Formatter确实在整个日志系统框架里面没有太多的重难点,只要做几个简单的例子就能够理解Formatter和Filter的用法,下一小节会深入日志框架里面核心部分,之所以说比较核心是因为它的子类比较多,而且在整个包的API里面占了很大的比重。不过Formatter有两个子类,一个是SimpleFormatter,记录日志的格式如同我们平时启动Tomcat服务器的时候服务器上的输出;另外一种就是XMLFormatter,其格式就如同上边我们看到的代码
[7]Handler:
Handler对象从Logger中获取日志信息,并且可以将这些信息导出,它可以将这些信息写入控制台或者文件,也可以将这些信息发送到网络日志服务器上,或者转发到其他操作系统日志中。如果要禁用Handler,就直接使用setLevel(Level.OFF)来操作,并且可以根据适当的级别来重新启用。在java.util.logging包里面,先看看整个Handler的继承树快照:
java.util.logging.Handler
|—java.util.logging.MemoryHandler
|—java.util.logging.StreamHandler
|—java.util.logging.ConsoleHandler
|—java.util.logging.FileHandler
|—java.util.logging.SocketHandler
MemoryHandler:
MemoryHandler在内存中的循环缓冲区中对请求进行缓冲处理:通常情况下,该Handler只将传入的LogRecord存储到内存缓冲区,并且丢弃原来的记录。此缓冲很经济,为避免格式化开销,在一定的触发条件下,MemoryHandler将其当前的缓冲区内容push到目标Handler中,通常情况下此Handler将内容发布到外界。该触发条件包括:
传入的LogRecord类型大于预定义的pushLevel级别
外部类显示调用push方法
如果记录符合某些标准,则子类重写log方法,并扫描每个传入的LogRecord,调用push
配置:默认情况下,使用LogManager配置属性初始化每个MemoryHandler,如果没有定义,则使用指定的默认值。当默认值读取不到的时候会抛出RuntimeException。
java.util.logging.MemoryHandler.level指定Handler的级别(默认为Level.ALL)
java.util.logging.MemoryHandler.filter指定要使用的Filter类的名称(默认为无Filter)
java.util.logging.MemoryHandler.size定义缓冲区的大小(默认为1000)
java.util.logging.MemoryHandler.push定义pushLevel(默认为Level.SEVERE)
java.util.logging.MemoryHandler.target制定目标Handler类的名称(无默认值)
StreamHandler:
该Handler是基于流的日志Handler,此类主要作为基类,或支持实现其他日志Handlers所用的类,直接将LogRecord发布到给定的java.io.OutputStream
配置:
java.util.logging.StreamHandler.level指定Handler的默认级别(默认值为 Level.INFO)
java.util.logging.StreamHandler.filter指定要使用的Filter类的名称(默认值非 Filter)
java.util.logging.StreamHandler.formatter指定要使用的Formatter(默认值为java.util.logging.SimpleFormatter)
java.util.logging.StreamHandler.encoding要使用的字符集编码的名称(默认值为默认平台编码)
ConsoleHandler:
此Handler用于向控制台发布日志记录,是StreamHandler的子类,默认情况下直接使用SimpleFormatter生成简短摘要,但是注意一点发布到控制台的时候是向System.err发布,也就是说在IDE环境下很可能输出为红色字体
java.util.logging.ConsoleHandler.level为Handler指定默认的级别(默认为 Level.INFO)
java.util.logging.ConsoleHandler.filter指定要使用的Filter类的名称(默认为无 Filter)
java.util.logging.ConsoleHandler.formatter指定要使用的Formatter类的名称(默认为java.util.logging.SimpleFormatter)
java.util.logging.ConsoleHandler.encoding指定要使用的字符集编码的名称(默认为使用默认平台的编码)
SocketHander:
此Handler用于将LogRecord发布到网络日志,默认格式化为XMLFormatter,而且输入IO是缓冲的,每次写入LogRecord的时候都会刷新
java.util.logging.SocketHandler.level指定Handler的默认级别(默认值为 Level.ALL)
java.util.logging.SocketHandler.filter指定要使用的Filter类的名称(默认值非 Filter)
java.util.logging.SocketHandler.formatter指定要使用的Formatter(默认值为java.util.logging.XMLFormatter)
java.util.logging.SocketHandler.encoding要使用的字符集编码的名称(默认值为默认平台编码)
java.util.logging.SocketHandler.host指定要连接到的目标主机名(无默认值)
java.util.logging.SocketHandler.port指定要使用的目标TCP端口(无默认值)
FileHandler:
FileHandler可与将日志写入指定的文件,也可以写入文件轮换集。对于文件轮换集而言,到达每个文件的给定大小限制后,就关闭该文件,将其轮换出去,并打开新的文件。通过在基本文件中添加“0”,“1”,“2”等来依次命名旧文件。默认情况下,IO库中启用了缓冲,当缓冲完成的时候,每个记录都会被刷新,默认情况下,FileHandler的格式化输出使用的也是XMLFormatter。
java.util.logging.FileHandler.level为Handler指定默认的级别(默认为 Level.ALL)
java.util.logging.FileHandler.filter指定要使用的Filter类的名称(默认为无 Filter)
java.util.logging.FileHandler.formatter指定要使用的Formatter类的名称(默认为java.util.logging.XMLFormatter)
java.util.logging.FileHandler.encoding指定要使用的字符集编码的名称(默认使用默认的平台编码)
java.util.logging.FileHandler.limit指定要写入到任意文件的近似最大量(以字节为单位)如果该数为0,则没有限制(默认为无限制)
java.util.logging.FileHandler.count指定有多少输出文件参与循环(默认为1)
java.util.logging.FileHandler.pattern为生成的输出文件名称指定一个模式。有关细节请参见以下内容(默认为 "%h/java%u.log")
java.util.logging.FileHandler.append指定是否应该将FileHandler追加到任何现有文件上(默认为 false)
文件作日志记录的时候会遇到模式匹配问题,模式包括了以下特殊组件的字符串,则运行时需要替换这些组件:
"/" 本地路径名分隔符
"%t" 系统临时目录
"%h" "user.home"系统属性的值
"%g" 区分循环日志的生成号
"%u" 解决冲突的唯一号码
"%%" 转换为单个百分数符号"%"
上边出现过的日志里面的模式匹配会在日志记录的时候去读取相对应的值
2)关于配置文件:
[1]配置相关说明:
有一点需要说明,该日志记录框架是JDK 1.4 Logger Framework,那么我们在使用该框架的时候对JDK的要求是1.4以及以上的版本。默认情况下logging.properties的位置位于JAVA_HOME/jre/lib/logging.properties,在我们使用命令行执行某个程序需要使用自定义的配置文件的时候,需要按照下边这中方式来执行:
java -Djava.util.logging.config.file=D:\logging.properties LoggingTester
配置文件的记录格式说明如下:
Logger和Handler的属性名称是以圆点分隔的Logger或Handler的名称开头。
全局日志属性可以包括:
属性"handlers"。该属性为handler类定义类名的空白或逗号分隔列表,以便作为处理程序在根Logger(该Logger名为"")中加载和注册。每个类名必须用于具有默认构造方法的Handler类。注意,刚开始使用这些Handler时,它们可能是以延迟方式创建的。
属性"<logger>.handlers"。该属性为handler类定义空白分隔或逗号分隔的列表,以便作为处理程序加载和注册到指定的logger。每个类名必须用于一个具有默认构造方法的Handler类。注意,刚开始使用这些Handler时,它们可能是以延迟方式创建的。
属性"<logger>.useParentHandlers"。该属性定义一个boolean值。默认情况下,每个logger除了自己处理日志消息外,还可能调用其父级来处理,这往往也会导致根logger来处理消息。将此属性设置为false时,需要为此logger配置Handler,否则不传递任何消息。
属性"config"。此属性允许运行任意配置代码。该属性定义类名的空白或逗号分隔的列表。为每个指定类创建新实例。每个类的默认构造方法都可以执行任意代码来更新日志配置,如设置logger级别、添加处理程序、添加过滤器,等等。
注意,在LogManager配置期间加载的所有类,其搜索顺序是先从系统类路径中搜索,然后才从用户类中搜索。这包括LogManager类、任何config类和任何handler类。Logger是按其圆点分隔的名称被组织到命名层次结构中的。因此,"a.b.c"是"a.b"的子级,但"a.b1"和a.b2"属于同一级。假定所有以".level"结尾的名称的属性为Logger定义日志级别。因此,"foo.level"就为名称为"foo"的logger定义了日志级别,进而为指定层次结构中它的所有子级也逐个定义了日志级别。日志级别是按其在属性文件中的定义顺序应用的。因此,树中子节点的级别设置应该迟于其父级设置。属性名".level"可用于设置树的根级。
[2]配置文件例子:
# handlers
handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler
# general level
.level=INFO
# file handler
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
# console handler
java.util.logging.ConsoleHandler.level = FINEST
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
test.de.jayefem.log4e.logkits.JDK1_4_Logging.level = FINEST
[3]这里再提供一段读取远程配置文件的代码:
package org.susan.java.logging;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.logging.LogManager;
public class RemoteConfigReader {
private String urlString = "http://localhost/logging.properties";
private URL url;
private URLConnection urlConn = null;
private InputStream inputStream = null;
private LogManager logManager = null;
public RemoteConfigReader(){
try{
url = new URL(urlString);
urlConn = url.openConnection();
inputStream = urlConn.getInputStream();
logManager = LogManager.getLogManager();
// 如果是从本地文件读取,该原理也是一样的,多调用一次readConfiguration
logManager.readConfiguration(inputStream);
}catch(MalformedURLException mue){
System.err.println("Could not open url:" + urlString);
}catch(IOException ioe){
System.err.println("IOException occured in reading:" + urlString);
}catch(SecurityException se){
System.err.println("Security exception occured in class RemoteConfigReader");
}
}
public static void main(String args[]){
new RemoteConfigReader();
}
}
根据这段代码就可以知道,从本地非默认配置文件进行日志记录,只需要传入一个InputStream到readConfiguration方法就可以了……
本文来自CSDN博客:http://blog.csdn.net/silentbalanceyh/archive/2009/09/18/4564884.aspx
相关推荐
总结来说,Java异常处理机制提供了一种结构化的方法来处理程序运行时的错误,通过try-catch-finally结构捕获和处理异常,同时,利用断言进行内部逻辑验证,以及日志记录来跟踪程序行为。这样的机制增强了代码的健壮...
2. **Assert(断言)**:在Java编程中,`assert`关键字用于在测试阶段检查代码假设是否正确,它是进行单元测试和调试的强大工具。在本项目中,通过扩展Spring Boot的断言功能,我们可以创建自定义的断言方法,以适应...
Java异常处理是编程中至关重要的一个环节,它确保了程序在遇到错误情况时能够优雅地处理问题而不是突然崩溃。在Java中,异常分为两大类:错误(Error)和违例(Exception)。错误通常是JVM系统内部的问题,如内存...
Java提供了丰富的异常体系,包括标准的Java异常类和自定义异常。 1. **算术异常类:ArithmeticException** - 当程序执行了非法的数学运算,如除以零,就会抛出此异常。 2. **空指针异常类:NullPointerException**...
Java异常处理是编程中至关重要的一个环节,尤其是在Android开发中,理解并熟练运用异常处理能够有效地定位和解决程序运行中的问题。以下将详细介绍在Java和Android开发中常见的一些异常类型及其处理策略。 1. **空...
包括Java基本的程序结构、对象与类、继承、接口与内部类、图形程序设计、事件处理、Swing用户界面组件、部署应用程序和Applet、异常日志断言和调试、叙述方式深入浅出,并包含大量示例,从而帮助读者充分理解Java...
异常处理、断言和日志记录章节提供了错误处理和调试的方法。在泛型编程章节,读者将学习如何使用泛型来增强代码的类型安全性和重用性。集合框架的介绍涵盖了ArrayList、LinkedList、Set、Map等容器的使用,以及迭代...
Java异常处理是编程中至关重要的一个环节,它帮助开发者识别并修复程序运行时可能出现的问题。在Java中,异常是通过类来表示的,这些类都继承自`java.lang.Throwable`,并分为两种主要类型:Error和Exception。Error...
- **异常与测试**:在单元测试中,通过断言预期的异常被正确抛出来验证代码的健壮性,是一种常见的做法。这有助于确保程序在遇到错误情况时能够按照预期的方式响应。 通过上述讨论,可以看出自定义异常在 Java 开发...
手册会指导如何正确使用Java的异常处理机制,以及如何设置有效的日志记录,以便在问题发生时进行有效的调试和故障排查。 3. **单元测试**:单元测试是确保代码质量的重要手段。手册会介绍JUnit等测试框架的使用,...
包括Java基本的程序结构、对象与类、继承、接口与内部类、图形程序设计、事件处理、Swing用户界面组件、部署应用程序和Applet、异常日志断言和调试、叙述方式深入浅出,并包含大量示例,从而帮助读者充分理解Java...
包括Java基本的程序结构、对象与类、继承、接口与内部类、图形程序设计、事件处理、Swing用户界面组件、部署应用程序和Applet、异常日志断言和调试、叙述方式深入浅出,并包含大量示例,从而帮助读者充分理解Java...
在Java的第二季度技能等级二级考题中,我们聚焦于两个关键领域:源码理解和工具使用。源码分析是理解程序运行机制、优化代码性能以及解决问题的基础,而工具的熟练运用则能提升开发效率和团队协作。在这个考试中,...
### Java异常总结与详解 #### 引言 在Java编程中,异常处理是软件开发过程中不可或缺的一部分。良好的异常处理能够帮助我们及时发现并解决问题,从而提高程序的稳定性和健壮性。本文旨在全面总结Java中常见的异常...
二、异常日志 异常处理和日志记录是确保系统稳定运行的关键。开发者应根据异常的严重程度和类型选择合适的处理方式,如捕获并记录异常,但不立即终止程序;日志级别应区分调试(debug)、信息(info)、警告(warn)和错误...
第1章 Java 程序设计概述 1.1 Java 程序设计平台 1.2 Java 白皮书的关键术语 1.2.1 简单性 1.2.2 面向对象 1.2.3 网络技能 ...第11章 异常、日志、断言和调试 第12章 泛型程序设计 第13章 集合 第14章 多线程
第1章 Java 程序设计概述 1.1 Java 程序设计平台 1.2 Java 白皮书的关键术语 1.2.1 简单性 1.2.2 面向对象 1.2.3 网络技能 ...第11章 异常、日志、断言和调试 第12章 泛型程序设计 第13章 集合 第14章 多线程
这份"java后端开发规范word文档"包含了多个方面的内容,包括但不限于编程风格、命名规则、异常处理、并发控制、数据库操作、单元测试、日志记录以及代码组织结构等。 1. **编程风格**:编程风格是代码可读性的基础...