Skip to content

Commit

Permalink
Print full exception when console is non-interactive (#88297)
Browse files Browse the repository at this point in the history
The console logger truncates stack traces so a user is not bombarded
with enormouse messages on startup. However, when the output is
redirected to a file, there is no need to avoid large messages, and in
fact it is a hinderance to debugging. This commit adds an internal flag
to the console logging exception convert which disables the truncation
when attached to a non-interactive console.
  • Loading branch information
rjernst authored Jul 6, 2022
1 parent 51fa9e3 commit 26c256c
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 8 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/88297.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 88297
summary: Print full exception when console is non-interactive
area: Infra/Core
type: enhancement
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
package org.elasticsearch.bootstrap;

import org.apache.lucene.util.SetOnce;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.SuppressForbidden;

import java.util.Dictionary;
Expand Down Expand Up @@ -50,8 +51,10 @@ public static boolean isSystemCallFilterInstalled() {
}

/**
* Returns a reference to a stream attached to Standard Output, iff we have determined that stdout is a console (tty)
* Returns information about the console (tty) attached to the server process, or {@code null}
* if no console is attached.
*/
@Nullable
public static ConsoleLoader.Console getConsole() {
return console.get();
}
Expand Down Expand Up @@ -143,7 +146,7 @@ public static Dictionary<Object, Object> getSystemProperties() {

public static void init() {}

static void setConsole(ConsoleLoader.Console console) {
static void setConsole(@Nullable ConsoleLoader.Console console) {
BootstrapInfo.console.set(console);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,25 @@
import org.apache.logging.log4j.core.pattern.ConverterKeys;
import org.apache.logging.log4j.core.pattern.PatternConverter;
import org.apache.logging.log4j.core.pattern.ThrowablePatternConverter;
import org.elasticsearch.bootstrap.BootstrapInfo;
import org.elasticsearch.bootstrap.StartupException;
import org.elasticsearch.common.inject.CreationException;

/**
* Outputs a very short version of exceptions for the console, pointing to full log for details.
* Outputs a very short version of exceptions for an interactive console, pointing to full log for details.
*
* <p> If a non-interactive console is attached, the full exception is always printed.
*/
@Plugin(name = "consoleException", category = PatternConverter.CATEGORY)
@ConverterKeys({ "consoleException" })
public class ConsoleThrowablePatternConverter extends ThrowablePatternConverter {
private ConsoleThrowablePatternConverter(String[] options, Configuration config) {

// true if exceptions should be truncated, false if they should be delegated to the super class
private final boolean enabled;

private ConsoleThrowablePatternConverter(String[] options, Configuration config, boolean enabled) {
super("ConsoleThrowablePatternConverter", "throwable", options, config);
this.enabled = enabled;
}

/**
Expand All @@ -34,20 +42,26 @@ private ConsoleThrowablePatternConverter(String[] options, Configuration config)
* @return instance of class.
*/
public static ConsoleThrowablePatternConverter newInstance(final Configuration config, final String[] options) {
return new ConsoleThrowablePatternConverter(options, config);
return newInstance(config, options, BootstrapInfo.getConsole() != null);
}

// package private for tests
static ConsoleThrowablePatternConverter newInstance(final Configuration config, final String[] options, boolean enabled) {
return new ConsoleThrowablePatternConverter(options, config, enabled);
}

@Override
public void format(final LogEvent event, final StringBuilder toAppendTo) {
Throwable error = event.getThrown();
if (error == null) {
if (enabled == false | error == null) {
super.format(event, toAppendTo);
return;
}
if (error instanceof StartupException e) {
error = e.getCause();
toAppendTo.append("\n\nElasticsearch failed to startup normally.\n\n");
}

appendShortStacktrace(error, toAppendTo);
if (error instanceof CreationException) {
toAppendTo.append("There were problems initializing Guice. See log for more details.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,31 @@
import static org.hamcrest.Matchers.not;

public class ConsoleThrowablePatternConverterTests extends ESTestCase {
static final ConsoleThrowablePatternConverter converter = ConsoleThrowablePatternConverter.newInstance(null, null);
static final ConsoleThrowablePatternConverter converter = ConsoleThrowablePatternConverter.newInstance(null, null, true);

String format(Throwable e) {
String format(ConsoleThrowablePatternConverter converter, Throwable e) {
LogEvent event = Log4jLogEvent.newBuilder().setThrown(e).build();
var builder = new StringBuilder();
converter.format(event, builder);
return builder.toString();
}

String format(Throwable e) {
return format(converter, e);
}

public void testNoException() {
assertThat(format(null), emptyString());
}

public void testDisabledPassthrough() {
// mimic no interactive console
var converter = ConsoleThrowablePatternConverter.newInstance(null, null, false);
var e = new StartupException(new RuntimeException("a cause"));
// the cause of StartupException is not extracted by the parent pattern converter
assertThat(format(converter, e), allOf(containsString("StartupException: "), containsString("RuntimeException: a cause")));
}

public void testStartupExceptionUnwrapped() {
var e = new StartupException(new RuntimeException("an error"));
assertThat(
Expand Down

0 comments on commit 26c256c

Please sign in to comment.