はじめに

Webサーバを運営する上で、実際のアクセスがどれくらいあるのか、アクセスにどのような傾向があるのかを調べることは非常に重要です。
しかしながら、apacheのログはアクセスのあったURLや時間などの情報を都度保存するだけなので(まあそれがログなんですけど)、それだけではアクセスの傾向や分布等が把握しづらかったりします。

このページでは、perlスクリプトを利用してapacheのログを解析する方法について説明します。

combined形式のフォーマット

apacheをデフォルトの状態で導入した際のログ出力(CustomLog)は、combined形式になっています。

combined形式のフォーマット記述(httpd.confより抜粋)
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

このcombined形式のログを各要素ごとに分解するには、下記のような正規表現を利用します。

combined形式ログの正規表現
/^(.*) (.*) (.*) \[(.*)\] "(.*)" (.*) (.*) "(.*)" "(.*)"$/;

perlスクリプトによる各属性の読み込み

上記正規表現を利用し、ログファイルから各属性を読み込みます。
ログファイルのサイズにもよりますが、処理にはかなりの負荷がかかりますのでご注意ください。

ログ解析用perlスクリプト
open(LOG,"</var/log/httpd-access.log");

my(@REMOTE_HOST)=();
my(@FI)=();
my(@REMOTE_USER)=();
my(@DATE_TIME)=();
my(@METHOD)=();
my(@REQUEST_URL)=();
my(@HTTP_VERSION)=();
my(@HTTP_RESPONSE)=();
my(@DATA_BYTES)=();
my(@REFERER)=();
my(@USER_AGENT)=();

my($tLine)=0;

while(<LOG>)
{
    /^(.*) (.*) (.*) \[(.*)\] "(.*)" (.*) (.*) "(.*)" "(.*)"$/;

    $REMOTE_HOST[$tLine]=$1;
    $FI[$tLine]=$2;
    $REMOTE_USER[$tLine]=$3;
    $DATE_TIME[$tLine]=$4;
    ($METHOD[$tLine],$REQUEST_URL[$tLine],$HTTP_VERSION[$tLine])=split(/ /,$5);
    $HTTP_RESPONSE[$tLine]=$6;
    $DATA_BYTES[$tLine]=$7;
    $REFERER[$tLine]=$8;
    $USER_AGENT[$tLine]=$9;

    $tLine++;
}

apache上でVirtualHost(SSLを運用している場合も含む)を動かしている場合、それぞれのVirtualHostでCustomLogを正しく設定していないと、ログファイルにcommon等のcombinecd以外の形式でログが出力されることがあります。
この場合、上記のperlスクリプトでは各属性が正しく格納されない場合がありますので、ログにはcombined以外の形式のログが出力されないよう設定してください。

common形式のフォーマット記述(httpd.confより抜粋)
LogFormat "%h %l %u %t \"%r\" %>s %b" common

日ごとのアクセス数を集計する

上記の方法で格納した配列を利用して日ごとのアクセス数を集計するには、下記のような集計関数を利用します。

集計用サブルーチン(要素文字列の昇順でソート)
sub fold1
{
	my($aArray)=@_;

	my(%tSummery)=();

	for(my($tCount1)=0;$tCount1<=($#$aArray);$tCount1++)
	{
		$tSummery{$$aArray[$tCount1]}++;
	}

	foreach(sort keys %tSummery)
	{
		my($tKey)=$_;
		my($tValue)=$tSummery{$tKey};

		$tValue+=0;

		print $tKey.' : '.$tValue."\n";
	}
}

この関数は、参照で渡された配列のうち、重複する要素を畳み込んでカウントし、要素文字列の昇順で表示します。
以下のように呼び出して利用します。

&fold1(\@DATE_TIME);

この実行結果は下記の例のようになります。

01/Jan/2007 : 749
02/Jan/2007 : 772
03/Jan/2007 : 624
04/Jan/2007 : 870
05/Jan/2007 : 846
06/Jan/2007 : 744

URLやUser-Agent等の値を畳み込んで表示する

URLやUser-Agent等を集計する際には、下記のような集計関数を利用します。

集計用サブルーチン(カウント数の降順でソート)
sub fold2
{
	my($aArray)=@_;

	my(%tSummery)=();

	for(my($tCount1)=0;$tCount1<=($#$aArray);$tCount1++)
	{
		$tSummery{$$aArray[$tCount1]}++;
	}

	foreach(sort {$tSummery{$b}<=>$tSummery{$a}} keys %tSummery)
	{
		my($tKey)=$_;
		my($tValue)=$tSummery{$tKey};

		$tValue+=0;

		print $tValue.' : '.$tKey."\n";
	}
}

この関数は、参照で渡された配列のうち、重複する要素を畳み込んでカウントし、カウント数の降順で表示します。(先ほどの「fold1」と違うのは、foreach文のソート条件と表示の個所のみです)
以下のように呼び出して利用します。

&fold2(\@USER_AGENT);

この実行結果は下記の例のようになります。

4422 : Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; SV1; .NET CLR 1.1.4322)
3993 : Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705)
1812 : Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
1413 : Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)
1135 : msnbot/1.0 (+http://search.msn.com/msnbot.htm)

集計結果をHTMLに吐き出す際の注意

クライアントから送信されるUser-Agentには、HTMLのタグが埋め込まれている可能性があります。
このため、集計結果をHTMLに吐き出す際には、XSS脆弱性を衝かれないよう、各文字列をサニタイズするようにしてください。

変更履歴

2010/04/05

・perlのサンプルに存在していた、不正にエントリ数を間引いて表示してしまう不具合を修正。(連想配列を使って書き直しました、しらけんさんご指摘ありがとうございました!)

2009/07/24

・perlのサンプルを「use strict」でも動作するよう修正。


あなたの探し物は見つかりましたか?
まさにこれだ
参考になった
ちょっと違う
これじゃない

何かメッセージがあればお願いします

このメッセージを非公開にする

ご注意

・頂いたメッセージは管理者のチェックの後、公開されます。
・メッセージの公開を希望されない場合には、「このメッセージを非公開にする」にチェックを入れてください。
・管理者が不適切と判断したメッセージは公開しませんので、予めご了承ください。


まさにこれだ
1 (33%)
参考になった
2 (67%)

表示できるメッセージはありません。