Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
218 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
# Running the Schedule | ||
|
||
To run tasks when they are due, the schedule should be *run* **every minute** | ||
on your production server(s) indefinitely. | ||
|
||
*The schedule doesn't have to be run every minute but if it isn't, jobs | ||
scheduled in between the frequency you choose will never run. If you are | ||
careful when choosing task frequencies, this might not be an issue. If not | ||
running every minute, it must be run at predictable times like every hour | ||
exactly on the hour (ie 08:00, not 08:01).* | ||
|
||
If multiple tasks are due at the same time, they are run synchronously in the | ||
order they were defined. If you define tasks in multiple places | ||
([Configuration](define-schedule.md#bundle-configuration), | ||
[Builder Service](define-schedule.md#schedulebuilder-service), | ||
[Kernel](define-schedule.md#your-kernel), | ||
[Self-Scheduling Commands](define-schedule.md#self-scheduling-commands)) only | ||
the order of tasks defined in each place is guaranteed. | ||
|
||
Shipped with this bundle is a [`schedule:run`](cli-commands.md#schedulerun) | ||
console command. Running this command determines the due tasks (if any) for | ||
the current time and runs them. | ||
|
||
## Cron Job on Server | ||
|
||
The most common way to run the schedule is a Cron job that runs the | ||
[`schedule:run`](cli-commands.md#schedulerun) every minute. The following | ||
should be added to production server's | ||
[crontab](http://man7.org/linux/man-pages/man5/crontab.5.html): | ||
|
||
``` | ||
* * * * * cd /path-to-your-project && bin/console schedule:run >> /dev/null 2>&1 | ||
``` | ||
|
||
## Symfony Cloud | ||
|
||
The [Symfony Cloud](https://symfony.com/cloud/) platform has the ability to | ||
configure Cron jobs. Add the following configuration to run your schedule every | ||
minute: | ||
|
||
```yaml | ||
# .symfony.cloud.yaml | ||
|
||
cron: | ||
spec: * * * * * | ||
cmd: bin/console schedule:run | ||
|
||
# ... | ||
``` | ||
|
||
*[View the full Cron Jobs Documentation](https://symfony.com/doc/master/cloud/cookbooks/crons.html)* | ||
|
||
## Alternatives | ||
|
||
If you don't have the ability to run Cron jobs on your server there may be | ||
other ways to run the schedule. | ||
|
||
The schedule can alternatively be run in your code. Behind the scenes, the | ||
`schedule:run` command invokes the [`ScheduleRunner`](../src/Schedule/ScheduleRunner.php) | ||
service which does all the work. The return value of `ScheduleRunner::__invoke()` is an | ||
[`AfterScheduleEvent`](../src/Event/AfterScheduleEvent.php) object. | ||
|
||
The following is a list of alternative scheduling options (*please add your own solutions | ||
via PR*): | ||
|
||
### Webhook | ||
|
||
Perhaps you have a service that can *ping* an endpoint (`/run-schedule`) defined in | ||
your app every minute (AWS Lamda can be configured to do this). This endpoint | ||
can run the schedule: | ||
|
||
```php | ||
// src/Controller/RunScheduleController.php | ||
|
||
use Symfony\Component\HttpFoundation\Response; | ||
use Symfony\Component\Routing\Annotation\Route; | ||
use Zenstruck\ScheduleBundle\Schedule\ScheduleRunner; | ||
|
||
/** | ||
* @Route("/run-schedule") | ||
*/ | ||
class RunScheduleController | ||
{ | ||
public function __invoke(ScheduleRunner $scheduleRunner): Response | ||
{ | ||
$result = $scheduleRunner(); | ||
|
||
return new Response('', $result->isSuccessful() ? 200 : 500); | ||
} | ||
} | ||
``` | ||
|
||
## Dealing with Failures | ||
|
||
It is probable that at some point, a scheduled tasks will fail. Because the | ||
schedule runs in the background, administrators need to be made aware failures. | ||
|
||
*If multiple tasks are due at the same time, one failure will not prevent the | ||
other due tasks from running.* | ||
|
||
The following are different methods of being alerted to failures: | ||
|
||
### Logs | ||
|
||
All schedule/task events are logged (if using monolog, on the `schedule` channel). | ||
Errors and Exceptions are logged with the `error` and `critical` levels respectively. | ||
The log's context contains useful information like duration, memory usage, task output | ||
and the exception (if failed). | ||
|
||
The following is an example log file (some context excluded): | ||
|
||
``` | ||
[2020-01-20 13:17:13] schedule.INFO: Running 4 due tasks. {"total":22,"due":4} [] | ||
[2020-01-20 13:17:13] schedule.INFO: Running "CommandTask": my:command | ||
[2020-01-20 13:17:13] schedule.INFO: Successfully ran "CommandTask": my:command | ||
[2020-01-20 13:17:13] schedule.INFO: Running "ProcessTask": fdere -dsdfsd | ||
[2020-01-20 13:17:13] schedule.ERROR: Failure when running "ProcessTask": fdere -dsdfsd | ||
[2020-01-20 13:17:13] schedule.INFO: Running "CallbackTask": some callback | ||
[2020-01-20 13:17:13] schedule.CRITICAL: Exception thrown when running "CallbackTask": some callback | ||
[2020-01-20 13:24:11] schedule.INFO: Running "CommandTask": another:command | ||
[2020-01-20 13:24:11] schedule.INFO: Skipped "CommandTask": another:command {"reason":"the reason for skip..."} | ||
[2020-01-20 13:24:11] schedule.ERROR: 3/4 tasks ran {"total":4,"successful":1,"skipped":1,"failures":2,"duration":"< 1 sec","memory":"10.0 MiB"} | ||
``` | ||
|
||
Services like [Papertrail](https://papertrailapp.com) can be configured to alert | ||
administrators when a filter (ie `schedule.ERROR OR schedule.CRITICAL`) is matched. | ||
|
||
### Email on Schedule Failure | ||
|
||
Administrators can be notified via email when tasks fail. This can be configured | ||
[per task](define-tasks.md#email-output) or | ||
[for the entire schedule](define-schedule.md#email-on-failure). | ||
|
||
### `schedule:run` exit code/output | ||
|
||
The [`schedule:run`](cli-commands.md#schedulerun) command will have an exit code of | ||
`1` if one or more tasks fail. The command's output also contains detailed output. | ||
The crontab entry [shown above](#cron-job-on-server) ignores the exit code and | ||
dumps the command's output to `/dev/null` but this could be changed to log the | ||
output and/or alert an administrator. | ||
|
||
### Alert with Symfony Cloud | ||
|
||
When defining the `schedule:list` cron job with [Symfony Cloud](#symfony-cloud), you can | ||
[prefix the command with `croncape` to be alerted via email](https://symfony.com/doc/master/cloud/cookbooks/crons.html#command-to-run) | ||
when something goes wrong: | ||
|
||
```yaml | ||
# .symfony.cloud.yaml | ||
|
||
cron: | ||
spec: * * * * * | ||
cmd: croncape bin/console schedule:run | ||
|
||
# ... | ||
``` | ||
|
||
## Ensuring Schedule is Running | ||
|
||
It is important to be assured your schedule is always running. The best method | ||
is to use a Cron health monitoring tool like [Cronitor](https://cronitor.io/), | ||
[Laravel Envoyer](https://envoyer.io/) or [Healthchecks](https://healthchecks.io/). | ||
These services give you a unique URL endpoint to *ping*. If the endpoint doesn't | ||
receive a ping after a specified amount of time, an administrator is notified. | ||
|
||
You can [configure your schedule to ping](define-schedule.md#ping-webhook) after | ||
running (assumes your endpoint is `https://hc-ping.com/445f6ea7-16d8-4685-ae51-c7416ccb8eae`): | ||
|
||
```yaml | ||
# config/packages/zenstruck_schedule.yaml | ||
|
||
zenstruck_schedule: | ||
schedule_extensions: | ||
ping_after: https://hc-ping.com/445f6ea7-16d8-4685-ae51-c7416ccb8eae | ||
``` | ||
|
||
This will ping the endpoint after the schedule runs (every minute). If this is too | ||
frequent, you can configure a *[null task](define-tasks.md#nulltask)* to [ping the | ||
endpoint](define-tasks.md#ping-webhook) at a different frequency: | ||
|
||
```yaml | ||
zenstruck_schedule: | ||
tasks: | ||
- command: ~ | ||
description: Health check | ||
frequency: '@hourly' | ||
ping_after: https://hc-ping.com/445f6ea7-16d8-4685-ae51-c7416ccb8eae | ||
``` | ||
|
||
In this case, a notification from one of these services means your schedule isn't | ||
running. | ||
|
||
Alternatively, you can configure a *[null task](define-tasks.md#nulltask)* to [email | ||
an administrator](define-tasks.md#email-output) at a specific frequency to let them | ||
know the schedule is still running: | ||
|
||
```yaml | ||
zenstruck_schedule: | ||
tasks: | ||
- command: ~ | ||
description: Email health check | ||
frequency: '0 7 * * *' # daily @ 7am | ||
email_after: | ||
to: admin@example.com | ||
subject: Your schedule is still running! | ||
``` | ||
|
||
*This is inferior to using a Cron health monitoring tool as the administrator needs | ||
to remember that if they do not get an email, something is wrong.* |