Логи

Раздел телеметрии для структурированных логов приложения: приём через API (описан ниже) и экран просмотра/поиска в интерфейсе.

Просмотр и поиск

Экран логов проекта — /projects/{id}/logs (пункт «Логи» в навигации проекта). Список — самые новые записи сверху; клик по строке раскрывает полное тело, таблицу атрибутов (log_attributes и resource_attrs отдельно) и trace_id/span_id, если запись пришла с ними.

Фильтры

Форма фильтров над списком — без обязательного JavaScript (обычная GET-форма, «Применить» пересобирает страницу):

  • Период — общий контрол окна времени продукта (пресеты 1ч/24ч/7д/30д + произвольный диапазон). Для логов окно дополнительно обрезается сроком хранения (GOTCHA_LOG_RETENTION_DAYS, по умолчанию 14 дней — см. Конфигурацию): выбранный пресет шире фактического TTL даёт укороченное окно, запрос за пределы хранения всё равно вернул бы пусто. Экран показывает это явно — подпись «окно урезано до срока хранения логов (N дней)» рядом с фильтрами, а не молча более короткий список.
  • Уровень — мультивыбор из шести канонических severity (tracefatal, см. канонизацию ниже).
  • Сервис и Окружение — точное совпадение по значениям, которые попали в запись при приёме (service.name/deployment.environment.name из OTLP).
  • Поиск по тексту — подстрочный (регистронезависимый) поиск по телу записи (body).

Активные фильтры сохраняются в адресной строке — ссылкой на текущий срез можно поделиться или обновить страницу без потери состояния.

Фасеты

Сайдбар справа от списка показывает фасеты — топ значений с числом записей в текущем окне и с учётом остальных активных фильтров:

  • Уровень, сервис, окружение — те же три измерения, что и в форме фильтров; клик по значению добавляет его в фильтр (или снимает, если оно уже выбрано). У фасета «Уровень» свой фильтр в подсчёт не входит — он всегда показывает распределение по всем уровням, даже когда один-два уже выбраны (иначе выбранный уровень тут же «съедал» бы весь фасет).
  • Атрибуты — ключи, автоматически обнаруженные в log_attributes записей текущего окна (топ по частоте), каждый со счётчиком. Клик по ключу раскрывает его топ-10 значений (тоже со счётчиками) — сервер считает их «лениво», только для раскрытого ключа, а не для всех сразу. Клик по значению добавляет точечный фильтр по этому атрибуту (см. ниже).

Если фасет не успевает посчитаться в отведённое время (очень широкое окно и объём данных), вместо значений он показывает пометку «слишком много данных» — сузьте окно или добавьте фильтр. Это не ошибка всей страницы: список и остальные фасеты остаются рабочими.

Автокомплит ключей атрибутов

Поле поиска атрибута в сайдбаре подсказывает ключи по мере ввода (typeahead): начните печатать префикс (например, http.) — предложатся совпавшие ключи (http.method, http.status_code, …) с частотой. Ищет в том же окне времени, что и текущий фильтр списка (пресет/произвольный диапазон выше), а не в отдельном фиксированном окне — подсказки совпадают с тем, что видно в сайдбаре и списке. Это JS-улучшение поверх сайдбар-фасетов «Атрибуты», а не отдельная форма: без JavaScript само поле неактивно (никуда не отправляется), но ключи всё равно доступны — списком с частотами прямо в сайдбаре, кликом раскрывающим топ значений (см. выше).

Точечные фильтры по атрибутам

Кроме клика по фасету, срез по конкретному атрибуту можно задать вручную через query-параметр attr, повторяемый для нескольких условий:

/projects/{id}/logs?attr=http.method:GET&attr=http.status_code:500

