Разбор логов одной строкой: конвейеры на практике

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

Конвейер анализа логов — это типовая цепочка grep | awk | sort | uniq -c | sort -rn: отфильтровать нужное, вырезать поле, посчитать вхождения, отсортировать по частоте. Освоив шаблон, вы разбираете логи без выгрузки в БД.

Сервер отдаёт 500-е? Кто-то перебирает пароли? Какие URL самые популярные? Откуда больше всего трафика? Все эти вопросы решаются за секунды прямо на сервере связкой стандартных утилит — не нужны ни Elasticsearch, ни выгрузка логов на ноутбук. Это базовый навык дежурного инженера и системного администратора.

Формат access.log и наша подопытная строка

Возьмём типовой combined-формат веб-сервера. Одна строка лога:

203.0.113.7 - - [27/Jun/2026:10:15:42 +0000] "GET /api/users HTTP/1.1" 200 1534 "-" "curl/8.4"

Разбиение по пробелам даёт удобные поля для awk: $1 — IP-адрес клиента, $7 — URL запроса, $9 — код ответа HTTP, $10 — размер ответа в байтах. На этих позициях держится почти весь разбор.

Топ IP-адресов по числу запросов

Кто шлёт больше всего запросов? Берём первое поле, группируем, сортируем по убыванию, показываем первые 10:

awk '{ print $1 }' access.log | sort | uniq -c | sort -rn | head -10

Вывод:

  4821 203.0.113.7
  1903 198.51.100.42
   774 192.0.2.15
   201 203.0.113.99

Это тот самый шаблон: awk извлёк поле → sort сгруппировал → uniq -c посчитал → sort -rn расставил по убыванию → head обрезал топ. Подозрительно большое число от одного IP — кандидат на блокировку или rate-limit.

Распределение кодов ответа

Сколько каких HTTP-статусов отдал сервер? Девятое поле — код ответа:

awk '{ print $9 }' access.log | sort | uniq -c | sort -rn

Вывод:

 18342 200
  1204 404
   531 301
    88 500
    12 503

Рост 5xx — сигнал, что серверу плохо. Сразу вытащим, какие именно запросы падают с ошибками сервера, с помощью awk-условия по диапазону кода:

# строки с кодом 5xx: URL и статус
awk '$9 >= 500 { print $9, $7 }' access.log | sort | uniq -c | sort -rn | head

# то же через grep по полю кода (быстрее на огромных файлах)
awk '$9 ~ /^5/ { print $7 }' access.log | sort | uniq -c | sort -rn | head

Самые популярные URL

Какие эндпоинты нагружают сервис сильнее всего? Седьмое поле — путь запроса:

awk '{ print $7 }' access.log | sort | uniq -c | sort -rn | head -20

Часто полезно сначала отсечь статику (картинки, css), чтобы увидеть реальные API-вызовы:

grep -vE '\.(png|jpg|css|js|ico)' access.log \
  | awk '{ print $7 }' | sort | uniq -c | sort -rn | head -20

Здесь grep -v выбрасывает строки со статикой, а -E включает расширенную регулярку для группы расширений. Обратный слэш в конце строки — перенос длинной команды на следующую строку.

Поиск перебора паролей и фильтр по времени

Признак брутфорса — шквал запросов к /login с большим числом 401/403. Совместим фильтр по URL и по коду:

# кто долбится в /login с ошибками авторизации
grep '/login' access.log \
  | awk '$9 == 401 || $9 == 403 { print $1 }' \
  | sort | uniq -c | sort -rn | head

Анализ часто нужен за конкретный интервал. Поле времени в квадратных скобках — пятый «столбец» по пробелам, но проще выдернуть час подстрокой через grep или вырезать cut:

# запросы за 10-й час (по совпадению строки времени)
grep '27/Jun/2026:10:' access.log | wc -l

# распределение запросов по часам: вырезать HH из поля времени
awk '{ print $4 }' access.log | cut -d: -f2 | sort | uniq -c

Суммирование и средние через awk

uniq считает только количество строк. Когда нужно просуммировать значения (например, отданный трафик), берём накопление в awk. Десятое поле — размер ответа:

