Java记录器,自动确定调用者的类名

yan*_*nko 35 java logging stack-trace

public static Logger getLogger() {
    final Throwable t = new Throwable();
    final StackTraceElement methodCaller = t.getStackTrace()[1];
    final Logger logger = Logger.getLogger(methodCaller.getClassName());
    logger.setLevel(ResourceManager.LOGLEVEL);
    return logger;
}
Run Code Online (Sandbox Code Playgroud)

此方法将返回一个记录器,该记录器知道它正在记录的类.反对它的任何想法?

许多年后:https://github.com/yanchenko/droidparts/blob/master/droidparts/src/org/droidparts/util/L.java

eri*_*son 23

创建堆栈跟踪是一个相对较慢的操作.您的调用者已经知道它所属的类和方法,因此浪费了精力.解决方案的这一方面效率低下.

即使您使用静态类信息,也不应该为每条消息再次获取Logger.来自 Log4j 的作者,CekiGülcü:

包装类中最常见的错误是在每个日志请求上调用Logger.getLogger方法.这可以保证对您的应用程序的性能造成严重破坏.真!!!

这是在类初始化期间获取Logger的传统,高效的习惯用法:

private static final Logger log = Logger.getLogger(MyClass.class);
Run Code Online (Sandbox Code Playgroud)

请注意,这为层次结构中的每种类型提供了单独的Logger.如果您想出一个getClass()在实例上调用的方法,您将看到由基类型记录的消息显示在子类型的记录器下.也许这在某些情况下是可取的,但我发现它令人困惑(而且我倾向于赞成合成而不是继承).

显然,使用动态类型via getClass()将要求您每个实例至少获取一次记录器,而不是像使用静态类型信息的推荐习惯用法那样每个类获取一次.

  • 我认为getLogger方法至少可以在java 6中使用类作为参数.但是你可以使用自Java 5以来的MyClass.class.getCanonicalName(). (3认同)
  • 标准成语是标准的原因.如果你做了不同的事情,请确保必须了解差异的额外维护是值得的. (2认同)

Nee*_*raj 22

MethodHandles类(如Java 7)包括查找类,从静态上下文,可以找到并返回当前类的名称.请考虑以下示例:

import java.lang.invoke.MethodHandles;

public class Main {
  private static final Class clazz = MethodHandles.lookup().lookupClass();
  private static final String CLASSNAME = clazz.getSimpleName();

  public static void main( String args[] ) {
    System.out.println( CLASSNAME );
  }
}
Run Code Online (Sandbox Code Playgroud)

运行时会产生:

Main
Run Code Online (Sandbox Code Playgroud)

对于记录器,您可以使用:

private static Logger LOGGER = 
  Logger.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
Run Code Online (Sandbox Code Playgroud)

  • 表达式中不需要getSimpleName。私有静态Logger logger = Logger.getLogger(MethodHandles.lookup()。lookupClass()); (4认同)
  • 为什么默认情况下这不包含在记录器 API 中,例如 slf4j,例如:LoggerFactory.getLogger(); (2认同)

Cow*_*wan 18

我们实际上在LogUtils类中有类似的东西.是的,它有点狡猾,但就我而言,优点是值得的.我们希望确保我们没有任何开销,因为它被重复调用,所以我们(有点hackily)确保它只能从静态初始化器上下文调用,la:

private static final Logger LOG = LogUtils.loggerForThisClass();
Run Code Online (Sandbox Code Playgroud)

如果从普通方法或实例初始化程序调用它(即如果"静态"不在上面),它将失败,以降低性能开销的风险.方法是:

public static Logger loggerForThisClass() {
    // We use the third stack element; second is this method, first is .getStackTrace()
    StackTraceElement myCaller = Thread.currentThread().getStackTrace()[2];
    Assert.equal("<clinit>", myCaller.getMethodName());
    return Logger.getLogger(myCaller.getClassName());
}
Run Code Online (Sandbox Code Playgroud)

有人问过这有什么好处

= Logger.getLogger(MyClass.class);
Run Code Online (Sandbox Code Playgroud)

可能从来没有必要处理那些从其他地方复制并粘贴该行并忘记更改类名的人,让您处理将其所有内容发送到另一个记录器的类.

  • Matt:如果他们使用不当(就像你说的那样),断言将失败并抛出RuntimeException.这通常会鼓励解决问题.:)(实际上,当我在那家公司时,AFAIK从未发生过这种情况;但在我任职期间至少十几次复制并粘贴记录器并记录错误记录器名称的东西) (3认同)

Daa*_*aan 16

我想这会为每个课程增加很多开销.每个班级都必须"抬头".你创建新的Throwable对象来做到这一点......这些扔掉的东西不是免费的.

  • "很多开销"?对于每个类(非对象),此代码将使用此方法执行一次.我认为可以忽略不计. (3认同)
  • @Matt:你是对的.但是这种方法通常被声明为静态和最终字段,因为记录器通常是. (2认同)

EGB*_*EGB 8

假设您将静态引用保存到记录器,这里是一个独立的静态单例:

public class LoggerUtils extends SecurityManager
{
    public static Logger getLogger()
    {
        String className = new LoggerUtils().getClassName();
        Logger logger = Logger.getLogger(className);
        return logger;
    }

    private String getClassName()
    {
        return getClassContext()[2].getName();
    }
}
Run Code Online (Sandbox Code Playgroud)

用法很干净:

Logger logger = LoggerUtils.getLogger();
Run Code Online (Sandbox Code Playgroud)

  • 好戏.我建议保留SecurityManager.当您创建它的实例时,它每次都会检查权限.此外,您还必须在doPrivileged()块中实例化LoggerUtils. (2认同)

18R*_*bit 6

对于使用它的每个类,您都必须查找 Logger,因此您不妨在这些类中使用静态 Logger。

private static final Logger logger = Logger.getLogger(MyClass.class.getName());
Run Code Online (Sandbox Code Playgroud)

然后,当您需要处理日志消息时,只需引用该记录器即可。您的方法所做的事情与静态 Log4J Logger 已经做的事情相同,那么为什么要重新发明轮子呢?