Class 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 using prependAndBuild(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, or ISE 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
    The user-facing message doesn't talk about any Druid-specific concepts and just tries to relay a high-level understanding of what happened. The admin-facing message includes Druid notions in it as it expects that an Admin will understand the various node types of Druid.

    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

    1. 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
    2. 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) or fromFailure(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 using ErrorResponse(DruidException) and then that ErrorResponse is used for serialization. DruidException carries a toErrorResponse() 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
    • 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.
      • getErrorCode

        public String getErrorCode()
      • getContextValue

        public String getContextValue​(String key)
      • 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 string
        args - Arguments to be passed to the message if it is a Java format string
        Returns:
        a new DruidException with prepended-message