From 60c2ad597ab44610298e94987582443368863269 Mon Sep 17 00:00:00 2001 From: kaisun2000 <52840222+kaisun2000@users.noreply.github.com> Date: Fri, 27 Oct 2023 01:50:19 -0700 Subject: [PATCH] Enhance json parser error logging to better track Istio Proxy error message (#15176) Currently the inter Druid communication via rest endpoints is based on json formatted payload. Upon parsing error, there is only a generic exception stating expected json token type and current json token type. There is no detailed error log about the content of the payload causing the violation. In the micro-service world, the trend is to deploy the Druid servers in k8 with the mesh network. Often the istio proxy or other proxies is used to intercept the network connection between Druid servers. The proxy may give error messages for various reasons. These error messages are not expected by the json parser. The generic error message from Druid can be very misleading as the user may think the message is based on the response from the other Druid server. For example, this is an example of mysterious error message QueryInterruptedException{msg=Next token wasn't a START_ARRAY, was[VALUE_STRING] from url[http://xxxxx:8088/druid/v2/], code=Unknown exception, class=org.apache.druid.java.util.common.IAE, host=xxxxx:8088}" While the context of the message is the following from the proxy when it can't tunnel the network connection. pstream connect error or disconnect/reset before header So this very simple PR is just to enhance the logging and get the real underlying message printed out. This would save a lot of head scratching time if Druid is deployed with mesh network. Co-authored-by: Kai Sun --- .../druid/client/JsonParserIterator.java | 11 +++- .../druid/client/JsonParserIteratorTest.java | 52 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/apache/druid/client/JsonParserIterator.java b/server/src/main/java/org/apache/druid/client/JsonParserIterator.java index 237614fb4ff..00dea3dff0e 100644 --- a/server/src/main/java/org/apache/druid/client/JsonParserIterator.java +++ b/server/src/main/java/org/apache/druid/client/JsonParserIterator.java @@ -185,8 +185,17 @@ public class JsonParserIterator implements CloseableIterator } else if (nextToken == JsonToken.START_OBJECT) { throw convertException(jp.getCodec().readValue(jp, QueryException.class)); } else { + String errMsg = jp.getValueAsString(); + if (errMsg != null) { + errMsg = errMsg.substring(0, Math.min(errMsg.length(), 192)); + } throw convertException( - new IAE("Next token wasn't a START_ARRAY, was[%s] from url[%s]", jp.getCurrentToken(), url) + new IAE( + "Next token wasn't a START_ARRAY, was[%s] from url[%s] with value[%s]", + jp.getCurrentToken(), + url, + errMsg + ) ); } } diff --git a/server/src/test/java/org/apache/druid/client/JsonParserIteratorTest.java b/server/src/test/java/org/apache/druid/client/JsonParserIteratorTest.java index 3ac2b4122d2..5cce3c2fd3f 100644 --- a/server/src/test/java/org/apache/druid/client/JsonParserIteratorTest.java +++ b/server/src/test/java/org/apache/druid/client/JsonParserIteratorTest.java @@ -316,8 +316,60 @@ public class JsonParserIteratorTest } } + public static class IAEExceptionConversionTest + { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private String errorMessage = "pstream connect error or disconnect/reset before header"; + private String nullErrMsg = null; + + @Test + public void testNullErrorMsg() throws JsonProcessingException + { + JsonParserIterator iterator = new JsonParserIterator<>( + JAVA_TYPE, + Futures.immediateFuture( + mockErrorResponse(nullErrMsg) + ), + URL, + null, + HOST, + OBJECT_MAPPER + ); + + expectedException.expect(QueryInterruptedException.class); + expectedException.expectMessage(""); + iterator.hasNext(); + } + + @Test + public void testParsingError() throws JsonProcessingException + { + JsonParserIterator iterator = new JsonParserIterator<>( + JAVA_TYPE, + Futures.immediateFuture( + mockErrorResponse(errorMessage) + ), + URL, + null, + HOST, + OBJECT_MAPPER + ); + + expectedException.expect(QueryInterruptedException.class); + expectedException.expectMessage(errorMessage); + iterator.hasNext(); + } + } + private static InputStream mockErrorResponse(Exception e) throws JsonProcessingException { return new ByteArrayInputStream(OBJECT_MAPPER.writeValueAsBytes(e)); } + + private static InputStream mockErrorResponse(String errMsg) throws JsonProcessingException + { + return new ByteArrayInputStream(OBJECT_MAPPER.writeValueAsBytes(errMsg)); + } }