我已阅读大量帖子和文档(在此网站和其他地方),指出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我们仍在决定)将“缓存”记录器,而且无论如何服务器的运行速度都远低于它们的容量,所以这不是问题。
有任何帮助指出这种方法的潜在问题吗?
答案 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 SLF4J或log4j常见问题解答。
我会建议反对老板建议的“包装”。像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
)答案 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)
为什么你的老板有两个原因?方法没有实现他的想法。
较小的原因是添加静态记录器的开销可以忽略不计。毕竟,记录器设置是这个相当冗长的序列的一部分:
File
。另外,你的老板什么都不保存
对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(每个实例化())所以最初关于创建记录器的多次调用的关注是错误的。
关于添加包装类的所有问题的其他评论也非常重要。