アクセスログの記録
 
Webサーバでは通常、アクセスログ(どこから、どのファイルをアクセスしたかなど)を記録しています。しかし、NSP(ネットワーク サービス プロバイダ)のWebサーバを個人で利用している場合は、アクセスログを見ることはできません。そこで、あるWebページ(たいていはあなたのホームページだと思いますが)の簡易アクセスログを自分で記録してみます。これはアクセスカウンタがそのWebページを見てくれた人への情報提供するのに対し、Webページ提供者が情報を得ることです。その意味で処理の流れはアクセスカウンタのプログラムに似ています。ファイルにアクセスカウントを記録する代わりに、クライアントの情報を記録するわけです。クライアントの情報は環境変数により簡単に得られます。
SSIを利用したり、いくつか方法は考えられますが、ここではCGIを直接起動してCGIパラメタで接続先のURLを指定できるようにします。例えば
 
 
http://your_server/cgi-bin/log.cgi?URL=/path/some.html

のような形式です。URLパラメタの指定をせずに
 
http://your_server/cgi-bin/log.cgi

とすると http://your_server/ に接続するようにします。
もし、あなたのホームページを表示したときにログを採りたい場合は、ホームページに次のようなものを使うこともできます(JavaScript の onLoad を使っています)。この例では、log.cgiを起動したあとにホームページ(/)に移動します。
 
 
log.html
<HTML>
<HEAD>
<TITLE>アクセスログの採取サンプル</TITLE>
<SCRIPT LANGUAGE="JavaScript">
<!--
function goURL(path)
{
        url = "/cgi-bin/log.cgi?URL=" + path;
        document.location.href = url;
}
//-->
</SCRIPT>
</HEAD>
<BODY onLoad=goURL('/')>
</BODY>
</HTML>

[サンプルの実行]
 

アクセスログは次のようなフォーマットでファイルに記録します。

the.remote.host 123.456.789 [Wed Feb 10 16:15:31 1999] "Mozilla/4.5 [ja] (Win95; I)" /index.html

クッキーを使い、ある一定時間内のアクセスは記録しないことにします。

さらに、集計表示をしてみます。

[サンプルプログラムの実行]
 
 
log.cgi
#!/usr/bin/perl
#
# log.cgi
#
# (C)1999 Kaoru Fujita
#
use lib './lib';
require 'util.pl';


#
# 定数
#
$Title = 'アクセスログ サンプル プログラム';
# CGI の仮想パス
$CGIPath = '/cgi-bin';
# プログラム名
use File::Basename;
$Program = basename($0);
# 漢字コード
$CharSet = 'Shift_JIS';
# ログファイル名
$File = './tmp/access.log';
# 古いログファイル名
$OldFile = './tmp/OLDaccess.log';
# フォーマット
#REMOTE_HOST REMOTE_ADDR [Date] HTTP_USER_AGENT
# 棒グラフの色
$BarColor = "#0000F0";
$MaxNumColor = "#F00000";
# ログの行数の最大値  1000行で20Kbytesくらい
$MaxLine = 1000;
#
$Expiration = '1MINUTES';
#
@Field = qw(リモートホスト リモートIPアドレス
                時間帯 ブラウザコード名 ブラウザバージョン Webページ);


#
# メインプログラム
#
(*data) = parseInput();

# テスト
#$data{'Action'} = 'Stats';
#$data{'Target'} = 2;
$MaxLine = 300;

$act = $data{'Action'};

if ($act eq 'Stats') {
        viewStats($data{'Target'});
}
else {
        logLog();
}

exit(0);


#
# サブルーチン
#

sub logLog
{
        $date = localtime();

        if ($data{'URL'}) {
                $url = $data{'URL'};
        }
        else {
                #
                # PWS で http://the.web.server/cgi-bin/log.cgi
                # のように呼び出されると PATH_INFO には /cgi-bin/log.cgi
                # が設定されてしまい、プログラムがループしてしまう
                # のでそれを回避する。
                if ($ENV{'PATH_INFO'} =~ /$CGIPath\/$Program/) {
                        $url = '/';
                }
                else {
                        ($url) = ($ENV{'PATH_INFO'} =~ (/^(\/.+)/));
                }
        }

        local(%cookies);
        if ($ENV{'HTTP_COOKIE'}) {
                (*cookies) = &getCookie();
                $cookies{'count'}++;
                $cookie = &setCookie(*cookies);
                openURL($url, $cookie);
                return;
        }
        else {
                # 初回のアクセスでは 0 を設定する。
                $cookies{'count'} = 0;
                $cookies{'expires'} = &getDate.qq( \+ $Expiration);
                $cookie = setCookie(*cookies);
        }

        if (-e $File) {
                openLock(LOG, "+>>$File")
                        or exitError("ファイル $File がオープンできません。");
        }
        else {
                openLock(LOG, ">$File")
                        or exitError("ファイル $File が作成できません。");
        }

        my(@lines) = <LOG>;
        if (@lines > $MaxLine) {
                if (! openLock(OLDLOG, ">$OldFile")) {
                        closeUnlock(LOG, $File);
                        exitError("古いログファイル $OldFile がオープンできません。");
                }
                print OLDLOG join(/\n/, @lines);
                closeUnlock(OLDLOG, $OldFile);
                truncate(LOG, 0);               # ファイルサイズを0に
        }
        seek(LOG, 0, 2);                # ファイルの最後に移動


        print LOG $ENV{'REMOTE_HOST'}, ' ', $ENV{'REMOTE_ADDR'}, ' [',
                $date, '] "', $ENV{'HTTP_USER_AGENT'}, '" ', $url, "\n";

        closeUnlock(LOG, $File);

        openURL($url, $cookie);
}

