Java中的日志

日志

每个程序员都很熟悉在有问题的代码中插入一些System.out.println方法调用来帮助观察程序的行为。当然,一旦发现问题的根源,就要将这些print语句从代码中删去。如果接下来又出现问题,就需要再插入几个调用System.out.println方法的语句。日志API就是为了解决这个问题而设计的。下面是这个API的主要优点:

  • 可以很容易地取消全部日志记录,或者仅仅取消某个级别以下的日志,而且可以很容易地再次打开日志开关。
  • 可以很简单地禁止日志记录,因此,将这些日志代码留在程序中的开销很小。
  • 日志记录可以被定向到不同的处理器,如控制台显示、写至文件,等等。
  • 日志记录器和处理器都可以对记录进行过滤。过滤器可以根据过滤器实现器指定的标准丢弃那些无用的记录项。
  • 日志记录可以采用不同的方式格式化,例如,纯文本或XML。
  • 应用程序可以使用多个日志记录器,它们使用与包名类似的有层次结构的名字。
  • 日志系统的配置由配置文件控制。

很多应用会使用其他日志框架,如Log4J2,它们能提供比标准Java日志框架更高的性能。这些框架的API稍有区别。SLF4J和Commons Logging等日志门面提供了一个统一的API,利用这个API,你无须重写应用就可以替换日志框架。

基本日志

要生成简单的日志记录,可以使用全局日志记录器(global logger)并调用其info方法:

Logger.getGlobal().info("xxx");

但是如果在适当的地方(如main的最前面)调用

Logger.getGlobal().setLevel(Level.OFF);

将会取消所有日志。

高级日志

在一个专业的应用程序中,你肯定不想将所有的日志都记录到一个全局日志记录器中,你可以定义自己的日志记录器。

可以调用getLogger方法创建或获取日志记录器:

private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp");

未被任何变量引用的日志记录器可能会被垃圾回收。为了防止这种情况发生,要像上面的例子中一样,用静态变量存储日志记录器的引用。

与包名类似,日志记录器也具有层次结构。事实上,与包相比,日志记录器的层次性更强。对于包来说,包与父包之间没有语义关系,但是日志记录器的父与子之间将共享某些属性。例如,如果对日志记录器“com.mycompany”设置了日志级别,它的子日志记录器也会继承这个级别。

通常,有以下7个日志级别:

  • SEVERE
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST

在默认情况下,实际上只记录前3个级别。也可以设置一个不同的级别:

logger.setLevel(Level.FINE);

现在,FINE以及所有更高级别的日志都会记录。

另外,还可以使用Level.ALL开启所有级别的日志记录,或者使用Level.OFF关闭所有级别的日志记录。

所有级别都有日志记录方法:

logger.warning(message);
logger.fine(message);

或者,还可以使用log方法并指定级别:

logger.log(Level.FINE,message);

默认的日志配置会记录INFO或更高级别的所有日志,因此,应该使用CONFIG、FINE和FINEST级别来记录那些有助于诊断但对用户意义不大的调试信息。

如果将记录级别设置为比INFO更低的级别,还需要修改日志处理器的配置。默认的日志处理器会抑制低于INFO级别的消息。

默认的日志记录将显示根据调用堆栈得出的包含日志调用的类名和方法名。不过,如果虚拟机对执行过程进行了优化,就得不到准确的调用信息。此时,可以使用logp方法获得调用类和方法的确切位置,这个方法的签名:

void logp(Level l, String className, String methodName, String message)

有一些用来跟踪执行流的便利方法:

void entering(String className, String methodName)
void entering(String className, String methodName, Object param)
void entering(String className, String methodName, Object[] params)
void exiting(String className, String methodName)
void exiting(String className, String methodName, Object result)

例如:

int read(String file, String pattern) {
	logger.entering("com.company.mylib.Reader","read",new Object[]{file,pattern});
	...
	logger.exitint("com.company.mylib.Reader","read",count);
	return count;
}

这些调用将生成FINER级别而且以字符串ENTRY和RETURN开头的日志记录。

