Используем apm c laravel и lumen

Форум APM Copter

Реклама. Купить это место.

Реклама. Купить это место.

APM Copter Forum

Изображение с: https://www.elastic.co/guide/en/apm/get-started/current/images/apm-architecture-cloud.png

APM – Мониторинг производительности приложений

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.

Вот почему мне пришлось создать свой собственный пакет практически с нуля. В этой статье я собираюсь продемонстрировать вам, как можно использовать мой пакет с абсолютно любым PHP-кодом. Пакет уже достаточно просто использовать с Laravel или Lumen. Но вам не обязательно использовать что-либо из этого. Прелесть заключается в том, что вы можете использовать его как захотите.

Что такое Elastic APM?

Обратили ли вы внимание на изображение вначале статьи? Давайте ненадолго вернемся к нему. На этом изображении отражена основная структура того, как все это работает. Вам нужно установить APM-агент для вашего конкретного языка (APM-агенты до сих пор поддерживают не все языки, так что имейте это ввиду).

Этот агент будет собирать данные о выполнении вашего кода. Затем он будет отправлять их на APM-сервер. Далее серверы APM передают эти данные на сервер Elasticsearch, и вы сможете просмотреть эти данные в Kibana. Собственно, ничего сверхъестественного.

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

Про сертификаты:  Геотестиль Дорнит 200 оптом по цене производителя | GeoSM

Инсталляция

Благодарение Elastic статья была переведена из английского языка на русский язык.

### Установка Elastic APM агента для PHP

Elastic предоставляет APM-агента для PHP. Этот агент собирает данные с сервера и передает их на APM-сервер. Если вы разместили свое PHP-приложение в Docker-контейнере, вам потребуется настроить Docker-файл следующим образом:

```Dockerfile
FROM php:7.4-fpm

RUN apt-get update
RUN apt-get install -y libpq-dev libpng-dev curl nano unzip zip git jq supervisor
RUN docker-php-ext-install pdo_pgsql
RUN pecl install -o -f redis
RUN docker-php-ext-enable redis
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN apt-get -y update && apt-get -y install build-essential autoconf libcurl4-openssl-dev --no-install-recommends

RUN mkdir -p /home/apm-agent && \
    cd /home/apm-agent && \
    git clone https://github.com/elastic/apm-agent-php.git apm && \
    cd apm/src/ext && \
    /usr/local/bin/phpize && ./configure --enable-elastic_apm && \
    make clean && make && make install

COPY ./elastic_apm.ini /usr/local/etc/php/conf.d/elastic_apm.ini

RUN mkdir /app
WORKDIR /app

Игнорируйте раздел CMD в Docker-файле.

Настройка и установка

В Docker-файле обратите внимание на команды в строке 16. Мы клонируем репозиторий APM с GitHub, настраиваем его и устанавливаем в контейнер.

Строка 22 копирует .ini файл с настройками для APM-агента. Он выглядит следующим образом:

extension=elastic_apm.so
elastic_apm.bootstrap_php_part_file=/home/apm-agent/apm/src/bootstrap_php_part.php
elastic_apm.enabled=true
elastic_apm.server_url=http://docker.for.mac.localhost:8200
elastic_apm.service_name=PHP APM Test Service
elastic_apm.log_level=DEBUG

В файле elastic_apm.ini строка 2 указывает путь к файлу src/bootstrap_php_part.php, а строка 4 указывает URL APM-сервера.

Основные понятия

Прежде чем начать работу с агентом, важно понять два основных понятия:

  1. Транзакция: Каждый запрос вашего приложения считается транзакцией, они имеют имя и тип.

  2. Спан: Спан – это запись операции, выполняемой одним фрагментом кода. Например, запрос к базе данных или HTTP-запрос.

Интеграция с PHP и Laravel/Lumen

Агент требует PHP версии ≥ 7.2. Для установки и работы с пакетом, ознакомьтесь с документацией Elastic APM.

### Интеграция с PHP

* Класс `Anik\ElasticApm\Agent` является публичной точкой доступа для всех взаимодействий. Класс Agent не может быть инстанцирован – он представляет собой синглтон. То есть, всякий раз, когда вам нужно как-либо с ним взаимодействовать, вы будете вызывать `Agent::instance()`. Вы получите один и тот же объект, откуда бы вы его не вызывали, на протяжении всего жизненного цикла запроса.

* Чтобы задать транзакции **имя** и **тип** вам нужно будет инстанцировать объект `Anik\ElasticApm\Transaction` с параметрами _name_ и _type_. После успешного инстанцирования объекта вам нужно будет передать его в класс `Agent` посредством его метода `setTransaction`.

Agent::instance()->setTransaction(new Transaction(name, type));


* Если вы хотите отправить данные этой транзакции на сервер, вам придется использовать спан. Чтобы можно было создать новый спан, должен быть реализован интерфейс `Anik\ElasticApm\Contracts\SpanContract`, в котором можно найти такие методы, как `getSpanData()`, `getName()`, `getType()`, `getSubType()`. Но если вы используете трейт `Anik\ElasticApm\Spans\SpanEmptyFieldsTrait`, тогда вы можете опустить методы `getAction()` и `getLabels()`. Если же вы хотите отправлять данные на свой APM-сервер, то вам не помешало бы реализовать эти методы. Я не буду здесь останавливаться на возвращаемых значениях методов, вы можете найти эту информацию в документации по агенту приведенной выше.

* Когда реализация класса **Span** будет готова, вы сможете добавлять спан следующим образом:

Agent::instance()->addSpan( class=formula inline>implementedSpanObject);


* Наконец, когда вы закончите добавлять спаны, то, прежде чем возвращать результат, вам нужно будет отправить все эти _транзакции_ и _спаны_ в APM-агент. Для этого используйте

Agent::instance()->capture();


Приведенный выше метод обрабатывает все транзакции и спаны, а затем передает их агенту, после чего агент берет на себя заботу об отправке их на сервер.

**Примечание**: Если вы хотите делать все самостоятельно, вы можете использовать `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/Lumen
* Пакет уже поддерживает функцию обнаружения пакетов. Но все же добавьте `Anik\ElasticApm\Providers\ElasticApmServiceProvider::class` в массив providers вашего config/app.php.
* Добавьте `Anik\ElasticApm\Facades\Agent::class` в массив facade вашего config/app.php.
* Запустите `php artisan vendor:publish`, чтобы опубликовать файл конфигурации.
* Вам не нужно возиться с Facade, чтобы использовать этот пакет.
* Скопируйте elastic-apm.php из каталога src/config пакета в каталог config вашего lumen-проекта.

