-
Notifications
You must be signed in to change notification settings - Fork 180
Improve Error Reporting for New SQL Engine #691
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1531f38
0211c0d
0867d08
19e4c2c
245156c
9e0c70c
0898159
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,9 @@ | |
|
|
||
| import com.alibaba.druid.sql.parser.ParserException; | ||
| import com.google.common.collect.ImmutableList; | ||
|
|
||
| import java.io.PrintWriter; | ||
| import java.io.StringWriter; | ||
| import java.sql.SQLFeatureNotSupportedException; | ||
| import java.util.Arrays; | ||
| import java.util.HashMap; | ||
|
|
@@ -118,12 +121,20 @@ public String getName() { | |
| return "sql_action"; | ||
| } | ||
|
|
||
| /** | ||
| * Prepare and execute rest SQL request. In the event the V2 SQL engine fails, the V1 | ||
| * engine attempts the query. | ||
| * @param request : Rest request being made. | ||
| * @param client : Rest client for making the request. | ||
| * @return : Resulting values for request. | ||
| */ | ||
| @Override | ||
| protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { | ||
| Metrics.getInstance().getNumericalMetric(MetricName.REQ_TOTAL).increment(); | ||
| Metrics.getInstance().getNumericalMetric(MetricName.REQ_COUNT_TOTAL).increment(); | ||
|
|
||
| LogUtils.addRequestId(); | ||
| newSqlQueryHandler.setErrorStr(""); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it necessary?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this class is a singleton we can potentially re-use old error messages. This ensures we are starting with an empty error message.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if it's singleton will errorStr handle concurrent errors? |
||
|
|
||
| try { | ||
| if (!isSQLFeatureEnabled()) { | ||
|
|
@@ -161,8 +172,9 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli | |
| final QueryAction queryAction = explainRequest(client, sqlRequest, format); | ||
| return channel -> executeSqlRequest(request, queryAction, client, channel); | ||
| } catch (Exception e) { | ||
| LOG.error(LogUtils.getRequestId() + " V2 SQL error during query execution", QueryDataAnonymizer.anonymizeData(newSqlQueryHandler.getErrorStr())); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| logAndPublishMetrics(e); | ||
| return channel -> reportError(channel, e, isClientError(e) ? BAD_REQUEST : SERVICE_UNAVAILABLE); | ||
| return channel -> reportError(channel, e, isClientError(e) ? BAD_REQUEST : SERVICE_UNAVAILABLE, newSqlQueryHandler.getErrorStr()); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -180,14 +192,28 @@ private void handleCursorRequest(final RestRequest request, final String cursor, | |
| cursorRestExecutor.execute(client, request.params(), channel); | ||
| } | ||
|
|
||
| /** | ||
| * Log error message for exception and increment failure statistics. | ||
| * @param e : Caught exception. | ||
| */ | ||
| private static void logAndPublishMetrics(final Exception e) { | ||
| if (isClientError(e)) { | ||
| LOG.error(LogUtils.getRequestId() + " Client side error during query execution", e); | ||
| LOG.error(LogUtils.getRequestId() + " Client side error during query execution", QueryDataAnonymizer.anonymizeData(e.getMessage())); | ||
| Metrics.getInstance().getNumericalMetric(MetricName.FAILED_REQ_COUNT_CUS).increment(); | ||
| } else { | ||
| LOG.error(LogUtils.getRequestId() + " Server side error during query execution", e); | ||
| LOG.error(LogUtils.getRequestId() + " Server side error during query execution", QueryDataAnonymizer.anonymizeData(e.getMessage())); | ||
| Metrics.getInstance().getNumericalMetric(MetricName.FAILED_REQ_COUNT_SYS).increment(); | ||
| } | ||
|
|
||
| /** | ||
| * Use PrintWriter to copy the stack trace for logging. This is used to anonymize | ||
| * log messages, and can be reverted to the simpler implementation when | ||
| * the anonymizer is fixed. | ||
| */ | ||
| StringWriter sw = new StringWriter(); | ||
| e.printStackTrace(new PrintWriter(sw)); | ||
| String stackTrace = sw.toString(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't his block (205-207) be with line 215? It'll be easier to follow.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved under: 08981598f03df7da85f23fd547b70c1a08318c8c
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is for debug purpose, right? could it be wrap inside if (log.debug)?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We want the stack trace to be logged under normal debug levels as error. So not for debugging purposes only but error reporting like prior to this new implementation. I could wrap this inside of a Log.Error if that is more of your liking. |
||
| LOG.error(stackTrace); | ||
| } | ||
|
|
||
| private static QueryAction explainRequest(final NodeClient client, final SqlRequest sqlRequest, Format format) | ||
|
|
@@ -234,7 +260,7 @@ private static boolean isExplainRequest(final RestRequest request) { | |
| return request.path().endsWith("/_explain"); | ||
| } | ||
|
|
||
| private static boolean isClientError(Exception e) { | ||
| public static boolean isClientError(Exception e) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why change to public?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I updated this function to public to allow |
||
| return e instanceof NullPointerException // NPE is hard to differentiate but more likely caused by bad query | ||
| || e instanceof SqlParseException | ||
| || e instanceof ParserException | ||
|
|
@@ -253,8 +279,19 @@ private void sendResponse(final RestChannel channel, final String message, final | |
| channel.sendResponse(new BytesRestResponse(status, message)); | ||
| } | ||
|
|
||
| private void reportError(final RestChannel channel, final Exception e, final RestStatus status) { | ||
| sendResponse(channel, ErrorMessageFactory.createErrorMessage(e, status.getStatus()).toString(), status); | ||
| /** | ||
| * Report Error message to user. | ||
| * @param channel : Rest channel to sent response through. | ||
| * @param e : Exception caught when attempting query. | ||
| * @param status : Status for rest request made. | ||
| * @param v2SqlEngineError : Error message for new SQL engine. Can be removed when old SQL engine is deprecated. | ||
| */ | ||
| private void reportError(final RestChannel channel, final Exception e, final RestStatus status, String v2SqlEngineError) { | ||
| String errorMsg = ErrorMessageFactory.createErrorMessage(e, status.getStatus()).toString(); | ||
| errorMsg += v2SqlEngineError.isEmpty() ? "" : | ||
| "\nQuery failed on both V1 and V2 SQL parser engines. V2 SQL parser error following: \n" | ||
| + v2SqlEngineError; | ||
|
Comment on lines
+289
to
+293
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just discovered that you're concatenating 2 json objects here. In my test I'm getting You should update |
||
| sendResponse(channel, errorMsg, status); | ||
| } | ||
|
|
||
| private boolean isSQLFeatureEnabled() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,7 +26,8 @@ public class QueryDataAnonymizer { | |
| * Sensitive data includes index names, column names etc., | ||
| * which in druid parser are parsed to SQLIdentifierExpr instances | ||
| * @param query entire sql query string | ||
| * @return sql query string with all identifiers replaced with "***" | ||
| * @return sql query string with all identifiers replaced with "***" on success | ||
| * and failure string otherwise to ensure no non-anonymized data is logged in production. | ||
| */ | ||
| public static String anonymizeData(String query) { | ||
| String resultQuery; | ||
|
|
@@ -38,8 +39,9 @@ public static String anonymizeData(String query) { | |
| .replaceAll("false", "boolean_literal") | ||
| .replaceAll("[\\n][\\t]+", " "); | ||
| } catch (Exception e) { | ||
| LOG.warn("Caught an exception when anonymizing sensitive data"); | ||
| resultQuery = query; | ||
| LOG.warn("Caught an exception when anonymizing sensitive data."); | ||
| LOG.debug("String {} failed anonymization.", query); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not combine with LOG.warn?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For syntax errors, the |
||
| resultQuery = "Failed to anonymize data."; | ||
| } | ||
| return resultQuery; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use
@Getterand@Setterannotations instead of creatingsetErrorStrandgetErrorStr.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved under: 08981598f03df7da85f23fd547b70c1a08318c8c
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just want to confirm is this class (or
RestSqlActionwho create this) a singleton or created per request?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we really need errorStr? or Exception e? Could we use RestSqlAction produce the error message from e.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dai-chen This class is a singleton.
@penghuo We need to be able to capture error messages form potentially 2 exceptions. That is why we need the
errorStrmember variable.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RestSqlAction is created per request.