输出有效日志消息

Java 9并发编程指南 目录

日志系统是一种将信息写到一个或多个目的地机制,日志记录器组成部分如下:

  • **一个或多个处理器:**处理器确定日志消息的目标和格式,可以在控制台、文件或数据库中输出日志消息。
  • **名称:**通常类中使用的日志记录器的名称基于类名和其包的名。
  • **级别:**日志消息具有不同的级别,表明其重要性。日志记录器也有级别来决定哪些信息将要输出,它只输出重要性等于或者高于此级别的信息。

应当使用日志系统的两个主要原因如下所示:

  • 当捕获异常时,尽可能多的输出信息,这有助于定位错误并解决问题。
  • 输出程序正在执行的类和方法信息。

本节将学习如何使用java.util.logging包提供的类,在并发应用中添加日志系统。

准备工作

本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。

实现过程

通过如下步骤实现范例:

  1. 创建名为MyFormatter的类,继承java.util.logging.Formatter类。实现format()抽象方法,将LogRecord对象作为参数接收,并返回包含日志消息的String对象:

    public class MyFormatter extends Formatter {
    	@Override
    	public String format(LogRecord record) {
    		StringBuilder sb=new StringBuilder();
    		sb.append("["+record.getLevel()+"] - ");
    		sb.append(new Date(record.getMillis())+" : ");
    		sb.append(record.getSourceClassName()+ "." +record.getSourceMethodName()+" : ");
    		sb.append(record.getMessage()+"\n");
    		return sb.toString();
    	}
    }
    
  2. 创建名为MyLoggerFactory的类:

    public class MyLoggerFactory {
    
  3. 声明名为handler的私有静态Handler属性:

    	private static Handler handler;
    
  4. 实现公有静态方法getLogger(),创建用来输出日志消息的Logger对象,接收名为name的String参数。使用synchronized关键字同步此方法:

    	public synchronized static Logger getLogger(String name){
    
  5. 使用Logger类的getLogger()方法,得到将名字作为参数接收关联的java.util.logging.Logger:

    		Logger logger=Logger.getLogger(name);
    
  6. 使用setLevel()方法确立日志级别,输出所有日志消息:

    		logger.setLevel(Level.ALL);
    
  7. 如果handler属性值为null,创建新的FileHandler对象,输出日志消息到recipe6.log文件中。分配MyFormatter对象给处理器,使用setFormatter()对象指定格式:

    		try {
    			if (handler==null) {
    				handler=new FileHandler("recipe6.log");
    				Formatter format=new MyFormatter();
    				handler.setFormatter(format);
    			}
    
  8. 如果Logger对象没有与之关联的处理器,使用addHandler()方法分配处理器:

    			if (logger.getHandlers().length==0) {
    				logger.addHandler(handler);
    			}
    		} catch ( IOException e) {
    				e.printStackTrace();
    		}
    
  9. 返回创建的Logger对象:

    	return logger;
    	}
    }
    
  10. 创建名为Task的类,实现Runnable接口,用来测试Logger对象:

    public class Task implements Runnable{
    
  11. 实现run()方法:

    	@Override
    	public void run() {
    
  12. 首先,声明名为logger的Logger对象,使用MyLogger类的getLogger()方法,将Task类作为参数传递,进行初始化:

    		Logger logger= MyLoggerFactory.getLogger(this.getClass().getName());
    
  13. 使用entering()方法输出方法开始执行的日志消息:

    		logger.entering(Thread.currentThread().getName(), "run()");
    
  14. 休眠线程两秒钟:

    		try {
    			TimeUnit.SECONDS.sleep(2);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    
  15. 使用exiting()方法输出方法结束执行的日志消息:

    		logger.exiting(Thread.currentThread().getName(), "run()", Thread.currentThread());
    	}
    }
    
  16. 实现本范例主类,创建名为Main的类,包含main()方法:

    public class Main {
    	public static void main(String[] args) {
    
  17. 声明名为logger的Logger对象,使用MyLogger类的getLogger()方法,将Core字符串作为参数传递,进行初始化:

    		Logger logger=MyLoggerFactory.getLogger(Main.class.getName());
    
  18. 使用entering()方法输出主程序开始执行的日志消息:

    		logger.entering(Main.class.getName(), "main()",args);
    
  19. 创建Thread数组,存储五个线程:

    		Thread threads[]=new Thread[5];
    
  20. 创建五个Task对象和五个执行对象的线程。输出指明将要加载新线程和已经创建线程的日志消息:

    		for (int i=0; i<threads.length; i++) {
    			logger.log(Level.INFO,"Launching thread: "+i);
    			Task task=new Task();
    			threads[i]=new Thread(task);
    			logger.log(Level.INFO,"Thread created: "+ threads[i].getName());
    			threads[i].start();
    		}
    
  21. 输出指明已经创建线程的日志消息:

    		logger.log(Level.INFO,"Ten Threads created."+ "Waiting for its finalization");
    
  22. 使用join()方法等待五个线程结束。每个线程结束之后,输出指明线程已经完成的日志消息:

    		for (int i=0; i<threads.length; i++) {
    			try {
    				threads[i].join();
    				logger.log(Level.INFO,"Thread has finished its execution",
    				threads[i]);
    			} catch (InterruptedException e) {
    				logger.log(Level.SEVERE, "Exception", e);
    			}
    		}
    
  23. 使用exiting()方法输出主程序结束执行的日志消息:

    		logger.exiting(Main.class.getName(), "main()");
    	}
    }
    

工作原理

本节使用Java日志API提供的Logger类,在并发应用中输出日志消息。首先,实现MyFormatter类给日志消息指定格式,此类继承声明抽象方法format()的Formatter类。此方法接收具有所有日志消息的LogRecord对象,且返回格式化的日志消息。在类中,用到如下LogRecord类的方法,获得日志消息:

  • getLevel():返回消息级别
  • getMillis():当Logger对象发送消息时,返回日期
  • getSourceClassName():返回已经发送消息到日志记录器的类名
  • getSourceMessageName():返回已经发送消息到日志记录器的方法名
  • getMessage():返回日志消息

MyLogger类实现静态方法getLogger(),此方法创建Logger对象,指派Handler对象使用MyFormatter格式,输出应用的日志消息到recipe6.log文件中。创建包含Logger类的静态方法getLogger()的Logger对象,此方法返回作为参数传递的每个名称的不同对象。我们只创建了一个Handler对象,因此所有Logger对象输出日志消息到相同的文件中。还配置日志记录器输出所有日志消息,而不考虑其级别。

最后,实现了Task对象和输出日志消息到日志文件中的主程序,使用如下方法:

  • entering():使用FINER级别输出消息,指明方法已开始执行
  • exiting():使用FINER级别输出消息,指明方法已结束执行
  • log():输出指定级别的消息

扩展学习

当使用日志系统时,需要考虑两个要点:

  • **输出必要信息:**如果编写很少的信息,日志记录器无法达到其目的而失去作用,如果编写太多信息,将生成庞大混乱的日志文件,也将很难获取必要信息。
  • **消息使用适当级别:**如果输出高级别信息消息或者低级别错误消息,会混淆使用者查看日志文件。将更难知道在错误情况下发生了什么,或者因为太多的信息很难知道错误的主要原因。

还有其它库提供比java.util.logging包更完整的日志系统,例如Log4j或者slf4j类库。但java.util.logging包是Java API的一部分,并且所有方法都是多线程安全的,所以在并发应用中使用它不会出现任何问题。

更多关注

  • 第七章“并发集合”中的“使用非阻塞线程安全双端队列”、“使用阻塞线程安全双端队列”、“使用具有延迟元素的线程安全列表”和“使用线程安全的可操纵映射”小节

猜你喜欢

转载自blog.csdn.net/nicolastsuei/article/details/84648385