题
这是一个场景。您有大量遗留脚本,全部使用通用库。所述脚本使用“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 很糟糕”或“这个例子有缺陷 x y 和 z”等问题......这是为了简洁和清晰,大大简化了。)
基本问题归结为捕获和处理传递给 print 的数据(或任何 Perl 内置函数,按照这些推理)。是否可以?有什么办法可以干干净净吗?是否有任何日志记录模块可以让您执行此操作?或者它是像瘟疫一样应该避免的东西,我应该放弃捕获和处理打印输出吗?
额外的:这必须跨平台运行 - windows 和 *nix 都一样。运行脚本的过程必须保持不变,脚本的输出也必须保持不变。
附加附加:codelogic 的答案的评论中提出了一个有趣的建议:
你可以子类化 http://perldoc.perl.org/IO/Handle.html 并创建自己的文件句柄,该文件将完成日志记录工作。——卡米尔·基西尔
这可能会做到这一点,但有两个注意事项:
1)我需要一种方法将此功能导出给使用公共库的任何人。它必须自动应用于 STDOUT,也可能应用于 STDERR。
2) IO::句柄 文档说你不能对它进行子类化,到目前为止我的尝试没有结果。是否需要任何特殊的东西才能使子类化 IO::Handle 工作?标准的“use base 'IO::Handle'”然后覆盖 new/print 方法似乎什么也没做。
最终编辑:看起来 IO::Handle 是死胡同,但 Tie::Handle 可以做到。感谢所有的建议;他们都非常好。我将尝试 Tie::Handle 路线。如果它引起问题我会回来的!
附录:请注意,经过一番处理后,我发现如果您不做任何棘手的事情,Tie::Handle 就可以工作。如果您将 IO::Handle 的任何功能与绑定的 STDOUT 或 STDERR 一起使用,那么要让它们可靠地工作基本上是一个冒险 - 我找不到一种方法来让 IO::Handle 的自动刷新方法在我的绑定上工作处理。如果我在绑手柄之前启用自动冲洗,它就会起作用。如果这对您有用,那么 Tie::Handle 路线可能是可以接受的。
解决方案
您可以覆盖许多内置函数(请参阅 perlsub)。然而, print
是不能以这种方式工作的内置函数之一。超越的困难 print
详细介绍在这 erlmonk 的主题.
然而,你 能
- 创建一个包
- 系上一个把手
- 选择该手柄。
现在,几个人已经给出了基本框架,但它的工作原理是这样的:
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, @_ ));
}
看 领带::手柄 您可以覆盖 STDOUT 的行为。
其他提示
您可以使用Perl的选择重定向STDOUT。
open my $fh, ">log.txt";
print "test1\n";
my $current_fh = select $fh;
print "test2\n";
select $current_fh;
print "test3\n";
在文件句柄可以是任何东西,甚至管到另一个过程,后期处理您的日志信息。
PerlIO的::三通在在 PerlIO的::的Util 模块似乎可以让你“三通”的文件句柄到多个目的地(例如,日志处理器和STDOUT)。
的输出的选择很多。使用select()来更改打印默认的文件句柄。或配合STDOUT。或重新打开它。或施加IO层到它。
这是不回答你的问题,但你应该能够通过逻辑供自己使用。如果不是这样,也许别人会觉得它有用。
追赶标题格式错误,防患于未然......
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";
您可以从捕获原始脚本的标准输出,并写入输出某处明智的包装脚本运行的脚本。