ようへいの日々精進XP

よかろうもん

(超メモ)初老が考えた Datadog を使った ログ監視 2 パターン

おはげようございます。

tl;dr

Datadog でログ監視(ログの中から特定のパターンにマッチしたら何らかのアクションを行う)の方法を模索していて以下の 2 つのパターンを考えたのでメモ。

  • Datadog の Agent Check で監視する
  • Fluentd を利用する

アプリケーションは Apache を利用し、ステータスコード毎に異なるアクションを行う(正常であれば行わない)シナリオを作って試してみた。

f:id:inokara:20151120004315p:plain

基本的には上図のような構成で、最終的には Datadog Event にポストする際にステータスコードによって event_typewarningerror で振り分ける


Datadog の Agent Check で監視する

参考

Agent Check でログ監視の基本

Datadog の Agent Check でログ監視を行う場合、ログからメトリクス又はイベントを抽出することが出来るが、今回は二つのパターン共にイベントの抽出でやってみる。

尚、Datadog デフォルトのログフォーマットの場合には以下のように datadog.conf に記述するとログ解析が始めることが出来る。

dogstreams: /var/log/web.log

Datadog デフォルトのログフォーマットは半角スペースで区切られたフォーマットで以下のようなフォーマット。

me.web.requests 1320786966 157 metric_type=counter unit=request
me.web.latency 1320786966 250 metric_type=gauge unit=ms

オリジナルログフォーマットの場合

オリジナルのログフォーマットの場合には言わずもがなログパーサーを書く必要がある。Apacheアクセスログもオリジナルのログフォーマットに該当する。今回はイベントの抽出なのでこちらのドキュメントサンプルを参考に以下のようなパーサーを作ってみた。

from time import strftime
import re
import time

def apache_log_parser(log, line):
    regex = '([(\d\.)]+) - - \[(.*?)\] "(.*?)" (\d+) (.*?) "(.*?)" "(.*?)"'
    parsed = list(re.match(regex, line).groups())
    # print parsed

    event = {}
    event["aggregation_key"]  = "apache.log-monitor"
    event["timestamp"] = int(time.time())
    event["msg_title"] = "Apache log monitor / Got status code: " + parsed[3]
    event["msg_text"] = "From: " + parsed[0] + " / " + "User-Agent: " + parsed[6]

    if re.match('^5\d\d$', parsed[3]):
        event["alert_type"] = "error"
        event["event_type"] = "apache.error"
    elif re.match('^4\d\d$', parsed[3]):
        event["alert_type"] = "warning"
        event["event_type"] = "apache.warning"
    else:
        event["alert_type"] = "info"
        event["event_type"] = "apache.info"

    #print event
    return [event]

#apache_log_parser()

尚、前後するが、実装にあたっては以下のような条件がある。

  • Python で書く
  • 呼び出し元には Dictionary か配列で返却する必要がある
  • msg_title キーは必須

スクリプトを適当はパスに配置した上で datadog.conf に以下のように記載する。

dogstreams: /var/log/apache2/access.log:/path/to/apache-log-parser.py:apache_log_parser

監視したいログのパスの後に : で区切って監視スクリプトのパス、更に : で区切って実際に解析を行うメソッド名を記載する。

あとは datadog-agent を再起動する。

$ sudo service datadog-agent restart

再起動すると /var/log/datadog/collector.log に以下のようなログが記録される。

2015-11-20 07:58:11 JST | INFO | dd.collector | checks.collector(datadog.py:144) | Instantiating function-based dogstream
2015-11-20 07:58:11 JST | INFO | dd.collector | checks.collector(datadog.py:151) | dogstream: parsing /var/log/syslog with <function syslog_parser at 0x7f8b2c6b6cf8> (requested /opt/datadog-bin/test.py:syslog_parser)
2015-11-20 07:58:11 JST | INFO | dd.collector | checks.collector(datadog.py:151) | dogstream: parsing /var/log/apache2/access.log with <function apache_log_parser at 0x7f8b2c6b6de8> (requested /opt/datadog-bin/apache-log-parser.py:apache_log_parser)
2015-11-20 07:58:11 JST | INFO | dd.collector | checks.collector(datadog.py:48) | Dogstream parsers: [<checks.datadog.Dogstream object at 0x7f8b2c6fbb50>, <checks.datadog.Dogstream object at 0x7f8b2c6fbbd0>]
2015-11-20 07:58:15 JST | DEBUG | dd.collector | checks.collector(datadog.py:188) | Done dogstream check for file /var/log/syslog
2015-11-20 07:58:15 JST | DEBUG | dd.collector | checks.collector(datadog.py:188) | Done dogstream check for file /var/log/apache2/access.log

