Fluentdの正規表現を攻略する

# formatのデバッグツール 
Fluentdのログの正規表現が正しいかはFluentularで確認しならが操作できる。
http://fluentular.herokuapp.com/

![スクリーンショット 2017-03-27 3.18.16.png](https://qiita-image-store.s3.amazonaws.com/0/81083/f9d8a526-e8b4-8a74-456c-7170be63fcee.png "スクリーンショット 2017-03-27 3.18.16.png")


ただし、これはHerokuの無料プランで動いているらしく使用できないことも多いのでDockerからでもできる

```
$ docker pull tomohiro/fluentular
$ docker run -p 8080:8080 tomohiro/fluentular
```


# 作成方法
定義されたフォーマットを指定するか正規表現を利用します。

## 定義されたフォーマット一覧
fluentd側で予め10個の定義が用意されています。デフォルトのログの設定を利用している場合はこれらを使用することができると思われるので使用すると良いでしょう。

- apache2
- apache_error
- nginx
- syslog
- tsv
- csv
- ltsv
- json
- none
- multiline

ただし、注意点としてこれは常に通用するものではなく、該当する場合にのみ使用することができます。

### apache2

```
format /^(?<host>[^ ]*) [^ ]* (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/
time_format %d/%b/%Y:%H:%M:%S %z
```

### apache_error

```
format /^\[[^ ]* (?<time>[^\]]*)\] \[(?<level>[^\]]*)\](?: \[pid (?<pid>[^\]]*)\])? \[client (?<client>[^\]]*)\] (?<message>.*)$/
```

### nginx

```
format /^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*) +\S*)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")?$/
time_format %d/%b/%Y:%H:%M:%S %z
```

### syslog

```
format /^(?<time>[^ ]*\s*[^ ]* [^ ]*) (?<host>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[0-9]+)\])?(?:[^\:]*\:)? *(?<message>.*)$/
time_format %b %d %H:%M:%S
```

### tsv

```
format tsv
keys key1,key2,key3
time_key key2
```

### csv

```
format csv
keys key1,key2,key3
time_key key3
```

### ltsv

```
format ltsv
delimiter =               # Optional. ':' is used by default
time_key time_field_name
```

### json

```
format json
time_key key3
```


### none

```
format none
message_key my_message
```

### multiline

```
format multiline
format_firstline /^Started/
format1 /Started (?<method>[^ ]+) "(?<path>[^"]+)" for (?<host>[^ ]+) at (?<time>[^ ]+ [^ ]+ [^ ]+)\n/
format2 /Processing by (?<controller>[^\u0023]+)\u0023(?<controller_method>[^ ]+) as (?<format>[^ ]+?)\n/
format3 /(  Parameters: (?<parameters>[^ ]+)\n)?/
format4 /  Rendered (?<template>[^ ]+) within (?<layout>.+) \([\d\.]+ms\)\n/
format5 /Completed (?<code>[^ ]+) [^ ]+ in (?<runtime>[\d\.]+)ms \(Views: (?<view_runtime>[\d\.]+)ms \| ActiveRecord: (?<ar_runtime>[\d\.]+)ms\)/
```


## 正規表現で記述する

### 名前付きキャプチャ
正規表現から該当箇所と通り出したいときに()で括ったところは＄1,＄2,＄3,…のように取り出しますが、名前付きキャプチャを利用することで取り出しの部分に名前をつけることができます。

(?＜name＞pattern)


http://qiita.com/jnchito/items/cceb669cb06fc044f411

### これだけは抑えておきたい正規表現
####^ log $
^以降にログのパターンを書き始め、$で終端。

####[pattern]
patternの文字列

####[^pattern]
pattarn以外の文字列

#### \S
空白文字（半角スペース、\t、\n、\r、\f）以外すべて

####.
任意の1文字

####*
0文字以上

####+
1文字以上

####\special
エスケープ文字の役割を担う文字を文字そのものとして表現したいときは`\`を文字の前に付ける
[,],", 空白

####(?: pattern )
パターンのグループを表す。

####( pattern1 | pattern2 )
pattern1またはpattern2

### 実際に正規表現を組み立てる
Apacheの`httpd.conf`におけるログの設定は以下の通りとなります。

```
ErrorLog "logs/error_log"
LogLevel warn

<IfModule log_config_module>
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %q" combined
    LogFormat "%h %l %u %t \"%r\" %>s %b %q" common

    <IfModule logio_module>
      LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O %q" combinedio
    </IfModule>
    CustomLog "logs/access_log" combined
</IfModule>
```


```
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
```


LogFormatの各項目の詳細は以下のURLを参照する。
https://httpd.apache.org/docs/2.4/ja/mod/mod_log_config.html


実際に吐き出されたアクセスログは以下の通りとなります。

```
10.0.0.85 - - [03/Feb/2017:11:53:21 +0900] "GET /requests/form/34 HTTP/1.1" 200 95409 "https://sample.jp/sample_nailists?station=%E4%B8%AD%E9%87%8E%E6%96%B0%E6%A9%8B" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Mobile/14D27"
```

```
10.0.1.172 - - [30/Jan/2017:22:26:50 +0900] "GET /nailists/set_reservable?login=1 HTTP/1.1" 302 233 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Version/10.0 Mobile/14D27 Safari/602.1" ?login=1
```

```
10.0.0.85 - - [19/Feb/2017:03:28:41 +0900] "-" 408 - "-" "-"
```

3つめはELBによるヘルスチェックが記録されています。4つめはリクエストがタイムアウトされた様子を表しています。

```
10.0.0.85 - - [21/Feb/2017:23:41:21 +0900] "GET /healthcheck.txt HTTP/1.1" 200 - "-" "ELB-HealthChecker/1.0"
```


上記のようなApacheのアクセスログを正規表現します。

## 正規表現 作成手順
1.最初に1つめから考えていきます。
最初は`10.0.0.85`から始まり、IPアドレスを表します。厳密に考えるとプライベートアドレスなどの範囲を考えてXXX.XXX.XXX.XXXの表記となるような表現となりますが、ログの吐き出しは吐き出しが限定されるので厳密にやる必要はなく、空白以外の文字が0文字以上となる表現でOK。空白以外は`[^ ]`で表されこれが0文字以上になるので`[~ ]*`で表される。これに名前付けをするために`(?<host>[^ ]*)`となる。最後にこのホスト名から始まるので`^`を先頭につけて、`^(?<host>[^ ]*)`となる。

2.次に空白を挟み、`-`が2回繰り返される。最初のremotelogが存在しないことによるもの。次の`-`はuserが存在しないことによるもの。` (?<remotelog>[^ ]*) (?<user>[^ ]*) `で表されます。空白に注意。以降空白の説明は省略します。

3.`[03/Feb/2017:11:53:21 +0900]`は時間を表します。時間については別途time_formatで指定します。`[`, `]`ですが、これは特殊文字なので`\`でエスケープして使うのでこれは`\[　time \]`で表現します。次に`time`に相当するものですが、時間中には`]`は含まれないのでこれ以外の任意の文字列なので`]`は除外するためにこれ以外の文字が0文字以上となるので`[^\]`と`*`を組み合わせて`[^\]*`で表現します。これに名前付けをするために`(?<time>[^\]]*)`となり、全て合わせて`\[(?<time>[^\]]*)\]`となります。
`time_format`ですが`03/Feb/2017:11:53:21 +0900`なのでそれぞれの日付表記に対応するフォーマットを使用して`%d/%b/%Y:%H:%M:%S %z`となります。

4.`"GET /requests/form/34 HTTP/1.1"`ですが、これは`"`で囲まれた要素に対して、`GET`と`/requests/form/34`と`HTTP/1.1`の要素から成ります。最初に`"`で囲まれているので`"pattern"`のようにします。
次に`pattern`についてですが、
`GET /requests/form/34 HTTP/1.1`ですが、`GET`は`(?<method>\S+)`、このあとに空白が来て続いてくるので` +`、`/requests/form/34`、`(?<path>[^ ]*)`、空白が来るので、` +`、`HTTP/1.1`が来るので`\S*`とします。

以下、同様にして組み立てていくと例えば、以下のような正規表現を作成することができます。




```
^(?<host>[^ ]*) (?<remotelog>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<status>[^ ]*) (?<size>[^ ]*) "(?<referer>[^\"]*)" "(?<agent>.*)"\ *(?<querystring>[^\"]*)$
```


作成したものとFluentularの出力の見本は以下のURLとなります。
http://fluentular.herokuapp.com/parse?regexp=%5E%28%3F%3Chost%3E%5B%5E+%5D*%29+%28%3F%3Cremotelog%3E%5B%5E+%5D*%29+%28%3F%3Cuser%3E%5B%5E+%5D*%29+%5C%5B%28%3F%3Ctime%3E%5B%5E%5C%5D%5D*%29%5C%5D+%22%28%3F%3Cmethod%3E%5CS%2B%29%28%3F%3A+%2B%28%3F%3Cpath%3E%5B%5E+%5D*%29+%2B%5CS*%29%3F%22+%28%3F%3Cstatus%3E%5B%5E+%5D*%29+%28%3F%3Csize%3E%5B%5E+%5D*%29+%22%28%3F%3Creferer%3E%5B%5E%5C%22%5D*%29%22+%22%28%3F%3Cagent%3E.*%29%22%5C+*%28%3F%3Cquerystring%3E%5B%5E%5C%22%5D*%29%24&input=10.0.0.85+-+-+%5B03%2FFeb%2F2017%3A11%3A53%3A21+%2B0900%5D+%22GET+%2Frequests%2Fform%2F34+HTTP%2F1.1%22+200+95409+%22https%3A%2F%2Fsample.jp%2Fsample%3Fstation%3D%25E4%25B8%25AD%25E9%2587%258E%25E6%2596%25B0%25E6%25A9%258B%22+%22Mozilla%2F5.0+%28iPhone%3B+CPU+iPhone+OS+10_2_1+like+Mac+OS+X%29+AppleWebKit%2F602.4.6+%28KHTML%2C+like+Gecko%29+Mobile%2F14D27%22&time_format=%25d%2F%25b%2F%25Y%3A%25H%3A%25M%3A%25S+%25z

正しく作成されている場合、以下のように名前付きラベルに対応する値の内容が表示される。

![スクリーンショット 2017-03-27 3.17.58.png](https://qiita-image-store.s3.amazonaws.com/0/81083/957b1448-14ab-d565-66de-3411d2912cfd.png "スクリーンショット 2017-03-27 3.17.58.png")

# サンプル

## アクセスログ

### ログ

```access_log
10.0.0.85 - - [03/Feb/2017:11:53:21 +0900] "GET /requests/form/34 HTTP/1.1" 200 95409 "https://sample.jp/sample_nailists?station=%E4%B8%AD%E9%87%8E%E6%96%B0%E6%A9%8B" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Mobile/14D27"


10.0.1.172 - - [30/Jan/2017:22:26:50 +0900] "GET /nailists/set_reservable?login=1 HTTP/1.1" 302 233 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Version/10.0 Mobile/14D27 Safari/602.1" ?login=1

10.0.0.85 - - [21/Feb/2017:23:41:21 +0900] "GET /healthcheck.txt HTTP/1.1" 200 - "-" "ELB-HealthChecker/1.0"

10.0.0.85 - - [19/Feb/2017:03:28:41 +0900] "-" 408 - "-" "-"
```

### 正規表現

```
^(?<host>[^ ]*) (?<remotelog>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<status>[^ ]*) (?<size>[^ ]*) "(?<referer>[^\"]*)" "(?<agent>.*)"\ *(?<querystring>[^\"]*)$
```

```
%d/%b/%Y:%H:%M:%S %z
```

## エラーログ

### ログ

```error_log
[Sun Feb 19 03:08:02.063361 2017] [auth_digest:notice] [pid 26649] AH01757: generating secret for digest authentication ...
[Sun Feb 19 03:08:02.065290 2017] [lbmethod_heartbeat:notice] [pid 26649] AH02282: No slotmem from mod_heartmonitor
[Sun Feb 19 03:08:02.203091 2017] [mpm_prefork:notice] [pid 26649] AH00163: Apache/2.4.23 (Amazon) PHP/5.6.22 configured -- resuming normal operations
[Sun Feb 19 03:08:02.203473 2017] [core:notice] [pid 26649] AH00094: Command line: '/usr/sbin/httpd'
[Mon Feb 20 23:34:38.774239 2017] [:error] [pid 20741] [client 10.0.1.172:21883] CSRF state token does not match one provided.
[Mon Feb 20 23:34:39.373401 2017] [:error] [pid 20741] [client 10.0.1.172:21883] CSRF state token does not match one provided.
[Wed Feb 22 10:12:12.180706 2017] [:error] [pid 17757] [client 10.0.1.172:10406] CSRF state token does not match one provided.
[Wed Feb 22 16:45:54.469282 2017] [:error] [pid 27395] [client 10.0.0.85:59532] CSRF state token does not match one provided.
[Wed Feb 22 16:48:53.326150 2017] [:error] [pid 27433] [client 10.0.0.85:59628] PHP Fatal error:  Call to a member function is_in_area() on null in /var/www/sample/releases/20170222074811/fuel/app/classes/controller/api/requests.php on line 116, referer: https://sample.jp/requests/confirm?judge=2
~
```

### 正規表現

```
^\[(?<time>[^\]]*)\] \[(?<type>[^\]]*)\] \[(?<pid>[^\]]*)\] (\[(?<client>[^\]]*)\](?<error>.*)|(?<error>.*))$
```

```
%a %b %d %H:%M:%S.%N %Y
```

# td-agent.confの設定例


```td-agent.conf
<source>
  type tail
  format /^(?<host>[^ ]*) (?<remotelog>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^ ]*) +\S*)?" (?<status>[^ ]*) (?<size>[^ ]*) "(?<referer>[^\"]*)" "(?<agent>.*)"\ *(?<querystring>[^\"]*)$/
  time_format %d/%b/%Y:%H:%M:%S %z
  path /var/log/httpd/access_log
  tag sample.service.access.log.aggregate
  pos_file /tmp/fluent/logpos/access_log.pos
</source>

<source>
  type tail
  format /^\[(?<time>[^\]]*)\] \[(?<type>[^\]]*)\] \[(?<pid>[^\]]*)\] (\[(?<client>[^\]]*)\](?<error>.*)|(?<error>.*))$/
  time_format %a %b %d %H:%M:%S.%N %Y
  path /var/log/httpd/error_log
  tag sample.service.error.log.aggregate
  pos_file /tmp/fluent/logpos/error_log.pos
