Error Handling in KRL


Errors for Marco

If you've been following along, you know that KRL, the Kinetic Rule Language, is an event processing language for processing events on behalf of an individual entity (usually a person). Think of it as the People's Complex Event Processing language.

For a long time, it's been difficult for KRL developers to handle errors. The language had no mechanism for exceptions and the only hope was to turn on debugging and fix them. Too often, however, we don't want to fix errors so much as handle them. Errors are not always problems in the code--bugs to be fixed--but exceptional conditions that a program needs to be ready for and deal with appropriately.

Most modern language have some kind of exception handling mechanism--some more sophisticated than others. I realized that exception handling is usually treated as a small event system within a larger imperative language. KRL, being an event language, is perfectly suited to handling errors--if only the system would raise them as events. As of today, it does.

The most recent update of the Kinetic Rules Engine provides support in KRL for the following features related to errors and error handling in KRL:

  • system errors - system errors (like applying an operator to an operand of an inappropriate type in a way that the system can't recover) will raise an event with event domain system and event type error. The event can have the following attributes:
    • msg - the error message as a text string
    • level - the error level, one of error, warn, info, of debug
    • genus - the major categorization of the error according to the event hierarchy shown in the documentation.
    • species - the minor categorization of the error according to the event hierarchy.
    • rid - the ruleset ID that caused the error
    • rule_name - the name of the rule that caused the error
  • explicit errors - as we will see below, developers can explicitly cause and error event to be raised. These errors have the event domain user and the event type error can have msg, level, rid, and rule_name attributes.

  • error redirection - the meta block now supports the inclusion of the errors to pragma which defines the ruleset that is handling errors for the current ruleset.

    meta {
     ...
     errors to a16x88 version "dev"
     ...
    }
    

    The version clause is optional.

  • error handling - the ErrorStack module supports sending errors to the ErrorStack online service. Of course, other actions can also be taken as appropriate.

Raising events on errors is a natural means of dealing with out of bounds conditions in a KRL ruleset since writing rules that respond to errors is a natural extension of the base behavior of the system. When the system raises an error event, or a developer causes one to be raised with an explicit error statement in the rule postlude, that event is evaluated by the engine in the current execution cycle and any rule that selects for that error with it's given event domain, type and attributes will be added to the evaluation schedule.

In the absence of the errors to pragma in the meta block, the rules in the current ruleset will be evaluated for selection. If the errors to pragma is present, the rules in the ruleset identified by that pragma will be be evaluation for selection. If a ruleset does not have any rules to handle the error event and no errors to pragma is present, the error event, like any other unhandled event, has no effect on the execution of the ruleset or the system.

The ErrorStack module is a simple example of how errors can be reported. ErrorStack is a service that provides an API for reporting errors that are then presented to the owner of the error stack with support for categorization, alerting, and visualization. When you sign up for the Error Stack service, you can create any number of different stacks to report errors into. A developer key uniquely identifies the errors stack that the errors are reported to.

The ErrorStack module has a single action defined, send_error, that takes a message. When the module is used in a ruleset (via the use module pragma) it is configured with the stack key that the developer receives from Error Stack. The following code fragment shows a rule that handles errors and reports them to an Error Stack using the send_error action:

rule process_error {
  select when system error
           or user error
  pre{
    genus = event:param("genus");
    species = event:param("species");
  }
  es:send_error("(#{genus}:#{species}) " + event:param("msg"))
       with rule_name = event:param("rule_name")
        and rid = event:param("rid");
}

The rule assumes that the following pragma was present in the ruleset's meta block and the developer key was stored in the ruleset's keychain:

use module a16x104 alias es
      with es_key = keys:errorstack()

Error Stack isn't the only way to handle errors. More generally, you could use the HTTP library to send the notification to some other API like notifio, the Twilio library to send an SMS, or even tweet the error. Alternately, there may be some mitigation to apply.

Developers can choose to raise their own errors using the error statement in the postlude. Technically, since an error is simply an event, developers have been able to do this using Explicit Event and still can. The error statement just makes it more convinient. The syntax of an explicit error statement is:

error <level> <expr>
where <level> is one of error, warn, info or debug and <expr> is an valid KRL expression that results in a string (or something that can be cast as a string such as a number). The following example would raise an event with domain user and type error with level info and a message with the value of a variable named query if the rule fired:
fired {
  error info "query:"+query
}

Every ruleset ought to have a catchall error handling rule like the one above or send their errors to a ruleset that's been created to handle errors. Error handling rules can, of course, be mode more specific with more sophisticated select statements, filtering on error genus and species as well as the error message itself. Error handlers that themselves causes errors won't cause an infinite recursion in any case as the system will catch those and break the event chain.