log4perl: группировка сообщений

Я использую log4perl для регистрации сообщений из сценария perl. С mwe.pl, как показано ниже, я получаю следующий (желаемый) вывод в test.log

INFO: some information
      more information

Моя текущая реализация использует:

my $logmessage = "some information\n";
$logmessage .= "more information";
$logger->info($logmessage);

В частности, обратите внимание, что я указал разрыв строки вручную, используя \n, чего я хотел бы избежать.

Есть ли способ, которым я могу добиться желаемого результата (test.log), без необходимости заполнять входные данные журнала?

mwe.pl

#!/usr/bin/env perl
use strict;
use warnings;
use Log::Log4perl qw(get_logger :levels);

my $logger = get_logger();
$logger->level($INFO);

my $layout = Log::Log4perl::Layout::PatternLayout->new("%p: %m{indent}%n");
my $appender = Log::Log4perl::Appender->new(
    "Log::Dispatch::File",
    filename => "test.log",
    mode     => "write",
);

$appender->layout($layout);
$logger->add_appender($appender);

my $logmessage = "some information\n";
$logmessage .= "more information";
$logger->info($logmessage);

exit(0);

person cmhughes    schedule 17.09.2017    source источник
comment
почему вы не хотите иметь INFO в каждой строке? Мне нужно было бы взглянуть на внутреннюю реализацию, но, возможно, вы можете создать что-то, что обертывает ->info, чтобы вести себя так, как вы хотите, когда вы передаете список. Тогда у вас может быть @logmessages вместо $logmessage.   -  person simbabque    schedule 17.09.2017
comment
С другой стороны, это нарушило бы поведение по умолчанию, согласно которому каждый аргумент представляет собой одно целое сообщение.   -  person simbabque    schedule 17.09.2017
comment
@simbabque есть «группы» сообщений журнала, которые, естественно, идут вместе и предназначены для информации. Мне легче читать, если эти группы отформатированы так, как я описал выше. если в каждой строке есть INFO или WARN или что-то еще, то я игнорирую их, что (для меня) лишает их значения   -  person cmhughes    schedule 17.09.2017
comment
Почему бы просто не сделать свою собственную оболочку, чтобы скрыть ту часть новой строки, которая вам не нравится?   -  person simbabque    schedule 17.09.2017
comment
Хотелось бы увидеть такую ​​обертку   -  person cmhughes    schedule 17.09.2017
comment
Как вы решаете, какие сообщения попадают в группу из всех возможных info?   -  person zdim    schedule 19.09.2017
comment
Я не уверен, что вы бы подумали о снятии строительных лесов. (В конце концов, необходимы какие-то средства сообщения о том, что два сообщения связаны.) Вы просите что-то вроде $logger->info("some information"); $logger->moreinfo("more information");?   -  person ikegami    schedule 19.09.2017
comment
Я думаю что-то вроде logger-›info(...,heading) для строк с INFO и logger-›info(...) для не-заголовков. Или, возможно, logger-›info-header и logger-›info.   -  person cmhughes    schedule 19.09.2017
comment
Вам нужно отметить меня (@ikegami), чтобы я получил уведомление.   -  person ikegami    schedule 21.09.2017
comment
Ваша оболочка: когда вы получаете вызов заголовка, очищайте свой буфер (соединяйте содержимое с помощью новых строк, затем отправьте буфер в фактический регистратор), затем добавьте новые данные в буфер. Когда вы получаете вызов без заголовка, добавьте новые данные в буфер. Смыв при разрушении.   -  person ikegami    schedule 21.09.2017
comment
@ikegami звучит как хороший подход, мне, конечно, было бы интересно на это посмотреть. Мне нравится ответ zdim, и я использую его, например, $logger->info("*...") для «заголовков» и $logger->info("...") для «не заголовков» (соответственно я скорректировал анонимную подпрограмму)   -  person cmhughes    schedule 21.09.2017
comment
Re Мне, конечно, было бы интересно на это посмотреть., хм? Я только что показал это.   -  person ikegami    schedule 21.09.2017
comment
Re Мне нравится ответ zdim, да, наверное, проще. Я просто отвечал на ваш вопрос о том, как написать обертку.   -  person ikegami    schedule 21.09.2017


