You are here

services_client_error.module in Services Client 7.2

Same filename and directory in other branches
  1. 7 services_client_error/services_client_error.module

Services Client error handling, re-try and reporting.

File

services_client_error/services_client_error.module
View source
<?php

/**
 * @file
 * Services Client error handling, re-try and reporting.
 */
define('SC_ERROR_UNPROCESSED', 0);
define('SC_ERROR_FAILED', 1);
define('SC_ERROR_OVERWRITTEN', 2);
define('SC_ERROR_COMPLETED', 3);

/**
 * Implements hook_menu().
 */
function services_client_error_menu() {
  $items = array();
  $items['admin/structure/services_client/errors'] = array(
    'title' => 'Errors',
    'page callback' => 'services_client_error_admin_list',
    'access arguments' => array(
      'administer services client',
    ),
    'file' => 'services_client_error.admin.inc',
    'type' => MENU_LOCAL_TASK,
    'weight' => 100,
  );
  $items['admin/structure/services_client/errors/%services_client_error/delete'] = array(
    'title' => 'Delete error',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'services_client_error_admin_delete_confirm',
      4,
    ),
    'access arguments' => array(
      'administer services client',
    ),
    'file' => 'services_client_error.admin.inc',
    'type' => MENU_CALLBACK,
  );
  $items['admin/structure/services_client/errors/%services_client_error/log'] = array(
    'title' => 'Error history',
    'page callback' => 'services_client_error_admin_log_list',
    'page arguments' => array(
      4,
    ),
    'access arguments' => array(
      'administer services client',
    ),
    'file' => 'services_client_error.admin.inc',
    'type' => MENU_CALLBACK,
  );
  $items['admin/structure/services_client/errors/%services_client_error/retry'] = array(
    'title' => 'Error retry',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'services_client_error_admin_repair',
      4,
    ),
    'access arguments' => array(
      'administer services client',
    ),
    'file' => 'services_client_error.admin.inc',
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function services_client_error_form_services_client_settings_alter(&$form, $form_state) {
  $form['error_settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('Error handling'),
    '#collapsed' => TRUE,
    '#collapsible' => TRUE,
    '#tree' => FALSE,
    '#group' => 'additional_settings',
    '#weight' => 0,
  );
  $form['error_settings']['services_client_error_retries'] = array(
    '#type' => 'textfield',
    '#title' => t('Retries'),
    '#default_value' => variable_get('services_client_error_retries', 3),
    '#description' => t('How many reties before error will be marked as failed'),
  );
  $form['error_settings']['services_client_error_remove_period'] = array(
    '#type' => 'textfield',
    '#title' => t('Remove old errors'),
    '#default_value' => variable_get('services_client_error_remove_period', 7),
    '#description' => t('Remove old errors after specified time of days'),
  );
  $form['error_settings']['services_client_error_notify'] = array(
    '#type' => 'checkbox',
    '#title' => t('Send notification on errors'),
    '#default_value' => variable_get('services_client_error_notify', FALSE),
    '#description' => t('Send email notifications on services client errors'),
  );
  $form['error_settings']['services_client_error_notify_recipients'] = array(
    '#type' => 'textarea',
    '#title' => t('Notification recipients'),
    '#default_value' => variable_get('services_client_error_notify_recipients', ''),
    '#description' => t('Enter one email address per row'),
    '#states' => array(
      'visible' => array(
        ':input[name="services_client_error_notify"]' => array(
          'checked' => TRUE,
        ),
      ),
    ),
  );
}

/**
 * Implements hook_cron().
 */
function services_client_error_cron() {
  $timestamp = time() - variable_get('services_client_error_remove_period', 7) * 24 * 60 * 60;
  $eids = db_query_range("SELECT eid FROM {services_client_error} WHERE created < :time", 0, 50, array(
    ':time' => $timestamp,
  ))
    ->fetchCol();
  if (!empty($eids)) {

    // Delete log history
    db_delete('services_client_error_log')
      ->condition('eid', $eids)
      ->execute();

    // Delete error.
    db_delete('services_client_error')
      ->condition('eid', $eids)
      ->execute();
  }
}

/**
 * Map of available statuses of error.
 *
 * @return array
 *   Array of all statuses
 */
