日志系统是一种将信息写到一个或多个目的地机制,日志记录器组成部分如下:
- **一个或多个处理器:**处理器确定日志消息的目标和格式,可以在控制台、文件或数据库中输出日志消息。
- **名称:**通常类中使用的日志记录器的名称基于类名和其包的名。
- **级别:**日志消息具有不同的级别,表明其重要性。日志记录器也有级别来决定哪些信息将要输出,它只输出重要性等于或者高于此级别的信息。
应当使用日志系统的两个主要原因如下所示:
- 当捕获异常时,尽可能多的输出信息,这有助于定位错误并解决问题。
- 输出程序正在执行的类和方法信息。
本节将学习如何使用java.util.logging包提供的类,在并发应用中添加日志系统。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤实现范例:
-
创建名为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(); } }
-
创建名为MyLoggerFactory的类:
public class MyLoggerFactory {
-
声明名为handler的私有静态Handler属性:
private static Handler handler;
-
实现公有静态方法getLogger(),创建用来输出日志消息的Logger对象,接收名为name的String参数。使用synchronized关键字同步此方法:
public synchronized static Logger getLogger(String name){
-
使用Logger类的getLogger()方法,得到将名字作为参数接收关联的java.util.logging.Logger:
Logger logger=Logger.getLogger(name);
-
使用setLevel()方法确立日志级别,输出所有日志消息:
logger.setLevel(Level.ALL);
-
如果handler属性值为null,创建新的FileHandler对象,输出日志消息到recipe6.log文件中。分配MyFormatter对象给处理器,使用setFormatter()对象指定格式:
try { if (handler==null) { handler=new FileHandler("recipe6.log"); Formatter format=new MyFormatter(); handler.setFormatter(format); }
-
如果Logger对象没有与之关联的处理器,使用addHandler()方法分配处理器:
if (logger.getHandlers().length==0) { logger.addHandler(handler); } } catch ( IOException e) { e.printStackTrace(); }
-
返回创建的Logger对象:
return logger; } }
-
创建名为Task的类,实现Runnable接口,用来测试Logger对象:
public class Task implements Runnable{
-
实现run()方法:
@Override public void run() {
-
首先,声明名为logger的Logger对象,使用MyLogger类的getLogger()方法,将Task类作为参数传递,进行初始化:
Logger logger= MyLoggerFactory.getLogger(this.getClass().getName());
-
使用entering()方法输出方法开始执行的日志消息:
logger.entering(Thread.currentThread().getName(), "run()");
-
休眠线程两秒钟:
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
-
使用exiting()方法输出方法结束执行的日志消息:
logger.exiting(Thread.currentThread().getName(), "run()", Thread.currentThread()); } }
-
实现本范例主类,创建名为Main的类,包含main()方法:
public class Main { public static void main(String[] args) {
-
声明名为logger的Logger对象,使用MyLogger类的getLogger()方法,将Core字符串作为参数传递,进行初始化:
Logger logger=MyLoggerFactory.getLogger(Main.class.getName());
-
使用entering()方法输出主程序开始执行的日志消息:
logger.entering(Main.class.getName(), "main()",args);
-
创建Thread数组,存储五个线程:
Thread threads[]=new Thread[5];
-
创建五个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(); }
-
输出指明已经创建线程的日志消息:
logger.log(Level.INFO,"Ten Threads created."+ "Waiting for its finalization");
-
使用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); } }
-
使用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的一部分,并且所有方法都是多线程安全的,所以在并发应用中使用它不会出现任何问题。
更多关注
- 第七章“并发集合”中的“使用非阻塞线程安全双端队列”、“使用阻塞线程安全双端队列”、“使用具有延迟元素的线程安全列表”和“使用线程安全的可操纵映射”小节