$Id: apache_log.rd,v 1.5 2001/06/28 01:25:02 akira Exp $)Apacheサーバにおけるログの取り方と読み方について解説しよう。
ただ「ログ」と言っても、 Apacheサーバでは標準でいくつかの目的のためのログを 取ることができるようになっている。 そこで、まずはそれらを分類するところから始めることにする。
そのApacheサーバによって提供される情報に対する アクセスについての記録をとるためのログ。 通常、外部からのアクセスがある度に記録が残される。
関係するディレクティブ: LogFormat, CustomLog, TransferLog
設定上のエラーやアクセスされた情報に対応するファイルがないなど、 エラーが起った場合にそのエラーの内容を記録するためのログ。
関係するディレクティブ: ErrorLog
CGIスクリプトが実行され、 それがエラーになった場合にその状況を記録するためのログ。 これはデバッグのためのもので、 通常運用時にはこのログは使用しない。
関係するディレクティブ: ScriptLog
mod_usertrackによるクッキー情報を記録するためのログ。
関係するディレクティブ: CookieLog
この記事では最初のアクセスログと二つ目のエラーログについて扱うことにする。
まずアクセスログについてだが 一般にアクセスログには、アクセス毎に そのアクセスがあった時刻やアクセスされた情報、 アクセス元のホスト名やIPアドレスといった情報が記録される。 言うまでもないが、アクセスログは当該サイトに対して どのようなアクセスがなされているのかを 判断するための基礎データとなるもので、非常に重要である。
このログを取る機能は、Apacheサーバにおいては
モジュールmod_log_configによって実装されているため
同モジュールをApacheサーバに組み込んでおくか、
あるいは起動時にロードしておく必要がある。
動的にロードする必要がある場合は以下のような記述が
httpd.confにあるかどうかを確認すると良いだろう。
LoadModule config_log_module /usr/lib/apache/1.3/mod_log_config.so
モジュールそのものがある場所は インストール時の条件やバイナリパッケージの作られ方によって 異るので各環境に合わせて読み替えてほしい。
ところで、mod_log_configの特長の一つとして ログに記録する内容をかなり自由にカスタマイズできる点を挙げることができる。 よって、ログに記録される内容は固定ではなく、 むしろ様々な形にすることが可能であり、 必ずしも時刻やホスト名ばかりが記録されるわけではない。 カスタマイズのやり方については後で述べるが、 この機能をうまく使って後で集計・分析をするときのために あらかじめより処理しやすい形式でログをとることが可能となる。
このカスタマイズ機能を活用した典型的な例として、 古くはAgentLog(mod_log_agent)、RefererLog(mod_log_referer)で 記録していた2つのログをmod_log_configによって取ることが 推奨されるようになっているというのがある。 実際、次の二つの記述はまったく同じ意味を持つが、 今後は後者のやり方にしておく方が良いだろう。
古いやり方
AgentLog agent.log RefererLog referer.log
mod_log_configによるやり方
LogFormat "%{User-agent}i" agent
LogFormat "%{Referer}i -> %U" referer
CustomLog agent.log agent
CustomLog referer.log refererこれらの記述の詳細な意味は後述することにして、 まずは一般的なアクセスログの読み方から解説することにしよう。
ここでは「一般的なアクセスログ」の形式として Common Log Formatというフォーマットを取り上げる。 Common Log FormatというのはTransferLogディレクティブで ログを取るようにした時のデフォルトの形式である。 httpd.confで次のような記述をした場合にaccess.logに記録される形式がそれだ。
TransferLog access.log
このTransferLogと後で出てくるCustomLogという2つのディレクティブは ログの記録先やログのフォーマットを指定するためのものである。 両ディレクティブに深く関ってくるのディレクティブに LogFormatというものがあって、 こちらはログのフォーマットを具体的に定義するために用いられる。
さて、これら3つのディレクティブの関係だが、次のようなものとなる。
LogFormatには二つの書式がある。 一つはログフォーマットを名前を付けて定義するためのもので 次のような書き方をする。
LogFormat "ログのフォーマットの指定" <名前>
もう一つの書式は上の名前の部分を省略したもので、
このようになる。
LogFormat "ログのフォーマットの指定"
こちらはTransferLogによって記録されるログのフォーマットを 決定するためのものである。
TransferLogによって記録されるログは 直前の名前なしのLogFormatで指定されたフォーマットに従う。 特にLogFormatの指定がない場合には Common Log Formatとよばれるフォーマットで記録される。
TransferLogとの違いはログフォーマットの指定の仕方にある。 TransferLogでは直前のLogFormatによる指定だったが、 CustomLogではログファイルの名前とともに フォーマットを明示的に指定することができる。 その際、LogFormatで定義した名前によって指定できる他、 LogFormatで行うような形でフォーマットを直接指定することもできる。
CustomLog access.log <名前> CustomLog access.log "ログのフォーマットの指定"
同じフォーマットを使いまわす場合には フォーマットに対して名前を付けておいた方が便利である。
TransferLog、CustomLogともにログの対象は 当該サイトにおいて提供されている情報に対するアクセスである。 したがって指定されたログファイルには、基本的には 一回のアクセスに対応して一つのエントリが書き出されることになる。
LogFormatによる指定なしに TransferLogでログの設定を行うと Common Log Formatというフォーマットにしたがったログが記録される。 たとえば次のようなものである。
211.18.217.219 - - [20/Jun/2001:19:16:15 +0900] "GET / HTTP/1.0" 200 4636 211.18.217.219 - - [20/Jun/2001:19:16:15 +0900] "GET /pbpenguin.png HTTP/1.0" 304 - 211.18.217.219 - - [20/Jun/2001:19:16:15 +0900] "GET /pbdebian.png HTTP/1.0" 304 - 211.18.217.219 - - [20/Jun/2001:19:16:27 +0900] "GET /ruby/ HTTP/1.0" 200 4371 211.18.217.219 - - [20/Jun/2001:19:16:30 +0900] "GET /ruby/rice.html HTTP/1.0" 200 2388
これらのログは7つのパーツからなっている。 それぞれの部分が示す意味を、 一行目を例にとって解説すると次のようになる。
211.18.217.219アクセス元のIPアドレス。
ただし、HostNameLookups onになっている時のみホスト名になる。
逆に言えばHostNameLookupsがonでないかぎり、
ログの中にアクセス元ホストのホスト名が現れることはない。
-この部分には本来はアクセス元のユーザ名(IDENTによるもの)が入るが、
この時にはユーザ名を得るこるができなかったために
-となっている*1。
-この部分には認証を行った際のユーザ名が入るが、
この時はそもそも認証を行っていないために-となっている。
認証を要するページへのアクセスであった場合には
-ではなく対応するユーザ名が入る。
[20/Jun/2001:19:16:15 +0900]このエントリで表されるアクセスがあった日時。 この例については 2001年6月20日の19時16分15秒にアクセスがあったことがわかる。
"GET / HTTP/1.0"アクセスの際のリクエストの内容。
この場合/に対するGETリクエストを受けたことがわかる。
200そのリクエストに対応するステータス。
200はアクセスできたことを意味するステータスである。
アクセスが拒否されたり、リダイレクトされたりすると
この部分の値が200ではなく別のものとなる。
4636実際に送出したデータ量(バイト)。
Common Log Formatのように内蔵されたものではないが、 デォルトのhttpd.confにおいていくつかのフォーマットが名前付きで定義されている。 これらについて説明しよう。
これはCommon Log Formatそのものである。
したがって前述したように何もしないでTransferLogで設定するのと
CustomLogでcommonを指定するのとは同じ結果になる。
<一つ前にアクセスしていたページ> -> <今回アクセスしたページ>
というフォーマットでログが記録される。
意味としてはどのページからのリンクをたどって
そのページに到達したかを記録したものと考えることができる。
ただし、これらはブラウザなどからのリクエストと一緒に送られてくる
Refererヘッダの内容を元にしているものなので、
必ずしも正確なものではないし、そうした情報そのものがない場合もある。
- -> / http://arika.org/ -> /pbpenguin.png http://arika.org/ -> /pbdebian.png http://arika.org/ -> /ruby/ http://arika.org/ruby/ -> /ruby/rice.html
以上は前述したアクセスログが記述された際に
いっしょに記録したものである。
なお、一行目が-になっているのはRefererヘッダが
与えられなかったことを意味している。
アクセスしてきたブラウザが名乗った
クライアント名などに関する情報が記録される。
先のrefererと同じく必ずしも正確ではない場合がある。
Mozilla/5.0 (X11; U; Linux 2.2.19 i686; ja-JP; rv:0.9.1) Gecko/20010618 Mozilla/5.0 (X11; U; Linux 2.2.19 i686; ja-JP; rv:0.9.1) Gecko/20010618 Mozilla/5.0 (X11; U; Linux 2.2.19 i686; ja-JP; rv:0.9.1) Gecko/20010618 Mozilla/5.0 (X11; U; Linux 2.2.19 i686; ja-JP; rv:0.9.1) Gecko/20010618 Mozilla/5.0 (X11; U; Linux 2.2.19 i686; ja-JP; rv:0.9.1) Gecko/20010618
以上も先の二つのログが記録されたタイミングで記録されたログである。 いずれものアクセスも、 Mozillaというブラウザを使っていると考えることができる。
Common Log Formatにさらにrefererとagentの内容を
付け加えた形のログを記録することができる。
これは例を見てもらえばどのようなものかわかるだろう。
211.18.217.219 - - [20/Jun/2001:19:16:15 +0900] "GET / HTTP/1.0" 200 4636 "-" "Mozilla/5.0 (X11; U; Linux 2.2.19 i686; ja-JP; rv:0.9.1) Gecko/20010618" 211.18.217.219 - - [20/Jun/2001:19:16:15 +0900] "GET /pbpenguin.png HTTP/1.0" 304 - "http://arika.org/" "Mozilla/5.0 (X11; U; Linux 2.2.19 i686; ja-JP; rv:0.9.1) Gecko/20010618" 211.18.217.219 - - [20/Jun/2001:19:16:15 +0900] "GET /pbdebian.png HTTP/1.0" 304 - "http://arika.org/" "Mozilla/5.0 (X11; U; Linux 2.2.19 i686; ja-JP; rv:0.9.1) Gecko/20010618" 211.18.217.219 - - [20/Jun/2001:19:16:27 +0900] "GET /ruby/ HTTP/1.0" 200 4371 "http://arika.org/" "Mozilla/5.0 (X11; U; Linux 2.2.19 i686; ja-JP; rv:0.9.1) Gecko/20010618" 211.18.217.219 - - [20/Jun/2001:19:16:30 +0900] "GET /ruby/rice.html HTTP/1.0" 200 2388 "http://arika.org/ruby/" "Mozilla/5.0 (X11; U; Linux 2.2.19 i686; ja-JP; rv:0.9.1) Gecko/20010618"
以上もやはりこれまでのログと同じタイミングで記録されたものである。
なお、ここまでに例示したログを採取するにあたっては
次のような記述をhttpd.confにおいて行った。
TransferLog /var/log/apache/transfer.log CustomLog /var/log/apache/access.log combined CustomLog /var/log/apache/common.log common CustomLog /var/log/apache/referer.log referer CustomLog /var/log/apache/agent.log agent
この例でもわかる通り、 Apacheでは同時に複数のログを記録することが可能となっている。
さて、いよいよカスタマイズのやり方について説明しよう。 …と言ったものの、実はその例はすでに登場している。
LogFormat "%{User-agent}i" agent
LogFormat "%{Referer}i -> %U" referer
というのがそれである。
これらは前述のagentおよびrefererという
ログフォーマットを定義するための設定になる。
ここでhttpd.confをながめてもらうと
おそらく発見できると思うが、やはり前述した
commonおよびcombinedに対応するものとして
次のような設定がされているだろう。
LogFormat "%h %l %u %t \"%r\" %>s %b" common
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
もしもこれらの記述がhttpd.confにないのであれば、
これらのフォーマットを利用するにあたっては
対応する記述をhttpd.conf内にしておく必要がある。
ログフォーマットのカスタマイズというのは、
要するにフォーマットの定義のことである。
フォーマットの定義はC言語やRuby、Perlでおなじみの
strftimeに似たやり方で行う。
すなわち%charという記述(フォーマット引数と呼ぶことがある)
によって様々なログ情報を表しつつ、
ログに記録する際の体裁を作るわけである。
まずはすでに出てきているものから考えてみよう。
LogFormat "%h %l %u %t \"%r\" %>s %b" common
これはCommon Log Formatと同じフォーマットを定義するための記述である。
フォーマットを表しているのは中央の"…"の部分で、
この中にある%hや%lなど%charといった記述は
実際にログが記録される際にそれぞれcharに対応した
情報で置き換えられる。
また、それ以外の部分についてはそのままログに記録される。
各%charとログ情報の対応は次のようになる。
%h %l %u %t \"%r\" %>s %b
| | | | | | |
| | | | | | `---------------------------------------.
| | | | | `---------------------------------------. \
| | | | `----------------------------. \ \
| | | `----. \ \ \
| | `------. \ \ \ \
| `--------. \ \ \ \ \
| \ \ \____________________________ \______________ \ \
211.18.217.219 - - [20/Jun/2001:19:16:15 +0900] "GET / HTTP/1.0" 200 4636
^ ^ ^ ^^ ^^ ^
^で印を付けた部分はフォーマット指定中の文字が
そのまま記録されたものである。
ここで少し注意しておきたいのは%>sだ。
このケースのように%とcharの間に
なんからの文字列が入れることができる場合がある。
この例での>が影響するのは
リクエストが内部的にリダイレクトされた場合だけで、
そのようなケースで最終的なステータスを記録する(>付きのとき)か
最初のリクエストに対するステータスを記録する(>なしのとき)か
という違いがでてくる。
このような、ログに記録する情報を調整することができる
指定をすることができるものは他にもある。
たとえば%tがそうで、
単に%tと書いた場合には先の例の通りの記録がなされるが、
%{format}tというような書き方をすると
formatの部分でstrftime(3)でゆるされている
時刻に関する記述を自由にすることができるようになる。
%{%Y-%m-%d %H:%M:%S %Z}tであれば
2001-06-22 01:52:09 JSTのように記録されるわけである。
この種の修飾子は特定のcharについて有効なものであるが、
すべての文字について有効な修飾子もある。
それはログの記録条件を指定するもので、
%の直後にステータスを記述することができる。
たとえば%200rのように書く。
この場合ならステータスが200だった時にだけ
そのリクエストの内容を記録することになる。
また%!200rのように!を付けておくと、
続くステータス以外の場合にだけ記録するということを意味する。
さらに両者のケースについて、
複数のステータスを指定することも可能である。
たとえば%200,300,301rや%!200,300,301rのように指定すると、
それぞれ「ステータスが200、300、301だった場合」および
「ステータスが200、300、301以外だった場合」にだけ情報を記録することができる。
いずれにしても条件にあてはまらない場合には
charに対応する情報のかわりに-がログに残されることになる。
その他のフォーマット引数のうち、主なものについて解説しておく。
%aアクセス元ホストのIPアドレス。
%hと違ってどのような場合もIPアドレスで記録される。
%AApacheサーバ側のIPアドレス。
%UリクエストされたURLのパス。
%qリクエスト中にquery stringがあればそれを前置された?付きで記録する。
query stringがなかった場合には空文字列が記録される。
%fリクエストに対応するファイル名。
%Tリクエストの処理にかかった時間。
%vリクエストに対応するバーチャルホスト名。
また必ず%{...}tスタイルで指定するフォーマット引数として
以下のものが挙げられる。
%{FOOBAR}e環境変数FOOBARの値。
%{Foobar}iリクエストヘッダFoobarの内容。
agentやrefererの説明の際に、
リクエストヘッダから必要な情報を取り出しているということを述べたが、
そのために用いられるのがこの記述である。
%{Foobar}oレスポンスヘッダFoobarの内容。
以上に従って設定をしてやれば 自在にログのフォーマットをカスタマイズできるだろう。 それでは続いてそうして設定したログを どこに出力するのかという点のカスタマイズについて解説する。
フォーマット以外にもカスタマイズできるとこがある。 続いてそれらについて紹介しよう。
ログの記録先の指定はCustomLogディレクティブで行う。
CustomLog access.log combined
このように書くとログはServerRootで指定されたディレクトりの下の
access.logというファイルに記録されるようになる。
またフルパスで/var/log/apache/access.logと書けば
ServerRootとは関係なくそのファイルに記録される。
いずれの場合もすでにファイルがあれば追加で、
なければ新たにファイルを作った上でそのファイルに記録される。
なお、この動作はapachectl gracefulを実行した際にも行われる。
したがって、前もってログファイルを別の名前にしておき、
その上でapachectl gracefulを実行することによって
ログファイルを切り換えることが可能である。
mv access.log access.log.0 apachectl graceful
それまでのログをaccess.log.0とすることで、
一時的にaccess.logというファイルがなくなる。
しかし、このままの状態であればログは依然として
access.log.0に書き込まれ続けている。
この時にapchectl gracefulを実行すると
それによってログファイルのリセットがかかり、
Apacheサーバは改めてログファイルをオープンし直そうとする。
このタイミングでaccess.logが新設され、
それ以降のログは新しいaccess.logに書き込まれるようになるわけである。
ところで、このような動作を自動的に行う方法も用意されている。
それはApacheサーバのソースの中のsrc/supportにある
rotatelogsというプログラムとログのパイプへの出力機能を利用したもので、
たとえば次のような設定によって可能である。
TransferLog "|/path/to/rotatelogs /var/log/apache/access.log 86400"
このようにログの出力先としてはプログラムも指定可能で、
その場合には|を置いてプログラムのコマンドラインを書いてやればよい。
この例ならログの内容がパイプを介して/path/to/rotatoelogsに
渡されるわけである。この時、/path/to/rotatoelogsは
Apacheサーバを起動したユーザの権限で実行される点に注意が必要である。
ちなみに、上の例ではログファイル/var/log/apache/access.logは
一日一回(86400秒に一回)リセットされることになる。
一ファイルについてきっかり一日分ずつログをとることができるわけだ。
ログを記録する際の条件付けとして
フォーマット引数でのステータス指定が可能であることを解説したが、
もう一つ別のレベルでの条件付けが可能となっている。
それはログを記録するタイミングで環境変数が設定されているかどうかによって
制御できるというもので、
CustomLogディレクティブの3つ目の引数で指定できる。
CustomLog gif-requests.log common env=gif-image
この例について解説すると、
gif-requests.logというファイルに
commonという名前で定義されたフォーマットに従って
環境変数gif-imageが設定されている時にだけ
ログを記録する―ということになる。
環境変数で切り変えるといわれると
あまり意味がないように思われるかもしれないが、
この機能はSetEnvIfなどとの組み合わせで使用することで
動的な条件判定を行うことができるようになる。
以下にマニュアルにあるサンプルを示す。
SetEnvIf Request_URI \.gif$ gif-image CustomLog gif-requests.log common env=gif-image CustomLog nongif-requests.log common env=!gif-image
ここでSetEnvIfディレクティブの意図は
*.gifに対してアクセスされた時にだけ
gif-imageという環境変数を設定することにある。
また続くCustomLogではgif-imageが設定されているかどうかで
ログファイルを切り換えるということをやっており、
結果的に*.gifに対するアクセスであった時にはgif-requests.logに、
そうでない時にはnongif-requests.logに
それぞれログが記録されることになる。
同様にHTMLファイル(実際には拡張子が.htmlであるページ)に対する
アクセスについてだけログに記録したければ次のようにするとよい。
SetEnvIf Request_URI \.html$ text-html CustomLog html-requests.log common env=text-html
逆にHTMLファイルに対するアクセスだけを除外したければこうだ。
SetEnvIf Request_URI \.html$ text-html CustomLog html-requests.log common env=!text-html
同様の応用だが、Referer情報を利用して
ある特定サイトにあるリンクをたどって到達したと考えられる
アクセスについてのみログに記録することも可能である。
このような目的にはSetEnvIfを次のように使うとよい。
SetEnvIf Referer www\.linux\.or\.jpa wloj_referral CustomLog wloj-referral.log common env=wloj_referral
www\.linux\.or\.jpを自サイトのホスト名(バーチャルホスト名)にすれば
自サイト内での移動をログに残さないようにすることも可能となる。
ただし、前述した通りReferer情報は万能ではないから、
その点には注意しておく必要がある。
一般にログはとっておけば良いというものでない *2。 とったものは整理しなければせっかくのログも ディスクを圧迫するだけのゴミとなってしまう。 その意味ではログをとるにあたって 「何のためにログをとるのか」ということを考えておく必要があるだろう。
おそらく目的は様々であろうが、 それに応じてログのフォーマットを調整し、 後処理をしやすくしておくと良いだろう。 一般にログ記録後の処理は適当なプログラムを作って、 それによって行うことが多い。 たとえばファイル毎のアクセス数を集計したいという程度であれば、 まず次のような形でログを取っておく。
CustomLog access.log "%t %200f"
その上で次のようなスクリプト(ここではRubyを使った)で集計することができる。
#!/usr/bin/ruby
sum = {} # 集計用のハッシュ
ARGF.each do |line| # ログファイルから一行ずつ取り出す
/^\[(.*)\] (.*)$/ =~ line # 時刻とファイル名に分ける
next if $2 == '-' # ファイル名が - なら次の行に
sum[$2] ||= 0
sum[$2] += 1
end
# アクセス数でソートして表示
sum.sort{|a, b| b[1] <=> a[1]}.each do |file, n|
printf "%3d %s\n", n, file
end
ここで記録されるログは、ステータスが200だったときにだけ
ファイル名を記録するようにしてある(%200f)。
したがって、ファイル名にあたる部分が-でない場合にだけ
カウントするようにすることで、
実際にファイルにアクセスされた回数だけを集計することができる。
また、このスクリプトでは特に使用していないが
ログファイルには時刻も記録するようにしている(%t)ため、
日毎や時間毎といったもう少し細い期間について調べることも可能であろう。
もう一つ簡単なスクリプトを書いてみよう。
これはコマンドライン引数で指定したページについての記録だけを
ログファイルの中から抽出し、それについての集計をするものである。
先程のスクリプトでは独自のログフォーマットを期待する仕様としたが、
このスクリプトでは一般に作われているうちのcombined形式を
対象とすることにした。
#!/usr/bin/ruby
target = ARGV.shift
pattern = /^(\S+) (\S+) (\S+) \[([^\[\]]*)\] "([^"\s]+)\s+(#{Regexp.quote(target)}[^"\s]*)(?:\s+[^"]*)?" (\d+) (\d+|-) "([^"]*)" "([^"]*)"$/
access = 0
query = {}
referer = {}
ARGF.each do |line|
m = pattern.match(line)
if m
remote, user, auth, timestr,
method, path, status, size, ref, agent = m[1..10]
qstr = path.split(/\?/, 2)[1]
if qstr
qstr.gsub!(/%([\da-f]{2})/i){$1.hex.chr}
query[qstr] ||= 0
query[qstr] += 1
end
if ref != '-'
base, qstr = ref.split(/\?/, 2)
if qstr
qstr.gsub!(/%([\da-f]{2})/i){$1.hex.chr}
else
qstr = ''
end
referer[[base, qstr]] ||= 0
referer[[base, qstr]] += 1
end
access += 1
end
end
printf("target: %s\ntotal %dhits\n", target, access)
if query.size > 0
printf("\ncnt: query string\n")
query.sort{|a, b| b[1]<=>a[1]}[0, 10].each do |qstr, n|
printf("%3d: %s\n", n, qstr)
end
end
if referer.size > 0
printf("\ncnt: referer & query string\n")
referer.sort{|a, b| b[1]<=>a[1]}[0, 10].each do |x, n|
printf("%3d: %s %s\n", n, x[0], x[1])
end
end
実行するとこんな感じになる。
$ cat access.log.0 access.log | ruby logana.rb /ruby/ target: /ruby/ total 158hits cnt: referer & query string 31: http://arika.org/ 16: http://arika.org/ruby/ 9: http://www.ruby-lang.org/~rubikitch/computer/rnet.html 5: http://www.webring.ne.jp/cgi-bin/webring ring=ruby;list;page=1 4: http://www.webring.ne.jp/cgi-bin/webring ring=ruby;list 3: http://arika.org/index.html.ja 2: http://www.notwork.org/ipr/2nd/ …
ここではホスト名については特に扱っていないが、
外部からのアクセスを分析するにあたって
アクセス元のホスト名を知っておきたいということがよくある。
しかし、ログを取る際にいちいちIPアドレスからホスト名への変更をかけるのは
非常に効率が悪いため、多くの場合、HostNameLookups offとして
IPアドレスのまま記録するのが常である。
いきおい、ホスト名を求めるのはログ解析プログラムの役割りとなるわけだが、
ホスト名を一つずつ解決していくというのでは実用上問題になるだろう。
というのも、IPアドレスからホスト名を求めるという処理は
比較的時間のかかる類種のものだからである。
そうかといってホスト名を求める処理を複数並列で動かせばいいかというと、
そうすることでたしかに速度的には優利になるものの、
必要な処理をプログラムにするのがなかなかやっかいだったりする。
このような時に役立ってくれるのが jdresolveなどのIPアドレス→ホスト名変換ツールだ。 jdresolve <URL:http://www.jdrowell.com/Linux/Projects/jdresolve> は Perlで書かれたスクリプトで、 Net::DNSを使ってDNS検索の並列化をはかるなどの特長がある。 使い方としては次の通りである。
$ jdresolve access.log
IPアドレス→ホスト名変換を行った後のログは 標準出力に出力されるので、適当にリダイレクトするとよい。 また、次のようにIPアドレス-ホスト名の対応をキャッシュするための データベースファイルを指定して実行すると、 より処理効率を高めることが可能である。
$ jdresolve --database=cache.db access.log
ログファイル名のかわりに-を指定すると
標準入力からログを読み込もうとするようになる。
必要に応じてこのようなツールを解析プログラムと併用するとよいだろう。
以上のようにログの解析は目的に応じたフォーマットのログと それを読み取るプログラムを作成することで十分に可能である。 しかし、ごく一般的に行われる日毎、月毎、曜日毎でのアクセス数の集計や それらのグラフ化といった目的についていえば、 すでに広く使われている汎用のログ解析ツールが存在している。 そうしたツールのうちフリーソフトウェアであり、 かつ比較的有名なものとして以下の2つが挙げられるだろう。
Analogについては次の章で詳しく解説することになっているのでおくとして、 ここではその他のツールについて簡単に紹介しておこう。
Webalizerはアクセスログを解析・集計して HTML形式のレポートを作成してくれるツールの一つである。 このツールは<URL:http://www.linux.or.jp/>などでも使われていて <URL:http://www.linux.or.jp/webalizer/>で参照することができる。 Webalizerの特長の一つはCommon Log Formatに対応している他、 Proxyサーバとして有名なSquidのログや wu-ftpdのログにも対応している点であり、 3種のログについて解析が可能である。
出力を実際に見てもらえばわかるが、 集計結果は表とグラフで表現され、 年、月、日、時刻といった単位での集計がなされている。 それぞれ単純なアクセス数の集計だけではなく、 ファイル毎の集計やステータス毎、 あるいは転送データ量などいくつかの観点での集計が行われる。
基本的な使用方法は簡単で、ログファイルのパスを指定して
webalizerというコマンドを実行するだけである。
$ webalizer -o /path/to/output/ access.log
ModloganというのはModular Log Analyzerの略である。
このツールの特長は、集計・出力にあたって ログの解析部分と結果の出力部分をモジュール化することによって、 様々な形式のログに対応し、 しかも様々な形式で出力できるようにしていることである。
対応しているログフォーマットとしては Common Log Format、MS IIS 5.0、wu-ftpd、ProFTPd、Squidなどで、 めずらしいところではRealServerなどにも対応しているようである。 出力についてはHTML形式のオリジナルとテキストによよる出力、 それから先のWebalizer風の出力を標準でサポートしている。 オリジナルの出力形式では各種の集計毎にページをわけであり、 なかなか見易いよう工夫されている。
使い方は設定ファイルにログファイルのファイル名や
出力先ディレクトリを記述し、必要ならその他の設定をしてから
modloganコマンドを実行するだけである。
$ modlogan
設定ファイルの[global]セクションに
enable_resolver=1と書いておくと
IPアドレスをホスト名に変換しつつ集計を行ってくれるのだが、
その際、DNSへの問い合わせにadnsという
並列問い合わせをサポートした
resolverライブラリを使っている点でもおもしろいツールである。
利用例は <URL:http://www.kneschke.de/projekte/modlogan/sample/modlogan/default/> で見ることができる。
http-analyzeは対応しているログフォーマットが
やや少なくCommon Log Formatとcombined形式だけになる。
ただし出力についてはVRMLを使うなど独特である。
また、細い点だが、ステータスコードの割り合いを
グラフ化しているところなどもおもしろい。
利用例は <URL:http://www.netstore.de/stats/> で見ることができる。
ただしこのツールは個人利用か非商用での利用以外は ライセンス料が必要となるので注意しなくてはならない。
以上、簡単に紹介してきたツールはいずれも バイナリパッケージが用意されていることも多いので、 それらを利用してまずは試してみるのがよいだろう。
もちろんここに名前を挙けたツール以外にも いくつかの解析ツールが開発されている。 中にはユーザのアクセスの仕方を詳しく解析することで、 どのページがどのようなタイミングで参照されるかといったことまで 解析するものもあるようである。
これをもってログ解析ツールを自作することを止めるものではないが、 実際になにかを作りはじめる前に自分の用途にあったツールがすでにないかどうか 調べてみることをおすすめする。
続いてエラーログについて解説しよう。 エラーログはApacheサーバ運用中に起ったエラーについての情報や 起動時あるいは停止時の情報が記録されるものである。 アクセスログとは違って、エラーログを記録する機能は Apacheサーバに組み込まれる形になっているため、 エラーログを取るためにモジュールをロードするなどする必要はない。 またフォーマットについてのカスタマイズもできない。
エラーログについて設定できるのは次の二つである。
エラーログを記録するファイル名
例: ErrorLog error.log
エラーログに記録する重要度
例: LogLevel notice
ErrorLogに指定できるのはServerRootからの相対パス、
絶対パス、syslogのいずれかである。
二種類のパスについては説明は特に必要ないと思うが、
いずれのケースでも指定したファイルにログが書き出されるようになる。
一方syslogを指定した場合だが、
このケースではsyslogd(8)によってログを記録する形になる。
この際、単にsyslogと設定するのではなく
syslog:facilityのようにしてファシリティを
指定することもできる。ファシリティを指定しなかった場合には
local7がデフォルトのファシリティとなる。
出力されるログ情報にはそれぞれ重要度が設定されている。 重要度が高いほど致命的なエラー状態を示すものであり、 重要度の高いものから順に以下のようなものがある。
緊急事態、動作不能。
即座に対処する必要あり。
危機的な状態。
エラー状態。
警告。
動作はしているものの一部に問題あり。
情報。
デバッグ情報。
これらのうち、エラーログに記録するレベルを設定するのが
LogLevelディレクティブである。
例のようにnoticeを指定すると
notice〜emergだけが記録され
infoおよびdebugの各情報は記録されなくなる。
エラーログについての設定は以上の通りだが、 実際にどのようなログ情報が記録されるのかを見てみよう。 以下はエラーログで比較的よく見られるメッセージを 簡単に分類してみたものである。
[error] [client 127.0.0.1] File does not exist: /home/akira/public_html/not_exist
外部からリクエストされた情報に対応するファイルが 存在しなかったことを意味している。 そもそも間違ったアクセスであった場合もあるだろうし、 サーバ上のページ間のリンクが間違っていることもある。 後者のケースではコンテンツが正常に閲覧できないことにつながる。
[error] [client 127.0.0.1] no acceptable variant: /var/www/arika.org/index
これはMultiViewsを活用しているケースで起り得る状態である。
コンテントネゴシエーションの結果、送り返すべき
適当な情報が見つからなかったことを意味している。
[crit] [client 127.0.0.1] (13)Permission denied: /home/akira/public_html/.htaccess pcfg_openfile: unable to check htaccess file, ensure it is readable
.htaccessファイルがあるが
そのファイルを読めなかった場合にこのようなログが残される。
.htaccessファイルのオーナーおよびモードを確認する必要がある。[alert] [client 127.0.0.1] /home/akira/public_html/.htaccess: Multiple <Files> arguments not (yet) supported.
こちらは.htaccessの内容に間違いがあった場合のログ。
先の場合もそうだが、.htaccess自体に間違いがあると
それ以下のコンテンツにはアクセスできなくなってしまうため
注意が必要である。
[alert] [client 127.0.0.1] /home/akira/public_html/.htaccess: Invalid command 'hogehoge', perhaps mis-spelled or defined by a module not included in the server configuration
これは.htaccessの記述が根本的に間違っている場合である。
ここではhogehogeなる存在しない設定をしようとしたことによる。
[error] [client 127.0.0.1] client denied by server configuration: /home/akira/public_html/index.html
ホスト名やIPアドレスなどによるアクセス制限に反するアクセスがあった。 単なるアクセスミスのこともあるが、 同じホストから何度もくり返されるような場合には注意が必要である。 故意に不正アクセスを試みようとしているようなケースばかりではなく、 アクセス制限がかかっているエリアへの(不用意な)リンクがある可能性もある。
index.htmlのようなファイルがないディレクトリに
アクセスするとこのようなログが記録される。[error] [client 127.0.0.1] Symbolic link not allowed: /home/ftp/pub/ruby
FollowSymlinksやSymlinksIfOwnerMatchを
指定していない状態でシンボリックリンクを使っていると
このようなエラーとなる。
[error] [client 127.0.0.1] Premature end of script headers: /var/www/cgi-bin/result.cgi
CGIスクリプトに間違いがあり、正しく実行できなかった。 このメッセージの意味は、本来CGIスクリプトが出力すべきである ヘッダ部分が壊れているため、 リクエストされた情報をブラウザに送り返すことが できなかったということである。 ありがちなのは、次のようなことが原因となって 出力の途中でスクリプトが異常終了してしまうケースである。
#!行が間違っている以下の例はこわれたクライアントからの アクセスなどによるものであると考えられる。
ログの重要度や類種、頻度および状況などに応じて対処する必要がある。
以上、Apacheサーバにおけるログについて解説してきた。 システムを正常な運用*3を維持する上で ログは最も基本的な情報源の一つである。 そうしたログを正しく読むことが重要であるというのは言うまでもないが、 高率的に読めるようにすることもまた重要であると言えるだろう。 というのは、一般にログファイルには かなりたくさんの情報がつめこまれるものとなるからだ。 そうした中から視認によって本当に重要な情報を探し出そうというの およそ無望な試みであり、したがって、ログを解析するための なんらかの手段を持つことが非常に重要な作業の一つとなってくるだろう。
これまでの解説がその一助となればさいわいである。
*1対応するログ情報がない場合には
一般に-が入る。
*2なんのも考えなしにまったくログは取らないというのよりはマシだろうが。
*3もちろんそのシステムにとって
何が「正常」であるかを定義することが必要である。