fluentdでつくる監視系

いつもアプリケーションの開発ばかりで、まじめに監視系を考えたことがなかったので、
fluentdを中心にした監視系を作ってみた。

```
ツッコミ大歓迎です。アチラコチラの資料を参考にしつつ作ったので、オカシイところがあれば是非ともコメント下さいませ。
```

# 前提
1. 複数台のアプリケーションサーバ
2. 一台のログ収集サーバ
3. ログにはエラーログとアクセスログの大きく2種類を用意する
4. エラーログは更に複数のレベルでファイル単位にわかれている
   1. fatal
   2. error
   3. warn
5. アプリケーションサーバとログ収集サーバは同一ネットワーク上にある

# やりたいこと
メールで来ても絶対に気がつかない自信がある。
異常の側から教えてくれる仕組みを目指す。

1. fatalログが出た場合は、電話による通知を行う
2. 全てのエラーログはchatツールに出力する
3. ログのバックアップ
4. ログの分析・可視化

この記事では1, 2, 3についてまとめる。

# 構築
## fluentdのインストール
公式のドキュメントが一番わかり易い。
[Installation | Fluentd](http://docs.fluentd.org/v0.12/categories/installation)

## ログの送信側
既にログをJSONでファイルに吐き出していたのでtailする。

サンプルログ。仮にこういうログだったとする。

```json
{"name":"applicationX","hostname":"localhost","req":{"method":"GET","url":"/","ua":"Mozilla/5.0 ... "},"res":{"statusCode":200},"error":"", "time":"2015-02-26T08:23:28.998Z"}
```

/etc/td-agent/td-agent.conf

```
<source>
  type tail
  path <PATH/TO/LOG>
  pos_file <PATH/TO/POSFILE>
  tag app.access
  format json
  include_time_key true
  include_tag_key true
  time_key sacrifice            # avoid overwrite <time> field
</source>

<source>
  type tail
  path <PATH/TO/LOG>
  pos_file <PATH/TO/POSFILE>
  tag app.fatal
  format json
  include_time_key true
  include_tag_key true
  time_key sacrifice            # avoid overwrite <time> field
</source>

# 以下、error, warn, debugそれぞれ<source> ... </source>

<match *.**>
  type forward
  heartbeat_type tcp
  <server>
    host <YOUR LOG SERVER>
  </server>
</match>
```

これで

```bash
2015-02-26 17:23:28 +0900 app.access: {"name":"applicationX","hostname":"localhost","req":{"method":"GET","url":"/","ua":"Mozilla/5.0 ... "},"res":{"statusCode":200},"time":"2015-02-26T08:23:28.998Z"}
# 構造：time tag record
```

という内容のログが送信される

### time_key

ログにtimeという要素があるので、これでアクセス時刻別に処理しようと思っていたが、
fluentdを通した時点でなくなっていた。なので、time_keyで存在しない要素を指定して要素の削除を回避している。

### heartbeat_type
デフォルトではUDPの24224を使って死活管理をしている。
udbポートを開けたくなかったので、ハートビートもtcpで行うように変更。

### tagについて
fluentdはピリオドで区切ると扱うのが楽なのでピリオド区切り。

## ログの受信側
```bash
$ sudo /opt/td-agent/embedded/bin/fluent-gem install fluent-plugin-forest  # td-agent.confを構造化して書くため
$ sudo /opt/td-agent/embedded/bin/fluent-gem install fluent-plugin-twilio  # 電話
$ sudo /opt/td-agent/embedded/bin/fluent-gem install fluent-plugin-hipchat # chat通知
$ sudo /opt/td-agent/embedded/bin/fluent-gem install fluent-plugin-s3      # S3にバックアップ
```

/etc/td-agent/td-agent.conf

```
<source>
  type forward
  port 24224
</source>

<match app.access>
  type copy
  <store>
    type stdout # debug用 /var/log/td-agent/td-agent.logに流れる
  </store>
  <store>
    type s3
    aws_key_id "#{ENV['AWS_ACCESS_KEY_ID']}"
    aws_sec_key "#{ENV['AWS_SECRET_KEY']}"
    s3_bucket <AWS S3 BUCKET NAME>
    s3_region ap-northeast-1
    path app/access/
    buffer_path /var/log/td-agent/buffer/s3_app_access
    time_slice_format %Y-%m-%d/%H
    time_slice_wait 10m
  </store>
</match>

<match app.*>
  type forest
  subtype copy
  <template>
    <store>
      type s3
      aws_key_id "#{ENV['AWS_ACCESS_KEY_ID']}"
      aws_sec_key "#{ENV['AWS_SECRET_KEY']}"
      s3_bucket <AWS S3 BUCKET NAME>
      s3_region ap-northeast-1
      path ${tag_parts[0]}/${tag_parts[1]}/ # app/fatal/, app/error/, ...
      buffer_path /var/log/td-agent/buffer/s3_${tag_parts[0]}_${tag_parts[1]}
      time_slice_format %Y-%m-%d/%H
      time_slice_wait 10m
    </store>
    <store>
      type hipchat
      api_token "#{ENV['HIPCHAT_API_TOKEN']}"
      default_room <YOUR CHAT ROOM>
      default_from fluentd
      default_color yellow
      default_notify 1
      default_format text  # メンションを含めたい場合はtextである必要がある。HipChatのAPI仕様
      default_timeout 3
      key_name error
    </store>
  </template>
  <case app.fatal>
    <store>
     type twilio
     account_sid    "#{ENV['TWILIO_ACCOUNT_SID']}"
     auth_token     "#{ENV['TWILIO_AUTH_TOKEN']}"
     from_number    +810000000000                # twilioで取得した番号
     default_number +811111111111, +812222222222 # 電話を受け取りたい番号
    </store>
  </case>
</match>
```

アクセスログとエラーログでmatchを分けたがまとめられるような気もする・・・。
S3の記述が重複しておりもやもや。

=> [続・fluentdでつくる監視系 - Qiita](http://qiita.com/kotatsu360/items/937d12e5aebfaec0f1fc)で対応

### AWSのアクセストークン
S3のフルパーミッションが付与されているトークン。
EC2インスタンスかつIAM Roleが付与されている場合は省略可

### hipchatのkey_name
ここで指定した要素をJSONから取り出して通知する。
なお、渡せるのは文字列のみ。構造を持った要素（例えば、res）を渡すと無視される。

~~エラーの詳細を全部流したいので、対策を考え中。~~
[続・fluentdでつくる監視系 - Qiita](http://qiita.com/kotatsu360/items/937d12e5aebfaec0f1fc)で解決
## twilio
messageという要素があると、読み上げてくれる。
~~今はmessageという要素が無いため電話をとった瞬間に切れる。~~
[続・fluentdでつくる監視系 - Qiita](http://qiita.com/kotatsu360/items/937d12e5aebfaec0f1fc)で解決

# その他
* 同一ネットワーク上にあるのでforwardで転送したが、間にインターネットを挟む場合はsecure_forwardがいいと思う。
* アプリケーションログ以外、サーバのシステム監視はまた今度
* fluentd側でlog_levelを設定したらもっとスッキリかけるかもしれない

# まとめ
最低限、fatalは電話、エラー系のログはchatにポスト、ログはバックアップ、ができた。

# 参考
[fluentdの簡単な使い方、設定方法一覧 - Hive Color](http://hivecolor.com/id/37)
[fluentd で json に time を残したい - Qiita](http://qiita.com/rch850/items/3b7ce04e38c85a1ce5d0)
[日本語読み上げ対応。FluentdからTwilioの電話APIを操作する「fluent-plugin-twilio」を使って電話を掛けてみた - Y-Ken Studio](http://y-ken.hatenablog.com/entry/fluent-plugin-twilio-has-released)
[fluent-plugin-s3](https://github.com/fluent/fluent-plugin-s3)
[fluent-plugin-hipchat](https://github.com/hotchpotch/fluent-plugin-hipchat)
[fluent-plugin-forest](https://github.com/tagomoris/fluent-plugin-forest)