From ea907d0bf21a5d5b5aacbfb63553e29d2ad6c1c5 Mon Sep 17 00:00:00 2001 From: Forden Date: Wed, 5 Aug 2020 16:03:13 +0300 Subject: [PATCH] feat: added logs & refactored webhooks --- bot.py | 60 ++++++++++++++++++++++---------------- data/config.py | 4 +++ requirements.txt | 1 + utils/misc/logging.py | 30 +++++++++++++++++++ web_handlers/__init__.py | 2 ++ web_handlers/health.py | 29 ++++++++++++++++++ web_handlers/tg_updates.py | 21 +++++++++++++ 7 files changed, 122 insertions(+), 25 deletions(-) create mode 100644 utils/misc/logging.py create mode 100644 web_handlers/__init__.py create mode 100644 web_handlers/health.py create mode 100644 web_handlers/tg_updates.py diff --git a/bot.py b/bot.py index 59f6cc3..6d66703 100644 --- a/bot.py +++ b/bot.py @@ -1,42 +1,52 @@ -from aiogram import Bot, Dispatcher, types +from typing import List + +from aiogram import Bot, Dispatcher from aiogram.contrib.fsm_storage.redis import RedisStorage2 +from aiogram.types import ParseMode from aiohttp import web from loguru import logger -import filters -import handlers -import middlewares from data import config -bot = Bot(token=config.BOT_TOKEN, parse_mode=types.ParseMode.HTML) -storage = RedisStorage2(**config.aiogram_redis) -dp = Dispatcher(bot, storage=storage) - # noinspection PyUnusedLocal -async def on_startup(web_app: web.Application): - filters.setup(dp) +async def on_startup(app: web.Application): + import middlewares, filters + import handlers middlewares.setup(dp) + filters.setup(dp) handlers.errors.setup(dp) handlers.user.setup(dp) - await dp.bot.delete_webhook() + logger.info('Configure Webhook URL to: {url}', url=config.WEBHOOK_URL) await dp.bot.set_webhook(config.WEBHOOK_URL) -async def execute(req: web.Request) -> web.Response: - upds = [types.Update(**(await req.json()))] - Bot.set_current(dp.bot) - Dispatcher.set_current(dp) - try: - await dp.process_updates(upds) - except Exception as e: - logger.error(e) - finally: - return web.Response() +async def on_shutdown(app: web.Application): + app_bot: Bot = app['bot'] + await app_bot.close() + + +async def init() -> web.Application: + from utils.misc import logging + import web_handlers + logging.setup() + app = web.Application() + subapps: List[str, web.Application] = [ + ('/health/', web_handlers.health_app), + ('/tg/webhooks', web_handlers.tg_updates_app), + ] + for prefix, subapp in subapps: + subapp['bot'] = bot + subapp['dp'] = dp + app.add_subapp(prefix, subapp) + app.on_startup.append(on_startup) + app.on_shutdown.append(on_shutdown) + return app if __name__ == '__main__': - app = web.Application() - app.on_startup.append(on_startup) - app.add_routes([web.post(config.WEBHOOK_PATH, execute)]) - web.run_app(app, port=5151, host='localhost') + bot = Bot(config.BOT_TOKEN, parse_mode=ParseMode.HTML, validate_token=True) + storage = RedisStorage2(**config.aiogram_redis) + dp = Dispatcher(bot, storage=storage) + + web.run_app(init()) diff --git a/data/config.py b/data/config.py index 7555a33..8d1d218 100644 --- a/data/config.py +++ b/data/config.py @@ -1,8 +1,12 @@ +from pathlib import Path + BOT_TOKEN = '' BASE_URL = 'https://example.com' # Webhook domain WEBHOOK_PATH = f'/webhook/bot/{BOT_TOKEN}' WEBHOOK_URL = f'{BASE_URL}{WEBHOOK_PATH}' +LOGS_BASE_PATH = str(Path(__file__).parent.parent / 'logs') + admins = [] ip = { diff --git a/requirements.txt b/requirements.txt index 4b343f8..aed7b7c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ aiogram==2.9.2 aiohttp==3.6.2 aioredis==1.3.1 loguru==0.5.1 +aiohttp_healthcheck==1.3.1 \ No newline at end of file diff --git a/utils/misc/logging.py b/utils/misc/logging.py new file mode 100644 index 0000000..fe7aaae --- /dev/null +++ b/utils/misc/logging.py @@ -0,0 +1,30 @@ +import logging +import sys + +from loguru import logger + +from data import config + + +class InterceptHandler(logging.Handler): + LEVELS_MAP = { + logging.CRITICAL: "CRITICAL", + logging.ERROR: "ERROR", + logging.WARNING: "WARNING", + logging.INFO: "INFO", + logging.DEBUG: "DEBUG", + } + + def _get_level(self, record): + return self.LEVELS_MAP.get(record.levelno, record.levelno) + + def emit(self, record): + logger_opt = logger.opt(depth=6, exception=record.exc_info) + logger_opt.log(self._get_level(record), record.getMessage()) + + +# noinspection PyArgumentList +def setup(): + logger.add(sys.stderr, format="{time} {level} {message}", filter="my_module", level="INFO") + logger.add(config.LOGS_BASE_PATH + "/file_{time}.log") + logging.basicConfig(handlers=[InterceptHandler()], level=logging.INFO) diff --git a/web_handlers/__init__.py b/web_handlers/__init__.py new file mode 100644 index 0000000..38f3213 --- /dev/null +++ b/web_handlers/__init__.py @@ -0,0 +1,2 @@ +from .health import health_app +from .tg_updates import tg_updates_app diff --git a/web_handlers/health.py b/web_handlers/health.py new file mode 100644 index 0000000..aae7791 --- /dev/null +++ b/web_handlers/health.py @@ -0,0 +1,29 @@ +from typing import Tuple + +from aiogram import Bot +from aiohttp import web +from aiohttp_healthcheck import HealthCheck +from loguru import logger + + +async def health_check() -> Tuple[bool, str]: + return True, 'Server alive' + + +async def check_webhook() -> Tuple[bool, str]: + from data import config + bot: Bot = health_app['bot'] + + webhook = await bot.get_webhook_info() + if webhook.url and webhook.url == config.WEBHOOK_URL: + return True, f'Webhook configured. Pending updates count {webhook.pending_update_count}' + else: + logger.error('Configured wrong webhook URL {webhook}', webhook=webhook.url) + return False, 'Configured invalid webhook URL' + + +health_app = web.Application() +health = HealthCheck() +health.add_check(health_check) +health.add_check(check_webhook) +health_app.add_routes([web.get('/check', health)]) diff --git a/web_handlers/tg_updates.py b/web_handlers/tg_updates.py new file mode 100644 index 0000000..d072883 --- /dev/null +++ b/web_handlers/tg_updates.py @@ -0,0 +1,21 @@ +from aiogram import Bot, Dispatcher, types +from aiohttp import web + +from loguru import logger + +tg_updates_app = web.Application() + + +async def execute(req: web.Request) -> web.Response: + upds = [types.Update(**(await req.json()))] + Bot.set_current(req.app['bot']) + Dispatcher.set_current(req.app['dp']) + try: + await req.app['dp'].process_updates(upds) + except Exception as e: + logger.error(e) + finally: + return web.Response() + + +tg_updates_app.add_routes([web.post('/{token}', execute)])