Сервис разбора access-логов nginx: читает файл в потоке, раскладывает строки в PostgreSQL и отдаёт аналитику через REST API и дашборд.
Access-лог nginx — это плоский текст, по которому нельзя задать вопрос. Чтобы узнать, какие URL отдают 5xx чаще остальных или какой адрес выбирает больше всего трафика, приходится каждый раз собирать конвейер из grep, awk и sort, а на файле в несколько гигабайт это занимает минуты.
Сервис делает разбор один раз: строка лога превращается в запись со схемой, и дальше вопросы задаются запросами к базе.
access.log ──► парсер (tail -f) ──► PostgreSQL ──► REST API ──► дашборд
└─► CLI
Парсер (apps/services/nginx_log_parser.py) открывает файл, встаёт в конец и опрашивает размер раз в секунду. Появились новые байты — читает и разбирает их регулярным выражением под combined-формат. Если файл стал короче, чем был, значит его провернул logrotate: позиция сбрасывается в ноль, чтение продолжается с начала нового файла.
Разобранная строка становится записью с полями: время с таймзоной, адрес клиента, метод, URI, версия протокола, код ответа, размер ответа, реферер, user-agent и идентификатор сервера. Записи разных серверов лежат в одной таблице и разделяются внешним ключом, поэтому один инстанс собирает логи с нескольких машин.
apps/
api/v1/
handlers/ роутеры: серверы, записи лога, аналитика
cruds/ доступ к данным поверх SQLAlchemy
models/ ORM-модели
schemas/ схемы запроса и ответа
auth/ JWT: выдача токенов, проверка, зависимости
db/ движок, сессии, аннотированные поля
services/ парсер access-лога
utils/ healthcheck, перечисления
settings.py конфигурация на pydantic-settings
main.py сборка приложения
migrations/ Alembic
tests/
Обработчики не ходят в базу напрямую — между ними и SQLAlchemy лежит слой CRUD с общим базовым классом. Таблицы вынесены в отдельную схему Postgres nginx_parser_schema, а не в public: так сервис можно поселить в общую базу, не смешивая его таблицы с чужими.
Все ручки, кроме /health и /login, требуют токен — в заголовке Authorization или в куке.
| метод | путь | назначение |
|---|---|---|
| POST | /login |
получить пару токенов |
| DELETE | /logout |
удалить токены из кук |
| GET | /check_user |
текущий пользователь |
| GET | /api/servers |
список серверов |
| GET | /api/servers/{server_id} |
сервер вместе с его записями |
| GET | /api/log-entries |
записи лога |
| GET | /api/analytics/status-codes |
распределение по кодам ответа |
| GET | /api/analytics/top-ips |
адреса по числу запросов и среднему размеру ответа |
| GET | /api/analytics/top-urls |
самые запрашиваемые URI |
| GET | /api/analytics/errors |
коды 4xx и 5xx |
| GET | /api/analytics/traffic |
запросы, уникальные адреса, объём |
| GET | /api/analytics/time-series |
ряд по корзинам заданной ширины |
| GET | /health |
проверка живости |
Аналитические ручки принимают hours — окно в часах от текущего момента. У time-series есть interval_minutes: ширина корзины, по которой группируется ряд.
Схема OpenAPI доступна на /docs.
Нужны Python 3.11+, PostgreSQL 14+ и uv.
uv venv && source .venv/bin/activate
uv pip install ".[dev]"
cp env.example .env
createdb nginx_log_analyzer
alembic upgrade head
make run
API поднимется на http://localhost:8000, дашборд — на http://localhost:8000/static/index.html.
В контейнерах:
docker compose up -d --build
Образ собирается в два этапа, приложение работает не от root.
python cli.py monitor /var/log/nginx/access.log --server-id 1
python cli.py check /var/log/nginx/access.log
monitor запускает непрерывное чтение и запись в базу, check показывает размер файла и первые строки — удобно, чтобы убедиться, что формат распознаётся, до запуска мониторинга.
make test pytest с покрытием
ruff check apps tests migrations cli.py линтер
ruff format --check apps tests формат
67 тестов: разбор строк лога и краевые случаи формата, CRUD, аналитические запросы к живому Postgres, аутентификация, CLI, миграции и сквозной сценарий от файла до ответа API. Тестовые данные генерируются кодом, поэтому набор запускается на чистом клоне без подготовки файлов.
CI прогоняет линтер, формат, миграции и тесты на каждый push.
- парсер рассчитан на combined-формат nginx; свой
log_formatпотребует правки регулярного выражения - опрос файла по таймеру раз в секунду, без inotify: задержка до секунды и лишние системные вызовы на простое
- пользователь один и задаётся конфигурацией, ролей и разграничения доступа нет
- аналитика считается запросами по всей таблице без предагрегации: на десятках миллионов строк потребуются материализованные представления или сворачивание старых данных
- ретеншн не реализован — таблица растёт, пока её не почистить руками
- парсер запускается отдельным процессом через CLI и не управляется из API