Выпуск 27. Май 2015
← От редактора | Содержание | Операторы Perl 6. Часть 1 →Отладка приложений на AnyEvent
Отладка асинхронных приложений часто затруднена из-за нелинейного характера выполняемого кода, когда ошибки проявляются под нагрузкой или в каких-то исключительных трудновоспроизводимых ситуациях. Привычные инструменты могут быть неудобны и неинформативны. Если приложение построено на основе AnyEvent, то для него уже существуют готовые рецепты отладки.
AnyEvent::Log
Самое эффективное средство отладки — по-прежнему тщательное размышление с правильно расставленными операторами print.
– Брайан Керниган, «Unix for Beginners» (1979)
AnyEvent::Log — это фреймворк для ведения журнала событий. Основное его достоинство в том, что он поставляется вместе с дистрибутивом AnyEvent и может лениво загружаться, как только вы первый раз вызываете метод AE::log
в своём коде. Кроме того, сам AnyEvent также начинает вести журналирование своих внутренних событий, позволяя глубже понимать происходящее. По сути, AnyEvent::Log
просто даёт публичный доступ к внутренним механизмам ведения логов, применяющимся внутри AnyEvent.
Концепция журналирования AnyEvent::Log
довольно запутанная, но есть много общего с привычными Log::Dispatch
и Log::Log4perl
, например, контекст, уровни детализации и контейнеры для вывода сообщений. Начать успешно использовать AnyEvent::Log
можно опустив практически все тонкости его организации.
use AnyEvent;
AE::log trace => "прочитан фрагмент статьи в 200 байт";
AE::log debug => "функция total_readed вернула 500";
AE::log infо => "читаю статью";
AE::log note => "функция understood_readed вернула 0, пробую еще раз";
AE::log warn => "не понимаю, что тут написано"
AE::log error => "строкой выше пропущена точка с запятой";
AE::log critical => "место для записи закончилось, сохраняю в памяти";
AE::log alert => "больше свободной памяти нет";
AE::log fatal => "в статье не найдены котики, продолжать чтение невозможно";
В данном примере сообщения разделяются по уровню важности или детализации действий программы, каждый уровень имеет своё цифровое значение — чем выше значение, тем более высокий уровень детализации:
- 9 — trace — трассировочные сообщения, подразумевающие обильный вывод для каждого шага программы;
- 8 — debug — отладочные сообщения, применяемые, как правило, для подробного вывода входных параметров функций или её результатов;
- 7 — info — информационные сообщения о нормальном течении программы, например, для веб-сервера это сообщения о доступе к ресурсу в access.log;
- 6 — note — сообщение о необычном или граничном условии;
- 5 — warn — предупреждение, не обязательно ошибка, но, вполне вероятно, проблема;
- 4 — error — некритичная ошибка в программе;
- 3 — critical — критическая ошибка, отказ резервирования;
- 2 — alert — критическая ошибка, отказ основной системы;
- 1 — fatal — фатальная ошибка, дальнейшая работа программы невозможна.
По умолчанию AnyEvent устанавливает уровень детализации error, это означает, что выводиться будут сообщения с уровнями с 1 по 4 (alert — error), а все сообщения с большим уровнем подробности отсекаются.
Регулировать уровень детализации можно с помощью переменной окружения PERL_ANYEVENT_VERBOSE
(или более короткий алиас AE_VERBOSE
), например, задать самый подробный уровень trace:
$ export PERL_ANYEVENT_VERBOSE=9
$ ./foo.pl
По умолчанию весь вывод идёт на STDERR
. Это можно изменить через переменную окружения PERL_ANYEVENT_LOG
(или AE_LOG
), например, задав имя файла, куда направлять весь вывод:
$ export PERL_ANYEVENT_LOG=log=file=/some/path
Или, например, направить весь вывод в syslog:
$ export PERL_ANYEVENT_LOG=log=syslog
Иерархия контекстов AnyEvent::Log
Мы рассмотрели самый простой способ использования AnyEvent::Log
, в котором уровень детализации и направление вывода задаётся глобально для всего приложения. В некоторых случаях требуется более тонкая и детальная настройка. AnyEvent::Log
предоставляет подобную возможность, но, чтобы разобрать дальнейшие рецепты, требуется понимание контекста журналирования и иерархии контекстов.
Каждое сообщение ассоциируется со своим контекстом журналирования. AnyEvent::Log
создаёт контекст на основе имени пакета вызывающего кода. Например,
package Foo::Bar;
use AnyEvent;
sub baz {
AE::log error => "вызов из baz пакета Foo::Bar"
}
package main;
use AnyEvent;
AE::log error => "вызов из main";
Foo::Bar::baz
Получим следующий вывод:
2015-04-27 19:29:30.000000 +0300 error main: вызов из main
2015-04-27 19:29:30.000000 +0300 error Foo::Bar: вызов из baz пакета Foo::Bar
Каждое сообщение имеет метку времени и значение уровня отладки. Непосредственно само сообщение предваряет заголовок текущего контекста, которым в данном примере является имя пакета. Таким образом, первое сообщение из основного пакета main
выполняется из контекста main
, а вызов log
из функции baz
находится в контексте Foo::Bar
.
Каждый контекст журналирования выполняет три основные функции:
- Фильтрация. Каждый контекст устанавливает уровень детализации или, иначе, маску журнала. Сообщения, которые не попадают в диапазон уровней, игнорируются (маскируются).
- Запись. Для записи сообщений каждый контекст создаёт две функции:
- функцию форматирования, которой передаётся временная метка, контекст, уровень и само сообщение. Данная функция отвечает непосредственно за форматирование строки сообщения;
- функцию записи, которая отвечает за запись сообщения и его дальнейшее распространение.
- Распространение. Каждый контекст может иметь несколько подчинённых контекстов. Если сообщение не было отфильтровано на этапе фильтрации и не было «поглощено» на шаге записи, то в этом случае контекст передаёт сообщение всем подчинённым контекстам.
По умолчанию все контексты имеют полный набор уровней детализации (т.е. ничего не фильтруют), но у них отключены функции форматирования и записи, поэтому по умолчанию все контексты просто передают сообщения дальше подчинённым контекстам.
Каждому контексту привязывается ровно один подчинённый контекст — это контекст, который имеет имя пакета текущего контекста за исключением последнего компонента. Например, в указанном выше примере создаётся контекст Foo::Bar
, к которому подключается подчинённый контекст Foo
. Если же контекст имеет только один компонент в имени пакета, то по умолчанию подчинённым контекстом становится специальный контекст $AnyEvent::Log::COLLECT
, который создаёт AnyEvent::Log
.
Всего AnyEvent::Log
создаёт три специальных контекста:
$AnyEvent::Log::COLLECT
— это глобальный контекст, к которому сходится вся иерархия контекстов пакетов.$AnyEvent::Log::FILTER
— контекст, который становится подчинённым контекста$AnyEvent::Log::COLLECT
и его основная задача — фильтровать все сообщения, уровень которых выше, чем установленный в переменнойPERL_ANYEVENT_VERBOSE
.$AnyEvent::Log::LOG
— финальный контекст, который становится подчинённым$AnyEvent::Log::FILTER
и выводит все сообщения с помощью функцииwarn
.
Если взять описанный выше пример, то получаем такую иерархию контекстов:
.----------.
| Foo::Bar |---.
'----------' |
v
.--------. .--------.
| Foo |--------.--------| main |
'--------' | '--------'
v
.-------------------------.
| $AnyEvent::Log::COLLECT |
'-------------------------'
|
v
.-------------------------.
| $AnyEvent::Log::FILTER |
'-------------------------'
|
v
.-------------------------.
| $AnyEvent::Log::LOG |
'-------------------------'
Помимо контекстов журналирования, входящих в подобную иерархию, можно создавать анонимные контексты, которые не имеют подчинённых контекстов. Такие объекты удаляются, как только на них перестают ссылаться.
Все неанонимные контексты хранятся в публичном хеше %AnyEvent::Log::CTX
, где в качестве ключа используется имя пакета. При желании их всегда можно проинспектировать.
Для чего нужна подобная иерархия? Она позволяет гибко настраивать поведение каждого контекста, что будет наглядно продемонстрировано далее.
Глобальный уровень детализации
Чтобы установить глобальный уровень детализации для всех сообщений, достаточно задать уровень детализации контекста $AnyEvent::Log::FILTER
:
# Установить глобальный уровень детализации в info
$AnyEvent::Log::FILTER->level ("info");
Разумеется, то же самое можно было сделать в контексте COLLECT
, так как через него проходят все сообщения, но делать этого не рекомендуется, поскольку задача COLLECT
— сбор сообщений, которые потом можно распределить на другие контексты.
Запись сообщений в файл вместо вывода на STDERR
Как уже было сказано, функция-колбек для записи сообщений по умолчанию определена только в $AnyEvent::Log::LOG, который выводит их на STDERR. Таким образом, чтобы переопределить эту запись, нужно задать запись в файл, например так:
# Специальный метод для установки записи в файл
$AnyEvent::Log::LOG->log_to_file ($path);
Опять же, то же самое можно было бы задать на уровне $AnyEvent::Log::FILTER
, но это некорректно, поскольку задача контекста фильтрации в другом. Кроме того, в этом случае сообщения по-прежнему бы выводились на STDERR
, так как метод записи в $AnyEvent::Log::LOG
не был бы переопределён.
Различные уровни детализации: глобальный и заданного пакета
Добавим к нашему первоначальному примеру следующий фрагмент:
# Получить контекст пакета 'Foo'
my $foo_ctx = AnyEvent::Log::ctx('Foo');
# Задать уровень info
$foo_ctx->level('info');
# Функция логирования
$foo_ctx->log_cb( sub {
warn shift;
# 0 - разрешает дальнейшее распространение
# сообщения по иерархии
# 1 - запрещает дальнейшее распространение
# сообщения по иерархии
0;
});
Теперь журналирование для всех пакетов под иерархией пакета ‘Foo’ будет вестись на уровне info, в то время как глобальный уровень останется таким, как задан в переменной окружения AE_VERBOSE
. Нужно обязательно задать функцию-колбек для записи, так как по умолчанию у всех контекстов эта функция не установлена.
Дублирование всех сообщений в файл
Предположим, что требуется дополнительно сохранять все поступающие сообщения с любым уровнем детализации в файл независимо от глобального фильтра уровня детализации. Поскольку требуется получать все сообщения иерархии, то удобно создать новый анонимный контекст и сделать его подчинённым для $AnyEvent::Log::COLLECT
: этот контекст получает все сообщения от иерархии, и они ещё не были отфильтрованы глобальным фильтром в контексте $AnyEvent::Log::FILTER
.
# анонимный контекст
my $annon_ctx = AnyEvent::Log::Ctx->new( log_to_file => $path );
# подключаем его как подчинённый контекст к $AnyEvent::Log::COLLECT
$AnyEvent::Log::COLLECT->attach( $annon_ctx );
Оптимизации
После того как стала понятна концепция журналирования в AnyEvent::Log
, можно перейти к некоторым полезным фокусам с оптимизацией. Например, оптимизация по скорости выполнения.
Как правило, сообщения уровней trace и debug могут вызываться очень часто и могут ощутимо замедлять работу приложения, когда оно работает в нормальном режиме без глубокого уровня детализации, из-за накладных расходов на вызов функции. Для этого случая предусмотрена следующая оптимизация:
use AnyEvent::Log;
# Создаём трассировщик, переменная $trace
# хранит его состояние (активен/неактивен)
my $tracer = AnyEvent::Log::logger trace => \my $trace;
# Если нет режима trace, то $trace -- это ложь
# и функция не вызывается
$tracer->("trace message") if $trace;
# или так
$trace and $tracer->("trace message");
Тут важно отметить, что переменная $trace
передаётся как ссылка. Таким образом, её значение может устанавливаться и обновляться в случае, если уровень детализации меняется по ходу выполнения программы.
Также можно использовать ленивое создание сообщений:
use AnyEvent::Log;
use Data::Dumper;
AE::log debug => sub {
Dumper $some_big_structure
};
В данном примере функция AE::log
будет вызвана при любом уровне детализации, но функция, возвращающая сообщение, вызывающая Dumper
большого объекта, будет вызываться только в режиме debug, что существенно ускоряет работу при низком уровне детализации.
AnyEvent::Strict
Модуль AnyEvent::Strict
при загрузке включает строгую проверку параметров, передаваемую функциям AnyEvent, что несколько замедляет работу, но позволяет выявить типичные ошибки. Кроме того все функции-колбеки оборачиваются в специальную функцию, которая проверяет, что вызываемый колбек не модифицирует переменную $_
, что явно свидетельствует об ошибке.
Как правило, включать AnyEvent::Strict
в коде не обязательно, достаточно установить переменную окружения PERL_ANYEVENT_STRICT
при запуске AnyEvent-приложения. Это позволит быстрее отловить некоторые простые ошибки и избавит от рутинной отладки.
AnyEvent::Debug
В случае, если журнал приложения и строгий режим проверки параметров не дают результатов при поиске ошибок, то со следующим этапом отладки может помочь AnyEvent::Debug
.
Основа AnyEvent::Debug
— это интерактивная командная строка, встраиваемая в приложение. После запуска приложения вы в любой момент можете буквально «зателнетиться» в работающее приложение и получить возможность запускать различные команды, которые будут выполняться в контексте запущенного кода. В простейшем случае можно проинспектировать значения глобальных переменных.
Итак, в начало отлаживаемой программы требуется добавить следующие строки:
use AnyEvent::Debug;
my $shell = AnyEvent::Debug::shell "unix/", "/path/to/sock";
Функция AnyEvent::Debug::shell
в данном случае создаёт файловый сокет. Вместо файлового сокета можно указать в параметрах IP-адрес и порт:
my $shell = AnyEvent::Debug::shell '127.0.0.1', 2222;
Но, поскольку никакой авторизации для доступа в командную строку нет, то это может быть небезопасно, и в общем случае рекомендуется использовать файловые сокеты, доступ к которым можно легко ограничивать при помощи стандартных прав файлового доступа.
После запуска приложения создаётся сокет, к которому можно подключиться с помощью стандартных утилит. В случае файлового сокета можно использовать утилиту socat. Например:
$ socat readline,history=.ae-debug-history unix:/path/to/sock
В данном примере socat откроет сокет /path/to/sock
и при этом загрузит библиотеку GNU readline для работы со стандартным вводом. Параметр history позволяет указать файл, в котором будет сохраняться история команд, набираемых в командной строке, что может быть очень удобно при частом подключении.
Команды оболочки AnyEvent::Debug
Интерактивная строка является обычным REPL (Read-Eval-Print Loop), позволяя выполнять Perl-код в контексте приложения. Кроме того, доступно несколько встроенных команд, которые позволяют работать с обработчиками событий.
help
Справка по доступным командам.
v [level]
При подключении командной оболочки, создаётся контекст журналирования $LOGGER
, который подключается к контексту $AnyEvent::Log::COLLECT
приложения, что позволяет захватывать все отладочные сообщения приложения и выводить их прямо в консоли.
Команда v
позволяет регулировать уровень детализации вывода логов контекста $LOGGER
в диапазоне от 0 до 9.
wr [level]
Любое приложение, работающие на AnyEvent создаёт обработчики событий. Команда wr
позволяет включать/выключать режим добавления специальной обёртки для вновь добавляемых обработчиков событий. Возможны три значения режима:
- 0 — Отключает режим обёртки.
- 1 — Включает режим обёртки. Теперь, когда в приложении происходит создание нового обработчика, создаётся объект класса
AnyEvent::Debug::Wrapped
, который сохраняет информацию о месте в программе, где был создан обработчик, оборачивается функция-колбек обработчика, добавляя трассировочный вывод. Также добавляется счётчик, который считает количество вызовов функции-колбека. - 2 — То же самое, что и уровень 1, только дополнительно сохраняет полный бектрейс в точке создания обработчика в коде. Это существенно замедляет создание обработчиков, но даёт детальную информацию о стеке вызовов.
Кроме того, включить режим создания обёртки можно и при запуске приложения, установив переменную окружения PERL_ANYEVENT_DEBUG_WRAP
.
wl 'regex'
Команда wl
позволяет вывести список обёрток обработчиков. Если регулярное выражение опущено, то выводится весь список.
i id
Команда i
позволяет вывести детальную информацию об обработчике по его уникальному номеру.
t/u [id]
Команда t
— включает режим трассировки всех вновь создаваемых обработчиков (по умолчанию), а команда u
— отключает режим трассировки. Также, если указан номер, то можно включать/выключать трассировку индивидуально для каждого обработчика.
coro [command]
Команда позволяет переключиться в оболочку Coro::Debug
, если она доступна, и выполнить её команды. Полезно, если приложение также использует модуль Coro
.
Пример отладки
Рассмотрим пример отладки веб-сервера Twiggy, который является приложением, работающим на AnyEvent.
С помощью утилиты plackup
мы запускаем тривиальное веб-приложение и создаём файловый сокет twiggy.sock
для отладочного подключения.
$ PERL_ANYEVENT_DEBUG_WRAP=2 \
plackup -s Twiggy \
-MAnyEvent::Debug \
-e 'AnyEvent::Debug::shell "unix/", "twiggy.sock";
sub { [ 200, [], ["hello, world"] ] }'
Переменная окружения PERL_ANYEVENT_DEBUG_WRAP
включит режим обёртки с самого запуска программы, чтобы отловить создание всех обработчиков.
Подключимся к файловому сокету и получим приглашение:
$ sockat readline unix:twiggy.sock
Welcome, unix/:, use 'help' for more info!
>
Теперь включим самый высокий уровень детализации:
> v 9
verbose logging is now enabled.
Получим список всех обработчиков:
> wl
12261272 (eval 16):1(Plack::Runner::ANON)>io>AnyEvent::Socket:489
12864152 AnyEvent::Debug:527((eval))>io>AnyEvent::Debug:112
19500264 AnyEvent::Debug:539(Wrap::ANON)>io>AnyEvent::Base::_signal_exec
19403480 Twiggy::Server:598(run)>signal>Twiggy::Server:27
19326992 Twiggy::Server:79(_create_tcp_server)>io>AnyEvent::Socket:489
Каждый обработчик указан в отдельной строке. Первой колонкой записан адрес объекта-обёртки, далее указан номер строки, где был создан обработчик и какого он типа (io, signal).
Первые два обработчика относятся непосредственно к AnyEvent::Debug
: сам unix-сокет, сокет текущего подключения (socat). Далее идёт служебный пайп модуля Async::Interrupt
, который асинхронно отлавливает сигналы для AnyEvent.
Последние два обработчика были созданы непосредственно Twiggy: обработчик сигнала QUIT
и непосредственно прослушиваемый TCP-сокет для подключения веб-клиентов.
Можно посмотреть последний более детально с помощью команды i
:
> i 19326992
19326992 Twiggy::Server:79(_create_tcp_server)>io>AnyEvent::Socket:489
type: io watcher
args: fh GLOB(0x126e258) poll r
created: 2015-04-28 14:01:19.084551 +0300 (1430218879.08455)
file: .../lib/site_perl/5.20.1/Twiggy/Server.pm
line: 79
subname: Twiggy::Server::_create_tcp_server
context:
tracing: enabled
cb: CODE(0xbeeff0) (AnyEvent::Socket:489)
invoked: 0 times
...
В выводе для краткости опущен длинный вывод трассировки стека до точки создания обработчика. Каждая строка описывает все доступные характеристики обработчика, это и тип, аргументы, время создания, файл исходного кода, строка и подпрограмма. Также видно и количество вызовов функции-колбека.
Попробуем выполнить тестовый запрос к серверу:
$ curl http://0:5000/
В отладочной консоли можно будет увидеть следующий вывод (метки времени опущены для краткости) :
debug AnyEvent::Base: using Time::HiRes for sub-second timing accuracy.
trace AnyEvent::Debug: enter Twiggy::Server:79(_create_tcp_server)>io>AnyEvent::Socket:489
trace AnyEvent::Debug: leave Twiggy::Server:79(_create_tcp_server)>io>AnyEvent::Socket:489
Первое сообщение относится к AnyEvent, который загрузил Time::HiRes
для получения временных меток большей точности. Следующее сообщение оставляет обёртка обработчика tcp-сокета Twiggy, видно что обработчик запустился (enter) и завершился (leave). Указано точное место обработчика в исходном коде.
Если теперь повторно запросить информацию по обработчику, то можно увидеть, что счётчик вызовов увеличился на 1.
> i 19326992
...
invoked: 1 times
...
Теперь попробуем подключиться с помощью telnet к веб-серверу Twiggy.
$ telnet 0 5000
В консоли появилось больше сообщений:
14:10:28.747 trace AnyEvent::Debug: enter Twiggy::Server:79(_create_tcp_server)>io>AnyEvent::Socket:489
14:10:28.748 trace AnyEvent::Debug: creat Twiggy::Server:209(_create_req_parsing_watcher)>timer>Twiggy::Server:27
14:10:28.749 trace AnyEvent::Debug: creat Twiggy::Server:224(_create_req_parsing_watcher)>io>Twiggy::Server:27
14:10:28.749 trace AnyEvent::Debug: leave Twiggy::Server:79(_create_tcp_server)>io>AnyEvent::Socket:489
Первое и последние сообщения относятся к обработчику TCP-сокета: функция-колбек приняла подключение. В процессе работы обработчика он создаёт два новых обработчика с типами timer и io в функции _create_req_parsing_watcher
. Если заглянуть в исходный код Twiggy, то видно, что функция _create_req_parsing_watcher
вызывается в случае, если подключение есть, но данных в нём ещё нет. Создаваемый io-обработчик ждёт данных в сокете от клиента. А timer-обработчик создаётся для того, чтобы реализовать функционал таймаута. В случае с запуском curl этих обработчиков не было, поскольку curl без промедления отправил все данные запроса в сокет.
Команда wl
покажет нам эти два новых обработчика:
> wl '_create_req'
19477640 Twiggy::Server:209(_create_req_parsing_watcher)>timer>Twiggy::Server:27
19568344 Twiggy::Server:224(_create_req_parsing_watcher)>io>Twiggy::Server:27
Посмотрим обработчик таймера:
> i 19477640
...
args: after 300 interval 0
...
Как видно, таймаут составляет 300 секунд, и, если мы подождём эти 5 минут, то увидим следующие сообщения:
14:15:28.748 trace AnyEvent::Debug: enter Twiggy::Server:209(_create_req_parsing_watcher)>timer>Twiggy::Server:27
14:15:28.748 trace AnyEvent::Debug: dstry Twiggy::Server:224(_create_req_parsing_watcher)>io>Twiggy::Server:27
14:15:28.749 trace AnyEvent::Debug: leave Twiggy::Server:209(_create_req_parsing_watcher)>timer>Twiggy::Server:27
14:15:28.749 trace AnyEvent::Debug: dstry Twiggy::Server:209(_create_req_parsing_watcher)>timer>Twiggy::Server:27
Видно, что запускается обработчик таймера, который уничтожает io-обработчик, то есть закрывает соединение с клиентом. После завершения функции-колбека таймера сам обработчик таймера также уничтожается.
В данном примере мы рассмотрели способ изучения работы асинхронного приложения, наблюдая за его обработчиками.
Доступ к переменным приложения
Иногда полезно получить доступ к переменным и структурам данных приложения. На этот счёт в документации AnyEvent::Debug
есть простая рекомендация: использовать глобальные переменные. Например, сам AnyEvent::Debug
хранит все обёртки к обработчикам в хеше %AnyEvent::Debug::Wrapped
, которые легко проинспектировать в отладочной консоли:
> use DDP
> p %AnyEvent::Debug::Wrapped
{
12261272 AnyEvent::Debug::Wrapped {
public methods (4) : DESTROY, id, trace, verbose
private methods (1) : ANON
internals: {
arg {
fh *AnyEvent::Socket::$state{...} (read/write, flags: nonblocking, layers: unix perlio),
poll "r"
},
...
Также можно использовать классы-синглтоны, которые позволяют через вызов метода получать какие-либо данные.
В крайнем случае можно воспользоваться модулем PadWalker
, который, используя чёрную магию XS, извлекает на свет лексические переменные. Например,
> i 19403480
...
cb: CODE(0xbc1f38) (Twiggy::Server:27)
...
Как видно, нам доступна ссылка на код обработчика. С помощью функции PadWalker::peek_sub
мы можем проинспектировать лексические переменные, определённые в этой функции.
> use DDP
> use PadWalker
> p PadWalker::peek_sub $AnyEvent::Debug::Wrapped{19403480}{cb}
{
$self Twiggy::Server {
...
$w AnyEvent::Debug::Wrapped {
...
}
Как видно, в выводе есть объект $self
класса Twiggy::Server
, который можно проинспектировать.
Мониторинг приложений
Помимо отладки приложения, можно использовать шелл-сокет AnyEvent::Debug
для мониторинга состояния обработчиков. Например, периодическое подключение со считыванием числа активных обработчиков или счётчиков вызовов определённых функций-колбеков. Это становится возможным даже если в самом приложении не было предусмотрено никакого API для мониторинга.
Заключение
Дистрибутив AnyEvent
содержит полезные инструменты для отладки и инспектирования работы приложений такие как: AnyEvent::Log
, AnyEvent::Strict
и AnyEvent::Debug
. Их использование позволяет отлаживать код «на горячую» в работающем приложении, не требуя перезапуска и минимизируя побочные эффекты вмешательства в нормальный ход работы программы.
← От редактора | Содержание | Операторы Perl 6. Часть 1 →