Class DruidException
- java.lang.Object
-
- java.lang.Throwable
-
- java.lang.Exception
-
- java.lang.RuntimeException
-
- org.apache.druid.error.DruidException
-
- All Implemented Interfaces:
Serializable
@NotThreadSafe public class DruidException extends RuntimeException
Represents an error condition exposed to the user and/or operator of Druid. Given that a DruidException is intended to be delivered to the end user, it should generally never be caught. DruidExceptions are generated at terminal points where the operation that was happening cannot make forward progress. As such, the only reason to catch a DruidException is if the code has some extra context that it wants to add to the message of the DruidException usingprependAndBuild(String, Object...)
. If code wants to catch and handle an exception instead, it should not be using the DruidException.Said another way, when a developer builds a DruidException in the code, they should be confident that the exception will make its way back to the user. DruidException is always the answer to "how do I generate an error message and deliver it to the user"?
At the time that DruidException was introduced, this type of "show this to the user please" exception was largely handled by created
RE
,IAE
, orISE
objects. It is intended that DruidException replaces all usage of these exceptions where the intention is to deliver a message to the user, which we believe to be the vast majority of usages. In cases where those exceptions are with the intention of being caught and acted upon, they should no change should occur.Notes about exception messages Firstly, exception messages should always be written with the notions from the style conventions covered in
dev/style-conventions.md
. Whenever possible, we should also try to provide an action to take to resolve the issue.Secondly, given that the DruidException requires defining a target persona, exception messages should always be written with that target persona in mind. Reviewers should use the targetPersona as added input to help validate that an exception message in meaningful.
For example, at the time that this exception was introduced, there is an exception that the router throws which is an
ISE
with the message"No default server found!"
. This exception is thrown when the router is unable to find a broker to forward a request to. It is completely meaningless to an end-user trying to run a query (what's a default server? why does it need to be found?). If we were to convert the exception to a DruidException and keep the same message, we should mark it as targeting the DEVELOPER persona as that is the only persona who should actually be able to figure out what a default server is and why it is important. That said, does it make sense for an exception that means "router cannot find a broker to forward the query to" to only be targeting the DEVELOPER? The answer to that is no, it's something that should really be made meaningful to a wider group. Some options could be- USER persona: Cannot find a queryable server, contact your cluster administrator to validate that all services are operational
- OPERATOR persona: Router unable to find a broker, check that brokers are up and active
If we think about this error more, we will realize that it's fundamentally something wrong with the cluster setup, which is something that we would expect an operator to be in charge of. So, we would pick the OPERATOR persona message, which also allows us to include more specific information about what server was not found and provide a more meaningful action to take (check the health of your brokers).
Description of fields of DruidException Every error consists of:- A target persona
- A categorization of the error
- An error code
- An error message
- A context (possibly empty)
The target persona indicates who the message is written for. This is important for 2 reasons
- It identifies why the developer is creating the exception and who they believe can take action on it. This context allows for code reviewers and other developers to evaluate the message with the persona in mind
- It can be used as a way to control which error messages should be routed where. For example, a user-targeted error message should be able to be exposed directly to the user, while an operator-targeted error message should perhaps be routed to the operators of the system instead of the end user firing a query.
The category indicates what kind of failure occurred. This is leveraged to align response codes (e.g. HTTP response codes) for similar exception messages.
The error code is a code that indicates a grouping of error messages. There is no forced structure around whether a specific error code can be reused for different problems or not. That is, an error code like "general" will get reused in many places as it's the basic error code used whenever a DruidException is created in-line. But, we might decide that a specific type of error should be identified explicitly by its error code and should only mean one thing, in which case that error code might only exist on a single error.
The error message is a message written targeting the target persona. It should have values interpolated into it in order to be as meaningful as possible for the target persona without leaking potentially sensitive information.
The context is a place to add extra information about the error that is not necessarily interpolated into the error message. It's a way to carry extra information that might be useful to a developer, but not necessarily to the target persona.
Notes for developers working with DruidException A DruidException can be built from one of 2 static methods:
forPersona(org.apache.druid.error.DruidException.Persona)
orfromFailure(Failure)
. The only way to set a specific error code is to build a DruidException from a Failure, when built in-line using forPersona, it will always be an "general" error.Additionally, DruidException is not intended to be directly serialized. The intention is that something converts it into an
ErrorResponse
first usingErrorResponse(DruidException)
and then that ErrorResponse is used for serialization. DruidException carries atoErrorResponse()
method because there are some code paths that directly serialize Exceptions and adjusting them was deemed out-of-scope for the PR that introduced DruidException.- See Also:
- Serialized Form
-
-
Nested Class Summary
Nested Classes Modifier and Type Class Description static class
DruidException.Category
Category of error.static class
DruidException.DruidExceptionBuilder
static class
DruidException.Failure
static class
DruidException.PartialDruidExceptionBuilder
static class
DruidException.Persona
The persona that the message on a DruidException is targeting
-
Method Summary
All Methods Static Methods Instance Methods Concrete Methods Modifier and Type Method Description static DruidException.DruidExceptionBuilder
defensive()
Build a "defensive" exception, this is an exception that should never actually be triggered, but we are throwing it inside of a defensive check.static DruidException
defensive(String format, Object... args)
Build a "defensive" exception, this is an exception that should never actually be triggered, but we are throwing it inside of a defensive check.static DruidException.PartialDruidExceptionBuilder
forPersona(DruidException.Persona persona)
Starts building a "general" DruidException targeting the specific persona.static DruidException
fromFailure(DruidException.Failure failure)
Builds a DruidException using the provided Failure class.DruidException.Category
getCategory()
Map<String,String>
getContext()
String
getContextValue(String key)
String
getErrorCode()
int
getStatusCode()
DruidException.Persona
getTargetPersona()
DruidException
prependAndBuild(String msg, Object... args)
Builds a new DruidException with a message that is the result of prepending the message passed as a parameter with the message already on the DruidException.ErrorResponse
toErrorResponse()
Returns this DruidException as an ErrorResponse.DruidException
withContext(String key, Object value)
DruidException
withContext(Map<String,String> values)
-
Methods inherited from class java.lang.Throwable
addSuppressed, fillInStackTrace, getCause, getLocalizedMessage, getMessage, getStackTrace, getSuppressed, initCause, printStackTrace, printStackTrace, printStackTrace, setStackTrace, toString
-
-
-
-
Method Detail
-
forPersona
public static DruidException.PartialDruidExceptionBuilder forPersona(DruidException.Persona persona)
Starts building a "general" DruidException targeting the specific persona.- Parameters:
persona
- the target persona of the exception message- Returns:
- a builder that can be used to complete the creation of the DruidException
-
fromFailure
public static DruidException fromFailure(DruidException.Failure failure)
Builds a DruidException using the provided Failure class. The errorCode is determined by the specific Failure class being used and the Failure class is responsible for setting all other required fields of the DruidException- Parameters:
failure
- failure implementation to use to build the DruidException- Returns:
- DruidException instance built from the Failure instance provided
-
defensive
public static DruidException.DruidExceptionBuilder defensive()
Build a "defensive" exception, this is an exception that should never actually be triggered, but we are throwing it inside of a defensive check.- Returns:
- A builder for a defensive exception.
-
defensive
public static DruidException defensive(String format, Object... args)
Build a "defensive" exception, this is an exception that should never actually be triggered, but we are throwing it inside of a defensive check.- Returns:
- A builder for a defensive exception.
-
withContext
public DruidException withContext(String key, Object value)
-
withContext
public DruidException withContext(Map<String,String> values)
-
getTargetPersona
public DruidException.Persona getTargetPersona()
-
getCategory
public DruidException.Category getCategory()
-
getErrorCode
public String getErrorCode()
-
getStatusCode
public int getStatusCode()
-
toErrorResponse
public ErrorResponse toErrorResponse()
Returns this DruidException as an ErrorResponse. This method exists for compatibility with some older code paths that serialize out Exceptions directly using Jackson. Instead of serializing a DruidException directly, code should be structured to take the DruidException and build an ErrorResponse from it to be used to push across the wire.As such, this method should be deleted in some future world. Anyone wondering how to serialize and deserialize a DruidException should look at
ErrorResponse
and leverage that instead of this.- Returns:
- an ErrorResponse
-
prependAndBuild
public DruidException prependAndBuild(String msg, Object... args)
Builds a new DruidException with a message that is the result of prepending the message passed as a parameter with the message already on the DruidException.- Parameters:
msg
- Message to be prepended, can be a Java format stringargs
- Arguments to be passed to the message if it is a Java format string- Returns:
- a new DruidException with prepended-message
-
-