diff -Nrup services/services.module services_patched/services.module
--- services/services.module	2008-09-07 01:17:30.000000000 +0200
+++ services_patched/services.module	2009-01-14 00:12:28.750000000 +0100
@@ -241,14 +241,22 @@ function services_error($message) {
   return $message;
 }
 
+function services_access_log($loglevel, $method_name, $args, $method_requires_key = FALSE, $api_key = '') {
+  if (module_exists('services_statistics')) {
+  	services_statistics_access_log($loglevel, $method_name, $args, $method_requires_key, $api_key);
+  }
+}
+
 /**
  * This is the magic function through which all remote method calls must pass.
  */
 function services_method_call($method_name, $args = array()) {
   $method = services_method_get($method_name);
+  $original_args = $args;
 
   // Check that method exists.
   if (empty($method)) {
+    services_access_log(SERVICES_STATS_UNKNOWNMETHOD, $method_name, $original_args);
     return services_error(t('Method %name does not exist.', array('%name' => $method_name)));
   }
 
@@ -257,6 +265,7 @@ function services_method_call($method_na
   foreach ($method['#args'] as $key => $arg) {
     if (!$arg['#optional']) {
       if (!is_numeric($args[$key]) and empty($args[$key])) {
+        services_access_log(SERVICES_STATS_MISSINGREQUIREDARGUMENTS, $method_name, $original_args, $method['#key']);
         return services_error(t('Missing required arguments.'));
       }
     }
@@ -286,6 +295,7 @@ function services_method_call($method_na
     $expiry_time = $timestamp + variable_get('services_key_expiry', 30);
 
     if ($expiry_time < time()) {
+      services_access_log(SERVICES_STATS_EXPIREDTOKEN, $method_name, $original_args, TRUE);
       return services_error(t('Token has expired.'));
     }
 
@@ -293,6 +303,7 @@ function services_method_call($method_na
     if (db_result(db_query("SELECT count(*) FROM {services_timestamp_nonce}
         WHERE domain = '%s' AND timestamp = %d AND nonce = '%s'",
         $domain, $timestamp, $nonce))) {
+      services_access_log(SERVICES_STATS_USEDTOKEN, $method_name, $original_args, TRUE);
       return services_error(t('Token has been used previously for a request.'));
     }
     else{
@@ -302,9 +313,15 @@ function services_method_call($method_na
 
     $api_key = db_result(db_query("SELECT kid FROM {services_keys} WHERE domain = '%s'", $domain));
 
-    if (!services_validate_key($api_key, $timestamp, $domain, $nonce, $method_name, $hash_parameters, $hash)) {
+    if (!$api_key) {
+      services_access_log(SERVICES_STATS_INVALIDKEY, $method_name, $original_args, TRUE);
       return services_error(t('Invalid API key.'));
     }
+
+    if (!services_validate_key($api_key, $timestamp, $domain, $nonce, $method_name, $hash_parameters, $hash)) {
+      services_access_log(SERVICES_STATS_INVALIDHASH, $method_name, $original_args, TRUE, $api_key);
+      return services_error(t('Invalid hash.'));
+    }
   }
 
   // Add additonal processing for methods requiring authentication
@@ -312,6 +329,7 @@ function services_method_call($method_na
   if ($method['#auth'] and variable_get('services_use_sessid', TRUE)) {
     $sessid = array_shift($args);
     if (empty($sessid)) {
+      services_access_log(SERVICES_STATS_INVALIDSESSION, $method_name, $original_args, $method['#key']);
       return services_error(t('Invalid sessid.'));
     }
     $session_backup = services_session_load($sessid);
@@ -326,6 +344,7 @@ function services_method_call($method_na
   $access_arguments = isset($method['#access arguments']) ? $method['#access arguments'] : $args;
   // Call default or custom access callback
   if (call_user_func_array($method['#access callback'], $access_arguments) != TRUE) {
+    services_access_log(SERVICES_STATS_ACCESSDENIED, $method_name, $original_args, $method['#key']);
     return services_error(t('Access denied.'));
   }
 
@@ -346,6 +365,7 @@ function services_method_call($method_na
     services_session_unload($session_backup);
   }
 
+  services_access_log(SERVICES_STATS_SUCCESS, $method_name, $original_args, $method['#key']);
   return $result;
 }
 
diff -Nrup services/services_statistics/services_statistics.info services_patched/services_statistics/services_statistics.info
--- services/services_statistics/services_statistics.info	1970-01-01 01:00:00.000000000 +0100
+++ services_patched/services_statistics/services_statistics.info	2009-01-14 00:06:09.750000000 +0100
@@ -0,0 +1,6 @@
+; $Id$
+name = Services log
+description = Logs and records services access.
+package = Services - optional
+dependencies[] = services
+core = 6.x
\ No newline at end of file
diff -Nrup services/services_statistics/services_statistics.install services_patched/services_statistics/services_statistics.install
--- services/services_statistics/services_statistics.install	1970-01-01 01:00:00.000000000 +0100
+++ services_patched/services_statistics/services_statistics.install	2009-01-14 00:07:36.375000000 +0100
@@ -0,0 +1,75 @@
+<?php
+// $Id$
+
+/**
+ * Implementation of hook_install().
+ */
+function services_statistics_install() {
+  drupal_install_schema('services_statistics');
+}
+
+/**
+ * Implementation of hook_uninstall().
+ */
+function services_statistics_uninstall() {
+  drupal_uninstall_schema('services_statistics');
+}
+
+/**
+ * Implementation of hook_schema().
+ */
+function services_statistics_schema() {
+  $schema['services_log'] = array(
+    'description' => t('Stores services access information for statistics.'),
+    'fields' => array(
+      'lid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => t('Primary Key: Unique services log ID.'),
+      ),
+      'timestamp' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('Timestamp of when the service method was called.'),
+      ),
+      'loglevel' => array(
+        'type' => 'int',
+        'size' => 'small',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => t('How far did the service request get? Saves \'succes\' on completion.'),
+      ),
+      'method' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Name of service method.'),
+      ),
+      'arguments' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Method arguments (serialized).'),
+      ),
+      'kid' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('The API key (if available).'),
+      ),
+    ),
+    'indexes' => array(
+      'services_log_timestamp' => array('timestamp'),
+      'method_name' => array('method'),
+      'api_key' => array('kid'),
+    ),
+    'primary key' => array('lid'),
+  );
+
+  return $schema;
+}
diff -Nrup services/services_statistics/services_statistics.module services_patched/services_statistics/services_statistics.module
--- services/services_statistics/services_statistics.module	1970-01-01 01:00:00.000000000 +0100
+++ services_patched/services_statistics/services_statistics.module	2009-01-14 00:18:56.781250000 +0100
@@ -0,0 +1,112 @@
+<?php
+// $Id$
+/**
+ * @file
+ *  Enable statistics for services module.
+ */
+
+/**
+ * Define log levels
+ */
+define('SERVICES_STATS_UNKNOWNMETHOD', 2);
+define('SERVICES_STATS_MISSINGREQUIREDARGUMENTS', 3);
+define('SERVICES_STATS_EXPIREDTOKEN', 4);
+define('SERVICES_STATS_USEDTOKEN', 5);
+define('SERVICES_STATS_INVALIDHASH', 6);
+define('SERVICES_STATS_INVALIDKEY', 7);
+define('SERVICES_STATS_INVALIDSESSION', 8);
+define('SERVICES_STATS_ACCESSDENIED', 9);
+define('SERVICES_STATS_SUCCESS', 100);
+
+/**
+ * Implementation of hook_menu().
+ */
+function services_statistics_menu() {
+  $items['admin/reports/services'] = array(
+    'title'             => 'Services access log',
+    'description'       => 'View services\'s access logs.',
+    'page callback'     => 'services_statistics_overview',
+    'access arguments'  => array('administer services'),
+  );
+
+  return $items;
+}
+
+/**
+ * Page showing log entries
+ */
+function services_statistics_overview() {
+  $output = '<p>'.t('Shows wich methods have been accessed, when, which API key was used (if available) and which log level was reached.').'</p>';
+
+  $sql = 'SELECT loglevel, timestamp, method, kid FROM {services_log}';
+  $limit = 20;
+  $header = array(
+    array('data' => t('Timestamp'), 'field' => 'timestamp', 'sort' => 'desc'),
+    array('data' => t('Level'), 'field' => 'loglevel'),
+    array('data' => t('Method'), 'field' => 'method'),
+    array('data' => t('API key'), 'field' => 'kid')
+  );
+  $tablesort = tablesort_sql($header);
+  $result = pager_query($sql . $tablesort, $limit);
+  $rows = array();
+  while ($log = db_fetch_object($result)) {
+    $rows[] = array(
+      date('d/m/y H:i:s', $log->timestamp), 
+      services_statistics_loglevel_tostring($log->loglevel), 
+      check_plain($log->method), 
+      check_plain($log->kid)
+    );
+  }
+  if (!count($rows)) {
+    $rows[] = array(array('data' => t('No statistics yet.'), 'colspan' => 4));
+  }
+  $output .= theme('table', $header, $rows);
+  $output .= theme('pager', NULL, $limit, 0);
+
+  return $output;
+}
+
+function services_statistics_loglevel_tostring($loglevel) {
+  switch ($loglevel) {
+  	case SERVICES_STATS_UNKNOWNMETHOD:
+  		return t('unknown method');
+  		break;
+  	case SERVICES_STATS_MISSINGREQUIREDARGUMENTS:
+  		return t('missing required arguments');
+  		break;
+  	case SERVICES_STATS_EXPIREDTOKEN:
+  		return t('token expired');
+  		break;
+  	case SERVICES_STATS_USEDTOKEN:
+  		return t('token has been used before');
+  		break;
+  	case SERVICES_STATS_INVALIDHASH:
+  		return t('invalid hash');
+  		break;
+  	case SERVICES_STATS_INVALIDKEY:
+  		return t('invalid API key');
+  		break;
+  	case SERVICES_STATS_INVALIDSESSION:
+  		return t('invalid session');
+  		break;
+  	case SERVICES_STATS_ACCESSDENIED:
+  		return t('access denied');
+  		break;
+  	case SERVICES_STATS_SUCCESS:
+  		return t('success');
+  		break;
+  	default:
+  		return t('unknown log level');
+  		break;
+  }
+}
+
+function services_statistics_access_log($loglevel, $method_name, $args, $method_requires_key = FALSE, $api_key = '') {
+  if ($method_requires_key && variable_get('services_use_key', TRUE) && empty($api_key)) {
+    $api_key = db_result(db_query("SELECT kid FROM {services_keys} WHERE domain = '%s'", $args[1]));
+  }
+  
+  db_query("INSERT INTO {services_log} (timestamp, loglevel, method, arguments, kid) 
+              values(%d, %d, '%s', '%s', '%s')", 
+      time(), $loglevel, $method_name, serialize($args), $api_key);
+}
