Laravel 4 から Slack へメッセージを投げる

この記事は Laravel Advent Calendar 2014 の 24日目のエントリーです。

はじめに

11月から、仕事で Laravel 4 と 話題の Slack を使うようになりました。

  • Laravel
  • Slack

ChatOps の第一歩として、 Slack の Integrations の機能を使って、メンバーの Commit / pullreq を #github channel に垂れ流すようにしました。これがとっても便利で、他のエンジニアの方が何をしているか可視化されることで、業務効率も上がりました。なにより、単純に楽しいということが大事ですよね。

こうなってくると、プロダクトのアラートメールなども一元的に Slack で受け取れるようにしたいな…と思ってきてしまいました。

Slack API の Incoming WebHooks

Slack は API を提供しており、自由に外部サービスからメッセージを投げることができたりします。Incoming WebHooks を登録すると、token 込みの WebHook URL を取得できるので、それを使って、まずは試しに自分の Team の #general channel にメッセージを投げてみましょう。

curl -X POST --data-urlencode 'payload={"channel": "#general", "username": "webhookbot", "text": "This is posted to #general and comes from a bot named webhookbot.", "icon_emoji": ":ghost:"}' (WebhookURL)
image

こんな感じでメッセージが投げられたでしょうか。

Laravel 4 から メッセージを投げてみる

PHP から Slack へメッセージを投げるプラグインは、このページを見る限りでもずいぶんたくさんあります。https://api.slack.com/community

この中でも、Laravel 4 で一番使いやすそうな maknz / slack を使わせてもらうことにしました。

インストールは README を見れば簡単にできます。試しに Laravel から送信してみるための artisan command を書いてみました。Webhook URL は、別途 Config から取得するようにしています。

<?php
use IlluminateConsoleCommand;
use SymfonyComponentConsoleInputInputOption;

class ProjectTestSlackCommand extends Command
{
    protected $name = 'project:testslack';
    protected $description = 'Slack へ post できるか疎通確認をする';

    public function __construct()
    {
        parent::__construct();
    }

    public function fire()
    {
        $message = $this->option('message');
        $config = [];

        if (! empty($this->option('username'))) {
            $config['username'] = $this->option('username');
        }

        if (! empty($this->option('channel'))) {
            $config['channel'] = $this->option('channel');
        }

        if (! empty($this->option('icon'))) {
            $config['icon'] = $this->option('icon');
        }

        $slack = App::make(
            'MaknzSlackClient',
            [
                Config::get('slack.endpoint'),
                $config
            ]
        );

        $slack->send($message);
    }

    protected function getOptions()
    {
        return [
            [
                'message',
                '',
                InputOption::VALUE_REQUIRED,
                'post する message'
            ],
            [
                'username',
                '',
                InputOption::VALUE_OPTIONAL,
                'post する bot の名前'
            ],
            [
                'icon',
                '',
                InputOption::VALUE_OPTIONAL,
                'post する bot の icon(URL でも、 Slack の絵文字でも OK)'
            ],
            [
                'channel',
                '',
                InputOption::VALUE_OPTIONAL,
                '投稿先(例:#general @ryo.shibayama)'
            ]
        ];
    }
}

README を見ればわかると思いますが、以下のように chaining して書くこともできます。

$client->from('Username')->to('@regan')->send('A message');

アラートメールの代替手段としての Slack

Slack の API は、1 request / sec までのリクエストしか受け付けてません。厳密にコントロールされているかは分かりませんが、大量に送るのは考えものです。

https://api.slack.com/docs/rate-limits

今回は、アラートメールの代替手段として考えていたので、デプロイ直後などにアラートが飛びまくる可能性があることも念のため考慮しました。

以下のような感じで、 SlackHelper クラスを作ってしまい、Slack へのメッセージングはここで管理するようなイメージです。連投を防ぐ手段としては、Slack にメッセージを投げるたびに /tmp/postslack という空ファイルを touch して、その最終更新日時を見ることで、メッセージを投げていいか判定しています。

<?php
use CarbonCarbon;

class SlackHelper
{

    private $file;

    public function __construct()
    {
        $this->file = Config::get('slack.filePath');
    }

    public function sendAlertToDeveloper($exceptionAsString)
    {
        $message = "*Error (" . App::environment() . ")*";
        $message .= "n";
        $message .= "```" . $exceptionAsString . "```";

        try {
            if ($this->can()) {
                Slack::from(Config::get('slack.displayName.alert'))
                    ->to('#alert')
                    ->withIcon(Config::get('slack.icon.alert'))
                    ->send($message);
                $this->touch();
            }
        } catch (Exception $e) {
        }
    }

    /**
     * Slack に post 可能か判定
     *
     * @return bool
     */
    private function can()
    {
        if (! File::exists($this->file)) {
            return true;
        }

        if (Carbon::now()->subSeconds(Config::get('slack.intervalSec'))->timestamp > File::lastModified($this->file)) {
            return true;
        }

        return false;
    }

    /**
     * Slack に最後に post した日時を記録する
     *
     * @return void
     */
    private function touch()
    {
        touch($this->file);
    }
}

エラーハンドリングは、 Laravel のドキュメントにある通り、ログリスナーを登録する形で行っています。http://laravel.com/docs/4.2/errors#logging

Slack 最高

これで無事にアラートが Slack に投げられるようになりました。アラート以外にも他の通知系でメールを使用している部分も少しずつ Slack に寄せたいなと思ったりしています。

余談

この package ですが、 json_encode の UTF-8 問題で、最初は日本語文字列が含まれている場合 false が返ってきてしまう不具合があり送信できなかったのですが、 issue 登録したら、すぐに対応してくれました!

Couldn't send multibyte string. · Issue #4 · maknz/slack

You can't perform that action at this time. You signed in with another tab or window. You signed out in another tab or window. Reload to refresh your session. Reload to refresh your session.

Couldn't send multibyte string. · Issue #4 · maknz/slack

さいごに

明日はついに最終日ですね!@localdisk さんのポエムに期待しつつ、みなさん良いクリスマスを!