Apacheログ概論

Apacheサーバにおけるログの取り方と読み方について解説しよう。

ただ「ログ」と言っても、 Apacheサーバでは標準でいくつかの目的のためのログを 取ることができるようになっている。 そこで、まずはそれらを分類するところから始めることにする。

アクセスログ

そのApacheサーバによって提供される情報に対する アクセスについての記録をとるためのログ。 通常、外部からのアクセスがある度に記録が残される。

関係するディレクティブ: LogFormat, CustomLog, TransferLog

エラーログ

設定上のエラーやアクセスされた情報に対応するファイルがないなど、 エラーが起った場合にそのエラーの内容を記録するためのログ。

関係するディレクティブ: ErrorLog

CGIスクリプトのエラーログ

CGIスクリプトが実行され、 それがエラーになった場合にその状況を記録するためのログ。 これはデバッグのためのもので、 通常運用時にはこのログは使用しない。

関係するディレクティブ: ScriptLog

mod_usertrackによるクッキーのためのログ

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によって取ることが 推奨されるようになっているというのがある。 実際、次の二つの記述はまったく同じ意味を持つが、 今後は後者のやり方にしておく方が良いだろう。

これらの記述の詳細な意味は後述することにして、 まずは一般的なアクセスログの読み方から解説することにしよう。

一般的なアクセスログの読み方

ここでは「一般的なアクセスログ」の形式として Common Log Formatというフォーマットを取り上げる。 Common Log FormatというのはTransferLogディレクティブで ログを取るようにした時のデフォルトの形式である。 httpd.confで次のような記述をした場合にaccess.logに記録される形式がそれだ。

TransferLog access.log

このTransferLogと後で出てくるCustomLogという2つのディレクティブは ログの記録先やログのフォーマットを指定するためのものである。 両ディレクティブに深く関ってくるのディレクティブに LogFormatというものがあって、 こちらはログのフォーマットを具体的に定義するために用いられる。

さて、これら3つのディレクティブの関係だが、次のようなものとなる。

LogFormat

LogFormatには二つの書式がある。 一つはログフォーマットを名前を付けて定義するためのもので 次のような書き方をする。

LogFormat "ログのフォーマットの指定" <名前>

もう一つの書式は上の名前の部分を省略したもので、 このようになる。

LogFormat "ログのフォーマットの指定"

こちらはTransferLogによって記録されるログのフォーマットを 決定するためのものである。

TransferLog

TransferLogによって記録されるログは 直前の名前なしのLogFormatで指定されたフォーマットに従う。 特にLogFormatの指定がない場合には Common Log Formatとよばれるフォーマットで記録される。

CustomLog

TransferLogとの違いはログフォーマットの指定の仕方にある。 TransferLogでは直前のLogFormatによる指定だったが、 CustomLogではログファイルの名前とともに フォーマットを明示的に指定することができる。 その際、LogFormatで定義した名前によって指定できる他、 LogFormatで行うような形でフォーマットを直接指定することもできる。

CustomLog access.log <名前>

CustomLog access.log "ログのフォーマットの指定"

同じフォーマットを使いまわす場合には フォーマットに対して名前を付けておいた方が便利である。

TransferLog、CustomLogともにログの対象は 当該サイトにおいて提供されている情報に対するアクセスである。 したがって指定されたログファイルには、基本的には 一回のアクセスに対応して一つのエントリが書き出されることになる。

Common Log Format

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になっている時のみホスト名になる。 逆に言えばHostNameLookupsonでないかぎり、 ログの中にアクセス元ホストのホスト名が現れることはない。

-

