当我们遇到问题或者需要深入了解 Elasticsearch 的运行机制时,调整日志等级( logging level
)到更详细的级别,比如 DEBUG
、TRACE
,会是一个有效且必须要掌握的方法。
Elasticsearch 提供了如下的接口来支持动态变更 logging level,logger 后面是 package name 或者 class name。
PUT _cluster/settings
{
"persistent": {
"logger": {
"org.elasticsearch.action": "DEBUG"
}
}
}
当然,你也可以去修改配置目录下面的 log4j2.properties,然后重启节点,但这种方法太过笨重,建议你不要用。
如果后续想要调整回默认设置,操作也简单,如下所示:
PUT _cluster/settings
{
"persistent": {
"logger": {
"org.elasticsearch.action": null
}
}
}
上面的示例只是指定了一个 logger,当然也可以在一次请求中设定多个 logger,如下所示:
PUT _cluster/settings
{
"persistent": {
"logger": {
"_root": "INFO",
"org.elasticsearch.action": "DEBUG",
"org.elasticsearch.action.admin.cluster.health": "TRACE"
}
}
}
上面的设定中,调用者的意图可能如下:
- root 的日志等级(默认所有 logger 的日志级别)设定为 INFO,虽然 log4j2.properties 中已经设定过了,保险起见,这里再指定一次。
- 设定 org.elasticsearch.action 这个 package 下所有 logger 的日志级别都为 DEBUG,需要查看下 transport action 的执行日志。
- 设定 org.elasticsearch.action.admin.cluster.health 这个 package 下所有 logger 的日志级别都为 TRACE,需要查看 Cluster Health 执行的更多日志。
但实际去运行时,Elasticsearch 并没有按照预期的结果去执行,没有相关 DEBUG
和 TRACE
级别的日志输出。这里直接给出原因和解决方案。
原因是 elasticsearch 在设定 logging level 时,会优先采用 _root
和 parent logger
的设定,这里和 log4j2.properties 中的设定有所差异。
上面的调用,最终结果是采用 _root 的设定,所有 logger 都是 INFO
,其他的设定都无效了。
解决方案如下,去除 _root 设定和 parent logger 的设定。
PUT _cluster/settings
{
"persistent": {
"logger": {
"_root": null,
"org.elasticsearch.action": null,
"org.elasticsearch.action.admin.cluster.health": "TRACE"
}
}
}
下面就是源码分析了,感兴趣的可以继续看下去~
源码分析
相关实现逻辑在 ClusterSetting.LoggingSettingUpdater
里面,这里简单给下定位的思路,感兴趣的同学可以自己去翻下源码。
- rest 请求的入口是
RestClusterUpdateSettingsAction
,这里会转发请求到 master 节点 - master 处理的入口是
TransportClusterUpdateSettingsAction
,这里会去 update Cluster Setting,关键词为updater.updateSettings
。 - 在 updateSettings的时候会调用所有的 ClusterSettingUpdater,Logging 就是其中之一。
这里 apply setting 的代码如下:
for (String key : value.keySet()) {
assert loggerPredicate.test(key);
String component = key.substring("logger.".length());
if ("level".equals(component)) {
continue;
}
if ("_root".equals(component)) {
final String rootLevel = value.get(key);
if (rootLevel == null) {
Loggers.setLevel(LogManager.getRootLogger(), Loggers.LOG_DEFAULT_LEVEL_SETTING.get(settings));
} else {
Loggers.setLevel(LogManager.getRootLogger(), rootLevel);
}
} else {
Loggers.setLevel(LogManager.getLogger(component), value.get(key));
}
}
浅显易懂,不废话,而且这里的逻辑看起来很正常,那么继续来看下 Loggers.setLevel代码。
public static void setLevel(Logger logger, Level level) {
if (!LogManager.ROOT_LOGGER_NAME.equals(logger.getName())) {
Configurator.setLevel(logger.getName(), level);
} else {
final LoggerContext ctx = LoggerContext.getContext(false);
final Configuration config = ctx.getConfiguration();
final LoggerConfig loggerConfig = config.getLoggerConfig(logger.getName());
loggerConfig.setLevel(level);
ctx.updateLoggers();
}
// we have to descend the hierarchy
final LoggerContext ctx = LoggerContext.getContext(false);
for (final LoggerConfig loggerConfig : ctx.getConfiguration().getLoggers().values()) {
if (LogManager.ROOT_LOGGER_NAME.equals(logger.getName()) || loggerConfig.getName().startsWith(logger.getName() + ".")) {
Configurator.setLevel(loggerConfig.getName(), level);
}
}
}
最后的处理逻辑会在每个 logger 设定完成后,去重新刷一遍现有的 logger,应用 root 或者 parent logger 的设定。
顺着代码的修改记录,找到了当初的修改 PR 如下:
[https://github.com/elastic/el...]()
其中也描述了修改的原因:
Today when setting the logging level via the command-line or an API
call, the expectation is that the logging level should trickle down the
hiearchy to descendant loggers. However, this is not necessarily the
case. For example, if loggers x and x.y are already configured then
setting the logging level on x will not descend to x.y. This is because
the logging config for x.y has already been forked from the logging
config for x. Therefore, we must explicitly descend the hierarchy when
setting the logging level and that is what this commit does.
从这段描述看,当时要解决的问题是 x.y 没有继承 x logging level 的问题,所以加了这段显示继承的逻辑。
虽然这解决了继承的问题,但其行为本身与 log4j2.properties 中 logger 的修改逻辑就不一致了,难免带来困扰。
但考虑到这个配置是一个专家级别的配置,很少用户会使用,自己心里明白正确的使用方法就好了^_^
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。