У меня есть сценарий perl, который записывает сообщения в STDOUT и STDERR (через операторы print / croak), но я также перенаправляю STDOUT и STDERR в файл журнала:
File::Tee::tee STDOUT, ">>", "$logFile" ;
File::Tee::tee STDERR, ">>", "$logFile" ;
Теперь выходной файл журнала сообщения от STDOUT и STDERR отображаются не по порядку. Также фактический выходной сигнал на клемме также не в порядке. Я попытался очистить буферы (как рекомендовано здесь: https://perl.plover.com/FAQs/Buffering.html), но это не помогает:
select(STDERR) ;
$| = 1 ;
select(STDOUT) ;
$| = 1 ;
Кто-нибудь знает, что я должен сделать, чтобы увидеть вывод в порядке (Я также попытался дополнительно очистить дескриптор файла, соответствующий $ logfile, но он все тот же)?
РЕДАКТИРОВАТЬ:
Спасибо всем, кто ответил. Большая часть дискуссии по этому поводу закончилась комментариями, поэтому я собираюсь перечислить несколько вещей, которые я попробовал, основываясь на отзывах всех вас.
- Я уже сбрасывал STDOUT и STDERR, прежде чем использовал File :: Tee. Как и подозревал @jimtut, виновником был File :: Tee - удаление его восстановило порядок на консоли. Но я действительно хотел перенаправить STDOUT и STDERR.
- @ mob предложил вместо этого использовать IO :: Tee, но я не совсем понял, как сделать так, чтобы они работали так, как я хочу в своем коде.
- @ briandfoy указал, что не существует надежного способа обеспечить отображение 2 отдельных файловых дескрипторов в правильном порядке в реальном времени, а также предложил использовать процедуру записи в журнал, которая является единственным местом, которое может записывать в STDOUT / STDERR. , @zimd также отметил, что File :: Tee использует fork, который является сердцем проблемы, поскольку 2 процесса не могут гарантировать какой-либо порядок вывода.
- Поскольку виноват File :: Tee, я попытался удалить это из кода. Я обновил свою функцию ведения журнала, чтобы печатать в STDOUT / STDERR, а также дополнительно печатать в дескриптор файла $ log. Далее для сбора предупреждений в журнале я сделал следующее:
sub warning_handler {
my $msg = $_[0] ;
print STDERR $msg ;
print $log $msg if defined $log ;
}
$SIG{__WARN__} = \&warning_handler ;
Это отлично работало для всего кода под моим контролем. Теперь все печаталось по порядку как на консоли, так и в лог-файле. Однако я понял, что не могу использовать это решение, так как я также вызывал чьи-то пакеты perl для некоторой функциональности, и, очевидно, я не мог перехватить print / croak et c, которая писала в STDOUT / STDERR в пределах 'off the полочный пакет. Так что сейчас у меня нет хорошего решения. Однако я подозреваю, что если я найду способ перехватить STDOUT / STDERR в perl, я смогу получить то, что мне нужно.
EDIT2: я добавил свой собственный ответ, который, вероятно, наиболее близок к решению проблемы, изменив решение моба использовать IO :: Tee вместо File :: Tee, но даже при этом пропускаются некоторые сообщения (хотя это исправляет порядок).
EDIT3: наконец-то найдено «решение»
use IO::Tee ;
use Capture::Tiny qw(capture);
...
...
select(STDERR) ;
$| = 1 ;
select(STDOUT) ;
$| = 1 ;
open (my $log, ">", $logfilename) ;
*REALSTDOUT = *STDOUT ;
*REALSTDERR = *STDERR ;
*STDOUT = IO::Tee->new(\*REALSTDOUT, $log);
*STDERR = IO::Tee->new(\*REALSTDERR, $log);
# Regular Perl code here which sends output to STDOUT/STDERR
...
...
# system calls / calls to .so needs to be catpured
&log_streams(sub { &some_func_which_calls_shared_object() ; }) ;
sub log_streams {
my ($cr, @args) = @_; # code reference, with its arguments
my ($out, $err, $exit) = capture { $cr->(@args) };
if ($out) {
print STDOUT $out;
}
if ($err) {
print STDERR $err;
}
}
Использование IO :: Tee гарантирует, что все сгенерированные perl выходные данные для консоли также go для файла журнала, и это происходит немедленно, обновляя журнал и консоль в реальном времени. Поскольку IO :: Tee меняет значение файловых дескрипторов STDOUT / STDERR, чтобы теперь ссылаться на дескрипторы teed, он может перехватывать только stdio из операторов perl, он пропускает вызовы sys, поскольку они пропускают дескрипторы perl STDOUT / STDERR. Таким образом, мы записываем выходные данные системного вызова и затем используем процедуру log_streams, чтобы переслать ее в потоки STDOUT / STDERR с псевдонимами. Это создает задержку в сгенерированном системном вызове выводе, отображаемом в журнале / терминале, но нет задержки для perl сгенерированного вывода - т.е. лучшего из обоих миров. Обратите внимание, что порядок stderr и stdout, генерируемый вызовом подпрограммы some_func_which_calls_shared_object, не сохраняется, поскольку в подпрограмме log_streams мы сначала печатаем в STDOUT, а затем в STDERR - до тех пор, пока системный вызов не атомом c и не сделать многое с точки зрения чередования сообщений stdout / stderr, мы должны быть в порядке. Цените решения от briandfoy, mob и zimd, ответы которых я объединил, чтобы прийти к этому решению! Никогда не думал, что потребуется 1048 * для прохождения этой детали, что кажется очень простой проблемой.