mirror of
https://github.com/Balshgit/gpt_chat_bot.git
synced 2025-09-11 22:30:41 +03:00
* rename chatgpt service * add zeus tool for new provider * add zeus tool for new provider * update chat service * update README.md
129 lines
5.2 KiB
Python
129 lines
5.2 KiB
Python
import logging
|
||
import os
|
||
import sys
|
||
from types import FrameType
|
||
from typing import TYPE_CHECKING, Any, cast
|
||
|
||
import graypy
|
||
from loguru import logger
|
||
from sentry_sdk.integrations.logging import EventHandler
|
||
|
||
from constants import LogLevelEnum
|
||
from settings.config import DIR_LOGS, settings
|
||
|
||
if TYPE_CHECKING:
|
||
from loguru import Record
|
||
else:
|
||
Record = dict[str, Any]
|
||
|
||
|
||
class Formatter:
|
||
@staticmethod
|
||
def json_formatter(record: Record) -> str:
|
||
# Обрезаем `\n` в конце логов, т.к. в json формате переносы не нужны
|
||
return Formatter.scrap_sensitive_info(record.get("message", "").strip())
|
||
|
||
@staticmethod
|
||
def sentry_formatter(record: Record) -> str:
|
||
if message := record.get("message", ""):
|
||
record["message"] = Formatter.scrap_sensitive_info(message)
|
||
return "{name}:{function} {message}"
|
||
|
||
@staticmethod
|
||
def text_formatter(record: Record) -> str:
|
||
# WARNING !!!
|
||
# Функция должна возвращать строку, которая содержит только шаблоны для форматирования.
|
||
# Если в строку прокидывать значения из record (или еще откуда-либо),
|
||
# то loguru может принять их за f-строки и попытается обработать, что приведет к ошибке.
|
||
# Например, если нужно достать какое-то значение из поля extra, вместо того чтобы прокидывать его в строку
|
||
# формата, нужно прокидывать подстроку вида {extra[тут_ключ]}
|
||
|
||
if message := record.get("message", ""):
|
||
record["message"] = Formatter.scrap_sensitive_info(message)
|
||
|
||
# Стандартный формат loguru. Задается через env LOGURU_FORMAT
|
||
format_ = (
|
||
"<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
||
"<level>{level: <8}</level> | "
|
||
"<magenta>{name}</magenta>:<magenta>{function}</magenta>:<magenta>{line}</magenta> - "
|
||
"<level>{message}</level>"
|
||
)
|
||
|
||
# Добавляем мета параметры по типу user_id, art_id, которые передаются через logger.bind(...)
|
||
extra = record["extra"]
|
||
if extra:
|
||
formatted = ", ".join(f"{key}" + "={extra[" + str(key) + "]}" for key, value in extra.items())
|
||
format_ += f" - <cyan>{formatted}</cyan>"
|
||
|
||
format_ += "\n"
|
||
|
||
if record["exception"] is not None:
|
||
format_ += "{exception}\n"
|
||
|
||
return format_
|
||
|
||
@staticmethod
|
||
def scrap_sensitive_info(message: str) -> str:
|
||
return message.replace(settings.TELEGRAM_API_TOKEN, "TELEGRAM_API_TOKEN".center(24, "*"))
|
||
|
||
|
||
class InterceptHandler(logging.Handler):
|
||
def emit(self, record: logging.LogRecord) -> None:
|
||
# Get corresponding Loguru level if it exists
|
||
try:
|
||
level = logger.level(record.levelname).name
|
||
except ValueError:
|
||
level = str(record.levelno)
|
||
|
||
# Find caller from where originated the logged message
|
||
frame, depth = logging.currentframe(), 2
|
||
while frame.f_code.co_filename == logging.__file__:
|
||
frame = cast(FrameType, frame.f_back)
|
||
depth += 1
|
||
|
||
logger.opt(depth=depth, exception=record.exc_info).log(
|
||
level, Formatter.scrap_sensitive_info(record.getMessage())
|
||
)
|
||
|
||
|
||
def configure_logging(
|
||
*, level: LogLevelEnum, enable_json_logs: bool, enable_sentry_logs: bool, log_to_file: str | None = None
|
||
) -> None:
|
||
intercept_handler = InterceptHandler()
|
||
|
||
formatter = Formatter.json_formatter if enable_json_logs else Formatter.text_formatter
|
||
|
||
base_config_handlers = [intercept_handler]
|
||
|
||
base_loguru_handler = {
|
||
"level": level.name,
|
||
"serialize": enable_json_logs,
|
||
"format": formatter,
|
||
"colorize": False,
|
||
}
|
||
loguru_handlers = [
|
||
{**base_loguru_handler, "colorize": True, "sink": sys.stdout},
|
||
]
|
||
|
||
if settings.ENABLE_GRAYLOG:
|
||
graylog_handler = graypy.GELFUDPHandler(settings.GRAYLOG_HOST, settings.GRAYLOG_PORT)
|
||
base_config_handlers.append(graylog_handler)
|
||
loguru_handlers.append({**base_loguru_handler, "sink": graylog_handler})
|
||
|
||
if log_to_file:
|
||
file_path = DIR_LOGS / log_to_file
|
||
if not os.path.exists(log_to_file):
|
||
with open(file_path, "w") as f:
|
||
f.write("")
|
||
loguru_handlers.append({**base_loguru_handler, "sink": file_path})
|
||
|
||
logging.basicConfig(handlers=base_config_handlers, level=level.name)
|
||
logger.configure(handlers=loguru_handlers)
|
||
|
||
# sentry sdk не умеет из коробки работать с loguru, нужно добавлять хандлер
|
||
# https://github.com/getsentry/sentry-python/issues/653#issuecomment-788854865
|
||
# https://forum.sentry.io/t/changing-issue-title-when-logging-with-traceback/446
|
||
if enable_sentry_logs:
|
||
handler = EventHandler(level=logging.WARNING)
|
||
logger.add(handler, diagnose=True, level=logging.WARNING, format=Formatter.sentry_formatter)
|