Масштабный разбор веб-журналов и журналов приложений
Извлекайте коды состояния, задержки и поля клиентов из журналов доступа с помощью grep, cut и awk
«Масштабный разбор веб-журналов и журналов приложений» — бесплатный урок DevOps Bootcamp на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения DevOps Bootcamp, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс DevOps Bootcamp содержит 4 уроков всего.
Что такое журнал доступа к веб-серверу
Каждый HTTP-сервер — Apache, Nginx, Caddy — записывает одну строку в журнал доступа для каждого запроса. Понимание структуры этих строк — основа любого анализа журналов.
Типичная строка объединённого формата журнала (CLF) выглядит так:
- IP-адрес клиента — кто выполнил запрос
- Временная метка — когда это произошло
- Строка запроса — метод, путь и протокол
- Код состояния — ответ HTTP (200, 404, 500…)
- Отправленные байты — размер тела ответа
- Referer — исходная страница
- User-Agent — строка браузера или бота
Пример строки из /var/log/nginx/access.log:
192.168.1.10 - alice [11/Jun/2026:14:32:01 +0000] "GET /api/orders HTTP/1.1" 200 1482 "-" "curl/7.88.1"
При большом объёме трафика эти файлы разрастаются до миллионов строк в день. Цель этого урока — эффективно извлекать из них поля, фильтровать и агрегировать их с помощью стандартных инструментов BASH.
Просмотр текущего журнала с помощью tail и grep
Перед созданием любого конвейера изучите журнал, чтобы понять его структуру. tail позволяет наблюдать поток данных в реальном времени, а grep сразу отбирает нужные строки.
Распространённые варианты:
tail -n 1000 access.log— последние 1000 строкtail -f access.log— отслеживание в реальном времениtail -f access.log | grep '" 5'— только ошибки 5xx по мере их появления
Важно помнить, что grep сопоставляет шаблон со всей строкой, поэтому существенное значение имеет привязка шаблона. Сопоставление ' 500 ' (с пробелами) предотвращает случайное совпадение с путем URL, содержащим строку 500.
#!/usr/bin/env bash
# Watch only HTTP 5xx errors arriving in real time
tail -f /var/log/nginx/access.log \
| grep --line-buffered '" 5[0-9][0-9] 'Извлечение кода состояния с помощью cut
cut разделяет каждую строку по разделителю и выводит выбранные поля. В объединённом формате журнала код состояния находится в поле 9 при разделении по пробелам, но кавычки вокруг строки запроса означают, что безопаснее вести отсчёт от известного разделителя.
Надёжный приём: поскольку строка запроса всегда заключена в кавычки, код состояния всегда является первым элементом после закрывающей кавычки поля запроса. Команда cut -d'"' -f3 выделяет всё после кавычки запроса, а затем вторая команда cut -d' ' -f2 выбирает код состояния.
Такое двухэтапное применение cut — классический приём для журналов CLF: быстро и без внешних зависимостей.
#!/usr/bin/env bash
# Print only the HTTP status code from each log line
# Input format: ... "GET /path HTTP/1.1" 200 1482 ...
cut -d'"' -f3 /var/log/nginx/access.log \
| cut -d' ' -f2 \
| sort \
| uniq -c \
| sort -rnПодсчёт кодов состояния с помощью awk
awk мощнее, чем cut, поскольку может сохранять состояние между строками. Идиоматичный способ подсчёта вхождений — ассоциативный массив с ключами, соответствующими интересующему значению.
В CLF поле $9 (нумерация начинается с 1, разделитель — пробел) содержит код состояния. awk обрабатывает каждую строку, увеличивает счётчик, а затем выводит отсортированную сводку в блоке END.
Почему стоит предпочесть awk конструкции cut | sort | uniq -c? Потому что awk выполняет всё за один проход, не сортируя предварительно весь файл, что критически важно, когда журнал занимает сотни гигабайт.
#!/usr/bin/env bash
# Count HTTP status codes in a single awk pass
awk '{ count[$9]++ }
END {
for (status in count)
printf "%6d %s\n", count[status], status
}' /var/log/nginx/access.log \
| sort -rnФильтрация ошибок и извлечение IP-адресов клиентов
Одна из самых распространённых операционных задач — определить, какие IP-адреса клиентов создают больше всего ошибок. Здесь объединяются фильтрация (только строки с ошибками) и извлечение поля (IP-адрес в поле 1).
Стратегия конвейера:
- Используйте
awk, чтобы за один шаг отфильтровать строки по диапазону кодов состояния и извлечь IP-адрес — не выполняйте отдельный проход с помощьюgrep - Передайте результат в
sort | uniq -c | sort -rn | head, чтобы быстро получить список лидеров
Этот шаблон достаточно быстр, чтобы обрабатывать файл журнала размером 10 ГБ на одном сервере, не загружая файл в память.
#!/usr/bin/env bash
# Top 10 IPs generating HTTP 4xx or 5xx errors
awk '$9 ~ /^[45][0-9][0-9]$/ { print $1 }' \
/var/log/nginx/access.log \
| sort \
| uniq -c \
| sort -rn \
| head -10Разбор задержки ответа в журналах приложений
Серверы приложений (Rails, Gunicorn, Express с morgan и другие) часто записывают длительность запроса. Nginx можно настроить на добавление $request_time в качестве дополнительного поля в конце каждой строки.
Пример пользовательского формата журнала Nginx в nginx.conf:
log_format timed '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" rt=$request_time';
Если задержка записывается в журнал, можно использовать awk, чтобы вычислять среднее значение, максимум и приближённые процентили для миллионов запросов, не загружая данные в базу данных.
#!/usr/bin/env bash
# Compute average and max request_time from Nginx timed log
# Assumes last field is rt=<seconds> e.g. rt=0.042
awk '{
# Extract numeric value after rt=
n = split($NF, a, "=")
if (n == 2 && a[1] == "rt") {
t = a[2] + 0
sum += t
count++
if (t > max) max = t
}
}
END {
if (count > 0)
printf "Requests: %d Avg: %.4fs Max: %.4fs\n", count, sum/count, max
}' /var/log/nginx/timed_access.logПостроение гистограммы задержки с помощью awk
Одно среднее значение скрывает задержки в хвосте распределения. Гистограмма показывает распределение: выполняются ли большинство запросов быстро, а несколько — очень медленно (длинный хвост), или распределение равномерно.
Секрет заключается в том, чтобы отнести каждое значение к округлённому диапазону с помощью целочисленной арифметики внутри awk. Умножение на 1000 (перевод секунд в миллисекунды) с последующим целочисленным делением даёт аккуратные границы диапазонов.
В результате получается текстовая гистограмма, которую можно читать прямо в терминале. Для быстрого исследования это часто быстрее, чем отправлять данные в Grafana.
#!/usr/bin/env bash
# Latency histogram (50ms buckets) from Nginx timed log
awk '{
n = split($NF, a, "=")
if (n == 2 && a[1] == "rt") {
ms = int(a[2] * 1000) # convert to ms
bucket = int(ms / 50) * 50 # round down to 50ms boundary
hist[bucket]++
}
}
END {
for (b in hist)
printf "%6dms %d\n", b, hist[b]
}' /var/log/nginx/timed_access.log \
| sort -nИзвлечение User-Agent и обнаружение ботов
Поле User-Agent (поле 6 при разделении по ") идентифицирует клиентов. Сканеры, сборщики данных и вредоносные боты часто искажают метрики и увеличивают число ошибок. Их фильтрация даёт более точное представление о трафике реальных пользователей.
Распространённые признаки ботов: bot, crawler, spider, curl, python-requests, Googlebot, Bingbot.
Используйте grep -iv (инвертированный поиск без учёта регистра), чтобы исключить известных ботов, или используйте awk, чтобы разделить строку по " и сопоставить поле UA напрямую.
#!/usr/bin/env bash
# Count top 15 User-Agent strings, excluding known bots
awk -F'"' '{ print $6 }' /var/log/nginx/access.log \
| grep -iv -e 'bot' -e 'crawler' -e 'spider' -e 'curl' \
-e 'python' -e 'wget' -e 'Go-http-client' \
| sort \
| uniq -c \
| sort -rn \
| head -15Агрегирование трафика по конечным точкам
Знание о том, какие конечные точки получают больше всего трафика и создают больше всего ошибок, помогает расставлять приоритеты при оптимизации и планировании пропускной способности. Путь запроса находится внутри заключённого в кавычки поля запроса.
Разделите строку по ", возьмите поле 2 (строку запроса), затем удалите метод и протокол, чтобы выделить путь. Для API с параметрами пути, такими как /users/12345, может также потребоваться нормализовать идентификаторы до вида /users/:id с помощью sed или более сложного шаблона awk.
#!/usr/bin/env bash
# Top 20 requested endpoints (method + path, no query string)
awk -F'"' '{ print $2 }' /var/log/nginx/access.log \
| awk '{ print $1, $2 }' \
| sed 's|/[0-9][0-9]*\b|/:id|g' \
| sort \
| uniq -c \
| sort -rn \
| head -20Связывание ошибок с конечными точками с помощью awk
Самый мощный анализ за один проход одновременно объединяет несколько полей: конечную точку, код состояния и, при необходимости, задержку. Ассоциативные массивы awk с ключами, составленными из нескольких значений, делают такой анализ простым и быстрым.
Приведённый ниже шаблон за один проход подсчитывает ошибки 5xx для каждой конечной точки: временные файлы не нужны, промежуточная сортировка выполняется только в самом конце. Такой подход используется в промышленных скриптах наблюдаемости, когда нужно получить ответ менее чем за минуту при работе с большим журналом.
#!/usr/bin/env bash
# Count 5xx errors per endpoint path in a single pass
awk -F'"' '{
# $2 = request line e.g. "GET /api/orders HTTP/1.1"
# $0 in original space-split: $9 = status
split($0, fields, " ")
status = fields[9]
if (status ~ /^5/) {
split($2, req, " ")
path = req[2]
# Normalise numeric IDs
gsub(/\/[0-9]+/, "/:id", path)
errors[path]++
}
}
END {
for (p in errors)
printf "%6d %s\n", errors[p], p
}' /var/log/nginx/access.log \
| sort -rn \
| head -20Обработка архивированных и сжатых журналов
На большинстве серверов журналы ротируются ежедневно. Старые файлы сжимаются с помощью gzip и получают имена access.log.1.gz, access.log.2.gz и так далее. Стандартные инструменты не могут читать их напрямую, но есть два удобных подхода:
zcat— распаковывает данные в стандартный вывод, после чего их можно передать в конвейерzgrep— выполняет поиск grep непосредственно внутри файлов gzip, не распаковывая их
Чтобы проанализировать полную неделю журналов, включающую как несжатые, так и сжатые файлы, используйте подстановку процесса или объедините данные с помощью zcat. Приведённый ниже фрагмент обрабатывает последние 7 ротированных файлов и текущий журнал в одном вызове awk — временные файлы не требуются.
#!/usr/bin/env bash
# Aggregate status codes across a week of rotated logs
# Handles both plain and gzip-compressed rotation files
LOG_DIR="/var/log/nginx"
{
cat "${LOG_DIR}/access.log" 2>/dev/null
zcat "${LOG_DIR}/access.log".*.gz 2>/dev/null
} | awk '
{ count[$9]++ }
END {
for (s in count)
printf "%6d %s\n", count[s], s
}' | sort -rnВ каком поле awk находится код состояния HTTP в объединённом формате журнала?
Вы пишете однострочную команду awk, чтобы отфильтровать только ответы HTTP 4xx из стандартного журнала доступа Nginx в объединённом формате журнала (поля разделены пробелами, строка запроса заключена в кавычки). Какой номер поля правильно соответствует коду состояния HTTP?
Итоги урока: конвейеры анализа журналов
В этом уроке Вы создали полный набор инструментов для анализа журналов веб-приложений и приложений в больших масштабах, используя только стандартные утилиты BASH.
Основные рассмотренные приёмы:
- Сначала изучите структуру — объединённый формат журнала имеет предсказуемую структуру полей; зная её, Вы сможете надёжно разделять записи с помощью
cut -d'"'или ссылок на поля вawk. - Извлечение кода состояния —
awk '{ count[$9]++ }'подсчитывает все коды за один проход; используйте фильтр$9 ~ /^5/для ошибок сервера. - Анализ задержки — разбирайте пользовательское поле
rt=с помощьюawk, чтобы вычислять средние и максимальные значения, а также интервалы гистограммы без внешних инструментов. - Анализ клиентов и ботов — разделяйте по
"с помощью-F'"', чтобы получить доступ к полю User-Agent; перед объединением пропускайте данные черезgrep -iv, исключая ботов. - Нормализация конечных точек — используйте
gsub(/\/[0-9]+/, "/:id")внутриawk, чтобы сворачивать пути с параметрами перед подсчётом. - Ротация журналов — объединяйте
catиzcatво вложенной оболочке, чтобы передать все файлы ротации в один проход конвейера.
Эти приёмы можно комбинировать: Вы можете последовательно выполнять фильтрацию, извлечение, нормализацию и объединение в одном конвейере, обрабатывающем сотни миллионов строк на обычном оборудовании. Освойте эти базовые средства — и для разовых расследований инцидентов Вам редко понадобится специализированная служба агрегирования журналов.
Часто задаваемые вопросы
Урок «Масштабный разбор веб-журналов и журналов приложений» бесплатный?
Да — полный текст урока «Масштабный разбор веб-журналов и журналов приложений» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс DevOps Bootcamp, подпишись на CoddyKit PRO. Курс DevOps Bootcamp содержит 4 уроков всего.
Чему я научусь в уроке «Масштабный разбор веб-журналов и журналов приложений»?
Извлекайте коды состояния, задержки и поля клиентов из журналов доступа с помощью grep, cut и awk Ты практикуешь DevOps Bootcamp с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать DevOps Bootcamp?
Предыдущий опыт не требуется. DevOps Bootcamp на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Масштабный разбор веб-журналов и журналов приложений»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке DevOps Bootcamp?
Да. Каждый урок DevOps Bootcamp включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Масштабный разбор веб-журналов и журналов приложений
- Отслеживание журналов в реальном времени и потоковые оповещения
- Запросы к journald с journalctl в скриптах
- Вычисление показателей и гистограмм из потоков журналов