自动确定调用者的类名的 Java 记录器
-
09-06-2019 - |
题
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;
}
此方法将返回一个记录器,该记录器知道它正在记录的类。有什么反对的想法吗?
许多年后: https://github.com/yanchenko/droidparts/blob/master/droidparts/src/org/droidparts/util/L.java
解决方案
我想这会给每门课增加很多开销。每个班级都必须“查找”。您创建新的 Throwable 对象来做到这一点......这些可扔物品不是免费的。
其他提示
创建堆栈跟踪是一个相对较慢的操作。你的调用者已经知道它所在的类和方法,所以这些努力都是浪费的。您的解决方案的这方面效率很低。
即使您使用静态类信息,您也不应该为每条消息再次获取 Logger。 来自作者 Log4j、Ceki Gülcü 的:
包装类中最常见的错误是在每个日志请求上调用 Logger.getLogger 方法。这肯定会对应用程序的性能造成严重破坏。真的吗!!!
这是在类初始化期间获取 Logger 的传统、高效的习惯用法:
private static final Logger log = Logger.getLogger(MyClass.class);
请注意,这为层次结构中的每种类型提供了一个单独的记录器。如果你想出一个方法来调用 getClass()
在实例上,您将看到由基本类型记录的消息显示在子类型的记录器下。也许这在某些情况下是可取的,但我发现它令人困惑(无论如何我倾向于组合而不是继承)。
显然,通过使用动态类型 getClass()
将要求您为每个实例至少获取一次记录器,而不是像使用静态类型信息的推荐习惯用法那样为每个类获取一次。
这 方法句柄 类(从 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 );
}
}
运行时会产生:
Main
对于记录器,您可以使用:
private static Logger LOGGER =
Logger.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
实际上,我们在 LogUtils 类中有一些非常相似的东西。是的,这有点令人讨厌,但就我而言,其优点是值得的。我们想确保我们不会因为重复调用它而产生任何开销,所以我们的(有点粗俗)确保它只能从静态初始化上下文中调用,a la:
private static final Logger LOG = LogUtils.loggerForThisClass();
如果从普通方法或实例初始值设定项(即,如果上面省略了“静态”)以降低性能开销的风险。方法是:
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());
}
如果有人问这比
= Logger.getLogger(MyClass.class);
可能从来没有遇到过从其他地方复制并粘贴该行并忘记更改类名的人,让您处理一个将其所有内容发送到另一个记录器的类。
假设您保留对记录器的静态引用,这是一个独立的静态单例:
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();
}
}
使用起来干净整洁:
Logger logger = LoggerUtils.getLogger();
对于使用它的每个类,您都必须查找 Logger,因此您不妨在这些类中使用静态 Logger。
private static final Logger logger = Logger.getLogger(MyClass.class.getName());
然后,当您需要处理日志消息时,只需引用该记录器即可。您的方法所做的事情与静态 Log4J Logger 已经做的事情相同,那么为什么要重新发明轮子呢?
那么最好的事情就是两者的混合。
public class LoggerUtil {
public static Level level=Level.ALL;
public static java.util.logging.Logger getLogger() {
final Throwable t = new Throwable();
final StackTraceElement methodCaller = t.getStackTrace()[1];
final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(methodCaller.getClassName());
logger.setLevel(level);
return logger;
}
}
然后在每个班级:
private static final Logger LOG = LoggerUtil.getLogger();
在代码中:
LOG.fine("debug that !...");
您将获得静态记录器,您可以将其复制并粘贴到每个类中,并且无需任何开销......
阿拉
通过阅读该站点上的所有其他反馈,我创建了以下与 Log4j 一起使用的内容:
package com.edsdev.testapp.util;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.log4j.Level;
import org.apache.log4j.Priority;
public class Logger extends SecurityManager {
private static ConcurrentHashMap<String, org.apache.log4j.Logger> loggerMap = new ConcurrentHashMap<String, org.apache.log4j.Logger>();
public static org.apache.log4j.Logger getLog() {
String className = new Logger().getClassName();
if (!loggerMap.containsKey(className)) {
loggerMap.put(className, org.apache.log4j.Logger.getLogger(className));
}
return loggerMap.get(className);
}
public String getClassName() {
return getClassContext()[3].getName();
}
public static void trace(Object message) {
getLog().trace(message);
}
public static void trace(Object message, Throwable t) {
getLog().trace(message, t);
}
public static boolean isTraceEnabled() {
return getLog().isTraceEnabled();
}
public static void debug(Object message) {
getLog().debug(message);
}
public static void debug(Object message, Throwable t) {
getLog().debug(message, t);
}
public static void error(Object message) {
getLog().error(message);
}
public static void error(Object message, Throwable t) {
getLog().error(message, t);
}
public static void fatal(Object message) {
getLog().fatal(message);
}
public static void fatal(Object message, Throwable t) {
getLog().fatal(message, t);
}
public static void info(Object message) {
getLog().info(message);
}
public static void info(Object message, Throwable t) {
getLog().info(message, t);
}
public static boolean isDebugEnabled() {
return getLog().isDebugEnabled();
}
public static boolean isEnabledFor(Priority level) {
return getLog().isEnabledFor(level);
}
public static boolean isInfoEnabled() {
return getLog().isInfoEnabled();
}
public static void setLevel(Level level) {
getLog().setLevel(level);
}
public static void warn(Object message) {
getLog().warn(message);
}
public static void warn(Object message, Throwable t) {
getLog().warn(message, t);
}
}
现在在你的代码中你需要的是
Logger.debug("This is a test");
或者
Logger.error("Look what happened Ma!", e);
如果您需要更多地了解 log4j 方法,只需从上面列出的 Logger 类中委托它们即可。
您当然可以仅使用 Log4J 和适当的模式布局:
例如,对于类名“org.apache.xyz.SomeClass”,模式 %C{1} 将输出“SomeClass”。
http://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html
我更喜欢为每个类创建一个(静态)记录器(具有显式的类名)。我按原样使用记录器。
您不需要创建新的 Throwable 对象。你可以打电话Thread.currentThread().getStackTrace()[1]
我的大部分课程开始时都会有以下几行。
private static final Logger log =
LoggerFactory.getLogger(new Throwable().getStackTrace()[0].getClassName());
是的,第一次创建该类的对象时会产生一些开销,但我主要在 Web 应用程序中工作,因此在 20 秒的启动中添加微秒并不是真正的问题。
Google Flogger 日志 API 支持此功能,例如
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
为什么不?
public static Logger getLogger(Object o) {
final Logger logger = Logger.getLogger(o.getClass());
logger.setLevel(ResourceManager.LOGLEVEL);
return logger;
}
然后当你需要一个类的记录器时:
getLogger(this).debug("Some log message")
这种机制在运行时付出了很多额外的努力。
如果您使用 Eclipse 作为 IDE,请考虑使用 日志4e. 。这个方便的插件将使用您最喜欢的日志框架为您生成记录器声明。编码时间要多花一点力气,但是 很多 运行时工作量减少。
除非你 真的 需要你的记录器是静态的,你可以使用
final Logger logger = LoggerFactory.getLogger(getClass());
请参阅我的静态 getLogger() 实现(在 JDK 7 上使用相同的“sun.*”魔法作为默认的 java Logger doit)
注意静态日志记录方法(使用静态导入)没有丑陋的日志属性......
导入静态 my.pakg.Logger.*;
它们的速度相当于原生 Java 实现(用 100 万条日志跟踪进行检查)
package my.pkg;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.IllegalFormatException;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import sun.misc.JavaLangAccess;
import sun.misc.SharedSecrets;
public class Logger {
static final int CLASS_NAME = 0;
static final int METHOD_NAME = 1;
// Private method to infer the caller's class and method names
protected static String[] getClassName() {
JavaLangAccess access = SharedSecrets.getJavaLangAccess();
Throwable throwable = new Throwable();
int depth = access.getStackTraceDepth(throwable);
boolean lookingForLogger = true;
for (int i = 0; i < depth; i++) {
// Calling getStackTraceElement directly prevents the VM
// from paying the cost of building the entire stack frame.
StackTraceElement frame = access.getStackTraceElement(throwable, i);
String cname = frame.getClassName();
boolean isLoggerImpl = isLoggerImplFrame(cname);
if (lookingForLogger) {
// Skip all frames until we have found the first logger frame.
if (isLoggerImpl) {
lookingForLogger = false;
}
} else {
if (!isLoggerImpl) {
// skip reflection call
if (!cname.startsWith("java.lang.reflect.") && !cname.startsWith("sun.reflect.")) {
// We've found the relevant frame.
return new String[] {cname, frame.getMethodName()};
}
}
}
}
return new String[] {};
// We haven't found a suitable frame, so just punt. This is
// OK as we are only committed to making a "best effort" here.
}
protected static String[] getClassNameJDK5() {
// Get the stack trace.
StackTraceElement stack[] = (new Throwable()).getStackTrace();
// First, search back to a method in the Logger class.
int ix = 0;
while (ix < stack.length) {
StackTraceElement frame = stack[ix];
String cname = frame.getClassName();
if (isLoggerImplFrame(cname)) {
break;
}
ix++;
}
// Now search for the first frame before the "Logger" class.
while (ix < stack.length) {
StackTraceElement frame = stack[ix];
String cname = frame.getClassName();
if (isLoggerImplFrame(cname)) {
// We've found the relevant frame.
return new String[] {cname, frame.getMethodName()};
}
ix++;
}
return new String[] {};
// We haven't found a suitable frame, so just punt. This is
// OK as we are only committed to making a "best effort" here.
}
private static boolean isLoggerImplFrame(String cname) {
// the log record could be created for a platform logger
return (
cname.equals("my.package.Logger") ||
cname.equals("java.util.logging.Logger") ||
cname.startsWith("java.util.logging.LoggingProxyImpl") ||
cname.startsWith("sun.util.logging."));
}
protected static java.util.logging.Logger getLogger(String name) {
return java.util.logging.Logger.getLogger(name);
}
protected static boolean log(Level level, String msg, Object... args) {
return log(level, null, msg, args);
}
protected static boolean log(Level level, Throwable thrown, String msg, Object... args) {
String[] values = getClassName();
java.util.logging.Logger log = getLogger(values[CLASS_NAME]);
if (level != null && log.isLoggable(level)) {
if (msg != null) {
log.log(getRecord(level, thrown, values[CLASS_NAME], values[METHOD_NAME], msg, args));
}
return true;
}
return false;
}
protected static LogRecord getRecord(Level level, Throwable thrown, String className, String methodName, String msg, Object... args) {
LogRecord record = new LogRecord(level, format(msg, args));
record.setSourceClassName(className);
record.setSourceMethodName(methodName);
if (thrown != null) {
record.setThrown(thrown);
}
return record;
}
private static String format(String msg, Object... args) {
if (msg == null || args == null || args.length == 0) {
return msg;
} else if (msg.indexOf('%') >= 0) {
try {
return String.format(msg, args);
} catch (IllegalFormatException esc) {
// none
}
} else if (msg.indexOf('{') >= 0) {
try {
return MessageFormat.format(msg, args);
} catch (IllegalArgumentException exc) {
// none
}
}
if (args.length == 1) {
Object param = args[0];
if (param != null && param.getClass().isArray()) {
return msg + Arrays.toString((Object[]) param);
} else if (param instanceof Throwable){
return msg;
} else {
return msg + param;
}
} else {
return msg + Arrays.toString(args);
}
}
public static void severe(String msg, Object... args) {
log(Level.SEVERE, msg, args);
}
public static void warning(String msg, Object... args) {
log(Level.WARNING, msg, args);
}
public static void info(Throwable thrown, String format, Object... args) {
log(Level.INFO, thrown, format, args);
}
public static void warning(Throwable thrown, String format, Object... args) {
log(Level.WARNING, thrown, format, args);
}
public static void warning(Throwable thrown) {
log(Level.WARNING, thrown, thrown.getMessage());
}
public static void severe(Throwable thrown, String format, Object... args) {
log(Level.SEVERE, thrown, format, args);
}
public static void severe(Throwable thrown) {
log(Level.SEVERE, thrown, thrown.getMessage());
}
public static void info(String msg, Object... args) {
log(Level.INFO, msg, args);
}
public static void fine(String msg, Object... args) {
log(Level.FINE, msg, args);
}
public static void finer(String msg, Object... args) {
log(Level.FINER, msg, args);
}
public static void finest(String msg, Object... args) {
log(Level.FINEST, msg, args);
}
public static boolean isLoggableFinest() {
return isLoggable(Level.FINEST);
}
public static boolean isLoggableFiner() {
return isLoggable(Level.FINER);
}
public static boolean isLoggableFine() {
return isLoggable(Level.FINE);
}
public static boolean isLoggableInfo() {
return isLoggable(Level.INFO);
}
public static boolean isLoggableWarning() {
return isLoggable(Level.WARNING);
}
public static boolean isLoggableSevere() {
return isLoggable(Level.SEVERE);
}
private static boolean isLoggable(Level level) {
return log(level, null);
}
}
一个好的替代方法是使用 lombok 日志注释(之一):https://projectlombok.org/features/Log.html
它会生成与当前类对应的日志语句。
从 Java 7 开始,这是一个很好的方法:
private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
记录器可以是 static
那很好。这里使用的是SLF4J API
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
但原则上可以与任何日志框架一起使用。如果记录器需要字符串参数添加 toString()