Мониторинг PHP приложений с помощью Proto Observability

Подключение трейсинга для PHP приложений.

Установка PHP трейсера Proto Observability Platform

На этой странице:

Введение

Общий процесс подключения PHP приложения на мониторинг:

  1. Установка ProtoOBP Агента
  2. Установка трейсера

Если приложение запускается в Docker контейнере

  1. Скачайте и разахивируйте архив с агентом:

    curl --header "PRIVATE-TOKEN:<your_token>"  "https://git.proto.group/api/v4/projects/125/packages/generic/pobptrace-php-agent/2.3.2/protoobp-php-agent-2.3.2.tar.gz" --output protoobp-php-agent-2.3.2.tar.gz
    
    tar -xzvf protoobp-php-agent-2.3.2.tar.gz
    
  2. Добавьте соответствующий пакет с агентом в ваш контейнер:

    Платформа Пакет
    Ubuntu, Debian protoobp-php-tracer_2.3.2_amd64.deb
    Centos protoobp-php-tracer-2.3.2-1.x86_64.rpm
    Alpine protoobp-php-tracer_2.3.2_x86_64.apk
  3. Установите пакет:

    Платформа Пакет
    Ubuntu, Debian RUN dpkg -i protoobp-php-tracer_2.3.2_amd64.deb
    Centos RUN rpm -ivh protoobp-php-tracer-2.3.2-1.x86_64.rpm
    Alpine RUN apk add --allow-untrusted protoobp-php-tracer_2.3.2_x86_64.apk
  4. Добавьте следующие переменные окружения:

    Переменная Значение Описание
    POBP_AGENT_HOST <proto_backend_address> IP адрес хоста, где запущен ProtoOBP агент, доступный из сети Docker контейнера. В случае если агент запускается в виде Docker контейнера, то достаточно указать имя контейнера (обычно это protoobp-agent)
    POBP_SERVICE <service_name> имя сервиса, которое будет отображаеться в интерфейсе

Пример готового Dockerfile

FROM php:7.4-apache

RUN apt-get update && apt-get install -yqq unzip wget curl libzip-dev \
        && docker-php-ext-install pdo_mysql opcache zip

COPY status.conf /etc/apache2/mods-available/status.conf
RUN a2enmod rewrite && a2enmod status

WORKDIR /var/www/html

COPY html/ /var/www/html

COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN composer install