```php
// в ваш файл bootstrap/app.php.

use Anik\ElasticApm\Providers\ElasticApmServiceProvider;

$app->register(ElasticApmServiceProvider::class);

$app->configure(elastic-apm);

Вы также можете смело изменять файл конфигурации в соответствии с вашими требованиями.

Про сертификаты:  Курсы массажа - обучение массажу в Санкт-Петербурге

Отслеживание ошибок приложений

Если вы хотите отправить данные об ошибке на ваш APM-сервер, то

  • Для Laravel, в bootstrap/app.php
// ЭТОТ РАЗДЕЛ СЛЕДУЕТ ЗАКОММЕНТИРОВАТЬ

/**
 *  $app->singleton(
 *      Illuminate\Contracts\Debug\ExceptionHandler::class,
 *      App\Exceptions\Handler::class
 *  );
 */

// ИСПОЛЬЗУЙТЕ ЭТОТ РАЗДЕЛ

use Illuminate\Contracts\Debug\ExceptionHandler;
use Anik\ElasticApm\Exceptions\Handler;
use App\Exceptions\Handler as AppExceptionHandler;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use GuzzleHttp\Exception\ConnectException;

$app->singleton(ExceptionHandler::class, function ($app) {
    return new Handler(new AppExceptionHandler($app), [
        // NotFoundHttpException::class, //(1)
        // ConnectException::class, //(2)
    ]);
});

Anik\ElasticApm\Exceptions\Handler принимает массив классов исключений в качестве второго параметра (который не будет отправляться на APM-сервер). По умолчанию ошибки NotFoundHttpException не отправляются на APM-сервер. Строчки, помеченные (1) и (2) были закомментированы, чтобы указать вам на это.

Если ваше приложение сталкивается с ошибкой, которая была успешно перехвачена обработчиком исключений (Exception Handler), и при этом уже настроены транзакции, то APM-сервер гарантированно получит стек-трейс ошибки. Поскольку PHP-агент не предоставляет API для отправки стека-трейса, ваш трейс может быть обрезан обработчиком исключений.

В ответе были возвращены код 500 (отмечено) и исключение со стек-трейсом.

Отслеживание запросов и ответов вашего приложения

Для отслеживания количества запросов, которые обслуживает ваше приложение, кодов состояния и времени, затраченного на их обработку, у нас есть специальное встроенное middleware.

  • Для Laravel, в вашем классе app/Http/Kernel.php:
<?php

use Anik\ElasticApm\Middleware\RecordForegroundTransaction;

class Kernel extends HttpKernel {
    protected $middleware = [
        // ...        
        RecordForegroundTransaction::class,
        // ..
    ];
}

  • Для Lumen, в вашем файле bootstrap/app.php:
use Anik\ElasticApm\Middleware\RecordForegroundTransaction;

$app->middleware([
    // ...
RecordForegroundTransaction::class,

// ...

]);




Имя транзакции для обрабатываемых запросов подчиняется следующей логике:



* Если ничего не подходит, 404, затем используется index.php или предоставленное пользователем имя из конфигурации.



Транзакция запроса (Шаг 1)



Называем маршрут как транзакцию (шаг 2)



Маршрут с глаголом (шаг 3)



Маршруты не найдены (Шаг 4)



Спан с обработанным запросом



### Отслеживание удаленных HTTP-вызовов 



Если вы используете Guzzle, вы можете использовать встроенное middleware для Guzzle.



use GuzzleHttp\HandlerStack;

Про сертификаты:  ПОРЯДОК ПРЕДСТАВЛЕНИЯ ДОКУМЕНТА, ПОДТВЕРЖДАЮЩЕГО СТАТУС НАЛОГОВОГО РЕЗИДЕНТА РОССИЙСКОЙ ФЕДЕРАЦИИ / КонсультантПлюс

use GuzzleHttp\Client;

use Anik\ElasticApm\Middleware\RecordHttpTransaction;

$stack = HandlerStack::create();

$stack->push(new RecordHttpTransaction(), ‘whatever-you-wish’);

$client = new Client([

'base_uri' => 'https://httpbin.org',

'timeout'  => 10.0,

'handler'  => $stack,

]);

$client->request(‘GET’, ‘/’);




Отслеживание удаленного HTTP-вызова



### Отслеживание работника очереди



Чтобы отслеживать задачи (jobs), вам необходимо использовать встроенное (Job) middleware всякий раз, когда вы диспатчите новую задачу. Вы можете использовать любое из приведенных ниже:



* Из класса с методом **middleware**:



use Anik\ElasticApm\Middleware\RecordBackgroundTransaction;

use Illuminate\Contracts\Queue\ShouldQueue;

class TestSimpleJob implements ShouldQueue

{

public function middleware () {

    return [ new RecordBackgroundTransaction()];

}



public function handle () {

    app('log')->info('job is handled');

}

}




* В противном случае, при диспатче задачи:



use Anik\ElasticApm\Middleware\RecordBackgroundTransaction as JM;

use App\Jobs\ExampleJob;

dispatch((new ExampleJob())->through([new JM()]);




Отслеживание обработки задач



**Примечание**: Если вы используете `php artisan queue:work`, то это значит, что задача выполняется достаточно долго. Именно поэтому будет отправлена только одна транзакция. Если не создается процесс, то вы не получите ни транзакции, ни спана. С другой стороны, если вы используете `queue:listen`, т.е.: `php artisan queue:listen` – будет использован новый процесс для каждой задачи, поэтому вы получите новую транзакцию и спаны для этой транзакции для каждой задачи.



### Отслеживание выполнения запроса



Выполнение запроса обрабатывается автоматически и передается на APM-сервер.



Вот и все. Надеюсь, вам понравилось. Не забудьте поставить этому проекту звезду)



### Отслеживание выполнения Redis-запросов 



Выполнение Redis-запроса не обрабатывается автоматически. Если вы используете Redis в качестве драйвера кэширования (Cache Driver), вам нужно явно указать, что вы хотите включить Redis Query Logging, добавив `ELASTIC_APM_SEND_REDIS=true` в ваш .env файл.



А также в целях саморазвития, вот вам docker-compose.yml файл для ES, Kibana и APM (**_Не используйте в продакшене!_**)



version: "2"

services:

php:

    build:

        dockerfile: php.dockerfile

        context: .

    volumes:

        - .:/app

    ports:

        - 8008:80

    links:

        - apm



elasticsearch:

    image: bitnami/elasticsearch:7.8.0

    volumes:

        - ~/.backup/elasticsearch/elastic-apm:/bitnami/elasticsearch/data

        - ./bitnami_es_config.yml:/opt/bitnami/elasticsearch/config/elasticsearch.yml

    ports:

        - 60200:9200

    environment:

        - BITNAMI_DEBUG=true



kibana:

    image: bitnami/kibana:7.8.0

    ports:

        - 5601:5601

    volumes:

        - ~/.backup/kibana/elastic-apm:/bitnami

    links:

        - elasticsearch

    environment:

        - KIBANA_ELASTICSEARCH_URL=elasticsearch



apm:

    image: docker.elastic.co/apm/apm-server-oss:7.8.0

    ports:

        - 8200:8200

    user: apm-server

    links:

        - elasticsearch

        - kibana

    command: --strict.perms=false

    environment:

        - apm-server.host=0.0.0.0

        - apm-server.kibana.enabled=true

        - apm-server.kibana.host="http://kibana:5601"

        - output.elasticsearch.hosts=["elasticsearch:9200"]

        - output.elasticsearch.max_retries=1



apm2:

    image: docker.elastic.co/apm/apm-server-oss:6.8.9

    ports:

        - 8201:8200

    user: apm-server

    links:

        - elasticsearch

        - kibana

    command: --strict.perms=false

    environment:

        - apm-server.host=0.0.0.0

        - apm-server.kibana.enabled=true

        - apm-server.kibana.host="http://kibana:5601"

        - output.elasticsearch.hosts=["elasticsearch:9200"]

        - output.elasticsearch.max_retries=1



---



Материал подготовлен в преддверии старта онлайн-курса "PHP Developer. Professional".

Оцените статью
Мой сертификат
Добавить комментарий