Retries and Action Logs
Core Forms provides automatic retry logic for failed actions and structured logging for every action execution.
Automatic Retries
When an action fails during background processing, it is automatically retried with exponential backoff:
| Attempt | Delay | Scheduled At |
|---|---|---|
| 1st retry | 2 minutes | now + 120s |
| 2nd retry | 4 minutes | now + 240s |
| 3rd retry | 8 minutes | now + 480s |
After 3 failed attempts (configurable via max_retries), the action is marked as failed.
Backoff Formula
delay = 2^attempts * 60 seconds
For attempt 1: 2^1 * 60 = 120s (2 minutes)
For attempt 2: 2^2 * 60 = 240s (4 minutes)
For attempt 3: 2^3 * 60 = 480s (8 minutes)
How Retries Work
// On failure, the queue updates the item:
if ( $attempts <= $max_retries ) {
$delay = pow( 2, $attempts ) * 60;
// Status set to 'retrying', scheduled_at set to future time
$wpdb->update( $table, [
'status' => 'retrying',
'attempts' => $attempts,
'last_error' => $e->getMessage(),
'scheduled_at' => gmdate( 'Y-m-d H:i:s', time() + $delay ),
], [ 'id' => $item->id ] );
} else {
// All retries exhausted
$wpdb->update( $table, [
'status' => 'failed',
'attempts' => $attempts,
'last_error'=> $e->getMessage(),
], [ 'id' => $item->id ] );
}
Manual Retry
Failed actions can be manually retried via the REST API. This resets the attempt counter and re-queues the item:
# Retry a specific failed action
curl -X POST \
https://example.com/wp-json/cf/v1/action-queue/42/retry \
-H "X-WP-Nonce: {nonce}"
// Programmatic retry
use Core_Forms\Workflows\ActionQueue;
ActionQueue::retry( $queue_id ); // Returns true if reset for retry
The retry method only works on items with failed status. It resets attempts to 0 and sets scheduled_at to now.
Action Logger
The ActionLogger records every action execution with structured metadata. Logs are stored in the wp_cf_action_logs table.
Log Entry Structure
| Column | Type | Description |
|---|---|---|
id |
INT | Primary key |
queue_id |
INT | Queue item ID (0 for synchronous actions) |
form_id |
INT | Form post ID |
submission_id |
INT | Submission ID |
action_type |
VARCHAR(50) | Action type (e.g., email, webhook) |
status |
VARCHAR(20) | success, failed, or skipped |
message |
TEXT | Human-readable message or error details |
meta |
LONGTEXT | JSON metadata (request/response data) |
created_at |
TIMESTAMP | When the log entry was created |
Logging an Action
use Core_Forms\Workflows\ActionLogger;
// Log a successful action
ActionLogger::log(
$queue_id, // 0 for synchronous actions
$form->ID,
$submission->id,
'webhook', // action type
'success',
'Webhook delivered to https://api.example.com/hook',
[ 'response_code' => 200, 'response_body' => '...' ]
);
// Log a failure
ActionLogger::log(
$queue_id,
$form->ID,
$submission->id,
'webhook',
'failed',
'Connection timeout after 30s'
);
Querying Logs
// Get logs for a specific submission
$logs = ActionLogger::get_for_submission( $submission_id );
// Get logs for a form (paginated)
$logs = ActionLogger::get_for_form( $form_id, $per_page = 20, $page = 1 );
// Get recent failures across all forms
$failures = ActionLogger::get_recent_failures( $limit = 20 );
// Get summary statistics
$stats = ActionLogger::get_stats( $form_id );
// Returns: [ 'total' => 150, 'success' => 140, 'failed' => 10, 'by_type' => [...] ]
Log Cleanup
Logs older than 90 days are automatically cleaned up:
$deleted = ActionLogger::cleanup( $days = 90 );
REST API Endpoints
List Action Logs
GET /wp-json/cf/v1/action-logs
Parameters: form_id, submission_id, status, per_page (default 20), page (default 1).
Log Statistics
GET /wp-json/cf/v1/action-logs/stats
Parameters: form_id (optional).
Returns counts by status and action type.
Recent Failures
GET /wp-json/cf/v1/action-logs/failures
Returns the 20 most recent failed log entries.
Retry a Failed Action
POST /wp-json/cf/v1/action-queue/{id}/retry
Resets the queue item for reprocessing. Returns { "retried": true } on success.
Test an Action
POST /wp-json/cf/v1/actions/test
Body:
{
"action_type": "email",
"form_id": 123,
"settings": { "to": "test@example.com", "subject": "Test", "message": "Hello" },
"test_data": { "name": "Test User", "email": "test@example.com" }
}
All endpoints require the edit_forms capability.