RUN rm -Rf /var/www/var/*
RUN chown -R www-data /var/www
RUN chmod -R 777 /var/www

#Установка Proto Observability PHP Agent
ENV POBP_AGENT_HOST=protoobp-agent
ENV POBP_SERVICE=my_service
COPY ./protoobp-php-tracer_2.0.0_amd64.deb /opt
RUN dpkg -i /opt/protoobp-php-tracer_2.0.0_amd64.deb

Если приложение запускается в Linux среде

  1. Скачайте и разахивируйте архив с PHP трейсером :

    curl --header "PRIVATE-TOKEN:<your_token>"  "https://git.proto.group/api/v4/projects/125/packages/generic/pobptrace-php-agent/2.3.2/protoobp-php-agent-2.3.2.tar.gz" --output protoobp-php-agent-2.3.2.tar.gz
    
    tar -xzvf protoobp-php-agent-2.3.2.tar.gz
    
  2. Установите пакет PHP трейсера:

    Платформа Пакет
    Ubuntu, Debian dpkg -i protoobp-php-tracer_2.3.2_amd64.deb
    Centos rpm -ivh protoobp-php-tracer-2.3.2-1.x86_64.rpm
    Alpine apk add --allow-untrusted protoobp-php-tracer_2.3.2_x86_64.apk
  3. В файле 98-pobptrace.ini* укажите следующее:

    protoobp.service = Hello_ProtoOBP ; название сервиса
    

    *Расположение .ini файла вы можете узнать, выполнив php --ini

  4. Перезапустите PHP (например, Nginx, Apache )

Если приложение работает в Kubernetes

Убедитесь, что у вас успешно установлен и настроен ProtoOBP Агент для Kubernetes.

Дополнительно необходимо передать поду переменную окружения POBP_AGENT_HOST со значением IP адреса воркер-ноды, а также переменные окружения для связи трейсов с инфраструктурой (имя k8s кластера нужно задать вручную).

apiVersion: apps/v1
kind: Deployment
#(...)
    spec:
      containers:
      - name: "<CONTAINER_NAME>"
        image: "<CONTAINER_IMAGE>/<TAG>"
        env:
          - name: POBP_SERVICE
            value: dispatch			
          - name: POBP_AGENT_HOST
            valueFrom:
              fieldRef:
                fieldPath: status.hostIP
          - name: NODE_NAME
            valueFrom:
              fieldRef:
                fieldPath: spec.nodeName
          - name: POD_NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
          - name: POD_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: POBP_TAGS
            value: "pod_name:$(POD_NAME),node:$(NODE_NAME),kube_namespace:$(POD_NAMESPACE),kube_cluster_name:<my_cluster_name>"            
            

Отключение PHP-трейсера

Для php-fpm - остановите сервис php-fpm, или остановите веб-сервер Apache. Удалите файлы 98-pobptrace.ini и 99-pobptrace-custom.ini из папки конфигураций php. Для php-fpm - перезапустите сервис php-fpm, или перезапустите веб-сервер Apache.

Если вы используете кэширование второго уровня в OPcache, задав параметр opcache.file_cache - удалите папку с кэшем.

Также можно отключить работу трейсера (при этом PHP-модуль трейсера будет оставаться загруженным):

  • через файл конфигурации

    pobptrace.trace.enabled=0

  • через переменную окружения

    POBP_TRACE_ENABLED

      Значение по умолчанию: 1
      Комментарий: Включает трейсер глобально.

Поддерживаемые технологии

Версии PHP

Версия Поддерживается
8.3.x
8.2.x
8.1.x
8.0.x
7.4.x
7.3.x
7.2.x
7.1.x
7.0.x
5.6.x
5.5.x ✅ Поддерживается ProtoOBP трейсером версии 2.2.3
5.4.x ✅ Поддерживается ProtoOBP трейсером версии 2.2.3
Режим работы PHP Поддерживается
apache2handler
cli
fpm-fcgi
cgi-fcgi

Автоматическая инструментация библиотек и фреймворков

Трейсинг запросов включен по умолчанию после установки агента. Proto Observability Platform поддерживает все PHP фреймворки из коробки, с инструментацией на уровне фреймворка или стандартного трейсинга веб-приложения.

Автоматическая инструментация работает благодаря модификации PHP рантайма для оборачивания функций и методов для их трейсинга.

Благодаря автоматической инструментации вам доступна следующая информация:

  • время исполнения метода;
  • данные транзакции, такие как URL и код ответа для веб запросов, текст SQL запроса для запросов к базам данных;
  • необработанные исключения, в том числе стэк трейс, если он доступен.

PHP фреймворки

По умолчанию Proto Observability Platform поддерживает все PHP фреймворки из коробки, с инструментацией на уровне фреймворка или стандартного трейсинга веб-приложения.

Инструментация на уровне фреймворка включает отслеживание внутренних методов и простановку тегов, специфичных для фреймворка.

Стандартный трейсинг веб-приложения включает спан web.request для отслеживания задержки и ошибок, возникающих при вызове, в дополнение к спанам для вызовов поддерживаемых библиотек, например, при обращении к базам данных или HTTP-клиентам.

В следующей таблице перечислены некоторые из фреймворков и версий, для которых Proto Observability Platform успешно производит автоматический трейсинг.

Фреймворк Версия Версии PHP Уровень инструментации
Битрикс все Все поддерживаемые версии PHP Стандартный трейсинг веб-приложения
CakePHP 2.x Все поддерживаемые версии PHP Инструментация методов уровня фреймворка
CodeIgniter 2.x PHP 7+ Инструментация методов уровня фреймворка
CodeIgniter 3.x PHP 7+ Стандартный трейсинг веб-приложения
Drupal Все поддерживаемые версии PHP Инструментация методов уровня фреймворка
FuelPHP 1.1 PHP 7+ Стандартный трейсинг веб-приложения
Laminas Все поддерживаемые версии PHP Инструментация методов уровня фреймворка
Laravel 4.2, 5.x, 6.x Все поддерживаемые версии PHP Инструментация методов уровня фреймворка
Laravel 8+ 8.x, 9.x, 10.x Все поддерживаемые версии PHP Инструментация методов уровня фреймворка
Lumen 5.2+ Все поддерживаемые версии PHP Инструментация методов уровня фреймворка
Magento 1 Все поддерживаемые версии PHP Стандартный трейсинг веб-приложения
Magento 2 PHP 7+ Инструментация методов уровня фреймворка
Neos Flow 1.1 Все поддерживаемые версии PHP Стандартный трейсинг веб-приложения
Phalcon 1.3, 3.4 Все поддерживаемые версии PHP Стандартный трейсинг веб-приложения
RoadRunner 2.x Все поддерживаемые версии PHP Инструментация методов уровня фреймворка
Slim 2.x, 3.x, 4.x Все поддерживаемые версии PHP Инструментация методов уровня фреймворка
Symfony 2.x, 3.3, 3.4, 4.x, 5.x, 6.x Все поддерживаемые версии PHP Инструментация методов уровня фреймворка
WordPress 4.x, 5.x, 6.x PHP 7+ Инструментация методов уровня фреймворка
Yii 1.1, 2.0 Все поддерживаемые версии PHP Инструментация методов уровня фреймворка
Zend Framework 1.12, 1.21 Все поддерживаемые версии PHP Инструментация методов уровня фреймворка
Zend Framework 2.x Все поддерживаемые версии PHP Стандартный трейсинг веб-приложения

CLI

Поддержка трейсинга PHP CLI отключена по умолчанию. Для включения трейсинга PHP CLI скриптов, установите переменную окружения POBP_TRACE_CLI_ENABLED=true.

Модуль Версии Поддерживается
CakePHP Console 2.x
Laravel Artisan 5.x, 8.x, 9.x, 10.x
Symfony CLI 4.x, 5.x, 6.x

Базы данных и datastore

Модуль Версии Поддерживается
Amazon RDS (используя PDO или MySQLi) Все
Elasticsearch 1+
Eloquent Все поддерживаемые версии Laravel
Laravel Queues Все поддерживаемые версии Laravel
Memcache Все
Memcached Все
MongoDB 1.4.x
MySQLi Все
PDO Все
PhpRedis 3, 4, 5 ✅ PHP 7, 8
Predis 1.1
SQLSRV Все

Прочие библиотеки и HTTP-клиенты

Модуль Версии Поддерживается
php-amqplib 2.x, 3.x ✅ PHP 7.1+
Curl Все
Guzzle 5.x, 6.x, 7.x

Пользовательская (дополнительная) инструментация

Инструментация через аннотации

Если вы используете PHP 8, то вы можете добавлять аннотации кода для его инструментации. Это более легкая альтернатива пользовательской инструментации, написанной в коде. Например, добавьте атрибут #[POBPTrace\Trace] к методам, чтобы Proto Observability Platform отслеживал их.

<?php
class Server {
    #[POBPTrace\Trace(name: "spanName", resource: "resourceName", type: "Custom", service: "myService", tags: ["aTag" => "aValue"])]
    static function process($arg) {}

    #[POBPTrace\Trace]
    function get() {
      Foo::simple(1);
    }
}

Вы можете указать следующие аргументы:

  • $name: Имя операции, которое должно быть назначено спану. По умолчанию используется имя функции.
  • $resource: Ресурс, который будет присвоен спану.
  • $type: Тип, который будет присвоен спану.
  • $service: Имя сервиса, которое будет назначена спану. По умолчанию принимает наследуемое имя сервиса.
  • $tags: Теги, которые будут присвоены спану.
  • $recurse: Должны ли отслеживаться рекурсивные вызовы.
  • $run_if_limited: Должна ли функция трассироваться в ограниченном режиме. (Например, при превышении лимита спанов).

Добавление пользовательской инструментации в коде

Если вам необходимо добавить собственную инструментацию, рассмотрите следующий пример приложения и пройдитесь по примеру.

Пример приложения, которое необходимо инструментировать

Предположим, что структура каталогов следующая:

.
|-- composer.json
|-- docker-compose.yml
|-- index.php
`-- src
    |-- Exceptions
    |   `-- NotFound.php
    |-- Services
    |   `-- SampleRegistry.php
    `-- utils
        `-- functions.php

Внутри него в двух файлах содержатся функции и методы, которые интересны для инструментации. Наиболее значимыми файлами являются src/utils/functions.php:

namespace App;

function some_utility_function($someArg)
{
    return 'result';
}

И src/Services/SampleRegistry.php:

namespace App\Services;

use App\Exceptions\NotFound;
use Exception;

class SampleRegistry
{
    public function put($key, $value)
    {
        \App\some_utility_function('some argument');
        // Возвращает идентификатор вставленного элемента
        return 456;
    }

    public function faultyMethod()
    {
        throw new Exception('Генерируется во время выполнения');
    }

    public function get($key)
    {
        // Для сообщения о том, что ключ не найден, сервис использует исключение.
        throw new NotFound('Ключ не был найден');
    }

    public function compact()
    {
        // Эта функция выполняет некоторые операции над реестром и
        // ничего не возвращает. В середине функции имеется интересное значение,
        // которое не возвращается, но может быть связано с замедлением функции

        $numberOfItemsProcessed = 123;

        // ...
    }
}

Написание пользовательской инструментации

Чтобы не смешивать бизнес-логику приложения или сервиса с кодом инструментации, напишем необходимый код в отдельном файле.

  1. Создадим файл protoobp/instrumentation.php и добавим его в автозагрузку composer.

    //composer.json
    {
      ...
      "autoload": {
          ...
          "files": [
              ...
              "protoobp/instrumentation.php"
          ]
      },
      ...
    }
    
  2. Обновите autoloader, например, выполнив composer dump.

  3. В файле protoobp/instrumentation.php, проверьте, что расширение загружено. Если расширение не загружено, то все функции, используемые в этом файле, не существуют.

    #protoobp/instrumentation.php
    if (!extension_loaded('pobptrace')) {
        return;
    }
    
  4. Инструментируем функцию \App\some_utility_function. Если вас не интересует какой-либо конкретный аспект функции, кроме времени выполнения, то это все, что требуется:

    #protoobp/instrumentation.php
    \POBPTrace\trace_function('App\some_utility_function', function (\POBPTrace\SpanData $span, $args, $ret, $exception) {});
    
  5. Допустим, что для метода SampleRegistry::put, вы хотите не только сгенерировать спан, но и добавить тег со значением возвращаемого идентификатора элемента, а также тег для ключа. Поскольку put - это метод, используйте \POBPTrace\trace_method вместо \POBPTrace\trace_function:

    #protoobp/instrumentation.php
    ...
    \POBPTrace\trace_method(
     'App\Services\SampleRegistry',
     'put',
     function (\POBPTrace\SpanData $span, $args, $ret, $exception) {
         $span->meta['app.cache.key'] = $args[0]; // Первый аргумент 'key'
         $span->meta['app.cache.item_id'] = $ret; // Возвращаемое значение
     }
    );
    
  6. В коде примера SampleRegistry::faultyMethod генерирует исключение. При этом вам не требуется ничего дополнительно делать с точки зрения пользовательской инструментации. Если метод инструментирован, то стандартный механизм сообщения об исключениях позаботится о добавлении в спан сообщения об исключении и трассировки стека.

    #protoobp/instrumentation.php
    ...
    \POBPTrace\trace_method(
     'App\Services\SampleRegistry',
     'faultyMethod',
     function (\POBPTrace\SpanData $span, $args, $ret, $exception) {
     }
    );
    
  7. Метод SampleRegistry::get использует исключение NotFound для уведомления о том, что элемент не найден. Допустим, это исключение является ожидаемой частью бизнес-логики приложения, и вы не хотите отмечать спан с этим исключением как ошибку. Тогда вы можете просто изменить имя ресурса, чтобы добавить его в пул операций not_found. Для этого необходимо сделать unset исключению для спана:

    #protoobp/instrumentation.php
    ...
    \POBPTrace\trace_method(
       'App\Services\SampleRegistry',
       'get',
       function (\POBPTrace\SpanData $span, $args, $ret, $exception) {
         if ($exception instanceof \App\Exceptions\NotFound) {
             unset($span->exception);
             $span->resource = 'cache.get.not_found';
         }
     }
    );
    
  8. Метод SampleRegistry::compact демонстрирует интересный случай использования. Если вас интересует добавление тега со значением, которое не является ни аргументом, ни значением, возвращаемым функцией, то для этого отредактируйте файл protoobp/instrumentation.php и файл класса src/Services/SampleRegistry.php:

    #protoobp/instrumentation.php
    ...
    \POBPTrace\trace_method(
     'App\Services\SampleRegistry',
     'compact',
     function (\POBPTrace\SpanData $span, $args, $ret, $exception) {
     }
    );
    

    В файле src/Services/SampleRegistry.php отредактируйте тело метода:

    #src/Services/SampleRegistry.php
    ...
     public function compact()
     {
         // Эта функция выполняет некоторые операции над реестром и
         // ничего не возвращает. В середине функции имеется интересное значение,
         // которое не возвращается, но может быть связано с замедлением функции
    
         $numberOfItemsProcessed = 123;
    
         // Добавьте код инструментации в бизнес-логику.
         if (\function_exists('\POBPTrace\active_span') && $span = \POBPTrace\active_span()) {
             $span->meta['registry.compact.items_processed'] = $numberOfItemsProcessed;
         }
    
         // ...
     }
    

Подробнее о trace_function и trace_method

Функции POBPTrace\trace_function и POBPTrace\trace_method инструментируют (трассируют) вызовы определенных функций и методов. Эти функции автоматически решают следующие задачи:

  • Открытие спана перед выполнением кода.
  • Добавление в спан всех ошибок, возникших при выполнении инструменированного вызова.
  • Закрытие спана после завершения инструментированного вызова.

Дополнительные теги устанавливаются на спан из закрытия (называемого закрытием трассировки).

Например, в следующем фрагменте выполняется трассировка метода CustomDriver::doWork и добавляются пользовательские теги. Исключения автоматически отслеживаются на спане.

<?php
\POBPTrace\trace_method(
    'CustomDriver',
    'doWork',
    function (\POBPTrace\SpanData $span, array $args, $retval, $exception) {
        // Это закрытие выполняется после инструментального вызова
        // Спан был автоматически создан до вызова инструментированного вызова

        // SpanData::$name по умолчанию устанавливается в 'ClassName.methodName' если не задано
        $span->name = 'CustomDriver.doWork';
        // SpanData::$resource по умолчанию устанавливается в SpanData::$name не задано
        $span->resource = 'CustomDriver.doWork';

        // Если исключение было выдано из неинструментированного вызова, возвращаемое значение является null
        $span->meta['doWork.size'] = $exception ? 0 : count($retval),
        // Доступ к членам объекта через $this
        $span->meta['doWork.thing'] = $this->workToDo;

        // Спан автоматически закроется
    }
);

// Для функций
\POBPTrace\trace_function(
    'doCustomDriverWork',
    function (\POBPTrace\SpanData $span, array $args, $retval, $exception) {
        // Закрытие такое же как для POBPTrace\trace_method
    }
);
?>

Доступ к активным спанам

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

Текущий спан

Следующий метод возвращает объект POBPTrace\SpanData. Если трассировка отключена, возвращается null.

<?php
$span = \POBPTrace\active_span();
if ($span) {
    $span->meta['customer.id'] = get_customer_id();
}
?>
Корневой спан

Следующий метод возвращает объект POBPTrace\SpanData. Если трассировка отключена, возвращается null. Это полезно в тех случаях, когда метаданные, которые должны быть добавлены к корневому спану, не существуют на ранних этапах выполнения скрипта.

<?php
$span = \POBPTrace\root_span();
if ($span) {
    $span->meta['customer.id'] = get_customer_id();
}
?>

Добавление тегов

Локально

Добавьте теги к спану с помощью массива POBPTrace\SpanData::$meta.

<?php

\POBPTrace\trace_function(
    'myRandFunc',
    function(\POBPTrace\SpanData $span, array $args, $retval) {
        // ...
        $span->meta['rand.range'] = $args[0] . ' - ' . $args[1];
        $span->meta['rand.value'] = $retval;
    }
);
Глобально

Установите переменную окружения POBP_TAGS для автоматического добавления тегов к каждому создаваемому спану.

POBP_TAGS=key1:value1,<TAG_KEY>:<TAG_VALUE>
Ошибки

Выброшенные исключения автоматически привязываются к активному спану, если только исключение не было выброшено на более глубоком уровне стека вызовов и не было поймано до того, как оно достигло какой-либо трассируемой функции.

<?php

function doRiskyThing() {
    throw new Exception('Ой!');
}

\POBPTrace\trace_function(
    'doRiskyThing',
    function() {
        // Спан будет помечен как ошибочный, и к нему будут добавлены в качестве тегов
        // трассировка стека и сообщение исключения
    }
);

Добавьте тег error.msg, чтобы вручную отметить спан как ошибочный.

<?php

function doRiskyThing() {
    return SOME_ERROR_CODE;
}

\POBPTrace\trace_function(
    'doRiskyThing',
    function(\POBPTrace\SpanData $span, $args, $retval) {
        if ($retval === SOME_ERROR_CODE) {
            $span->meta['error.msg'] = 'Ошибка Х';
            // Опционально:
            $span->meta['error.type'] = 'CustomError';
            $span->meta['error.stack'] = (new \Exception)->getTraceAsString();
        }
    }
);