Значение — key:value, разделитель первого : (само значение может содержать двоеточие, например URL: attr=http.url:http://example.com/x). Чтобы отфильтровать по атрибуту ресурса (resource_attrs, а не log_attributes) — префикс res:: attr=res:host.name:web-01. Несколько attr сужают выборку (логическое И). Поддерживается только точное совпадение — регулярные выражения и «не равно» вне MVP.

Гистограмма объёма

Над списком — гистограмма количества записей по времени, разбитая по severity (тот же стек-стиль, что и у остальных графиков продукта). Ширина корзины подбирается под размах выбранного окна. Если за текущий фильтр нет данных, гистограмма скрывается вместо пустого графика.

Пагинация

Список показывает порцию свежих записей; кнопка «Показать старее» подгружает следующую такую же порцию курсором по времени — счётчика «всего найдено» и глубокой постраничной навигации нет (на объёме логов offset-пагинация с подсчётом общего числа была бы дорогим полным сканом). Собрать точную статистику по всему периоду проще через фасеты и гистограмму, чем листанием списка до конца.

Склейка с ошибками, трейсами и хостами

Экран логов принимает фильтр по trace_id через query-параметр ?trace_id=… — в списке фильтров он показывается отдельным снимаемым чипом (крестик рядом с укороченным ID снимает фильтр, не трогая остальные). Задать его вручную можно и напрямую в адресной строке, но обычно на экран логов попадают уже с готовым срезом по одной из трёх ссылок в остальном интерфейсе:

  • «Логи вокруг события» — на странице ошибки, у выбранного события. Если у события есть trace_id, ссылка ведёт на логи с этим trace_id внутри окна [время события ± 5 мин]; если trace_id нет — сужает тот же временной срез по environment события вместо точного trace-фильтра.
  • «Логи этого трейса» — на waterfall трейса (/traces/{id}). Точный фильтр по trace_id плюс временное окно самого трейса (от начала до конца
    • запас в 1 секунду), чтобы приём не сканировал лишние партиции.
  • «Логи хоста» — на карточке хоста. Не про trace_id: это точечный фильтр по resource-атрибуту (?attr=res:host.name:<имя>), см. «Точечные фильтры по атрибутам» выше. Работает, только если сам источник логов кладёт host.name в resource-атрибуты записи — у собственного gotcha-agent (см. Хосты) логов нет вообще, он шлёт только метрики, поэтому эта ссылка покажет пустой список логов для хостов, подключённых через gotcha-agent. Атрибут появится, только если тот же хост дополнительно шлёт логи через OTel-коллектор с resource.attributes.host.name, выставленным вручную в его конфиге.

Как отправить лог

Принимаются два независимых формата на разных путях одного хоста (см. DSN проекта) — выбирайте тот, что проще для вашего стека.

Аутентификация

Как и /v1/metrics, оба эндпойнта приёма логов авторизуются публичным ключом проекта в заголовке:

Authorization: Bearer <PUBLIC_KEY>

<PUBLIC_KEY> — часть DSN проекта между https:// и @ (см. страницу проекта «Подключение» и SDK и интеграции). Неверный или отсутствующий ключ — ответ 401.

OTLP: POST /v1/logs

Для клиентов с OTel SDK/коллектором — тот же протокол, что у метрик и трасс. Поддерживаются обе стандартные кодировки OTLP:

Content-TypeФормат тела
application/x-protobuf (или application/protobuf)Бинарный protobuf — то, что шлёт экспортёр OTel по умолчанию
application/jsonOTLP/JSON — тот же протокол в JSON

Пример OTLP/JSON без SDK:

curl -X POST https://gotcha.example.com/v1/logs \
  -H "Authorization: Bearer a1b2c3d4e5f6" \
  -H "Content-Type: application/json" \
  -d '{
    "resourceLogs": [{
      "resource": {
        "attributes": [
          {"key": "service.name", "value": {"stringValue": "my-php-app"}},
          {"key": "deployment.environment.name", "value": {"stringValue": "production"}}
        ]
      },
      "scopeLogs": [{
        "logRecords": [{
          "timeUnixNano": "1700000000000000000",
          "severityNumber": 17,
          "severityText": "ERROR",
          "body": {"stringValue": "payment failed: gateway timeout"},
          "attributes": [{"key": "order_id", "value": {"stringValue": "42"}}],
          "traceId": "5b8aa5a2d2c872e8321cf37308d69df2",
          "spanId": "051581bf3cb55c13"
        }]
      }]
    }]
  }'

Ответ при успехе — пустой 200 OK (стандартный пустой OTLP-конверт). service.name/deployment.environment.name (или устаревший deployment.environment) из ресурсных атрибутов идут в поля «сервис» и «окружение» записи, как и у метрик; traceId/spanId сохраняются как есть (hex) — задел под склейку лог↔трейс в будущей версии.

Текст записи (body) проходит тот же безусловный скраб URL, что и сообщения ошибок: query-токены и basic-auth в URL внутри текста вычищаются всегда, независимо от GOTCHA_SCRUB_FREETEXT (см. Приватность) — приватность по умолчанию, как у остальной телеметрии.

Для регулярной отправки удобнее настроить экспортёр логов вашего OTel SDK переменными окружения, аналогично метрикам:

OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=https://gotcha.example.com/v1/logs
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=http/protobuf
OTEL_EXPORTER_OTLP_LOGS_HEADERS=Authorization=Bearer%20a1b2c3d4e5f6

NDJSON: POST /logs

