Разбор логов одной строкой: конвейеры на практике
Собираем изученные инструменты в боевые однострочники: фильтрация, извлечение полей, группировка и сортировка для анализа реальных логов.
Конвейер анализа логов — это типовая цепочка
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.