この部分には本来はアクセス元のユーザ名(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

これはCommon Log Formatそのものである。 したがって前述したように何もしないでTransferLogで設定するのと CustomLogcommonを指定するのとは同じ結果になる。

referer

<一つ前にアクセスしていたページ> -> <今回アクセスしたページ> というフォーマットでログが記録される。 意味としてはどのページからのリンクをたどって そのページに到達したかを記録したものと考えることができる。 ただし、これらはブラウザなどからのリクエストと一緒に送られてくる Refererヘッダの内容を元にしているものなので、 必ずしも正確なものではないし、そうした情報そのものがない場合もある。

- -> /
http://arika.org/ -> /pbpenguin.png
http://arika.org/ -> /pbdebian.png
http://arika.org/ -> /ruby/
http://arika.org/ruby/ -> /ruby/rice.html

以上は前述したアクセスログが記述された際に いっしょに記録したものである。 なお、一行目が-になっているのはRefererヘッダが 与えられなかったことを意味している。

agent

アクセスしてきたブラウザが名乗った クライアント名などに関する情報が記録される。 先の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というブラウザを使っていると考えることができる。

combined

Common Log Formatにさらにrefereragentの内容を 付け加えた形のログを記録することができる。 これは例を見てもらえばどのようなものかわかるだろう。

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アドレスで記録される。

%A

Apacheサーバ側のIPアドレス。

%U

リクエストされたURLのパス。

%q

リクエスト中にquery stringがあればそれを前置された?付きで記録する。 query stringがなかった場合には空文字列が記録される。

%f

リクエストに対応するファイル名。

%T

リクエストの処理にかかった時間。

%v

リクエストに対応するバーチャルホスト名。

また必ず%{...}tスタイルで指定するフォーマット引数として 以下のものが挙げられる。

%{FOOBAR}e

環境変数FOOBARの値。

%{Foobar}i

リクエストヘッダFoobarの内容。 agentrefererの説明の際に、 リクエストヘッダから必要な情報を取り出しているということを述べたが、 そのために用いられるのがこの記述である。

%{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が設定されている時にだけ ログを記録する―ということになる。

gifイメージについてだけのログをとる

環境変数で切り変えるといわれると あまり意味がないように思われるかもしれないが、 この機能は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 <URL:http://www.mrunix.net/webalizer/>

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 <URL:http://www.kneschke.de/projekte/modlogan/>

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 <URL:http://www.netstore.de/Supply/http-analyze/>

http-analyzeは対応しているログフォーマットが やや少なくCommon Log Formatとcombined形式だけになる。 ただし出力についてはVRMLを使うなど独特である。 また、細い点だが、ステータスコードの割り合いを グラフ化しているところなどもおもしろい。

利用例は <URL:http://www.netstore.de/stats/> で見ることができる。

ただしこのツールは個人利用か非商用での利用以外は ライセンス料が必要となるので注意しなくてはならない。

以上、簡単に紹介してきたツールはいずれも バイナリパッケージが用意されていることも多いので、 それらを利用してまずは試してみるのがよいだろう。

もちろんここに名前を挙けたツール以外にも いくつかの解析ツールが開発されている。 中にはユーザのアクセスの仕方を詳しく解析することで、 どのページがどのようなタイミングで参照されるかといったことまで 解析するものもあるようである。

これをもってログ解析ツールを自作することを止めるものではないが、 実際になにかを作りはじめる前に自分の用途にあったツールがすでにないかどうか 調べてみることをおすすめする。

エラーログ

続いてエラーログについて解説しよう。 エラーログはApacheサーバ運用中に起ったエラーについての情報や 起動時あるいは停止時の情報が記録されるものである。 アクセスログとは違って、エラーログを記録する機能は Apacheサーバに組み込まれる形になっているため、 エラーログを取るためにモジュールをロードするなどする必要はない。 またフォーマットについてのカスタマイズもできない。

エラーログについて設定できるのは次の二つである。

ErrorLogに指定できるのはServerRootからの相対パス、 絶対パス、syslogのいずれかである。 二種類のパスについては説明は特に必要ないと思うが、 いずれのケースでも指定したファイルにログが書き出されるようになる。 一方syslogを指定した場合だが、 このケースではsyslogd(8)によってログを記録する形になる。 この際、単にsyslogと設定するのではなく syslog:facilityのようにしてファシリティを 指定することもできる。ファシリティを指定しなかった場合には local7がデフォルトのファシリティとなる。

出力されるログ情報にはそれぞれ重要度が設定されている。 重要度が高いほど致命的なエラー状態を示すものであり、 重要度の高いものから順に以下のようなものがある。

emerg

緊急事態、動作不能。

alert

即座に対処する必要あり。

crit

危機的な状態。

error

エラー状態。

warn

警告。

notice

動作はしているものの一部に問題あり。

info

情報。

debug

デバッグ情報。

これらのうち、エラーログに記録するレベルを設定するのが LogLevelディレクティブである。 例のようにnoticeを指定すると noticeemergだけが記録され infoおよびdebugの各情報は記録されなくなる。

エラーログの読み方

エラーログについての設定は以上の通りだが、 実際にどのようなログ情報が記録されるのかを見てみよう。 以下はエラーログで比較的よく見られるメッセージを 簡単に分類してみたものである。

設定・運営上のミス
アクセス制限など
CGI関係
異常なリクエスト

以下の例はこわれたクライアントからの アクセスなどによるものであると考えられる。

ログの重要度や類種、頻度および状況などに応じて対処する必要がある。

以上、Apacheサーバにおけるログについて解説してきた。 システムを正常な運用*3を維持する上で ログは最も基本的な情報源の一つである。 そうしたログを正しく読むことが重要であるというのは言うまでもないが、 高率的に読めるようにすることもまた重要であると言えるだろう。 というのは、一般にログファイルには かなりたくさんの情報がつめこまれるものとなるからだ。 そうした中から視認によって本当に重要な情報を探し出そうというの およそ無望な試みであり、したがって、ログを解析するための なんらかの手段を持つことが非常に重要な作業の一つとなってくるだろう。

これまでの解説がその一助となればさいわいである。


*1対応するログ情報がない場合には 一般に-が入る。
*2なんのも考えなしにまったくログは取らないというのよりはマシだろうが。
*3もちろんそのシステムにとって 何が「正常」であるかを定義することが必要である。