Верх страницы
Обложка к записи Логирование внешних HTTP-запросов с Laravel Telescope
Время для прочтения: 1 мин. 53 сек.

Логирование внешних HTTP-запросов с Laravel Telescope

Самая большая проблема при работе с внешними API заключается в том, что у нас очень мало прозрачности.

Мы интегрируем их в нашу кодовую базу и тестируем, но мы не знаем, насколько часто мы их используем, если только API, с которым мы интегрируемся, не предоставляет метрики, которые мы можем использовать. Я довольно долго был расстроен этим, но появилось элегантное решение.

Laravel Telescope — это помощник по отладке для вашего приложения, который умеет логировать и давать вам представление о том, что происходит на высоком уровне. Мы можем подключиться к этому и добавить пользовательские наблюдатели для повышения отладки и логирования, и именно это мы сделаем в этом коротком руководстве.

После установки Laravel Telescope убедитесь, что вы опубликовали конфигурацию и выполнили миграцию базы данных, мы можем начать создавать наш наблюдатель для Guzzle — клиента под фасадом Http.

Самое логичное место для хранения этих классов, по крайней мере для меня, — внутри app/Telescope/Watchers, поскольку код принадлежит нашему приложению — но мы расширяем сам Telescope. Но как выглядит стандартный Наблюдатель?

class YourWatcher extends Watcher
{
  public function register($app): void
  {
    // handle code for watcher here.
  }
}

Вы можете добавить столько методов, сколько нужно, чтобы создать Наблюдателя, который работает для вас. Итак, без лишних слов, давайте создадим новый класс app/Telescope/Watchers/GuzzleRequestWatcher.php, и мы разберем, что он должен делать.

declare(strict_types=1);
 
namespace App\\Telescope\\Watchers;
 
use GuzzleHttp\\Client;
use GuzzleHttp\\TransferStats;
use Laravel\\Telescope\\IncomingEntry;
use Laravel\\Telescope\\Telescope;
use Laravel\\Telescope\\Watchers\\FetchesStackTrace;
use Laravel\\Telescope\\Watchers\\Watcher;
 
final class GuzzleRequestWatcher extends Watcher
{
  use FetchesStackTrace;
}

Сначала нам нужно включить трейт FetchesStackTrace, поскольку он позволяет перехватывать запросы. Если мы рефакторим эти HTTP-вызовы в другие места, мы можем убедиться, что вызываем их так, как задумано. Далее нам нужно добавить метод для регистрации нашего Наблюдателя:

declare(strict_types=1);
 
namespace App\\Telescope\\Watchers;
 
use GuzzleHttp\\Client;
use GuzzleHttp\\TransferStats;
use Laravel\\Telescope\\IncomingEntry;
use Laravel\\Telescope\\Telescope;
use Laravel\\Telescope\\Watchers\\FetchesStackTrace;
use Laravel\\Telescope\\Watchers\\Watcher;
 
final class GuzzleRequestWatcher extends Watcher
{
  use FetchesStackTrace;
 
  public function register($app)
  {
    $app->bind(
      abstract: Client::class,
      concrete: $this->buildClient(
        app: $app,
      ),
    );
  }
}

Мы перехватываем клиент Guzzle и регистрируем его в контейнере, но для этого мы хотим указать, как мы хотим, чтобы клиент был построен. Давайте посмотрим на метод buildClient:

private function buildClient(Application $app): Closure
{
  return static function (Application $app): Client {
      $config = $app['config']['guzzle'] ?? [];
 
    if (Telescope::isRecording()) {
      // Record our Http query.
    }
 
    return new Client(
      config: $config,
    );
  };
}

Здесь мы возвращаем статическую функцию, которая строит наш клиент Guzzle. Сначала мы получаем любую конфигурацию guzzle — а затем, если Telescope записывает, мы добавляем способ записи запроса. Наконец, мы возвращаем клиент с его конфигурацией. Так как же мы записываем наш HTTP-запрос? Давайте посмотрим:

if (Telescope::isRecording()) {
  $config['on_stats'] = static function (TransferStats $stats): void {
    $caller = $this->getCallerFromStackTrace(); // This comes from the trait we included.
 
    Telescope::recordQuery(
      entry: IncomingEntry::make([
        'connection' => 'guzzle',
        'bindings' => [],
        'sql' => (string) $stats->getEffectiveUri(),
        'time' => number_format(
          num: $stats->getTransferTime() * 1000,
          decimals: 2,
          thousand_separator: '',
        ),
        'slow' => $stats->getTransferTime() > 1,
        'file' => $caller['file'],
        'line' => $caller['line'],
        'hash' => md5((string) $stats->getEffectiveUri())
      ]),
    );
  };
}

Таким образом, мы расширяем конфигурацию, добавляя опцию on_stats, которая является колбэком. Этот колбэк получит стек-трейс и запишет новый запрос. Эта новая запись будет содержать все релевантные вещи, связанные с запросом, которые мы можем записать. Итак, если собрать все вместе:

declare(strict_types=1);
 
namespace App\Telescope\Watchers;
 
use Closure;
use GuzzleHttp\Client;
use GuzzleHttp\TransferStats;
use Illuminate\Foundation\Application;
use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;
use Laravel\Telescope\Watchers\FetchesStackTrace;
use Laravel\Telescope\Watchers\Watcher;
 
final class GuzzleRequestWatcher extends Watcher
{
    use FetchesStackTrace;
 
    public function register($app): void
    {
        $app->bind(
            abstract: Client::class,
            concrete: $this->buildClient(
                app: $app,
            ),
        );
    }
 
    private function buildClient(Application $app): Closure
    {
        return static function (Application $app): Client {
            $config = $app['config']['guzzle'] ?? [];
 
            if (Telescope::isRecording()) {
                $config['on_stats'] = function (TransferStats $stats) {
                    $caller = $this->getCallerFromStackTrace();
                    Telescope::recordQuery(
                        entry: IncomingEntry::make([
                            'connection' => 'guzzle',
                            'bindings' => [],
                            'sql' => (string) $stats->getEffectiveUri(),
                            'time' => number_format(
                                num: $stats->getTransferTime() * 1000,
                                decimals: 2,
                                thousands_separator: '',
                            ),
                            'slow' => $stats->getTransferTime() > 1,
                            'file' => $caller['file'],
                            'line' => $caller['line'],
                            'hash' => md5((string) $stats->getEffectiveUri()),
                        ]),
                    );
                };
            }
 
            return new Client(
                config: $config,
            );
        };
    }
}

Теперь все, что нам нужно сделать, — это убедиться, что мы регистрируем этот новый Наблюдатель внутри config/telescope.php, и мы должны увидеть, как логируются наши HTTP-запросы.

'watchers' => [
  // all other watchers
  App\\Telescope\\Watchers\\GuzzleRequestWatcher::class,
]

Чтобы протестировать его, создайте тестовый маршрут:

Route::get('/guzzle-test', function () {
    Http::post('<https://jsonplaceholder.typicode.com/posts>', ['title' => 'test']);
});

Когда вы откроете Telescope, вы должны увидеть элемент навигации сбоку под названием HTTP Client, и если вы откроете его, то увидите логи — вы можете проверить заголовки, полезную нагрузку и статус запроса.

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

Комментарии
Подписаться
Уведомить о
guest

0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии
Предыдущая запись
Следующая запись

Давайте дружить
в Telegram

Авторский блог вашего покорного слуги в Telegram про web, программирование, алгоритмы, инструменты разработчика, WordPress, Joomla, Opencart, Symfony, Laravel, Moonshine, фильмы и сериалы