如何在Java 8&amp ;;中分解条件日志检查。 SLF4J?

时间:2017-06-25 03:27:38

标签: java logging slf4j

我正在尝试分析if (log.isInfoEnabled())if (log.isWarnEnabled())语句,所以我想在 Java 8 中创建一个接口,如下所示但是我不确定我是否可以运行任何问题?

public interface Logging {

    Logger log_ = LoggerFactory.getLogger(Thread.currentThread().getContextClassLoader().getClass());

    default void logInfo(String... messages) {
        if (log_.isInfoEnabled()) {
            String meg = String.join("log message: ", messages)
            log_.info(msg);
        }
    }
}


public class Hello implements Logging {

    //Imagine this function is called lot of times like imagine
    // an infinite loop calling this function
    public void doSomething(String name) {
        if (name.equals("hello")) {
            logInfo("What class name does the log print here ? ")  
        }
    }

     //Imagine this function is called lot of times like imagine
    // an infinite loop calling this function
   public void doSomething2(String name) {
        if (name.equals("hello2")) {
            logInfo("What class name does the log print here ? ")  
        }
    }

    //Imagine this function is called lot of times like imagine
    // an infinite loop calling this function
    public void doSomething3(String name) {
        if (name.equals("hello3")) {
            logInfo("What class name does the log print here ? ")  
        }
    }
}

VS

public class Hello {

    Logger log_ = LoggerFactory.getLogger(Hello.class);

    public void doSomething(String name) {
        if (name.equals("hello")) {
            if (log_.isInfoEnabled()) { // Don't want to have this code everywhere
                logInfo("What class name does the log print here ? ") 
            }
        }
    }
}

这两个相同吗?如果我使用上面的Logging界面会出现任何问题吗?

6 个答案:

答案 0 :(得分:1)

我认为你的方法即使有效,也毫无意义。 log方法已经完成了这项工作。

我们使用日志保护条件的原因是字符串连接。

问题在于:

log.debug("This is the case where " + varToBeChecked);

在这种情况下,您必须使用保护条件:

if (log.isDebugEnabled()) {
    log.debug("This is the case where " + varToBeChecked);
}  

这不仅可以避免执行日志语句,还可以避免字符串连接,这将在previos示例中完成。

因此,如果您只有一个没有连接的固定字符串,则没有理由使用保护条件:

log.info("Method starts here");

在您的提案中,界面并未保护您免受此问题的影响,因此您将在不解决问题的情况下增加复杂性。

此日志工作人员的解决方案可能是更改日志界面:

log.debug("My log message for %s of run", myVarToBeLogged);

在这种情况下,我们可以避免字符串连接,但我不确定它与使用保护条件的性能是否相同。

答案 1 :(得分:1)

只有在启用特定日志记录级别时,日志记录方法才会输出要记录的值。手动检查日志记录级别的唯一情况是创建要记录的消息是昂贵的。比如说,您必须从数据库中检索延迟加载的字段,或者遍历包含许多项的集合。在这种情况下,创建一个接口来模拟mixin并使用函数编程是有意义的,如下所示:

public interface Logging {

    Logger getLogger();

    default void logInfo(Supplier<String> msg){
        Logger logger = getLogger();
        if (logger.isInfoEnabled()){
            logger.info(msg.get());
        }
    }

}

public class Something implements Logging {

    public static Logger LOG = LoggerFactory.getLogger(Something.class);

    @Override
    public Logger getLogger() {
        return LOG;
    }

    public void doSomething(){
        logInfo(this::createExpensiveMessage); 
    }

    private String createExpensiveMessage(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // Nobody cares 
        }

        return "Something happened";
   }

}

答案 2 :(得分:1)

  

我试图分析if(log.isInfoEnabled())或if   (log.isWarnEnabled())语句

我认为你不明白使用if (log.isLevelXXXEnabled()){...}模式的方式和原因。

你在这里写的是什么:

