Browse Source

Remove ExceptionHelper.detailedMessage (#45878)

Closes #19069. This method tries to summarize an exception in a single
string, but the entire point of the structured exception work was to get
real stack traces. This method just hurts, and does not help. Remove this
method, replacing it with a mix of ` e.getMessage()`,
`ExceptionsHelper.stackTrace`.

In some cases, I rearranged some message formats so that exceptions are
reported at the end of the message, instead of in the middle.

Other changes:

   * Rewrite DetailedErrorsEnabledIT to parse responses. Instead of relying
     on substring matching, parse the response JSON and check fields
     explicitly. This uses Jackson, which has been added to the build
     dependencies for :qa:smoke-test-http.
   * Add a getter to `VerificationFailure` so that the `cause` field can be
     accessed and added as suppressed exceptions elsewhere.
Rory Hunter 6 years ago
parent
commit
ff9e8c6224
45 changed files with 202 additions and 139 deletions
  1. 2 2
      client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateApiKeyResponseTests.java
  2. 2 2
      client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateTokenResponseTests.java
  3. 4 7
      modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/IngestRestartIT.java
  4. 4 2
      modules/reindex/src/test/java/org/elasticsearch/index/reindex/ReindexFailureTests.java
  5. 1 0
      qa/smoke-test-http/build.gradle
  6. 33 12
      qa/smoke-test-http/src/test/java/org/elasticsearch/http/DetailedErrorsEnabledIT.java
  7. 1 1
      server/src/main/java/org/elasticsearch/ElasticsearchException.java
  8. 0 29
      server/src/main/java/org/elasticsearch/ExceptionsHelper.java
  9. 1 1
      server/src/main/java/org/elasticsearch/action/search/ShardSearchFailure.java
  10. 3 4
      server/src/main/java/org/elasticsearch/action/support/DefaultShardOperationFailedException.java
  11. 1 1
      server/src/main/java/org/elasticsearch/action/support/replication/ReplicationResponse.java
  12. 2 2
      server/src/main/java/org/elasticsearch/cluster/action/shard/ShardStateAction.java
  13. 1 1
      server/src/main/java/org/elasticsearch/cluster/routing/UnassignedInfo.java
  14. 2 2
      server/src/main/java/org/elasticsearch/cluster/routing/allocation/FailedShard.java
  15. 3 3
      server/src/main/java/org/elasticsearch/cluster/routing/allocation/decider/RestoreInProgressAllocationDecider.java
  16. 1 1
      server/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java
  17. 1 2
      server/src/main/java/org/elasticsearch/index/shard/StoreRecovery.java
  18. 4 0
      server/src/main/java/org/elasticsearch/repositories/VerificationFailure.java
  19. 5 1
      server/src/main/java/org/elasticsearch/repositories/VerifyNodeRepositoryAction.java
  20. 1 1
      server/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java
  21. 1 2
      server/src/main/java/org/elasticsearch/search/suggest/phrase/DirectCandidateGeneratorBuilder.java
  22. 1 1
      server/src/main/java/org/elasticsearch/snapshots/SnapshotShardsService.java
  23. 1 1
      server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java
  24. 2 2
      server/src/main/java/org/elasticsearch/tasks/TaskManager.java
  25. 9 2
      server/src/test/java/org/elasticsearch/ElasticsearchExceptionTests.java
  26. 12 1
      server/src/test/java/org/elasticsearch/action/admin/indices/close/CloseIndexResponseTests.java
  27. 6 2
      server/src/test/java/org/elasticsearch/action/bulk/BulkProcessorClusterSettingsIT.java
  28. 7 3
      server/src/test/java/org/elasticsearch/action/search/SearchScrollAsyncActionTests.java
  29. 22 5
      server/src/test/java/org/elasticsearch/action/support/DefaultShardOperationFailedExceptionTests.java
  30. 1 1
      server/src/test/java/org/elasticsearch/action/support/broadcast/node/TransportBroadcastByNodeActionTests.java
  31. 8 6
      server/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/RestoreInProgressAllocationDeciderTests.java
  32. 7 4
      server/src/test/java/org/elasticsearch/env/OverrideNodeVersionCommandTests.java
  33. 8 10
      server/src/test/java/org/elasticsearch/indices/memory/breaker/CircuitBreakerServiceIT.java
  34. 2 2
      server/src/test/java/org/elasticsearch/rest/BytesRestResponseTests.java
  35. 1 1
      server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileGridParserTests.java
  36. 21 3
      server/src/test/java/org/elasticsearch/search/query/SearchQueryIT.java
  37. 1 1
      server/src/test/java/org/elasticsearch/search/query/SimpleQueryStringIT.java
  38. 1 1
      server/src/test/java/org/elasticsearch/search/sort/FieldSortIT.java
  39. 3 3
      server/src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchIT.java
  40. 9 7
      server/src/test/java/org/elasticsearch/snapshots/RepositoriesIT.java
  41. 1 1
      server/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java
  42. 2 2
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/InvalidateTokenResponseTests.java
  43. 1 1
      x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/action/TransportMonitoringBulkActionTests.java
  44. 2 2
      x-pack/plugin/security/src/test/java/org/elasticsearch/integration/SecurityCachePermissionTests.java
  45. 1 1
      x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/ReadActionsTests.java

+ 2 - 2
client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateApiKeyResponseTests.java

@@ -71,8 +71,8 @@ public class InvalidateApiKeyResponseTests extends ESTestCase {
                 containsInAnyOrder(previouslyInvalidatedApiKeys.toArray(Strings.EMPTY_ARRAY)));
         assertThat(response.getErrors(), is(notNullValue()));
         assertThat(response.getErrors().size(), is(errors.size()));
-        assertThat(response.getErrors().get(0).toString(), containsString("type=illegal_argument_exception"));
-        assertThat(response.getErrors().get(1).toString(), containsString("type=illegal_argument_exception"));
+        assertThat(response.getErrors().get(0).getCause().toString(), containsString("type=illegal_argument_exception"));
+        assertThat(response.getErrors().get(1).getCause().toString(), containsString("type=illegal_argument_exception"));
     }
 
     public void testEqualsHashCode() {

+ 2 - 2
client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateTokenResponseTests.java

@@ -83,9 +83,9 @@ public class InvalidateTokenResponseTests extends ESTestCase {
             assertThat(response.getPreviouslyInvalidatedTokens(), Matchers.equalTo(previouslyInvalidatedTokens));
             assertThat(response.getErrorsCount(), Matchers.equalTo(2));
             assertThat(response.getErrors().get(0).toString(), containsString("type=exception, reason=foo"));
-            assertThat(response.getErrors().get(0).toString(), containsString("type=illegal_argument_exception, reason=bar"));
+            assertThat(response.getErrors().get(0).getCause().getMessage(), containsString("type=illegal_argument_exception, reason=bar"));
             assertThat(response.getErrors().get(1).toString(), containsString("type=exception, reason=boo"));
-            assertThat(response.getErrors().get(1).toString(), containsString("type=illegal_argument_exception, reason=far"));
+            assertThat(response.getErrors().get(1).getCause().getMessage(), containsString("type=illegal_argument_exception, reason=far"));
         }
     }
 }

+ 4 - 7
modules/ingest-common/src/test/java/org/elasticsearch/ingest/common/IngestRestartIT.java

@@ -99,7 +99,7 @@ public class IngestRestartIT extends ESIntegTestCase {
             }
 
         });
