Как я могу подключиться к печати Perl? - PullRequest
19 голосов
/ 23 декабря 2008

Вот сценарий. У вас есть большое количество устаревших сценариев, и все они используют общую библиотеку. Указанные сценарии используют оператор 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 sucks» или «в этом примере есть недостатки 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, по сути, это просто дрянь, чтобы они работали надежно - я не смог найти способ заставить метод autoflush IO :: Handle работать с моими связанными справиться. Если бы я включил автозапуск до того, как связал ручку, это сработало бы. Если это работает для вас, маршрут Tie :: Handle может быть приемлемым.

Ответы [ 5 ]

24 голосов
/ 23 декабря 2008

Существует ряд встроенных модулей, которые вы можете переопределить (см. 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.

9 голосов
/ 23 декабря 2008

Вы можете использовать Perl's 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) .

7 голосов
/ 23 декабря 2008

Много вариантов. Используйте select (), чтобы изменить дескриптор файла, для которого по умолчанию используется печать. Или связать STDOUT. Или снова откройте его. Или примените к нему слой ввода-вывода.

3 голосов
/ 08 июня 2011

Это не ответ на ваш вопрос, но вы должны быть в состоянии принять логику для собственного использования. Если нет, может быть, кто-то найдет это полезным.

Поймать искаженные заголовки, прежде чем они произойдут ...

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";
0 голосов
/ 23 декабря 2008

Вы можете запустить сценарий из сценария-обертки, который захватывает стандартный вывод исходного сценария и записывает вывод в разумное место.

...