Perlでのロギング - Log::Log4perl と Log::Dispatch
ロギングは、アプリケーションの動作を追跡し、問題を診断するために不可欠です。Perlには強力なロギングモジュールがあり、適切に使うことでデバッグとメンテナンスが大幅に楽になります。
ロギングの重要性
ロギングがなぜ重要なのか:
- デバッグ: 問題の原因を特定できる
- 監視: システムの健全性を確認できる
- 監査: 誰が何をしたか記録できる
- パフォーマンス分析: ボトルネックを発見できる
- セキュリティ: 不正アクセスを検出できる
1
2
3
4
5
6
7
| # 悪い例: printでのデバッグ
print "User logged in\n"; # 本番環境で残ってしまう
print "DEBUG: value = $value\n"; # 管理が困難
# 良い例: ロガーを使用
$log->info("User logged in"); # レベルで制御可能
$log->debug("value = $value"); # 本番では出力されない
|
Log::Log4perl の基本
Log::Log4perlは、Javaの人気ライブラリLog4jのPerl移植版で、非常に強力で柔軟性があります。
簡単な使い方
1
2
3
4
5
6
7
8
9
10
11
12
13
| use Log::Log4perl qw(:easy);
# 簡易設定
Log::Log4perl->easy_init($DEBUG);
my $log = Log::Log4perl->get_logger();
$log->trace("トレースレベルのメッセージ");
$log->debug("デバッグメッセージ");
$log->info("情報メッセージ");
$log->warn("警告メッセージ");
$log->error("エラーメッセージ");
$log->fatal("致命的エラー");
|
設定ファイルを使用
1
2
3
4
5
6
7
8
9
| use Log::Log4perl;
# 設定ファイルから読み込み
Log::Log4perl->init('/path/to/log4perl.conf');
my $log = Log::Log4perl->get_logger('MyApp');
$log->info("Application started");
$log->debug("Debug information");
|
log4perl.conf の例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # ルートロガーの設定
log4perl.rootLogger = INFO, Screen, File
# 画面出力用のアペンダー
log4perl.appender.Screen = Log::Log4perl::Appender::Screen
log4perl.appender.Screen.stderr = 0
log4perl.appender.Screen.layout = PatternLayout
log4perl.appender.Screen.layout.ConversionPattern = %d %p %c - %m%n
# ファイル出力用のアペンダー
log4perl.appender.File = Log::Log4perl::Appender::File
log4perl.appender.File.filename = /var/log/myapp.log
log4perl.appender.File.mode = append
log4perl.appender.File.layout = PatternLayout
log4perl.appender.File.layout.ConversionPattern = %d [%P] %p %c - %m%n
# 特定のパッケージのログレベルを変更
log4perl.logger.MyApp.Database = DEBUG
|
ログレベルの使い分け
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| use Log::Log4perl qw(:easy);
Log::Log4perl->easy_init($INFO);
my $log = Log::Log4perl->get_logger();
# TRACE: 非常に詳細な情報(通常は使わない)
$log->trace("Entering function foo() with args: ", join(', ', @args));
# DEBUG: 開発時のデバッグ情報
$log->debug("Variable x = $x, y = $y");
# INFO: 一般的な情報(処理の流れ)
$log->info("User $username logged in successfully");
# WARN: 警告(問題になる可能性がある)
$log->warn("Disk space is running low: $free_space MB remaining");
# ERROR: エラー(処理は継続可能)
$log->error("Failed to connect to database, retrying...");
# FATAL: 致命的エラー(処理継続不可能)
$log->fatal("Configuration file not found, exiting");
|
パターンレイアウト
Log::Log4perlは、豊富なフォーマット指定子を提供します:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # よく使うパターン
%d 日時
%p ログレベル (INFO, DEBUG, etc.)
%c カテゴリ名(ロガー名)
%m メッセージ
%n 改行
%P プロセスID
%H ホスト名
%F ファイル名
%L 行番号
%M メソッド名
# 実用的なパターン例
log4perl.appender.File.layout.ConversionPattern = [%d] [%P] %p %c %M:%L - %m%n
# 出力例: [2025/12/20 10:30:45] [12345] INFO MyApp::User login:42 - User logged in
|
複数のアペンダー
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
| use Log::Log4perl;
my $conf = q{
log4perl.rootLogger = DEBUG, Screen, File, Email
# 画面出力(INFOレベル以上)
log4perl.appender.Screen = Log::Log4perl::Appender::Screen
log4perl.appender.Screen.Threshold = INFO
log4perl.appender.Screen.layout = PatternLayout
log4perl.appender.Screen.layout.ConversionPattern = %p: %m%n
# ファイル出力(すべてのログ)
log4perl.appender.File = Log::Log4perl::Appender::File
log4perl.appender.File.filename = /var/log/app.log
log4perl.appender.File.layout = PatternLayout
log4perl.appender.File.layout.ConversionPattern = %d %p - %m%n
# メール通知(ERRORレベル以上)
log4perl.appender.Email = Log::Dispatch::Email::MailSend
log4perl.appender.Email.Threshold = ERROR
log4perl.appender.Email.to = admin@example.com
log4perl.appender.Email.from = app@example.com
log4perl.appender.Email.subject = Application Error
log4perl.appender.Email.layout = SimpleLayout
};
Log::Log4perl->init(\$conf);
my $log = Log::Log4perl->get_logger();
$log->debug("デバッグ情報"); # ファイルのみ
$log->info("情報"); # 画面とファイル
$log->error("エラー発生"); # すべて(メールも送信)
|
ローテーション可能なログファイル
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| use Log::Log4perl;
my $conf = q{
log4perl.rootLogger = INFO, Logfile
log4perl.appender.Logfile = Log::Dispatch::FileRotate
log4perl.appender.Logfile.filename = /var/log/myapp.log
log4perl.appender.Logfile.max = 10
log4perl.appender.Logfile.DatePattern = yyyy-MM-dd
log4perl.appender.Logfile.TZ = JST
log4perl.appender.Logfile.layout = PatternLayout
log4perl.appender.Logfile.layout.ConversionPattern = %d %p - %m%n
};
Log::Log4perl->init(\$conf);
my $log = Log::Log4perl->get_logger();
$log->info("This will be logged with rotation");
|
Log::Dispatch の使用
Log::Dispatchは、Log::Log4perlよりシンプルで、コードで直接設定します。
基本的な使い方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| use Log::Dispatch;
my $log = Log::Dispatch->new(
outputs => [
[ 'Screen', min_level => 'info' ],
[ 'File',
min_level => 'debug',
filename => '/var/log/myapp.log',
mode => 'append',
],
],
);
$log->debug("Debug message");
$log->info("Info message");
$log->warning("Warning message");
$log->error("Error message");
|
複数の出力先
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
| use Log::Dispatch;
use Log::Dispatch::Screen;
use Log::Dispatch::File;
use Log::Dispatch::Syslog;
my $log = Log::Dispatch->new;
# 画面出力
$log->add(
Log::Dispatch::Screen->new(
name => 'screen',
min_level => 'info',
stderr => 1,
)
);
# ファイル出力
$log->add(
Log::Dispatch::File->new(
name => 'file',
min_level => 'debug',
filename => '/var/log/myapp.log',
mode => 'append',
binmode => ':encoding(UTF-8)',
)
);
# syslog出力
$log->add(
Log::Dispatch::Syslog->new(
name => 'syslog',
min_level => 'warning',
ident => 'myapp',
facility => 'daemon',
)
);
$log->info("Application started");
|
実用的なロギング戦略
アプリケーションロガークラス
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
| package MyApp::Logger;
use Moo;
use Log::Log4perl;
has config_file => (
is => 'ro',
default => '/etc/myapp/log4perl.conf',
);
has logger => (is => 'lazy');
sub _build_logger {
my $self = shift;
Log::Log4perl->init($self->config_file);
return Log::Log4perl->get_logger(__PACKAGE__);
}
sub debug { shift->logger->debug(@_) }
sub info { shift->logger->info(@_) }
sub warn { shift->logger->warn(@_) }
sub error { shift->logger->error(@_) }
sub fatal { shift->logger->fatal(@_) }
# 構造化ロギング
sub log_event {
my ($self, $event, $data) = @_;
my $message = sprintf(
"EVENT=%s USER=%s ACTION=%s",
$event,
$data->{user} // 'unknown',
$data->{action} // 'unknown'
);
$self->info($message);
}
package main;
my $log = MyApp::Logger->new;
$log->info("Application started");
$log->log_event('user_login', {
user => 'john',
action => 'login',
});
|
パフォーマンス測定
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
| use Log::Log4perl qw(:easy);
use Time::HiRes qw(gettimeofday tv_interval);
Log::Log4perl->easy_init($INFO);
my $log = Log::Log4perl->get_logger();
sub measure_time {
my ($name, $code) = @_;
my $start = [gettimeofday];
$log->info("Starting: $name");
my $result = $code->();
my $elapsed = tv_interval($start);
$log->info(sprintf("Finished: %s (%.3f seconds)", $name, $elapsed));
return $result;
}
# 使用例
my $result = measure_time('database_query' => sub {
# データベース処理
sleep 2;
return "result";
});
|
エラーコンテキストの記録
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
| use Log::Log4perl qw(:easy);
use Try::Tiny;
Log::Log4perl->easy_init($DEBUG);
my $log = Log::Log4perl->get_logger();
sub process_request {
my ($request_id, $user_id) = @_;
# コンテキスト情報を設定
Log::Log4perl::MDC->put('request_id', $request_id);
Log::Log4perl::MDC->put('user_id', $user_id);
$log->info("Processing request");
try {
# 処理
die "Something went wrong" if rand() > 0.5;
$log->info("Request processed successfully");
} catch {
$log->error("Request failed: $_");
};
# コンテキストをクリア
Log::Log4perl::MDC->remove();
}
# log4perl.conf で MDC を使用
# log4perl.appender.File.layout.ConversionPattern = %d [%X{request_id}] [%X{user_id}] %p - %m%n
|
JSON形式でのロギング
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
| use Log::Log4perl;
use JSON::MaybeXS;
my $conf = q{
log4perl.rootLogger = INFO, JSONFile
log4perl.appender.JSONFile = Log::Log4perl::Appender::File
log4perl.appender.JSONFile.filename = /var/log/app.json
log4perl.appender.JSONFile.layout = Log::Log4perl::Layout::NoopLayout
};
Log::Log4perl->init(\$conf);
my $log = Log::Log4perl->get_logger();
sub log_json {
my ($level, $message, $data) = @_;
my $entry = {
timestamp => time(),
level => $level,
message => $message,
data => $data,
};
$log->info(encode_json($entry) . "\n");
}
log_json('INFO', 'User logged in', {
user_id => 123,
ip => '192.168.1.1',
});
|
まとめ
- Log::Log4perl: 強力で柔軟、設定ファイルで制御
- Log::Dispatch: シンプルで直感的、コードで制御
- ログレベル: DEBUG < INFO < WARN < ERROR < FATAL
- 複数出力: 画面、ファイル、syslog、メールなど
- ローテーション: 古いログを自動的にアーカイブ
- 構造化ログ: JSON形式で機械可読なログ
適切なロギングは、アプリケーションの品質とメンテナンス性を大幅に向上させます。開発時はDEBUGレベル、本番環境はINFOレベルが一般的です。