# суммарный объём отданных данных в мегабайтах
awk '{ sum += $10 } END { printf "%.1f MB\n", sum/1024/1024 }' access.log

# трафик по каждому IP (сумма байт на адрес), топ-5
awk '{ bytes[$1] += $10 }
     END { for (ip in bytes) print bytes[ip], ip }' access.log \
  | sort -rn | head -5

Это уже мини-аналитика: ассоциативный массив bytes[$1] += $10 суммирует трафик по ключу-IP, а финальный sort -rn выводит самых «тяжёлых» клиентов.

Живой мониторинг: tail -f в конвейере

Анализ можно вести в реальном времени, подавая в конвейер «хвост» растущего файла:

# видеть только ошибки по мере их появления
tail -f access.log | grep --line-buffered -E ' (4|5)[0-9]{2} '

# считать 5xx за последнюю минуту по мере поступления (приблизительно)
tail -f access.log | awk '$9 ~ /^5/ { c++; print c, $7 }'

Флаг --line-buffered у grep важен: иначе grep буферизует вывод блоками и в реальном времени вы ничего не увидите, пока не накопится буфер.

Как это работает под капотом

Весь разбор держится на том, что лог — это структурированный текст с устойчивыми позициями полей, а конвейер запускает все звенья одновременно, передавая данные через каналы ядра. grep отсеивает строки на лету и почти не ест память; awk разбивает строку на поля и либо извлекает нужное, либо накапливает суммы в ассоциативном массиве; sort — единственное «узкое место», потому что обязан прочитать весь поток, прежде чем выдать порядок (на гигабайтных логах он буферизует на диск). Поэтому порядок звеньев влияет на скорость: чем раньше в конвейере отсечь лишнее через grep, тем меньше данных дойдёт до дорогого sort. Шаблон sort | uniq -c | sort -rn — это, по сути, операция «GROUP BY ... ORDER BY count DESC» из SQL, разложенная на потоковые утилиты: первый sort группирует одинаковое рядом, uniq -c схлопывает и считает, второй sort расставляет по частоте. Понимание этой аналогии с SQL помогает быстро собирать новые запросы под любой формат лога.

Частые ошибки

  • Неверный номер поля. Если в логе IP, имя пользователя или URL содержат пробелы (или формат не combined), позиции $7/$9 сдвигаются. Сверяйте поля на реальной строке прежде чем считать.
  • uniq без sort. Тот же подвох, что и раньше: дубликаты IP не схлопнутся, если перед uniq не стоит sort.
  • Лексикографический sort вместо -n. Счётчики «1000» и «999» отсортируются неверно без -rn: будет сравнение по символам.
  • grep без --line-buffered в tail -f. Живой мониторинг «зависает» — вывод копится в буфере и не показывается вовремя.
  • Ротация логов. Свежие события могут уже уехать в access.log.1 или access.log.1.gz. Для архивов используйте zcat file.gz | ... или zgrep.

Итоги

  • Базовый шаблон разбора: grep (фильтр) | awk (поле/сумма) | sort | uniq -c | sort -rn | head.
  • В combined-логе: $1 — IP, $7 — URL, $9 — код ответа, $10 — размер.
  • awk '$9 ~ /^5/' и awk '$9 >= 500' вытаскивают ошибки сервера; условия по полю фильтруют точечно.
  • Для сумм трафика — накопление в ассоциативный массив awk, а не uniq.
  • Живой анализ — tail -f | grep --line-buffered; для архивов — zcat/zgrep.
  • Чем раньше отфильтровать данные в конвейере, тем быстрее отработает дорогой sort.
Проверьте себя
1. Что делает конвейер awk '{ print $1 }' access.log | sort | uniq -c | sort -rn | head ?
Aпоказывает первые строки лога
Bвыводит IP-адреса по алфавиту
Cпоказывает топ IP-адресов по числу запросов
Dсуммирует переданные байты по IP
2. Почему uniq -c не подходит для подсчёта суммарного трафика (поля размера) по каждому IP?
Auniq не умеет работать с числами
Buniq -c считает количество строк, а не сумму значений в поле
Cuniq требует флага -n для чисел
Duniq игнорирует первое поле