我如何勾选Perl的印刷品?

时间:2008-12-22 23:06:08

标签: perl printing hook tie

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

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

作为此处理的示例,库应仅记录包含“警告”,“错误”,“通知”或“注意”字样的打印行。下面的极其简单和受控的示例代码(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”等问题。为了简洁和清晰起见,这大大简化了。)

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

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

补充附加:在codelogic的回答中提出了一个有趣的建议:

  

你可以继承http://perldoc.perl.org/IO/Handle.html并创建你的     自己的文件句柄,它将执行日志记录工作。 - Kamil Kisiel

这可能会这样做,有两点需要注意:

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

2)the IO::Handle文档说你不能将它子类化,到目前为止我的尝试都没有结果。是否有任何特殊需要使子类化IO :: Handle工作?标准的'使用基础'IO :: Handle'然后覆盖新的/打印方法似乎什么都不做。

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

附录:请注意,在使用了这个之后,我发现如果你不做任何棘手的事情,Tie :: Handle会起作用。如果您使用IO :: Handle的任何功能与绑定的STDOUT或STDERR,它基本上是一个让它们可靠地工作的废话 - 我找不到一种方法来获得IO :: Handle的autoflush方法来处理我的绑定处理。如果我在绑上手柄之前启用了autoflush,它就能正常工作。如果这对您有用,可以接受Tie :: Handle路线。

5 个答案:

答案 0 :(得分:24)

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

但是,可以

  1. 创建包
  2. 拉手
  3. 选择此句柄。
  4. 现在,有几个人已经给出了基本框架,但它有点像这样:

    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行为的内容。

答案 1 :(得分:9)

您可以使用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::tee模块中的

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

答案 2 :(得分:7)

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

答案 3 :(得分:3)

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

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

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";

答案 4 :(得分:-1)

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