为什么不建议每次都调用LoggerFactory.getLogger(...)?

dan*_*rod 42 java logging log4j logback slf4j

我已经阅读了很多帖子和文档(在这个网站和其他地方),指出SFL4J日志记录的推荐模式是:

public class MyClass {
    final static Logger logger = LoggerFactory.getLogger(MyClass.class);

    public void myMethod() {
        //do some stuff
        logger.debug("blah blah blah");
    }
}
Run Code Online (Sandbox Code Playgroud)

我的老板更喜欢我们只使用一个包装器来拦截日志调用,并避免使用样板代码来声明每个类的记录器:

public class MyLoggerWrapper {
    public static void debug(Class clazz, String msg){
        LoggerFactory.getLogger(clazz).debug(msg));
    }
}
Run Code Online (Sandbox Code Playgroud)

并简单地使用它:

public class MyClass {

    public void myMethod() {
        //do some stuff
        MyLoggerWrapper.debug(this.getClass(), "blah blah blah");
    }
}
Run Code Online (Sandbox Code Playgroud)

我假设每次我们记录时实例化一个记录器有点贵,但我一直无法找到支持该假设的任何文件.除此之外他肯定说框架(LogBack或Log4J我们仍在决定)将"缓存"记录器,而且无论如何服务器的运行速度都远低于它们的容量,所以这不是问题.

有任何帮助指出这种方法的潜在问题?

mat*_*t b 30

这个方法有一个明显的问题:String消息将在每次调用时构造debug(),没有明显的方法在你的包装器中使用一个guard子句.

log4j/commons-logging/slf4j的标准习惯用法是使用一个保护子句,例如:

if (log.isDebugEnabled()) log.debug("blah blah blah");
Run Code Online (Sandbox Code Playgroud)

目的是如果DEBUG没有为记录器启用级别,编译器可以避免将您可能发送的任何更长的字符串连接在一起:

if (log.isDebugEnabled()) log.debug("the result of method foo is " + bar 
     + ", and the length is " + blah.length());
Run Code Online (Sandbox Code Playgroud)

请参阅"(不)记录的最快方式是什么?" 在SLF4Jlog4j FAQ中.

我建议不要反对老板建议的"包装".像slf4j或commons-logging这样的库已经是围绕所使用的实际底层日志记录实现的一个外观.此外,记录器的每次调用都会变得更长 - 比较上面的内容

 MyLoggerWrapper.debug(Foo.class, "some message");
Run Code Online (Sandbox Code Playgroud)

这是微不足道的,不重要"包装"和模糊处理,供应比增加间接的和丑陋的,fying你的代码层之外没有什么实际意义的类型.我认为你的老板可以找到更重要的问题来讨论.

  • 我不确定我是否同意每个类的一行代码做`final static Logger logger = LoggerFactory.getLogger(MyClass.class)`就是那么难看.使用您选择的IDE,很容易想出一个简单的模板/宏,这样您只需几次击键就可以将这个片段添加到文件中.然而美丽在旁观者的眼中...... (5认同)
  • 好吧,他更像是后卫子句使用参数化的消息-as你的链接和其他文件我已经读 - 我们刚刚实现在包装所需的方法进行解释已经采取的护理团队铅/铅程序员;-)如所须.与包装日志丑陋同意()调用,但我还没有任何这里的答案确信这是比记录器实例化更糟糕的每一个类,这是在颈部疼痛,有点丑也. (3认同)
  • 在eclipse中,这是我在上下文中创建的模板:Java类型成员`private static final Logger log = LoggerFactory.getLogger($ {enclosing_type} .class); $ {:进口(org.slf4j.Logger,org.slf4j.LoggerFactory)} $ {光标}` (2认同)

Pét*_*rök 12

