Skip to content

Commit

Permalink
Update README.md (#34)
Browse files Browse the repository at this point in the history
* Update README.md

* Add docs for FastAPI middlewares

* Add docs-linter
  • Loading branch information
Kiruha01 authored Sep 5, 2022
1 parent 041fc93 commit c39137d
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 112 deletions.
1 change: 1 addition & 0 deletions .github/workflows/actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ jobs:
- name: Run lint
run: |
poetry run ./scripts/lint
poetry run ./scripts/docs-lint
22 changes: 22 additions & 0 deletions .snippets_setup/logger_api_call.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from typing import Any, Dict, Literal, Optional
from urllib.error import HTTPError

from httpx import AsyncClient, HTTPStatusError

from pybotx_smart_logger import smart_log

base_url = ""


class ResponseSchema:
pass # noqa: WPS420, WPS604


class RequestToAwesomeAPIError(Exception):
pass # noqa: WPS420, WPS604


class InvalidStatusCodeFromAwesomeAPIError(Exception):
pass # noqa: WPS420, WPS604


13 changes: 13 additions & 0 deletions .snippets_setup/logger_check_role.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from pybotx import Bot, IncomingMessage, IncomingMessageHandlerFunc, OutgoingMessage

from pybotx_smart_logger import smart_log


def only_subscribed_users_allowed_message(message: IncomingMessage) -> OutgoingMessage:
return OutgoingMessage(
bot_id=message.bot.id,
chat_id=message.chat.id,
body=message.body,
)


11 changes: 11 additions & 0 deletions .snippets_setup/logger_common_use.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from typing import Any, Dict

from pybotx_smart_logger import wrap_smart_logger

kwargs: Dict[Any, Any] = {}


async def make_request(**kwargs1: Dict[Any, Any]) -> Dict[Any, Any]:
return kwargs1


7 changes: 7 additions & 0 deletions .snippets_setup/logger_debug_enable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from uuid import UUID

from pybotx import Bot, HandlerCollector, IncomingMessage

collector = HandlerCollector()


5 changes: 5 additions & 0 deletions .snippets_setup/logger_debug_enable_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from pybotx import Bot, HandlerCollector, IncomingMessage

collector = HandlerCollector()


13 changes: 13 additions & 0 deletions .snippets_setup/logger_fastapi_use.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from typing import Callable

from fastapi import FastAPI, Request

from pybotx_smart_logger import wrap_smart_logger


def pformat_str_request(request: Request) -> str:
return ""


DEBUG = True

27 changes: 27 additions & 0 deletions .snippets_setup/logger_init_bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from uuid import uuid4

from pybotx import (
Bot,
BotAccountWithSecret,
HandlerCollector,
IncomingMessage,
IncomingMessageHandlerFunc,
)


async def smart_logger_middleware(
message: IncomingMessage,
bot: Bot,
call_next: IncomingMessageHandlerFunc,
) -> None:
return # noqa: WPS324


collector = HandlerCollector()
BOT_CREDENTIALS = BotAccountWithSecret( # noqa: S106
secret_key="",
host="",
id=uuid4(),
)


11 changes: 11 additions & 0 deletions .snippets_setup/logger_init_middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from typing import Optional

from pybotx import Bot, IncomingMessage, IncomingMessageHandlerFunc

from pybotx_smart_logger import wrap_smart_logger


def format_raw_command(command: Optional[dict]) -> str:
return str(command)


173 changes: 61 additions & 112 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ _Shows logs when you need it_

## Использование

Используя функцию `smart_log(log_message: str, log_item: Any = undefined)` логируете всю
Используя функцию `smart_log(log_message: str, *args: Any, **kwargs: Any)` логируете всю
информацию, которая поможет в диагностике ошибки. Если во время обработки сообщения
будет выброшено исключение, в логи попадёт:

Expand All @@ -29,120 +29,70 @@ _Shows logs when you need it_

Если обработка сообщения завершится успешно, накопленные логи будут "выброшены".

В ботах часто используются фоновые задачи, исключения которых не могут быть перехвачены
хендлером. Для них используется `smart_logger_decorator`, позволяющий получить
аналогичное для обработки сообщений поведение.

Вы так же можете подлючить миддлварь для возможности логирования из FastAPI хендлеров. В таком случае, если во время обработки исключения будет выброшено ислючение, в логи попадет:

1. Метод
2. Урл запроса с query параметрами
3. Заголовки запроса

При необходимости, тело запроса нужно логировать в самом хендлере:
``` python
`smart_log(await request.json())`
```


## Режим отладки

Появление необходимых логов при возникновении ошибки очень удобно. Однако есть
ситуации, когда необходимо понаблюдать за нормальной работой кода. Тем более, что вызовы
`smart_log` уже расставлены. Поэтому для сообщений и фоновых задач предусмотрены функции
отладки.

`BotXSmartLoggerMiddleware` принимает пользовательскую функцию
`debug_enabled_for_message(message: Message) -> bool`, позволяющую включить режим отладки в
зависимости от входящего сообщения.

`make_smart_logger_decorator` принимает пользовательскую функцию
`debug_enabled_for_task(task_name: str) -> bool` позволяющую включить режим отладки в
зависимости от имени функции. `smart_logger_decorator` знает имя функции, которую он
оборачивает и передаёт его в `debug_enabled_for_task` в качестве аргумента.

Эти функции можно использовать для включения отладки через переменные окружения, стейт
бота, Redis, PostgreSQL и т.д. Рекомендую завести команды, которые позволят включать
режим отладки отправкой сообщения (см. пример ниже).

`FastApiSmartLoggerMiddleware` принимает аргумент `debug_enabled: bool`.


## Настройка

1. Устанавливаем библиотеку:
```bash
poetry add pybotx-smart-logger
```

2. Подключаем мидлварь и хендлер исключений к боту. Хендлер должен быть подключен к
типу Exception, т.е. заменяет подключенный в коробке `internal_error_handler`.
2. Подключим мидлварь для логирования входящих сообщений:

```python
from pybotx import Bot
from pybotx_smart_logger import make_smart_logger_exception_handler, BotXSmartLoggerMiddleware

from app.resources import strings

smart_logger_exception_handler = make_smart_logger_exception_handler(
strings.BOT_INTERNAL_ERROR_TEXT
)
**middlewares/smart_logger.py**
```python #logger_init_middleware
async def smart_logger_middleware(
message: IncomingMessage,
bot: Bot,
call_next: IncomingMessageHandlerFunc,
) -> None:
async with wrap_smart_logger(
log_source="Incoming message",
context_func=lambda: format_raw_command(message.raw_command),
debug=True,
):
await call_next(message, bot)
```

bot = Bot(
collectors=...,
bot_accounts=...,
exception_handlers={Exception: smart_logger_exception_handler},
middlewares=[BotXSmartLoggerMiddleware, debug_enabled_for_message=False]
**bot.py**
```python #logger_init_bot
Bot(
collectors=[collector],
bot_accounts=[BOT_CREDENTIALS],
middlewares=[
smart_logger_middleware,
],
)
```
3. Для того чтобы логировать какие-то другие части приложения, необходимо обернуть в контекстный менджер:
```python #logger_common_use
async def handler() -> None:
async with wrap_smart_logger(
log_source="Request to Server",
context_func=lambda: str(kwargs),
debug=False,
):
await make_request(**kwargs)
```

3. [Опционально] Для фоновых задач создаём декоратор и запускаем фоновые задачи в при
старте бота:

```python
import asyncio
from pybotx_smart_logger import make_smart_logger_decorator

smart_logger_decorator = make_smart_logger_decorator(lambda task_name: False)


@smart_logger_decorator
async def update_users_tasks() -> None:
pass


async def update_background_task() -> None:
while True:
await update_users_tasks()
await asyncio.sleep(60)
4. Также можно использовать smart_logger для логирования запросов к FastAPI приложению:
```python #logger_fastapi_use
app = FastAPI()

# Внутри функции бота `start_app`:
asyncio.create_task(update_background_task())
```

4. [Опционально] Возможность логирования из FastAPI хендлера:
В файле `app/main.py`
4.1 Подлключаем миддлварь:
``` python
from pybotx_smart_logger import FastApiSmartLoggerMiddleware
...
def get_application() -> FastAPI:
...
application.middleware("http")(FastApiSmartLoggerMiddleware(debug_enabled=False))
```
4.2 Подключаем хендлер исключений:
``` python
from pybotx_smart_logger import FastApiSmartLoggerMiddleware, fastapi_exception_handler
...
def get_application() -> FastAPI:
...
application.middleware("http")(FastApiSmartLoggerMiddleware(debug_enabled=False))
application.add_exception_handler(Exception, fastapi_exception_handler)
@app.middleware("http")
async def smart_logger_middleware(request: Request, call_next: Callable) -> None:
async with wrap_smart_logger(
log_source="Incoming request",
context_func=lambda: pformat_str_request(request),
debug=DEBUG,
):
return await call_next(request)
```
`log_source` определяет источник логов. `context_func` - пользовательская функция для форматирования логов.

## Пример команд для включения отладки

```python
```python #logger_debug_enable
@collector.command("/_debug:enable-for-huids", visible=False)
async def enable_debug_for_users(message: IncomingMessage, bot: Bot) -> None:
try:
Expand All @@ -153,11 +103,11 @@ async def enable_debug_for_users(message: IncomingMessage, bot: Bot) -> None:

# TODO: Обновите список user_huid

await bot.answer_message("Список user_huid для отладки обновлён")
await bot.answer_message(f"Список user_huid для отладки обновлён {huids}")
```


```python
```python #logger_debug_enable_command
@collector.command("/_debug:enable-for-tasks", visible=False)
async def enable_debug_for_tasks(message: IncomingMessage, bot: Bot) -> None:
# TODO: Обновите список имён задач
Expand All @@ -170,18 +120,17 @@ async def enable_debug_for_tasks(message: IncomingMessage, bot: Bot) -> None:

1. Проверка роли:

```python
from pybotx_smart_logger import smart_log

```python #logger_check_role
# TODO: Мидлварь для заполнения message.state.user


async def subscribed_users_only_middleware(
message: IncomingMessage,
bot: Bot,
call_next: IncomingMessageHandlerFunc,
) -> None:
if not message.state.user.is_subscribed:
await bot.send(only_subscribed_users_allowed_message(message))
await bot.send(message=only_subscribed_users_allowed_message(message))

return

Expand All @@ -192,36 +141,36 @@ async def subscribed_users_only_middleware(

2. Обращение в API:

```python
from pybotx_smart_logger import smart_log

```python #logger_api_call
async def _perform_request(
self,
method: Literal["GET", "POST"],
url: str,
query_params: Optional[Dict[str, Any]] = None,
body_dict: Optional[Dict[str, Any]] = None,
) -> ResponseSchema:
) -> str:
smart_log("Performing request to YourAwesomeAPI")
smart_log("Method:", method)
smart_log("URL:", url)
smart_log("Query parameters:", query_params)
smart_log("Body dict:", body_dict)

try:
async with AsyncClient(base_url=self._base_url) as client:
async with AsyncClient(base_url=base_url) as client:
response = await client.request(
method, url, params=query_params, json=body_dict
method,
url,
params=query_params,
json=body_dict,
)
except HTTPError as exc:
raise RequestToAwesomeAPIFailed from exc
raise RequestToAwesomeAPIError from exc

smart_log("Response text:", response.text)

try:
response.raise_for_status()
except HTTPStatusError as exc: # noqa: WPS440
raise InvalidStatusCodeFromAwesomeAPI from exc
raise InvalidStatusCodeFromAwesomeAPIError from exc

return response.text
```
Expand Down
Loading

0 comments on commit c39137d

Please sign in to comment.