APM расшифровывается как Application Performance Monitoring (мониторинг производительности приложений). Если на вашем пути встречается эта аббревиатура, то речь скорее всего идет о измерении производительность вашего приложения и ваших серверов. Как они справляются, сколько памяти они потребляют, где узкие места? И это далеко не все. С помощью APM можно настроить специальные уведомления, которые будут оповещать вас, например, о том, что потребление памяти достигло очень высоких показателей или удаленный вызов занимает слишком много времени. Триггеры для подобных уведомлений могут опираться на довольно широкий набор показателей и событий. Но давайте не будем забегать вперед. Когда я еще работал в Pathao, мы использовали New Relic. New Relic очень удобен. Чтобы начать с ним работу в PHP вам просто нужно установить его пакет и агент. И на этом все. Вы сразу же увидите всю необходимую информацию на дашборде New Relic, как только сервер начнет обслуживать запросы. Но моя нынешняя компания (Digital Healthcare Solutions, ранее известная как Telenor Health), предложила Elastic APM. Поэтому мне пришлось разбираться, как он работает. До этого момент я уже как минимум 4 раза пробовал заставить себя освоить Elastic Search, но все безуспешно. Поэтому я искал доступные готовые пакеты. И я нашел кое-что. Это был очень хороший пакет, но под капотом он отправлял HTTP-запросы на сервер APM. Что на самом деле достаточно затратно. И он не поддерживал APM Server 7.x. github.com Вот почему мне пришлось создать свой собственный пакет практически с нуля. В этой статье я собираюсь продемонстрировать вам, как можно использовать мой пакет с абсолютно любым PHP-кодом. Пакет уже достаточно просто использовать с Laravel или Lumen. Но вам не обязательно использовать что-либо из этого. Прелесть заключается в том, что вы можете использовать его как захотите. Обратили ли вы внимание на изображение вначале статьи? Давайте ненадолго вернемся к нему. На этом изображении отражена основная структура того, как это все работает. Вам нужно установить APM-агент для вашего конкретного языка (APM-агенты до сих пор поддерживают не все языки, так что имейте это ввиду). Этот агент будет собирать данные о выполнении вашего кода. Затем он будет отправлять их на APM-сервер. Далее серверы APM передают эти данные на сервер Elasticsearch, и вы сможете просмотреть эти данные в Kibana. Собственно, ничего сверхъестественного. Но я обнаружил, что APM Dashboard UI поставляется с XPack, а это означает, что вам придется немного раскошелиться. Elastic предоставляет APM-агента для PHP. Этот агент будет собирать данные с нашего сервера и передавать их на APM-сервер. Свое PHP-приложение я разместил в докер-контейнере. Докер-файл выглядит следующим образом: Докер-файл для PHP-контейнера. Игнорируйте раздел CMD. В приведенном выше фрагменте вам стоит обратить внимание на  команды в строке 16. Мы клонируем git-репозиторий APM, а затем настраиваем его и устанавливаем в наш контейнер. В строке 22 мы копируем .ini файл. Вот этот .ini файл: elastic_apm.ini file В файле elastic_apm.ini строка 2 указывает путь к нашему файлу клонированного git-репозитория – src/bootstrap_php_part.php. Строка 4 указывает URL APM-сервера. Если вы не используете докер, вы можете сделать git clone репозитория, а затем просто инсталлировать его. Дальше мы интегрируем с php расширение. Это все, что касается установки APM-агента. Прежде чем мы углубимся в сам пакет, вы можете получить некоторые сведения об основных понятиях из документации по агенту. По сути все сводится всего к двум: Транзакция: Когда ваше приложение работает, оно генерирует транзакции. Каждый запрос считается транзакцией. Каждая транзакция имеет имя и тип. Спан: Когда вы выполняете какой-ниубдь код, информация, с которой вы имеете дело, может быть отправлена ​​на сервер в виде спана. Спан (span) — это запись операции, выполняемая одним фрагментом кода. Запрос к базе данных может быть спаном, точно так же как и информация HTTP-запроса. github.com Итак, сначала давайте разберемся, как интегрировать пакет с PHP. Затем мы рассмотрим, как он интегрируется с Laravel/Lumen. Сам агент требует PHP версии ≥ 7.2, вот почему минимальное требование для пакета – PHP 7.2. Класс Anik\ElasticApm\Agent является публичной точкой доступа для всех взаимодействий. Класс Agent не может быть инстанцирован – он представляет собой синглтон. То есть, всякий раз, когда вам нужно как-либо с ним взаимодействовать, вы будете вызывать Agent::instance(). Вы получите один и тот же объект, откуда бы вы его не вызывали, на протяжении всего жизненного цикла запроса. Чтобы задать транзакции имя и тип вам нужно будет инстанцировать объект Anik\ElasticApm\Transaction с параметрами name и type. После успешного инстанцирования объекта вам нужно будет передать его в класс Agent посредством его метода setTransaction. Если вы хотите отправить данные этой транзакции на сервер, вам придется использовать спан. Чтобы можно было создать новый спан, должен быть реализован интерфейс Anik\ElasticApm\Contracts\SpanContract, в котором можно найти такие методы, как getSpanData(), getName(), getType(), getSubType(). Но если вы используете трейт Anik\ElasticApm\Spans\SpanEmptyFieldsTrait, тогда вы можете опустить методы getAction() и getLabels(). Если же вы хотите отправлять данные на свой APM-сервер, то вам не помешало бы реализовать эти методы. Я не буду здесь останавливаться на возвращаемых значениях методов, вы можете найти эту информацию в документации по агенту приведенной выше. Когда реализация класса Span будет готова, вы сможете добавлять спан следующим образом: Наконец, когда вы закончите добавлять спаны, то, прежде чем возвращать результат, вам нужно будет отправить все эти транзакции и спаны в APM-агент. Для этого используйте Приведенный выше метод обрабатывает все транзакции и спаны, а затем передает их агенту, после чего агент берет на себя заботу об отправке их на сервер. Примечание: Если вы хотите делать все самостоятельно, вы можете использовать Agent::getElasticApmTransaction(), чтобы получить текущую транзакцию агента, или Agent::newApmTransaction($name, $type), чтобы создать новую транзакцию. Обязательно вызывайте метод end(), если вы создали новый объект Transaction. Или, если вы хотите поместить спаны, которые вы добавляете, в новую транзакцию, вы можете использовать метод Agent::captureOnNew(), чтобы отправить их с новой транзакцией. Вам не нужно вызывать end, когда вы используете captureOnNew. Если вам вдруг понадобиться получить свежий инстанс Agent, вы можете сначала вызвать Agent::reset(), а потом Agent::instance(), но Agent::reinstance() будет делать то же самое. Наконец, имейте в виду, что если вы вызываете любой из методов capture*(), то с ним должен быть предоставлен объект Transaction. Без объекта Transaction вы получите исключение Anik\ElasticApm\Exceptions\RequirementMissingException. На этом мы закончили с интеграцией с PHP. Для Laravel: Пакет уже поддерживает функцию обнаружения пакетов. Но все же добавьте Anik\ElasticApm\Providers\ElasticApmServiceProvider::class в массив providers вашего config/app.php. Добавьте Anik\ElasticApm\Facades\Agent::class в массив facade вашего config/app.php. Запустите php artisan vendor:publish, чтобы опубликовать файл конфигурации. Для Lumen: Вам не нужно возиться с Facade, чтобы использовать этот пакет. Скопируйте elastic-apm.php из каталога src/config пакета в каталог config вашего lumen-проекта. Вы также можете смело изменять файл конфигурации в соответствии с вашими требованиями. Если вы хотите отправить данные об ошибке на ваш APM-сервер, то Для Laravel, в bootstrap/app.php Anik\ElasticApm\Exceptions\Handler принимает массив классов исключений в качестве второго параметра (который не будет отправляться на APM-сервер). По умолчанию ошибки NotFoundHttpException не отправляются на APM-сервер. Строчки, помеченные (1) и (2) были закомментированы, чтобы указать вам на это. Если ваше приложение сталкивается с ошибкой, которая была успешно перехвачена обработчиком исключений (Exception Handler), и при этом уже настроены транзакции, то APM-сервер гарантированно получит стек-трейс ошибки. Поскольку PHP-агент не предоставляет API для отправки стека-трейса, ваш трейс может быть обрезан обработчиком исключений. Для отслеживания количества запросов, которые обслуживает ваше приложение, кодов состояния и времени, затраченного на их обработку, у нас есть специальное встроенное middleware. Для Laravel, в вашем классе app/Http/Kernel.php: Для Lumen, в вашем файле bootstrap/app.php: Имя транзакции для обрабатываемых запросов подчиняется следующей логике: Если route handler использует параметр uses, т.е.; HomeController@index (метод контроллера). Если обработчик маршрута использует  параметр as, т.е.;['as' => 'home.index'] (именованный маршрут). Если это не вариант выше, то тогда – HTTP_VERB ROUTE_PATH, т.е.; GET /user/api. Если ничего не подходит, 404, затем используется index.php или предоставленное пользователем имя из конфигурации. Если вы используете Guzzle, вы можете использовать встроенное middleware для Guzzle. Чтобы отслеживать задачи (jobs), вам необходимо использовать встроенное (Job) middleware всякий раз, когда вы диспатчите новую задачу. Вы можете использовать любое из приведенных ниже: Из класса с методом middleware: В противном случае, при диспатче задачи: Примечание: Если вы используете  php artisan queue:work, то это значит, что задача выполняется достаточно долго. Именно поэтому будет отправлена только одна транзакция. Если не создается процесс, то вы не получите ни транзакции, ни спана. С другой стороны, если вы используете queue:listen, т.е.: php artisan queue:listen – будет использован новый процесс для каждой задачи, поэтому вы получите новую транзакцию и спаны для этой транзакции для каждой задачи. Выполнение запроса обрабатывается автоматически и передается на APM-сервер. Вот и все. Надеюсь, вам понравилось. Не забудьте поставить этому проекту звезду) Выполнение Redis-запроса не обрабатывается автоматически. Если вы используете Redis в качестве драйвера кэширования (Cache Driver), вам нужно явно указать, что вы хотите включить Redis Query Logging, добавив ELASTIC_APM_SEND_REDIS=true в ваш .env файл. А также в целях саморазвития, вот вам docker-compose.yml файл для ES, Kibana и APM (Не используйте в продакшене!) docker-compose.yml Материал подготовлен в преддверии старта онлайн-курса "PHP Developer. Professional".  Редактор