【Apache】アクセスログをCSV化してグラフ出力

## はじめに
webサイトへのアクセスがどの時間帯にどのくらいあったのか？
⇒便利なツールが利用できない環境で手作業は時間かかるなぁ
⇒なのでスクリプトでやってみます

## 動作環境
Raspbian GNU/Linux 10
Apache/2.4.38 
perl v5.28.1
Python 3.7.3

## コード
1. まず生ログをCSVに変換  

`alog.pl`
```perl
#!/usr/bin/perl
# Format apache log by tab delimiter
#
# Output: IP timestamp request status byte time referer
#
# Usage:
# alog.pl logfile i.e. alog.pl access.log
# alog.pl < logfile > fmt.txt
# zcat logfile.gz | alog.pl
#
# LogFormat:
# pattern1:combined
# "%a %D %u %t \"%r\" %>s %b \"{Referer}i\" \"%{User-Agent}i\" \"-\J"" combined
#127.0.0.1 - - [12/Nov/2021:14:04:13 +0900] "GET /wp-content HTTP/1.0" 200 5021 "http://foo.com/home.htm" "Mozilla/5.0 (Windows NT 5.01; as-IN; rv:1.9.0.20) Gecko/2021-01-10 16:56:07 Firefox/14.0"
#
# pattern1:common
# "%h %l %u %t \"%r\" %>s %b" common
# 127.0.0.1 - - [10/Apr/2021:17:12:42 +0900] "GET /favicon.ico HTTP/1.1" 200 1150
#
use strict;
use warnings;
use Getopt::Long;

my $opt_fmt = "combined";
my $opt_help = 0;

GetOptions(
    "format=s" => \$opt_fmt,
    "help" => \$opt_help,
) or disp_usage();
disp_usage() if $opt_help;

sub disp_usage {
    print "format apacke log by tab delimiter\n";
    print "Usage: $0 [--format=common][--help] <filename> i.e. $0 --format=common access.log\n";
    exit;
}

#print "$opt_fmt\n";

my $fmt = qr/^(\S+) (\S+) (\S+) \[(.*?)\] "(\S+)?(\s\S+)?(\s\S+)?" (\d+) (\d+|-)$/;
if ($opt_fmt eq "combined" ) {
    $fmt = qr/^(\S+) (\S+) (\S+) \[(.*?)\] "(\S+)?(\s\S+)?(\s\S+)?" (\d+) (\d+|-) "([^"]*)" (.*)$/;
}

my ($ip,$ptime,$usr,$ts,$req,$url,$prt,$sts,$sz,$ref,$oth,$rlog);
my $tabstr;
while (<>) {
    if ($_ =~ /$fmt/) {
        if ($opt_fmt eq "combined" ) {
            ($ip,$ptime,$usr,$ts,$req,$url,$prt,$sts,$sz,$ref,$oth) = ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11);
            if (defined $req){
                $tabstr = join("\t",$ip,$ts,$req,$sts,$sz,$ptime,$ref);
            } else {
                $tabstr = join("\t",$ip,$ts,"",$sts,$sz,$ptime,$ref);
            }
        } else {
            ($ip,$rlog,$usr,$ts,$req,$url,$prt,$sts,$sz) = ($1,$2,$3,$4,$5,$6,$7,$8,$9);
            $tabstr = join("\t",$ip,$ts,$req,$sts,$sz);
        }
        print "$tabstr\n";
    } else {
        print "Unmatch* $_";
    }
}
```
注意：apacheのログフォーマットによるので必要に応じて修正すること



2.CSVファイルからグラフを作成して画像ファイルを出力   

`aplot.py`
```python
#!/usr/bin/env python3
# plot graph for appache log: number of access by hour
# input: access_log
# outout: aplot.png
# usage ./aplot.png 

import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime

df = pd.read_csv('alog.tsv', sep='\t', header=None, names=['ip', 'datetime', 'method', 'status', 'size', 'referer', 'url'])

# convert date (12/Nov/2023:09:42:45 +0900)
df['datetime'] = df['datetime'].apply(lambda x: datetime.strptime(x.split()[0], "%d/%b/%Y:%H:%M:%S"))

# round time by 1h (2023-11-12 09:00:00)
df['hour'] = df['datetime'].dt.floor('H')

# group by time
access_counts = df.groupby('hour').size()

# plot graph
plt.figure(figsize=(12, 6))
access_counts.plot(kind='line', marker='o')
plt.bar(access_counts.index.astype(str), access_counts.values)
plt.title('Accesses per hour')
plt.xlabel('time')
plt.ylabel('number of access')
plt.grid(True)
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('aplot.png')

# if you have GUI
#plt.show()
```

## 使い方

アパッチのアクセスログを整形してタブ区切りCSVを生成する
`./alog.pl < access_log > alog.tsv`

できたCSVからグラフを画像ファイルに出力する
`python3 ./aplot.py`

Xが使えるなら次のコマンドで画像ファイルを描画
`xdg-open aplot.png`

## おわりに
便利なGUIツールが入れられない場合でもperlは入っていることが多いはず
CSVファイルをインポートしてエクセルの機能でグラフにしてもよい
毎分、IPアドレス毎などでグラフしたい場合は、pythonコードを改良したり、エクセルのテーブルで対応可能

## 参考リンク
[Apache mod_log_config format](https://httpd.apache.org/docs/2.4/en/mod/mod_log_config.html#formats)

[github.com/kojimura/apache/blob/main/alog.pl](https://github.com/kojimura/apache/blob/main/alog.pl)

[github.com/kojimura/apache/blob/main/aplot.py](https://github.com/kojimura/apache/blob/main/aplot.py)