function services_client_error_status_map() {
  static $map = array();
  if (empty($map)) {
    $map = array(
      SC_ERROR_UNPROCESSED => t('Unprocessed'),
      SC_ERROR_FAILED => t('Failed'),
      SC_ERROR_OVERWRITTEN => t('Overwritten'),
      SC_ERROR_COMPLETED => t('Completed'),
    );
  }
  return $map;
}

/**
 * Retrieve status title by code.
 *
 * @param int $status
 *   Status ID.
 *
 * @return string
 *   Status title.
 */
function services_client_error_status_title($status) {
  $map = services_client_error_status_map();
  return isset($map[$status]) ? $map[$status] : 'N/A';
}

/**
 * Implements hook_services_client_process_errors().
 */
function services_client_error_services_client_process_events($events) {
  $errors = array();

  // Store errors for further processing.
  foreach ($events as $result) {

    // Don't handle successful calls and loop errors.
    if (!$result
      ->success() && $result->error_type != ServicesClientErrorType::LOOP) {
      services_client_error_save($result);
      $queue_data = array(
        'title' => t('Processing services client error !eid', array(
          '!eid' => $result
            ->getEntityId(),
        )),
        'eid' => $result->eid,
      );
      queue_runner_add($queue_data, 'sce_retry_call', variable_get('services_client_error_retries', 3));
      $errors[] = $result;
    }
  }
}

/**
 * Implements hook_sc_process_data().
 */
function services_client_error_services_client_before_request($handler, $object) {

  // Don't react on syncs triggred by services client error handling.
  if ($handler
    ->hasTag('sc_error')) {
    return;
  }

  // Load entity_id
  $event = $handler
    ->getEvent();
  list($id) = entity_extract_ids($event->entity_type, $handler
    ->getEntity());

  // Get list of unprocessed errors, that are related to entity. Mark them as
  // overwritten and store new data.
  $result = db_query("SELECT eid FROM {services_client_error} WHERE entity_type = :type AND entity_id = :id AND status = :status AND event = :event", array(
    ':type' => $event->entity_type,
    ':id' => $id,
    ':status' => SC_ERROR_UNPROCESSED,
    ':event' => $event->name,
  ))
    ->fetchAllKeyed(0, 0);

  // No errors are unprocessed.
  if (empty($result)) {
    return;
  }

  // Mark all as overwritten.
  db_update('services_client_error')
    ->fields(array(
    'status' => SC_ERROR_OVERWRITTEN,
  ))
    ->condition('eid', $result, 'IN')
    ->execute();
  foreach ($result as $eid) {
    $log = array(
      'eid' => $eid,
      'message' => 'Overwritten by data update.',
      'status_change' => SC_ERROR_OVERWRITTEN,
      'entity' => $handler
        ->getEntity(),
    );
    services_client_error_log_save($log);
  }
}

/**
 * Send email notification with error.
 *
 * @param ServicesClientEventResult $error
 *   Error that occured during sync.
 */
function services_client_error_notify($error) {

  // Send email notification
  if (variable_get('services_client_error_notify', FALSE)) {
    $recipients = explode("\n", variable_get('services_client_error_notify_recipients', ''));
    foreach ($recipients as $mail) {
      $mail = trim($mail);
      if (valid_email_address($mail)) {
        $params = array(
          'errors' => array(
            $error,
          ),
        );
        drupal_mail('services_client_error', 'notify_errors', $mail, language_default(), $params);
      }
    }
  }
}

/**
 * Implements hook_mail().
 */
function services_client_error_mail($key, &$message, $params) {
  if ($key == 'notify_errors') {
    $message['subject'] = t('SC ERROR: c: !errors, site: @id (@site)', array(
      '!errors' => count($params['errors']),
      '@id' => services_client_get_id(),
      '@site' => variable_get('site_name', 'Drupal'),
    ));
    foreach ($params['errors'] as $error) {
      $data = array();
      $data['entity_type'] = $error->entity_type;
      $data['entity_id'] = $error
        ->getEntityId();
      $uri = entity_uri($error->entity_type, $error->entity);
      $data['entity_url'] = url($uri['path'], $uri['options'] + array(
        'absolute' => TRUE,
      ));
      $data['error'] = $error->error_code . ' ' . $error->error_message;
      if (!empty($error->eid)) {
        $data['error_url'] = url('admin/structure/services_client/errors/' . $error->eid . '/log', array(
          'absolute' => TRUE,
        ));
      }
      $row = array(
        '------------------------------',
      );
      foreach ($data as $id => $val) {
        $row[] = $id . ': ' . $val;
      }
      $message['body'][] = implode("\n", $row);
    }
  }
}

