Since the provision project is back end only, i need a mechanism to present errors to the front end.

To that end i've been using a simple provision_set_error() singleton pattern that uses a bitmask integer (with defines) to represent the error codes, at the end it returns the provision_get_errors() value with exit();

This is unfortunately not very useful for figuring out what goes wrong when drush is not working as it should be (a good example here would be DRUSH_DB_INACCESSIBLE_ERROR)

I have code here that does all that, but don't have the time to integrate it right now.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

moshe weitzman’s picture

Status: Active » Postponed (maintainer needs more info)

Not sure what to do here. Need code or more description of the proposed changes.

adrian’s picture

Status: Postponed (maintainer needs more info) » Needs work

Here's what we have at the moment :


/**
 * Set an error code for the error handling system.
 *
 * @param error_code
 *   Any of the defined error status definitions. A numerical bitmask value.
 * @return
 *   The current aggregate error status
 */
function provision_set_error($error_code = 0) {
  static $error = 0;

  if ($error_code) {
    $error = $error | (int) $error_code;    
  }

  return $error;
}

/**
 * Return the current error handling status
 *
 * @return
 *   The current aggregate error status
 */
function provision_get_error() {
  return provision_set_error();
}

/**
 * Check if a specific error status has been set.
 *
 * @param error
 *   Any of the defined error status definitions. A numerical bitmask value. 
 * @return
 *   TRUE if the specified error has been set, FALSE if not 
 */
function provision_cmp_error($error) {
  return provision_get_error() ^ $error;
}

And we set up errors like :


/** Queue not run */
define('PROVISION_QUEUED', 0);
/** Succesful **/
define('PROVISION_SUCCESS', 1);
/** Drupal was unable to complete it's installation */
define('PROVISION_INSTALL_ERROR', 2);
/** Could not create files due to permission error - potentially less severe */ 
define('PROVISION_PERM_ERROR', 4); 
/** Web server could not be restarted, or other server related issues - less severe */
define('PROVISION_WEB_ERROR', 8);
/** To be used while testing if the provision framework is actually working. */
define('PROVISION_FRAMEWORK_ERROR', 16);
/** When the site is not available on this platform, or has not been installed. */
define('PROVISION_SITE_NOT_FOUND', 32);
/** When the site is already installed, ie: there is a conflict. */
define('PROVISION_SITE_INSTALLED', 64);
/** Database could not be accessed, or configured */
define('PROVISION_DB_ERROR', 128);

//List of provisioning errors. This is a global so that other modules can reference it.
global $provision_errors;
$provision_errors = array(
  PROVISION_SUCCESS => pt("Successful"),
  PROVISION_QUEUED => pt("Queued"),
  PROVISION_DB_ERROR => pt("Database error"),
  PROVISION_INSTALL_ERROR => pt("Drupal installation error"),
  PROVISION_PERM_ERROR => pt("File permission error"),
  PROVISION_WEB_ERROR => pt("Web server error"),
  PROVISION_FRAMEWORK_ERROR => pt("Provision framework error"),
  PROVISION_SITE_NOT_FOUND => pt("Site not found"),
  PROVISION_SITE_INSTALLED => pt("Site has already been installed"),  
  );

So you just exit with exit(provision_get_error());

This obviously needs to be generalized for drush, but it is also extensible.

adrian’s picture

Status: Needs work » Needs review
FileSize
5.69 KB

Here is a patch for drush, that keeps the same structure as the provision code for the error handling.

It might be a bit too provision specific (for instance provision_web_error is only applicable when you are directly interacting with the web server, although could be re-used for trying to download files with wget and the like).

This code has been tested thoroughly in provision, and is the basis of a whole mess of neat functionality we discussed previously.

What's left to do is implementing the error codes intelligently across the board, and it also probably means that drush_die has to .. die. By exiting, you stop the ability to handle errors that may have occurred and provide rollback to the previous system state.

moshe weitzman’s picture

Yeah, I think we should move some of the error codes to provision. BUt that begs another question - how are modules supposed to define their own error codes and have them properly namespaced. We use module prefixes for namespacing so I wonder if it would work to use string based error codes. IDs are poorly extensible by modules - we have this problem with filter formats and build_modes for nodes.

I hope others take a look at this patch and give some feedback.

Likely needs reroll now that command arguments is in.

adrian’s picture

Status: Needs review » Needs work

I think i've figured out the following general error codes :

