Как читать логи в Loki¶
Шпаргалка по LogQL: где смотреть, как фильтровать, как коррелировать с
трейсами. Полный reference по формату логов — в
../conventions/logging.md.
Содержание¶
- Где логи
- Основы LogQL
- Структурированный поиск по JSON
- Rate и агрегация
- Временные диапазоны
- Типовые запросы
- Correlation с traces
- Локальные логи (dev)
- Troubleshooting
- Anti-patterns
- Связанные разделы
Где логи¶
Все prod-логи централизованы в Loki. UI — Grafana → Explore → datasource Loki.
Все сервисы пишут JSON в stdout, контейнерный log driver кладёт поток
в Loki. Формат записи зафиксирован в
../conventions/logging.md: service,
request_id, correlation_id, trace_id, user_id, route, level
и прочее — обязательные структурные поля.
Основы LogQL¶
Базовая форма: {<label-selector>} <pipeline>.
Выбор потока по labels¶
Labels — это метаданные Loki (service, pod, namespace), не поля JSON-payload'а. Индексируются заранее, поэтому фильтруй сначала по ним.
Текстовый фильтр¶
{service="review"} |= "error" # substring
{service="review"} != "health" # отрицание
{service="review"} |~ "(?i)panic" # regex, case-insensitive
{service="review"} !~ "^debug" # отрицание regex
Комбинации¶
Структурированный поиск по JSON¶
Каждая запись — JSON; распарсить можно через | json:
Дальше — фильтры по полям:
{service="review"} | json | user_id="42"
{service="review"} | json | status_code >= 500
{service="review"} | json | duration_ms > 1000
{service="review"} | json | level="ERROR"
Комбинируй:
Line format для удобочитаемости¶
Rate и агрегация¶
Count за период¶
Rate per second¶
Группировка¶
Топ-N по error message¶
Временные диапазоны¶
- UI по-умолчанию — последний час. Меняй через селектор в правом верхнем углу Grafana.
- URL-параметры —
&from=1713830400000&to=1713834000000(ms epoch). Удобно шарить ссылку с инцидентом. - Long-term (> 30 дней) — может быть недоступно из-за Loki retention. Для исторического анализа — экспорт или отдельный storage.
Типовые запросы¶
Ошибки конкретного сервиса за 15 минут¶
Все логи конкретного user за час¶
Полезно при обращении в поддержку «у пользователя 42 что-то не работает» — собираешь полную трассу его действий по всем сервисам.
Все логи одного trace¶
Переход с trace на логи — основной способ распутать distributed
инцидент. См. ./read-traces.md.
Latency spike debug¶
{service="review"}
| json
| duration_ms > 500
| line_format "{{.time}} {{.route}} {{.duration_ms}}ms {{.request_id}}"
Outbox lag в конкретный момент¶
Kafka consumer ошибки¶
Все HTTP 5xx за час¶
{service=~".+"}
| json
| status_code >= 500
| line_format "{{.service}} {{.route}} {{.status_code}} {{.request_id}}"
Correlation с traces¶
Loki ↔ Tempo интеграция настроена в Grafana datasource'ах.
- Из Tempo в Loki. Открыл trace → каждый span имеет кнопку
Logs for this span. Под капотом запрос вида
{service="<span.service>"} | json | trace_id="<trace>". - Из Loki в Tempo. В JSON-записи, где виден
trace_id, в раскрытом представлении кнопка View trace открывает trace в Tempo.
Это базовый workflow отладки распределённого запроса:
- Grafana dashboard показывает аномалию (p99 latency spike).
- Exemplar на графике → прыжок в Tempo с конкретным trace.
- Из trace → в Loki по
trace_id→ полный контекст каждого span'а.
Локальные логи (dev)¶
Когда Loki не доступен (локальная разработка, standalone-запуск):
docker compose logs -f review | jq 'select(.level=="ERROR")'
docker compose logs -f review | jq 'select(.user_id==42)'
docker compose logs -f review | jq 'select(.trace_id=="abc123")'
Для plain-text в dev используется SERVICE_LOG_FORMAT=text, если сервис
поддерживает (см. ../conventions/logging.md).
В prod — только JSON, jq не нужен.
Troubleshooting¶
«Нет данных за N минут»¶
- Проверь, что селектор labels соответствует живым потокам:
{service=~".+"}без других фильтров за те же 5 минут. - Если и этого нет — проблема в ingester Loki, эскалируй SRE.
Slow queries¶
- Сузь
time rangeдо минимально нужного. - Добавь label-фильтр (
namespace,service) — Loki не ищет в потоках, которые ты не выбрал. - Вынеси
| jsonпосле всех текстовых фильтров:|=и!=отрабатывают до парсинга и дёшевы.
«No logs found»¶
- Проверь правильность имени сервиса в label:
{service="review"}, не{service="review-service"}. Значение label приходит изnewLogger(...)With("service", "review")— см.../conventions/logging.md. - Проверь time range. Инцидент был час назад, а ты смотришь «последние 15 минут» — данных нет.
Только часть полей доступна после | json¶
Loki по умолчанию извлекает «плоский» JSON. Вложенные объекты надо доставать явно:
Anti-patterns¶
{service=~".+"}на всё без time-window. Запрос идёт по всему кластеру на длительный период — timeout'ится и нагружает ingester.- Полнотекстовый regex без label-selector.
|~ "foo"на всём — то же самое. - Поиск по
msgsubstring вместо label. Если одна и та же строка пишется в десятке мест — фильтруй по дополнительному JSON-полю (event_type,route), а не по кускуmsg. - Зависимость от поля, которого нет.
| json | err != ""падает, если в записи поляerrнет. Используй| err=""наоборот — LogQL нормально трактует отсутствие. - Логика через логи. Если алерт рассчитывается LogQL-ом на каждое срабатывание — это гораздо дороже, чем counter-метрика. Метрики — в Prometheus, логи — для контекста.
Связанные разделы¶
../conventions/logging.md— формат JSON-записи, обязательные поля, PII-маскирование.../conventions/observability.md— как связаны logs/metrics/traces../read-traces.md— Tempo для distributed tracing.