21

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

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

作为这种处理的一个例子,库应该只记录包含单词“警告”、“错误”、“通知”或“注意”的打印行。下面的非常琐碎和人为的示例代码(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的回答的评论中提出的一个有趣的建议:

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

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

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

2) 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 路线可能是可以接受的。

4

5 回答 5

25

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

但是,您可以

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

于 2008-12-23T04:57:59.110 回答
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 ::Util模块中的PerlIO ::tee似乎允许您将文件句柄的输出“tee”到多个目的地(例如日志处理器和 STDOUT)。

于 2008-12-22T23:29:46.637 回答
7

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

于 2008-12-23T01:57:32.407 回答
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";
于 2011-06-08T15:51:04.003 回答
-1

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

于 2008-12-22T23:37:51.643 回答