Edited at

Fluentdの正規表現を攻略する

More than 1 year has passed since last update.


formatのデバッグツール

Fluentdのログの正規表現が正しいかはFluentularで確認しならが操作できる。

http://fluentular.herokuapp.com/

スクリーンショット 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/34HTTP/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


サンプル


アクセスログ


ログ

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


エラーログ


ログ

[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