public void doSomething(String name) {
    if (name.equals("hello")) {
        if (log_.isInfoEnabled()) { // Don't want to have this code everywhere
            logInfo("What class name does the log print here ? ") 
        }
    }
}

实际上不需要被包围:

if (log_.isInfoEnabled()){

为什么?
 因为您将String传递给不需要任何计算的日志:"What class name does the log print here ?"到您的记录器 此外,记录器已设计为仅在记录器的实际级别与请求的日志级别匹配时才写入日志。

现在,假设您使用昂贵的toString()方法传递了一个对象 记录器确实调用传递对象的toString()方法,以便在实际执行日志时记录。 但在这种情况下,在记录之前使用检查仍然无用。

假设myObject是一个变量,它引用一个类的实例,toString()执行多个计算或只是一组对象。 这很无奈:

if (log_.isInfoEnabled()) { 
    logInfo(myObject); 
}

因为只有当记录器的有效日志级别与请求的日志记录级别匹配时,记录器实现才会执行myObject.toString()

只有在必须对传递的对象执行相对昂贵的计算时,才使用此检查日志模式 通常,当您执行计算并将其作为参数提供给日志操作时 例如:

if (log_.isInfoEnabled()) { 
    log_.info("name :" + myPerson.getName() + ", order history :" + myPerson.getOrders()); 
}

这里有意义不是因为你节省了4个连接,而是因为myPerson.getOrders()是一个具有潜在数百个元素的集合,并且仅在你真正记录时才应该调用toString()
< / p>

现在您如何设计一种可以进行此特定检查和任何其他特定检查的通用方法? 这是不可能的。
事实上,您尝试做的是可能的,但只会重复记录器库已经为您做的事情 您应该为每个特定情况执行检查的具体情况,因为它很昂贵。

现在根据喜好,您当然可以替换它:

if (log_.isInfoEnabled()) { 
    log_.info("name :" + myPerson.getName() + ", order history :" + myPerson.getOrders()); 
}

通过其他语法:

log_.info("name : {}, order history : {}", myPerson.getName(), myPerson.getOrders()); 

依赖于

public void info(String format, Object... arguments);

org.slf4j.Logger类的方法。

使用if (log_.isInfoEnabled()) {public void info(String format, Object... arguments);并没有真正改变问题,因为您应该始终考虑使用这种方式来节省潜在的计算。

答案 3 :(得分:1)

防护装置(应该)主要用于防止在不需要时进行昂贵的操作 - 仅用于调试。

所以以下是正确的模式:

    if (log_.isInfoEnabled()) {
        String meg = String.join("log message: ", f(), g(), h);
        log_.info(msg);
    }

default void logInfo(Supplier<String> messageSupplier) {
    if (log_.isInfoEnabled()) {
        log_.info(messageSupplier.get());
    }
}

logInfo(() -> String.join("log message: ", f(), g(), h));

但是,您可能会将此方法用于简单的日志记录案例,将其转换为代价高昂的额外函数调用logInfo和该lambda。

答案 4 :(得分:0)

如果使用带参数的消息且参数为对象,则不需要防护。

https://www.slf4j.org/faq.html#logging_performance

这是一条参数化消息:SELECT ps_stock_available.quantity FROM ps_stock_available INNER JOIN ps_product_attribute ON ps_product_attribute.id_product ON ps_stock_available.id_product WHERE ps_product_attribute.reference =100102

答案 5 :(得分:0)

如果日志记录参数使用某种类型的处理来访问我们要记录的对象,则仍然需要保护措施。 slf4j延迟的只是对对象的toString()方法的调用,而不是对获取对象本身而执行的计算。这个警卫合理的放在这里:

if (log.isdebugEnabled()) {
 log.debug("Value {} is not valid", obj.connectToDBAndRetrieveValue());
}

因此,使用自定义日志包装器封装保护逻辑可能很方便。而且,它将把这种情况的单元测试从客户端代码转移到具体的包装器类,这将变得更容易,更清洁。

请注意,由于INFO通常在生产中启用,因此对于INFO级别实际上不需要这种包装。除非不是你这种情况。