_ とある Web アプリのログを取りたい。通常のアクセスログではなく、POST で送ったデータと、その応答。具体的には XML や JSON のような構造を持ったデータの一部を記録しておきたい。どうやればいいんだべ。ちなみに、その Web アプリは他人様が動かしてるものなのでこちらではいじれない。クライアント側でログを取ることを想定。
_ こういうことやる場合は、apache なプロクシを経由させるようにして、その apache 上で mod_dumpioを使うというのがまず思いつくんだけど、これ使いものにならんよな。特定の URL へのアクセスだけ、さらにそのコンテンツに含まれる必要な情報だけをロギングするということができないので、ほんとうに欲しい情報が大量のゴミに埋もれてしまう。一時的なデバッグにはいいかもしれないけど、常用するべきものじゃない。
_ apache には input/output filter という仕組みがあって、入出力されるコンテンツに任意のフィルタ処理をさせることができるので、これを使ってログを取るのがよさそう。が、入力と出力でまったく別個にフィルタ実行されて連携が取れないので、こういうデータを POST したらこういう応答が返ってきた、という関連づけするのがめんどくせぇ。mod_unique_id で一意の ID を生成してやればいいんだけど、この ID から入力側のログと出力側のログを付き合わせるというよけいな作業が別途必要になる。まあ、メールも from/to のログが分離してるわけなので大して違いはないといえなくてもないけど。もっと大きいのは、アクセス1回につき外部スクリプトが入出力で合わせて2回実行される効率の悪さ。自分が使うだけなのでパフォーマンスはわりとどうでもいいんだけど、でももう少しなんとかならんものか。
_ うーん、と悩んで煮詰まって、現実逃避に lsyncd で lua な設定ファイルをいじっていたらひらめいた(なお、本来は lsyncd の方がお仕事で、Web のログ取りこそ仕事とはまったく無関係な現実逃避の模様)。 mod_luaを使えば外部スクリプトじゃなくて apache の内部で動くフィルタを書けるじゃん。内部で動くから入出力の連携できる上にパフォーマンスもよくなるじゃん。ちうことで、こんな感じでさくっと実装。
-- httpd.conf LuaInputFilter filter_in /path/to/filter.lua filter_in LuaOutputFilter filter_out /path/to/filter.lua filter_out CustomLog /var/log/httpd/webapi.log "%{%Y/%m/%d %H:%M:%S}t \"%r\" %{filterlog}n" <Proxy http://ログを取りたいURL> SetInputFilter filter_in SetOutputFilter filter_out </Proxy> -- filter.lua function filter_in(r) local req = "" coroutine.yield() while bucket do req = req .. bucket coroutine.yield(bucket) end r.notes.postdata = req:sub(1, 1024) -- request body の先頭1024バイトをメモ領域に格納 coroutine.yield() end function filter_out(r) local res = "" local req = r.notes.postdata or "" -- メモ領域から POST された情報を取得 coroutine.yield() while bucket do res = res .. bucket coroutine.yield(bucket) end --[[ このへんに POST されたデータ(req)やらその応答(res)やらから 必要な情報をひっぺがす処理 ]]-- r.notes.filterlog = ... -- 取得した情報をメモ領域に格納 coroutine.yield() end_ apache はリクエストごとに内部のモジュール間で情報のやりとりをするためのメモ領域を持つので、そいつを介して入力フィルタで取得した POST データを出力フィルタに受け渡し、そこで必要な情報だけを抽出する。CustomLog はメモ領域をログに吐き出すこともできるので、ロギングも自前でやらずにそちらにお任せする。このフィルタが実行されなかった場合でも空っぽのログが出力されてしまうのがうっとうしいけど、まあいいや(CustomLog を使わず自前でログを吐くようにすれば解決できる問題)。
_ が、これでもまだめんどくさいよね。mod_lua なんてまだまだ実用するには怖いものだし。もっとエレガントに実現する方法はないかねぇ。
_ ちなみに、ほかに検討した案は pcap でパケットを覗き見するとか、squid の外部フィルタとして ICAP サーバを独自実装するとか。どれもやりたいことのわりに仕掛けが大がかり過ぎるのでボツ。apache 上で動くフィルタを書くのも十分大げさだけど。