在我看来,当集合作为参数传递而不是简单地调用toString()时,Log4j正试图做一些智能。
请注意以下代码,例如
public static void main(String[] args) {
Collection<String> x = TreeMultiset.create(); //From Guava
x.add("John");
x.add("John");
x.add("John");
x.add("Dummy");
System.out.println(x);
LOG.info("People {}", x);
LOG.info("People {}", x.toString());
}
这里LOG基本上是log4j LogManager的记录器。这给出了以下输出。
[Dummy, John x 3]
2018-02-16 09:57:42,804 INFO main [MyClass] People [Dummy, John, John, John]
2018-02-16 09:57:42,806 INFO main [MyClass] People [Dummy, John x 3]
我期待日志行中的最后一行输出,但我现在在我的日志文件中有[Dummy,John,John,John ....]。
仔细检查后,罪魁祸首似乎是org.apache.logging.log4j.message.ParameterFormatter#recursiveDeepToString,其代码为
private static void recursiveDeepToString(final Object o, final StringBuilder str, final Set<String> dejaVu) {
if (appendSpecialTypes(o, str)) {
return;
}
if (isMaybeRecursive(o)) {
appendPotentiallyRecursiveValue(o, str, dejaVu);
} else {
tryObjectToString(o, str);
}
}
appendPotentiallyRecursiveValue只是遍历Collection而不是调用.toString()
我的问题是:这真的有必要吗? (甚至危险吗?)
答案 0 :(得分:1)
很好的发现。这种行为实际上并不是特定于Log4j 2.8,它一直存在于自Log4j2的alpha版本以来的代码库中。
那么,为什么Log4j这样做呢?简短的回答是,这是一项安全措施。想象一下这段代码:
List<Object> list = new ArrayList<>();
list.add(list); // add list to itself
// option 1: what does this print?
System.out.println(list);
// option 2: what does this print/log?
logger.info(“list contents: {}”, list);
第一个示例(内部调用列表中的toString
)将一直挂起,直到它出现堆栈溢出错误。
第二个例子,使用Log4j记录相同的列表将打印出合理的内容。而且,更重要的是,它不会死。
这可能是一个简单的例子,但在实际应用中,自我引用并不总是容易预防。用户喜欢它,如果记录对象,即使他们犯了错误,也不会在生产中杀死他们的应用程序。 : - )