-        
+
         checkPipelineExists.accept(pipelineIdWithoutScript);
         checkPipelineExists.accept(pipelineIdWithScript);
 
@@ -119,12 +119,9 @@ public class IngestRestartIT extends ESIntegTestCase {
         assertThat(exception.getHeader("processor_type"), equalTo(Arrays.asList("unknown")));
         assertThat(exception.getRootCause().getMessage(),
             equalTo("pipeline with id [" + pipelineIdWithScript + "] could not be loaded, caused by " +
-                "[ElasticsearchParseException[Error updating pipeline with id [" + pipelineIdWithScript + "]]; " +
-                "nested: ElasticsearchException[java.lang.IllegalArgumentException: cannot execute [inline] scripts]; " +
-                "nested: IllegalArgumentException[cannot execute [inline] scripts];; " +
-                "ElasticsearchException[java.lang.IllegalArgumentException: cannot execute [inline] scripts]; " +
-                "nested: IllegalArgumentException[cannot execute [inline] scripts];; java.lang.IllegalArgumentException: " +
-                "cannot execute [inline] scripts]"));
+                "[org.elasticsearch.ElasticsearchParseException: Error updating pipeline with id [" + pipelineIdWithScript + "]; " +
+                "org.elasticsearch.ElasticsearchException: java.lang.IllegalArgumentException: cannot execute [inline] scripts; " +
+                "java.lang.IllegalArgumentException: cannot execute [inline] scripts]"));
 
         Map<String, Object> source = client().prepareGet("index", "doc", "1").get().getSource();
         assertThat(source.get("x"), equalTo(0));

+ 4 - 2
modules/reindex/src/test/java/org/elasticsearch/index/reindex/ReindexFailureTests.java

@@ -32,6 +32,7 @@ import static org.hamcrest.Matchers.both;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.either;
 import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.instanceOf;
 import static org.hamcrest.Matchers.lessThanOrEqualTo;
 
 /**
@@ -61,7 +62,8 @@ public class ReindexFailureTests extends ReindexTestCase {
                 .batches(1)
                 .failures(both(greaterThan(0)).and(lessThanOrEqualTo(maximumNumberOfShards()))));
         for (Failure failure: response.getBulkFailures()) {
-            assertThat(failure.getMessage(), containsString("IllegalArgumentException[For input string: \"words words\"]"));
+            assertThat(failure.getCause().getCause(), instanceOf(IllegalArgumentException.class));
+            assertThat(failure.getCause().getCause().getMessage(), containsString("For input string: \"words words\""));
         }
     }
 
@@ -79,7 +81,7 @@ public class ReindexFailureTests extends ReindexTestCase {
         BulkByScrollResponse response = copy.get();
         assertThat(response, matcher().batches(1).versionConflicts(1).failures(1).created(99));
         for (Failure failure: response.getBulkFailures()) {
-            assertThat(failure.getMessage(), containsString("VersionConflictEngineException[["));
+            assertThat(failure.getMessage(), containsString("VersionConflictEngineException: ["));
         }
     }
 

+ 1 - 0
qa/smoke-test-http/build.gradle

@@ -23,6 +23,7 @@ apply plugin: 'elasticsearch.rest-test'
 apply plugin: 'elasticsearch.test-with-dependencies'
 
 dependencies {
+    testCompile "com.fasterxml.jackson.core:jackson-databind:2.8.11"
     testCompile project(path: ':modules:transport-netty4', configuration: 'runtime') // for http
     testCompile project(path: ':plugins:transport-nio', configuration: 'runtime') // for http
 }

+ 33 - 12
qa/smoke-test-http/src/test/java/org/elasticsearch/http/DetailedErrorsEnabledIT.java

@@ -19,7 +19,8 @@
 
 package org.elasticsearch.http;
 
-import org.apache.http.util.EntityUtils;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import org.elasticsearch.client.Request;
 import org.elasticsearch.client.Response;
 import org.elasticsearch.client.ResponseException;
@@ -27,36 +28,56 @@ import org.elasticsearch.client.ResponseException;
 import java.io.IOException;
 
 import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.startsWith;
 
 /**
  * Tests that by default the error_trace parameter can be used to show stacktraces
  */
 public class DetailedErrorsEnabledIT extends HttpSmokeTestCase {
 
-    public void testThatErrorTraceWorksByDefault() throws IOException {
+    public void testThatErrorTraceCanBeEnabled() throws IOException {
+        ObjectMapper mapper = new ObjectMapper();
+
         try {
             Request request = new Request("DELETE", "/");
             request.addParameter("error_trace", "true");
             getRestClient().performRequest(request);
             fail("request should have failed");
-        } catch(ResponseException e) {
+        } catch (ResponseException e) {
             Response response = e.getResponse();
             assertThat(response.getHeader("Content-Type"), containsString("application/json"));
-            assertThat(EntityUtils.toString(response.getEntity()),
-                    containsString("\"stack_trace\":\"[Validation Failed: 1: index / indices is missing;]; " +
-                    "nested: ActionRequestValidationException[Validation Failed: 1:"));
+
+            JsonNode jsonNode = mapper.readTree(response.getEntity().getContent());
+
+            assertThat(
+                jsonNode.get("error").get("stack_trace").asText(),
+                startsWith("org.elasticsearch.action.ActionRequestValidationException: Validation Failed: 1: index / indices is missing"));
+
+            // An ActionRequestValidationException isn't an ElasticsearchException, so when the code tries
+            // to work out the root cause, all it actually achieves is wrapping the actual exception in
+            // an ElasticsearchException. At least this proves that the root cause logic is executing.
+            assertThat(
+                jsonNode.get("error").get("root_cause").get(0).get("stack_trace").asText(),
+                startsWith("org.elasticsearch.ElasticsearchException$1: Validation Failed: 1: index / indices is missing"));
         }
+    }
+
+    public void testThatErrorTraceDefaultsToDisabled() throws IOException {
 
         try {
             getRestClient().performRequest(new Request("DELETE", "/"));
             fail("request should have failed");
-        } catch(ResponseException e) {
+        } catch (ResponseException e) {
             Response response = e.getResponse();
-            assertThat(response.getHeader("Content-Type"), containsString("application/json; charset=UTF-8"));
-            assertThat(EntityUtils.toString(response.getEntity()),
-                    not(containsString("\"stack_trace\":\"[Validation Failed: 1: index / indices is missing;]; "
-                    + "nested: ActionRequestValidationException[Validation Failed: 1:")));
+            assertThat(response.getHeader("Content-Type"), containsString("application/json"));
+
+            ObjectMapper mapper = new ObjectMapper();
+            JsonNode jsonNode = mapper.readTree(response.getEntity().getContent());
+
+            assertFalse("Unexpected .stack_trace in JSON response", jsonNode.get("error").has("stack_trace"));
+            assertFalse(
+                "Unexpected .error.root_cause[0].stack_trace in JSON response",
+                jsonNode.get("error").get("root_cause").get(0).has("stack_trace"));
         }
     }
 }

+ 1 - 1
server/src/main/java/org/elasticsearch/ElasticsearchException.java

@@ -681,7 +681,7 @@ public class ElasticsearchException extends RuntimeException implements ToXConte
             }
             builder.append(' ');
         }
