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

时间:2010-10-13 13:09:04

标签: 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");
    }
}

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

public class MyLoggerWrapper {
    public static void debug(Class clazz, String msg){
        LoggerFactory.getLogger(clazz).debug(msg));
    }
}

并简单地使用它:

public class MyClass {

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

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

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

12 个答案:

答案 0 :(得分:29)

这种方法存在一个明显的问题:每次调用debug()时都会构造String消息,没有明显的方法可以在包装器中使用guard子句。

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

if (log.isDebugEnabled()) log.debug("blah blah blah");

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

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

请参阅"What is the fastest way of (not) logging?" in the SLF4Jlog4j常见问题解答。

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

进行比较
 MyLoggerWrapper.debug(Foo.class, "some message");

这是一种琐碎且不重要的“包装”和混淆,除了添加间接层和丑陋的代码之外,它没有任何实际用途。我认为你的老板可以找到更重要的问题来讨论。

答案 1 :(得分:12)

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

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

答案 2 :(得分:10)

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

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

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

  • 您的应用程序无法再使用logger.isDebugEnabled()来最小化调试被禁用时的开销。
  • MyLoggerWrapper类隐藏了应用程序调试调用的类名和行号。
  • 如果您进行多次记录器调用,使用MyLoggerWrapper的代码可能会更详细。冗长将出现在最易影响可读性的领域;即在那些需要记录的方法中。

最后,这只是“不是它完成的方式”。


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

答案 3 :(得分:9)

除了上面提到的原因之外,老板的建议很糟糕,因为:

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

答案 4 :(得分:6)

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

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

之前说过:你失去了使用if( log.isXXXEnabled() ){}作为一组陈述的能力。

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

答案 5 :(得分: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());
    }
}

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

log(INFO, "This is the first part ","of my string ","and this ","is the last");

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

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

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

logger().debug("This is my message");

您甚至可以通过为所有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);
    }
}

当然,如前所述,这意味着每次登录时,您都必须在LoggerFactory中完成Logger查找过程 - 除非您覆盖logger()方法......在这种情况下,您不妨这样做"推荐"方式。

答案 6 :(得分:4)

我只需要说推荐的模式最容易阅读和实现。我认为没有理由偏离它。特别没有好处。

然而,我的主要观点是关于前面提到的警卫。我不建议明确保护您的日志,因为这已经由log4j在内部完成,并且是重复工作。

下载log4j的源代码并查看Logger和Category类,以便亲自查看。

答案 7 :(得分:4)

2015版的答案:请用lombok @slf4j释放你的想法。

答案 8 :(得分:4)

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

final static Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

通过这种方式,您可以参考该课程而无需使用关键字"此"。

答案 9 :(得分:3)

没有。除此之外,它会混淆调用堆栈。这会破坏允许您查看执行日志的代码的方法名称和类的方法。

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

答案 10 :(得分:1)

为什么你的老板有两个原因?方法没有实现他的想法。

较小的原因是添加静态记录器的开销可以忽略不计。毕竟,记录器设置是这个相当冗长的序列的一部分:

  • 找到该类,即遍历所有.jars和目录。 Java代码。由于文件系统调用,开销很大。可以创建辅助对象,例如与File
  • 加载字节码,即将其复制到JVM内的数据结构中。原生代码。
  • 验证字节码。原生代码。
  • 链接字节码,即迭代字节码中的所有类名,并用指向引用类的指针替换它们。原生代码。
  • 运行静态初始化程序。从本机代码触发,初始化程序当然是Java代码。 Logger在这里创建。
  • 过了一会儿,也许是JIT-编译课程。本机代码。 巨大的开销(与其他操作相比)。

另外,你的老板什么都不保存 对LoggerFactor.getLogger的第一次调用将创建记录器并将其放在全局名称到Logger HashMap中。即使对于isXxxEnabled调用也会发生这种情况,因为要执行这些操作,您需要首先构造Logger对象...
Class对象将携带一个额外的静态变量指针。这被传递clazz参数 - 字节码中的附加指令和附加指针大小的引用的开销所抵消,因此您已经失去了类大小中的至少一个字节。

代码也经历了额外的间接,LoggerFactory.getLogger(Class)使用Class#getName并委托给LoggerFactory.getLogger(String)

现在,如果你的老板不是在表演之后,而是在简单地复制静态声明之后,他可以使用一个检查调用堆栈并检索类名的函数。该函数应该注释@CallerSensitive,并且无论何时使用新的JVM,它仍然需要进行测试 - 如果你不控制用户运行代码的JVM,那就不好了。 / p>

最不成问题的方法是使用IDE来检查记录器实例化。这可能意味着找到或编写插件。

答案 11 :(得分:0)

我可能在之前的一条评论中错过了它,但我没有看到记录器静态的提及,对LoggerFactory的调用是ONCE(每个实例化())所以最初关于创建记录器的多次调用的关注是错误的。

关于添加包装类的所有问题的其他评论也非常重要。