</source>

<match **>
  type forward

  <server>
    name fluent01
    host 10.0.0.31
    port 24224
  </server>
</match>
```

```td-agent.conf
<source>
  type forward
  port 24224
</source>

<match sample.service.access.**>
  type copy

  <store>
    type elasticsearch
    host https://search-sample-service-logs-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com
    type_name access_log
    logstash_format true
    logstash_prefix sample-service-log-access
    request_timeout 10s
    buffer_type memory
    flush_interval 60
    retry_limit 17
    retry_wait 1.0
    num_threads 1
  </store>

  <store>
    type s3
    aws_key_id AKIAxxxxxxxxxxxxxxxxxxxx
    aws_sec_key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    s3_bucket sample-log
    s3_region ap-northeast-1
    s3_object_key_format %{path}%{time_slice}/%{index}.%{hostname}.%{file_extension}
    path access-logs/
    buffer_path /tmp/fluent/s3/access/
    time_slice_format %Y%m%d-%H
    time_slice_wait 10m
    utc
    include_time_key true
  </store>
</match>

<match sample.service.error.**>
  type copy

  <store>
    type elasticsearch
    host https://search-sample-service-logs-xxxxxxxxxxxxxxxxxxxxxxxxxxx.ap-northeast-1.es.amazonaws.com
    type_name error_log
    logstash_format true
    logstash_prefix sample-service-log-error
    request_timeout 10s
    buffer_type memory
    flush_interval 60
    retry_limit 17
    retry_wait 1.0
    num_threads 1
  </store>

  <store>
    type s3
    aws_key_id AKIAxxxxxxxxxxxxxxxxx
    aws_sec_key xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    s3_bucket sample-log
    s3_region ap-northeast-1
    s3_object_key_format %{path}%{time_slice}/%{index}.%{hostname}.%{file_extension}
    path error-logs/
    buffer_path /tmp/fluent/s3/error
    time_slice_format %Y%m%d-%H
    time_slice_wait 10m
    utc
    include_time_key true
  </store>
</match>

```

## (参照)権限設定

```
$ sudo chgrp td-agent /var/log/httpd/
$ sudo chgrp td-agent /var/log/messages

$ sudo chmod g+rx /var/log/httpd/
$ sudo chmod g+rx /var/log/messages
```

以上


# Appendix
## td-agent.conf パラメータ一覧

```
type
tag
path
exclude_path
refresh_interval
limit_recently_modified
skip_refresh_on_startup
read_from_head
read_lines_limit
multiline_flush_interval
pos_file
format
time_format
path_key
rotate_wait
enable_watch_timer
```


# 参考
http://docs.fluentd.org/v0.12/articles/in_tail
http://www.tachibanakikaku.com/2013/12/fluentdformat.html