记录日志的常见用途是记录那些预料之外的异常。可以使用下面两个便利方法在日志记录中包含异常的描述。

void throwing(String className, String methodName, Throwable t)
void log(Level l, String message, Throwable t)

典型用法是:

if (...) {
	var e = new IOEXception("...");
	logger.throwing("com.mycompany.mylib.Reader","read",e);
	throw e;
}

try {
	...
}
catch (IOException e) {
	Logger.getLogger("com.mycompany.myapp").log(Level.WARNING,"Reading image",e);
}

throwing调用可以记录一条FINER级别的日志记录和一条以THROW开始的消息。

处理器

在默认情况下,日志记录器将记录发送到ConsoleHandler,并由它输出到System.err流。具体地,日志记录器会把记录发送到父处理器,而最终的祖先处理器(名为“”)有一个ConsoleHandler。

与日志记录器一样,处理器也有日志级别。对于一个要记录的日志记录,它的日志级别必须高于日志记录器和处理器二者的阈值。日志管理器配置文件将默认的控制台处理器的日志级别设置为

java.util.logging.ConsoleHandler.level=INFO

要想记录FINE级别的日志,就必须修改配置文件中的默认日志记录器级别和处理器级别。另外,还可以绕过配置文件,安装你自己的处理器:

Logger logger = Logger.getLogger("com.mycompany.myapp");
logger.setLevel(Level.FINE);
logger.setUseParentHandlers(false);
var handler = new ConsoleHandler();
handler.setLevel(Level.FINE);
logger.addHandler(handler);

在默认情况下,日志记录器将记录发送到自己的处理器和父日志记录器的处理器。我们的日志记录器是祖先日志记录器的子类,而这个祖先日志记录器会把所有等于或高于INFO级别的记录发送到控制台。不过,我们不想两次看到这些记录,因此应该将useParentHandlers属性设置为false。

想要将日志记录发送到其他地方,就要添加其他的处理器。日志API为此提供了两个很有用的处理器,一个是FileHandler;另一个是SocketHandler。SocketHandler将记录发送到指定的主机和端口。而更令人感兴趣的是FileHandler,它可以将记录收集到文件中。

示例:

package section5_5;

import java.io.IOException;
import java.util.Objects;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

public class test {

    public static void main(String[] args) throws IOException {
        Logger logger = Logger.getLogger("section5_5");
        FileHandler fileHandler = new FileHandler();
        logger.addHandler(fileHandler);
        logger.info("this is a record");
        String property = System.getProperty("user.home");
        System.out.println(property);
    }
}

输出:

C:\Users\Think
三月 04, 2021 10:44:57 上午 section5_5.test main
信息: this is a record

在C:\Users\Think下可以看到生成了java0.log文件:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
  <date>2021-03-04T10:44:57</date>
  <millis>1614825897452</millis>
  <sequence>0</sequence>
  <logger>section5_5</logger>
  <level>INFO</level>
  <class>section5_5.test</class>
  <method>main</method>
  <thread>1</thread>
  <message>this is a record</message>
</record>
</log>

过滤器

在默认情况下,或根据日志记录的级别进行过滤。每个日志记录器和处理器都有一个可选的过滤器来完成附加的过滤。要定义一个过滤器,需要实现Filter接口并定义以下方法:

boolean isLoggable(LogRecord record)

在这个方法中,可以使用你喜欢的标准分析日志记录,对那些应该包含在日志中的记录返回true。

要想将一个过滤器安装到一个日志记录器或处理器中,只需要调用setFilter方法就可以了。注意,同一时刻最多只能有一个过滤器。

格式化器

ConsoleHandler类和FileHandler类可以生成文本和XML格式的日志记录。不过,你也可以自定义格式。这需要扩展Formatter类并覆盖下面这个方法:

String format(LogRecord record)

可以根据自己的需要以任何方式对记录中的信息进行格式化,并返回结果字符串。在format方法中,可能会调用下面这个方法:

String formatMessage(LogRecord record)

这个方法对记录中的消息部分进行格式化,将替换参数并应用到本地化处理。

最后调用setFormatter方法将格式化器安装到处理器中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值