添加错误平台

Co-authored-by: SiHuan <sihuan@sakuya.love>
Co-authored-by: 洛水居室 <luoshuijs@outlook.com>
This commit is contained in:
omg-xtao 2022-10-19 20:22:24 +08:00 committed by GitHub
parent 8c12237e85
commit 1f17e56824
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 204 additions and 15 deletions

View File

@ -58,3 +58,9 @@ LOGGER_LOCALS_MAX_STRING=80
# WEB_URL=http://localhost:8080/ # WEB_URL=http://localhost:8080/
# WEB_HOST=localhost # WEB_HOST=localhost
# WEB_PORT=8080 # WEB_PORT=8080
# error
# ERROR_PB_URL=https://fars.ee
# ERROR_PB_SUNSET=43200
# ERROR_PB_MAX_LINES=1000
# ERROR_SENTRY_DSN=

View File

@ -64,6 +64,11 @@ class BotConfig(BaseSettings):
web_host: str = "localhost" web_host: str = "localhost"
web_port: int = 8080 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: class Config:
case_sensitive = False case_sensitive = False
json_loads = json.loads json_loads = json.loads

26
modules/error/pb.py Normal file
View File

@ -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"]

42
modules/error/sentry.py Normal file
View File

@ -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)

View File

@ -11,6 +11,8 @@ from telegram.ext import CallbackContext
from core.bot import bot from core.bot import bot
from core.plugin import error_handler, Plugin from core.plugin import error_handler, Plugin
from modules.error.pb import PbClient
from modules.error.sentry import Sentry
from utils.log import logger from utils.log import logger
notice_chat_id = bot.config.error_notification_chat_id 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") report_dir = os.path.join(current_dir, "report")
if not os.path.exists(report_dir): if not os.path.exists(report_dir):
os.mkdir(report_dir) os.mkdir(report_dir)
pb_client = PbClient()
sentry = Sentry()
class ErrorHandler(Plugin): class ErrorHandler(Plugin):
@ -90,3 +94,19 @@ class ErrorHandler(Plugin):
except (BadRequest, Forbidden) as exc: except (BadRequest, Forbidden) as exc:
logger.error(f"发送 update_id[{update.update_id}] 错误信息失败 错误信息为") logger.error(f"发送 update_id[{update.update_id}] 错误信息失败 错误信息为")
logger.exception(exc) 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"错误信息已上传至 <a href='{pb_url}'>fars</a> 请查看",
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)

View File

@ -5,6 +5,7 @@ from telegram.constants import ChatAction
from telegram.ext import CommandHandler, CallbackContext from telegram.ext import CommandHandler, CallbackContext
from core.plugin import Plugin, handler from core.plugin import Plugin, handler
from modules.error.pb import PbClient
from utils.decorators.admins import bot_admins_rights_check from utils.decorators.admins import bot_admins_rights_check
from utils.log import logger from utils.log import logger
@ -14,6 +15,21 @@ debug_log = os.path.join(current_dir, "logs", "debug", "debug.log")
class Log(Plugin): 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) @handler(CommandHandler, command="send_log", block=False)
@bot_admins_rights_check @bot_admins_rights_check
async def send_log(self, update: Update, _: CallbackContext): 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 命令请求") logger.info(f"用户 {user.full_name}[{user.id}] send_log 命令请求")
message = update.effective_message message = update.effective_message
if os.path.exists(error_log) and os.path.getsize(error_log) > 0: 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_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: else:
await message.reply_text("错误日记未找到") await message.reply_text("错误日记未找到")
if os.path.exists(debug_log) and os.path.getsize(debug_log) > 0: 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_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: else:
await message.reply_text("调试日记未找到") await message.reply_text("调试日记未找到")

78
poetry.lock generated
View File

@ -388,6 +388,28 @@ url = "https://github.com/thesadru/genshin.py"
reference = "HEAD" reference = "HEAD"
resolved_reference = "7b3a4a71bfdf84d9f1bf984e91c0bcf73f9dfa7f" 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]] [[package]]
name = "greenlet" name = "greenlet"
version = "1.1.3" version = "1.1.3"
@ -899,6 +921,38 @@ typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9
[package.extras] [package.extras]
jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] 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]] [[package]]
name = "setuptools" name = "setuptools"
version = "65.4.1" version = "65.4.1"
@ -920,6 +974,14 @@ category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 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]] [[package]]
name = "sniffio" name = "sniffio"
version = "1.3.0" version = "1.3.0"
@ -1503,6 +1565,14 @@ frozenlist = [
{file = "frozenlist-1.3.1.tar.gz", hash = "sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8"}, {file = "frozenlist-1.3.1.tar.gz", hash = "sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8"},
] ]
genshin = [] 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 = [ greenlet = [
{file = "greenlet-1.1.3-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:8c287ae7ac921dfde88b1c125bd9590b7ec3c900c2d3db5197f1286e144e712b"}, {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"}, {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-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"},
{file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"}, {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 = [ setuptools = [
{file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"}, {file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"},
{file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"}, {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-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, {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 = [ sniffio = [
{file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},

View File

@ -37,6 +37,8 @@ lxml = "^4.9.1"
arko-wrapper = "^0.2.3" arko-wrapper = "^0.2.3"
fastapi = "^0.85.1" fastapi = "^0.85.1"
uvicorn = {extras = ["standard"], version = "^0.18.3"} uvicorn = {extras = ["standard"], version = "^0.18.3"}
sentry-sdk = "^1.9.10"
GitPython = "^3.1.29"
[tool.poetry.extras] [tool.poetry.extras]
pyro = ["Pyrogram", "TgCrypto"] pyro = ["Pyrogram", "TgCrypto"]

View File

@ -254,17 +254,5 @@ class Handler(DefaultRichHandler):
class FileHandler(Handler): class FileHandler(Handler):
def __init__(self, *args, path: Path, **kwargs): def __init__(self, *args, path: Path, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
while True: path.parent.mkdir(exist_ok=True, parents=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)
self.console = Console(width=180, file=FileIO(path), theme=Theme(DEFAULT_STYLE)) self.console = Console(width=180, file=FileIO(path), theme=Theme(DEFAULT_STYLE))