Ответы (1)


Один из способов — добавить пользовательские "cspecs" в файл PatternLayout. Использование API

Log::Log4perl::Layout::PatternLayout::add_global_cspec(
    'A', sub { ... }
);                    # can now use %A

где это должно произойти перед вызовом new, который затем может использовать спецификатор %A.

Это можно настроить в конфигурации вместо этого, как показано в связанных документах. Или метод add_global_cspec можно вызвать для объекта $layout (но я не смог понять интерфейс.)

Анонимный саб получает

($layout, $message, $category, $priority, $caller_level)  

layout: объект PatternLayout, вызвавший его
message: сообщение журнала (%m)
категория: например. бакалея.напитки.взрослое.пиво.шлиц
приоритет: напр. DEBUG|WARN|INFO|ERROR|FATAL
caller_level: сколько уровней резервного копирования стека вызовов вам нужно пройти, чтобы найти вызывающего абонента

что можно использовать для реализации критериев форматирования отпечатков.

Вот простой пример пользовательского указания всего формата

use strict;
use warnings;
use Log::Log4perl qw(get_logger :levels);

my $logger = get_logger();
$logger->level($INFO);

Log::Log4perl::Layout::PatternLayout::add_global_cspec( 
    'A', sub { return ( 
        $_[1] !~ /^more/                 # /^more/ taken to indicate 
           ?  "$_[3]: "                  # the continuation criterion,
           :  ' ' x length $_[3] . '  '  # or start with 'INFO: '
    ) . $_[1]
});

my $layout = Log::Log4perl::Layout::PatternLayout->new("%A%n");

my $appender = Log::Log4perl::Appender->new(
    "Log::Dispatch::File",
    filename => "new_test.log",
    mode     => "write",
);

$logger->info('some info');
$logger->info('more info');

$logger->info('info');
$logger->info('more and more info');

который печатает

INFO: some info
      more info
INFO: info
      more and more info

Такой пользовательский спецификатор, конечно, можно комбинировать с предоставленными.

Поскольку список в info(...) объединяется регистратором в строку, которая передается в приложение (я), мы можем выбрать заголовок в вызывающем объекте с очевидным интерфейсом.

$logger->info('*', "... message ...");  # * for heading (add INFO:)

где первая строка выше — это то, что ищет регулярное выражение в нашем cspec.

Это форматирует каждую строку журнала на основе ее содержимого. Более универсальный вариант — напишите свой собственный аппендер (FAQ), который представляет собой довольно простой класс, в котором вы можете хранить строки и управлять ими по мере необходимости. См. пример группирование сообщений (FAQ).

Наконец, правильный способ настроить выбор сообщений — добавить категория. Затем вы можете получить новый регистратор и настроить его для отображения INFO: (для строки заголовка), в то время как остальные сообщения в этой группе отправляются другим регистратором, настроенным так, чтобы он не отображался. См. этот пост для простого примера.

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

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

person zdim    schedule 19.09.2017
comment
Большое спасибо, мне это очень нравится. За ссылки тоже спасибо, очень информативно. Я подожду несколько дней, прежде чем принять и присудить награду, чтобы максимизировать рекламу. - person cmhughes; 20.09.2017
comment
@cmhughes Приятно слышать, что это помогает, спасибо, что сказали это. Я только что заметил ваши комментарии и добавил идею интерфейса для этого. Спасибо за атрибуцию. - person zdim; 24.09.2017
comment
@cmhughes Я добавил еще один подход, который на самом деле является правильным способом точной настройки таких вещей. (Вероятно, это слишком тяжело для этой цели, если только в действительности нет большего.) - person zdim; 26.09.2017