为什么每次调用LoggerFactory.getLogger(…)都不推荐?

我已经阅读了大量的post和文档(在本站和其他地方),指出推荐的SFL4J日志logging模式是:

public class MyClass { final static Logger logger = LoggerFactory.getLogger(MyClass.class); public void myMethod() { //do some stuff logger.debug("blah blah blah"); } } 

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

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

我认为每次我们logging日志的时候都要实例化一个logging器,但是我一直无法find支持这个假设的任何文档。 除了他肯定地说,框架(LogBack或Log4J我们仍在决定)将“caching”logging器,而且无论如何,服务器运行的速度远远低于它们的容量,所以这不是问题。

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

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

log4j / commons-logging / slf4j的标准习惯用法是使用如下的守卫子句:

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

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

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

请参阅“(不)logging的最快方式是什么?” 在SLF4J或log4j常见问题。

我会推荐你​​的老板build议的“包装”。 像slf4j或commons-logging这样的库已经成为使用的实际底层日志实现的一个外观。 此外,logging器的每个调用都变得更加冗长 – 比较上面的

  MyLoggerWrapper.debug(Foo.class, "some message"); 

这是一种不重要的,不重要的“包装”和混淆,除了增加间接和丑陋的代码之外,没有任何真正的目的。 我认为你的老板可以find更多重要的问题去追求。

logging器对象肯定会被重用,所以不会有任何额外的瞬变发生。 我看到的更大的问题是您的文件/行号信息将是无用的,因为logging器将始终忠实地logging每个消息是从类LoggerWrapper ,行12发出:-(

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

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

如果您的应用程序对您的MyLoggerWrapper.debug方法进行了大量调用,则这可能会带来显着的性能提升。 而在一个multithreading的应用程序中,这可能是一个并发瓶颈。

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

  • 您的应用程序不能再使用logger.isDebugEnabled()来最大限度地减lessdebugging禁用时的开销。
  • MyLoggerWrapperMyLoggerWrapper应用程序debugging调用的类名和行号。
  • 如果您进行多个logging器调用,使用MyLoggerWrapper的代码可能会更加冗长。 详细程度将在最易影响可读性的领域; 即在做需要logging的事情的方法。

最后,这只是“不是这样做”。


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

再加上已经提到的原因,老板的build议是不好的,因为:

  • 它强制你重复input一些与日志无关的东西,每次你想logging一些东西: this.getClass()
  • 在静态和非静态上下文之间创build一个非统一的接口(因为在静态上下文中没有this
  • 额外的不必要的参数为错误创造了空间,使得同一类中的语句可以到不同的logging器(认为不小心的复制粘贴)
  • 虽然它可以节省74个字符的logging器声明,但是每个logging调用都会增加27个字符。 这意味着如果一个class级使用logging器的次数超过2次,那么您将增加字符数的样板代码。

当使用像这样的东西:MyLoggerWrapper.debug(this.getClass(),“blah”)当您使用AOP框架或代码注入工具时,您将得到错误的类名。 类名不像起源,而是生成的类名。 使用包装的另一个缺点是:对于每个日志语句,必须包含附加的代码“MyClass.class”。

logging器的“caching”取决于使用的框架。 但即使这样做了,它仍然必须查找所需的日志logging器来logging每个日志。 所以在一个方法中有3个语句,它必须查找3次。 使用它作为一个静态variables,它只能查找一次!

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

你的上司反对“社区默认接受和推荐的方式”是什么? 引入包装并没有增加更多的效率。 相反,您必须使用每个日志语句的类名称。 过了一段时间,你想“改善”,所以你添加另一个variables,或另一个包装,使自己更加困难。

这里有一个简单的方法可以在Java 8中进行日志logging – 定义一个接口来为你做。 例如:

 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()函数负责连接你的string,但只有在testing启用之后。 它默认logging为debugging,如果您想loggingdebugging,则可以省略LogLevel参数。 这是一个非常简单的例子。 你可以做任何事情来改善这一点,比如实现单独的方法,即error(),trace(),warn()等等。你也可以简单地实现“logger”作为返回logging的函数:

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

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

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

您甚至可以通过为所有Logger方法生成委托方法来使其function完整,以便每个实现类都是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); } } 

当然,如前所述,这意味着每次login时,都必须通过LoggerFactory中的Logger查找过程 – 除非您重写logger()方法…在这种情况下,您可能会这样做“推荐”的方式。

我只是说,推荐的模式是最容易阅读和实施。 我看不出有什么理由偏离它。 尤其没有好处。

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

下载log4j的源代码,看看Logger和Category类,看看你自己。

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

没有,除了它弄乱了调用堆栈。 这干扰了方法,使您可以查看执行日志的代码的方法名称和类。

您可以考虑查看一下Jetty Web容器,其中包含自己的构build在slf4j之上的抽象。 非常好。

正如SLF4J团队在这里所陈述的那样,您可以使用JDK 1.7中引入的MethodLookup()。

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

这样你就可以在不需要使用关键字“this”的情况下引用这个类。

有两个原因,为什么你的老板的方法没有达到他的想法。

更小的原因是添加静态logging器的开销可以忽略不计。 毕竟,logging器设置是这个非常冗长的序列的一部分:

  • find类,即走所有的.jars和目录。 Java代码。 由于文件系统调用相当大的开销。 可以创build助手对象,例如使用File
  • 加载字节码,即将其复制到JVM中的数据结构中。 原生代码。
  • validation字节码。 原生代码。
  • 链接字节码,即迭代字节码中的所有类名称,并用指向类的指针replace它们。 原生代码。
  • 运行静态初始化程序。 从本机代码触发,当然初始化器是Java代码。 logging器在这里创build。
  • 过了一段时间,也许JIT编译类。 原生代码。 巨大的开销(与其他操作相比)。

而且,你的老板什么都不保存。
第一次调用LoggerFactor.getLogger将创buildlogging器并将其放置在全局名称到logging器HashMap中。 即使对于isXxxEnabled调用也会发生这种情况,因为要做到这些,您需要先构造Logger对象…
Class对象将为静态variables携带一个额外的指针。 这是通过传递clazz参数的开销来弥补的 – 在字节码中增加一个额外的指令和一个额外的指针大小的引用,所以你已经失去了至less一个字节的字节大小。

代码也是通过额外的间接LoggerFactory.getLogger(Class)LoggerFactory.getLogger(Class)使用Class#getName并委托给LoggerFactory.getLogger(String)

现在,如果你的老板不是在性能之后,而是在复制静态声明的能力之后,他可以使用一个检查调用栈并检索类名的函数。 该函数应该注解@CallerSensitive ,而且每当使用新的JVM时,它仍然是需要testing的东西 – 如果不控制用户正在运行代码的JVM,那就不好。

问题最less的方法是有一个检查logging器实例化的IDE。 这可能意味着find或写一个插件。

我可能在之前的一个注释中错过了它,但是我没有看到logging器是静态的 ,LoggerFactory的调用是ONCE(每个实例化的类) – 所以最初关心多个调用创buildlogging器是错的。

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