Dockerのログをrsyslogで出力する

## 概要
Dockerのログをrsyslogで出力する方法。

## 関連
- [任意のログをlogrotateを使って管理する](https://qiita.com/Esfahan/items/a8058f1eb593170855a1)

## 環境
- CentOS 7.7
- rsyslog 8.24.0-41.el7_7.2

## Dockerの設定
Dockerもしくはdocker-composeで、以下のパラメータを指定し、rsyslogにログが渡るようにする。
ログには、`This is a example.`という文字列が出力されるようにする。

| 設定内容 | Docker | docker-compose |
| --- | --- | --- |
| syslogを使う | --log-driver=syslog | logging.driver |
| ログの種類の分類 |--log-opt syslog-facility={facility名} | logging.options.syslog-facility |
| ログやファイル名に使用する名前 | --log-opt tag={任意の文字列} | logging.options.tag |

### dockerコマンドの場合

```shell-session
$ sudo docker run --rm \
    --name sample-container \
    --log-driver=syslog \
    --log-opt syslog-facility=daemon \
    --log-opt tag=sample-app/{{.Name}}/{{.ID}} \
    -it ${Dockerイメージ名} \
    /bin/bash -c 'echo "This is a example."'
```

### docker-composeの場合

```yaml:docker-compose.yml
version: '3'
services:
  sample-container:
    image: sample-image
    command: 'echo "This is a example."'
    logging:
      driver: syslog
      options:
        syslog-facility: daemon
        tag: sample-app/{{.Name}}/{{.ID}}
```

| 変数 | 意味 |
| --- | --- |
| {{.Name}} | コンテナ名 |
| {{.ID}} | コンテナID |

### rsyslogの設定
rsyslogのtemplateという機能を使って、ログファイル名の指定をする。
/var/log/docker/sample-app_年月日.logというログファイル名になるようにする。

/etc/rsyslog.d/10-docker.conf

```sh:/etc/rsyslog.d/10-docker.conf
$template DockerLogs, "/var/log/docker/%programname%_%$year%%$month%%$day%.log"

if $syslogfacility-text == 'daemon' and $programname contains 'sample-app' then -?DockerLogs
& stop
```

| 変数 | 意味 |
| --- | --- |
| $template | templateを使うための宣言 |
| DockerLogs | template名。任意の文字列でOK |
| %programname% | ログを出力しているプログラム名 |
| $programname | ログを出力しているプログラム名 |
| $syslogfacility-text | **--log-opt syslog-facility** もしくは **logging.options.syslog-facility** に指定した文字列 |

その他の変数は以下を参照
[rsyslog.confの文法](https://qiita.com/11ohina017/items/087b1b7c4411c3b3195a#%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3)

```
if $syslogfacility-text == 'daemon' and $programname contains 'sample-app' then -?DockerLogs
```

`$syslogfacility-text`が`daemon`で、`$programname`に`sample-app`という文字列が含まれるときのみログを出す。
`$syslogfacility-text`は`--log-opt syslog-facility` もしくは `logging.options.syslog-facility`と、`$programname`は`--log-opt tag` もしくは `logging.options.tag` と合わせる必要がある。

### rsyslogのrestart
/etc/rsyslog.d/10-docker.confを変更したあとはrestartが必要。

```shell-session
$ sudo systemctl restart rsyslog
```

### 出力されるログ
Dockerもしくはdocker-composeを実行。
実際に出力されるログ。

- **Format:** `/var/log/docker/{%programname%}_{%$year%%$month%%$day%}.log`
- **e.g.):** `/var/log/docker/sample-app_20200218.log`

`sample-app/{コンテナ名}/{コンテナID}`の部分は、`--log-opt tag` もしくは `logging.options.tag` に指定した文字列。

```lang:/var/log/docker/sample-app_20200218.log
Feb 18 17:03:19 example-hostname sample-app/sample-container/{コンテナID}[{dockerdのプロセスID}]: This is a example.
```


## コンテナIDのみを抽出してログファイル名にする
nomatch-Modeを使って、文字列を抽出してファイル名に使用する方法。
この方法を利用して、コンテナIDのみをログファイル名にしてみる。

### rsyslogの設定
/etc/rsyslog.d/10-docker.confに以下のように設定する。

```sh:/etc/rsyslog.d/10-docker.conf
$template DockerLogs, "/var/log/docker/%syslogtag:R,ERE,1,FIELD:sample-app/sample-container/(.*)\[(.*)\]:--end%.log"

if $syslogfacility-text == 'daemon' and $programname contains 'sample-app' then -?DockerLogs
& stop
```

`"/var/log/docker/%syslogtag:R,ERE,1,FIELD:sample-app/sample-container/(.*)\[(.*)\]:--end%.log"`の部分の説明。

| 変数 | 意味 |
| --- | --- |
| /var/log/docker/ | ログファイルを出力する場所 |
| %syslogtag% | **--log-opt tag** もしくは **logging.options.tag** に指定した文字列。正規表現で抽出する対象文字列となる。 |
| R | nomatchを利用するときはここはR |
| ERE | 正規表現のタイプ。BRE of ERE。EREはBREを拡張したモード。通常こちらを選択する。 |
| 1 | [submatch](http://rdr.utopiat.net/docs/reference/etc/reg.htm)の番号 |
| FIELD | nomatchモードの指定。枠下参照。|
| `sample-app/sample-container/(.*)\[(.*)\]:` | 「R」の前の文字列（この場合は%syslogtag%）からこの正規表現で文字列を抽出し、ファイル名に使用する |
| --end%.log | 最後に必ず書く |

今回の設定の場合は、`%syslogtag%`から正規表現`sample-app/sample-container/(.*)\[(.*)\]:`で抽出した文字列をファイル名として使用するということになる。


### nomatchモードについて
正規表現でマッチするものがなかった場合に返す、デフォルトの文字列をModeによって指定する。
各Modeごとに返す文字列は以下のようになっているが、`FIELD`を指定した場合は、正規表現対象の文字列全てが返される。（この場合は%syslogtag%）

| Mode | Returned |
|:--|:--|
| DFLT | "\*\*NO MATCH\*\*" |
| BLANK | "" (empty string) |
| ZERO | "0" |
| FIELD | full content of original field |

引用元：[Property Replacer nomatch mode](https://www.rsyslog.com/doc/v8-stable/configuration/nomatch.html)

### 今回のFIELDの正規表現の場合の挙動
`%syslogtag%` = `sample-app/sample-container/f76f6af73829[27081]`

から、以下の正規表現で抽出をする。

```
sample-app/sample-container/(.*)\[(.*)\]:
```

submatchは`1`を指定しているため、1つ目の`(.*)`のみが抽出される。
つまり、コンテナIDである`f76f6af73829`が抽出される。

結果、以下がファイル名となる。

```
/var/log/docker/f76f6af73829.log
```

### rsyslogのrestart
/etc/rsyslog.d/10-docker.confを変更したあとはrestartが必要。

```shell-session
$ sudo systemctl restart rsyslog
```

### 出力されるログ
Dockerもしくはdocker-composeを実行。
この設定で実際に出力されるログ。

- **Format:** `/var/log/docker/sample-app/{コンテナ名}/{コンテナID}.log`
- **e.g.):** `/var/log/docker/f76f6af73829.log`

`sample-app/{コンテナ名}/{コンテナID}`の部分は、`--log-opt tag` もしくは `logging.options.tag` に指定した文字列。

```
Feb 18 17:21:23 example-hostname sample-app/sample-container/f76f6af73829[27081]: This is a example.
```

## 正規表現にマッチしない場合
### rsyslogの設定
/etc/rsyslog.d/10-docker.confの正規表現の一部を、`sample-app_nomatch`としてマッチしないようにしてみる。

```sh:/etc/rsyslog.d/10-docker.conf
$template DockerLogs, "/var/log/docker/%syslogtag:R,ERE,1,FIELD:sample-app_nomatch/sample-container/(.*)\[(.*)\]:--end%.log"

if $syslogfacility-text == 'daemon' and $programname contains 'sample-app' then -?DockerLogs
& stop
```

### rsyslogのrestart
/etc/rsyslog.d/10-docker.confを変更したあとはrestartが必要。

```shell-session
$ sudo systemctl restart rsyslog
```

### 出力されるログ
Dockerもしくはdocker-composeを実行。
この設定で実際に出力されるログ。

- **Format:** `/var/log/docker/sample-app/{コンテナ名}/{コンテナID}[{dockerdのプロセスID}].log`
- **e.g.):** `/var/log/docker/sample-app/sample-container/de64dadc7e97\[27081\]\:.log`

`sample-app/{コンテナ名}/{コンテナID}`の部分は、`--log-opt tag` もしくは `logging.options.tag` に指定した文字列。

```
Feb 18 17:21:23 example-hostname sample-app/sample-container/de64dadc7e97[27081]: This is a example
```

正規表現がマッチせず、`FIELD`をしているので、`%syslogtag%`の中身がそのままファイル名となる。
前述したように、`DFLT`の場合はファイル名が`**NO MATCH**.log`、`ZERO`の場合は`0.log`がファイル名となる。`BLANK`の場合はログファイルが出力されない。


## 参考
- [【Docker Compose】ログをコンテナごとのログファイルに出力する](https://hacknote.jp/archives/26996/)
- [マクロとテンプレートによるrsyslog活用法](https://www.atmarkit.co.jp/ait/articles/0812/05/news152_2.html)
- [rsyslog.confの文法](https://qiita.com/11ohina017/items/087b1b7c4411c3b3195a)
- [Rsyslogで送信メッセージを編集する方法は？](https://tutorialmore.com/questions-45260.htm)
- [Property Replacer nomatch mode](https://www.rsyslog.com/doc/v8-stable/configuration/nomatch.html)