Содержание

Логи

Каждый запрос, дошедший до API гейта, попадает в журнал. Запись идёт пакетно и вне горячего пути: запрос не ждёт базы.

Что записывается

Кто и куда: ключ клиента (имя денормализовано — удаление ключа не должно стирать историю его расхода), провайдер, метка ключа провайдера, запрошенная и обслужившая модели, эндпоинт, формат, операция, признак потока.

Как прошло: код ответа, тип и текст ошибки, задержка, время до первого байта, число попыток, цепочка фолбэков, IP и User-Agent клиента.

Сколько: входные, выходные, суммарные токены, чтение и запись кэша, reasoning-токены, длительность аудио, себестоимость и списанное.

Тела: полный JSON запроса и ответа, если включено. Вызовы инструментов — отдельной таблицей, с направлением: «предложен клиентом» или «вызван моделью».

Две колонки стоимости

cost_base_usd — чего запрос стоил по цене модели. cost_usd — что списано с ключа, то есть та же величина, умноженная на наценку владельца.

Одно число не смогло бы ответить на оба вопроса: обзор — про то, чего трафик стоит развёртыванию, и он не должен дёргаться, когда кому-то поднимают наценку; страница ключа — про то, что должен его владелец. Каждая страница читает ту колонку, которую имеет в виду.

Две колонки модели

requested_model — как адресовал клиент. resolved_model — что ушло провайдеру. Подробно — Адресация моделей.

Цена считается по второму. Страница цен ищет неоплаченные модели тоже по второму. Разбивки по моделям — на обзоре, у ключа, на странице пользователя — по первому: там интересно, чем пользуются, а не как это разложилось внутри.

Разбор тела в диалог

Страница запроса показывает сохранённое тело диалогом: роли, текст, вызовы инструментов с разобранными аргументами, ответы инструментов, рассуждения модели под спойлером, вложения без инлайна base64.

Сохранённое тело — это wire-формат, о котором три провайдера не договорились: OpenAI кладёт содержимое строкой, Anthropic списком типизированных блоков, Responses API третьим способом. В сыром виде это экран JSON, в середине которого где-то лежит то, за чем пришли.

Понимаются chat completions, Anthropic Messages и Responses API. Тело, которое разобрать не удалось, даёт пустой результат — и страница показывает только сырой JSON, он остаётся на месте всегда. Это вид над строкой, а не её замена.

Аргументы инструментов OpenAI присылает строкой с JSON внутри; напечатанная как есть, она даёт строку экранированных кавычек — ровно то, ради избавления от чего этот вид и существует. Поэтому они разбираются и печатаются с отступами.

Скорость

Длительность запроса показывается в секундах, рядом — выходных токенов в секунду.

Скорость считается по всему запросу, а не по одной генерации: время до первого байта гейт записывает не для всех ответов, а величина, у которой знаменатель молча меняется между строками, хуже стабильно консервативной. То есть это то, что почувствовал вызывающий, а не decode rate, который назвал бы провайдер.

Длительность аудио

Для модели, тарифицируемой за минуту, плитка «Токены» заменяется на «Аудио»: секунды, минуты и токены рядом (запрос может иметь и то и другое). В списке секунды показываются в колонке токенов.

Ноль у аудио-запроса означает, что длительность измерить не удалось — см. Цены.

Что не записывается

  • Тело потокового ответа. Буферизация сломала бы выдачу. Токены, стоимость и вызовы инструментов при этом считаются полностью — по служебным полям потока, который для этого прослушивается на проходе.
  • Тела не-JSON. Multipart-загрузка — это байты аудио или картинки; хранить их значило бы раздуть таблицу без аналитической пользы.
  • Тела больше предела. max_logged_body_bytes, по умолчанию 256 КиБ. Тело сверх предела не сохраняется; всё остальное о запросе записывается.
  • /healthz и /api/show. Ни один не несёт трафика, за который кто-то платит или отвечает: один проба живости, другой вопрос о возможностях, который некоторые клиенты задают перед каждым запросом. Их запись похоронила бы журнал под строками, которые никто не читает, и раздула бы все счётчики по ключам на тех страницах, которые читают.

Настройки

В панели, применяются гейтом без перезапуска:

Настройка Смысл
Писать тело запроса
Писать тело ответа
Предел размера тела по умолчанию 256 КиБ
Срок хранения по умолчанию 30 дней

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

Отбор

Журнал фильтруется по подстроке (искать можно и по запрошенной модели, и по обслужившей), провайдеру, ключу, коду ответа и окну времени. Фильтры применяются на сервере и не меняют URL — страница подгружает строки htmx-ом.