Featured image of post 第1回-ログを1行ずつ読み込んでみよう - PerlとMooで学ぶDecorator

第1回-ログを1行ずつ読み込んでみよう - PerlとMooで学ぶDecorator

PerlとMooでログ解析の基礎を学ぶ連載第1回。ファイルを1行ずつ読み込むLogReaderクラスを実装し、Decoratorパターン学習の土台を作ります。Perl入学式卒業者向け実践シリーズ。

@nqounetです。

今回から新シリーズ「PerlとMooで学ぶDecorator - ログ解析パイプライン実装」をスタートします。

このシリーズでは、Webサーバーのアクセスログ(Apache/Nginx)をリアルタイムで解析するパイプラインツールを作りながら、デザインパターンの1つである「Decoratorパターン」を実践的に学んでいきます。

第1回は、すべての基礎となる「ログファイルを1行ずつ読み込むクラス」を作成します。

以前のシリーズ

このシリーズは、以下のシリーズの続編として位置づけています。Mooの基本についてはこちらを参照してください。

Mooで覚えるオブジェクト指向プログラミング

今回のゴール:LogReaderクラス

まずはシンプルに、指定されたログファイルを1行ずつ返す LogReader クラスを作ります。

Perl入学式で習った openwhile を、Mooのクラスの中にカプセル化(包み込む)してみましょう。

コード例1: LogReader.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
42
43
44
45
46
47
package LogReader;
use Moo;
use strict;
use warnings;
use experimental qw(signatures);
use namespace::clean;

# 読み込むファイル名(必須)
has filename => (
    is       => 'ro',
    required => 1,
);

# ファイルハンドル(内部用)
has _fh => (
    is      => 'rw',
    default => undef,
);

# インスタンス生成後に自動実行されるメソッド
sub BUILD ($self, $args) {
    my $filename = $self->filename;
    open my $fh, '<', $filename or die "Cannot open file $filename: $!";
    $self->_fh($fh);
}

# 次の1行を返すメソッド
sub next_line ($self) {
    my $fh = $self->_fh;
    
    # 完全に読み終わっていたらundefを返す
    return undef unless defined $fh;

    my $line = <$fh>;
    
    # ファイル末尾に達したら閉じる(行が取得できなかった場合)
    unless (defined $line) {
        close $fh;
        $self->_fh(undef); # ハンドルをクリア
        return undef;
    }

    chomp $line;
    return $line;
}

1;

ポイント解説

  1. has filename: 読み込むログファイル名を指定するアトリビュートです。required => 1 なので、new するときに必ず指定しないとエラーになります。
  2. has _fh: ファイルハンドルを保持するプライベートなアトリビュートです。default => undef で初期化しています。名前に _ を付けるのは「内部用(プライベート)」を表す慣習です。
  3. sub BUILD: Mooの機能で、new が終わった直後に自動的に呼ばれます。ここでファイルを open しています。Perl入学式で習った open my $fh, '<', $filename と同じですね。
  4. sub next_line: 呼び出されるたびにファイルの次の1行を読み込んで返します。chomp で改行コードを削除しています。

コード例2: 使用するスクリプト

このクラスを使う側のコード(main.pl)は以下のようになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use strict;
use warnings;
use lib '.';
use LogReader;

# テスト用のダミーログファイルを用意しておく必要があります
# echo "127.0.0.1 - - [19/Jan/2026:21:15:21 +0900] \"GET /index.html HTTP/1.1\" 200 1234" > access.log

my $reader = LogReader->new(filename => 'access.log');

# 1行ずつ読み込んで表示
while (defined(my $line = $reader->next_line)) {
    print "Read: $line\n";
}

これで、オブジェクト指向スタイルでファイルを読み込む準備ができました!

なぜクラスにするの?

「普通に open して while で回せば良くない?」と思うかもしれません。

確かに単純なスクリプトならそれで十分です。しかし、クラス(オブジェクト)にしておくことで、「このLogReaderを別の部品と入れ替える」ことが簡単になります。

この「部品の入れ替え」や「部品の装飾(デコレーション)」こそが、今後学ぶDecoratorパターンの肝となります。

次回予告

次回は、読み込んだログの文字列を「意味のあるデータ(IPアドレスやステータスコード)」に分解します。正規表現を使って、ログパーサーへと進化させましょう!

第2回: 正規表現でログをパースしよう

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