【问题标题】:Using perl module Log::Syslog::Fast - Unable to catch exception使用 perl 模块 Log::Syslog::Fast - 无法捕获异常
【发布时间】:2016-08-16 00:16:52
【问题描述】:

我正在使用 Log::Syslog::Fast 将日志转发到 syslog 服务器。我正在测试脚本,看看如果系统日志服务器突然崩溃它会如何反应。

为了测试,我创建了一个包含测试消息的文件,启动了脚本,然后在 syslog 服务器收到 2 条消息后关闭了 syslog 服务器。

脚本发送了第三条消息,然后死掉了。终止没有被eval 捕获并且'use warnings 'FATAL' => 'all';' 没有帮助。

有人可以帮我捕捉异常并更优雅地关闭脚本吗?

这里需要做的是 - 发送 Command2 后,脚本应该捕获异常并显示:

Fail: Command3

代码摘录:

$logger = Log::Syslog::Fast->new(LOG_TCP,$server, 514, 13, 6, "test_machine", "Syslog");
$logger->set_pid(0);

foreach $line(<SPOOL>)
{
        ($machine,$time,$message)=(split '\|',$line);
        eval{
                $logger->set_sender($machine);
                $logger->send($message,$time);
        };
        if($@)
        {
                print "\nFail: $message\n";
                exit;
        }
        else
        {
                print "\nSuccess: $message\n";
        }
        sleep 5;
}

输入文件:

test_machine1|1461201306|Command1
test_machine1|1461201311|Command2
test_machine1|1461203214|Command3
test_machine1|1461203219|Command4
test_machine2|1461204005|Command5
test_machine2|1461204006|Command6
test_machine2|1461204149|Command7
test_machine3|1461204154|Command8
test_machine3|1461206936|Command9
test_machine3|1461206942|Command10

输出:

Success: Command1

Success: Command2

Success: Command3

Strace 输出:

read(4, "test_machine1|1461201306|Command"..., 4096) = 341
read(4, "", 4096)                       = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=3519, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=3519, ...}) = 0
sendto(3, "<110>Apr 20 21:15:06 test_machin"..., 59, 0, NULL, 0) = 59
write(1, "Success Command1\n\n\n", 19Success Command1


)  = 19
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({5, 0}, 0x7ffc707478f0)       = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=3519, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=3519, ...}) = 0
sendto(3, "<110>Apr 20 21:15:11 test_machin"..., 59, 0, NULL, 0) = 59
write(1, "Success Command2\n\n\n", 19Success Command2


)  = 19
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({5, 0}, 0x7ffc707478f0)       = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=3519, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=3519, ...}) = 0
sendto(3, "<110>Apr 20 21:46:54 test_machin"..., 59, 0, NULL, 0) = 59

我希望脚本在尝试发送第三条消息时失败。

write(1, "Success Command3\n\n\n", 19Success Command3


)  = 19
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({5, 0}, 0x7ffc707478f0)       = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=3519, ...}) = 0
stat("/etc/localtime", {st_mode=S_IFREG|0644, st_size=3519, ...}) = 0
sendto(3, "<110>Apr 20 21:46:59 test_machin"..., 59, 0, NULL, 0) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=26037, si_uid=3179} ---
+++ killed by SIGPIPE +++

脚本在尝试发送第四条消息时最终死掉了。不幸的是,eval 没有捕捉到异常。

【问题讨论】:

  • 在每种情况下(对于“如果”),您的 $@ 价值得到了什么?
  • 第三条消息之后会发生什么?它会无限期地继续报告成功,还是最终抛出异常?我认为this 可能是相关的。
  • 完美地为我工作。您是否尝试过 strace 以获取有关正在发生的事情的更多信息?
  • @tale 在成功和失败的情况下 $@ 在我尝试打印时为空。
  • @This 在第三条消息之后,脚本就死了,返回码为 141。

标签: perl exception tcp exception-handling eval


【解决方案1】:

我希望脚本在尝试发送第三条消息时失败。

TL;DR由于 TCP 协议的工作方式,您不能这样做。

客户端和服务器通过套接字进行通信。当客户端写入套接字时,它实际上是在写入缓冲区;没有任何迹象表明该消息是否实际已送达。

客户端只有在一些数据真正发送到服务器后才能知道连接关闭,所以第一次写入缓冲区会成功。

这是发生了什么:

  1. 当您关闭服务器时,它会发送一个 TCP FIN 数据包。一个FIN表示连接的一侧已经完成发送数据,但仍然可以接收;它并不表示连接已关闭。

  2. 客户端成功将您的第三条日志消息写入套接字缓冲区,因此不会引发异常。

  3. 服务器发送一个 TCP RST 数据包表示它不再监听。

  4. 由于RST,操作系统现在知道服务器端的 TCP 连接已关闭。当客户端尝试写入套接字缓冲区时,进程会收到SIGPIPE 的信号,并且写入返回EPIPE


脚本在尝试发送第四条消息时最终死掉了。不幸的是,eval 没有捕捉到异常。

你没有处理SIGPIPE,所以你的程序在收到信号时就死掉了。在脚本顶部附近添加以下内容以忽略 SIGPIPE

$SIG{PIPE} = 'IGNORE';

现在您可以随意处理send 方法引发的异常。


进一步阅读:

【讨论】:

    【解决方案2】:

    尝试添加一行

    $SIG{PIPE} = sub {
    die "SIGPIPE";
    };
    

    在发送任何东西之前。

    您可能还想尝试“打印”而不是死。

    【讨论】:

    • 谢谢。当我将上述行添加到我的脚本时,能够更有效地处理错误。然而,脚本仍然等待命令 4 注意到损坏的管道。如何让命令 3 的失败更加敏感?
    【解决方案3】:

    您可能希望像这样捕获 SIGPIPE:

    $SIG{PIPE} = "IGNORE";
    

    来自perlipc

    如果您正在写入管道,您还应该捕获 SIGPIPE。否则,想想当你启动一个不存在的命令的管道时会发生什么:open() 很可能会成功(它只反映 fork() 的成功),但是你的输出将失败 - -壮观。 Perl 无法知道该命令是否有效,因为您的命令实际上是在一个单独的进程中运行,该进程的 exec() 可能已经失败。因此,虽然虚假命令的读者只返回一个快速的 EOF,但虚假命令的编写者会受到信号的影响,他们最好准备好处理。

    此外,在写入损坏的套接字时,请查看 C 程序中的 this behavior

    【讨论】:

    • 谢谢。当我将上述行添加到我的脚本时,能够更有效地处理错误。然而,脚本仍然等待命令 4 注意到损坏的管道。如何让命令 3 的失败更加敏感?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-21
    • 1970-01-01
    • 2021-09-20
    • 2014-11-06
    • 2010-10-29
    • 1970-01-01
    相关资源
    最近更新 更多