Skip to content

EVF Tutorial Errors

Paul Rogers edited this page Jun 18, 2019 · 2 revisions

Better Error Reporting

One of the typical user complaints about Drill (or really any Java-based service) is that errors often include a wonderfully robust set of stack traces, but no actual information about what went wrong in a form that helps the user. You can help your user by providing good error messages. We'll discus two techniques for doing so.

UserException

Drill, as a Java application is a bit odd: it does not use the Java checked exception feature. If it did, you'd know what kind of exception to throw when things go wrong. Instead, Drill uses unchecked exceptions. But, which one should you use?

You can actually use any kind you like (IllegalArgumentException, IllegalStateException and so on.) EVF will catch such exceptions and translate them to Drill's UserException class. (The name means, "exception to be sent to the user", not "exception caused by the user.")

UserException provides context information to help the user understand where things went off the rails. While EVF will try to guess if you use your own exceptions, you can do a better job by throwing UserException directly.

The log reader we looked at earlier has some nice examples:

        throw UserException
            .validationError()
            .message("Undefined column types")
            .addContext("Position", patternIndex)
            .addContext("Field name", name)
            .addContext("Type", typeName)
            .build(logger);

UserException works by creating a builder for the type of error you want to throw. For a reader, validationError() or dataReadError() are the most common. Use validation error if something in the setup fails. Use the read error for problems with external data sources or files. You can also use systemError if you detect an internal error, such as a violation of an invariant, that indicates a bug in Drill rather than a problem that the user can fix.

The addContext() methods are key: they allow you to fill in additional information to tell the user what went wrong.

Adding a Custom Error Context

EVF maintains additional context, such as the name of the plugin and the name of the table. You can use that context as follows. Once you've created a ResultSetLoader, you can obtain the error context from the loader:

      throw UserException
          .dataReadError(e)
          .message("Failed to open input file")
          .addContext("File path:", split.getPath())
          .addContext(loader.context())
          .build(logger);

The ResultSetLoader.context() provides context for the plugin as a whole, and for the file currently being read. It will fill in information such as the plugin name, the plugin class name, the file name, the user name and so on.

Errors Found Before the Loader is Built

The log format plugin first parses the config, then creates the ResultSetLoader. In this case, we can use the parent error context: the one that provides just the plugin context. For example:

  @Override
  public boolean open(FileSchemaNegotiator negotiator) {
    split = negotiator.split();
    setupPattern(negotiator.parentErrorContext());
    ...
  }

  private void setupPattern(CustomErrorContext errorContext) {
    try {
      // Turns out the only way to learn the capturing group count
      // is to create a matcher. We do so with a dummy string.

      Matcher m = pattern.matcher("dummy");
      capturingGroups = m.groupCount();
    } catch (PatternSyntaxException e) {
      throw UserException
          .validationError(e)
          .message("Failed to parse regex: \"%s\"", formatConfig.getRegex())
          .addContext(errorContext)
          .build(logger);
    }
  }

(Note: you won't see the above in actual source code: pattern parsing moved into the plugin for other reasons. The above however, illustrates the parent context idea.)

Customize the Error Context

You can customize the context with more information about your plugin or reader. You create an error context object which you pass to the SchemaNegotiator object in your open() method:

RowSetContext errorContext;

  @Override
  public boolean open(FileSchemaNegotiator negotiator) {
    CustomErrorContext myContext = new ChildErrorContext(negotiator.parentErrorContext()) {

    @Override
    public void addContext(UserException.Builder builder) {
      super.addContext(builder);
      builder.addContext("Your field", yourValue);
    }
    negotiator.setErrorContext(myContext)
    ...

The context you just built will be the one that the ResultSetLoader returns from its context() method.

This is a bit of an advanced topic; ask on the dev list if you want the details.


Next: Discover Schema While Reading

Clone this wiki locally