Datadog ログ監視でログ本文を通知

某abbix等だとログ本文を一覧で表示できたりしますが、
Datadogでは解析した物を使用する前提のようで、
本文を送る機能は用意されていません。

- [Datadog Docs - Datadog Agent によるログの解析方法](http://docs.datadoghq.com/ja/guides/logs/)

syslog(`/var/log/messages`)を対象に、
Datadogで任意の文字列に合致したログ本文を通知する設定を行いました。

# 事前準備

## 環境

CentOS 6.7


## DatadogAgent インストール

Datadogのメニューから辿ると、各OS(distribution)用のインストールコマンドが用意されていますので、そちらに沿って対象サーバにインストールします。

- [Integrations] -> [Agent] -> [CentOS/RedHat]   


## pyparsing インストール

ログをパースするのに必要になるのでpipでインストールします。  
注意点としてDatadogAgentは独自でpythonを持っており、
OSにインストールされているpythonに対してインストールしてもDatadogAgent内の処理では使用されません。
尚、pipは古いバージョンだったのでアップグレードしました。

```
/opt/datadog-agent/embedded/bin/pip install -U pip pyparsing
# /opt/datadog-agent/embedded/bin/pip --version
pip 8.0.2 from /opt/datadog-agent/embedded/lib/python2.7/site-packages (python 2.7)
```

### 標準でインストールされていた python 2.6

`/usr/bin/python` 用のpipでインストールしてもDatadogAgent(collector)からは使用できません。

```
# which python
/usr/bin/python
# python --version
Python 2.6.6
# which pip
/usr/bin/pip
# pip --version
pip 8.0.2 from /usr/lib/python2.6/site-packages (python 2.6)
```

### DatadogAgent(collector)用の Python 2.7

daemonの設定ファイルを見ると、ログ解析を行うcollectorが使用するpythonのパスが記載されています。
同ディレクトリにpipも存在するので、こちらを使用します。

```
# cat /etc/dd-agent/supervisor.conf
・・・略・・・
[program:collector]
command=/opt/datadog-agent/embedded/bin/python /opt/datadog-agent/agent/agent.py foreground --use-local-forwarder
・・・略・・・
# /opt/datadog-agent/embedded/bin/python --version
Python 2.7.10
# /opt/datadog-agent/embedded/bin/pip --version
/opt/datadog-agent/embedded/bin/pip --version
pip 6.1.1 from /opt/datadog-agent/embedded/lib/python2.7/site-packages/pip-6.1.1-py2.7.egg (python 2.7)
```

## 対象ファイルのパーミッション

今回の監視対象ログはsyslogであるため、root以外に権限がありません。
監視するためには読込権限が必要になるため、dd-agentユーザに付与します。(dd-agentをrootグループに所属させ、ファイルにはグループの読込権限のみ付与しています。)

```
# usermod -g dd-agent -G root dd-agent
# chmod 640 /var/log/messages
# usermod -g dd-agent -G root dd-agent
# chmod 640 /var/log/messages
# logrotate設定にも追加
# vi /etc/logrotate.d/syslog
・・・略・・・
create 0640 root root
・・・略・・・
```

# カスタムログパーサ

CentOSのsyslog(messages)を例にパーサを作成します。
公式ドキュメントと先輩様のスクリプトの参考(拝借)です。

- syslog-parser.py

```python
from pyparsing import Word, alphas, Suppress, Combine, nums, string, Optional, Regex
from time import strftime
import os.path
import time

def payload_event(line, log_array, alert_type):
  payload = {}
  payload["alert_type"] = alert_type
  payload["event_type"] = "syslog.error"
  payload["aggregation_key"]  = log_array[3]
  payload["host"]  = log_array[3]
  payload["msg_title"] = log_array[4]
  payload["msg_text"] = os.path.basename(__file__) + ' : ' + line
  return payload

def syslog_parser(log, line):
  # ignore case (specified in lower case)
  if ' error ' in line.lower():
    alert_type = 'error'
  elif ' warning ' in line.lower():
    alert_type = 'warning'
  else:
    return
  # case sensitive
  #if ' error ' in line:
  #  alert_type = 'error'
  #elif ' warning ' in line:
  #  alert_type = 'warning'
  #else:
  #  return

  ints = Word(nums)
  month = Word(string.uppercase, string.lowercase, exact=3)
  day   = ints
  hour  = Combine(ints + ":" + ints + ":" + ints)
  timestamp = month + day + hour
  hostname = Word(alphas + nums + "_" + "-" + ".")
  appname = Word(alphas + "/" + "-" + "_" + ".") + Optional(Suppress("[") + ints + Suppress("]")) + Suppress(":")
  message = Regex(".*")

  pattern = timestamp + hostname + appname + message
  parsed = pattern.parseString(line)
  event = payload_event(line, parsed, alert_type)
  return [event]

def test():
  # Set up the test logger
  import sys
  import logging
  logging.basicConfig(level=logging.DEBUG)

  param = sys.argv
  if (len(param) != 2):
    print 'Usage: python %s "TestMessage"' %param[0]
    quit()

  # Call the parse function
  actual = syslog_parser(logging, param[1])

  # Validate the results
  print 'Input:'
  print param[1]
  print 'Output:'
  print actual

if __name__ == '__main__':
  # For local testing, callable as "python /path/to/syslog-parser.py"
  test()
```

## 各関数説明

### payload_event

ログ本文からDatadogのイベントフィールドを生成します。  
この例では message_text はログ全文と検索用にスクリプト名を設定しています。

### syslog_parser

先頭のif文で error|warning の振り分けを行います。  
マッチしない行に対しては何も処理しません。(ログ全行を送るのは避けています。)

### test

テスト関数です。コマンドラインからのテストを実行します。  
引数でログメッセージを渡す事で、渡した文字列とパース後の文字列を出力します。

#### テスト実行例

```
# python parser.py "テスト文字列"
/opt/datadog-agent/embedded/bin/python /opt/datadog-agent/agent/checks/libs/syslog-parser.py "`date +'%b %d %H:%M:%S'` `uname -n` dd.collector[9999]: WARNING (disk.py:74): Using \`use_mount\` in datadog.conf has been deprecated in favor of \`use_mount\` in disk.yaml"
# 出力例
Input:
Feb 22 04:00:03 demo-clpami6 dd.collector[9999]: WARNING (disk.py:74): Using `use_mount` in datadog.conf has been deprecated in favor of `use_mount` in disk.yaml
Output:
[{'alert_type': 'warning', 'event_type': 'syslog.error', 'msg_title': 'dd.collector', 'host': 'demo-clpami6', 'msg_text': 'Feb 22 04:00:03 demo-clpami6 dd.collector[9999]: WARNING (disk.py:74): Using `use_mount` in datadog.conf has been deprecated in favor of `use_mount` in disk.yaml', 'aggregation_key': 'demo-clpami6'}]
```

## 格納場所と設定ファイル

datadog.conf に監視対象のログとパーススクリプトを指定します。

```
# vi /etc/dd-agent/datadog.conf
・・・略・・・
dogstreams: /var/log/messages:syslog-parser:syslog_parser
・・・略・・・
```

DatadogAgentの `PYTHONPATH` 配下であればパスの記述を省略できます。  
`PYTHONPATH` はdaemonの設定ファイルに記載されています。(ここでもcollectorが対象です)

```
# cat /etc/dd-agent/supervisor.conf
・・・略・・・
[program:collector]
・・・略・・・
environment=PYTHONPATH='/opt/datadog-agent/agent:/opt/datadog-agent/agent/checks/libs:$PYTHONPATH'
・・・略・・・
```

今回は `PYTHONPATH` であるlibs配下に格納しています。

```
/opt/datadog-agent/agent/checks/libs/syslog-parser.py
```

## 有効化と確認

スクリプトの配置、設定ファイルへの追加、読込権限付与等、
準備が整った所でDatadogAgentを再起動して反映します。

```
# service datadog-agent restart
Stopping Datadog Agent (using killproc on supervisord):    [  OK  ]
Starting Datadog Agent (using supervisord):                [  OK  ]
```

/var/log/datadog/collector.log に以下のようなログが出力されていれば成功です。

```
yyyy-mm-dd hh:mm:dd JST | INFO | dd.collector | checks.collector(datadog.py:151) | dogstream: parsing /var/log/messages with <function syslog_parser at 0xXXXXXXXXXX> (requested syslog-parser:syslog_parser)
```

# Datadog上での確認

## 参照

[Events] で参照できます。

<img width="1250" alt="Datadog_LogMonitoring_01.png" src="https://qiita-image-store.s3.amazonaws.com/0/89680/864bf6de-e1eb-4a4c-9dca-f069517847b7.png">

## 通知設定

[Monitors] -> [New Monitor] から通知設定を行います。  
今回はSlackのチャンネルへ通知します。

<img width="1080" alt="Datadog_LogMonitoring_02.png" src="https://qiita-image-store.s3.amazonaws.com/0/89680/ae99bdb9-02b2-fc09-a1bf-d53b2f857f74.png">

## 通知テスト

適当にログ出力を行い、Monitor設定で正しく通知されるかを確認します。

```
# logger -t TEST "`date +'%b %d %H:%M:%S'` `uname -n` dd.collector[9999]: warning LogParseTest"
```

通知メッセージにログ本文が含まれています。

<img width="642" alt="Datadog_LogMonitoring_03.png" src="https://qiita-image-store.s3.amazonaws.com/0/89680/47f8ad43-abed-0130-126d-e7d097f77a2f.png">

# 今後の課題

試行回数が少ないのですが、連続して出力があった場合に通知が飛ばない事があるようです。
Monitorの設定で最短(1分)にした場合にどの程度軽減できるか等確認が必要と思います。

<img width="1031" alt="Datadog_LogMonitoring_04.png" src="https://qiita-image-store.s3.amazonaws.com/0/89680/8559d6a0-a167-8fcb-c9b0-1bd2565c1293.png">