この記事は Laravel Advent Calendar 2014 の 24日目のエントリーです。
はじめに
11月から、仕事で Laravel 4 と 話題の 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)
こんな感じでメッセージが投げられたでしょうか。
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 登録したら、すぐに対応してくれました!
さいごに
明日はついに最終日ですね!@localdisk さんのポエムに期待しつつ、みなさん良いクリスマスを!