-        return builder.append(ExceptionsHelper.detailedMessage(this).trim()).toString();
+        return builder.append(super.toString().trim()).toString();
     }
 
     /**

+ 0 - 29
server/src/main/java/org/elasticsearch/ExceptionsHelper.java

@@ -99,35 +99,6 @@ public final class ExceptionsHelper {
         return result;
     }
 
-    /**
-     * @deprecated Don't swallow exceptions, allow them to propagate.
-     */
-    @Deprecated
-    public static String detailedMessage(Throwable t) {
-        if (t == null) {
-            return "Unknown";
-        }
-        if (t.getCause() != null) {
-            StringBuilder sb = new StringBuilder();
-            while (t != null) {
-                sb.append(t.getClass().getSimpleName());
-                if (t.getMessage() != null) {
-                    sb.append("[");
-                    sb.append(t.getMessage());
-                    sb.append("]");
-                }
-                sb.append("; ");
-                t = t.getCause();
-                if (t != null) {
-                    sb.append("nested: ");
-                }
-            }
-            return sb.toString();
-        } else {
-            return t.getClass().getSimpleName() + "[" + t.getMessage() + "]";
-        }
-    }
-
     public static String stackTrace(Throwable e) {
         StringWriter stackTraceStringWriter = new StringWriter();
         PrintWriter printWriter = new PrintWriter(stackTraceStringWriter);

+ 1 - 1
server/src/main/java/org/elasticsearch/action/search/ShardSearchFailure.java

@@ -72,7 +72,7 @@ public class ShardSearchFailure extends ShardOperationFailedException {
     public ShardSearchFailure(Exception e, @Nullable SearchShardTarget shardTarget) {
         super(shardTarget == null ? null : shardTarget.getFullyQualifiedIndexName(),
             shardTarget == null ? -1 : shardTarget.getShardId().getId(),
-            ExceptionsHelper.detailedMessage(e),
+            ExceptionsHelper.stackTrace(e),
             ExceptionsHelper.status(ExceptionsHelper.unwrapCause(e)),
             ExceptionsHelper.unwrapCause(e));
 

+ 3 - 4
server/src/main/java/org/elasticsearch/action/support/DefaultShardOperationFailedException.java

@@ -33,7 +33,6 @@ import org.elasticsearch.rest.RestStatus;
 
 import java.io.IOException;
 
-import static org.elasticsearch.ExceptionsHelper.detailedMessage;
 import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
 
 public class DefaultShardOperationFailedException extends ShardOperationFailedException implements Writeable {
@@ -63,11 +62,11 @@ public class DefaultShardOperationFailedException extends ShardOperationFailedEx
 
     public DefaultShardOperationFailedException(ElasticsearchException e) {
         super(e.getIndex() == null ? null : e.getIndex().getName(), e.getShardId() == null ? -1 : e.getShardId().getId(),
-            detailedMessage(e), e.status(), e);
+            ExceptionsHelper.stackTrace(e), e.status(), e);
     }
 
     public DefaultShardOperationFailedException(String index, int shardId, Throwable cause) {
-        super(index, shardId, detailedMessage(cause), ExceptionsHelper.status(cause), cause);
+        super(index, shardId, ExceptionsHelper.stackTrace(cause), ExceptionsHelper.status(cause), cause);
     }
 
     public static DefaultShardOperationFailedException readShardOperationFailed(StreamInput in) throws IOException {
@@ -79,7 +78,7 @@ public class DefaultShardOperationFailedException extends ShardOperationFailedEx
         f.shardId = in.readVInt();
         f.cause = in.readException();
         f.status = RestStatus.readFrom(in);
-        f.reason = detailedMessage(f.cause);
+        f.reason = ExceptionsHelper.stackTrace(f.cause);
     }
 
     @Override

+ 1 - 1
server/src/main/java/org/elasticsearch/action/support/replication/ReplicationResponse.java

@@ -238,7 +238,7 @@ public class ReplicationResponse extends ActionResponse {
             }
 
             public Failure(ShardId  shardId, @Nullable String nodeId, Exception cause, RestStatus status, boolean primary) {
-                super(shardId.getIndexName(), shardId.getId(), ExceptionsHelper.detailedMessage(cause), status, cause);
+                super(shardId.getIndexName(), shardId.getId(), ExceptionsHelper.stackTrace(cause), status, cause);
                 this.shardId = shardId;
                 this.nodeId = nodeId;
                 this.primary = primary;

+ 2 - 2
server/src/main/java/org/elasticsearch/cluster/action/shard/ShardStateAction.java

@@ -444,10 +444,10 @@ public class ShardStateAction {
             components.add("allocation id [" + allocationId + "]");
             components.add("primary term [" + primaryTerm + "]");
             components.add("message [" + message + "]");
+            components.add("markAsStale [" + markAsStale + "]");
             if (failure != null) {
-                components.add("failure [" + ExceptionsHelper.detailedMessage(failure) + "]");
+                components.add("failure [" + ExceptionsHelper.stackTrace(failure) + "]");
             }
-            components.add("markAsStale [" + markAsStale + "]");
             return String.join(", ", components);
         }
 

+ 1 - 1
server/src/main/java/org/elasticsearch/cluster/routing/UnassignedInfo.java

@@ -337,7 +337,7 @@ public final class UnassignedInfo implements ToXContentFragment, Writeable {
         if (message == null) {
             return null;
         }
-        return message + (failure == null ? "" : ", failure " + ExceptionsHelper.detailedMessage(failure));
+        return message + (failure == null ? "" : ", failure " + ExceptionsHelper.stackTrace(failure));
     }
 
     /**

+ 2 - 2
server/src/main/java/org/elasticsearch/cluster/routing/allocation/FailedShard.java

@@ -42,8 +42,8 @@ public class FailedShard {
 
     @Override
     public String toString() {
-        return "failed shard, shard " + routingEntry + ", message [" + message + "], failure [" +
-                   ExceptionsHelper.detailedMessage(failure) + "], markAsStale [" + markAsStale + "]";
+        return "failed shard, shard " + routingEntry + ", message [" + message + "], markAsStale [" + markAsStale + "], failure ["
+            + ExceptionsHelper.stackTrace(failure) + "]";
     }
 
     /**

+ 3 - 3
server/src/main/java/org/elasticsearch/cluster/routing/allocation/decider/RestoreInProgressAllocationDecider.java

@@ -59,10 +59,10 @@ public class RestoreInProgressAllocationDecider extends AllocationDecider {
                 }
             }
         }
-        return allocation.decision(Decision.NO, NAME, "shard has failed to be restored from the snapshot [%s] because of [%s] - " +
+        return allocation.decision(Decision.NO, NAME, "shard has failed to be restored from the snapshot [%s] - " +
             "manually close or delete the index [%s] in order to retry to restore the snapshot again or use the reroute API to force the " +
-            "allocation of an empty primary shard",
-            source.snapshot(), shardRouting.unassignedInfo().getDetails(), shardRouting.getIndexName());
+            "allocation of an empty primary shard. Details: [%s]",
+            source.snapshot(), shardRouting.getIndexName(), shardRouting.unassignedInfo().getDetails());
     }
 
     @Override

+ 1 - 1
server/src/main/java/org/elasticsearch/index/query/MoreLikeThisQueryBuilder.java

@@ -443,7 +443,7 @@ public class MoreLikeThisQueryBuilder extends AbstractQueryBuilder<MoreLikeThisQ
                 toXContent(builder, EMPTY_PARAMS);
                 return Strings.toString(builder);
             } catch (Exception e) {
-                return "{ \"error\" : \"" + ExceptionsHelper.detailedMessage(e) + "\"}";
+                throw new RuntimeException(e);
             }
         }
 

+ 1 - 2
server/src/main/java/org/elasticsearch/index/shard/StoreRecovery.java

@@ -367,8 +367,7 @@ final class StoreRecovery {
                     try {
                         files = Arrays.toString(store.directory().listAll());
                     } catch (Exception inner) {
-                        inner.addSuppressed(e);
-                        files += " (failure=" + ExceptionsHelper.detailedMessage(inner) + ")";
+                        files += " (failure=" + ExceptionsHelper.stackTrace(inner) + ")";
                     }
                     if (indexShouldExists) {
                         throw new IndexShardRecoveryException(shardId,

+ 4 - 0
server/src/main/java/org/elasticsearch/repositories/VerificationFailure.java

@@ -34,4 +34,8 @@ public class VerificationFailure {
     public String toString() {
         return "[" + nodeId + ", '" + cause + "']";
     }
+
+    public Exception getCause() {
+        return cause;
+    }
 }

+ 5 - 1
server/src/main/java/org/elasticsearch/repositories/VerifyNodeRepositoryAction.java

@@ -115,7 +115,11 @@ public class VerifyNodeRepositoryAction {
     private static void finishVerification(String repositoryName, ActionListener<List<DiscoveryNode>> listener, List<DiscoveryNode> nodes,
                                    CopyOnWriteArrayList<VerificationFailure> errors) {
         if (errors.isEmpty() == false) {
-            listener.onFailure(new RepositoryVerificationException(repositoryName, errors.toString()));
+            RepositoryVerificationException e = new RepositoryVerificationException(repositoryName, errors.toString());
+            for (VerificationFailure error : errors) {
+                e.addSuppressed(error.getCause());
+            }
+            listener.onFailure(e);
         } else {
             listener.onResponse(nodes);
         }

+ 1 - 1
server/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java

@@ -929,7 +929,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
         final long startTime = threadPool.absoluteTimeInMillis();
         final StepListener<Void> snapshotDoneListener = new StepListener<>();
         snapshotDoneListener.whenComplete(listener::onResponse, e -> {
-            snapshotStatus.moveToFailed(threadPool.absoluteTimeInMillis(), ExceptionsHelper.detailedMessage(e));
+            snapshotStatus.moveToFailed(threadPool.absoluteTimeInMillis(), ExceptionsHelper.stackTrace(e));
             listener.onFailure(e instanceof IndexShardSnapshotFailedException ? (IndexShardSnapshotFailedException) e
                 : new IndexShardSnapshotFailedException(store.shardId(), e));
         });

+ 1 - 2
server/src/main/java/org/elasticsearch/search/suggest/phrase/DirectCandidateGeneratorBuilder.java

@@ -27,7 +27,6 @@ import org.apache.lucene.search.spell.NGramDistance;
 import org.apache.lucene.search.spell.StringDistance;
 import org.apache.lucene.search.spell.SuggestMode;
 import org.apache.lucene.util.automaton.LevenshteinAutomata;
-import org.elasticsearch.ExceptionsHelper;
 import org.elasticsearch.common.ParseField;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.io.stream.StreamInput;
@@ -490,7 +489,7 @@ public final class DirectCandidateGeneratorBuilder implements CandidateGenerator
             toXContent(builder, EMPTY_PARAMS);
             return Strings.toString(builder);
         } catch (Exception e) {
-            return "{ \"error\" : \"" + ExceptionsHelper.detailedMessage(e) + "\"}";
+            throw new RuntimeException(e);
         }
     }
 

+ 1 - 1
server/src/main/java/org/elasticsearch/snapshots/SnapshotShardsService.java

@@ -308,7 +308,7 @@ public class SnapshotShardsService extends AbstractLifecycleComponent implements
                     @Override
                     public void onFailure(Exception e) {
                         logger.warn(() -> new ParameterizedMessage("[{}][{}] failed to snapshot shard", shardId, snapshot), e);
-                        notifyFailedSnapshotShard(snapshot, shardId, ExceptionsHelper.detailedMessage(e));
+                        notifyFailedSnapshotShard(snapshot, shardId, ExceptionsHelper.stackTrace(e));
                     }
                 });
             }

+ 1 - 1
server/src/main/java/org/elasticsearch/snapshots/SnapshotsService.java

@@ -567,7 +567,7 @@ public class SnapshotsService extends AbstractLifecycleComponent implements Clus
                             .finalizeSnapshot(snapshot.snapshot().getSnapshotId(),
                                 snapshot.indices(),
                                 snapshot.startTime(),
-                                ExceptionsHelper.detailedMessage(exception),
+                                ExceptionsHelper.stackTrace(exception),
                                 0,
                                 Collections.emptyList(),
                                 snapshot.getRepositoryStateId(),

+ 2 - 2
server/src/main/java/org/elasticsearch/tasks/TaskManager.java

@@ -226,7 +226,7 @@ public class TaskManager implements ClusterStateApplier {
         try {
             taskResult = task.result(localNode, error);
         } catch (IOException ex) {
-            logger.warn(() -> new ParameterizedMessage("couldn't store error {}", ExceptionsHelper.detailedMessage(error)), ex);
+            logger.warn(() -> new ParameterizedMessage("couldn't store error {}", ExceptionsHelper.stackTrace(error)), ex);
             listener.onFailure(ex);
             return;
         }
@@ -238,7 +238,7 @@ public class TaskManager implements ClusterStateApplier {
 
             @Override
             public void onFailure(Exception e) {
-                logger.warn(() -> new ParameterizedMessage("couldn't store error {}", ExceptionsHelper.detailedMessage(error)), e);
+                logger.warn(() -> new ParameterizedMessage("couldn't store error {}", ExceptionsHelper.stackTrace(error)), e);
                 listener.onFailure(e);
             }
         });

+ 9 - 2
server/src/test/java/org/elasticsearch/ElasticsearchExceptionTests.java

@@ -274,8 +274,15 @@ public class ElasticsearchExceptionTests extends ESTestCase {
     public void testToString() {
         ElasticsearchException exception = new ElasticsearchException("foo", new ElasticsearchException("bar",
                 new IllegalArgumentException("index is closed", new RuntimeException("foobar"))));
-        assertEquals("ElasticsearchException[foo]; nested: ElasticsearchException[bar]; nested: IllegalArgumentException" +
-                "[index is closed]; nested: RuntimeException[foobar];", exception.toString());
+        assertThat(exception.toString(), equalTo("org.elasticsearch.ElasticsearchException: foo"));
+    }
+
+    public void testGetDetailedMessage() {
+        ElasticsearchException exception = new ElasticsearchException("foo", new ElasticsearchException("bar",
+            new IllegalArgumentException("index is closed", new RuntimeException("foobar"))));
+        assertThat(exception.getDetailedMessage(),
+            equalTo("org.elasticsearch.ElasticsearchException: foo; org.elasticsearch.ElasticsearchException: bar; " +
+                "java.lang.IllegalArgumentException: index is closed"));
     }
 
     public void testToXContent() throws IOException {

+ 12 - 1
server/src/test/java/org/elasticsearch/action/admin/indices/close/CloseIndexResponseTests.java

@@ -42,6 +42,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import static org.elasticsearch.test.VersionUtils.getPreviousVersion;
 import static org.elasticsearch.test.VersionUtils.randomVersionBetween;
@@ -113,7 +114,17 @@ public class CloseIndexResponseTests extends AbstractWireSerializingTestCase<Clo
                             assertThat(actualFailure.getNodeId(), equalTo(expectedFailure.getNodeId()));
                             assertThat(actualFailure.index(), equalTo(expectedFailure.index()));
                             assertThat(actualFailure.shardId(), equalTo(expectedFailure.shardId()));
-                            assertThat(actualFailure.reason(), equalTo(expectedFailure.reason()));
+
+                            // Serialising and deserialising an exception seems to remove the "java.base/" part from the stack trace
+                            // in the `reason` property, so we don't compare it directly. Instead, check that the first lines match,
+                            // and that the stack trace has the same number of lines.
+                            List<String> expectedReasonLines = expectedFailure.reason().lines().collect(Collectors.toList());
+                            List<String> actualReasonLines = actualFailure.reason().lines().collect(Collectors.toList());
+                            assertThat(actualReasonLines.get(0), equalTo(expectedReasonLines.get(0)));
+                            assertThat("Exceptions have a different number of lines",
+                                actualReasonLines,
+                                hasSize(expectedReasonLines.size()));
+
                             assertThat(actualFailure.getCause().getMessage(), equalTo(expectedFailure.getCause().getMessage()));
                             assertThat(actualFailure.getCause().getClass(), equalTo(expectedFailure.getCause().getClass()));
                             assertArrayEquals(actualFailure.getCause().getStackTrace(), expectedFailure.getCause().getStackTrace());

+ 6 - 2
server/src/test/java/org/elasticsearch/action/bulk/BulkProcessorClusterSettingsIT.java

@@ -25,9 +25,11 @@ import org.elasticsearch.test.ESIntegTestCase;
 import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
 import org.elasticsearch.test.ESIntegTestCase.Scope;
 
+import static org.hamcrest.Matchers.equalTo;
+
 @ClusterScope(scope = Scope.TEST, numDataNodes = 0)
 public class BulkProcessorClusterSettingsIT extends ESIntegTestCase {
-    public void testBulkProcessorAutoCreateRestrictions() throws Exception {
+    public void testBulkProcessorAutoCreateRestrictions() {
         // See issue #8125
         Settings settings = Settings.builder().put("action.auto_create_index", false).build();
 
@@ -45,7 +47,9 @@ public class BulkProcessorClusterSettingsIT extends ESIntegTestCase {
         assertEquals(3, responses.length);
         assertFalse("Operation on existing index should succeed", responses[0].isFailed());
         assertTrue("Missing index should have been flagged", responses[1].isFailed());
-        assertEquals("[wontwork] IndexNotFoundException[no such index [wontwork]]", responses[1].getFailureMessage());
+        assertThat(
+            responses[1].getFailureMessage(),
+            equalTo("[wontwork] org.elasticsearch.index.IndexNotFoundException: no such index [wontwork]"));
         assertFalse("Operation on existing index should succeed", responses[2].isFailed());
     }
 }

+ 7 - 3
server/src/test/java/org/elasticsearch/action/search/SearchScrollAsyncActionTests.java

@@ -40,6 +40,9 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BiFunction;
 
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.startsWith;
+
 public class SearchScrollAsyncActionTests extends ESTestCase {
 
     public void testSendRequestsToNodes() throws InterruptedException {
@@ -267,7 +270,8 @@ public class SearchScrollAsyncActionTests extends ESTestCase {
         latch.await();
         ShardSearchFailure[] shardSearchFailures = action.buildShardFailures();
         assertEquals(1, shardSearchFailures.length);
-        assertEquals("IllegalStateException[node [node2] is not available]", shardSearchFailures[0].reason());
+        // .reason() returns the full stack trace
+        assertThat(shardSearchFailures[0].reason(), startsWith("java.lang.IllegalStateException: node [node2] is not available"));
 
         ScrollIdForNode[] context = scrollId.getContext();
         for (int i = 0; i < results.length(); i++) {
@@ -345,7 +349,7 @@ public class SearchScrollAsyncActionTests extends ESTestCase {
         latch.await();
         ShardSearchFailure[] shardSearchFailures = action.buildShardFailures();
         assertEquals(1, shardSearchFailures.length);
-        assertEquals("IllegalArgumentException[BOOM on shard]", shardSearchFailures[0].reason());
+        assertThat(shardSearchFailures[0].reason(), containsString("IllegalArgumentException: BOOM on shard"));
 
         ScrollIdForNode[] context = scrollId.getContext();
         for (int i = 0; i < results.length(); i++) {
@@ -431,7 +435,7 @@ public class SearchScrollAsyncActionTests extends ESTestCase {
 
         ShardSearchFailure[] shardSearchFailures = action.buildShardFailures();
         assertEquals(context.length, shardSearchFailures.length);
-        assertEquals("IllegalArgumentException[BOOM on shard]", shardSearchFailures[0].reason());
+        assertThat(shardSearchFailures[0].reason(), containsString("IllegalArgumentException: BOOM on shard"));
 
         for (int i = 0; i < results.length(); i++) {
             assertNull(results.get(i));

+ 22 - 5
server/src/test/java/org/elasticsearch/action/support/DefaultShardOperationFailedExceptionTests.java

@@ -43,9 +43,13 @@ import org.elasticsearch.test.ESTestCase;
 import java.io.EOFException;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.util.List;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.startsWith;
 
 public class DefaultShardOperationFailedExceptionTests extends ESTestCase {
 
@@ -53,19 +57,22 @@ public class DefaultShardOperationFailedExceptionTests extends ESTestCase {
         {
             DefaultShardOperationFailedException exception = new DefaultShardOperationFailedException(
                 new ElasticsearchException("foo", new IllegalArgumentException("bar", new RuntimeException("baz"))));
-            assertEquals("[null][-1] failed, reason [ElasticsearchException[foo]; nested: " +
-                "IllegalArgumentException[bar]; nested: RuntimeException[baz]; ]", exception.toString());
+            assertThat(exception.toString(), startsWith("[null][-1] failed, reason [org.elasticsearch.ElasticsearchException: foo"));
         }
+
         {
             ElasticsearchException elasticsearchException = new ElasticsearchException("foo");
             elasticsearchException.setIndex(new Index("index1", "_na_"));
             elasticsearchException.setShard(new ShardId("index1", "_na_", 1));
             DefaultShardOperationFailedException exception = new DefaultShardOperationFailedException(elasticsearchException);
-            assertEquals("[index1][1] failed, reason [ElasticsearchException[foo]]", exception.toString());
+            assertThat(
+                exception.toString(),
+                startsWith("[index1][1] failed, reason [[index1][[index1][1]] org.elasticsearch.ElasticsearchException: foo"));
         }
+
         {
             DefaultShardOperationFailedException exception = new DefaultShardOperationFailedException("index2", 2, new Exception("foo"));
-            assertEquals("[index2][2] failed, reason [Exception[foo]]", exception.toString());
+            assertThat(exception.toString(), startsWith("[index2][2] failed, reason [java.lang.Exception: foo"));
         }
     }
 
@@ -134,7 +141,17 @@ public class DefaultShardOperationFailedExceptionTests extends ESTestCase {
                 assertNotSame(exception, deserializedException);
                 assertThat(deserializedException.index(), equalTo(exception.index()));
                 assertThat(deserializedException.shardId(), equalTo(exception.shardId()));
-                assertThat(deserializedException.reason(), equalTo(exception.reason()));
+
+                // Serialising and deserialising an exception seems to remove the "java.base/" part from the stack trace
+                // in the `reason` property, so we don't compare it directly. Instead, check that the first lines match,
+                // and that the stack trace has the same number of lines.
+                List<String> expectedReasonLines = exception.reason().lines().collect(Collectors.toList());
+                List<String> actualReasonLines = deserializedException.reason().lines().collect(Collectors.toList());
+                assertThat(actualReasonLines.get(0), equalTo(expectedReasonLines.get(0)));
+                assertThat("Exceptions have a different number of lines",
+                    actualReasonLines,
+                    hasSize(expectedReasonLines.size()));
+
                 assertThat(deserializedException.getCause().getMessage(), equalTo(exception.getCause().getMessage()));
                 assertThat(deserializedException.getCause().getClass(), equalTo(exception.getCause().getClass()));
                 assertArrayEquals(deserializedException.getCause().getStackTrace(), exception.getCause().getStackTrace());

+ 1 - 1
server/src/test/java/org/elasticsearch/action/support/broadcast/node/TransportBroadcastByNodeActionTests.java

@@ -397,7 +397,7 @@ public class TransportBroadcastByNodeActionTests extends ESTestCase {
         List<BroadcastShardOperationFailedException> exceptions = nodeResponse.getExceptions();
         for (BroadcastShardOperationFailedException exception : exceptions) {
             assertThat(exception.getMessage(), is("operation indices:admin/test failed"));
-            assertThat(exception, hasToString(containsString("operation failed")));
+            assertThat(exception.getCause(), hasToString(containsString("operation failed")));
         }
     }
 

+ 8 - 6
server/src/test/java/org/elasticsearch/cluster/routing/allocation/decider/RestoreInProgressAllocationDeciderTests.java

@@ -48,6 +48,8 @@ import java.io.IOException;
 import java.util.Collections;
 
 import static java.util.Collections.singletonList;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.startsWith;
 
 /**
  * Test {@link RestoreInProgressAllocationDecider}
@@ -86,9 +88,9 @@ public class RestoreInProgressAllocationDeciderTests extends ESAllocationTestCas
 
         final Decision decision = executeAllocation(clusterState, primary);
         assertEquals(Decision.Type.NO, decision.type());
-        assertEquals("shard has failed to be restored from the snapshot [_repository:_missing/_uuid] because of " +
-            "[restore_source[_repository/_missing]] - manually close or delete the index [test] in order to retry to restore " +
-            "the snapshot again or use the reroute API to force the allocation of an empty primary shard", decision.getExplanation());
+        assertThat(decision.getExplanation(), equalTo("shard has failed to be restored from the snapshot " +
+            "[_repository:_missing/_uuid] - manually close or delete the index [test] in order to retry to restore the snapshot again " +
+            "or use the reroute API to force the allocation of an empty primary shard. Details: [restore_source[_repository/_missing]]"));
     }
 
     public void testCanAllocatePrimaryExistingInRestoreInProgress() {
@@ -152,10 +154,10 @@ public class RestoreInProgressAllocationDeciderTests extends ESAllocationTestCas
         Decision decision = executeAllocation(clusterState, primary);
         if (shardState == RestoreInProgress.State.FAILURE) {
             assertEquals(Decision.Type.NO, decision.type());
-            assertEquals("shard has failed to be restored from the snapshot [_repository:_existing/_uuid] because of " +
-                "[restore_source[_repository/_existing], failure IOException[i/o failure]] - manually close or delete the index " +
+            assertThat(decision.getExplanation(), startsWith("shard has failed to be restored from the snapshot " +
+                "[_repository:_existing/_uuid] - manually close or delete the index " +
                 "[test] in order to retry to restore the snapshot again or use the reroute API to force the allocation of " +
-                "an empty primary shard", decision.getExplanation());
+                "an empty primary shard. Details: [restore_source[_repository/_existing], failure java.io.IOException: i/o failure"));
         } else {
             assertEquals(Decision.Type.YES, decision.type());
             assertEquals("shard is currently being restored", decision.getExplanation());

+ 7 - 4
server/src/test/java/org/elasticsearch/env/OverrideNodeVersionCommandTests.java

@@ -19,6 +19,7 @@
 package org.elasticsearch.env;
 
 import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.ExceptionsHelper;
 import org.elasticsearch.Version;
 import org.elasticsearch.cli.MockTerminal;
 import org.elasticsearch.common.settings.Settings;
@@ -37,7 +38,6 @@ import static org.elasticsearch.env.NodeMetaData.NODE_VERSION_KEY;
 import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.hasToString;
 
 public class OverrideNodeVersionCommandTests extends ESTestCase {
 
@@ -163,9 +163,12 @@ public class OverrideNodeVersionCommandTests extends ESTestCase {
         final String nodeId = randomAlphaOfLength(10);
         final Version nodeVersion = NodeMetaDataTests.tooNewVersion();
         FutureNodeMetaData.FORMAT.writeAndCleanup(new FutureNodeMetaData(nodeId, nodeVersion, randomLong()), nodePaths);
-        assertThat(expectThrows(ElasticsearchException.class,
-            () -> NodeMetaData.FORMAT.loadLatestState(logger, xContentRegistry(), nodePaths)),
-            hasToString(containsString("unknown field [future_field]")));
+        try {
+            NodeMetaData.FORMAT.loadLatestState(logger, xContentRegistry(), nodePaths);
+            fail("An exception should have been thrown");
+        } catch (ElasticsearchException e) {
+            assertThat(ExceptionsHelper.stackTrace(e), containsString("unknown field [future_field]"));
+        }
 
         final MockTerminal mockTerminal = new MockTerminal();
         mockTerminal.addTextInput(randomFrom("y", "Y"));

+ 8 - 10
server/src/test/java/org/elasticsearch/indices/memory/breaker/CircuitBreakerServiceIT.java

@@ -247,10 +247,10 @@ public class CircuitBreakerServiceIT extends ESIntegTestCase {
             client.prepareSearch("cb-test").setQuery(matchAllQuery()).addAggregation(cardinality("card").field("test")).get();
             fail("aggregation should have tripped the breaker");
         } catch (Exception e) {
-            String errMsg = "CircuitBreakingException[[request] Data too large";
-            assertThat("Exception: [" + e.toString() + "] should contain a CircuitBreakingException", e.toString(), containsString(errMsg));
-            errMsg = "which is larger than the limit of [10/10b]]";
-            assertThat("Exception: [" + e.toString() + "] should contain a CircuitBreakingException", e.toString(), containsString(errMsg));
+            Throwable cause = e.getCause();
+            assertThat("Exception cause should be a CircuitBreakingException", cause, instanceOf(CircuitBreakingException.class));
+            assertThat(cause.toString(), containsString("Data too large"));
+            assertThat(cause.toString(), containsString("which is larger than the limit of [10/10b]"));
         }
     }
 
@@ -285,12 +285,10 @@ public class CircuitBreakerServiceIT extends ESIntegTestCase {
             assertTrue("there should be shard failures", resp.getFailedShards() > 0);
             fail("aggregation should have tripped the breaker");
         } catch (Exception e) {
-            String errMsg = "CircuitBreakingException[[request] Data too large, data for [<agg [my_terms]>] would be";
-            assertThat("Exception: [" + e.toString() + "] should contain a CircuitBreakingException",
-                    e.toString(), containsString(errMsg));
-            errMsg = "which is larger than the limit of [100/100b]]";
-            assertThat("Exception: [" + e.toString() + "] should contain a CircuitBreakingException",
-                    e.toString(), containsString(errMsg));
+            Throwable cause = e.getCause();
+            assertThat(cause, instanceOf(CircuitBreakingException.class));
+            assertThat(cause.toString(), containsString("[request] Data too large, data for [<agg [my_terms]>] would be"));
+            assertThat(cause.toString(), containsString("which is larger than the limit of [100/100b]"));
         }
     }
 

+ 2 - 2
server/src/test/java/org/elasticsearch/rest/BytesRestResponseTests.java

@@ -117,7 +117,7 @@ public class BytesRestResponseTests extends ESTestCase {
         String text = response.content().utf8ToString();
         assertThat(text, containsString("\"type\":\"unknown_exception\",\"reason\":\"an error occurred reading data\""));
         assertThat(text, containsString("{\"type\":\"file_not_found_exception\""));
-        assertThat(text, containsString("\"stack_trace\":\"[an error occurred reading data]"));
+        assertThat(text, containsString("\"stack_trace\":\"org.elasticsearch.ElasticsearchException$1: an error occurred reading data"));
     }
 
     public void testGuessRootCause() throws IOException {
@@ -164,7 +164,7 @@ public class BytesRestResponseTests extends ESTestCase {
             "\"reason\":\"foobar\",\"line\":1,\"col\":2}}]},\"status\":400}";
         assertEquals(expected.trim(), text.trim());
         String stackTrace = ExceptionsHelper.stackTrace(ex);
-        assertTrue(stackTrace.contains("Caused by: ParsingException[foobar]"));
+        assertThat(stackTrace, containsString("org.elasticsearch.common.ParsingException: foobar"));
     }
 
     public void testResponseWhenPathContainsEncodingError() throws IOException {

+ 1 - 1
server/src/test/java/org/elasticsearch/search/aggregations/bucket/geogrid/GeoTileGridParserTests.java

@@ -54,7 +54,7 @@ public class GeoTileGridParserTests extends ESTestCase {
         assertSame(XContentParser.Token.START_OBJECT, token);
         XContentParseException e = expectThrows(XContentParseException.class,
                 () -> GeoTileGridAggregationBuilder.parse("geotile_grid", stParser));
-        assertThat(ExceptionsHelper.detailedMessage(e),
+        assertThat(ExceptionsHelper.stackTrace(e),
                 containsString("[geotile_grid] precision doesn't support values of type: VALUE_BOOLEAN"));
     }
 

+ 21 - 3
server/src/test/java/org/elasticsearch/search/query/SearchQueryIT.java

@@ -25,6 +25,7 @@ import org.elasticsearch.action.index.IndexRequestBuilder;
 import org.elasticsearch.action.search.SearchPhaseExecutionException;
 import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.action.search.SearchType;
+import org.elasticsearch.action.search.ShardSearchFailure;
 import org.elasticsearch.bootstrap.JavaVersion;
 import org.elasticsearch.common.document.DocumentField;
 import org.elasticsearch.common.settings.Settings;
@@ -53,6 +54,7 @@ import org.elasticsearch.search.aggregations.AggregationBuilders;
 import org.elasticsearch.test.ESIntegTestCase;
 import org.elasticsearch.test.InternalSettingsPlugin;
 import org.elasticsearch.test.junit.annotations.TestIssueLogging;
+import org.hamcrest.Matcher;
 
 import java.io.IOException;
 import java.time.Instant;
@@ -105,8 +107,10 @@ import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.hasId;
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.hasScore;
 import static org.hamcrest.Matchers.closeTo;
 import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.emptyArray;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.not;
 
 public class SearchQueryIT extends ESIntegTestCase {
 
@@ -626,9 +630,23 @@ public class SearchQueryIT extends ESIntegTestCase {
 
         builder = multiMatchQuery("value1", "field1", "field2", "field4");
 
-        assertFailures(client().prepareSearch().setQuery(builder),
-                RestStatus.BAD_REQUEST,
-                containsString("NumberFormatException[For input string: \"value1\"]"));
+        //when the number for shards is randomized and we expect failures
+        //we can either run into partial or total failures depending on the current number of shards
+        Matcher<String> reasonMatcher = containsString("NumberFormatException: For input string: \"value1\"");
+        ShardSearchFailure[] shardFailures;
+        try {
+            client().prepareSearch().setQuery(builder).get();
+            shardFailures = searchResponse.getShardFailures();
+            assertThat("Expected shard failures, got none", shardFailures, not(emptyArray()));
+        } catch (SearchPhaseExecutionException e) {
+            assertThat(e.status(), equalTo(RestStatus.BAD_REQUEST));
+            shardFailures = e.shardFailures();
+        }
+
+        for (ShardSearchFailure shardSearchFailure : shardFailures) {
+            assertThat(shardSearchFailure.status(), equalTo(RestStatus.BAD_REQUEST));
+            assertThat(shardSearchFailure.reason(), reasonMatcher);
+        }
 
         builder.lenient(true);
         searchResponse = client().prepareSearch().setQuery(builder).get();

+ 1 - 1
server/src/test/java/org/elasticsearch/search/query/SimpleQueryStringIT.java

@@ -567,7 +567,7 @@ public class SimpleQueryStringIT extends ESIntegTestCase {
         SearchPhaseExecutionException e = expectThrows(SearchPhaseExecutionException.class, () ->
                 client().prepareSearch("test").setQuery(
                         simpleQueryStringQuery("foo123").lenient(false)).get());
-        assertThat(e.getDetailedMessage(), containsString("NumberFormatException[For input string: \"foo123\"]"));
+        assertThat(e.getDetailedMessage(), containsString("NumberFormatException: For input string: \"foo123\""));
     }
 
     public void testLimitOnExpandedFields() throws Exception {

+ 1 - 1
server/src/test/java/org/elasticsearch/search/sort/FieldSortIT.java

@@ -894,7 +894,7 @@ public class FieldSortIT extends ESIntegTestCase {
         } catch (SearchPhaseExecutionException e) {
             //we check that it's a parse failure rather than a different shard failure
             for (ShardSearchFailure shardSearchFailure : e.shardFailures()) {
-                assertThat(shardSearchFailure.toString(), containsString("[No mapping found for [kkk] in order to sort on]"));
+                assertThat(shardSearchFailure.getCause().toString(), containsString("No mapping found for [kkk] in order to sort on"));
             }
         }
 

+ 3 - 3
server/src/test/java/org/elasticsearch/search/suggest/CompletionSuggestSearchIT.java

@@ -406,7 +406,7 @@ public class CompletionSuggestSearchIT extends ESIntegTestCase {
                 .startArray("input").value("sth").endArray()
                 .field("weight", 2.5)
                 .endObject().endObject()).get());
-        assertThat(e.toString(), containsString("2.5"));
+        assertThat(e.getCause().getMessage(), equalTo("weight must be an integer, but was [2.5]"));
     }
 
     public void testThatWeightCanBeAString() throws Exception {
@@ -446,7 +446,7 @@ public class CompletionSuggestSearchIT extends ESIntegTestCase {
                 .field("weight", "thisIsNotValid")
                 .endObject().endObject()
             ).get());
-        assertThat(e.toString(), containsString("thisIsNotValid"));
+        assertThat(e.getCause().toString(), containsString("thisIsNotValid"));
     }
 
     public void testThatWeightAsStringMustBeInt() throws Exception {
@@ -460,7 +460,7 @@ public class CompletionSuggestSearchIT extends ESIntegTestCase {
                 .startArray("input").value("testing").endArray()
                 .field("weight", weight)
                 .endObject().endObject()).get());
-        assertThat(e.toString(), containsString(weight));
+        assertThat(e.getCause().toString(), containsString(weight));
     }
 
     public void testThatInputCanBeAStringInsteadOfAnArray() throws Exception {

+ 9 - 7
server/src/test/java/org/elasticsearch/snapshots/RepositoriesIT.java

@@ -18,6 +18,7 @@
  */
 package org.elasticsearch.snapshots;
 
+import org.elasticsearch.ExceptionsHelper;
 import org.elasticsearch.action.admin.cluster.repositories.get.GetRepositoriesResponse;
 import org.elasticsearch.action.admin.cluster.repositories.verify.VerifyRepositoryResponse;
 import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
@@ -128,7 +129,7 @@ public class RepositoriesIT extends AbstractSnapshotIntegTestCase {
         return null;
     }
 
-    public void testMisconfiguredRepository() throws Exception {
+    public void testMisconfiguredRepository() {
         Client client = client();
 
         logger.info("--> trying creating repository with incorrect settings");
@@ -136,7 +137,7 @@ public class RepositoriesIT extends AbstractSnapshotIntegTestCase {
             client.admin().cluster().preparePutRepository("test-repo").setType("fs").get();
             fail("Shouldn't be here");
         } catch (RepositoryException ex) {
-            assertThat(ex.toString(), containsString("missing location"));
+            assertThat(ex.getCause().getMessage(), equalTo("[test-repo] missing location"));
         }
 
         logger.info("--> trying creating fs repository with location that is not registered in path.repo setting");
@@ -148,12 +149,13 @@ public class RepositoriesIT extends AbstractSnapshotIntegTestCase {
                     .get();
             fail("Shouldn't be here");
         } catch (RepositoryException ex) {
-            assertThat(ex.toString(), containsString("location [" + location + "] doesn't match any of the locations specified " +
-                "by path.repo"));
+            assertThat(
+                ex.getCause().getMessage(),
+                containsString("location [" + location + "] doesn't match any of the locations specified by path.repo"));
         }
     }
 
-    public void testRepositoryAckTimeout() throws Exception {
+    public void testRepositoryAckTimeout() {
         logger.info("-->  creating repository test-repo-1 with 0s timeout - shouldn't ack");
         AcknowledgedResponse putRepositoryResponse = client().admin().cluster().preparePutRepository("test-repo-1")
                 .setType("fs").setSettings(Settings.builder()
@@ -183,7 +185,7 @@ public class RepositoriesIT extends AbstractSnapshotIntegTestCase {
         assertThat(deleteRepositoryResponse.isAcknowledged(), equalTo(true));
     }
 
-    public void testRepositoryVerification() throws Exception {
+    public void testRepositoryVerification() {
         disableRepoConsistencyCheck("This test does not create any data in the repository.");
 
         Client client = client();
@@ -229,7 +231,7 @@ public class RepositoriesIT extends AbstractSnapshotIntegTestCase {
                     ).get();
             fail("RepositoryVerificationException wasn't generated");
         } catch (RepositoryVerificationException ex) {
-            assertThat(ex.getMessage(), containsString("is not shared"));
+            assertThat(ExceptionsHelper.stackTrace(ex), containsString("is not shared"));
         }
     }
 }

+ 1 - 1
server/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java

@@ -3404,7 +3404,7 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas
         SnapshotInfo snapshotInfo = waitForCompletion(repo, snapshot, TimeValue.timeValueSeconds(60));
         assertEquals(1, snapshotInfo.shardFailures().size());
         assertEquals(0, snapshotInfo.shardFailures().get(0).shardId());
-        assertEquals("IndexShardSnapshotFailedException[Aborted]", snapshotInfo.shardFailures().get(0).reason());
+        assertThat(snapshotInfo.shardFailures().get(0).reason(), containsString("IndexShardSnapshotFailedException: Aborted"));
     }
 
     public void testSnapshotSucceedsAfterSnapshotFailure() throws Exception {

+ 2 - 2
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/token/InvalidateTokenResponseTests.java

@@ -39,8 +39,8 @@ public class InvalidateTokenResponseTests extends ESTestCase {
                 assertThat(serialized.getResult().getPreviouslyInvalidatedTokens(),
                     equalTo(response.getResult().getPreviouslyInvalidatedTokens()));
                 assertThat(serialized.getResult().getErrors().size(), equalTo(response.getResult().getErrors().size()));
-                assertThat(serialized.getResult().getErrors().get(0).toString(), containsString("this is an error message"));
-                assertThat(serialized.getResult().getErrors().get(1).toString(), containsString("this is an error message2"));
+                assertThat(serialized.getResult().getErrors().get(0).getCause().getMessage(), containsString("this is an error message"));
+                assertThat(serialized.getResult().getErrors().get(1).getCause().getMessage(), containsString("this is an error message2"));
             }
         }
 

+ 1 - 1
x-pack/plugin/monitoring/src/test/java/org/elasticsearch/xpack/monitoring/action/TransportMonitoringBulkActionTests.java

@@ -120,7 +120,7 @@ public class TransportMonitoringBulkActionTests extends ESTestCase {
         final MonitoringBulkRequest request = randomRequest();
         final ClusterBlockException e = expectThrows(ClusterBlockException.class, () -> ActionTestUtils.executeBlocking(action, request));
 
-        assertThat(e, hasToString(containsString("ClusterBlockException[blocked by: [SERVICE_UNAVAILABLE/2/no master]")));
+        assertThat(e, hasToString(containsString("ClusterBlockException: blocked by: [SERVICE_UNAVAILABLE/2/no master]")));
     }
 
     public void testExecuteIgnoresRequestWhenCollectionIsDisabled() throws Exception {

+ 2 - 2
x-pack/plugin/security/src/test/java/org/elasticsearch/integration/SecurityCachePermissionTests.java

@@ -22,7 +22,7 @@ import static org.hamcrest.Matchers.is;
 public class SecurityCachePermissionTests extends SecurityIntegTestCase {
 
     private final String READ_ONE_IDX_USER = "read_user";
-    
+
     @Override
     public String configUsers() {
         return super.configUsers()
@@ -68,7 +68,7 @@ public class SecurityCachePermissionTests extends SecurityIntegTestCase {
                     .execute().actionGet();
             fail("search phase exception should have been thrown! response was:\n" + response.toString());
         } catch (ElasticsearchSecurityException e) {
-            assertThat(e.toString(), containsString("ElasticsearchSecurityException[action"));
+            assertThat(e.toString(), containsString("ElasticsearchSecurityException: action"));
             assertThat(e.toString(), containsString("unauthorized"));
         }
     }

+ 1 - 1
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/ReadActionsTests.java

@@ -277,7 +277,7 @@ public class ReadActionsTests extends SecurityIntegTestCase {
             assertReturnedIndices(multiSearchResponse.getResponses()[0].getResponse(), "test1", "test2", "test3");
             assertTrue(multiSearchResponse.getResponses()[1].isFailure());
             assertThat(multiSearchResponse.getResponses()[1].getFailure().toString(),
-                    equalTo("[test4] IndexNotFoundException[no such index [test4]]"));
+                    equalTo("[test4] org.elasticsearch.index.IndexNotFoundException: no such index [test4]"));
         }
         {
             //we set ignore_unavailable and allow_no_indices to true, no errors returned, second item doesn't have hits.