Skip to content

Commit 0519fa2

Browse files
authored
Ensure logging is configured for CLI commands
Any CLI commands that depend on core Elasticsearch might touch classes (directly or indirectly) that depends on logging. If they do this and logging is not configured, Log4j will dump status error messages to the console. As such, we need to ensure that any such CLI command configures logging (with a trivial configuration that dumps log messages to the console). Previously we did this in the base CLI command but with the refactoring of this class out of core Elasticsearch, we no longer configure logging there (since we did not want this class to depend on settings and logging). However, this meant for some CLI commands (like the plugin CLI) we were no longer configuring logging. This commit adds base classes between the low-level command and multi-command classes that ensure that logging is configured. Any CLI command that depends on core Elasticsearch should use this infrastructure to ensure logging is configured. There is one exception to this: Elasticsearch itself because it takes reponsibility into its own hands for configuring logging from Elasticsearch settings and log4j2.properties. We preserve this special status. Relates elastic#27523
1 parent a29dc20 commit 0519fa2

File tree

13 files changed

+172
-53
lines changed

13 files changed

+172
-53
lines changed

core/cli/src/main/java/org/elasticsearch/cli/Command.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ public abstract class Command implements Closeable {
3838
/** A description of the command, used in the help output. */
3939
protected final String description;
4040

41+
private final Runnable beforeMain;
42+
4143
/** The option parser for this command. */
4244
protected final OptionParser parser = new OptionParser();
4345

@@ -46,8 +48,15 @@ public abstract class Command implements Closeable {
4648
private final OptionSpec<Void> verboseOption =
4749
parser.acceptsAll(Arrays.asList("v", "verbose"), "show verbose output").availableUnless(silentOption);
4850

49-
public Command(String description) {
51+
/**
52+
* Construct the command with the specified command description and runnable to execute before main is invoked.
53+
*
54+
* @param description the command description
55+
* @param beforeMain the before-main runnable
56+
*/
57+
public Command(final String description, final Runnable beforeMain) {
5058
this.description = description;
59+
this.beforeMain = beforeMain;
5160
}
5261

5362
private Thread shutdownHookThread;
@@ -75,7 +84,7 @@ public final int main(String[] args, Terminal terminal) throws Exception {
7584
Runtime.getRuntime().addShutdownHook(shutdownHookThread);
7685
}
7786

78-
beforeExecute();
87+
beforeMain.run();
7988

8089
try {
8190
mainWithoutErrorHandling(args, terminal);
@@ -93,12 +102,6 @@ public final int main(String[] args, Terminal terminal) throws Exception {
93102
return ExitCodes.OK;
94103
}
95104

96-
/**
97-
* Setup method to be executed before parsing or execution of the command being run. Any exceptions thrown by the
98-
* method will not be cleanly caught by the parser.
99-
*/
100-
protected void beforeExecute() {}
101-
102105
/**
103106
* Executes the command, but all errors are thrown.
104107
*/

core/cli/src/main/java/org/elasticsearch/cli/MultiCommand.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,14 @@ public class MultiCommand extends Command {
3535

3636
private final NonOptionArgumentSpec<String> arguments = parser.nonOptions("command");
3737

38-
public MultiCommand(String description) {
39-
super(description);
38+
/**
39+
* Construct the multi-command with the specified command description and runnable to execute before main is invoked.
40+
*
41+
* @param description the multi-command description
42+
* @param beforeMain the before-main runnable
43+
*/
44+
public MultiCommand(final String description, final Runnable beforeMain) {
45+
super(description, beforeMain);
4046
parser.posixlyCorrect(true);
4147
}
4248

core/src/main/java/org/elasticsearch/bootstrap/Elasticsearch.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class Elasticsearch extends EnvironmentAwareCommand {
5151

5252
// visible for testing
5353
Elasticsearch() {
54-
super("starts elasticsearch");
54+
super("starts elasticsearch", () -> {}); // we configure logging later so we override the base class from configuring logging
5555
versionOption = parser.acceptsAll(Arrays.asList("V", "version"),
5656
"Prints elasticsearch version information and exits");
5757
daemonizeOption = parser.acceptsAll(Arrays.asList("d", "daemonize"),
@@ -92,15 +92,6 @@ static int main(final String[] args, final Elasticsearch elasticsearch, final Te
9292
return elasticsearch.main(args, terminal);
9393
}
9494

95-
@Override
96-
protected boolean shouldConfigureLoggingWithoutConfig() {
97-
/*
98-
* If we allow logging to be configured without a config before we are ready to read the log4j2.properties file, then we will fail
99-
* to detect uses of logging before it is properly configured.
100-
*/
101-
return false;
102-
}
103-
10495
@Override
10596
protected void execute(Terminal terminal, OptionSet options, Environment env) throws UserException {
10697
if (options.nonOptionArguments().isEmpty() == false) {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.cli;
21+
22+
import org.apache.logging.log4j.Level;
23+
import org.elasticsearch.common.logging.LogConfigurator;
24+
import org.elasticsearch.common.settings.Settings;
25+
26+
/**
27+
* Holder class for method to configure logging without Elasticsearch configuration files for use in CLI tools that will not read such
28+
* files.
29+
*/
30+
final class CommandLoggingConfigurator {
31+
32+
/**
33+
* Configures logging without Elasticsearch configuration files based on the system property "es.logger.level" only. As such, any
34+
* logging will be written to the console.
35+
*/
36+
static void configureLoggingWithoutConfig() {
37+
// initialize default for es.logger.level because we will not read the log4j2.properties
38+
final String loggerLevel = System.getProperty("es.logger.level", Level.INFO.name());
39+
final Settings settings = Settings.builder().put("logger.level", loggerLevel).build();
40+
LogConfigurator.configureWithoutConfig(settings);
41+
}
42+
43+
}

core/src/main/java/org/elasticsearch/cli/EnvironmentAwareCommand.java

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@
2222
import joptsimple.OptionSet;
2323
import joptsimple.OptionSpec;
2424
import joptsimple.util.KeyValuePair;
25-
import org.apache.logging.log4j.Level;
2625
import org.elasticsearch.common.SuppressForbidden;
27-
import org.elasticsearch.common.logging.LogConfigurator;
2826
import org.elasticsearch.common.settings.Settings;
2927
import org.elasticsearch.env.Environment;
3028
import org.elasticsearch.node.InternalSettingsPreparer;
@@ -40,8 +38,25 @@ public abstract class EnvironmentAwareCommand extends Command {
4038

4139
private final OptionSpec<KeyValuePair> settingOption;
4240

43-
public EnvironmentAwareCommand(String description) {
44-
super(description);
41+
/**
42+
* Construct the command with the specified command description. This command will have logging configured without reading Elasticsearch
43+
* configuration files.
44+
*
45+
* @param description the command description
46+
*/
47+
public EnvironmentAwareCommand(final String description) {
48+
this(description, CommandLoggingConfigurator::configureLoggingWithoutConfig);
49+
}
50+
51+
/**
52+
* Construct the command with the specified command description and runnable to execute before main is invoked. Commands constructed
53+
* with this constructor must take ownership of configuring logging.
54+
*
55+
* @param description the command description
56+
* @param beforeMain the before-main runnable
57+
*/
58+
public EnvironmentAwareCommand(final String description, final Runnable beforeMain) {
59+
super(description, beforeMain);
4560
this.settingOption = parser.accepts("E", "Configure a setting").withRequiredArg().ofType(KeyValuePair.class);
4661
}
4762

@@ -104,26 +119,6 @@ private static void putSystemPropertyIfSettingIsMissing(final Map<String, String
104119
}
105120
}
106121

107-
@Override
108-
protected final void beforeExecute() {
109-
if (shouldConfigureLoggingWithoutConfig()) {
110-
// initialize default for es.logger.level because we will not read the log4j2.properties
111-
final String loggerLevel = System.getProperty("es.logger.level", Level.INFO.name());
112-
final Settings settings = Settings.builder().put("logger.level", loggerLevel).build();
113-
LogConfigurator.configureWithoutConfig(settings);
114-
}
115-
}
116-
117-
/**
118-
* Indicate whether or not logging should be configured without reading a log4j2.properties. Most commands should do this because we do
119-
* not configure logging for CLI tools. Only commands that configure logging on their own should not do this.
120-
*
121-
* @return true if logging should be configured without reading a log4j2.properties file
122-
*/
123-
protected boolean shouldConfigureLoggingWithoutConfig() {
124-
return true;
125-
}
126-
127122
/** Execute the command with the initialized {@link Environment}. */
128123
protected abstract void execute(Terminal terminal, OptionSet options, Environment env) throws Exception;
129124

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.cli;
21+
22+
/**
23+
* A command that is aware of logging. This class should be preferred over the base {@link Command} class for any CLI tools that depend on
24+
* core Elasticsearch as they could directly or indirectly touch classes that touch logging and as such logging needs to be configured.
25+
*/
26+
public abstract class LoggingAwareCommand extends Command {
27+
28+
/**
29+
* Construct the command with the specified command description. This command will have logging configured without reading Elasticsearch
30+
* configuration files.
31+
*
32+
* @param description the command description
33+
*/
34+
public LoggingAwareCommand(final String description) {
35+
super(description, CommandLoggingConfigurator::configureLoggingWithoutConfig);
36+
}
37+
38+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.cli;
21+
22+
/**
23+
* A multi-command that is aware of logging. This class should be preferred over the base {@link MultiCommand} class for any CLI tools that
24+
* depend on core Elasticsearch as they could directly or indirectly touch classes that touch logging and as such logging needs to be
25+
* configured.
26+
*/
27+
public abstract class LoggingAwareMultiCommand extends MultiCommand {
28+
29+
/**
30+
* Construct the command with the specified command description. This command will have logging configured without reading Elasticsearch
31+
* configuration files.
32+
*
33+
* @param description the command description
34+
*/
35+
public LoggingAwareMultiCommand(final String description) {
36+
super(description, CommandLoggingConfigurator::configureLoggingWithoutConfig);
37+
}
38+
39+
}

core/src/main/java/org/elasticsearch/common/settings/KeyStoreCli.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@
1919

2020
package org.elasticsearch.common.settings;
2121

22+
import org.elasticsearch.cli.LoggingAwareMultiCommand;
2223
import org.elasticsearch.cli.MultiCommand;
2324
import org.elasticsearch.cli.Terminal;
2425

2526
/**
2627
* A cli tool for managing secrets in the elasticsearch keystore.
2728
*/
28-
public class KeyStoreCli extends MultiCommand {
29+
public class KeyStoreCli extends LoggingAwareMultiCommand {
2930

3031
private KeyStoreCli() {
3132
super("A tool for managing settings stored in the elasticsearch keystore");
@@ -39,4 +40,5 @@ private KeyStoreCli() {
3940
public static void main(String[] args) throws Exception {
4041
exit(new KeyStoreCli().main(args, Terminal.DEFAULT));
4142
}
43+
4244
}

core/src/main/java/org/elasticsearch/index/translog/TranslogToolCli.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@
1919

2020
package org.elasticsearch.index.translog;
2121

22+
import org.elasticsearch.cli.LoggingAwareMultiCommand;
2223
import org.elasticsearch.cli.MultiCommand;
2324
import org.elasticsearch.cli.Terminal;
2425

2526
/**
2627
* Class encapsulating and dispatching commands from the {@code elasticsearch-translog} command line tool
2728
*/
28-
public class TranslogToolCli extends MultiCommand {
29+
public class TranslogToolCli extends LoggingAwareMultiCommand {
2930

3031
private TranslogToolCli() {
3132
super("A CLI tool for various Elasticsearch translog actions");

core/src/test/java/org/elasticsearch/cli/CommandTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class CommandTests extends ESTestCase {
2828
static class UserErrorCommand extends Command {
2929

3030
UserErrorCommand() {
31-
super("Throws a user error");
31+
super("Throws a user error", () -> {});
3232
}
3333

3434
@Override
@@ -46,7 +46,7 @@ protected boolean addShutdownHook() {
4646
static class UsageErrorCommand extends Command {
4747

4848
UsageErrorCommand() {
49-
super("Throws a usage error");
49+
super("Throws a usage error", () -> {});
5050
}
5151

5252
@Override
@@ -66,7 +66,7 @@ static class NoopCommand extends Command {
6666
boolean executed = false;
6767

6868
NoopCommand() {
69-
super("Does nothing");
69+
super("Does nothing", () -> {});
7070
}
7171

7272
@Override

core/src/test/java/org/elasticsearch/cli/MultiCommandTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ public class MultiCommandTests extends CommandTestCase {
2626

2727
static class DummyMultiCommand extends MultiCommand {
2828
DummyMultiCommand() {
29-
super("A dummy multi command");
29+
super("A dummy multi command", () -> {});
3030
}
3131
}
3232

3333
static class DummySubCommand extends Command {
3434
DummySubCommand() {
35-
super("A dummy subcommand");
35+
super("A dummy subcommand", () -> {});
3636
}
3737
@Override
3838
protected void execute(Terminal terminal, OptionSet options) throws Exception {

distribution/tools/plugin-cli/src/main/java/org/elasticsearch/plugins/PluginCli.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.apache.lucene.util.IOUtils;
2323
import org.elasticsearch.cli.Command;
24+
import org.elasticsearch.cli.LoggingAwareMultiCommand;
2425
import org.elasticsearch.cli.MultiCommand;
2526
import org.elasticsearch.cli.Terminal;
2627

@@ -31,7 +32,7 @@
3132
/**
3233
* A cli tool for adding, removing and listing plugins for elasticsearch.
3334
*/
34-
public class PluginCli extends MultiCommand {
35+
public class PluginCli extends LoggingAwareMultiCommand {
3536

3637
private final Collection<Command> commands;
3738

qa/evil-tests/src/test/java/org/elasticsearch/cli/EvilCommandTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public class EvilCommandTests extends ESTestCase {
3333
public void testCommandShutdownHook() throws Exception {
3434
final AtomicBoolean closed = new AtomicBoolean();
3535
final boolean shouldThrow = randomBoolean();
36-
final Command command = new Command("test-command-shutdown-hook") {
36+
final Command command = new Command("test-command-shutdown-hook", () -> {}) {
3737
@Override
3838
protected void execute(Terminal terminal, OptionSet options) throws Exception {
3939

0 commit comments

Comments
 (0)