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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3923971/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 06:59:39  来源:igfitidea点击:

Why calling LoggerFactory.getLogger(...) every time is not recommended?

javalogginglog4jlogbackslf4j

提问by danirod

I've read tons of posts and documents (on this site and elsewhere) pointing that the recommended pattern for SFL4J logging is:

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

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

    public void myMethod() {
        //do some stuff
        logger.debug("blah blah blah");
    }
}

My boss prefers we just use a wrapper to intercept log calls and avoid boiler plate code for declaring the logger on every class:

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

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

and simply using it like this:

并像这样简单地使用它:

public class MyClass {

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

I presume instantiating a logger every time we log is somewhat expensive but I've been unable to find any document backing that assumption. Besides he says surely the framework (LogBack or Log4J we're still deciding) will "cache" the loggers and also that in any case the servers are running very much below their capacity so it is not an issue.

我认为每次我们记录时实例化一个记录器有点昂贵,但我一直无法找到任何支持该假设的文档。此外,他说框架(我们仍在决定 LogBack 或 Log4J)肯定会“缓存”记录器,而且在任何情况下,服务器的运行能力都远远低于其容量,因此这不是问题。

Any help pointing out potential problems with this approach?

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

回答by Thorbj?rn Ravn Andersen

No. Not other than it messes up the call stack. That disrupts the methods that allow you to see the method name and class of the code doing the log.

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

You may consider having a look at the Jetty web container which contains their own abstraction which builds on top of slf4j. Very nice.

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

回答by Péter T?r?k

The logger objects are surely reused, so no extra instantation is going to happen either way. The bigger problem I see is that your file/line number info will be useless, since the logger will always faithfully log that each message was issued from class LoggerWrapper, line 12 :-(

记录器对象肯定会被重用,因此无论哪种方式都不会发生额外的实例化。我看到的更大问题是您的文件/行号信息将毫无用处,因为记录器将始终忠实地记录每条消息是从LoggerWrapper第 12 行类发出的:-(

OTOH SLF4Jis already a wrapper facade to hide the specific logging framework used, allowing you to freely change between different logging implementations. Therefore I see absolutely no point in hiding that behind yet another wrapper.

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

回答by matt b

Here is one obvious problem with this approach: the String messages will be constructed on each call to debug(), there is no obvious way to use a guard clause with your wrapper.

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

The standard idiom with log4j/commons-logging/slf4j is to use a guard clause such as:

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

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

With the purpose being that if the DEBUGlevel is not enabled for the logger, the compiler can avoid concatenating together any longer strings you may send it:

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

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

See "What is the fastest way of (not) logging?" in the SLF4Jor log4jFAQ.

请参阅“最快的(非)日志记录方式是什么?” 在 SLF4Jlog4j常见问题解答中。

I would recommend against the "wrapper" your boss suggests. A library like slf4j or commons-logging is already a facade around the actual underlying logging implementation used. In addition, each invocation of the logger becomes much lengthier - compare the above with

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

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

This is the type of trivial and unimportant "wrapping" and obfuscation that serves no real purpose other than adding layers of indirection and ugly-fying your code. I think your boss can find more important issues to obsess over.

这是一种琐碎且不重要的“包装”和混淆类型,除了添加间接层和丑陋的代码之外,没有真正的目的。我认为你的老板可以找到更重要的问题来解决。

回答by Stephen C

Repeated calls to LoggerFactory.getLogger(clazz)should not result in a new Logger object each time. But that does not mean that the calls are free. While the actual behaviour depends on the logging system behind the facade, it is highly likely that each getLogger entails a lookup in a concurrent or synchronized data structure1to look for a pre-existing instance.

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

If your application makes lots of calls to your MyLoggerWrapper.debugmethod, this can all add up to a significant performance hit. And in a multi-threaded application, it might be a concurrency bottleneck.

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

Other issues mentioned by other answers are also important:

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

  • Your application can no longer use logger.isDebugEnabled()to minimize the overheads when debugging is disabled.
  • The MyLoggerWrapperclass obscures the class names and line numbers of your application's debug calls.
  • The code using MyLoggerWrapperwill probably be more verbose if you make multiple logger calls. And the verbosity will be in the area where it impacts readability most; i.e. in the methods that do things that need logging.
  • logger.isDebugEnabled()当调试被禁用时,您的应用程序不能再使用来最小化开销。
  • MyLoggerWrapper班掩盖您的应用程序的调试调用的类名和行号。
  • MyLoggerWrapper如果您进行多个记录器调用,使用的代码可能会更加冗长。并且冗长将在最影响可读性的区域;即在做需要记录的事情的方法中。

Finally, this is just "not the way that it is done".

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



1 - Apparently it is a Hashtablein Logback and Log4j, and that means that the potential for a concurrency bottleneck definitely exists. Note that this is not a criticism of those logging frameworks. Rather, the getLoggermethod was not designed/optimized to be used this way.

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

回答by Alasdair

I just have to say that the recommended pattern is easiest to read and implement. I see no reason for straying from it. Especially no benefit.

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

However, my main point is about the guards mentioned previously. I would not recommend explicitly guarding your logs as this is already done internally by log4j and is a duplication of effort.

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

Download the source for log4j and have a look at the Logger and Category classes to see for yourself.

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

回答by oksayt

To add to the reasons already mentioned, your boss's suggestion is bad because:

除了已经提到的原因之外,你老板的建议很糟糕,因为:

  • It forces you to repeatedly type something that has nothing to with logging, every time you want to log something: this.getClass()
  • Creates a non-uniform interface between static and non-static contexts (because there is no thisin a static context)
  • The additional unnecessary parameters creates room for error, makes it possible for statements in the same class to go to different loggers (think careless copy pasting)
  • While it saves 74 characters of logger declaration, it adds 27 extra characters to each logging call. This means if a class uses the logger more than 2 times, you're increasing boilerplate code in terms of character count.
  • 它迫使你重复输入一些与日志无关的内容,每次你想记录一些东西时: this.getClass()
  • 在静态和非静态上下文之间创建一个非统一的接口(因为this在静态上下文中没有)
  • 额外的不必要参数为错误创造了空间,使同一类中的语句有可能进入不同的记录器(想想不小心的复制粘贴)
  • 虽然它节省了 74 个记录器声明的字符,但它为每个记录调用添加了 27 个额外的字符。这意味着如果一个类使用记录器超过 2 次,那么就字符数而言,您正在增加样板代码。

回答by SPee

When using something like: MyLoggerWrapper.debug(this.getClass(), "blah")You will get wrong classnames when using AOP frameworks or code-injection tools. The classnames are not like the origin, but a generated classname. And another drawback of using the wrapper: For every log statement, you must include additional code "MyClass.class".

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

The 'caching'of the loggers depends on the used frameworks. But even when it does, it must still look up the desired logger for everylog statement you make. So having 3 statements in a method, it must look it up 3 times. Using it as a staticvariable it must only look it up once!

记录器的“缓存”取决于使用的框架。但即使这样做,它仍然必须为您所做的每个日志语句查找所需的记录器。所以在一个方法中有 3 个语句,它必须查找它 3 次。将它用作static变量,它必须只查找一次!

And said before: you lose the ability to use if( log.isXXXEnabled() ){}for a set of statements.

并且之前说过:你失去了使用if( log.isXXXEnabled() ){}for语句集的能力。

What has your boss against the "community default accepted and recommended way"? Introducing the wrapper is not adding more efficiency. Instead you must use the classname for every log statement. After a while you want to "improve" that, so you add another variable, or another wrapper making it more difficult for yourself.

你的老板反对“社区默认接受和推荐的方式”是什么?引入包装器并没有提高效率。相反,您必须为每个日志语句使用类名。一段时间后,您想要“改进”它,因此您添加了另一个变量或另一个包装器,使您自己变得更加困难。

回答by Steve K

Here's one possibility for making it easy to do logging in Java 8 - define an interface to do it for you. For instance:

这是使在 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());
    }
}

