使我的记录器对我的 Java 应用程序非常有效

Make my logger very effective to my Java-application

我正在为以下问题而苦苦挣扎并寻求帮助。

我的应用程序有一个记录器模块。这采用跟踪级别和消息(作为字符串)。

通常应该是从不同来源 and/or 以不同方式构造的消息(例如,一次在记录之前使用 String.format,其他时候使用不同对象的 .toString 方法等)。因此:错误信息的构造方法不能一概而论

我想要的是让我的记录器模块有效。这意味着:只有在实际跟踪级别获得消息时才会构建跟踪消息。这是通过防止在我的应用程序中复制粘贴代码。

使用C/C++,通过使用宏很容易实现:

#define LOG_IT(level, message) if(level>=App.actLevel_) LOG_MSG(message);

LOG_MSG 和字符串构造仅在跟踪级别启用该消息时才完成。

对于 Java,我没有发现任何类似的可能性。这是为了防止:日志记录将是一行(没有 if-else 到处复制粘贴),并且仅在必要时才进行字符串构造(昂贵的操作)。

我知道的唯一解决方案是用 IF 语句包围每个记录器调用。但这正是我之前在 C++ 应用程序中避免的,也是我在实际 Java-实现中想要避免的。

我的问题是,在目标系统上只有 Java 1.6 可用。因此供应商不是一个选择。

我可以在 Java 做什么? C/C++这个方法怎么能轻松搞定?

最新的日志记录库,包括 java.util.logging,有第二种形式的方法,采用 Supplier<String>

例如log.info( ()->"Hello"); 而不是 log.info("Hello");.

供应商的 get() 方法仅在必须有效记录消息时调用,因此您的字符串仅在这种情况下构建。

首先,如果您正在考虑实现自己的记录器,我鼓励您阅读 this

然后,我鼓励您查看完善的日志记录 API,例如 SLF4j。虽然可以创建您自己的,但使用预先存在的 API 将节省您的时间、精力,最重要的是为您提供更多开箱即用的功能和灵活性(即基于文件的配置、可定制性(查看映射诊断上下文)).

对于您的具体问题,没有一种简单的方法可以完成您想要做的事情。 C/C++ 与 java 根本不同,因为预处理器允许使用您在上面创建的宏。 Java 确实没有易于使用的等效项,尽管有一些项目示例确实使用了编译时代码生成,这可能是最接近的等效项(即 Project Lombok、Mapstruct)。

据我所知,在日志记录时避免昂贵的字符串构建操作的最简单方法是用简单的条件包围字符串的构建:

if ( logger.isTraceEnabled() )
{
    // Really expensive operation here
}

或者,如果您使用的是 Java 8,则标准日志记录库采用 java.util.function.Supplier<T> 参数,仅当当前日志级别与正在调用的日志记录方法匹配时才会执行该参数:

 log.fine(()-> "Value is: " + getValue());

目前还有一张 SLF4j 的工单可以实现此功能 here

如果您真的打算实现自己的记录器,上述两个功能很容易自己实现,但我再次鼓励您不要这样做。

编辑:Aspectj 编译时编织可用于实现类似于您要实现的目标。它允许您使用条件语句包装所有日志语句,以删除样板检查。

我认为这里要理解的最重要的事情是 C/C++ 宏解决方案不会通过不构造记录的消息来节省计算量,在如果日志级别是这样的消息将不会被记录。 为什么会这样?仅仅是因为宏方法会使预处理器替换宏的每次使用:

LOG_IT(level, message)

使用代码:

if(level>=App.actLevel_) LOG_MSG(message);

将您作为 level 传递的任何内容替换为 message 传递的任何内容以及宏本身。要编译的结果代码将 与您在程序的任何地方复制并粘贴 code 完全相同。宏唯一可以帮助您的是避免实际的复制和粘贴,并使代码更具可读性和可维护性。
有时他们设法做到了,有时他们使代码更加神秘,因此更难维护。在任何情况下,宏都不提供延迟执行 来避免实际构造字符串,正如 Java8 Logger class 通过使用 lambda 所做的那样表达式。 Java 将 lambda 主体的执行推迟到最后可能的时间。换句话说,lambda 的主体是在 if 语句之后执行的。
回到 C\C++ 中的示例,作为开发人员,您可能希望代码无论日志级别如何都能正常工作,因此您将被迫构造一个有效的字符串消息并将其传递给宏.否则在某些日志级别,程序会崩溃!所以,由于消息字符串构造代码必须在调用宏之前,所以无论日志级别如何,你每次都会执行它。

因此,在 Java6 中制作等同于您的代码非常简单!您只需自动使用内置 class: Logger. This class provides support for logging levels,因此您不需要创建它们的自定义实现。
不过,如果你问的是如何在没有 lambda 的情况下实现延迟执行,我认为这是不可能的。

如果你想在 C\C++ 中进行真正的延迟执行,你必须将日志记录代码设为这样,将函数指针指向返回消息字符串的函数,你将编写你的代码执行通过 if 语句内的函数指针传递给您的函数,然后您将调用您的宏,传递的不是字符串,而是创建和 returns 字符串的函数!我相信执行此操作的实际 C\C++ 代码超出了这个问题的范围...这里的关键概念是 C\C++ 为您提供了制作工具延迟执行,仅仅是因为它们支持函数指针。 Java不支持函数指针,直到Java8