From 1f17e56824994bf9ca643436d0d179ec5d6a1baf Mon Sep 17 00:00:00 2001 From: omg-xtao <100690902+omg-xtao@users.noreply.github.com> Date: Wed, 19 Oct 2022 20:22:24 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E6=B7=BB=E5=8A=A0=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E5=B9=B3=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: SiHuan Co-authored-by: 洛水居室 --- .env.example | 6 +++ core/config.py | 5 +++ modules/error/pb.py | 26 ++++++++++++ modules/error/sentry.py | 42 ++++++++++++++++++ plugins/system/errorhandler.py | 20 +++++++++ plugins/system/log.py | 26 +++++++++++- poetry.lock | 78 ++++++++++++++++++++++++++++++++++ pyproject.toml | 2 + utils/log/_handler.py | 14 +----- 9 files changed, 204 insertions(+), 15 deletions(-) create mode 100644 modules/error/pb.py create mode 100644 modules/error/sentry.py diff --git a/.env.example b/.env.example index f0822ef9..a21c9065 100644 --- a/.env.example +++ b/.env.example @@ -58,3 +58,9 @@ LOGGER_LOCALS_MAX_STRING=80 # WEB_URL=http://localhost:8080/ # WEB_HOST=localhost # WEB_PORT=8080 + +# error +# ERROR_PB_URL=https://fars.ee +# ERROR_PB_SUNSET=43200 +# ERROR_PB_MAX_LINES=1000 +# ERROR_SENTRY_DSN= diff --git a/core/config.py b/core/config.py index d6839f30..d0f01f93 100644 --- a/core/config.py +++ b/core/config.py @@ -64,6 +64,11 @@ class BotConfig(BaseSettings): web_host: str = "localhost" web_port: int = 8080 + error_pb_url: str = "" + error_pb_sunset: int = 43200 + error_pb_max_lines: int = 1000 + error_sentry_dsn: str = "" + class Config: case_sensitive = False json_loads = json.loads diff --git a/modules/error/pb.py b/modules/error/pb.py new file mode 100644 index 00000000..683f63fe --- /dev/null +++ b/modules/error/pb.py @@ -0,0 +1,26 @@ +import httpx + +from core.config import config + + +class PbClient: + def __init__(self): + self.client = httpx.AsyncClient() + self.PB_API = config.error_pb_url + self.sunset: int = config.error_pb_sunset # 自动销毁时间 秒 + self.private: bool = True + self.max_lines: int = config.error_pb_max_lines + + async def create_pb(self, content: str) -> str: + if not self.PB_API: + return "" + content = "\n".join(content.splitlines()[-self.max_lines :]) + "\n" + data = { + "c": content, + } + if self.private: + data["p"] = "1" + if self.sunset: + data["sunset"] = self.sunset + data = await self.client.post(self.PB_API, data=data) # 需要错误处理 + return data.headers["location"] diff --git a/modules/error/sentry.py b/modules/error/sentry.py new file mode 100644 index 00000000..1c0c967f --- /dev/null +++ b/modules/error/sentry.py @@ -0,0 +1,42 @@ +import os + +import sentry_sdk +from git.repo import Repo +from git.repo.fun import rev_parse +from sentry_sdk.integrations.excepthook import ExcepthookIntegration +from sentry_sdk.integrations.httpx import HttpxIntegration +from sentry_sdk.integrations.logging import LoggingIntegration +from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration +from telegram import Update + +from core.config import config + +repo = Repo(os.getcwd()) +sentry_sdk_git_hash = rev_parse(repo, "HEAD").hexsha +sentry_sdk.init( + config.error_sentry_dsn, + traces_sample_rate=1.0, + release=sentry_sdk_git_hash, + environment="production", + integrations=[ + HttpxIntegration(), + ExcepthookIntegration(always_run=False), + LoggingIntegration(event_level=50), + SqlalchemyIntegration(), + ], +) + + +class Sentry: + @staticmethod + def report_error(update: Update, exc_info): + if not config.error_sentry_dsn: + return + try: + sender_id = update.effective_user.id if update.effective_user else update.effective_chat.id + except AttributeError: + sender_id = 0 + sentry_sdk.set_context( + "Target", {"ChatID": str(update.message.chat_id), "UserID": sender_id, "Msg": update.message.text or ""} + ) + sentry_sdk.capture_exception(exc_info) diff --git a/plugins/system/errorhandler.py b/plugins/system/errorhandler.py index a7b44196..ad82ef5a 100644 --- a/plugins/system/errorhandler.py +++ b/plugins/system/errorhandler.py @@ -11,6 +11,8 @@ from telegram.ext import CallbackContext from core.bot import bot from core.plugin import error_handler, Plugin +from modules.error.pb import PbClient +from modules.error.sentry import Sentry from utils.log import logger notice_chat_id = bot.config.error_notification_chat_id @@ -21,6 +23,8 @@ if not os.path.exists(logs_dir): report_dir = os.path.join(current_dir, "report") if not os.path.exists(report_dir): os.mkdir(report_dir) +pb_client = PbClient() +sentry = Sentry() class ErrorHandler(Plugin): @@ -90,3 +94,19 @@ class ErrorHandler(Plugin): except (BadRequest, Forbidden) as exc: logger.error(f"发送 update_id[{update.update_id}] 错误信息失败 错误信息为") logger.exception(exc) + try: + pb_url = await pb_client.create_pb(error_text) + if pb_url: + await context.bot.send_message( + chat_id=notice_chat_id, + text=f"错误信息已上传至 fars 请查看", + parse_mode=ParseMode.HTML, + ) + except Exception as exc: # pylint: disable=W0703 + logger.error("上传错误信息至 fars 失败") + logger.exception(exc) + try: + sentry.report_error(update, context.error) + except Exception as exc: # pylint: disable=W0703 + logger.error("上传错误信息至 sentry 失败") + logger.exception(exc) diff --git a/plugins/system/log.py b/plugins/system/log.py index a39386ce..8e58a0f7 100644 --- a/plugins/system/log.py +++ b/plugins/system/log.py @@ -5,6 +5,7 @@ from telegram.constants import ChatAction from telegram.ext import CommandHandler, CallbackContext from core.plugin import Plugin, handler +from modules.error.pb import PbClient from utils.decorators.admins import bot_admins_rights_check from utils.log import logger @@ -14,6 +15,21 @@ debug_log = os.path.join(current_dir, "logs", "debug", "debug.log") class Log(Plugin): + def __init__(self): + self.pb_client = PbClient() + self.pb_client.sunset = 3600 + self.pb_client.max_lines = 10000 + + async def send_to_pb(self, file_name: str): + pb_url = "" + try: + with open(file_name, "r", encoding="utf-8") as f: + pb_url = await self.pb_client.create_pb(f.read()) + except Exception as exc: # pylint: disable=W0703 + logger.error("上传错误信息至 fars 失败") + logger.exception(exc) + return pb_url + @handler(CommandHandler, command="send_log", block=False) @bot_admins_rights_check async def send_log(self, update: Update, _: CallbackContext): @@ -21,12 +37,18 @@ class Log(Plugin): logger.info(f"用户 {user.full_name}[{user.id}] send_log 命令请求") message = update.effective_message if os.path.exists(error_log) and os.path.getsize(error_log) > 0: + pb_url = await self.send_to_pb(error_log) await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT) - await message.reply_document(open(error_log, mode="rb+"), caption="Error Log") + await message.reply_document( + open(error_log, mode="rb+"), caption=f"Error Log\n{pb_url}/text" if pb_url else "Error Log" + ) else: await message.reply_text("错误日记未找到") if os.path.exists(debug_log) and os.path.getsize(debug_log) > 0: + pb_url = await self.send_to_pb(debug_log) await message.reply_chat_action(ChatAction.UPLOAD_DOCUMENT) - await message.reply_document(open(debug_log, mode="rb+"), caption="Debug Log") + await message.reply_document( + open(debug_log, mode="rb+"), caption=f"Debug Log\n{pb_url}/text" if pb_url else "Debug Log" + ) else: await message.reply_text("调试日记未找到") diff --git a/poetry.lock b/poetry.lock index 7c7082c4..37b86a09 100644 --- a/poetry.lock +++ b/poetry.lock @@ -388,6 +388,28 @@ url = "https://github.com/thesadru/genshin.py" reference = "HEAD" resolved_reference = "7b3a4a71bfdf84d9f1bf984e91c0bcf73f9dfa7f" +[[package]] +name = "gitdb" +version = "4.0.9" +description = "Git Object Database" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +smmap = ">=3.0.1,<6" + +[[package]] +name = "GitPython" +version = "3.1.29" +description = "GitPython is a python library used to interact with Git repositories" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +gitdb = ">=4.0.1,<5" + [[package]] name = "greenlet" version = "1.1.3" @@ -899,6 +921,38 @@ typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9 [package.extras] jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] +[[package]] +name = "sentry-sdk" +version = "1.9.10" +description = "Python client for Sentry (https://sentry.io)" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +certifi = "*" +urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} + +[package.extras] +aiohttp = ["aiohttp (>=3.5)"] +beam = ["apache-beam (>=2.12)"] +bottle = ["bottle (>=0.12.13)"] +celery = ["celery (>=3)"] +chalice = ["chalice (>=1.16.0)"] +django = ["django (>=1.8)"] +falcon = ["falcon (>=1.4)"] +fastapi = ["fastapi (>=0.79.0)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)"] +httpx = ["httpx (>=0.16.0)"] +pure_eval = ["asttokens", "executing", "pure-eval"] +pyspark = ["pyspark (>=2.4.4)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] +rq = ["rq (>=0.6)"] +sanic = ["sanic (>=0.8)"] +sqlalchemy = ["sqlalchemy (>=1.2)"] +starlette = ["starlette (>=0.19.1)"] +tornado = ["tornado (>=5)"] + [[package]] name = "setuptools" version = "65.4.1" @@ -920,6 +974,14 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "smmap" +version = "5.0.0" +description = "A pure Python implementation of a sliding window memory map manager" +category = "main" +optional = false +python-versions = ">=3.6" + [[package]] name = "sniffio" version = "1.3.0" @@ -1503,6 +1565,14 @@ frozenlist = [ {file = "frozenlist-1.3.1.tar.gz", hash = "sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8"}, ] genshin = [] +gitdb = [ + {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, + {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, +] +GitPython = [ + {file = "GitPython-3.1.29-py3-none-any.whl", hash = "sha256:41eea0deec2deea139b459ac03656f0dd28fc4a3387240ec1d3c259a2c47850f"}, + {file = "GitPython-3.1.29.tar.gz", hash = "sha256:cc36bfc4a3f913e66805a28e84703e419d9c264c1077e537b54f0e1af85dbefd"}, +] greenlet = [ {file = "greenlet-1.1.3-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:8c287ae7ac921dfde88b1c125bd9590b7ec3c900c2d3db5197f1286e144e712b"}, {file = "greenlet-1.1.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:870a48007872d12e95a996fca3c03a64290d3ea2e61076aa35d3b253cf34cd32"}, @@ -2084,6 +2154,10 @@ rich = [ {file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"}, {file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"}, ] +sentry-sdk = [ + {file = "sentry-sdk-1.9.10.tar.gz", hash = "sha256:4fbace9a763285b608c06f01a807b51acb35f6059da6a01236654e08b0ee81ff"}, + {file = "sentry_sdk-1.9.10-py2.py3-none-any.whl", hash = "sha256:2469240f6190aaebcb453033519eae69cfe8cc602065b4667e18ee14fc1e35dc"}, +] setuptools = [ {file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"}, {file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"}, @@ -2092,6 +2166,10 @@ six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +smmap = [ + {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, + {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, +] sniffio = [ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, diff --git a/pyproject.toml b/pyproject.toml index daaab937..9a4e6887 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,8 @@ lxml = "^4.9.1" arko-wrapper = "^0.2.3" fastapi = "^0.85.1" uvicorn = {extras = ["standard"], version = "^0.18.3"} +sentry-sdk = "^1.9.10" +GitPython = "^3.1.29" [tool.poetry.extras] pyro = ["Pyrogram", "TgCrypto"] diff --git a/utils/log/_handler.py b/utils/log/_handler.py index d774a288..260000f7 100644 --- a/utils/log/_handler.py +++ b/utils/log/_handler.py @@ -254,17 +254,5 @@ class Handler(DefaultRichHandler): class FileHandler(Handler): def __init__(self, *args, path: Path, **kwargs): super().__init__(*args, **kwargs) - while True: - try: - path.parent.mkdir(exist_ok=True) - break - except FileNotFoundError: - parent = path.parent - while True: - try: - parent.mkdir(exist_ok=True) - break - except FileNotFoundError: - parent = parent.parent - path.parent.mkdir(exist_ok=True) + path.parent.mkdir(exist_ok=True, parents=True) self.console = Console(width=180, file=FileIO(path), theme=Theme(DEFAULT_STYLE))