Apache
Fluentd
td-agent2

fluentdでApacheログをLTSVで転送するときのひと工夫(内部リダイレクト表記対策)

More than 3 years have passed since last update.

Apacheのアクセスログの書式、それをfluentd等の他のログ収集システムに持っていくときの問題です。


Apacheログ "%U%q" のパス出力は"%r"と互換性が無い

Apacheのmod_rewriteによる内部リダイレクト規則を適用する場合、例えば下記のような


.htaccess

<IfModule mod_rewrite.c>

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
</IfModule>

という設定を使う場合、例えば http://example.com/top/ というURLにアクセスすると、Apacheのカスタムログフォーマットでいうと、

%r: GET /top/ HTTP/1.1

%U: /top/
%q: ?url=top/

となり、

%U%q: /top/?url=top/

となります。/top/ になってほしいのに、内部rewrite処理で生成された?url=top が付いてしまうのです。その時のredirect_logはこんな感じ。


redirect_log

(3) [perdir /var/www/html/] add path info postfix: /var/www/html/top -> /var/www/html/top/

(3) [perdir /var/www/html/] strip per-dir prefix: /var/www/html/top/ -> top/
(3) [perdir /var/www/html/] applying pattern '^(.*)$' to uri 'top/'
(4) [perdir /var/www/html/] RewriteCond: input='/var/www/html/top' pattern='!-d' => matched
(4) [perdir /var/www/html/] RewriteCond: input='/var/www/html/top' pattern='!-f' => matched
(2) [perdir /var/www/html/] rewrite 'top/' -> 'index.php?url=top/'
(3) split uri=index.php?url=top/ -> uri=index.php, args=url=top/
(3) [perdir /var/www/html/] add per-dir prefix: index.php -> /var/www/html/index.php
(2) [perdir /var/www/html/] trying to replace prefix /var/www/html/ with /
(5) strip matching prefix: /var/www/html/index.php -> index.php
(4) add subst prefix: index.php -> /index.php
(1) [perdir /var/www/html/] internal redirect with /index.php [INTERNAL REDIRECT]

fluentdのログ転送で使うLTSVフォーマット指定で、パスの表記に%U%qで指定している例が多いので、このままだと通常のApacheログとfluentdで収集したLTSV表記Apacheログでパス表記が食い違うことに……


ここで唐突にApache CustomLog書式の確認

http://httpd.apache.org/docs/2.2/ja/mod/mod_log_config.html

フォーマット文字列
説明

%q
問い合せ文字列 (存在する場合は前に ? が追加される。 そうでない場合は空文字列)

%r
リクエストの最初の行

%U
リクエストされた URL パス。クエリ文字列は含まない


修飾子

(略)

修飾子 "<" と ">" は内部リダイレクトされたリクエストのログに 元のリクエストか最終的なリクエストのどちらを使用するかを 指定するために使います。デフォルトでは、% ディレクティブの %s, %U, %T, %D, %r は元のリクエストを、他は最終的なリクエストを 使用します。例えば、リクエストの最終ステータスを記録するには %>s を、内部的に認証されていないリソースへリダイレクトされた リクエストで元のリクエストで認証されたユーザを記録するためには %<u を使うことができます。



LogFormatで%U%<qすればいいじゃない

効かないんだなーこれが


解決法


td-agent.conf

## Apache 拡張ltsvログ用

# LogFormat "domain:%V\thost:%h\tserver:%A\tuser:%u\t
#time:%{%d/%b/%Y:%H:%M:%S %z}t\tmethod:%m\tpath:%U%q\tprotocol:%H\tcode:%>s
#\tsize:%b\treferer:%{Referer}i\tagent:%{User-Agent}i\tresponse_time:%D\t
#cookie:%{cookie}i\tset_cookie:%{Set-Cookie}o\tcountry:%{GEOIP_COUNTRY_CODE}e\t
#requestFirstLine:\"%r\"" combined_ltsv

<source>
type tail
path /var/log/httpd/ltsv_access_log
pos_file /var/log/td-agent/apache_access.pos
format ltsv
time_key time
time_format %d/%b/%Y:%H:%M:%S %z
tag apache.access.addHostname.fixPath
types code:integer,size:integer,response_time:integer
</source>

## record_transformerプラグインで、ホストを追加する
<filter **.addHostname>
@type record_transformer
<record>
hostname ${hostname}
</record>
</filter>

## record_transformerプラグインで、requestFirstLineからpathを分離し、追加する
<filter **.fixPath>
@type record_transformer
enable_ruby true
<record>
path ${requestFirstLine.split(/ /)[1]}
</record>
remove_keys requestFirstLine
</filter>

# Apache関連のログを転送
<match apache.**>
type copy

# デバッグ用
#<store>
# type stdout
#</store>

# fluentdサーバに送信
<store>
type forward
flush_interval 30s
<server>
host fluentd-server.example.com
port 24224
</server>
</store>

</match>


など、apacheの方で%rでrequestFirstLineを追加、record_transformerプラグインでrequestFirstLineからpath部を分離し、pathとして追加する方法を採用しました。

filterやrecord_transformerが使える、fluentd v0.12以降(td-agent2なら2015/4/6のtd-agent v2.2.0以降)が必要。

[http://cl.hatenablog.com/entry/apache-customlog より加筆]