/**
 * Implements hook_queue_runner_workers().
 */
function services_client_error_queue_runner_workers() {
  return array(
    'sce_retry_call' => array(
      'callback' => 'services_client_error_task_retry_call',
      'includes' => array(
        array(
          'file_extension' => 'inc',
          'module' => 'services_client_error',
          'file' => 'services_client_error.tasks',
        ),
      ),
      'finalize' => 'services_client_error_task_finalize',
    ),
  );
}

/**
 * Load error by id.
 *
 * @param int $eid
 *   ID of error.
 *
 * @return array
 *   Error record by {services_client_error} data.
 */
function services_client_error_load($eid) {
  $result = db_query("SELECT * FROM {services_client_error} WHERE eid = :eid", array(
    ':eid' => $eid,
  ))
    ->fetchAssoc();
  if ($result) {
    $result['entity'] = unserialize($result['entity']);
  }
  return $result;
}

/**
 * Save information about error to data log
 *
 * @param $data
 *   Error data from @services_client_data_process
 */
function services_client_error_save(ServicesClientEventResult $result) {
  $row = array(
    'created' => time(),
    'entity_type' => $result->entity_type,
    'entity_id' => $result
      ->getEntityId(),
    'event' => $result->event->name,
    'entity' => $result->entity,
    'error_code' => $result->error_code,
    'error_message' => $result->error_message,
    'retries' => 0,
    'result' => 0,
  );
  drupal_write_record('services_client_error', $row);
  $result->eid = $row['eid'];
}

/**
 * Save history item for services client error.
 *
 * @param array $data
 *   History data.
 */
function services_client_error_log_save($data) {
  $row = $data + array(
    'created' => time(),
  );
  if (empty($row['uid'])) {
    global $user;
    $row['uid'] = $user->uid;
  }
  if (empty($row['eid'])) {
    throw new Exception("Missing eid when saving error log item.");
  }
  return drupal_write_record('services_client_error_log', $row);
}

/**
 * Remove error from error log
 *
 * @param $eid
 *   ID of error.
 */
function services_client_error_delete($eid) {

  // Delete log history
  db_delete('services_client_error_log')
    ->condition('eid', $eid)
    ->execute();

  // Delete error.
  return db_delete('services_client_error')
    ->condition('eid', $eid)
    ->execute();
}

/**
 * Retry and execute error
 *
 * @param array $error
 *   Error record from DB.
 *
 * @return ServicesClientEventResult
 *   Reslt from sync operation.
 */
function services_client_error_retry($error) {
  if (empty($error['event'])) {
    return;
  }

  // Count error retry
  $error['retries']++;
  $handler = services_client_get_event($error['event']);
  $result = $handler
    ->addTag('sc_error')
    ->setEntity($error['entity'])
    ->execute();
  if ($result
    ->success()) {
    $error['status'] = $result->sc_error_status = SC_ERROR_COMPLETED;
  }
  else {
    $error['error_code'] = $result->error_code;
    $error['error_message'] = $result->error_message;
  }

  // Count retries.
  $result->sc_error_retries = $error['retries'];
  drupal_write_record('services_client_error', $error, array(
    'eid',
  ));
  return $result;
}

Functions

Namesort descending Description
services_client_error_cron Implements hook_cron().
services_client_error_delete Remove error from error log
services_client_error_form_services_client_settings_alter Implements hook_form_FORM_ID_alter().
services_client_error_load Load error by id.
services_client_error_log_save Save history item for services client error.
services_client_error_mail Implements hook_mail().
services_client_error_menu Implements hook_menu().
services_client_error_notify Send email notification with error.
services_client_error_queue_runner_workers Implements hook_queue_runner_workers().
services_client_error_retry Retry and execute error
services_client_error_save Save information about error to data log
services_client_error_services_client_before_request Implements hook_sc_process_data().
services_client_error_services_client_process_events Implements hook_services_client_process_errors().
services_client_error_status_map Map of available statuses of error.
services_client_error_status_title Retrieve status title by code.

Constants

Namesort descending Description
SC_ERROR_COMPLETED
SC_ERROR_FAILED
SC_ERROR_OVERWRITTEN
SC_ERROR_UNPROCESSED @file Services Client error handling, re-try and reporting.