diff --git a/classes/log/store.php b/classes/log/store.php index b3cbead61..a79e84280 100755 --- a/classes/log/store.php +++ b/classes/log/store.php @@ -115,6 +115,7 @@ public function process_events(array $events) { 'lrs_username' => $this->get_config('username', ''), 'lrs_password' => $this->get_config('password', ''), 'lrs_max_batch_size' => $this->get_max_batch_size(), + 'lrs_resend_failed_batches' => $this->get_config('resendfailedbatches', false), ], ]; $loadedevents = \src\handler($handlerconfig, $events); diff --git a/classes/task/emit_task.php b/classes/task/emit_task.php index dd024f948..a374a6444 100755 --- a/classes/task/emit_task.php +++ b/classes/task/emit_task.php @@ -41,6 +41,16 @@ private function get_failed_events($events) { return $failedevents; } + private function get_successful_events($events) { + $loadedevents = array_filter($events, function ($loadedevent) { + return $loadedevent['loaded'] === true; + }); + $successfulevents = array_map(function ($loadedevent) { + return $loadedevent['event']; + }, $loadedevents); + return $successfulevents; + } + private function get_event_ids($loadedevents) { return array_map(function ($loadedevent) { return $loadedevent['event']->id; @@ -61,13 +71,17 @@ private function delete_processed_events($events) { global $DB; $eventids = $this->get_event_ids($events); $DB->delete_records_list('logstore_xapi_log', 'id', $eventids); - mtrace("Events (".implode(', ', $eventids).") have been successfully sent to LRS."); } private function store_failed_events($events) { global $DB; $failedevents = $this->get_failed_events($events); $DB->insert_records('logstore_xapi_failed_log', $failedevents); + mtrace(count($failedevents) . " event(s) have failed to send to the LRS."); + } + + private function record_successful_events($events) { + mtrace(count($this->get_successful_events($events)) . " event(s) have been successfully sent to the LRS."); } /** @@ -81,6 +95,7 @@ public function execute() { $extractedevents = $this->extract_events($store->get_max_batch_size()); $loadedevents = $store->process_events($extractedevents); $this->store_failed_events($loadedevents); + $this->record_successful_events($loadedevents); $this->delete_processed_events($loadedevents); } } diff --git a/lang/en/logstore_xapi.php b/lang/en/logstore_xapi.php index d8bce2df1..77b33ee8c 100644 --- a/lang/en/logstore_xapi.php +++ b/lang/en/logstore_xapi.php @@ -49,3 +49,5 @@ $string['sendidnumber_desc'] = 'Statements will include the ID number (admin defined) for courses and activities in the object extensions'; $string['send_response_choices'] = 'Send response choices'; $string['send_response_choices_desc'] = 'Statements for multiple choice question answers will be sent to the LRS with the correct response and potential choices'; +$string['resendfailedbatches'] = 'Resend failed batches'; +$string['resendfailedbatches_desc'] = 'When processing events in batches, try re-sending events in smaller batches if a batch fails. If not selected, the whole batch will not be sent in the event of a failed event.'; diff --git a/settings.php b/settings.php index be04ce2c0..011cc9a8c 100644 --- a/settings.php +++ b/settings.php @@ -39,6 +39,10 @@ get_string('maxbatchsize', 'logstore_xapi'), get_string('maxbatchsize_desc', 'logstore_xapi'), 30, PARAM_INT)); + $settings->add(new admin_setting_configcheckbox('logstore_xapi/resendfailedbatches', + get_string('resendfailedbatches', 'logstore_xapi'), + get_string('resendfailedbatches_desc', 'logstore_xapi'), 0)); + $settings->add(new admin_setting_configcheckbox('logstore_xapi/mbox', get_string('mbox', 'logstore_xapi'), get_string('mbox_desc', 'logstore_xapi'), 0)); diff --git a/src/loader/lrs.php b/src/loader/lrs.php index b79516aec..0e25f900a 100644 --- a/src/loader/lrs.php +++ b/src/loader/lrs.php @@ -49,7 +49,7 @@ function load(array $config, array $events) { curl_close($request); if ($responsecode !== 200) { - throw new \Exception($responsetext); + throw new \Exception($responsetext, $responsecode); } }; return utils\load_in_batches($config, $events, $sendhttpstatements); diff --git a/src/loader/moodle_curl_lrs.php b/src/loader/moodle_curl_lrs.php index 278f2ee27..4cbd8308f 100644 --- a/src/loader/moodle_curl_lrs.php +++ b/src/loader/moodle_curl_lrs.php @@ -50,7 +50,7 @@ function load(array $config, array $events) { $responsecode = $request->info['http_code']; if ($responsecode !== 200) { - throw new \Exception($responsetext); + throw new \Exception($responsetext, $responsecode); } }; return utils\load_in_batches($config, $events, $sendhttpstatements); diff --git a/src/loader/utils/load_batch.php b/src/loader/utils/load_batch.php index 3ef49289d..df9af2738 100644 --- a/src/loader/utils/load_batch.php +++ b/src/loader/utils/load_batch.php @@ -28,10 +28,24 @@ function load_batch(array $config, array $transformedevents, callable $loader) { $loadedevents = construct_loaded_events($transformedevents, true); return $loadedevents; } catch (\Exception $e) { + $batchsize = count($transformedevents); $logerror = $config['log_error']; - $logerror("Failed load for event id #" . $eventobj->id . ": " . $e->getMessage()); + $logerror("Failed load batch (" . $batchsize . " events)" . $e->getMessage()); $logerror($e->getTraceAsString()); - $loadedevents = construct_loaded_events($transformedevents, false); + + // In the event of a 400 error, recursively retry sending statements in increasingly + // smaller batches so that only the actual bad data fails. + if ($batchsize === 1 || $e->getCode() !== 400 || $config['lrs_resend_failed_batches'] !== '1') { + $loadedevents = construct_loaded_events($transformedevents, false); + } else { + $newconfig = $config; + $newconfig['lrs_max_batch_size'] = round($batchsize / 2); + $batches = get_event_batches($newconfig, $transformedevents); + $loadedevents = array_reduce($batches, function ($result, $batch) use ($newconfig, $loader) { + $loadedbatchevents = load_batch($newconfig, $batch, $loader); + return array_merge($result, $loadedbatchevents); + }, []); + } return $loadedevents; } }