记录器对象肯定会被重用,因此无论如何都不会发生额外的瞬间.我看到的更大的问题是你的文件/行号信息将是无用的,因为记录器将始终忠实地记录每个消息是从类LoggerWrapper12行发出的:-(

OTOH SLF4J已经是一个包装器外观,用于隐藏所使用的特定日志框架,允许您在不同的日志记录实现之间自由切换.因此,我认为隐藏在另一个包装器后面绝对没有意义.

  • 应该指出,在日志记录输出中包含行号非常慢 (3认同)

Ste*_*n C 11

重复调用LoggerFactory.getLogger(clazz)不应每次都产生新的Logger对象.但这并不意味着电话是免费的.虽然实际行为取决于外观后面的日志记录系统,但每个getLogger很可能需要在并发或同步数据结构1中查找以查找预先存在的实例.

如果您的应用程序对您的MyLoggerWrapper.debug方法进行了大量调用,那么这些都可能会对性能产生重大影响.在多线程应用程序中,它可能是并发瓶颈.

其他答案提到的其他问题也很重要:

  • logger.isDebugEnabled()在禁用调试时,您的应用程序无法再用于最小化开销.
  • MyLoggerWrapper班掩盖您的应用程序的调试调用的类名和行号.
  • MyLoggerWrapper如果您进行多个记录器调用,则使用的代码可能会更加冗长.冗长将出现在最易影响可读性的领域; 即在做需要记录的事情的方法中.

最后,这只是"不是它完成的方式".


1 - 显然它是Hashtable在Logback和Log4j中,这意味着并发瓶颈的可能性肯定存在.请注意,这不是对那些日志框架的批评.相反,该getLogger方法未被设计/优化以便以这种方式使用.


oks*_*ayt 9

为了补充已经提到的原因,老板的建议很糟糕,因为:

  • 每次要记录某些内容时,它会强制您重复键入与日志记录无关的内容: this.getClass()
  • 在静态和非静态上下文之间创建非统一接口(因为this在静态上下文中没有)
  • 额外的不必要参数会产生错误空间,使同一类中的语句可以转到不同的记录器(想想粗心的复制粘贴)
  • 虽然它保存了74个字符的记录器声明,但它为每个记录调用添加了27个额外的字符.这意味着如果一个类使用记录器超过2次,那么就字符数增加了样板代码.


小智 7

使用类似的东西MyLoggerWrapper.debug(this.getClass(), "blah") 时:使用AOP框架或代码注入工具时,您将得到错误的类名.类名不是原点,而是生成的类名.使用包装器的另一个缺点:对于每个日志语句,您必须包含其他代码"MyClass.class".

记录器的"缓存"取决于使用的框架.但即使它确实如此,它仍然必须为您创建的每个日志语句查找所需的记录器.因此,在方法中有3个语句,它必须查找3次.使用它作为static变量,它只能查找一次!

之前说过:你失去了使用if( log.isXXXEnabled() ){}一组语句的能力.

你的老板对"社区默认接受和推荐的方式"有什么看法?介绍包装器并没有增加更多的效率.相反,您必须为每个日志语句使用classname.过了一段时间你想"改进"那个,所以你添加另一个变量,或者另一个包装器让你自己更难.


Ste*_*e K 5

这里有一种方法可以让您轻松地在Java 8中进行日志记录 - 定义一个接口来为您完成.例如:

package logtesting;

import java.util.Arrays;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public interface Loggable { 
    enum LogLevel {
        TRACE, DEBUG, INFO, WARN, ERROR
    }

    LogLevel TRACE = LogLevel.TRACE;
    LogLevel DEBUG = LogLevel.DEBUG;
    LogLevel INFO = LogLevel.INFO;
    LogLevel WARN = LogLevel.WARN;
    LogLevel ERROR = LogLevel.ERROR;

    default void log(Object...args){
        log(DEBUG, args);
    }

    default void log(final LogLevel level, final Object...args){
        Logger logger = LoggerFactory.getLogger(this.getClass());
        switch(level){
        case ERROR:
            if (logger.isErrorEnabled()){
                logger.error(concat(args));
            }
            break;
        case WARN:
            if (logger.isWarnEnabled()){
                logger.warn(concat(args));
            }
            break;          
        case INFO:
            if (logger.isInfoEnabled()){
                logger.info(concat(args));
            }
        case TRACE:
            if (logger.isTraceEnabled()){
                logger.trace(concat(args));
            }
            break;
        default:
            if (logger.isDebugEnabled()){
                logger.debug(concat(args));
            }
            break;
        }
    }

    default String concat(final Object...args){ 
        return Arrays.stream(args).map(o->o.toString()).collect(Collectors.joining());
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您所要做的就是确保您的类声明实现Logged,并且从其中任何一个,您可以执行以下操作:

log(INFO, "This is the first part ","of my string ","and this ","is the last");
Run Code Online (Sandbox Code Playgroud)

log()函数负责连接字符串,但只有在测试启用后才能使用.默认情况下,它会记录到调试,如果要记录到调试,则可以省略LogLevel参数.这是一个非常简单的例子.你可以做很多事情来改进,例如实现各个方法,即error(),trace(),warn()等.你也可以简单地将"logger"实现为一个返回记录器的函数:

public interface Loggable {
    default Logger logger(){
        return LoggerFactory.getLogger(this.getClass());
    }
}
Run Code Online (Sandbox Code Playgroud)

然后使用您的记录器变得非常简单:

logger().debug("This is my message");
Run Code Online (Sandbox Code Playgroud)

您甚至可以通过为所有Logger方法生成委托方法使其完全正常运行,这样每个实现类都是Logger的一个实例.

package logtesting;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.Marker;

public interface Loggable extends Logger {
    default Logger logger(){
        return LoggerFactory.getLogger(this.getClass());
    }

    default String getName() {
        return logger().getName();
    }

    default boolean isTraceEnabled() {
        return logger().isTraceEnabled();
    }

    default void trace(String msg) {
        logger().trace(msg);
    }

    default void trace(String format, Object arg) {
        logger().trace(format, arg);
    }

    default void trace(String format, Object arg1, Object arg2) {
        logger().trace(format, arg1, arg2);
    }

    default void trace(String format, Object... arguments) {
        logger().trace(format, arguments);
    }

    default void trace(String msg, Throwable t) {
        logger().trace(msg, t);
    }

    default boolean isTraceEnabled(Marker marker) {
        return logger().isTraceEnabled(marker);
    }

    default void trace(Marker marker, String msg) {
        logger().trace(marker, msg);
    }

    default void trace(Marker marker, String format, Object arg) {
        logger().trace(marker, format, arg);
    }

    default void trace(Marker marker, String format, Object arg1, Object arg2) {
        logger().trace(marker, format, arg1, arg2);
    }

    default void trace(Marker marker, String format, Object... argArray) {
        logger().trace(marker, format, argArray);
    }

    default void trace(Marker marker, String msg, Throwable t) {
        logger().trace(marker, msg, t);
    }

    default boolean isDebugEnabled() {
        return logger().isDebugEnabled();
    }

    default void debug(String msg) {
        logger().debug(msg);
    }

    default void debug(String format, Object arg) {
        logger().debug(format, arg);
    }

    default void debug(String format, Object arg1, Object arg2) {
        logger().debug(format, arg1, arg2);
    }

    default void debug(String format, Object... arguments) {
        logger().debug(format, arguments);
    }

    default void debug(String msg, Throwable t) {
        logger().debug(msg, t);
    }

    default boolean isDebugEnabled(Marker marker) {
        return logger().isDebugEnabled(marker);
    }

    default void debug(Marker marker, String msg) {
        logger().debug(marker, msg);
    }

    default void debug(Marker marker, String format, Object arg) {
        logger().debug(marker, format, arg);
    }

    default void debug(Marker marker, String format, Object arg1, Object arg2) {
        logger().debug(marker, format, arg1, arg2);
    }

    default void debug(Marker marker, String format, Object... arguments) {
        logger().debug(marker, format, arguments);
    }

    default void debug(Marker marker, String msg, Throwable t) {
        logger().debug(marker, msg, t);
    }

    default boolean isInfoEnabled() {
        return logger().isInfoEnabled();
    }

    default void info(String msg) {
        logger().info(msg);
    }

    default void info(String format, Object arg) {
        logger().info(format, arg);
    }

    default void info(String format, Object arg1, Object arg2) {
        logger().info(format, arg1, arg2);
    }

    default void info(String format, Object... arguments) {
        logger().info(format, arguments);
    }

    default void info(String msg, Throwable t) {
        logger().info(msg, t);
    }

    default boolean isInfoEnabled(Marker marker) {
        return logger().isInfoEnabled(marker);
    }

    default void info(Marker marker, String msg) {
        logger().info(marker, msg);
    }

    default void info(Marker marker, String format, Object arg) {
        logger().info(marker, format, arg);
    }

    default void info(Marker marker, String format, Object arg1, Object arg2) {
        logger().info(marker, format, arg1, arg2);
    }

    default void info(Marker marker, String format, Object... arguments) {
        logger().info(marker, format, arguments);
    }

    default void info(Marker marker, String msg, Throwable t) {
        logger().info(marker, msg, t);
    }

    default boolean isWarnEnabled() {
        return logger().isWarnEnabled();
    }

    default void warn(String msg) {
        logger().warn(msg);
    }

    default void warn(String format, Object arg) {
        logger().warn(format, arg);
    }

    default void warn(String format, Object... arguments) {
        logger().warn(format, arguments);
    }

    default void warn(String format, Object arg1, Object arg2) {
        logger().warn(format, arg1, arg2);
    }

    default void warn(String msg, Throwable t) {
        logger().warn(msg, t);
    }

    default boolean isWarnEnabled(Marker marker) {
        return logger().isWarnEnabled(marker);
    }

    default void warn(Marker marker, String msg) {
        logger().warn(marker, msg);
    }

    default void warn(Marker marker, String format, Object arg) {
        logger().warn(marker, format, arg);
    }

    default void warn(Marker marker, String format, Object arg1, Object arg2) {
        logger().warn(marker, format, arg1, arg2);
    }

    default void warn(Marker marker, String format, Object... arguments) {
        logger().warn(marker, format, arguments);
    }

    default void warn(Marker marker, String msg, Throwable t) {
        logger().warn(marker, msg, t);
    }

    default boolean isErrorEnabled() {
        return logger().isErrorEnabled();
    }

    default void error(String msg) {
        logger().error(msg);
    }

    default void error(String format, Object arg) {
        logger().error(format, arg);
    }

    default void error(String format, Object arg1, Object arg2) {
        logger().error(format, arg1, arg2);
    }

    default void error(String format, Object... arguments) {
        logger().error(format, arguments);
    }

    default void error(String msg, Throwable t) {
        logger().error(msg, t);
    }

    default boolean isErrorEnabled(Marker marker) {
        return logger().isErrorEnabled(marker);
    }

    default void error(Marker marker, String msg) {
        logger().error(marker, msg);
    }

    default void error(Marker marker, String format, Object arg) {
        logger().error(marker, format, arg);
    }

    default void error(Marker marker, String format, Object arg1, Object arg2) {
        logger().error(marker, format, arg1, arg2);
    }

    default void error(Marker marker, String format, Object... arguments) {
        logger().error(marker, format, arguments);
    }

    default void error(Marker marker, String msg, Throwable t) {
        logger().error(marker, msg, t);
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,如前所述,这意味着每次登录时,您都必须在LoggerFactory中完成Logger查找过程 - 除非您覆盖logger()方法......在这种情况下,您可以执行此操作"推荐"的方式.


Mar*_*NCE 5

正如SLF4J 团队在此处所述,您可以使用 JDK 1.7 中引入的 MethodLookup()。

final static Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
Run Code Online (Sandbox Code Playgroud)

这样您就可以引用该类,而无需使用关键字“this”。


Tho*_*sen 3

不,除了它会弄乱调用堆栈之外。这会破坏允许您查看执行日志的代码的方法名称和类的方法。

您可以考虑查看 Jetty Web 容器,它包含构建在 slf4j 之上的自己的抽象。很不错。