@nqounetです。
前回は LogDecorator を導入し、IPフィルタリング機能をDecoratorとして実装しました。
今回は、Decoratorパターンのもう一つの強力な側面である「状態(State)を持つDecorator」を使って、ログの統計情報を集計する機能を追加してみましょう。
今回のゴール:StatsAggregatorDecorator
通過するログデータを監視し、以下の統計情報を集計する StatsAggregatorDecorator を作ります。
- 総リクエスト数
- ステータスコードごとの件数(200 OK: 10件, 404 Not Found: 2件…)
- 総転送サイズ
このDecoratorはログをフィルタリング(除外)はせず、単に通過させながらデータを記録します。
コード例1: StatsAggregatorDecorator.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| package StatsAggregatorDecorator;
use Moo;
use experimental qw(signatures);
extends 'LogDecorator';
# 統計情報を保持するハッシュ(状態)
has stats => (
is => 'ro',
default => sub { { status_count => {}, total_size => 0, total_requests => 0 } },
);
around next_log => sub ($orig, $self) {
# 1. 中身(wrapped)からログを取得
my $log = $self->$orig;
# 2. ログが存在すれば集計(状態の更新)
if ($log) {
$self->stats->{total_requests}++;
$self->stats->{total_size} += ($log->{size} eq '-' ? 0 : $log->{size});
$self->stats->{status_count}->{ $log->{status} }++;
}
# 3. ログをそのまま返す(パススルー)
return $log;
};
# 集計結果を表示するメソッド
sub report ($self) {
my $s = $self->stats;
print "=== Log Stats ===\n";
print "Total Requests: $s->{total_requests}\n";
print "Total Size: $s->{total_size} bytes\n";
print "Status Codes:\n";
for my $code (sort keys %{ $s->{status_count} }) {
print " $code: $s->{status_count}->{$code}\n";
}
print "=================\n";
}
1;
|
ポイント解説
has stats: これが「状態」です。Decorator自身がデータを保持しています。- パススルー処理:
next_log の中でログを加工したり捨てたりせず、そのまま return $log しています。これにより、パイプラインの流れを止めずに「横から覗き見る」ような動作を実現しています。 - 独自メソッド
report: LogProcessor インターフェース(next_log)以外のメソッドを追加してもOKです。ただし、このメソッドを呼ぶには、このDecoratorのインスタンスを直接扱える必要があります。
コード例2: 使用するスクリプト
集計機能を「特定のIPフィルタリングの後」に配置してみましょう。そうすれば、「そのIPからのアクセス統計」が取れます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| use strict;
use warnings;
use lib '.';
use LogParser;
use IPFilterDecorator;
use StatsAggregatorDecorator;
# 1. 基本のパーサー
my $parser = LogParser->new(filename => 'access.log');
# 2. IPフィルター
my $filter = IPFilterDecorator->new(
wrapped => $parser,
target_ip => '127.0.0.1',
);
# 3. 統計集計(フィルターの後ろに配置!)
my $stats = StatsAggregatorDecorator->new(
wrapped => $filter,
);
# 処理実行
while (defined(my $log = $stats->next_log)) {
# 処理中は特に何も表示しなくてもOK
}
# 最後にレポート出力
$stats->report();
|
配置順序の妙
もし $stats を $filter の前(wrapped => $stats)に配置したらどうなるでしょうか?
その場合、「フィルタリングされる前の、全てのアクセスログの統計」が取れます。
Decoratorをつなぐ順番を変えるだけで、異なる意味のデータを取得できる。これもDecoratorパターンの大きな魅力です。
次回予告
次回は、統計だけでなく「異常検知」もやってみましょう。404エラーが連続したり、レスポンスタイムが異常に遅い場合に警告を出す「アラート通知機能」を追加します。
第7回: アラート通知Decoratorを追加しよう