【问题标题】:How can I hook into Perl's print?我怎样才能挂钩到 Perl 的打印?
【发布时间】:2010-09-28 02:41:28
【问题描述】:

这是一个场景。你有大量的遗留脚本,都使用一个公共库。所述脚本使用“打印”语句进行诊断输出。不允许对脚本进行任何更改 - 它们的范围很广,得到了他们的批准,并且早已离开了富有成效的监督和控制谷。

现在出现了一个新需求:现在必须将日志记录添加到库中。这必须自动且透明地完成,标准库的用户无需更改他们的脚本。公共库方法可以简单地添加日志调用;这是容易的部分。困难的部分在于这些脚本的诊断输出总是使用“打印”语句显示的。必须存储此诊断输出,但同样重要的是,必须对其进行处理。

作为此处理的示例,库应仅记录包含“警告”、“错误”、“通知”或“注意”字样的打印行。下面的非常琐碎和人为的示例代码 (tm) 将记录一些所说的输出:

sub CheckPrintOutput
{
    my @output = @_; # args passed to print eventually find their way here.
    foreach my $value (@output) {
         Log->log($value) if $value =~ /warning|error|notice|attention/i;
    }
}

(我想避免诸如“实际应该记录什么”、“打​​印不应该用于诊断”、“perl 很烂”或“此示例存在 xy 和 z 缺陷”等问题。 .为了简洁明了,这被大大简化了。)

基本问题归结为捕获和处理传递给打印(或任何 perl 内置,沿着这些推理)的数据。可能吗?有什么办法可以干净利落的吗?是否有任何带有钩子的日志记录模块可以让你做到这一点?还是像瘟疫一样应该避免,我应该放弃捕获和处理打印输出?

附加:这必须跨平台运行 - windows 和 *nix 一样。运行脚本的过程必须保持不变,脚本的输出也必须保持不变。

附加附加:在 codelogic 的答案的 cmets 中提出的一个有趣的建议:

您可以继承 http://perldoc.perl.org/IO/Handle.html 并创建您的 自己的文件句柄,它将完成日志记录工作。 – 卡米尔·基塞尔

这可能会做到,但有两个警告:

1) 我需要一种方法将此功能导出给使用公共库的任何人。它必须自动适用于 STDOUT,也可能适用于 STDERR。

2) the IO::Handle 文档说你不能子类化它,到目前为止我的尝试都没有结果。使子类化 IO::Handle 工作需要什么特别的东西吗?标准的 'use base 'IO::Handle' 然后覆盖 new/print 方法似乎什么都不做。

最终编辑:看起来 IO::Handle 是一个死胡同,但 Tie::Handle 可能会这样做。感谢所有的建议;他们都很好。我要试试 Tie::Handle 路线。如果它引起问题,我会回来!

附录:请注意,在稍微处理一下之后,我发现 Tie::Handle 会起作用,如果你不做任何棘手的事情的话。如果您将 IO::Handle 的任何功能与绑定的 STDOUT 或 STDERR 一起使用,那么让它们可靠地工作基本上是一个废话——我找不到让 IO::Handle 的 autoflush 方法在我的绑定上工作的方法处理。如果我在绑定手柄之前启用了自动冲洗,它会起作用。如果这对您有用,则 Tie::Handle 路线可能是可以接受的。

【问题讨论】:

  • 那么你允许改变什么?命令行?参数文件?例如,假设我说“是的,你可以挂钩打印”,你能做的范围是什么?
  • 我可以更改公共库中的任何内容。用户必须不需要以任何不同的方式运行他的脚本,或者必须在命令行中传递任何新内容。 STDOUT 和 STDERR 上的最终数据流必须与之前相同。
  • 原始输出会发生什么?你可以tail -f 并从那里处理吗?
  • 原始输出由其他程序处理。他们希望它与以前一样。此外,正如下面另一条评论中所提到的,我们也不想改变环境——所以用另一个执行日志处理的程序来屏蔽“perl”是有问题的。

标签: perl printing hook tie


【解决方案1】:

您可以覆盖许多内置函数(请参阅perlsub)。但是,print 是不能以这种方式工作的内置插件之一。覆盖print 的困难在此perlmonk's thread 中有详细说明。

但是,你可以

  1. 创建一个包
  2. 系一个把手
  3. 选择此句柄。

现在,有几个人给出了基本框架,但它的工作原理是这样的:

package IO::Override;
use base qw<Tie::Handle>;
use Symbol qw<geniosym>;

sub TIEHANDLE { return bless geniosym, __PACKAGE__ }

sub PRINT { 
    shift;
    # You can do pretty much anything you want here. 
    # And it's printing to what was STDOUT at the start.
    # 
    print $OLD_STDOUT join( '', 'NOTICE: ', @_ );
}

tie *PRINTOUT, 'IO::Override';
our $OLD_STDOUT = select( *PRINTOUT );

你可以用同样的方式覆盖printf