適当に Apache にアクセスする。

$ curl localhost > /dev/null

今回はデフォルトでステータスコード 500 を返すように設定しているのでアクセスする度に以下のように Datadog Evnet に登録される。

f:id:inokara:20151119235045p:plain

通知(例)

通知に関しては Barkdog を利用して Event Monitor に登録する。

monitor "Apache Status Monitor", :type=>"event alert" do
  query "events('status:error host:vagrant-ubuntu-trusty-64 \"status  500\"').rollup('count').last('1m') >= 10"
  message "Apache Status Monitor @slack-datadog-notification"
  options do
    no_data_timeframe 2
    notify_audit false
    notify_no_data false
    renotify_interval 0
    silenced({})
    thresholds "critical"=>10
    timeout_h 0
  end
end

Fluentd を利用する

参考

上記は Fluentd で収集した情報を Dogstatd で Datadog に送るパターンだがとても参考になった。

fluent-plugin-rewrite-tag-filter と fluentd-plugin-datadog_event を使う

fluent-plugin-rewrite-tag-filter と拙作の fluentd-plugin-datadog_event を使う。以下のように fluentd.conf を作成する。

<source>
  type tail
  path /var/log/apache2/access.log
  format apache2
  pos_file /tmp/apache.access.log.pos
  tag apache.access
</source>

<match apache.access>
  type copy
  <store>
    type stdout
  </store>
  <store>
    type rewrite_tag_filter
    capitalize_regex_backreference yes
    rewriterule2 code ^4\d\d$     dd.event.warn
    rewriterule3 code ^5\d\d$     dd.event.error
  </store>
</match>

<match dd.event.warn>
  type datadog_event
  api_key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  alert_type warning
  aggregation_key fluentd.dd.event
</match>

<match dd.event.error>
  type datadog_event
  api_key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  alert_type error
  aggregation_key fluentd.dd.event
</match>

ステータスコード 400 番台、500 番台を fluent-plugin-rewrite-tag-filter でそれぞれのタグを定義した fluentd-plugin-datadog_event に転送している。

そして、fluentd を起動する。

$ fluentd -c test.conf -l debug.log &

適当に Apache にアクセスする。

$ curl localhost > /dev/null

以下のように Datadog Event に登録される。

f:id:inokara:20151120001310p:plain

通知(例)

通知に関しては、先ほどと同様に Barkdog を利用して Event Monitor に登録する。

monitor "fluentd Datadog Event", :type=>"event alert" do
  query "events('status:error host:vagrant-ubuntu-trusty-64 \"fluentd 500\"').rollup('count').last('1m') > 10"
  message "fluentd Datadog Event @slack-datadog-notification"
  options do
    no_data_timeframe 2
    notify_audit false
    notify_no_data false
    renotify_interval 0
    silenced({})
    thresholds "critical"=>10
    timeout_h 0
  end
end

ということで...

どっちがいいのか?

  • Agent Check はログパーサーを自分で書けるので用途の応じたロジックでパーサーが書ける
  • Fluentd を使えばパーサーを書く必要が無い分手軽、様々なプラグインがあるので Datadog に限らないログ処理が行える

考慮する点

  • 自前のログパーサーのメンテなナンス
  • Fluentd を運用管理する必要がある

で、どっち?

  • 用途次第で使い分けましょう
  • どっちとも使うというのもありだと思うが、ホストの負荷等は十分に配慮しましょう