Health Checks¶
The health check runner lives under Subscribed\IncidentMonitor\HealthCheck\....
Bootstrap¶
<?php
use Subscribed\IncidentMonitor\HealthCheck\Bootstrap;
// Boot the health check runtime and register scheduler hooks.
// Migrations are registered through the base-plugin migration hook.
$app = Bootstrap::init();
Or include the package bootstrap.php file and call:
<?php
// Helper function defined by the package bootstrap file.
subscribed_incident_monitor_health_checks();
Registering checks¶
Register checks through the registry or the package_incident_monitor/checks filter.
<?php
use Subscribed\IncidentMonitor\HealthCheck\Definition\CheckDefinition;
use Subscribed\IncidentMonitor\HealthCheck\Value\CheckContext;
use Subscribed\IncidentMonitor\HealthCheck\Value\CheckResult;
// Register checks dynamically through a filter.
add_filter('package_incident_monitor/checks', function (array $checks): array {
$checks[] = new CheckDefinition(
'orders/stuck-capture',
'Orders missing capture',
function (CheckContext $context): CheckResult {
// Restore the last persisted cursor for this run.
$offset = (int) $context->state('offset', 0);
// Batch size comes from the check definition options.
$limit = $context->batchSize();
// Load the next slice of work.
$orderIds = my_expensive_lookup($offset, $limit);
if (empty($orderIds)) {
// No issues found, mark the run successful.
return CheckResult::ok('No uncaptured orders found.', ['processed' => $offset]);
}
// Process the current slice.
foreach ($orderIds as $orderId) {
inspect_order($orderId);
}
if (count($orderIds) === $limit) {
// Persist the next cursor so Action Scheduler can continue later.
return CheckResult::continue(
'Continuing order scan.',
['offset' => $offset + $limit],
['processed' => $offset + $limit]
);
}
return CheckResult::failed(
// The third argument becomes the stable incident key.
'Found uncaptured orders.',
['order_ids' => $orderIds],
'orders/stuck-capture',
// Optional: a result-level suggested solution, shown in the ticket.
// Takes priority over the ticket-level suggested_solution fallback below.
"Re-capture the affected orders manually:
```bash
wp order capture {$orderIds[0]}
```"
);
},
[
// How often the check becomes due again after a completed run.
'interval' => '5 minutes',
// Number of items to inspect per slice.
'batch_size' => 50,
// Intended wall time budget for each slice.
'time_budget' => 10,
// Retry count before the run is marked as failed.
'max_attempts' => 3,
// Finished run retention in days.
'retention_days' => 14,
'ticket' => [
// When enabled, failure incidents are forwarded to IncidentMonitorFacade.
'enabled' => true,
// Connector name resolved by the incident tracker bootstrap.
'connector' => 'asana',
// Logical project key resolved by the incident tracker bootstrap.
'project_key' => 'development',
// Only create/update a ticket after this many consecutive failures.
'failure_threshold' => 2,
// Tags passed through to the external ticket.
'tags' => ['health-check', 'orders'],
// Optional fallback: shown when CheckResult::failed() does not carry its own
// suggested_solution. Supports triple-backtick code blocks.
'suggested_solution' => "Check the capture job logs and re-trigger manually:
```bash
wp order capture <id>
```",
],
]
);
return $checks;
});
Issue tracking setup with health checks¶
If you want health checks to create tickets automatically, bootstrap IncidentMonitorFacade first and then register checks with ticket.enabled = true.
<?php
use League\Container\Container;
use Subscribed\IncidentMonitor\Container\IncidentMonitorBootstrap;
use Subscribed\IncidentMonitor\Facade\IncidentMonitorFacade;
use Subscribed\IncidentMonitor\HealthCheck\Bootstrap as HealthCheckBootstrap;
use Subscribed\IncidentMonitor\HealthCheck\Definition\CheckDefinition;
use Subscribed\IncidentMonitor\HealthCheck\Value\CheckContext;
use Subscribed\IncidentMonitor\HealthCheck\Value\CheckResult;
$container = new Container();
// Bootstrap the ticketing layer first so health check incidents have somewhere to go.
IncidentMonitorBootstrap::register($container, [
'asana_token' => getenv('ASANA_TOKEN'),
'project_map' => [
'development' => 'asana-project-id-dev',
],
'default_project_key' => 'development',
]);
IncidentMonitorFacade::setContainer($container);
// Register a health check that opens or updates an external incident ticket.
add_filter('package_incident_monitor/checks', function (array $checks): array {
$checks[] = new CheckDefinition(
'billing/missing-capture',
'Missing captures',
function (CheckContext $context): CheckResult {
$missing = find_missing_captures($context->batchSize());
if (empty($missing)) {
return CheckResult::ok('No missing captures.');
}
return CheckResult::failed(
'Found payments without capture.',
['payment_ids' => $missing],
'billing/missing-capture'
);
},
[
'interval' => '10 minutes',
'ticket' => [
// Forward incidents into one or more connectors.
'enabled' => true,
'connectors' => [
[
'connector' => 'asana',
'project_key' => 'development',
'tags' => ['health-check', 'billing'],
],
[
'connector' => 'sms',
'project_key' => 'on-call-primary',
'tags' => ['health-check', 'billing', 'urgent'],
],
],
'failure_threshold' => 1,
],
]
);
return $checks;
});
// Start the scheduler hooks and register migrations.
HealthCheckBootstrap::init();
Runner behavior¶
The runner:
- schedules due checks through Action Scheduler
- slices long-running work into resumable batches
- persists run state between slices
- retries failed slices
- emits incidents when failure thresholds are reached
Heavy checks should:
- keep each slice short
- return
CheckResult::continue()with the next cursor - keep per-item work idempotent where possible
WP-CLI¶
The package exposes health check commands through WP-CLI when it is loaded inside WordPress:
wp subscribed health-checks list
wp subscribed health-checks run billing/missing-capture
wp subscribed health-checks run --all
wp subscribed health-checks run --all --async
wp subscribed health-checks scan
Notes:
listshows registered checks and persisted status rowsrun <check-id>executes the check immediately in the current CLI processrun --all --asyncenqueues all registered checks into Action Schedulerscanperforms the normal due-check scan and enqueues only checks that are due
Storage¶
Status and run history are stored in:
{$wpdb->prefix}incident_monitor_health_check_statuses{$wpdb->prefix}incident_monitor_health_check_runs
Hooks¶
package_incident_monitor/checkspackage_incident_monitor/incident_createdpackage_incident_monitor/incident_updatedpackage_incident_monitor/incident_recoveredpackage_incident_monitor/logpackage_incident_monitor/max_parallel_checkspackage_incident_monitor/scan_intervalpackage_incident_monitor/run_retention_dayspackage_incident_monitor/cleanup_statusespackage_incident_monitor/default_ticket_project_keypackage_incident_monitor/default_connector_name