写有效的日志信息

一个 日志系统 是让您把信息写到一个或多个目的的机制。 一个 Logger 有以下组件:

  • 一个或多个处理器(handler):一个处理器将决定日志信息的目的和格式。您可以把日志写到终端、文件或数据库。
  • 名称: 一般用在类中的 Logger 的名称基于类名和包名
  • 级别:
    • 日志信息与一个表示重要性的级别关联。
    • Logger也有一个级别用来决定将输出哪些信息。它只输出重要性比它的级别一样或更重要的信息。

您应把日志系统用于以下2个主要目的:

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

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

实现

本节的示例代码在 com.elanzone.books.noteeg.chpt8.sect06 package中

  • MyFormatter 类 : extends java.util.logging.Formatter

    • format() 方法: 接受一个 LogRecord 对象作为参数并返回一个有日志信息的 String 对象
            @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();
            }
    
  • MyLogger 类

    • 静态属性 handler : java.util.logging.Handler 对象
            private static Handler handler;
    
    • 实现公有静态方法 getLogger() : 创建将用于写日志信息的 Logger 对象
      给 java.util.logging.Logger.getLogger() 的结果:
      • 设置日志级别,使输出所有级别的日志
      • 设置 handler 使输出到文件
            public static Logger getLogger(String name) {
                // 5. 获得与 name 关联的 Logger
                Logger logger = Logger.getLogger(name);
    
                // 6. 设置日志级别以输出所有级别的日志
                logger.setLevel(Level.ALL);
    
                try {
                    // 在用到时才创建 handler
                    if (handler == null) {
                        handler = new FileHandler("recipe8.log");
                        Formatter format = new MyFormatter();
                        handler.setFormatter(format);
                    }
                    if (logger.getHandlers().length == 0) {
                        logger.addHandler(handler);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
    
                return logger;
            }
    
  • Runnable 实现类 : Task : implements Runnable

    • run() 方法
      1. 获得与类名关联的 MyLogger 对象
      2. 调用 entering() 方法输出表示方法开始的日志信息
      3. 睡上2秒
      4. 调用 exiting() 方法输出表示方法结束的日志信息
            @Override
            public void run() {
                // 12. 获得类名对应的 Logger
                Logger logger = MyLogger.getLogger(this.getClass().getName());
    
                // 13. 用 entering() 方法输出一条表示方法开始执行的日志信息
                logger.entering(Thread.currentThread().getName(), "run()");
    
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                // 14. 用 exiting() 方法输出一条表示方法结束执行的日志信息
                logger.exiting(Thread.currentThread().getName(), "run()", Thread.currentThread());
            }
    
  • 控制类 : Main

    1. 获取与 Core 关联的 MyLogger 对象
    2. 调用 entering() 方法输出表示主程序开始的日志信息
    3. 创建5个 Task 对象及对应的线程,启动并等待其结束;在适当时机输出日志
    4. 调用 exiting() 方法输出表示主程序结束的日志信息
            public static void main(String[] args) {
                Logger logger = MyLogger.getLogger("Core");
                // 17. 用 entering() 方法输出一条表示主程序开始执行的日志信息
                logger.entering("Core", "main()", args);
    
                Thread threads[] = new Thread[5];
    
                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();
                }
    
                // 20. 输出 INFO 级别的日志,表示已创建了线程
                logger.log(Level.INFO, "Ten Threads created. Waiting for its finalization");
    
                // 21. 调用 join() 方法等待5个线程结束。每个线程计数后,输出日志表示线程已结束
                for (Thread thread : threads) {
                    try {
                        thread.join();
                        logger.log(Level.INFO, "Thread has finished its execution", thread);
                    } catch (InterruptedException e) {
                        logger.log(Level.SEVERE, "Exception", e);
                    }
                }
    
                // 22. 用 exiting() 方法输出一条表示主程序结束执行的日志信息
                logger.exiting("Core", "main()");
            }
    

讲解

在本节中,使用了Java logging API 提供的 Logger 类来在一个并发应用中写日志信息。 首先,实现了 MyFormatter 来来给日志信息一个格式。此类扩展了声明了抽象方法 format() 的 Formatter 类。 format() 方法接受一个有日志消息的所有信息的 LogRecord 对象作为参数,并返回一个格式好的日志消息。 在 MyFormatter 类中,使用了 LogRecord 类的以下方法来获得日志消息的信息:

  • getLevel(): 返回消息级别
  • getMillis(): 返回消息被送到 Logger 对象的时间
  • getSourceClassName(): 返回发送消息到 Logger 的类的名称
  • getSourceMethodName(): 返回发送消息到 Logger 的方法的名称
  • getMessage() : 返回日志消息

MyLogger 类实现了静态方法 getLogger(), 此方法创建一个 Logger 对象并设置一个 Handler 对象以把应用的日志消息按 MyFormatter 格式写到 recipe8.log 文件。 使用了 Logger 类的静态方法 getLogger() 来创建 Logger 对象。此方法对作为参数传递的每个名字返回一个不同的对象。 只创建了一个 Handler 对象,所以所有 Logger 对象将把日志写入同一个文件。 还配置了此 logger 写所有级别的日志消息,不管消息的级别。

最后,实现了一个 Task 对象和一个主程序,它们写不同的日志信息到日志文件。用了以下方法:

  • entering(): 写一条 FINER 级别的信息表示方法开始执行
  • exiting(): 写一条 FINER 级别的信息表示方法结束执行
  • log(): 写一条特定级别的信息

了解更多

使用日志系统时,必须考虑2个重点:

  • 输出必要的信息
    • 如果信息太少,logger因为不能达到目的而用处不大。
    • 如果信息太多,日志文件会很大,将难以管理,使得很难获得必要的信息
  • 为消息使用足够的级别 : 如果用一个较高的级别输出信息级别的消息或者用较低的级别输出错误信息,将使查看日志文件的用户困惑。 在出错时更难知道发生了什么,或者有太多信息来获知错误的主要原因。

有其他的类库提供比 java.util.logging 包更完备的日志系统,例如 Log4j 或 slf4j 库。 但 java.util.logging 包是 Java API 的一部分,并且所有方法是 多线程安全 的,所以能用之于并发应用,没有问题。