Then all you ever have to do is make sure your classes declare implement Logged, and from any of them, you can do things like:

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

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

The log() function takes care of concatenating your strings, but only after it tests for enabled. It logs to debug by default, and if you want to log to debug, you can omit the LogLevel argument. This is a very simple example. You could do any number of things to improve upon this, such as implementing the individual methods, ie error(), trace(), warn(), etc. You could also simply implement "logger" as a function that returns a logger:

log() 函数负责连接你的字符串,但只有在它测试启用之后。它默认记录调试,如果你想记录调试,你可以省略 LogLevel 参数。这是一个非常简单的例子。您可以做很多事情来改进这一点,例如实现单个方法,即 error()、trace()、warn() 等。您也可以简单地将“logger”实现为一个返回记录器的函数:

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

And then it becomes pretty trivial to use your logger:

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

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

You could even make it fully functional by generating delegate methods for all the Logger methods, so that every implementing class is an instance of Logger.

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

Of course, as mentioned before, this means that every time you log, you'll have to go through the Logger lookup process within your LoggerFactory - unless you override the logger() method... in which case you might as well do it the "recommended" way.

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

回答by Jay

I may have missed it in one of the earlier comments, but i didn't see a mention that the logger is static, the call to LoggerFactory is made ONCE (per instantiation of the class) - so the initial concern about multiple calls to create the logger is just wrong.

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

The other comments regarding all the issues with adding wrapping classes are very important as well.

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

回答by Mariano LEANCE

As stated hereby the SLF4J team you can use MethodLookup() introduced in JDK 1.7.

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

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

This way you can refer to the class without the need of using the keyword "this".

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