茂名市中国灵车网

Perl中捕获警告信息、异常信息并写入日志详解

2026-03-25 16:59:01 浏览次数:0
详细信息

1. 基本警告捕获

使用 $SIG{__WARN__} 处理警告

use strict;
use warnings;

# 保存原始警告处理器
my $orig_warn_handler = $SIG{__WARN__};

# 自定义警告处理器
$SIG{__WARN__} = sub {
    my $message = shift;

    # 记录到日志文件
    open my $log, '>>', 'warnings.log' or die "无法打开日志文件: $!";
    print $log localtime() . " - 警告: $message";
    close $log;

    # 同时打印到屏幕(可选)
    print STDERR "警告: $message";
};

# 示例:生成警告
my $undef_value;
print $undef_value;  # 会触发警告

更完善的警告处理器

use POSIX 'strftime';

my $LOG_FILE = 'app_warnings.log';

$SIG{__WARN__} = sub {
    my $msg = shift;
    chomp $msg;

    my $timestamp = strftime("%Y-%m-%d %H:%M:%S", localtime);

    # 写入日志
    if (open my $fh, '>>', $LOG_FILE) {
        print $fh "[$timestamp] WARN: $msg\n";
        close $fh;
    } else {
        # 如果无法写入日志,至少打印到STDERR
        print STDERR "无法写入警告日志: $! - 原始警告: $msg\n";
    }

    # 可选:调用原始处理器
    if (ref $orig_warn_handler eq 'CODE') {
        $orig_warn_handler->($msg);
    }
};

2. 异常(错误)捕获

使用 eval 捕获异常

use strict;
use warnings;

sub write_to_log {
    my ($level, $message) = @_;
    my $timestamp = localtime();

    open my $log, '>>', 'errors.log' or die "无法打开日志文件: $!";
    print $log "[$timestamp] $level: $message\n";
    close $log;
}

# 使用 eval 捕获异常
eval {
    # 可能出错的代码
    open my $fh, '<', 'nonexistent.txt' or die "无法打开文件: $!";
    # ... 处理文件
    1;  # 返回 true 表示成功
} or do {
    my $error = $@ || 'Unknown error';
    write_to_log('ERROR', $error);

    # 可以选择重新抛出或处理错误
    warn "操作失败: $error";
};

使用 Try::Tiny 模块(推荐)

use strict;
use warnings;
use Try::Tiny;

# 配置日志
my $LOG_FILE = 'app_errors.log';

sub log_error {
    my ($level, $message, @context) = @_;
    my $timestamp = localtime();
    my $context_str = @context ? " [" . join(", ", @context) . "]" : "";

    open my $fh, '>>', $LOG_FILE or die "无法打开日志文件: $!";
    print $fh "[$timestamp] $level: $message$context_str\n";
    close $fh;
}

try {
    # 可能出错的代码
    my $result = risky_operation();
    print "成功: $result\n";
}
catch {
    my $error = $_;
    chomp $error;

    # 记录错误
    log_error('ERROR', $error, __FILE__, __LINE__);

    # 可选:发送邮件通知等
    # notify_admin($error);

    # 恢复操作
    recover_from_error();
}
finally {
    # 无论成功失败都会执行
    cleanup_resources();
};

3. 使用专业的日志模块

使用 Log::Log4perl(推荐)

use strict;
use warnings;
use Log::Log4perl;

# 配置文件
my $config = q(
log4perl.rootLogger              = INFO, LOGFILE, SCREEN
log4perl.appender.LOGFILE        = Log::Log4perl::Appender::File
log4perl.appender.LOGFILE.filename = app.log
log4perl.appender.LOGFILE.mode   = append
log4perl.appender.LOGFILE.layout = PatternLayout
log4perl.appender.LOGFILE.layout.ConversionPattern = [%d] %p %m%n

log4perl.appender.SCREEN         = Log::Log4perl::Appender::Screen
log4perl.appender.SCREEN.stderr  = 1
log4perl.appender.SCREEN.layout = PatternLayout
log4perl.appender.SCREEN.layout.ConversionPattern = [%d] %p %m%n

# 捕获警告
log4perl.category.warn           = WARN, LOGFILE
log4perl.category.warn.additivity= 0
);

# 初始化
Log::Log4perl::init(\$config);
my $logger = Log::Log4perl->get_logger();

# 重定向警告到日志
$SIG{__WARN__} = sub {
    my $msg = shift;
    $logger->warn("警告捕获: $msg");
};

# 使用 try-catch 记录异常
use Try::Tiny;

sub risky_operation {
    try {
        # ... 操作代码
        $logger->info("操作开始");
        # 可能失败的操作
        die "操作失败" if rand() < 0.5;
        $logger->info("操作成功");
        return 1;
    }
    catch {
        $logger->error("操作失败: $_");
        $logger->error("堆栈跟踪: " . Carp::longmess());
        return 0;
    };
}

risky_operation();

使用 Log::Dispatch

use strict;
use warnings;
use Log::Dispatch;
use Log::Dispatch::File;
use Log::Dispatch::Screen;

# 创建分发器
my $log = Log::Dispatch->new(
    outputs => [
        [
            'File',
            min_level => 'debug',
            filename  => 'application.log',
            mode      => 'append',
            format    => '[%d] %p %m%n',
        ],
        [
            'Screen',
            min_level => 'warning',
            stderr    => 1,
            format    => '[%d] %p %m%n',
        ],
    ]
);

