Easy way of sending scheduled tasks output to Slack

Task Scheduling Output To Slack

If you are building any kind of serious Laravel based app you'll probably be using cron jobs, and since version 5.0 Laravel has a great task scheduling tool built right which many of us gladly use instead of tedious "manual" cron jobs. If you don't know what task scheduler is and why you should use it, check the official docs. I guarantee it's worth it.

What many of us grow accustomed to is having cron job output emailed to us in order to see if everything went ok. Laravel's task scheduler also supports emailing output of the commands but if you are like millions of developers out there then you are probably using Slack and it's possible that it crossed your mind that it would be great if we could get output of the cron command sent to Slack. So let's do that.

Btw there is an open issue in Laravel internals where we're proposing adding this to Laravel natively and we hope it'll be accepted by the community but till that's baked in, let's do it "manually".

So the first thing we need is to build a Slack custom integration. If you don't know how to do that, head over to Slack documentation and create your incoming webhook, we'll need it in order to send notifications.

The second step is to build a small Slack notifier class. It's worth mentioning that there are packages out there that communicate with Slack and which support many things out of the box which is cool, but since we like to keep things simple and not depend on packages if we don't have to we've decided to build a simple class. It can look something like this:

<?php

namespace App\Libs;

use GuzzleHttp\Client as HttpClient;

class SlackNotifier
{

    /**
     * @var HttpClient
     */
    private $client;
    private $hook;
    private $channel;

    public function __construct($hook, $channel = null)
    {
        $this->client = new HttpClient();
        $this->hook = $hook;
        $this->channel = $channel;
    }

    public function send($text, $attachments = [], $hook = null, $channel = null)
    {
        $hook    = $this->getHook($hook);
        $channel = $this->getChannel($channel);
        $payload = $this->preparePayload($text, $attachments, $channel);

        $this->client->request('POST', $hook, [
            'json' => $payload
        ]);
    }

    /**
     * @param $hook
     * @return mixed
     */
    private function getHook($hook)
    {
        $hook = (is_null($hook)) ? $this->hook : $hook;

        return $hook;
    }

    /**
     * @param $channel
     * @return null
     */
    private function getChannel($channel)
    {
        $channel = (is_null($channel)) ? $this->channel : $channel;

        return $channel;
    }

    /**
     * @param $text
     * @param $attachments
     * @param $channel
     * @return array
     */
    private function preparePayload($text, $attachments, $channel)
    {
        $payload = ['text' => $text, 'attachments' => [$attachments], 'channel' => $channel];

        return $payload;
    }

}

As you can see, it's just a simple class which depends on Guzzle (come on you have Guzzle installed already don't ya? :))

It only has one method send() which takes a "text" which is actually a header of the Slack message and it uses Slack's attachments to pass output of our command to.

In your Kernel.php you'll execute a command and send output to Slack like this:

$hook    = 'https://link/to/your/webhook';
$channel = '#channel';
$slack   = new SlackNotifier($hook, $channel);

$filePath = storage_path() . '/cron.log';

$c = $schedule->command('inspire')->everyMinute()->sendOutputTo($filePath);

$c->after(function () use ($c, $slack, $hook, $channel) {
    $command = 'Schuleded task executed: ' . '*' . $c->command . '*';
    $output  = ['text' => file_get_contents($c->output)];
    $slack->send($command, $output);
});

First we build our SlackNotifier object and prepare hook and channel variables:

$hook    = 'https://link/to/your/webhook';
$channel = '#channel';
$slack   = new SlackNotifier($hook, $channel);

Then we proceed to execute our command and save output to file:

$filePath = storage_path() . '/cron.log';

$c = $schedule->command('inspire')->everyMinute()->sendOutputTo($filePath);

After that's done we leverage an "after" hook of the command to send the output to Slack:

$c->after(function () use ($c, $slack, $hook, $channel) {
    $command = 'Schuleded task executed: ' . '*' . $c->command . '*';
    $output  = ['text' => file_get_contents($c->output)];
    $slack->send($command, $output);
});

It's pretty simple but it get's the job done.

If you like this idea let us know and please let the community know via Laravel Internals

and we'll implement this properly, probably with added support for other chat platforms like Hipchat etc...



comments powered by Disqus