1 = Success. If there are no error codes, this is returned.
2 = Command not found.
4 = Drupal bootstrap error - IE: couldn't find a settings.php even tho we needed one
8 = Drupal DB error. - We have discussed this before, basically, we need to use output buffering and the shutdown function to catch the exit() from db_set_active.
16 = File permission error - This is very common so could use a constant.
32 = Framework / Application error.
255 = Generic PHP error. This means the script could not be interpreted, etc.

drush_set_error would then accept either one of the constants we define, or a string.

When a string is passed, it will automatically add a 32 to the bitmask, and store the errors in an associative array.

We can use the drush_help hook to provide the text to be used when an error occurs. (ie: instead of the inline printing, the messages will be mapped to keys in the error array)

When in verbose mode, the error is immediately printed.

When in backend mode (a patch I also want to introduce, that allows drush commands to call each other),
it will return the errors and the exit code along with the module defined errors/ error messages.

This allows the errors to be extendable infinitely, and gives calling scripts the ability to access the specific error messages they need.

This ties into the following issue for hosting/provision : http://drupal.org/node/275511

moshe weitzman’s picture

the new list looks much smaller and neater ... note my question earlier about module defined errors and numeric vs. string.

adrian’s picture

Title: Documented exit codes, with checking for if anything goes wrong » Drush error handling API
Status: Needs work » Needs review
FileSize
11.39 KB

Here is an updated patch for the logging system. It's behaviour is as follows :

1) it provides a small subset of the most common, and nondescript errors that could occur, defined as constants.
2) One of the error constants is a 'catch all' used for extending in your own way.
3) You can also provide a simple string (or define your own constant matching that string).
a) if you specify the optional message, it will log it for you using drush_log.
b) If a message is not specified, it will call hook_drush_help with the parameter or 'error:MY_ERROR_STRING'
c) If no message is found in help, it will use the error string itself as the error.
4) Scripts can use drush_get_errors() at any point to check if an error status has occurred.
5) If you need to check if a specific error has occurred, you can use drush_cmp_error('MY_ERROR_STRING').

This gives a script the ability to do things like flag that a download has failed, and recover more gracefully in that situation.

This feature is heavily used in the proposed drush_invoke() api, which allows you to extend any drush command by placing code either before, during or after the execution of that command. If an error is generated at any time during the invocation of the script, it
will then allow you to reverse changes gracefully.

moshe weitzman’s picture

Looks OK to me at first glance, though I'm not really tuned in to all that this enables. I would love reviews from others on this.

I noticed a few PROVISION_FRAMEWORK_ERROR which should be changed to DRUSH_FRAMEWORK_ERROR I think.

adrian’s picture

FileSize
11.38 KB

Changed to drush_framework_error.

The basic idea is that this API gives us a central place to log and track errors, so that instead of having to check return codes for all the various things that can go wrong, that then need to be bubbled up through the API, you can just use drush_get_errors().

The expanded error statuses are also accessible to external calling applications.

adrian’s picture

FileSize
11.97 KB

I've cleaned it up a bit, so that any errors that get stored in the error control array, are now stored with their textual representation,
this simplified both the logging and the drush_cmp_error function, which is used to detect if a specific error has occurred.

In drupal terms, this API is analogous to form_set_error/form_get_errors, with the added benefits that errors also modify the return code, and the message can be provided by hook_drush_help.

The logging API that got committed, is analogous to drupal_get_messages and drupal_set_message. I actually have a function which I need to make a patch for, that will integrate any messages that drupal generates, and any PHP errors that occur, into the central logging system. So if you were writing a command that does a drupal_execute, any validation errors would also be displayed through the central logging system.

The invoke API is somewhat similar to FAPI in design, without the tree building and nested iteration, and it uses the above API in a very similar way that fapi does (ie: in validate you can trigger an error to stop execution of the _submit calback). It has the added benefit in that triggering an error causes a cleanup/rollback phase, where you can gracefully recover from filesystem manipulations etc.

The benefits to drush of these API's, is also similar in nature to the benefits of FAPI to core.

adrian’s picture

Here's a related patch for handling the DRUSH_DRUPAL_DB_ERROR status : http://drupal.org/node/374765

adrian’s picture

FileSize
11.95 KB

Here's my latest version of the patch.

moshe weitzman’s picture

Status: Needs review » Fixed

Committed after reviewing code and some testing. Thx Adrian.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.