Add quiet option to disable console logging (#20422)

This commit adds a -q/--quiet option to Elasticsearch so that it does not log anything in the console and closes stdout & stderr streams. This is useful for SystemD to avoid duplicate logs in both journalctl and /var/log/elasticsearch/elasticsearch.log while still allows the JVM to print error messages in stdout/stderr if needed.

closes #17220
This commit is contained in:
Tanguy Leroux 2016-09-12 16:56:13 +02:00
parent c7bfbe3e69
commit 6090c51fc5
11 changed files with 103 additions and 29 deletions

View File

@ -368,7 +368,8 @@ These are the linux flavors the Vagrantfile currently supports:
* debian-8 aka jessie, the current debian stable distribution * debian-8 aka jessie, the current debian stable distribution
* centos-6 * centos-6
* centos-7 * centos-7
* fedora-22 * fedora-24
* oel-6 aka Oracle Enterprise Linux 6
* oel-7 aka Oracle Enterprise Linux 7 * oel-7 aka Oracle Enterprise Linux 7
* sles-12 * sles-12
* opensuse-13 * opensuse-13
@ -377,7 +378,6 @@ We're missing the following from the support matrix because there aren't high
quality boxes available in vagrant atlas: quality boxes available in vagrant atlas:
* sles-11 * sles-11
* oel-6
We're missing the follow because our tests are very linux/bash centric: We're missing the follow because our tests are very linux/bash centric:

View File

@ -227,12 +227,12 @@ final class Bootstrap {
} }
/** /**
* This method is invoked by {@link Elasticsearch#main(String[])} * This method is invoked by {@link Elasticsearch#main(String[])} to startup elasticsearch.
* to startup elasticsearch.
*/ */
static void init( static void init(
final boolean foreground, final boolean foreground,
final Path pidFile, final Path pidFile,
final boolean quiet,
final Map<String, String> esSettings) throws BootstrapException, NodeValidationException { final Map<String, String> esSettings) throws BootstrapException, NodeValidationException {
// Set the system property before anything has a chance to trigger its use // Set the system property before anything has a chance to trigger its use
initLoggerPrefix(); initLoggerPrefix();
@ -259,8 +259,9 @@ final class Bootstrap {
} }
} }
final boolean closeStandardStreams = (foreground == false) || quiet;
try { try {
if (!foreground) { if (closeStandardStreams) {
final Logger rootLogger = ESLoggerFactory.getRootLogger(); final Logger rootLogger = ESLoggerFactory.getRootLogger();
final Appender maybeConsoleAppender = Loggers.findAppender(rootLogger, ConsoleAppender.class); final Appender maybeConsoleAppender = Loggers.findAppender(rootLogger, ConsoleAppender.class);
if (maybeConsoleAppender != null) { if (maybeConsoleAppender != null) {
@ -285,7 +286,7 @@ final class Bootstrap {
INSTANCE.start(); INSTANCE.start();
if (!foreground) { if (closeStandardStreams) {
closeSysError(); closeSysError();
} }
} catch (NodeValidationException | RuntimeException e) { } catch (NodeValidationException | RuntimeException e) {

View File

@ -26,7 +26,7 @@ import java.util.Map;
* Wrapper exception for checked exceptions thrown during the bootstrap process. Methods invoked * Wrapper exception for checked exceptions thrown during the bootstrap process. Methods invoked
* during bootstrap should explicitly declare the checked exceptions that they can throw, rather * during bootstrap should explicitly declare the checked exceptions that they can throw, rather
* than declaring the top-level checked exception {@link Exception}. This exception exists to wrap * than declaring the top-level checked exception {@link Exception}. This exception exists to wrap
* these checked exceptions so that {@link Bootstrap#init(boolean, Path, Map)} does not have to * these checked exceptions so that {@link Bootstrap#init(boolean, Path, boolean, Map)} does not have to
* declare all of these checked exceptions. * declare all of these checked exceptions.
*/ */
class BootstrapException extends Exception { class BootstrapException extends Exception {

View File

@ -44,6 +44,7 @@ class Elasticsearch extends SettingCommand {
private final OptionSpecBuilder versionOption; private final OptionSpecBuilder versionOption;
private final OptionSpecBuilder daemonizeOption; private final OptionSpecBuilder daemonizeOption;
private final OptionSpec<Path> pidfileOption; private final OptionSpec<Path> pidfileOption;
private final OptionSpecBuilder quietOption;
// visible for testing // visible for testing
Elasticsearch() { Elasticsearch() {
@ -58,6 +59,10 @@ class Elasticsearch extends SettingCommand {
.availableUnless(versionOption) .availableUnless(versionOption)
.withRequiredArg() .withRequiredArg()
.withValuesConvertedBy(new PathConverter()); .withValuesConvertedBy(new PathConverter());
quietOption = parser.acceptsAll(Arrays.asList("q", "quiet"),
"Turns off standard ouput/error streams logging in console")
.availableUnless(versionOption)
.availableUnless(daemonizeOption);
} }
/** /**
@ -92,17 +97,19 @@ class Elasticsearch extends SettingCommand {
final boolean daemonize = options.has(daemonizeOption); final boolean daemonize = options.has(daemonizeOption);
final Path pidFile = pidfileOption.value(options); final Path pidFile = pidfileOption.value(options);
final boolean quiet = options.has(quietOption);
try { try {
init(daemonize, pidFile, settings); init(daemonize, pidFile, quiet, settings);
} catch (NodeValidationException e) { } catch (NodeValidationException e) {
throw new UserException(ExitCodes.CONFIG, e.getMessage()); throw new UserException(ExitCodes.CONFIG, e.getMessage());
} }
} }
void init(final boolean daemonize, final Path pidFile, final Map<String, String> esSettings) throws NodeValidationException { void init(final boolean daemonize, final Path pidFile, final boolean quiet, final Map<String, String> esSettings)
throws NodeValidationException {
try { try {
Bootstrap.init(!daemonize, pidFile, esSettings); Bootstrap.init(!daemonize, pidFile, quiet, esSettings);
} catch (BootstrapException | RuntimeException e) { } catch (BootstrapException | RuntimeException e) {
// format exceptions to the console in a special way // format exceptions to the console in a special way
// to avoid 2MB stacktraces from guice, etc. // to avoid 2MB stacktraces from guice, etc.

View File

@ -43,6 +43,9 @@ public class ElasticsearchCliTests extends ESElasticsearchCliTestCase {
runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "--daemonize"); runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "--daemonize");
runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "-p", "/tmp/pid"); runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "-p", "/tmp/pid");
runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "--pidfile", "/tmp/pid"); runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "--pidfile", "/tmp/pid");
runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "-q");
runTestThatVersionIsMutuallyExclusiveToOtherOptions("--version", "--quiet");
runTestThatVersionIsReturned("-V"); runTestThatVersionIsReturned("-V");
runTestThatVersionIsReturned("--version"); runTestThatVersionIsReturned("--version");
} }
@ -66,7 +69,7 @@ public class ElasticsearchCliTests extends ESElasticsearchCliTestCase {
} }
private void runTestVersion(int expectedStatus, Consumer<String> outputConsumer, String... args) throws Exception { private void runTestVersion(int expectedStatus, Consumer<String> outputConsumer, String... args) throws Exception {
runTest(expectedStatus, false, outputConsumer, (foreground, pidFile, esSettings) -> {}, args); runTest(expectedStatus, false, outputConsumer, (foreground, pidFile, quiet, esSettings) -> {}, args);
} }
public void testPositionalArgs() throws Exception { public void testPositionalArgs() throws Exception {
@ -74,21 +77,21 @@ public class ElasticsearchCliTests extends ESElasticsearchCliTestCase {
ExitCodes.USAGE, ExitCodes.USAGE,
false, false,
output -> assertThat(output, containsString("Positional arguments not allowed, found [foo]")), output -> assertThat(output, containsString("Positional arguments not allowed, found [foo]")),
(foreground, pidFile, esSettings) -> {}, (foreground, pidFile, quiet, esSettings) -> {},
"foo" "foo"
); );
runTest( runTest(
ExitCodes.USAGE, ExitCodes.USAGE,
false, false,
output -> assertThat(output, containsString("Positional arguments not allowed, found [foo, bar]")), output -> assertThat(output, containsString("Positional arguments not allowed, found [foo, bar]")),
(foreground, pidFile, esSettings) -> {}, (foreground, pidFile, quiet, esSettings) -> {},
"foo", "bar" "foo", "bar"
); );
runTest( runTest(
ExitCodes.USAGE, ExitCodes.USAGE,
false, false,
output -> assertThat(output, containsString("Positional arguments not allowed, found [foo]")), output -> assertThat(output, containsString("Positional arguments not allowed, found [foo]")),
(foreground, pidFile, esSettings) -> {}, (foreground, pidFile, quiet, esSettings) -> {},
"-E", "foo=bar", "foo", "-E", "baz=qux" "-E", "foo=bar", "foo", "-E", "baz=qux"
); );
} }
@ -109,7 +112,7 @@ public class ElasticsearchCliTests extends ESElasticsearchCliTestCase {
expectedStatus, expectedStatus,
expectedInit, expectedInit,
outputConsumer, outputConsumer,
(foreground, pidFile, esSettings) -> assertThat(pidFile.toString(), equalTo(expectedPidFile.toString())), (foreground, pidFile, quiet, esSettings) -> assertThat(pidFile.toString(), equalTo(expectedPidFile.toString())),
args); args);
} }
@ -124,7 +127,22 @@ public class ElasticsearchCliTests extends ESElasticsearchCliTestCase {
ExitCodes.OK, ExitCodes.OK,
true, true,
output -> {}, output -> {},
(foreground, pidFile, esSettings) -> assertThat(foreground, equalTo(!expectedDaemonize)), (foreground, pidFile, quiet, esSettings) -> assertThat(foreground, equalTo(!expectedDaemonize)),
args);
}
public void testThatParsingQuietOptionWorks() throws Exception {
runQuietTest(true, "-q");
runQuietTest(true, "--quiet");
runQuietTest(false);
}
private void runQuietTest(final boolean expectedQuiet, final String... args) throws Exception {
runTest(
ExitCodes.OK,
true,
output -> {},
(foreground, pidFile, quiet, esSettings) -> assertThat(quiet, equalTo(expectedQuiet)),
args); args);
} }
@ -133,7 +151,7 @@ public class ElasticsearchCliTests extends ESElasticsearchCliTestCase {
ExitCodes.OK, ExitCodes.OK,
true, true,
output -> {}, output -> {},
(foreground, pidFile, esSettings) -> { (foreground, pidFile, quiet, esSettings) -> {
assertThat(esSettings.size(), equalTo(2)); assertThat(esSettings.size(), equalTo(2));
assertThat(esSettings, hasEntry("foo", "bar")); assertThat(esSettings, hasEntry("foo", "bar"));
assertThat(esSettings, hasEntry("baz", "qux")); assertThat(esSettings, hasEntry("baz", "qux"));
@ -147,7 +165,7 @@ public class ElasticsearchCliTests extends ESElasticsearchCliTestCase {
ExitCodes.USAGE, ExitCodes.USAGE,
false, false,
output -> assertThat(output, containsString("Setting [foo] must not be empty")), output -> assertThat(output, containsString("Setting [foo] must not be empty")),
(foreground, pidFile, esSettings) -> {}, (foreground, pidFile, quiet, esSettings) -> {},
"-E", "foo=" "-E", "foo="
); );
} }
@ -157,7 +175,7 @@ public class ElasticsearchCliTests extends ESElasticsearchCliTestCase {
ExitCodes.USAGE, ExitCodes.USAGE,
false, false,
output -> assertThat(output, containsString("network.host is not a recognized option")), output -> assertThat(output, containsString("network.host is not a recognized option")),
(foreground, pidFile, esSettings) -> {}, (foreground, pidFile, quiet, esSettings) -> {},
"--network.host"); "--network.host");
} }

View File

@ -21,10 +21,17 @@ ExecStartPre=/usr/share/elasticsearch/bin/elasticsearch-systemd-pre-exec
ExecStart=/usr/share/elasticsearch/bin/elasticsearch \ ExecStart=/usr/share/elasticsearch/bin/elasticsearch \
-p ${PID_DIR}/elasticsearch.pid \ -p ${PID_DIR}/elasticsearch.pid \
--quiet \
-Edefault.path.logs=${LOG_DIR} \ -Edefault.path.logs=${LOG_DIR} \
-Edefault.path.data=${DATA_DIR} \ -Edefault.path.data=${DATA_DIR} \
-Edefault.path.conf=${CONF_DIR} -Edefault.path.conf=${CONF_DIR}
# StandardOutput is configured to redirect to journalctl since
# some error messages may be logged in standard output before
# elasticsearch logging system is initialized. Elasticsearch
# stores its logs in /var/log/elasticsearch and does not use
# journalctl by default. If you also want to enable journalctl
# logging, you can simply remove the "quiet" option from ExecStart.
StandardOutput=journal StandardOutput=journal
StandardError=inherit StandardError=inherit

View File

@ -18,13 +18,36 @@ sudo systemctl stop elasticsearch.service
-------------------------------------------- --------------------------------------------
These commands provide no feedback as to whether Elasticsearch was started These commands provide no feedback as to whether Elasticsearch was started
successfully or not. Instead, this information will be written to the successfully or not. Instead, this information will be written in the log
`systemd` journal, which can be tailed as follows: files located in `/var/log/elasticsearch/`.
By default the Elasticsearch service doesn't log information in the `systemd`
journal. To enable `journalctl` logging, the `--quiet` option must be removed
from the `ExecStart` command line in the `elasticsearch.service` file.
When `systemd` logging is enabled, the logging information are available using
the `journalctl` commands:
To tail the journal:
[source,sh] [source,sh]
-------------------------------------------- --------------------------------------------
sudo journalctl -f sudo journalctl -f
-------------------------------------------- --------------------------------------------
Log files can be found in `/var/log/elasticsearch/`. To list journal entries for the elasticsearch service:
[source,sh]
--------------------------------------------
sudo journalctl --unit elasticsearch
--------------------------------------------
To list journal entries for the elasticsearch service starting from a given time:
[source,sh]
--------------------------------------------
sudo journalctl --unit elasticsearch --since "2016-10-30 18:17:16"
--------------------------------------------
Check `man journalctl` or https://www.freedesktop.org/software/systemd/man/journalctl.html for
more command line options.

View File

@ -52,11 +52,14 @@ Elasticsearch can be started from the command line as follows:
./bin/elasticsearch ./bin/elasticsearch
-------------------------------------------- --------------------------------------------
By default, Elasticsearch runs in the foreground, prints its logs to `STDOUT`, By default, Elasticsearch runs in the foreground, prints its logs to the
and can be stopped by pressing `Ctrl-C`. standard output (`stdout`), and can be stopped by pressing `Ctrl-C`.
include::check-running.asciidoc[] include::check-running.asciidoc[]
Log printing to `stdout` can be disabled using the `-q` or `--quiet`
option on the command line.
[[setup-installation-daemon]] [[setup-installation-daemon]]
==== Running as a daemon ==== Running as a daemon

View File

@ -38,7 +38,7 @@ public class EvilElasticsearchCliTests extends ESElasticsearchCliTestCase {
ExitCodes.OK, ExitCodes.OK,
true, true,
output -> {}, output -> {},
(foreground, pidFile, esSettings) -> { (foreground, pidFile, quiet, esSettings) -> {
assertThat(esSettings.size(), equalTo(1)); assertThat(esSettings.size(), equalTo(1));
assertThat(esSettings, hasEntry("path.home", value)); assertThat(esSettings, hasEntry("path.home", value));
}); });
@ -49,7 +49,7 @@ public class EvilElasticsearchCliTests extends ESElasticsearchCliTestCase {
ExitCodes.OK, ExitCodes.OK,
true, true,
output -> {}, output -> {},
(foreground, pidFile, esSettings) -> { (foreground, pidFile, quiet, esSettings) -> {
assertThat(esSettings.size(), equalTo(1)); assertThat(esSettings.size(), equalTo(1));
assertThat(esSettings, hasEntry("path.home", commandLineValue)); assertThat(esSettings, hasEntry("path.home", commandLineValue));
}, },

View File

@ -68,9 +68,24 @@ setup() {
# starting Elasticsearch so we don't have to wait for elasticsearch to scan for # starting Elasticsearch so we don't have to wait for elasticsearch to scan for
# them. # them.
install_elasticsearch_test_scripts install_elasticsearch_test_scripts
# Capture the current epoch in millis
run date +%s
epoch="$output"
systemctl start elasticsearch.service systemctl start elasticsearch.service
wait_for_elasticsearch_status wait_for_elasticsearch_status
assert_file_exist "/var/run/elasticsearch/elasticsearch.pid" assert_file_exist "/var/run/elasticsearch/elasticsearch.pid"
assert_file_exist "/var/log/elasticsearch/elasticsearch.log"
# Converts the epoch back in a human readable format
run date --date=@$epoch "+%Y-%m-%d %H:%M:%S"
since="$output"
# Verifies that no new entries in journald have been added
# since the last start
run journalctl _SYSTEMD_UNIT=elasticsearch.service --since "$since"
[ "$status" -eq 1 ]
} }
@test "[SYSTEMD] start (running)" { @test "[SYSTEMD] start (running)" {

View File

@ -32,7 +32,7 @@ import static org.hamcrest.CoreMatchers.equalTo;
abstract class ESElasticsearchCliTestCase extends ESTestCase { abstract class ESElasticsearchCliTestCase extends ESTestCase {
interface InitConsumer { interface InitConsumer {
void accept(final boolean foreground, final Path pidFile, final Map<String, String> esSettings); void accept(final boolean foreground, final Path pidFile, final boolean quiet, final Map<String, String> esSettings);
} }
void runTest( void runTest(
@ -46,9 +46,9 @@ abstract class ESElasticsearchCliTestCase extends ESTestCase {
final AtomicBoolean init = new AtomicBoolean(); final AtomicBoolean init = new AtomicBoolean();
final int status = Elasticsearch.main(args, new Elasticsearch() { final int status = Elasticsearch.main(args, new Elasticsearch() {
@Override @Override
void init(final boolean daemonize, final Path pidFile, final Map<String, String> esSettings) { void init(final boolean daemonize, final Path pidFile, final boolean quiet, final Map<String, String> esSettings) {
init.set(true); init.set(true);
initConsumer.accept(!daemonize, pidFile, esSettings); initConsumer.accept(!daemonize, pidFile, quiet, esSettings);
} }
}, terminal); }, terminal);
assertThat(status, equalTo(expectedStatus)); assertThat(status, equalTo(expectedStatus));