Featured image of post 第6回-統計集計Decoratorを追加しよう - PerlとMooで学ぶDecorator

第6回-統計集計Decoratorを追加しよう - PerlとMooで学ぶDecorator

状態(State)を持つDecoratorの実装例として、処理したログの統計情報を集計するStatsAggregatorDecoratorを作成します。Decoratorパターンの柔軟性を体験します。

@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;

ポイント解説

  1. has stats: これが「状態」です。Decorator自身がデータを保持しています。
  2. パススルー処理: next_log の中でログを加工したり捨てたりせず、そのまま return $log しています。これにより、パイプラインの流れを止めずに「横から覗き見る」ような動作を実現しています。
  3. 独自メソッド 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$filterwrapped => $stats)に配置したらどうなるでしょうか?

その場合、「フィルタリングされる前の、全てのアクセスログの統計」が取れます。

Decoratorをつなぐ順番を変えるだけで、異なる意味のデータを取得できる。これもDecoratorパターンの大きな魅力です。

次回予告

次回は、統計だけでなく「異常検知」もやってみましょう。404エラーが連続したり、レスポンスタイムが異常に遅い場合に警告を出す「アラート通知機能」を追加します。

第7回: アラート通知Decoratorを追加しよう

comments powered by Disqus
Hugo で構築されています。
テーマ StackJimmy によって設計されています。