This is an automated email from the ASF dual-hosted git repository.
adarshsanjeev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git
The following commit(s) were added to refs/heads/master by this push:
new 42c1117000b Add a new strategy to sanitize error messages based on the
error message role (#18487)
42c1117000b is described below
commit 42c1117000b7a1c924026126c94d09a841bd4313
Author: Adarsh Sanjeev <[email protected]>
AuthorDate: Wed Sep 17 10:31:00 2025 +0530
Add a new strategy to sanitize error messages based on the error message
role (#18487)
Add a new error message strategy to log the message and return an error id
to the user, based on the role of the Druid exception.
To enable this strategy, set
druid.server.http.errorResponseTransform.strategy to persona.
---
docs/configuration/index.md | 6 ++
.../msq/sql/resources/SqlStatementResource.java | 12 +++-
.../druid/msq/sql/resources/SqlTaskResource.java | 12 +++-
.../dart/controller/http/DartSqlResourceTest.java | 3 +-
.../resources/SqlMSQStatementResourcePostTest.java | 7 +-
.../sql/resources/SqlStatementResourceTest.java | 4 +-
.../exception/ErrorResponseTransformStrategy.java | 16 ++++-
.../PersonaBasedErrorTransformStrategy.java | 77 ++++++++++++++++++++++
.../common/exception/SanitizableException.java | 4 +-
.../java/util/emitter/service/AlertBuilder.java | 2 +-
.../PersonaBasedErrorTransformStrategyTest.java | 71 ++++++++++++++++++++
.../org/apache/druid/sql/avatica/ErrorHandler.java | 23 ++++++-
.../org/apache/druid/sql/http/SqlResource.java | 30 +++++++--
.../org/apache/druid/sql/http/SqlResourceTest.java | 9 ++-
14 files changed, 255 insertions(+), 21 deletions(-)
diff --git a/docs/configuration/index.md b/docs/configuration/index.md
index 394883a1b9b..c925fe964c1 100644
--- a/docs/configuration/index.md
+++ b/docs/configuration/index.md
@@ -627,6 +627,12 @@ On the other hand, if
`druid.server.http.errorResponseTransform.allowedRegex` is
{"error":"Plan validation
failed","errorMessage":"org.apache.calcite.runtime.CalciteContextException:
From line 1, column 15 to line 1, column 38: Object 'nonexistent-datasource'
not found","errorClass":null,"host":null}
```
+##### Persona based error response transform strategy
+
+In this mode, Druid transforms any exceptions which are targeted at non-users
personas. Instead of returning such exception directly, the strategy logs the
exception against a random id and returns the id along with a generic error
message to the user.
+
+To enable this strategy, set
`druid.server.http.errorResponseTransform.strategy` to `persona`.
+
### Overlord discovery
This config is used to find the [Overlord](../design/overlord.md) using
Curator service discovery. Only required if you are actually running an
Overlord.
diff --git
a/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlStatementResource.java
b/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlStatementResource.java
index d480264e003..28e5f257f09 100644
---
a/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlStatementResource.java
+++
b/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlStatementResource.java
@@ -76,6 +76,7 @@ import org.apache.druid.query.QueryException;
import org.apache.druid.rpc.HttpResponseException;
import org.apache.druid.rpc.indexing.OverlordClient;
import org.apache.druid.server.QueryResponse;
+import org.apache.druid.server.initialization.ServerConfig;
import org.apache.druid.server.security.Action;
import org.apache.druid.server.security.AuthenticationResult;
import org.apache.druid.server.security.AuthorizationResult;
@@ -134,6 +135,7 @@ public class SqlStatementResource
private final StorageConnector storageConnector;
private final AuthorizerMapper authorizerMapper;
private final DefaultQueryConfig defaultQueryConfig;
+ private final ServerConfig serverConfig;
@Inject
public SqlStatementResource(
@@ -142,7 +144,8 @@ public class SqlStatementResource
final OverlordClient overlordClient,
final @MultiStageQuery StorageConnectorProvider storageConnectorProvider,
final AuthorizerMapper authorizerMapper,
- final DefaultQueryConfig defaultQueryConfig
+ final DefaultQueryConfig defaultQueryConfig,
+ final ServerConfig serverConfig
)
{
this.msqSqlStatementFactory = msqSqlStatementFactory;
@@ -151,6 +154,7 @@ public class SqlStatementResource
this.storageConnector =
storageConnectorProvider.createStorageConnector(null);
this.authorizerMapper = authorizerMapper;
this.defaultQueryConfig = defaultQueryConfig;
+ this.serverConfig = serverConfig;
}
/**
@@ -200,7 +204,11 @@ public class SqlStatementResource
stmt = msqSqlStatementFactory.httpStatement(sqlQueryPlus, req);
}
catch (Exception e) {
- return SqlResource.handleExceptionBeforeStatementCreated(e,
sqlQuery.queryContext());
+ return SqlResource.handleExceptionBeforeStatementCreated(
+ e,
+ sqlQuery.queryContext(),
+ serverConfig.getErrorResponseTransformStrategy()
+ );
}
final String sqlQueryId = stmt.sqlQueryId();
diff --git
a/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlTaskResource.java
b/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlTaskResource.java
index 9336e7baec2..75c064b30bf 100644
---
a/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlTaskResource.java
+++
b/multi-stage-query/src/main/java/org/apache/druid/msq/sql/resources/SqlTaskResource.java
@@ -37,6 +37,7 @@ import org.apache.druid.query.DefaultQueryConfig;
import org.apache.druid.query.QueryException;
import org.apache.druid.query.http.SqlTaskStatus;
import org.apache.druid.server.QueryResponse;
+import org.apache.druid.server.initialization.ServerConfig;
import org.apache.druid.server.security.Access;
import org.apache.druid.server.security.AuthorizationUtils;
import org.apache.druid.server.security.ForbiddenException;
@@ -82,17 +83,20 @@ public class SqlTaskResource
private final SqlStatementFactory sqlStatementFactory;
private final DefaultQueryConfig defaultQueryConfig;
private final ObjectMapper jsonMapper;
+ private final ServerConfig serverConfig;
@Inject
public SqlTaskResource(
final @MultiStageQuery SqlStatementFactory sqlStatementFactory,
final DefaultQueryConfig defaultQueryConfig,
- final ObjectMapper jsonMapper
+ final ObjectMapper jsonMapper,
+ final ServerConfig serverConfig
)
{
this.sqlStatementFactory = sqlStatementFactory;
this.defaultQueryConfig = defaultQueryConfig;
this.jsonMapper = jsonMapper;
+ this.serverConfig = serverConfig;
}
/**
@@ -131,7 +135,11 @@ public class SqlTaskResource
stmt = sqlStatementFactory.httpStatement(sqlQueryPlus, req);
}
catch (Exception e) {
- return SqlResource.handleExceptionBeforeStatementCreated(e,
sqlQuery.queryContext());
+ return SqlResource.handleExceptionBeforeStatementCreated(
+ e,
+ sqlQuery.queryContext(),
+ serverConfig.getErrorResponseTransformStrategy()
+ );
}
final String sqlQueryId = stmt.sqlQueryId();
diff --git
a/multi-stage-query/src/test/java/org/apache/druid/msq/dart/controller/http/DartSqlResourceTest.java
b/multi-stage-query/src/test/java/org/apache/druid/msq/dart/controller/http/DartSqlResourceTest.java
index eba37c273a7..48d4cab9450 100644
---
a/multi-stage-query/src/test/java/org/apache/druid/msq/dart/controller/http/DartSqlResourceTest.java
+++
b/multi-stage-query/src/test/java/org/apache/druid/msq/dart/controller/http/DartSqlResourceTest.java
@@ -274,7 +274,8 @@ public class DartSqlResourceTest extends MSQTestBase
ResponseContextConfig.newConfig(false),
SELF_NODE
),
- DefaultQueryConfig.NIL
+ DefaultQueryConfig.NIL,
+ new ServerConfig()
);
// Setup mocks
diff --git
a/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlMSQStatementResourcePostTest.java
b/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlMSQStatementResourcePostTest.java
index 80c40725c89..4784dfea377 100644
---
a/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlMSQStatementResourcePostTest.java
+++
b/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlMSQStatementResourcePostTest.java
@@ -44,6 +44,7 @@ import org.apache.druid.query.DefaultQueryConfig;
import org.apache.druid.query.ExecutionMode;
import org.apache.druid.query.QueryContexts;
import org.apache.druid.segment.column.ValueType;
+import org.apache.druid.server.initialization.ServerConfig;
import org.apache.druid.sql.calcite.util.CalciteTests;
import org.apache.druid.sql.http.ResultFormat;
import org.apache.druid.sql.http.SqlQuery;
@@ -77,7 +78,8 @@ public class SqlMSQStatementResourcePostTest extends
MSQTestBase
indexingServiceClient,
s -> localFileStorageConnector,
authorizerMapper,
- new DefaultQueryConfig(Map.of("debug", "false"))
+ new DefaultQueryConfig(Map.of("debug", "false")),
+ new ServerConfig()
);
}
@@ -332,7 +334,8 @@ public class SqlMSQStatementResourcePostTest extends
MSQTestBase
indexingServiceClient,
s -> NilStorageConnector.getInstance(),
authorizerMapper,
- DefaultQueryConfig.NIL
+ DefaultQueryConfig.NIL,
+ new ServerConfig()
);
String errorMessage = "The sql statement api cannot read from the select
destination [durableStorage] provided in "
diff --git
a/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlStatementResourceTest.java
b/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlStatementResourceTest.java
index af21a85615f..d71bc4501c4 100644
---
a/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlStatementResourceTest.java
+++
b/multi-stage-query/src/test/java/org/apache/druid/msq/sql/resources/SqlStatementResourceTest.java
@@ -73,6 +73,7 @@ import org.apache.druid.segment.TestHelper;
import org.apache.druid.segment.column.ColumnType;
import org.apache.druid.segment.column.RowSignature;
import org.apache.druid.segment.column.ValueType;
+import org.apache.druid.server.initialization.ServerConfig;
import org.apache.druid.server.mocks.MockHttpServletRequest;
import org.apache.druid.server.security.Access;
import org.apache.druid.server.security.Action;
@@ -700,7 +701,8 @@ public class SqlStatementResourceTest extends MSQTestBase
overlordClient,
tempDir -> localFileStorageConnector,
authorizerMapper,
- new DefaultQueryConfig(Map.of("debug", "true"))
+ new DefaultQueryConfig(Map.of("debug", "true")),
+ new ServerConfig()
);
}
diff --git
a/processing/src/main/java/org/apache/druid/common/exception/ErrorResponseTransformStrategy.java
b/processing/src/main/java/org/apache/druid/common/exception/ErrorResponseTransformStrategy.java
index d7a2bbd3768..b48ba97ca90 100644
---
a/processing/src/main/java/org/apache/druid/common/exception/ErrorResponseTransformStrategy.java
+++
b/processing/src/main/java/org/apache/druid/common/exception/ErrorResponseTransformStrategy.java
@@ -21,14 +21,17 @@ package org.apache.druid.common.exception;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import org.apache.druid.error.DruidException;
import javax.validation.constraints.NotNull;
+import java.util.Optional;
import java.util.function.Function;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "strategy", defaultImpl =
NoErrorResponseTransformStrategy.class)
@JsonSubTypes(value = {
@JsonSubTypes.Type(name = "none", value =
NoErrorResponseTransformStrategy.class),
- @JsonSubTypes.Type(name = "allowedRegex", value =
AllowedRegexErrorResponseTransformStrategy.class)
+ @JsonSubTypes.Type(name = "allowedRegex", value =
AllowedRegexErrorResponseTransformStrategy.class),
+ @JsonSubTypes.Type(name = "persona", value =
PersonaBasedErrorTransformStrategy.class),
})
public interface ErrorResponseTransformStrategy
{
@@ -41,6 +44,17 @@ public interface ErrorResponseTransformStrategy
return exception.sanitize(getErrorMessageTransformFunction());
}
+ /**
+ * For a given {@link DruidException} apply the transformation strategy and
return a sanitized Exception
+ * if the transformation stategy was applied. This call does not log the
exception.
+ * It is the callers responsibility to do so. Returns Optional.empty() if no
transformation was applied.
+ * The errorId is provided to be used in the transformed Exception if needed.
+ */
+ default Optional<Exception> maybeTransform(DruidException exception,
Optional<String> errorId)
+ {
+ return Optional.empty();
+ }
+
/**
* Return a function for checking and transforming the error message if
needed.
* Function can return null if error message needs to be omitted or return
String to be use instead.
diff --git
a/processing/src/main/java/org/apache/druid/common/exception/PersonaBasedErrorTransformStrategy.java
b/processing/src/main/java/org/apache/druid/common/exception/PersonaBasedErrorTransformStrategy.java
new file mode 100644
index 00000000000..a0122204f79
--- /dev/null
+++
b/processing/src/main/java/org/apache/druid/common/exception/PersonaBasedErrorTransformStrategy.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.common.exception;
+
+import org.apache.druid.error.DruidException;
+import org.apache.druid.java.util.common.StringUtils;
+
+import java.util.Optional;
+import java.util.UUID;
+import java.util.function.Function;
+
+/**
+ * {@link ErrorResponseTransformStrategy} that modifies the error message of a
{@link DruidException} based on the
+ * persona. For non-user error messages, this logs the exception with a
randomly generated id and returns a new exception
+ * containing the id instead.
+ */
+public class PersonaBasedErrorTransformStrategy implements
ErrorResponseTransformStrategy
+{
+ private static final String ERROR_WITH_ID_TEMPLATE = "Internal server error,
please contact your administrator "
+ + "with Error ID [%s]
if the issue persists.";
+ public static final PersonaBasedErrorTransformStrategy INSTANCE = new
PersonaBasedErrorTransformStrategy();
+
+ /**
+ * Transforms the {@link DruidException} if required. Returns an optional
with a new Druid exception if the
+ * exception was modified. Returns an empty optional if no transformation
was performed.
+ */
+ @Override
+ public Optional<Exception> maybeTransform(DruidException druidException,
Optional<String> optionalErrorId)
+ {
+ if (druidException.getTargetPersona() == DruidException.Persona.USER) {
+ return Optional.empty();
+ }
+ String errorId = optionalErrorId.orElse(UUID.randomUUID().toString());
+
+ return Optional.of(DruidException.forPersona(DruidException.Persona.USER)
+
.ofCategory(DruidException.Category.RUNTIME_FAILURE)
+
.build(StringUtils.format(ERROR_WITH_ID_TEMPLATE, errorId)));
+ }
+
+ @Override
+ public Function<String, String> getErrorMessageTransformFunction()
+ {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) {
+ return true;
+ }
+ return !(o == null || getClass() != o.getClass());
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return PersonaBasedErrorTransformStrategy.class.hashCode();
+ }
+}
diff --git
a/processing/src/main/java/org/apache/druid/common/exception/SanitizableException.java
b/processing/src/main/java/org/apache/druid/common/exception/SanitizableException.java
index 2ac15e05eb8..b34eaf9521f 100644
---
a/processing/src/main/java/org/apache/druid/common/exception/SanitizableException.java
+++
b/processing/src/main/java/org/apache/druid/common/exception/SanitizableException.java
@@ -25,10 +25,10 @@ public interface SanitizableException
{
/**
* Apply the function for transforming the error message then return new
Exception with sanitized fields and transformed message.
- * The {@param errorMessageTransformFunction} is only intended to be use to
transform the error message
+ * The {@param errorMessageTransformFunction} is only intended to be used to
transform the error message
* String of the Exception as only the error message String is common to all
Exception classes.
* For other fields (which may be unique to each particular Exception
class), each implementation of this method can
- * decide for itself how to sanitized those fields (i.e. leaving unchanged,
changing to null, changing to a fixed String, etc.).
+ * decide for itself how to sanitize those fields (i.e. leaving unchanged,
changing to null, changing to a fixed String, etc.).
* Note that this method returns a new Exception of the same type since
Exception error message is immutable.
*/
Exception sanitize(
diff --git
a/processing/src/main/java/org/apache/druid/java/util/emitter/service/AlertBuilder.java
b/processing/src/main/java/org/apache/druid/java/util/emitter/service/AlertBuilder.java
index 352272d67bc..27c4144585e 100644
---
a/processing/src/main/java/org/apache/druid/java/util/emitter/service/AlertBuilder.java
+++
b/processing/src/main/java/org/apache/druid/java/util/emitter/service/AlertBuilder.java
@@ -69,7 +69,7 @@ public class AlertBuilder extends
ServiceEventBuilder<AlertEvent>
return this;
}
- public AlertBuilder addData(Map<String, Object> data)
+ public AlertBuilder addData(Map<String, ?> data)
{
dataMap.putAll(data);
return this;
diff --git
a/processing/src/test/java/org/apache/druid/common/exception/PersonaBasedErrorTransformStrategyTest.java
b/processing/src/test/java/org/apache/druid/common/exception/PersonaBasedErrorTransformStrategyTest.java
new file mode 100644
index 00000000000..9eb64ada0f1
--- /dev/null
+++
b/processing/src/test/java/org/apache/druid/common/exception/PersonaBasedErrorTransformStrategyTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.druid.common.exception;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.apache.druid.error.DruidException;
+import org.apache.druid.error.DruidExceptionMatcher;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Optional;
+
+public class PersonaBasedErrorTransformStrategyTest
+{
+ private PersonaBasedErrorTransformStrategy target;
+
+ @Before
+ public void setUp() throws Exception
+ {
+ target = new PersonaBasedErrorTransformStrategy();
+ }
+
+ @Test
+ public void testUserPersonaRemainsUnchanged()
+ {
+ DruidException druidException =
DruidException.forPersona(DruidException.Persona.USER)
+
.ofCategory(DruidException.Category.FORBIDDEN)
+ .build("Permission
exception");
+ Assert.assertEquals(Optional.empty(),
target.maybeTransform(druidException, Optional.empty()));
+ }
+
+ @Test
+ public void testDeveloperPersonaIsTransformed()
+ {
+ DruidException druidException = DruidException.defensive().build("Test
Defensive exception");
+
+ DruidExceptionMatcher druidExceptionMatcher = new DruidExceptionMatcher(
+ DruidException.Persona.USER,
+ druidException.getCategory(),
+ druidException.getErrorCode()
+ ).expectMessageContains("Could not process the query, please contact your
administrator with Error ID");
+
+ druidExceptionMatcher.matches(target.maybeTransform(druidException,
Optional.of("the-error")).get());
+ }
+
+ @Test
+ public void testEqualsAndHashCode()
+ {
+ EqualsVerifier.forClass(PersonaBasedErrorTransformStrategy.class)
+ .usingGetClass()
+ .verify();
+ }
+}
diff --git a/sql/src/main/java/org/apache/druid/sql/avatica/ErrorHandler.java
b/sql/src/main/java/org/apache/druid/sql/avatica/ErrorHandler.java
index a2daf4689ba..8e2927279ee 100644
--- a/sql/src/main/java/org/apache/druid/sql/avatica/ErrorHandler.java
+++ b/sql/src/main/java/org/apache/druid/sql/avatica/ErrorHandler.java
@@ -23,13 +23,19 @@ import com.google.inject.Inject;
import org.apache.druid.common.exception.ErrorResponseTransformStrategy;
import org.apache.druid.common.exception.NoErrorResponseTransformStrategy;
import org.apache.druid.common.exception.SanitizableException;
+import org.apache.druid.error.DruidException;
import org.apache.druid.java.util.common.ISE;
+import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.java.util.common.UOE;
+import org.apache.druid.java.util.common.logger.Logger;
import org.apache.druid.query.QueryException;
import org.apache.druid.query.QueryInterruptedException;
import org.apache.druid.server.initialization.ServerConfig;
import org.apache.druid.server.security.ForbiddenException;
+import java.util.Optional;
+import java.util.UUID;
+
/**
* ErrorHandler is a utility class that is used to sanitize exceptions.
@@ -37,6 +43,7 @@ import org.apache.druid.server.security.ForbiddenException;
class ErrorHandler
{
private final ErrorResponseTransformStrategy errorResponseTransformStrategy;
+ private static final Logger log = new Logger(ErrorHandler.class);
@Inject
ErrorHandler(final ServerConfig serverConfig)
@@ -79,6 +86,20 @@ class ErrorHandler
// could do `throw sanitize(error);` but just sanitizing immediately
avoids unnecessary going down multiple levels
return new
RuntimeException(errorResponseTransformStrategy.transformIfNeeded((SanitizableException)
error.getCause()));
}
+ if (error instanceof DruidException) {
+ String errorId = UUID.randomUUID().toString();
+ Optional<Exception> transformedException =
errorResponseTransformStrategy.maybeTransform(
+ (DruidException) error,
+ Optional.of(errorId)
+ );
+
+ if (transformedException.isPresent()) {
+ // Log the exception here itself, since the error has been transformed.
+ log.error(error, StringUtils.format("External Error ID: [%s]",
errorId));
+ }
+ QueryInterruptedException wrappedError =
QueryInterruptedException.wrapIfNeeded(transformedException.orElse((Exception)
error));
+ return (QueryException)
errorResponseTransformStrategy.transformIfNeeded(wrappedError);
+ }
QueryInterruptedException wrappedError =
QueryInterruptedException.wrapIfNeeded(error);
return (QueryException)
errorResponseTransformStrategy.transformIfNeeded(wrappedError);
}
@@ -86,7 +107,7 @@ class ErrorHandler
/**
* Check to see if something needs to be sanitized.
* <p>
- * Done by checking to see if the ErrorResponse is different than a NoOp
Error response transform strategy.
+ * Done by checking to see if the ErrorResponse is different from a NoOp
Error response transform strategy.
*
* @return a boolean that returns true if error handler has an error
response strategy other than the NoOp error
* response strategy
diff --git a/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
b/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
index 57df7c18945..98e39e7be3a 100644
--- a/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
+++ b/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
@@ -24,6 +24,7 @@ import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.sun.jersey.api.core.HttpContext;
+import org.apache.druid.common.exception.ErrorResponseTransformStrategy;
import org.apache.druid.error.DruidException;
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.java.util.common.logger.Logger;
@@ -32,6 +33,7 @@ import org.apache.druid.query.QueryContext;
import org.apache.druid.query.QueryContexts;
import org.apache.druid.server.QueryResource;
import org.apache.druid.server.QueryResultPusher;
+import org.apache.druid.server.initialization.ServerConfig;
import org.apache.druid.server.security.Action;
import org.apache.druid.server.security.AuthenticationResult;
import org.apache.druid.server.security.AuthorizationResult;
@@ -64,7 +66,9 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
+import java.util.UUID;
import java.util.stream.Collectors;
@Path(SqlResource.PATH)
@@ -83,6 +87,7 @@ public class SqlResource
private final SqlLifecycleManager sqlLifecycleManager;
private final SqlEngineRegistry sqlEngineRegistry;
private final DefaultQueryConfig defaultQueryConfig;
+ private final ServerConfig serverConfig;
@VisibleForTesting
@Inject
@@ -91,7 +96,8 @@ public class SqlResource
final SqlLifecycleManager sqlLifecycleManager,
final SqlEngineRegistry sqlEngineRegistry,
final SqlResourceQueryResultPusherFactory resultPusherFactory,
- final DefaultQueryConfig defaultQueryConfig
+ final DefaultQueryConfig defaultQueryConfig,
+ final ServerConfig serverConfig
)
{
this.resultPusherFactory = resultPusherFactory;
@@ -99,7 +105,7 @@ public class SqlResource
this.authorizerMapper = Preconditions.checkNotNull(authorizerMapper,
"authorizerMapper");
this.sqlLifecycleManager = Preconditions.checkNotNull(sqlLifecycleManager,
"sqlLifecycleManager");
this.defaultQueryConfig = Preconditions.checkNotNull(defaultQueryConfig,
"defaultQueryConfig");
-
+ this.serverConfig = serverConfig;
}
@GET
@@ -186,7 +192,11 @@ public class SqlResource
}
catch (Exception e) {
// Can't use the queryContext with SETs since it might not have been
created yet. Use the original one.
- return handleExceptionBeforeStatementCreated(e, sqlQuery.queryContext());
+ return handleExceptionBeforeStatementCreated(
+ e,
+ sqlQuery.queryContext(),
+ serverConfig.getErrorResponseTransformStrategy()
+ );
}
final String currThreadName = Thread.currentThread().getName();
@@ -296,12 +306,22 @@ public class SqlResource
/**
* Generates a response for a {@link DruidException} that occurs prior to
the {@link HttpStatement} being created.
*/
- public static Response handleExceptionBeforeStatementCreated(final Exception
e, final QueryContext queryContext)
+ public static Response handleExceptionBeforeStatementCreated(
+ final Exception e,
+ final QueryContext queryContext,
+ final ErrorResponseTransformStrategy strategy
+ )
{
if (e instanceof DruidException) {
final String sqlQueryId =
queryContext.getString(QueryContexts.CTX_SQL_QUERY_ID);
+ String errorId = sqlQueryId == null ? UUID.randomUUID().toString() :
sqlQueryId;
+ Optional<Exception> transformed =
strategy.maybeTransform((DruidException) e, Optional.of(errorId));
+ if (transformed.isPresent()) {
+ // Log the exception here itself, since the error has been transformed.
+ log.error(e, StringUtils.format("External Error ID: [%s]", errorId));
+ }
return QueryResultPusher.handleDruidExceptionBeforeResponseStarted(
- (DruidException) e,
+ (DruidException) transformed.orElse(e),
MediaType.APPLICATION_JSON_TYPE,
sqlQueryId != null
? ImmutableMap.<String, String>builder()
diff --git a/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
b/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
index 87e9285a30a..4f0d53dfd02 100644
--- a/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/http/SqlResourceTest.java
@@ -333,7 +333,8 @@ public class SqlResourceTest extends CalciteTestBase
TEST_RESPONSE_CONTEXT_CONFIG,
DUMMY_DRUID_NODE
),
- DefaultQueryConfig.NIL
+ DefaultQueryConfig.NIL,
+ new ServerConfig()
);
}
@@ -609,7 +610,8 @@ public class SqlResourceTest extends CalciteTestBase
TEST_RESPONSE_CONTEXT_CONFIG,
DUMMY_DRUID_NODE
),
- queryConfigWithTimezone
+ queryConfigWithTimezone,
+ new ServerConfig()
);
final List<Map<String, Object>> rows = doPost(
@@ -1720,7 +1722,8 @@ public class SqlResourceTest extends CalciteTestBase
TEST_RESPONSE_CONTEXT_CONFIG,
DUMMY_DRUID_NODE
),
- DefaultQueryConfig.NIL
+ DefaultQueryConfig.NIL,
+ new ServerConfig()
);
String errorMessage = "This will be supported in Druid 9999";
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]