告别System.out.print() —J2SDK1.4新增Java日志框架 (作者:Sonzhang Zhao ) J2SDK1.4的Java日志框架,其实总结起来主要是下面几点: 命名空间:空间外层包含内层的设置 Handler:可以将日志信息放入内容,定向到文件,或控制台等 Lever:规定日志的级别,低级别的日志可以被忽略 Formatter:负责将日志进行格式化,这样出来的东西比较好看一点 引言 作为一名Java 程序员,最熟悉的、使用最多的调用恐怕莫过于System.out.print(“…”)。当你没有调试工具而要跟踪一个变量的值得时候;当你需要显示捕获的Exception、Error的时候;当你想知道程序在运行的时候究竟发生了什么的时候,通常的做法就是调用System.out.print把他们在终端、控制台上打印出来。这种方式对于输出信息的分类、格式化及永久保存带来诸多不便。虽然我们可以把它写入一个文件然后进行分析,但是这要需要编写额外的程序代码,其成本不可忽视!而由此给目标系统本身增加的复杂程度不可避免的使开发、调试陷入一个深深的迷潭。 JDK1.4的推出,使得这一切即将成为历史。让我们向System.out.print()告别吧,使用Java Logging API为自己的程序构建一个完整的日志记录系统! 一、第一个实例 先看一个简单的实例:SimpleLoggingTest.java import java.util.logging.*; public class SimpleLoggingTest { public static void main(String args[]) { //程序的其它处理 //使用Logger的静态方法获得一个匿名Logger Logger logger1 = Logger.getAnonymousLogger(); //记录消息 logger1.log(Level.INFO,"第一条日志记录"); //程序的其它处理 } } 实例1 注意:编译、执行该程序需要JDK1.4及以上版本的支持。 运行该程序,可以在控制台看到程序运行结果: 2003-1-14 15:09:40 SimpleLoggingTest main 信息: 第一条日志记录 首先,程序引用java.util.Logging包(第1行)。接着,在适当的时候获得一个Logger(记录器)类的实例(第6行,获取一个匿名的Logger)。最后,在程序需要记录信息的地方调用Logger类的log方法进行记录(第8行,记录一个INFO级别的消息)。 二、Java Logging API Java Logging API封装在JDK1.4.0的java.util.Logging包中。它通过产生便于最终用户、系统管理员、故障维护工程师以及软件开发团队(工程师)进行分析的日志记录为软件的开发调试和维护提供便利的手段。它捕获操作系统平台和执行程序的安全故障、配置错误、执行瓶颈和(或)Bug等数据信息,以纯文本、XML或程序员自定的某种方式将其格式化成日志记录,然后传递给内存、系统输出流、控制台、文件、Sockets等多种系统资源进行缓存和输出。 (一)、该软件包中的关键类。 .. Logger: 应用程序进行日志记录调用的主要实体。 Logger对象用于记录特定系统或应用程序的消息。 .. LogRecord: 用于在日志框架和单个记录处理程序之间传递记录请求。 .. Handler: 日志数据的最终输出处理器。它将LogRecord对象导出到各种目标,包括内存、输出流、控制台、文件和套接字。多种 Handler子类可供用于这种用途。 .. Level: 定义一组标准的记录级别,可用于控制记录的输出。可以把程序配置为只输出某些级别的记录,而忽略其他级别的输出。 .. Filter: 精细过滤、控制记录的内容,比记录级别所提供的控制准确得多。 记录API支持通用的过滤器机制,这种机制允许应用程序代码添加任意过滤器以便控制记录的输出。 .. Formatter: 为LogRecord对象的格式化提供支持。 .. LogManager: Java Logging框架中唯一的、全局的对象,用于维护与Logger记录器及日志服务的一系列共享的数据结构及状态。它负责整个日志框架的初始化、维护一组全局性的Handle对象、 维护一个树形结构的Logger的名字空间、诊测日志框架配置文件的改变从而重新读入并应用相关的参数以及负责程序停止运行时整个日志框架的清理工作。 (二)Logger 1、Logger的命名空间 在SimpleLoggingTest.java实例中,我们使用了一个匿名的(没有命名的)Logger对象。在Java Logging 框架中,Logger是可以命名的。Logger的名字空间与java类的名字空间相同的结构相同:使用“.”间隔的字符串。Logger的名字空间体现了Logger的层次结构。例如:命名为“a.b”的Logger是命名为“a.b.c”的“父”(上一级)Logger记录器。Logger的命名可以是任意的字符串,一般情况下,使用包或类的名字为Logger 进行命名。 Logger的名字空间由全局单列类LogManager的实例进行创建、维护。 匿名Logger不被存储在命名空间中。 2、创建Logger实例 Logger对象可以通过调用工厂方法getLogger或getAnonymousLogger获取。 //获取一个名为“A”的Logger对象 Logger loggerA= Logger.getLogger(“A”); // 获取一个名为“A.B”的Logger对象,其上级记录器为loggerA。 Logger loggerAB= Logger.getLogger(“A.B”); //获取一个匿名Logger对象 Logger loggerTmp = Logger.getAnonymousLogger(); 对非匿名Logger,getLogger先在命名空间中查找同名的Logger对象,如果有,则返回该Logger对象;如果不存在,则在命名空间中创建注册一个新的Logger对象,并与其上级Logger对象相关联。匿名Logger对象属于创建它的对象的私有对象,只能由创建它的对象使用,记录一些临时性的日志信息。而命名Logger对象是全局性的,在日志框架的生存期内,除了创建它的对象外还,可由其它对象用于记录日志信息。 (三)、Handler Handler对象接收传来的日志消息将其输出。Handler可以把日志消息输出到多种目标资源,如:输出到控制台进行显示、写入日志文件、传送到网络上的远程日志服务进行处理、写入系统日志等任何物理资源。 Handler对象在创建时使用LogManager对象的相关属性的默认值(如Handler的Filter、Formatter、Level等对象属性)进行初始化。Handler对象可通过调用setLevel(Level.OFF)暂停工作;通过调用setLevel设置适当的记录日志消息级别恢复工作。 Handler是一个抽象类。在J2SDK1.4中,其子类及它们之间的关系见图一。 1、 MemoryHandler Handler的子类,在内存中的一个循环缓冲区用于缓存日志记录请求。通常MemoryHandler只简单的把传入的LogRecords存储到它的内存中。这种缓存的开销非常低廉,它去掉了格式化所产生的系统消耗。当某个触发条件满足时,MemoryHandler将其缓冲的数据push(发布)到目标Handler,由后者执行实际的输出。有三种模式触发MemoryHandler进行push操作:a、传入的LogRecords的级别高于MemoryHandler预先定义的push级别;b、有其他对象显式的调用其push方法;c、其子类重载了log方法,逐一检索每个传入的LogRecords,若符合特定的标准则进行push操作。 实例: 假设我们需要跟踪一个生产环境中的一个很少出现的Bug。在大多数场合,系统化产生大量的日志记录,而我们仅只关心记录中最近的几条,那么我们只需要使用MemoryHandler对日志记录进行缓存,当且仅当某个事件 发生时将最近的几条记录从内存中dump到制定的文件中。 //MemoryHandlerTest.java import java.util.logging.*; import java.io.*; public class MemoryHandlerTest { FileHandler fhandler; Logger logger; MemoryHandler mhandler; MemoryHandlerTest() { try { //构造名为my.log的日志记录文件 fhandler = new FileHandler("my.log"); int numRec = 5; //构造一个5个日志记录的MemoryHandler, //其目标Handler为一个FileHandler mhandler = new MemoryHandler (fhandler, numRec, Level.OFF) ; //构造一个记录器 logger = Logger.getLogger("com.mycompany"); //为记录器添加一个MemoryHandler logger.addHandler(mhandler); } catch (IOException e) { } } public static void main(String args[]) { MemoryHandlerTest mt = new MemoryHandlerTest(); int trigger = (int)(Math.random()*100); for (int i=1;i<100;i++) { //在MemoryHandler中缓存日志记录 mt.logger.log(Level.INFO,"日志记录"+i); if (i==trigger) { //触发事件成立,显式调用MemoryHandler的 //push方法触发目标Handler输出日志记录到 //my.log文件中 mt.mhandler.push(); break; } } } } 实例2 2、 FileHandler 文件处理器。 StreamHandler流处理器将日志记录以流的形式输出。FileHandler、ConsoleHandler、SocketHandler为StreamHandler的子类。 ConsoleHandler将日志记录输出到控制终端。前面的实例(实例2除外)都将日记记录数据输出到控制台。 FileHandler将日志记录输出到特定的文件,或循环的几个日志文件中。日志文件可以设置容量大小。当日志文件达到限定的容量时将被自动清空,重头开始写入新的日志记录数据。 例:创建一个容量为1Mb的文件处理器 int limit = 1000000; // 1 Mb FileHandler fh = new FileHandler("my.log", limit, 1); 对于循环的日志文件,每个文件将被指定容量限制。当当前的日志文件的长度达到制定值后该文件被关闭,新的日志文件被创建,旧的文件将在文件名模板后追加序号。如此产生多个顺序编号的日志记录文件。 例: try { // 创建一个拥有3个日志文件,每个容量为1Mb的文件处理器 String pattern = "my%g.log"; int limit = 1000000; // 1 Mb int numLogFiles = 3; FileHandler fh = new FileHandler(pattern, limit, numLogFiles); … } catch (IOException e) { } (四)、Level Level对象定义了一组日志消息的级别,用于控制日志消息的输出。一个级别对应一个整型值。日志消息级别按照其整数值的大小排定优先级。在Logger对象中设定一个级别,则大于等于该级别的日志消息将会被传递到某个Handler对象进行输出处理。 J2SDK1.4的Java Logging框架中定义了以下消息级别: 级别名称 int值(Windows 2000环境) OFF 2147483647 SEVERE 1000 WARNING 900 INFO 800 CONFIG 700 FINE 500 FINER 400 FINEST 300 ALL -2147483648 表一 Level.OFF具有最高的级别。将Logger的Level级别设置成Level.OFF 让我们看看消息级别是怎样工作的: LoggingLevelTest.java import java.util.logging.*; public class LoggingLevelTest { public static void main(String args[]) { //使用Logger的静态方法获得一个匿名Logger Logger logger1 = Logger.getAnonymousLogger(); //设置Logger对象记录的最低日志消息级别 logger1.setLevel(Level.FINER); //记录消息 logger1.severe("SEVERE级消息"); logger1.warning("WARNING级消息"); logger1.config("CONFIG级消息"); logger1.info("INFO级消息"); logger1.fine("FINE级消息"); logger1.finer("FINER级消息"); logger1.finest("FINEST级消息"); } } 实例3 运行结果: 2003-1-15 7:02:03 LoggingLevelTest main 服务器: SEVERE级消息 2003-1-15 7:02:04 LoggingLevelTest main 警告: WARNING级消息 2003-1-15 7:02:04 LoggingLevelTest main 配置: CONFIG级消息 2003-1-15 7:02:04 LoggingLevelTest main 信息: INFO级消息 可以看出,优先级低于INFO的日志消息不被记录。 Level的构造函数为protected便于程序员开发自己的消息级别类。 import java.util.logging.*; //自定义消息级别 class myLevel extends Level{ //定义自己的消息级别SYSE public static final Level SYSE = new myLevel("SYSE", Level.SEVERE.intValue()+10); public myLevel(String ln,int v) { super(ln,v); } } public class MyLevelTest { public static void main(String args[]) { Logger logger1 = Logger.getAnonymousLogger(); //设置消息级别 logger1.setLevel(myLevel.SYSE); //记录消息 logger1.log(myLevel.SYSE,"SYSE消息"); logger1.severe("SVERE消息"); } } 实例4 运行结果: 2003-1-15 15:40:04 MyLevelTest main SYSE: SYSE消息 只有SYSE消息被记录,SVERE消息不被记录,因为自定义级别SYSE高于SEVERE. (五)Formatter Formatter负责对LogRecords进行格式化。每个记录吕砥鱄andler同一个Formatter对象相关联。Formatter对象接收从Handler传来的LogRecord,将其格式化成字符串后返回给Handler进行输出。 Formatter是一个抽象类。在J2SDK1.4中,其子类及它们之间的关系见图二。 自定义扩展Formatter类。 实例:MyFormatterTest.java import java.util.Date; import java.util.logging.*; //创建每条日志记录以行的日志格式: //时间<空格>消息级别<空格>消息ID<空格>日志信息内容<换行> class MyFormatter extends Formatter { public String format(LogRecord rec) { StringBuffer buf = new StringBuffer(1000); buf.append(new Date().toLocaleString()); //时间 buf.append(' '); buf.append(rec.getLevel()); //消息级别 buf.append(' '); buf.append(rec.getMillis()); //作为消息ID buf.append(' '); buf.append(formatMessage(rec));//格式化日志记录数据 buf.append('\n'); //换行 return buf.toString(); } } public class MyFormatterTest { public static void main(String args[]){ //创建记录器 Logger log1 = Logger.getLogger("MyLogger"); //创建记录处理器 Handler mh = new ConsoleHandler(); //为记录处理器设置Formatter mh.setFormatter(new MyFormatter()); //为记录器添加记录处理器 log1.addHandler(mh); //禁止消息处理将日志消息上传给父级处理器 log1.setUseParentHandlers(false); //记录消息 log1.severe("消息1"); log1.warning("消息2"); log1.info("消息3"); log1.config("消息4"); } } 实例5 程序运行结果: 2003-1-15 16:59:38 SEVERE 1042621178968 消息1 2003-1-15 16:59:40 WARNING 1042621178985 消息2 2003-1-15 16:59:41 INFO 1042621179105 消息3 三、配置文件 J2SDK1.4的Java Logging框架的配置文件(Windows): %J2SDK1.4_HOME%/jre/lib/logging.properties 从配置文件可以看到: (一) 自定义日志配置文件: java -Djava.util.logging.config.file=myfile (二)全局Handler在Java VM启动时被加载。 (二) 全局Handler默认为java.util.logging.ConsoleHandler。 handlers= java.util.logging.ConsoleHandler 所以我们的任何日志记录动作都会在控制台进行显示。 (三) 缺省的消息记录级别为:INFO .level= INFO 在缺省情况下我们在控制台看不见低于INFO级别的日志消息。 (四) 缺省的Handler消息格式为java.util.logging.SimpleFormatter 四、日志框架在程序测试中的应用 Logger类提供了两个的方法:Logger.entering()、 ogger.exiting() 。 这对我们调试自己的方法调用提供了便利的方式。 例子: 记录方法调用的输入参数和输出参数 方法myMethod将一个int 追加在一个对象之后。 运行该程序应将logging.properties的 java.util.logging.ConsoleHandler.level = INFO 改为: java.util.logging.ConsoleHandler.level = ALL import java.util.logging.*; public class MyClass { public String myMethod(int p1, Object p2) { Logger logger = Logger.getLogger("com.mycompany.MyClass"); if (logger.isLoggable(Level.FINER)) { logger.entering(this.getClass().getName(), "myMethod", new Object[]{new Integer(p1), p2}); } String tmp = p2.toString() + p1; if (logger.isLoggable(Level.FINER)) { logger.exiting(this.getClass().getName(), "myMethod", tmp); } return tmp; } public static void main(String args[]) { MyClass mc = new MyClass(); String rslt = mc.myMethod(123,"Hello"); } } 后记 J2SDK1.4引入的日志记录框架为构建简易的日志记录系统提供了便利的解决方案。虽 然还有期它的一些专用日志包如Log4j,但从简单的打印输出到严密的、可扩展的日志记录 框架,J2SDK1.4的日志系统已经足以满足一般的系统开发的要求。

评论