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

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

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

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

```sh:.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はこんな感じ。

```sh: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](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すればいいじゃない

効かないんだなーこれが

# 解決法

```sh: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 より加筆]