# 警告处理器
$SIG{__WARN__} = sub {
    my $msg = shift;
    $log->warning("警告: $msg");
};

# 异常处理
eval {
    # 业务逻辑
    $log->info("开始处理");
    # ... 可能出错的代码
    $log->info("处理完成");
} or do {
    my $error = $@ || 'Unknown error';
    $log->error("异常捕获: $error");
};

# 记录不同级别
$log->debug("调试信息");
$log->info("一般信息");
$log->warning("警告信息");
$log->error("错误信息");
$log->critical("严重错误");

4. 完整的错误处理框架示例

#!/usr/bin/perl
use strict;
use warnings;
use Carp qw(cluck);
use Try::Tiny;

package MyApp::Logger;

use Log::Log4perl;

sub new {
    my ($class, %args) = @_;

    # 配置
    my $config = $args{config} || q(
        log4perl.rootLogger = INFO, MAIN
        log4perl.appender.MAIN = Log::Log4perl::Appender::File
        log4perl.appender.MAIN.filename = app.log
        log4perl.appender.MAIN.mode = append
        log4perl.appender.MAIN.layout = PatternLayout
        log4perl.appender.MAIN.layout.ConversionPattern = [%d] %p %F:%L %m%n
    );

    Log::Log4perl::init(\$config);

    my $self = {
        logger => Log::Log4perl->get_logger(),
    };

    bless $self, $class;
    $self->setup_warning_handler();
    return $self;
}

sub setup_warning_handler {
    my $self = shift;

    $SIG{__WARN__} = sub {
        my $msg = shift;
        $self->{logger}->warn($msg);
        # 保存原始警告输出
        print STDERR "警告: $msg" unless $ENV{SUPPRESS_WARNINGS};
    };
}

sub log_error {
    my ($self, $error, %context) = @_;

    my $message = $error;
    if (my $stack = $context{stack_trace}) {
        $message .= "\n堆栈跟踪:\n$stack";
    }

    $self->{logger}->error($message);
}

package MyApp;

use base 'MyApp::Logger';

sub new {
    my ($class) = @_;
    my $self = $class->SUPER::new();
    bless $self, $class;
    return $self;
}

sub risky_operation {
    my $self = shift;

    try {
        $self->{logger}->info("开始风险操作");

        # 模拟可能失败的代码
        if (rand() < 0.3) {
            die "随机失败";
        }

        # 可能产生警告
        my $undefined;
        print $undefined;  # 警告

        $self->{logger}->info("操作成功");
        return 1;
    }
    catch {
        my $error = $_;

        # 获取堆栈跟踪
        local $Carp::CarpLevel = 1;
        my $stack_trace = cluck();

        # 记录错误
        $self->log_error($error, 
            stack_trace => $stack_trace,
            timestamp   => time(),
            operation   => 'risky_operation',
        );

        # 恢复或重试逻辑
        $self->recover_from_error($error);

        return 0;
    };
}

sub recover_from_error {
    my ($self, $error) = @_;
    $self->{logger}->info("尝试恢复...");
    # 恢复逻辑
}

# 主程序
package main;

my $app = MyApp->new();
$app->risky_operation();

1;

5. 最佳实践建议

分层日志级别:使用 DEBUG、INFO、WARN、ERROR、FATAL 等不同级别 日志轮转:使用 Log::Log4perl 或 Log::Dispatch 支持日志轮转 上下文信息:记录错误发生时的上下文(时间、文件、行号、用户等) 避免敏感信息:不要在日志中记录密码等敏感信息 异步日志:对于高性能应用,考虑使用异步日志记录 监控告警:设置关键错误自动告警机制

6. 简单的自制日志模块

package SimpleLogger;

use strict;
use warnings;
use POSIX 'strftime';
use Fcntl qw(:flock);

sub new {
    my ($class, %args) = @_;

    my $self = {
        log_file => $args{log_file} || 'app.log',
        level    => $args{level}    || 'INFO',
        levels   => {
            DEBUG => 1,
            INFO  => 2,
            WARN  => 3,
            ERROR => 4,
            FATAL => 5,
        },
    };

    bless $self, $class;
    return $self;
}

sub log {
    my ($self, $level, $message, %context) = @_;

    # 检查日志级别
    return if $self->{levels}{$level} < $self->{levels}{$self->{level}};

    my $timestamp = strftime("%Y-%m-%d %H:%M:%S", localtime);
    my $file = $context{file} || (caller(1))[1];
    my $line = $context{line} || (caller(1))[2];

    my $log_msg = sprintf("[%s] %-5s %s:%d - %s\n",
        $timestamp, $level, $file, $line, $message);

    # 线程安全写入
    if (open my $fh, '>>', $self->{log_file}) {
        flock($fh, LOCK_EX);
        print $fh $log_msg;
        flock($fh, LOCK_UN);
        close $fh;
    }

    # 错误级别也输出到STDERR
    if ($level eq 'ERROR' || $level eq 'FATAL') {
        print STDERR $log_msg;
    }
}

# 快捷方法
sub debug { shift->log('DEBUG', @_) }
sub info  { shift->log('INFO',  @_) }
sub warn  { shift->log('WARN',  @_) }
sub error { shift->log('ERROR', @_) }
sub fatal { shift->log('FATAL', @_) }

1;

这个详细指南涵盖了从基本到高级的警告和异常捕获技术。根据项目需求选择合适的方案,大型项目推荐使用 Log::Log4perl,小型项目可以使用 Try::Tiny 配合自定义日志。

相关推荐