sub PRINTF { 
    shift;
    # You can do pretty much anything you want here. 
    # And it's printing to what was STDOUT at the start.
    # 
    my $format = shift;
    print $OLD_STDOUT join( '', 'NOTICE: ', sprintf( $format, @_ ));
}

请参阅Tie::Handle,了解您可以覆盖的所有 STDOUT 行为。

【讨论】:

  • 哦,这看起来不错。如果他们使用 print 写入文件句柄或仅写入 STDOUT,这会改变 print 的行为吗?
  • 查看更多。我明白了 - 它一次绑定到一个文件句柄,对吧?我很快就会试一试并报告。
  • “文件句柄”只是一种手段。一旦我们将流定向到我们可以指定行为的位置,我们就可以写出尽可能多的真实句柄,根据需要对其进行过滤——甚至进行数据库调用,如果这是我们想做的。跨度>
  • 太棒了,这正是我所希望的:只影响 STDOUT(也可能是 STDERR),对外透明。谢谢!
  • 重新分配 STDOUT glob 而不是 select()'ing 怎么样?我们已经将它存储在模块中......出于所有意图和目的,我们的新绑定句柄应该是 STDOUT。它看起来有点像,但并没有超出可操作性。你知道做这样的事情有什么重大问题吗?
【解决方案2】:

您可以使用 Perl 的 select 重定向 STDOUT。

open my $fh, ">log.txt";
print "test1\n";
my $current_fh = select $fh;
print "test2\n";
select $current_fh;
print "test3\n";

文件句柄可以是任何东西,甚至是通往另一个处理您的日志消息的进程的管道。

PerlIO::Util 模块中的PerlIO::tee 似乎允许您将文件句柄的输出“发送”到多个目标(例如日志处理器和 STDOUT)。

【讨论】:

  • select 一切都很好,但我们也需要处理这些数据。是否有一种文件句柄类型可以提供挂钩来评估和处理传递给它的数据?
  • 此外,我们无法修改这些脚本的当前行为,因此输出也必须保留在 STDOUT 上。
  • 您可以打开一个文件句柄作为另一个进程的管道。在那里做日志记录,然后你可以从里面打印到标准输出。
  • 除了处理它之外,您还可以让管道进程将其 STDIN 重定向到 STDOUT,这样当前行为不会受到影响。我不知道任何抽象此功能的模块。
  • 有趣的想法......会是像 fork() 这样的东西吗?还有另一个要求……这必须跨平台运行:windows 和 *nix 都一样。据我了解, fork 在 Windows 中并不能真正工作。还是您在谈论不同的管道机制?
【解决方案3】:

有很多选择。使用 select() 更改打印默认的文件句柄。或配合 STDOUT。或者重新打开它。或者对其应用 IO 层。

【讨论】:

    【解决方案4】:

    这不是您问题的答案,但您应该能够采用自己使用的逻辑。如果没有,也许其他人会觉得它有用。

    在格式错误的标头发生之前捕获它们...

    package PsychicSTDOUT;
    use strict;
    
    my $c = 0;
    my $malformed_header = 0;
    open(TRUE_STDOUT, '>', '/dev/stdout');
    tie *STDOUT, __PACKAGE__, (*STDOUT);
    
    sub TIEHANDLE {
        my $class = shift;
        my $handles = [@_];
        bless $handles, $class;
        return $handles;
    }
    
    sub PRINT {
        my $class = shift;
        if (!$c++ && @_[0] !~ /^content-type/i) {
            my (undef, $file, $line) = caller;
            print STDERR "Missing content-type in $file at line $line!!\n";
            $malformed_header = 1;
        }
        return 0 if ($malformed_header);
        return print TRUE_STDOUT @_;
    }
    1;
    

    用法:

    use PsychicSTDOUT;
    print "content-type: text/html\n\n"; #try commenting out this line
    print "<html>\n";
    print "</html>\n";
    

    【讨论】:

      【解决方案5】:

      您可以从包装脚本运行脚本,该脚本捕获原始脚本的标准输出并将输出写入合理的位置。

      【讨论】:

      • 不幸的是,这违反了“脚本必须像以前一样运行”的要求。用户不必做任何不同的事情来获取此日志信息。
      • 它没有违反任何规定。没有什么说当你输入 'perl' 时执行的是真正的 perl。
      • 从一般实现的角度来看是正确的;包装脚本是一种可能的解决方案。但是,要求用户运行另一个脚本只是为了运行他们的脚本,这并不是一种适用于这种情况的解决方案——命令行、perl 发行版和控制台输出必须保持不变。
      • 而且它必须只影响使用这个特定库的脚本。如果我改变了 shell 认为的“perl”,它会影响其他不需要公共库的脚本。这也不是该应用程序可接受的解决方案。
      • 但根据您的情况,可能是这样。 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-06
      • 2021-07-06
      • 2021-12-15
      • 2021-12-25
      • 2022-12-17
      • 1970-01-01
      相关资源
      最近更新 更多