DatadogでEDCBの録画ログ監視

## この記事でできること

以下のようにDatadogでEDCBの録画ログを収集することができます。

![2022-04-09 22_16_36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136134/abc4b51e-da6a-b46f-5e63-88d4e61ca6fe.png)

録画に失敗した場合は以下のようにSlack通知することができます。

![2022-04-09 22_39_37-Slack.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136134/7ab7fcde-c0f1-0a66-f24b-2aa8c93e617e.png)


## 前提条件

- Windows 10 or 11
- [EDCB (work-plus-s-220224)](https://github.com/xtne6f/EDCB/releases/tag/work-plus-s-220224)
- [td-agent v4](https://docs.fluentd.org/installation/install-by-msi)
- [Datadog agent v6 or v7](https://docs.datadoghq.com/ja/agent/basic_agent_usage/windows)
- Python3系

EDCB、td-agent、Datadogなどのインストール方法についてはこの記事では説明しません。
インストール方法や使用方法を説明している記事はたくさんあると思うのでそちらを参考にしてください。

### パス

この記事では以下にインストールされている、または設定ファイルが存在することとして説明します。
参考にする場合は各々の環境のパスで読み替えてください。

| アプリケーション | 本体、または設定ファイルが存在するパス|
|------------------|---------------------------------------|
|EDCB              | `C:\data\Mylib\EDCB`                  |
|td-agent          | `C:\opt\td-agent` (デフォルト)        |
|Datadog agent     | `C:\ProgramData\Datadog` (デフォルト) |
|Python            | `C:\python`                           |

## 収集する対象のファイル

録画情報は RecInfo.txt(`C:\data\Mylib\EDCB/Setting/RecInfo.txt`)に書き込まれているため、このファイルを収集の対象とします。
しかし、このファイルはログファイルではなく、EDCBの画面上に表示されている録画情報のファイルでしかありません。
また、このファイルは録画情報が更新されるたびに新規ファイル扱い(ファイルIDが変更)になる上、録画情報が100件以上になると古い情報が破棄されるようになっているため、通常のログファイルのように末尾にログが書き込まれません。
そのため、ログファイルとして扱うためには工夫をする必要があります。

## ログ収集の設定方法

### RecInfo.txtをUTF-8(BOM)付きに変換する

RecInfo.txtはデフォルトではCP932で出力されますが、 xtne6/EDCB ではUTF-8(BOM付き)に変換すると、以後その形式で読み書きします。CP932のまま後の処理で変換することも可能ですが、手間なだけなのでUTF-8(BOM付き)に変換しておきます。

[EDCB/Readme_Mod.txt  xtne6f/EDCB · GitHub](https://github.com/xtne6f/EDCB/blob/dd3b883a1fb562c8297b3b0f6e63bc2ebab33926/Document/Readme_Mod.txt#L602)
> ◇予約ファイル等のByteOrderMark付きUTF-8対応について【追加】
予約ファイル等(EpgAutoAdd.txt, ManualAutoAdd.txt, RecInfo.txt, RecInfo2.txt,
Reserve.txt)をメモ帳などを使って「文字コード:UTF-8(BOM付き)」に変換すると、以後
この形式で読み書きします。変換はEpgTimerSrv.exeを終了させてから行ってください。
番組名などにUnicode文字を含めても文字化けせずに扱えるようになります。
※これらのファイルを直接参照する外部ツールは利用できない可能性が高くなります。
※ファイル新規作成時の既定をUTF-8にしました。外部ツールに問題が出る場合は上記の
手順で「文字コード:ANSI」に戻してください。


### Pythonでログファイル(RecInfo.log)出力

`C:\data\Mylib\EDCB\copy.py` に以下のファイルを作成します。

```python:copy.py
#!/usr/bin/python3

import os
import shutil

edcb_dir = 'C:/data/Mylib/EDCB'
edcb_rec_info = f'{edcb_dir}/Setting/RecInfo.txt'
new_rec_info = f'{edcb_dir}/Setting/RecInfo/RecInfo.new.txt'
old_rec_info = f'{edcb_dir}/Setting/RecInfo/RecInfo.old.txt'
rec_info_log = f'{edcb_dir}/Setting/RecInfo/RecInfo.log'

def copy_file(src_file: str, dist_file: str):
    shutil.copyfile(src_file, dist_file)

def swap_old_file(old_file: str, new_file: str):
    copy_file(new_file, old_file)

def extract_new_rec_info(old_file: str, new_file: str):
    old_contents = []
    new_contents = None
    if os.path.exists(old_file):
        with open(old_file, "r", encoding='utf_8_sig') as old_f:
            old_contents = old_f.readlines()
    with open(new_file, "r", encoding='utf_8_sig') as new_f:
        new_contents = new_f.readlines()

    for data in new_contents:
        if data in old_contents:
            continue
        with open(rec_info_log, 'a', encoding='utf_8_sig') as output_f:
            output_f.write(data)

if __name__ == "__main__":
    copy_file(edcb_rec_info, new_rec_info)
    extract_new_rec_info(old_rec_info, new_rec_info)
    swap_old_file(old_rec_info, new_rec_info)
```

前述した通りRecInfo.txtはログファイルとしては扱いづらいので、扱いやすい形式に変更するためにPythonで編集するようにします。
やっていることはの1つ前のRecInfo.txtと現在のRecInfo.txtを比較して増えたものをログに追記し続けているだけです。
このままだとログが増え続けてしまうので、本来であればログローテーションしなければいけないと思いますが、年1ペースでメンテナンスすればそれほど大きなサイズにはならないと思うのでここではその処理は省略しています。
このプログラムでは `C:\data\Mylib\EDCB\Setting\RecInfo` ディレクトリにログファイルなどを作成します。

### EDCBの録画終了後バッチ(PostRecEnd.bat)でcopy.pyを実行

EDCBの録画終了後バッチ(`C:\data\Mylib\EDCB\PostRecEnd.bat`)で上記のプログラム(copy.py)を実行します。

```
@echo off
setlocal
set "EDCB_DIR=%~dp0"
set "PYTHON=C:\python\bin\python.exe"
cd /d "%EDCB_DIR%"

"%PYTHON%" copy.py
```

このままだとログファイルを吐き出す先のディレクトリがないため、コマンドプロンプトで以下を実行するか手動でディレクトリを作成しておいてください。
```
mkdir C:\data\Mylib\EDCB\Setting\RecInfo\logs
```


### Fluentd(td-agent)の設定

RecInfo.txtは以下のようにタブ区切りでデータが入っており、このままだと非常に扱いづらいので
```
R:\Rec\タモリ倶楽部 (20220408).ts	タモリ倶楽部[字]	2022/04/09	00:20:00	00:30:00	テレビ朝日	32741	32741	1064	2069	0	0	11	2022/04/09	00:20:00	録画終了（空き容量不足で別フォルダへの保存が発生）	0	
```

Fluentdで以下のような形式にパースしてやります。
```
{
	"path": "R:\\Rec\\タモリ倶楽部 (20220408).ts",
	"title": "タモリ倶楽部[字]",
	"date": "2022/04/09",
	"startTime": "00:20:00",
	"videoLength": "00:30:00",
	"channel": "テレビ朝日",
	"originalNetworkId": "32741",
	"transportStreamId": "32741",
	"serviceId": "1064",
	"eventId": "2069",
	"drop": "0",
	"scramble": "0",
	"statusCode": "11",
	"date2": "2022/04/09",
	"startTime2": "00:20:00",
	"statusMessage": "録画終了（空き容量不足で別フォルダへの保存が発生）",
	"code": "0"
}
```

#### pos ファイル格納用のディレクトリの作成
まず、コマンドプロンプトなどからpos ファイル格納用のディレクトリを作成しておきます。
```
mkdir C:\opt\td-agent\var\log
```


#### td-agentの設定
td-agentの設定ファイル(`C:\opt\td-agent\etc\td-agent\td-agent.conf`) に以下を追記します。

```:td-agent.conf
# RecInfo.logの監視とパース
<source>
  @type tail
  path C:\data\Mylib\EDCB\Setting\RecInfo\RecInfo.log
  pos_file C:\opt\td-agent\var\log\RecInfo.txt.pos
  tag edcb.log
  format /^(?<path>.*)\t(?<title>.+)\t(?<date>[0-9]{4}/[0-9]{2}/[0-9]{2})\t(?<startTime>[0-9]{2}:[0-9]{2}:[0-9]{2})\t(?<videoLength>\d\d:\d\d:\d\d)\t(?<channel>.+)\t(?<originalNetworkId>\d+)\t(?<transportStreamId>\d+)\t(?<serviceId>\d+)\t(?<eventId>\d+)\t(?<drop>\d+)\t(?<scramble>\d+)\t(?<statusCode>\d+)\t(?<date2>[0-9]{4}/[0-9]{2}/[0-9]{2})\t(?<startTime2>[0-9]{2}:[0-9]{2}:[0-9]{2})\t(?<statusMessage>.+)\t(?<code>\S+)/
</source>

# ログファイルのコピー
<match edcb.log>
  @type copy
  <store>
    @type file
    path C:\data\Mylib\EDCB\Setting\RecInfo\logs
    append true
  </store>
</match>

# fluentd の監視用設定
<source>
  type monitor_agent
  bind 0.0.0.0
  port 24220
</source>
```

パースの部分はもう少しキレイに正規表現を書けるんでしょうけど、とりあえずパースできればいいのでこの形にしています。
Fluentdの正規表現が正しいかは[Fluentular](http://fluentular.herokuapp.com/)で確認しならが操作できるそうです[^1]。

[Fluentularでのパース例](http://fluentular.herokuapp.com/parse?regexp=%5E%28%3F%3Cpath%3E.*%29%5Ct%28%3F%3Ctitle%3E.%2B%29%5Ct%28%3F%3Cdate%3E%5B0-9%5D%7B4%7D%2F%5B0-9%5D%7B2%7D%2F%5B0-9%5D%7B2%7D%29%5Ct%28%3F%3CstartTime%3E%5B0-9%5D%7B2%7D%3A%5B0-9%5D%7B2%7D%3A%5B0-9%5D%7B2%7D%29%5Ct%28%3F%3CvideoLength%3E%5Cd%5Cd%3A%5Cd%5Cd%3A%5Cd%5Cd%29%5Ct%28%3F%3Cchannel%3E.%2B%29%5Ct%28%3F%3CoriginalNetworkId%3E%5Cd%2B%29%5Ct%28%3F%3CtransportStreamId%3E%5Cd%2B%29%5Ct%28%3F%3CserviceId%3E%5Cd%2B%29%5Ct%28%3F%3CeventId%3E%5Cd%2B%29%5Ct%28%3F%3Cdrop%3E%5Cd%2B%29%5Ct%28%3F%3Cscramble%3E%5Cd%2B%29%5Ct%28%3F%3CstatusCode%3E%5Cd%2B%29%5Ct%28%3F%3Cdate2%3E%5B0-9%5D%7B4%7D%2F%5B0-9%5D%7B2%7D%2F%5B0-9%5D%7B2%7D%29%5Ct%28%3F%3CstartTime2%3E%5B0-9%5D%7B2%7D%3A%5B0-9%5D%7B2%7D%3A%5B0-9%5D%7B2%7D%29%5Ct%28%3F%3CstatusMessage%3E.%2B%29%5Ct%28%3F%3Ccode%3E%5CS%2B%29&input=R%3A%5CRec%5C%E3%82%BF%E3%83%A2%E3%83%AA%E5%80%B6%E6%A5%BD%E9%83%A8+%2820220408%29.ts%09%E3%82%BF%E3%83%A2%E3%83%AA%E5%80%B6%E6%A5%BD%E9%83%A8%5B%E5%AD%97%5D%092022%2F04%2F09%0900%3A20%3A00%0900%3A30%3A00%09%E3%83%86%E3%83%AC%E3%83%93%E6%9C%9D%E6%97%A5%0932741%0932741%091064%092069%090%090%0911%092022%2F04%2F09%0900%3A20%3A00%09%E9%8C%B2%E7%94%BB%E7%B5%82%E4%BA%86%EF%BC%88%E7%A9%BA%E3%81%8D%E5%AE%B9%E9%87%8F%E4%B8%8D%E8%B6%B3%E3%81%A7%E5%88%A5%E3%83%95%E3%82%A9%E3%83%AB%E3%83%80%E3%81%B8%E3%81%AE%E4%BF%9D%E5%AD%98%E3%81%8C%E7%99%BA%E7%94%9F%EF%BC%89%090&time_format=)

#### サービス化

[公式の手順](https://docs.fluentd.org/installation/install-by-msi#step-5-run-td-agent-as-windows-service)通りに fluentd を Windows サービス化します。

```
PS> Start-Service fluentdwinsvc
```

### Datadog agentの設定

まず、ログ収集機能を有効化します[^2]。

>    [Datadog Agent Manager] -> [Settings]
>       実体は C:\ProgramData\Datadog\datadog.yaml
```yaml
logs_enabled: true
```

[カスタムログ収集](https://docs.datadoghq.com/ja/agent/logs/?tab=tailfiles#%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%AD%E3%82%B0%E5%8F%8E%E9%9B%86)の設定ファイル(`C:\ProgramData\Datadog\conf.d\edcb.d/conf.yam`)を以下のように作成します。
```yaml:edcb.d/conf.yaml
logs:
  - type: file
    path: C:\data\Mylib\EDCB\Setting\RecInfo\logs\*.log
    service: edcb
    source: edcb_log
```

以上で、DatadogでEDCBの録画ログを収集できるようになります。

## Datadogで録画エラー時にSlack通知する設定

SlackとDatadogの連携方法は[公式サイトの設定方法](https://www.datadoghq.com/ja/blog/slack%E3%82%A4%E3%83%B3%E3%83%86%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E6%83%85%E5%A0%B1%E5%85%B1%E6%9C%89/)を参考にしてください。

https://app.datadoghq.com/monitors/manage
[Monitors] -> [+ New Monitor] から新規にMonitorを追加します。

以下の画面でLogsを選択

![2022-04-10 02_09_49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136134/9105e25a-00d2-9f7e-0ee7-1ed2f7433cfc.png)

queryに以下を入力します。
```
"\"録画開始処理に失敗しました\"" OR "\"チューナーのオープンに失敗しました\"" OR "\"次の予約開始のためにキャンセルされました\"" OR "\"録画時間に起動していなかった可能性があります\"" OR "\"チューナー不足のため失敗しました\"" OR "\"一部のみ録画が実行された可能性があります\"" OR "\"指定チャンネルのデータがBonDriverから出力されなかった可能性があります\"" OR "\"ファイル保存で致命的なエラーが発生した可能性があります\""
```

`Alert thresholdg` には0を入力します。

![2022-04-10 02_11_30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136134/88022f40-31a3-9b50-d88c-343f827a23b6.png)


`Notify your team`には通知したいSlackのチャンネル(ここでは `slack-datadog-notif` とする)を設定します。

タイトルを入力して、本文には以下のように設定します。
```
{{#is_alert}}
@slack-datadog-notif 
{{/is_alert}}
```

![2022-04-10 02_15_53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136134/a8755ec8-2d3d-26ce-a89a-110db87d2b1e.png)


以上で録画エラー時にSlackチャンネルに通知が飛ぶようになります。

# 参考サイト
 [^1]: [Fluentdの正規表現を攻略する](https://qiita.com/hayashier/items/7524000f07f63303e7e0), Online; accessed 09–Apr–2022.
 
 [^2]: [Datadog Logs Windows上の日本語ログ(SJIS)登録 - vague memory](https://htnosm.hatenablog.com/entry/2018/08/12/090000), Online; accessed 09–Apr–2022.