sub openURL
{
        my($url, $cookie) = @_;

        print <<END_OF_HTML;
Content-type: text/html
$cookie

<HTML>
<HEAD>
<TITLE>$Title</TITLE>
<SCRIPT language="JavaScript">
<!--
function viewURL(url)
{
        document.location.href = url;
}
//-->
</SCRIPT>
</HEAD>
<!--BODY onLoad="JavaScript:document.location.href='$url'"-->
<BODY onLoad="viewURL('$url')">
</BODY>
</HTML>
END_OF_HTML
}

sub viewStats
{
        my($key) = @_;

        openLock(LOG, "<$File")
                or exitError("ファイル $File がオープンできません。");

        while (<LOG>) {
# localhost 127.0.0.1 [Thu Feb 11 02:04:57 1999] "Mozilla/4.5 [ja] (Win95; I)" /
# 127.0.0.1 127.0.0.1 [Thu Feb 11 06:53:10 1999] "Mozilla/2.0 (compatible; MSIE 3.01; Windows 95)" /
                @line = (/^(.+?)\s+(.+?)\s+\[\w+\s\w+\s\d+\s(\d\d):\d\d:\d\d\s\d+]\s+"(.+?)\s*\((.+)\)"+(.+)/);
                $target = @line[$key];
                $log{$target}++;
        }

        closeUnlock(LOG, $File);

        print qq(Content-type: text/html\n\n);
        print qq(<HTML>\n);
        print qq(<HEAD>\n);
        print qq(<META HTTP-EQUIV="Content-Type" CONTENT="text/html\; charset=$CharSet">);
        print qq(<TITLE>$Title</TITLE>\n);
        print qq(<BODY>\n);
        print qq(<TABLE border="0" cellspacing="0">\n);
        print qq(<TR><TD>$Field[$key]</TD><TD>回数</TD><TD>(割合 %)</TD>);

        my($max, $maxkey, $increase, $amount);
        foreach (keys %log) {
                $max = $log{$_}, $maxkey = $_ if ($max < $log{$_});
                $amount += $log{$_};
        }
        $increase = $max / 50;

        foreach (sort keys %log) {
                my($val) = $log{$_};
                my($rate) = sprintf("%.3f", ($val/$amount)*100);
                print qq(<TR>\n);
                print qq(<TD>$_</TD>\n);
                if ($_ eq $maxkey) {
                        print qq(<TD align="right" bgcolor="$MaxNumColor">$val</TD>\n);
                        print qq(<TD align="right" bgcolor="$MaxNumColor">($rate)</TD>\n);
                }
                else {
                        print qq(<TD align="right">$val</TD>\n);
                        print qq(<TD align="right">($rate)</TD>\n);
                }
                for ($i=0; $i < $val/$increase; $i++) {
                        print qq(<TD BGCOLOR="$BarColor">&nbsp;</TD>);
                }
                print qq(\n</TR>\n);
        }
        print qq(<TR><TD align="right">合計</TD><TD align="right">$amount</TD><TD></TD>);

        print "</TABLE>\n";
        print "</BODY>\n";
        print "</HTML>\n";
}

#
# <IN>  なし
# <OUT> 日時(String)
#
sub getDate
{
@Mon = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
@Wday = qw(Sun Mon Tue Wed Thu Fri Sat);
        my($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime(time);
        $wday = $Wday[$wday];
        $mon = $Mon[$mon];
        return qq($wday, $mday-$mon-$year $hour:$min:$sec GMT);
}

#--End of log.cgi

 
accesslog.html
<HTML>
<HEAD>
<TITLE>アクセスログ</TITLE>
</HEAD>
<BODY>

<B>ログの採取</B><BR>
<A HREF="/cgi-bin/log.cgi">http://localhost/ でログを採ります。</A>
<P>
<HR noshade>
<P>
<B>ログの集計表示</B><BR>
集計条件を選んでください。
<FORM action="/cgi-bin/log.cgi">
<INPUT type="hidden" name="Action" value="Stats">
<SELECT name="Target">
<OPTION value="0" selected>リモートホスト
<OPTION value="1" selected>リモートIPアドレス
<OPTION value="2">時間帯
<OPTION value="3">ブラウザコード名
<OPTION value="4">ブラウザバージョン
<OPTION value="5">Webページ
</SELECT>
<INPUT type="submit" value="集計">
</FORM>

</BODY>
</HTML>