ЯдроКодаподготовка к экзаменам
Учебная платформа

Загружаем материалы

Подготавливаем материалы и навигацию по разделу.

awk и jq: столбцы, JSON и анализ журналов nginx

Автор: · Обновлено

Вопрос «сколько запросов завершилось ошибкой» требует сначала определить формат журнала. Позиция поля в access.log зависит от конфигурации nginx, а порядок ключей JSON может…

Вопрос «сколько запросов завершилось ошибкой» требует сначала определить формат журнала. Позиция поля в access.log зависит от конфигурации nginx, а порядок ключей JSON может меняться без изменения смысла. awk удобен для известной строковой структуры, jq — для JSON. Регулярное выражение по всей строке не гарантирует, что число 500 оказалось HTTP-статусом, а не частью URL.

Поля, условия и сумма в awk

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

parse_lab=$(mktemp -d)
printf 'name;bytes\nalice;120\nbob;80\nalice;20\n' >"$parse_lab/usage.txt"
awk -F';' 'NR > 1 { print $1 }' "$parse_lab/usage.txt"
awk -F';' 'NR > 1 { total += $2 } END { print total }' "$parse_lab/usage.txt"
awk -F';' 'NR > 1 { sum[$1] += $2 } END { for (name in sum) print name, sum[name] }' "$parse_lab/usage.txt" | LC_ALL=C sort

Ожидаемая сумма — 220, по пользователям — alice 140 и bob 80. $0 означает запись целиком, $1 — первое поле, NF — число полей, NR — номер записи. Без -F последовательности пробельных символов обычно разделяют поля. Порядок обхода массива не задаёт сортировку, поэтому она выполнена отдельно. Не подставляйте значение shell внутрь исходного кода awk: awk -v owner="$USER" '...' передаёт его как данные.

CSV требует собственного разбора

В строке alice,"Moscow, RU",120 запятая внутри кавычек не разделяет столбцы. Обычный awk -F, и cut её не отличат. Новые версии gawk поддерживают --csv, однако системный awk может быть другой реализацией или старой версией. Для переносимой обработки используйте CSV-библиотеку, например модуль csv Python. Не называйте произвольный файл CSV только потому, что строки содержат запятые.

Сначала проверить формат access.log

Следующий fixture соответствует стандартному combined-формату nginx: адрес, два служебных поля, дата с зоной, запрос в кавычках, статус, размер, referer и user-agent.

cat >"$parse_lab/access.log" <<'LOG'
192.0.2.10 - - [09/Oct/2026:10:00:00 +0000] "GET /health HTTP/1.1" 200 2 "-" "demo"
192.0.2.11 - - [09/Oct/2026:10:00:01 +0000] "GET /api HTTP/1.1" 502 150 "-" "demo"
192.0.2.11 - - [09/Oct/2026:10:00:02 +0000] "GET /api HTTP/1.1" 503 150 "-" "demo"
LOG
awk '$9 ~ /^5[0-9][0-9]$/ { print $1 }' "$parse_lab/access.log" | sort | uniq -c
awk '{ codes[$9]++ } END { for (code in codes) print code, codes[code] }' "$parse_lab/access.log" | sort

Получаем два ответа 5xx для 192.0.2.11 и по одному статусу 200, 502, 503. Перед применением к настоящему журналу проверьте log_format: добавленное поле меняет позиции. Счётчик адресов ещё не определяет виновника: reverse proxy может скрывать клиента, а status 502 требует сопоставления с error.log и состоянием upstream.

Разбор JSON через jq

cat >"$parse_lab/events.jsonl" <<'JSON'
{"path":"/health","status":200,"bytes":2}
{"bytes":150,"status":502,"path":"/api"}
{"path":"/api","status":503,"bytes":150}
JSON
jq -r 'select(.status >= 500) | .path' "$parse_lab/events.jsonl"
jq -s 'map(.bytes) | add' "$parse_lab/events.jsonl"
jq -r '[.path, .status] | @tsv' "$parse_lab/events.jsonl"

Фильтр выводит /api дважды, сумма — 302. Обычный jq обрабатывает последовательные JSON-объекты; -s собирает их в массив и потому требует памяти пропорционально входу. -r снимает JSON-кавычки со строк. Для значения из переменной используйте --arg wanted "$path" и сравнение .path == $wanted; для числа — --argjson. В конфигурации nginx log_format ... escape=json экранирует значения, однако корректную структуру объекта и типы полей задаёт сам шаблон.

Ошибка и диагностика

Сумма может получиться неверной, если поле содержит единицы 12MB, пустоту или заголовок. Явно отбрасывайте заголовок и проверяйте допустимый вид числа. В JSON строка "502" и число 502 — разные типы; согласуйте контракт производителя, прежде чем сравнивать. Не исправляйте испорченный JSON цепочкой sed: сохраните проблемную запись и устраните источник повреждения.

Практика

Добавьте JSON-запись с кодом 404 и размером 10, затем получите только пути 5xx и сумму всех байтов. Ответы должны остаться двумя /api и стать 312 соответственно. Переставьте ключи в объекте и убедитесь, что результат не изменился. В текстовый журнал добавьте такой же статус и проверьте счётчики. Объясните, какой вариант выдерживает добавление нового поля без исправления программы.

Что важно запомнить

Парсер выбирают по формату, а вывод сверяют с контрольным набором. Агрегация объясняет масштаб явления, но расследование ошибки требует времени, контекста запроса и журналов связанного сервиса.

Частые вопросы

Можно ли искать статус через grep 500?

Так можно найти кандидатов для просмотра, но число встречается и в URL, размере или времени. Для точного подсчёта выделите поле статуса согласно формату.

Почему jq выдаёт null?

У текущего объекта нет запрошенного ключа либо значение действительно null. Проверьте структуру через jq . и условие has("ключ") перед преобразованием.

Нужно ли всегда применять jq -s?

Нет. Для фильтрации отдельных событий достаточно потока. Большой журнал лучше агрегировать потоковым reduce или специализированной системой, учитывая доступную память и формат записей.

Источники