Разработать небольшое API для управления заметками. Каждый HTTP-запрос должен проходить через middleware, которое:
- измеряет время выполнения запроса;
- считает количество запросов;
- сохраняет HTTP-статус ответа;
- определяет IP-адрес клиента;
- добавляет уникальный идентификатор запроса;
- записывает информацию о запросе в лог;
- собирает простую статистику по работе API.
Главная цель проекта — понять, как middleware перехватывает запрос до передачи в endpoint и обрабатывает ответ после завершения endpoint.
- Python;
- FastAPI;
- Uvicorn;
- стандартный модуль
logging; - хранение данных в памяти или SQLite;
- Pydantic.
Поля:
id— идентификатор;title— заголовок;text— текст заметки;created_at— дата создания.
POST /notes
Пример тела запроса:
{
"title": "Изучить middleware",
"text": "Разобраться со сбором метрик и логированием"
}GET /notes
GET /notes/{note_id}
Если заметка не найдена, вернуть статус 404.
DELETE /notes/{note_id}
GET /health
Пример ответа:
{
"status": "ok"
}Создать middleware, которое для каждого запроса генерирует уникальный request_id.
Можно использовать uuid4.
Идентификатор необходимо:
- сохранить в
request.state.request_id; - добавить в заголовок ответа
X-Request-ID; - вывести в логах.
Пример:
request_id=7f91c632 method=GET path=/notes
Middleware должно фиксировать время начала обработки запроса и рассчитывать продолжительность выполнения.
Время выполнения добавить в заголовок ответа:
X-Process-Time: 0.025
Также время необходимо записывать в лог.
Пример:
GET /notes | status=200 | duration=0.025s
Для каждого запроса записывать:
- дату и время;
request_id;- HTTP-метод;
- путь;
- query-параметры;
- IP-адрес клиента;
- статус ответа;
- продолжительность выполнения.
Пример лога:
2026-07-15 17:20:31 | request_id=7f91c632 | ip=127.0.0.1 | GET /notes | status=200 | duration=0.025s
Запросы со статусом 500 должны записываться с уровнем ERROR.
Запросы со статусом 400–499 — с уровнем WARNING.
Остальные запросы — с уровнем INFO.
Логи выводить:
- в консоль;
- в файл
logs/app.log.
Хранить в памяти следующие метрики:
- общее количество запросов;
- количество успешных запросов;
- количество ответов со статусами
4xx; - количество ответов со статусами
5xx; - количество запросов для каждого endpoint;
- суммарное время обработки запросов;
- максимальное время обработки запроса.
Пример внутреннего состояния:
metrics = {
"total_requests": 15,
"successful_requests": 11,
"client_errors": 3,
"server_errors": 1,
"total_duration": 1.57,
"max_duration": 0.42,
"endpoints": {
"GET /notes": 7,
"POST /notes": 4,
"GET /health": 4
}
}Создать endpoint:
GET /metrics
Пример ответа:
{
"total_requests": 15,
"successful_requests": 11,
"client_errors": 3,
"server_errors": 1,
"average_duration": 0.104,
"max_duration": 0.42,
"endpoints": {
"GET /notes": 7,
"POST /notes": 4,
"GET /health": 4
}
}Сам запрос к /metrics можно не учитывать в статистике, чтобы он не изменял показатели при каждом просмотре.
Создать тестовый endpoint:
GET /slow
Он должен выполнять искусственную задержку от 1 до 3 секунд.
Пример:
await asyncio.sleep(2)Endpoint нужен для проверки времени выполнения и максимальной продолжительности запроса.
Создать тестовый endpoint:
GET /error
Endpoint намеренно вызывает исключение.
Например:
raise RuntimeError("Test server error")Middleware должно:
- записать ошибку в лог;
- сохранить
request_id; - увеличить счётчик ошибок
5xx; - вернуть корректный JSON-ответ.
Пример:
{
"detail": "Internal server error",
"request_id": "7f91c632"
}Закрыть endpoint /metrics простым API-ключом.
Клиент должен отправить заголовок:
X-API-Key: secret123
Если ключ отсутствует или неправильный, вернуть:
{
"detail": "Invalid API key"
}Статус ответа:
403 Forbidden
Проверка ключа также должна выполняться через middleware.
app/
├── main.py
├── schemas.py
├── storage.py
├── middleware/
│ ├── request_id.py
│ ├── logging_middleware.py
│ ├── metrics.py
│ └── api_key.py
├── routers/
│ ├── notes.py
│ └── system.py
└── logs/
└── app.log
После запуска проекта выполнить несколько запросов:
- Создать заметку.
- Получить список заметок.
- Запросить несуществующую заметку.
- Открыть
/slow. - Открыть
/error. - Открыть
/metricsбез API-ключа. - Открыть
/metricsс правильным API-ключом. - Проверить заголовки
X-Request-IDиX-Process-Time. - Проверить содержимое файла логов.
- Сравнить количество выполненных запросов с метриками.
Проект считается завершённым, если:
- API заметок работает;
- каждому запросу назначается уникальный
request_id; - в ответах присутствуют технические заголовки;
- время выполнения запросов измеряется;
- запросы записываются в файл и консоль;
- уровни логирования зависят от статуса ответа;
- метрики обновляются после каждого запроса;
- endpoint
/metricsвозвращает актуальную статистику; - endpoint
/metricsзащищён API-ключом; - необработанные ошибки возвращаются в едином формате.