Прочитайте, что SDK пишет в ваш лог

PHP

Дайте SDK логгер и прочитайте события, которые он пишет на каждый запрос, — маршрут, статус и время, но никогда ключ или тело.

5 мин чтения

SDK ведёт журнал того, что делает: каждый запрос и ответ на него, каждый повтор, каждый отказ и сбой, чтения ожидания и вебхуки, которые он отклонил. Он пишет журнал в логгер, который вы ему дали, а без логгера — никуда.

Дать SDK логгер

PHP
use Psr\Log\AbstractLogger;

// Stands in for the PSR-3 logger of your application: it prints each event with
// the route and, for an answer, its status.
$logger = new class extends AbstractLogger {
    public function log($level, string|\Stringable $message, array $context = []): void
    {
        $status = isset($context['status']) ? ' ' . $context['status'] : '';

        echo "{$level} {$message} {$context['method']} {$context['route']}{$status}", PHP_EOL;
    }
};
PHP
use Slen\Sdk\SlenConfig;
use Slen\Sdk\SlenSdk;

$slen = SlenSdk::create(new SlenConfig(
    apiKey: (string) getenv('SLEN_API_KEY'),
    logger: $logger,
));

Параметр logger принимает любой логгер PSR-3. Каждое событие — сообщение с контекстом его полей. В приложении на Laravel SLEN_LOG_CHANNEL называет канал журнала, а без неё журнал идёт в лог вашего приложения — Интеграция с Laravel.

PHP
use Slen\Sdk\Modules\Operations\Options\ExecuteOptions;

$execution = $slen->operations()->execute(
    'generate_email',
    ['messages' => [['role' => 'user', 'content' => 'Write a short follow-up email.']]],
    new ExecuteOptions(idempotencyKey: 'order-77:email'),
);

Запуск пишет два события, и логгер примера их печатает:

debug slen.request POST /v1/operations/execute
debug slen.response POST /v1/operations/execute 200

Логгер, который выбрасывает исключение, не ломает вызов, о котором пишет.

Логгер для одного запроса

Серверу, который обслуживает много запросов сразу, может понадобиться журнал каждого из них в своём логгере — например, в логгере, который несёт идентификатор запроса. Собранный SDK не меняется, поэтому ему не дают другой логгер: он делает копию, которая пишет в него.

SlenSdk::withLogger() возвращает новый SDK, который пишет в переданный вами логгер. Копия отправляет запросы через тот же HTTP-клиент и записывает спаны тем же записывающим. SDK, из которого её сделали, продолжает писать в свой логгер. Соберите SDK один раз и делайте копию для каждого запроса, которому она нужна, — Интеграция с Laravel.

События

СобытиеУровеньКогда
slen.requestdebugПеред каждой попыткой запроса
slen.responsedebugОтвет 2xx, с его статусом, временем вызова и идентификатором запроса
slen.retrywarningПопытка, которую повторят, с паузой и причиной: no_answer, rate_limited для 429, unavailable для 503
slen.refusedinfoОтвет 4xx, который отклоняет вызов: деньги, политика, проверка запроса, конфликт, неизвестный клиент
slen.unauthorizederror401 или 403 о самом ключе — insufficient_scope, invalid_api_key, not_a_member: настройка, которую надо исправить
slen.failederror5xx, ответ, который нельзя прочитать, или отсутствие ответа после всех попыток, с типом ошибки и числом попыток
slen.wait.polldebugЧтение ожидания, с прочитанным статусом и паузой перед следующим
slen.wait.poll_failedwarningЧтение ожидания, которое не удалось по временной причине; ожидание продолжается
slen.webhook.rejectedwarningДоставка вебхука, подпись или тело которой отклонены, с причиной — Вебхуки

PHP SDK пишет ещё одно событие: slen.config.timeout_not_applied, предупреждение, когда он нашёл HTTP-клиент, которому не может передать свои таймауты, — Конфигурация, — и когда вызов просит таймаут, которого его HTTP-клиент не принимает. LogMessages хранит имена событий как константы.

Поля каждого события — так, как их несёт контекст записи:

JSON
{
  "slen.request": { "method": "POST", "route": "/v1/operations/execute", "attempt": 1 },
  "slen.retry": [
    { "method": "POST", "route": "/v1/operations/execute", "attempt": 1, "delay_ms": 214, "reason": "no_answer", "status": null },
    { "method": "POST", "route": "/v1/operations/execute", "attempt": 2, "delay_ms": 2000, "reason": "rate_limited", "status": 429 },
    { "method": "POST", "route": "/v1/operations/execute", "attempt": 1, "delay_ms": 812, "reason": "unavailable", "status": 503 }
  ],
  "slen.response": { "method": "POST", "route": "/v1/operations/execute", "status": 200, "duration_ms": 1843, "request_id": "0d3c1b9e-5a47-4f0e-9a36-2f6c8d1e7b42" },
  "slen.refused": { "method": "POST", "route": "/v1/customers/{externalUserId}/charge", "status": 402, "error_code": "insufficient_credits", "request_id": "9c2d7e41-6b0a-4f3e-8d15-2a7f9e0c4b63" },
  "slen.failed": { "method": "POST", "route": "/v1/operations/execute", "status": null, "error_code": null, "error_type": "Connection", "request_id": null, "attempts": 3 },
  "slen.wait.poll": { "execution_id": "74438200973594800", "status": "running", "next_poll_ms": 2000 },
  "slen.wait.poll_failed": { "execution_id": "74438200973594800", "failures_in_a_row": 1, "error_type": "RateLimit" },
  "slen.webhook.rejected": { "reason": "no_matching_signature", "age_seconds": 2 }
}
  • route — шаблон пути, а не путь с его значениями: /v1/customers/{externalUserId}/charge, никогда не идентификатор вашего клиента.

  • duration_ms ответа — время всего вызова, вместе с повторами и паузами.

  • error_type — имя типа ошибки без окончания — Connection для ConnectionException и ConnectionError, — одно слово в обоих SDK.

  • execution_id пишется: по нему выполнение ищут в консоли.

Чего журнал никогда не несёт

  • API-ключа и заголовка Authorization;

  • секрета подписи ваших вебхуков;

  • ключа идемпотентности — в нём идентификаторы вашей собственной работы;

  • тел запросов и ответов: входа и выхода флоу, его сообщений и промптов;

  • тела доставки вебхука;

  • сообщения ошибки, которым ответил API, — оно может нести текст, который написал флоу; журнал пишет только её код;

  • значений пути и параметров запроса.

Поэтому журнал можно отправлять в лог вашего приложения как есть.

Что читать дальше