Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Schedule Monitor Laravel Package

spatie/laravel-schedule-monitor

Monitor Laravel scheduled tasks by logging starts, finishes, failures, and skips to a database table and viewing run history via an Artisan command. Optionally sync with Oh Dear to get alerts when tasks fail or don’t run on time.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require spatie/laravel-schedule-monitor
    php artisan vendor:publish --provider="Spatie\ScheduleMonitor\ScheduleMonitorServiceProvider" --tag="schedule-monitor-migrations"
    php artisan migrate
    

    Publish the config file (optional but recommended):

    php artisan vendor:publish --provider="Spatie\ScheduleMonitor\ScheduleMonitorServiceProvider" --tag="schedule-monitor-config"
    
  2. First Sync: Sync your Laravel schedule with the monitor:

    php artisan schedule-monitor:sync
    
  3. First Check: Verify monitored tasks:

    php artisan schedule-monitor:list
    

First Use Case: Debugging a Missed Task

If a scheduled task fails to run, use:

php artisan schedule-monitor:list

Look for tasks with red backgrounds (late/failed) and inspect their log entries in the monitored_scheduled_task_log_items table.


Implementation Patterns

Core Workflow

  1. Schedule Definition (in app/Console/Kernel.php):

    protected function schedule(Schedule $schedule) {
        $schedule->command('backup:run')->daily()
                ->monitorName('daily-backup')
                ->graceTimeInMinutes(10)
                ->storeOutputInDb();
    
        $schedule->job(new ProcessInvoices())->hourly()
                ->doNotMonitor(); // Skip monitoring for this task
    }
    
  2. Sync After Deployment: Add to your deployment script:

    php artisan schedule-monitor:sync
    
  3. Monitoring:

    • CLI: Use schedule-monitor:list for real-time status.
    • Database: Query monitored_scheduled_task_log_items for historical data:
      $logs = MonitoredScheduledTaskLogItem::where('task_name', 'daily-backup')
                                            ->orderBy('created_at', 'desc')
                                            ->take(10)
                                            ->get();
      

Integration with Oh Dear

  1. Configure Oh Dear in .env:
    OH_DEAR_API_TOKEN=your_token
    OH_DEAR_MONITOR_ID=12345
    OH_DEAR_QUEUE=ohdear
    
  2. Verify Setup:
    php artisan schedule-monitor:verify
    
  3. Sync with Oh Dear:
    php artisan schedule-monitor:sync --keep-old
    
    (Use --keep-old to avoid removing existing Oh Dear monitors.)

Advanced Patterns

  • Dynamic Grace Time: Override grace time per task or globally in config/schedule-monitor.php:

    'default_grace_time' => 15, // Global default
    
    $schedule->command('long-task')->daily()->graceTimeInMinutes(30);
    
  • Conditional Monitoring: Use middleware to skip monitoring for specific tasks:

    $schedule->command('test:slow')->daily()
             ->when(function () {
                 return config('app.env') === 'production';
             })
             ->monitorName('prod-slow-test');
    
  • Log Pruning: Automate log cleanup in app/Console/Kernel.php:

    $schedule->command('model:prune', [
        '--model' => \Spatie\ScheduleMonitor\Models\MonitoredScheduledTaskLogItem::class,
    ])->daily();
    

Gotchas and Tips

Common Pitfalls

  1. Missing Sync:

    • Issue: Tasks appear in schedule-monitor:list but not in Oh Dear.
    • Fix: Run schedule-monitor:sync after every deployment or schedule change.
    • Debug: Use --verbose flag for sync commands to see skipped tasks.
  2. Grace Time Mismatch:

    • Issue: False positives for "late" tasks.
    • Fix: Adjust graceTimeInMinutes per task or set a global default in config.
    • Tip: Use storeOutputInDb to inspect task duration:
      $log = MonitoredScheduledTaskLogItem::latest()->first();
      $duration = $log->finished_at->diffInSeconds($log->started_at);
      
  3. Oh Dear Ping Failures:

    • Issue: Intermittent PingOhDearJob failures.
    • Fix:
      • Enable debug logging:
        OH_DEAR_DEBUG_LOGGING=true
        
      • Check queue worker logs for timeouts.
      • Use a dedicated queue (OH_DEAR_QUEUE) to avoid contention.
  4. Multitenancy Conflicts:

    • Issue: PingOhDearJob fails with "tenant not set" errors.
    • Fix: Add to config/multitenancy.php:
      'not_tenant_aware_jobs' => [
          \Spatie\ScheduleMonitor\Jobs\PingOhDearJob::class,
      ],
      
  5. Log Item Cleanup:

    • Issue: Database bloat from old logs.
    • Fix: Use Laravel’s pruning or manually delete:
      php artisan model:prune --model=Spatie\ScheduleMonitor\Models\MonitoredScheduledTaskLogItem
      
    • Tip: Set delete_log_items_older_than_days in config (default: 30).

Debugging Tips

  • Inspect Raw Logs:
    php artisan tinker
    >>> \Spatie\ScheduleMonitor\Models\MonitoredScheduledTaskLogItem::query()->latest()->take(5)->get();
    
  • Check Oh Dear Sync Status:
    php artisan schedule-monitor:list --with-oh-dear-status
    
  • Verify Task Names: Ensure monitorName() is used for ambiguous tasks (e.g., closures):
    $schedule->call(fn () => $this->processData())->daily()
             ->monitorName('process-data-job');
    

Extension Points

  1. Custom Log Fields: Extend MonitoredScheduledTaskLogItem to add metadata:

    // app/Models/MonitoredScheduledTaskLogItem.php
    use Spatie\ScheduleMonitor\Models\MonitoredScheduledTaskLogItem as BaseLogItem;
    
    class MonitoredScheduledTaskLogItem extends BaseLogItem {
        protected $casts = [
            'meta' => 'array',
        ];
    
        public function setCustomField($key, $value) {
            $this->meta[$key] = $value;
        }
    }
    

    Use in schedule:

    $schedule->command('backup:run')->daily()
            ->monitorName('backup')
            ->storeOutputInDb()
            ->then(function () {
                $log = MonitoredScheduledTaskLogItem::latest()->first();
                $log->setCustomField('backup_size', '1.2GB');
                $log->save();
            });
    
  2. Custom Sync Logic: Override the sync command to filter tasks:

    // app/Console/Commands/SyncSchedule.php
    use Spatie\ScheduleMonitor\Commands\SyncSchedule as BaseSync;
    
    class SyncSchedule extends BaseSync {
        protected function getTasksToMonitor() {
            return parent::getTasksToMonitor()->reject(fn ($task) =>
                str_contains($task->command, 'test:')
            );
        }
    }
    

    Register in AppServiceProvider:

    public function boot() {
        $this->app->bind(
            \Spatie\ScheduleMonitor\Commands\SyncSchedule::class,
            app(\App\Console\Commands\SyncSchedule::class)
        );
    }
    
  3. Webhook Alerts: Trigger custom alerts on task failures by listening to log events:

    // app/Providers/EventServiceProvider.php
    use Spatie\ScheduleMonitor\Events\ScheduledTaskFailed;
    
    protected $listen = [
        ScheduledTaskFailed::class => [
            \App\Listeners\SendSlackAlert::class,
        ],
    ];
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope
anil/file-picker
broqit/fields-ai