Более простой путь для источников без OTel-экспортёра — произвольный скрипт, агент сбора логов, ручной curl. Тело — newline-delimited JSON: одна запись на строку, без обёртки массивом.

Схема строки:

ПолеОбязательностьОписание
messageобязательноТекст записи. Пустая или отсутствующая строка — запись строки пропускается целиком (не роняет остальной батч).
levelопциональноТекстовый уровень (info, warn, err, critical и т. п. — см. канонизацию ниже). Пусто или нераспознано → info.
timestampопциональноRFC3339-строка ("2026-08-18T12:00:00Z") либо unix-время в секундах числом (дробная часть допускается, 1755518400.5). Отсутствует, пусто, не разобрано или не позже эпохи — берётся время приёма сервером.
attributesопциональноПроизвольный JSON-объект: значения-строки/числа/bool копируются как есть, вложенные объекты/массивы сериализуются обратно в JSON-строку.
trace_idопциональноСтрока (без проверки формата, в отличие от OTLP), капается до 64 символов — длиннее обрезается, как и у прочих полей.
span_idопциональноТо же самое.

NDJSON-записи не несут ресурсных атрибутов — поля «сервис» и «окружение» у них не заполняются (это может измениться в будущей версии); если нужна эта привязка уже сейчас, используйте OTLP.

Пример:

curl -X POST https://gotcha.example.com/logs \
  -H "Authorization: Bearer a1b2c3d4e5f6" \
  --data-binary $'{"message":"payment failed: gateway timeout","level":"error","attributes":{"order_id":42}}\n{"message":"retrying in 5s","level":"info"}\n'

Ответ при успехе — 200 OK с телом {"accepted": N}, где N — число записей, реально принятых в квоту и сохранённых (может быть меньше числа строк в теле, если часть отклонена по квоте — см. ниже). Строка, которая не разбирается как JSON, или пустой message — строка молча пропускается и не входит в N, но не роняет остальной батч.

Что канонизируется в severity

UI и правила алертинга (когда появятся) работают с единым каноном из шести уровней, к которому приводится любой источник:

trace, debug, info, warn, error, fatal

Из OTLP SeverityNumber (1–24, спецификация OTel): 1–4 → trace, 5–8 → debug, 9–12 → info, 13–16 → warn, 17–20 → error, 21–24 → fatal. Число вне диапазона (0, отрицательное, >24) — не ошибка формата, запись не теряется, а получает нейтральный info.

Из текста (NDJSON level, а также OTLP SeverityText — но только как запасной путь, если SeverityNumber не заполнен): trace; debug; info; warn/warning; error/err; fatal/critical (регистр не важен). Числовая строка ("17") трактуется как SeverityNumber. Пустой или нераспознанный текст — тоже info.

Исходные severity_number/severity_text сохраняются рядом с канонической severity — для отладки и аудита, не только сам канон.

Лимиты

  • До 10000 записей в одном запросе (и OTLP, и NDJSON) — лишние в пределах запроса отбрасываются, батч не падает целиком.
  • До 64 КиБ на текст записи (body/message) — длиннее обрезается.
  • NDJSON: строка длиннее 256 КиБ отбрасывается без попытки разбора (и не считается ошибкой всего запроса).
  • До 64 атрибутов на запись, ключ — до 64 символов, значение — до 200; при превышении оставляются первые 64 по отсортированным ключам (детерминированно, не случайно).
  • Общий размер тела запроса ограничен той же переменной, что и у остальной телеметрии, GOTCHA_MAX_EVENT_BYTES (см. Конфигурацию) — тело крупнее лимита отклоняется с 413.
  • Превышение частоты запросов на DSN или исчерпание квоты — 429.

Окно приёма

Запись со временем старше 90 дней в прошлом или больше суток в будущем относительно момента приёма прижимается к границе этого окна (не отбрасывается совсем, в отличие от метрик — тело и уровень записи остаются полезны, даже если у клиента разъехались часы).

Хранение, квота и настройки

Логи хранятся в ClickHouse по общей политике инстанса, отдельной страницы настроек на уровне проекта пока нет. Срок хранения задаётся оператором через GOTCHA_LOG_RETENTION_DAYS (по умолчанию 14 дней — логи объёмнее событий, поэтому дефолт короче; 0 — хранить бессрочно). Приём логов считается в месячную квоту организации: лимит по умолчанию — GOTCHA_DEFAULT_LOG_QUOTA, тонко настраивается в «Настройках организации → Использование и лимиты» (см. Конфигурацию). При исчерпании квоты /v1/logs и /logs отвечают 429, уже принятые записи не удаляются.