Прочитайте, что SDK пишет в ваш лог
PHPДайте SDK логгер и прочитайте события, которые он пишет на каждый запрос, — маршрут, статус и время, но никогда ключ или тело.
5 мин чтения
SDK ведёт журнал того, что делает: каждый запрос и ответ на него, каждый повтор, каждый отказ и сбой, чтения ожидания и вебхуки, которые он отклонил. Он пишет журнал в логгер, который вы ему дали, а без логгера — никуда.
Дать SDK логгер
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;
}
};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.
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.request | debug | Перед каждой попыткой запроса |
slen.response | debug | Ответ 2xx, с его статусом, временем вызова и идентификатором запроса |
slen.retry | warning | Попытка, которую повторят, с паузой и причиной: no_answer, rate_limited для 429, unavailable для 503 |
slen.refused | info | Ответ 4xx, который отклоняет вызов: деньги, политика, проверка запроса, конфликт, неизвестный клиент |
slen.unauthorized | error | 401 или 403 о самом ключе — insufficient_scope, invalid_api_key, not_a_member: настройка, которую надо исправить |
slen.failed | error | 5xx, ответ, который нельзя прочитать, или отсутствие ответа после всех попыток, с типом ошибки и числом попыток |
slen.wait.poll | debug | Чтение ожидания, с прочитанным статусом и паузой перед следующим |
slen.wait.poll_failed | warning | Чтение ожидания, которое не удалось по временной причине; ожидание продолжается |
slen.webhook.rejected | warning | Доставка вебхука, подпись или тело которой отклонены, с причиной — Вебхуки |
PHP SDK пишет ещё одно событие: slen.config.timeout_not_applied, предупреждение, когда он нашёл HTTP-клиент, которому не может передать свои таймауты, — Конфигурация, — и когда вызов просит таймаут, которого его HTTP-клиент не принимает. LogMessages хранит имена событий как константы.
Поля каждого события — так, как их несёт контекст записи:
{
"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, — оно может нести текст, который написал флоу; журнал пишет только её код;
значений пути и параметров запроса.
Поэтому журнал можно отправлять в лог вашего приложения как есть.
Что читать дальше
Спаны — одна запись о каждом запуске, с его деньгами, для ваших трассировок.
Повторы и таймауты — о чём сообщает
slen.retry.Конфигурация — другие опции SDK.