This is an automated email from the ASF dual-hosted git repository.
abhishek pushed a commit to branch 0.23.0
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/0.23.0 by this push:
new c3612ef44d [Backport]Enforce console logging for peon process (#12527)
c3612ef44d is described below
commit c3612ef44dca066c7e790cc0e599f11fe45be911
Author: Frank Chen <[email protected]>
AuthorDate: Tue May 17 17:44:18 2022 +0800
[Backport]Enforce console logging for peon process (#12527)
Backport #12067 to 0.23
---
docs/configuration/logging.md | 16 +-
...soleLoggingEnforcementConfigurationFactory.java | 152 ++++++++++++
.../druid/indexing/overlord/ForkingTaskRunner.java | 2 +
.../tasklogs/ConsoleLoggingEnforcementTest.java | 262 +++++++++++++++++++++
website/.spelling | 1 +
5 files changed, 431 insertions(+), 2 deletions(-)
diff --git a/docs/configuration/logging.md b/docs/configuration/logging.md
index e0fc63a9ad..bcb70225f6 100644
--- a/docs/configuration/logging.md
+++ b/docs/configuration/logging.md
@@ -23,9 +23,12 @@ title: "Logging"
-->
-Apache Druid processes will emit logs that are useful for debugging to the
console. Druid processes also emit periodic metrics about their state. For more
about metrics, see [Configuration](../configuration/index.md#enabling-metrics).
Metric logs are printed to the console by default, and can be disabled with
`-Ddruid.emitter.logging.logLevel=debug`.
+Apache Druid processes will emit logs that are useful for debugging to log
files.
+These processes also emit periodic
[metrics](../configuration/index.md#enabling-metrics) about their state.
+Metric info logs can be disabled with `-Ddruid.emitter.logging.logLevel=debug`.
-Druid uses [log4j2](http://logging.apache.org/log4j/2.x/) for logging. The
default configuration file log4j2.xml ships with Druid under
conf/druid/{config}/_common/log4j2.xml .
+Druid uses [log4j2](http://logging.apache.org/log4j/2.x/) for logging.
+The default configuration file log4j2.xml ships with Druid under
conf/druid/{config}/_common/log4j2.xml .
By default, Druid uses `RollingRandomAccessFile` for rollover daily, and keeps
log files up to 7 days.
If that's not suitable in your case, you could modify the log4j2.xml to meet
your need.
@@ -67,6 +70,15 @@ An example log4j2.xml file is shown below:
</Configuration>
```
+> NOTE:
+> Although the log4j configuration file is shared with Druid's peon processes,
+> the appenders in this file DO NOT take effect for peon processes for they
always output logs to console.
+> And middle managers are responsible to redirect the console output to task
log files.
+>
+> But the logging levels settings take effect for these peon processes
+> which means you can still configure loggers at different logging level for
peon processes in this file.
+>
+
## How to change log directory
By default, Druid outputs the logs to a directory `log` under the directory
where Druid is launched from.
For example, if Druid is started from its `bin` directory, there will be a
subdirectory `log` generated under `bin` directory to hold the log files.
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/common/tasklogs/ConsoleLoggingEnforcementConfigurationFactory.java
b/indexing-service/src/main/java/org/apache/druid/indexing/common/tasklogs/ConsoleLoggingEnforcementConfigurationFactory.java
new file mode 100644
index 0000000000..1874963f3c
--- /dev/null
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/common/tasklogs/ConsoleLoggingEnforcementConfigurationFactory.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.indexing.common.tasklogs;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.ConsoleAppender;
+import org.apache.logging.log4j.core.config.AppenderRef;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.logging.log4j.core.config.xml.XmlConfiguration;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+
+import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * This class enforces console logging based on a user defined configuration.
+ * <p>
+ * For all loggers, this class ensure that only {@link ConsoleAppender} is
applied for each of them.
+ * <p>
+ * The reason why this configuration is still based on a user's configuration
is that
+ * user can still configure different logging levels for different loggers in
that file.
+ */
+public class ConsoleLoggingEnforcementConfigurationFactory extends
ConfigurationFactory
+{
+ /**
+ * Valid file extensions for XML files.
+ */
+ public static final String[] SUFFIXES = new String[]{".xml", "*"};
+
+ @Override
+ public String[] getSupportedTypes()
+ {
+ return SUFFIXES;
+ }
+
+ @Override
+ public Configuration getConfiguration(LoggerContext loggerContext,
ConfigurationSource source)
+ {
+ return new OverrideConfiguration(loggerContext, source);
+ }
+
+ /**
+ * override the original configuration source to ensure only console
appender is applied
+ */
+ static class OverrideConfiguration extends XmlConfiguration
+ {
+ public OverrideConfiguration(final LoggerContext loggerContext, final
ConfigurationSource configSource)
+ {
+ super(loggerContext, configSource);
+ }
+
+ @Override
+ protected void doConfigure()
+ {
+ super.doConfigure();
+
+ Appender consoleAppender = findConsoleAppender();
+ if (consoleAppender == null) {
+ // create a ConsoleAppender with default pattern if no console
appender is configured in the configuration file
+ consoleAppender = ConsoleAppender.newBuilder()
+
.setName("_Injected_Console_Appender_")
+ .setLayout(PatternLayout.newBuilder()
+
.withPattern("%d{ISO8601} %p [%t] %c - %m%n")
+ .build())
+ .build();
+ }
+
+ List<LoggerConfig> loggerConfigList = new ArrayList<>();
+ loggerConfigList.add(this.getRootLogger());
+ loggerConfigList.addAll(this.getLoggers().values());
+
+ //
+ // For all logger configuration, check if its appender is
ConsoleAppender.
+ // If not, replace it's appender to ConsoleAppender.
+ //
+ for (LoggerConfig logger : loggerConfigList) {
+ applyConsoleAppender(logger, consoleAppender);
+ }
+ }
+
+ @Nullable
+ private Appender findConsoleAppender()
+ {
+ for (Map.Entry<String, Appender> entry : this.getAppenders().entrySet())
{
+ Appender appender = entry.getValue();
+ if (appender instanceof ConsoleAppender) {
+ return appender;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * remove all appenders from a logger and append a console appender to it
+ */
+ private void applyConsoleAppender(LoggerConfig logger, Appender
consoleAppender)
+ {
+ if (logger.getAppenderRefs().size() == 1
+ &&
logger.getAppenderRefs().get(0).getRef().equals(consoleAppender.getName())) {
+ // this logger has only one appender and its the console appender
+ return;
+ }
+
+ Level level = Level.INFO;
+ Filter filter = null;
+
+ if (!logger.getAppenderRefs().isEmpty()) {
+ AppenderRef appenderRef = logger.getAppenderRefs().get(0);
+
+ // clear all appenders first
+ List<String> appendRefs = logger.getAppenderRefs()
+ .stream()
+ .map(AppenderRef::getRef)
+ .collect(Collectors.toList());
+ appendRefs.forEach(logger::removeAppender);
+
+ // use the first appender's definition
+ level = appenderRef.getLevel();
+ filter = appenderRef.getFilter();
+ }
+
+ // add ConsoleAppender to this logger
+ logger.addAppender(consoleAppender, level, filter);
+ }
+ }
+}
diff --git
a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/ForkingTaskRunner.java
b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/ForkingTaskRunner.java
index c35e10cb25..a7024dbaf7 100644
---
a/indexing-service/src/main/java/org/apache/druid/indexing/overlord/ForkingTaskRunner.java
+++
b/indexing-service/src/main/java/org/apache/druid/indexing/overlord/ForkingTaskRunner.java
@@ -47,6 +47,7 @@ import org.apache.druid.indexer.TaskLocation;
import org.apache.druid.indexer.TaskStatus;
import org.apache.druid.indexing.common.config.TaskConfig;
import org.apache.druid.indexing.common.task.Task;
+import
org.apache.druid.indexing.common.tasklogs.ConsoleLoggingEnforcementConfigurationFactory;
import org.apache.druid.indexing.common.tasklogs.LogUtils;
import org.apache.druid.indexing.overlord.autoscaling.ScalingStats;
import org.apache.druid.indexing.overlord.config.ForkingTaskRunnerConfig;
@@ -332,6 +333,7 @@ public class ForkingTaskRunner
command.add(
StringUtils.format("-Ddruid.task.executor.enableTlsPort=%s",
node.isEnableTlsPort())
);
+
command.add(StringUtils.format("-Dlog4j2.configurationFactory=%s",
ConsoleLoggingEnforcementConfigurationFactory.class.getName()));
// These are not enabled per default to allow the user
to either set or not set them
// Users are highly suggested to be set in
druid.indexer.runner.javaOpts
diff --git
a/indexing-service/src/test/java/org/apache/druid/indexing/common/tasklogs/ConsoleLoggingEnforcementTest.java
b/indexing-service/src/test/java/org/apache/druid/indexing/common/tasklogs/ConsoleLoggingEnforcementTest.java
new file mode 100644
index 0000000000..ea5622074b
--- /dev/null
+++
b/indexing-service/src/test/java/org/apache/druid/indexing/common/tasklogs/ConsoleLoggingEnforcementTest.java
@@ -0,0 +1,262 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.indexing.common.tasklogs;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.ConsoleAppender;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+public class ConsoleLoggingEnforcementTest
+{
+ private static final String ROOT = "ROOT";
+
+ @Test
+ public void testConsoleConfiguration() throws IOException
+ {
+ // the loggers in configuration already uses Console appender
+ String log4jConfiguration = "<Configuration status=\"WARN\">\n"
+ + " <Appenders>\n"
+ + " <Console name=\"Console\"
target=\"SYSTEM_OUT\">\n"
+ + " <PatternLayout pattern=\"%m\"/>\n"
+ + " </Console>\n"
+ + " </Appenders>\n"
+ + " <Loggers>\n"
+ + " <Root level=\"info\">\n"
+ + " <AppenderRef ref=\"Console\"/>\n"
+ + " </Root>\n"
+ + " <Logger level=\"debug\"
name=\"org.apache.druid\" additivity=\"false\">\n"
+ + " <AppenderRef ref=\"Console\"/>\n"
+ + " </Logger>\n"
+ + " </Loggers>\n"
+ + "</Configuration>";
+
+ LoggerContext context = enforceConsoleLogger(log4jConfiguration);
+
+ // this logger is not defined in configuration, it derivates ROOT logger
configuration
+ assertHasOnlyOneConsoleAppender(getLogger(context, "name_not_in_config"),
Level.INFO);
+ assertHasOnlyOneConsoleAppender(getLogger(context, "org.apache.druid"),
Level.DEBUG);
+ assertHasOnlyOneConsoleAppender(getLogger(context, ROOT), Level.INFO);
+
+ PatternLayout layout = (PatternLayout) getLogger(context,
"anything").getAppenders()
+
.values()
+
.stream()
+
.findFirst()
+ .get()
+
.getLayout();
+ Assert.assertEquals("%m", layout.getConversionPattern());
+ }
+
+ @Test
+ public void testNoConsoleAppender() throws IOException
+ {
+ // this logger configuration has no console logger appender, a default one
will be created
+ String log4jConfiguration = "<Configuration status=\"WARN\">\n"
+ + " <Appenders>\n"
+ + " <RollingRandomAccessFile
name=\"FileAppender\" fileName=\"a.log\">\n"
+ + " <PatternLayout pattern=\"%d{ISO8601}
%p [%t] %c - %m%n\"/>\n"
+ + " </RollingRandomAccessFile>\n"
+ + " </Appenders>\n"
+ + " <Loggers>\n"
+ + " <Root level=\"info\">\n"
+ + " <AppenderRef ref=\"FileAppender\"/>\n"
+ + " </Root>\n"
+ + " </Loggers>\n"
+ + "</Configuration>";
+
+ LoggerContext context = enforceConsoleLogger(log4jConfiguration);
+
+ // this logger is not defined in configuration, it derivates ROOT logger
configuration
+ assertHasOnlyOneConsoleAppender(getLogger(context, "name_not_in_config"),
Level.INFO);
+ assertHasOnlyOneConsoleAppender(getLogger(context, ROOT), Level.INFO);
+
+ String defaultPattern = "%d{ISO8601} %p [%t] %c - %m%n";
+ PatternLayout layout = (PatternLayout) getLogger(context,
"anything").getAppenders()
+
.values()
+
.stream()
+
.findFirst()
+ .get()
+
.getLayout();
+ Assert.assertEquals(defaultPattern, layout.getConversionPattern());
+ }
+
+ @Test
+ public void testHasConsoleAppenderButNotUsed() throws IOException
+ {
+ // this logger has a console appender, but is not referenced by any logger
+ String log4jConfiguration = "<Configuration status=\"WARN\">\n"
+ + " <Appenders>\n"
+ + " <Console name=\"Console\"
target=\"SYSTEM_OUT\">\n"
+ + " <PatternLayout pattern=\"%m\"/>\n"
+ + " </Console>\n"
+ + " <RollingRandomAccessFile
name=\"FileAppender\" fileName=\"a.log\">\n"
+ + " <PatternLayout pattern=\"%d{ISO8601}
%p [%t] %c - %m%n\"/>\n"
+ + " </RollingRandomAccessFile>\n"
+ + " </Appenders>\n"
+ + " <Loggers>\n"
+ + " <Root level=\"info\">\n"
+ + " <AppenderRef ref=\"FileAppender\"/>\n"
+ + " </Root>\n"
+ + " </Loggers>\n"
+ + "</Configuration>";
+
+ LoggerContext context = enforceConsoleLogger(log4jConfiguration);
+
+ // this logger is not defined in configuration, it derivates ROOT logger
configuration
+ assertHasOnlyOneConsoleAppender(getLogger(context, "name_not_in_config"),
Level.INFO);
+
+ assertHasOnlyOneConsoleAppender(getLogger(context, ROOT), Level.INFO);
+
+ // the ConsoleAppender should be exactly the same as it's in the
configuration
+ PatternLayout layout = (PatternLayout) getLogger(context,
"anything").getAppenders()
+
.values()
+
.stream()
+
.findFirst()
+ .get()
+
.getLayout();
+ Assert.assertEquals("%m", layout.getConversionPattern());
+ }
+
+ @Test
+ public void testMultipleAppender() throws IOException
+ {
+ // this logger configuration contains multiple appenders and appender
refers
+ String log4jConfiguration = "<Configuration status=\"WARN\">\n"
+ + " <Appenders>\n"
+ + " <Console name=\"Console\"
target=\"SYSTEM_OUT\">\n"
+ + " <PatternLayout pattern=\"%m\"/>\n"
+ + " </Console>\n"
+ + " <RollingRandomAccessFile
name=\"FileAppender\" fileName=\"a.log\">\n"
+ + " <PatternLayout pattern=\"%d{ISO8601}
%p [%t] %c - %m%n\"/>\n"
+ + " </RollingRandomAccessFile>\n"
+ + " </Appenders>\n"
+ + " <Loggers>\n"
+ + " <Root level=\"info\">\n"
+ + " <AppenderRef ref=\"FileAppender\"/>\n"
+ + " <AppenderRef ref=\"Console\"/>\n"
+ + " </Root>\n"
+ + " <Logger level=\"debug\"
name=\"org.apache.druid\" additivity=\"false\">\n"
+ + " <AppenderRef ref=\"FileAppender\"/>\n"
+ + " <AppenderRef ref=\"Console\"/>\n"
+ + " </Logger>\n"
+ + " </Loggers>\n"
+ + "</Configuration>";
+
+ LoggerContext context = enforceConsoleLogger(log4jConfiguration);
+
+ // this logger is not defined in configuration, it derivates ROOT logger
configuration
+ assertHasOnlyOneConsoleAppender(getLogger(context, "name_not_in_config"),
Level.INFO);
+
+ assertHasOnlyOneConsoleAppender(getLogger(context, "org.apache.druid"),
Level.DEBUG);
+ assertHasOnlyOneConsoleAppender(getLogger(context, ROOT), Level.INFO);
+
+ // the ConsoleAppender should be exactly the same as it's in the
configuration
+ PatternLayout layout = (PatternLayout) getLogger(context,
"anything").getAppenders()
+
.values()
+
.stream()
+
.findFirst()
+ .get()
+
.getLayout();
+ Assert.assertEquals("%m", layout.getConversionPattern());
+ }
+
+ @Test
+ public void testEmptyAppender() throws IOException
+ {
+ // the ROOT logger has no appender in this configuration
+ String log4jConfiguration = "<Configuration status=\"WARN\">\n"
+ + " <Appenders>\n"
+ + " <Console name=\"Console\"
target=\"SYSTEM_OUT\">\n"
+ + " <PatternLayout pattern=\"%m\"/>\n"
+ + " </Console>\n"
+ + " <RollingRandomAccessFile
name=\"FileAppender\" fileName=\"a.log\">\n"
+ + " <PatternLayout pattern=\"%d{ISO8601}
%p [%t] %c - %m%n\"/>\n"
+ + " </RollingRandomAccessFile>\n"
+ + " </Appenders>\n"
+ + " <Loggers>\n"
+ + " <Root level=\"info\">\n"
+ + " </Root>\n"
+ + " <Logger level=\"debug\"
name=\"org.apache.druid\" additivity=\"false\">\n"
+ + " <AppenderRef ref=\"FileAppender\"/>\n"
+ + " <AppenderRef ref=\"Console\"/>\n"
+ + " </Logger>\n"
+ + " </Loggers>\n"
+ + "</Configuration>";
+
+ LoggerContext context = enforceConsoleLogger(log4jConfiguration);
+
+ // this logger is not defined in configuration, it derivates ROOT logger
configuration
+ assertHasOnlyOneConsoleAppender(getLogger(context, "name_not_in_config"),
Level.INFO);
+
+ assertHasOnlyOneConsoleAppender(getLogger(context, "org.apache.druid"),
Level.DEBUG);
+ assertHasOnlyOneConsoleAppender(getLogger(context, ROOT), Level.INFO);
+
+ // the ConsoleAppender should be exactly the same as it's in the
configuration
+ PatternLayout layout = (PatternLayout) getLogger(context,
"anything").getAppenders()
+
.values()
+
.stream()
+
.findFirst()
+ .get()
+
.getLayout();
+ Assert.assertEquals("%m", layout.getConversionPattern());
+ }
+
+ private LoggerContext enforceConsoleLogger(String configuration) throws
IOException
+ {
+ LoggerContext context = new LoggerContext("test");
+ ConfigurationSource source = new ConfigurationSource(new
ByteArrayInputStream(configuration.getBytes(StandardCharsets.UTF_8)));
+
+ // enforce the console logging for current configuration
+ context.reconfigure(new
ConsoleLoggingEnforcementConfigurationFactory().getConfiguration(context,
source));
+ return context;
+ }
+
+ private void assertHasOnlyOneConsoleAppender(Logger logger, Level level)
+ {
+ // there's only one appender
+ Assert.assertEquals(1, logger.getAppenders().size());
+
+ // and this appender must be ConsoleAppender
+ Assert.assertEquals(ConsoleAppender.class, logger.getAppenders()
+ .values()
+ .stream()
+ .findFirst()
+ .get()
+ .getClass());
+ if (level != null) {
+ Assert.assertEquals(level, logger.getLevel());
+ }
+ }
+
+ private Logger getLogger(LoggerContext context, String name)
+ {
+ final String key = ROOT.equals(name) ? LogManager.ROOT_LOGGER_NAME : name;
+ return context.getLogger(key);
+ }
+}
diff --git a/website/.spelling b/website/.spelling
index e83d1b8598..03d25cfb02 100644
--- a/website/.spelling
+++ b/website/.spelling
@@ -563,6 +563,7 @@ IEC
100x
- ../docs/configuration/logging.md
_common
+appenders
- ../docs/dependencies/deep-storage.md
druid-hdfs-storage
druid-s3-extensions
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]