Featured image of post Perlでのロギング - Log::Log4perl と Log::Dispatch

Perlでのロギング - Log::Log4perl と Log::Dispatch

PerlでのログライブラリLog::Log4perlとLog::Dispatchの使い方、設定例、運用上の注意点を解説。

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レベルが一般的です。

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