Compare commits
8 Commits
exceptionh
...
main
Author | SHA1 | Date | |
---|---|---|---|
4f88ab21ad | |||
34bdabc4db | |||
7f92741462 | |||
8108e1fc85 | |||
e5aa752c59 | |||
3866df01a3 | |||
e86cee955e | |||
f1e120c48a |
100
Log4p/Log4p.py
Normal file
100
Log4p/Log4p.py
Normal file
|
@ -0,0 +1,100 @@
|
|||
from Log4p.plugins_for_core import *
|
||||
|
||||
class LogManager:
|
||||
def __init__(self) -> None:
|
||||
self.public_formatter = logging.Formatter(
|
||||
fmt='[%(asctime)s][%(name)s/%(levelname)s][%(funcName)s]:%(message)s',
|
||||
datefmt='%H:%M:%S'
|
||||
)
|
||||
|
||||
def GetLogger(self, log_name: str = "default",
|
||||
out_to_console: bool = True,
|
||||
web_log_mode: bool = False,
|
||||
WSpost_url: str = "",
|
||||
HTTPpost_url: str = "",
|
||||
http_mode: bool = False,
|
||||
custom_formatter: logging.Formatter = None):
|
||||
# 确保日志名称有效
|
||||
log_name = log_name if log_name else "default"
|
||||
if out_to_console:
|
||||
log_folder = f'./logs/{log_name}'
|
||||
if not os.path.exists(log_folder):
|
||||
os.makedirs(log_folder, exist_ok=True)
|
||||
|
||||
logger = logging.getLogger(log_name)
|
||||
if logger.hasHandlers():
|
||||
# Logger已经配置过处理器,避免重复配置
|
||||
return logger
|
||||
|
||||
# 颜色配置
|
||||
log_color_config = {
|
||||
'DEBUG': 'bold_blue', 'INFO': 'bold_cyan',
|
||||
'WARNING': 'bold_yellow', 'ERROR': 'red',
|
||||
'CRITICAL': 'bold_red', 'RESET': 'reset',
|
||||
'asctime': 'green'
|
||||
}
|
||||
if out_to_console:
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.DEBUG)
|
||||
console_formatter = colorlog.ColoredFormatter(
|
||||
fmt='%(log_color)s [%(asctime)s][%(name)s/%(levelname)s][%(funcName)s]:%(message)s %(reset)s',
|
||||
datefmt='%H:%M:%S',
|
||||
log_colors=log_color_config
|
||||
)
|
||||
if custom_formatter:
|
||||
console_formatter = custom_formatter
|
||||
|
||||
if isinstance(console_handler, logging.StreamHandler):
|
||||
console_formatter = colorlog.ColoredFormatter(fmt=f"%(log_color)s {console_formatter._fmt} %(reset)s",datefmt=console_formatter.datefmt, log_colors=log_color_config)
|
||||
console_handler.setFormatter(console_formatter)
|
||||
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
|
||||
file_handler = logging.FileHandler(
|
||||
filename=f'logs/{log_name}/{log_name}.log', mode='a', encoding='utf-8')
|
||||
file_handler.setLevel(logging.INFO)
|
||||
file_handler.setFormatter(self.public_formatter)
|
||||
|
||||
if custom_formatter:
|
||||
file_handler.setFormatter(custom_formatter)
|
||||
|
||||
# 检查代码是否在异步环境中运行
|
||||
if asyncio.iscoroutinefunction(logging.Handler.emit):
|
||||
queue = asyncio.Queue()
|
||||
queue_handler = QueueHandler(queue)
|
||||
queue_listener = QueueListener(queue, file_handler)
|
||||
logger.addHandler(queue_handler)
|
||||
asyncio.ensure_future(queue_listener.start())
|
||||
else:
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
if web_log_mode and WSpost_url:
|
||||
websocket_handler = WebsocketHandler(WSpost_url)
|
||||
websocket_handler.setLevel(logging.INFO)
|
||||
formatter = self.public_formatter
|
||||
if custom_formatter:
|
||||
formatter = custom_formatter
|
||||
websocket_handler.setFormatter(formatter)
|
||||
logger.addHandler(websocket_handler)
|
||||
|
||||
if http_mode and HTTPpost_url:
|
||||
# 检查代码是否在异步环境中运行
|
||||
if asyncio.iscoroutinefunction(logging.Handler.emit):
|
||||
async_http_hander = AsyncHTTPhandler(HTTPpost_url)
|
||||
async_http_hander.setLevel(logging.INFO)
|
||||
formatter = self.public_formatter
|
||||
if custom_formatter:
|
||||
formatter = custom_formatter
|
||||
async_http_hander.setFormatter(formatter)
|
||||
logger.addHandler(async_http_hander)
|
||||
http_handler = HTTPhandler(HTTPpost_url)
|
||||
http_handler.setLevel(logging.INFO)
|
||||
formatter = self.public_formatter
|
||||
if custom_formatter:
|
||||
formatter = custom_formatter
|
||||
http_handler.setFormatter(formatter)
|
||||
logger.addHandler(http_handler)
|
||||
|
||||
return logger
|
49
Log4p/Readme.MD
Normal file
49
Log4p/Readme.MD
Normal file
|
@ -0,0 +1,49 @@
|
|||
|
||||
# Log4p日志库
|
||||
|
||||
## 描述
|
||||
|
||||
这是一个可以支持异步记录日志,发送到HTTP,websocket服务器,同时配置简单,它还实现了识别代码异步和同步的环境
|
||||
以支持不同环境下的日志记录,当然了,理论上,这个日志如果2个模式都启用(websocket,http),那么会分别的往2个地方发送日志
|
||||
|
||||
### 示例用法
|
||||
|
||||
```python
|
||||
#from core import *
|
||||
from Log4p import LogManager
|
||||
|
||||
logger = LogManager().GetLogger(
|
||||
log_name='example',
|
||||
out_to_console=True,
|
||||
web_log_mode=True,
|
||||
WSpost_url='ws://localhost:8765',
|
||||
HTTPpost_url='http://localhost:8765',
|
||||
http_mode = True
|
||||
custom_fomatter='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
logger.info('这是一个成功信息')
|
||||
logger.debug('这是一个调试信息')
|
||||
logger.critical('这是一个严重错误信息')
|
||||
logger.error('这是一个错误信息')
|
||||
logger.warning('这是一个警告信息')
|
||||
```
|
||||
|
||||
### 参数介绍
|
||||
|
||||
- `log_name`: 指定日志记录器的名称
|
||||
- `out_to_console`: 是否输出到控制台,默认true
|
||||
- `web_log_mode`:是否启用websocket日志输出模式,默认false
|
||||
- `WSpost_url`: 如果启用websocket模式,则传入post_url,否则不管,默认None
|
||||
- `HTTPpost_url`: 如果启用http模式,则传入post_url,否则不管,默认None
|
||||
- `http_mode`: 是否启用http日志输出模式,默认false
|
||||
|
||||
## 更新日志
|
||||
|
||||
# 2024/2/20 1:01
|
||||
|
||||
更新了自定义格式功能,修复了少量bug
|
||||
|
||||
## 添加参数
|
||||
|
||||
- `custom_formatter`: 自定义格式化函数,默认None
|
BIN
Log4p/__init__.py
Normal file
BIN
Log4p/__init__.py
Normal file
Binary file not shown.
1
Log4p/core.py
Normal file
1
Log4p/core.py
Normal file
|
@ -0,0 +1 @@
|
|||
from Log4p.Log4p import LogManager
|
11
Log4p/plugins/DecoratorsTools.py
Normal file
11
Log4p/plugins/DecoratorsTools.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
import warnings
|
||||
|
||||
def deprecated(use_func, d_version):
|
||||
def decorator(func):
|
||||
async def wrapper(*args, **kwargs):
|
||||
warnings.warn(f"The '{func.__name__}' function is deprecated, "
|
||||
f"use '{use_func}' instead. Deprecated in version {d_version}.",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
return await func(*args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
35
Log4p/plugins/HttpHander.py
Normal file
35
Log4p/plugins/HttpHander.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
import logging
|
||||
import requests
|
||||
import httpx
|
||||
|
||||
class HTTPhandler(logging.Handler):
|
||||
def __init__(self, url):
|
||||
super().__init__()
|
||||
self.url = url
|
||||
|
||||
def emit(self, record):
|
||||
log_entry = self.format(record)
|
||||
payload = {'log': log_entry}
|
||||
try:
|
||||
response = requests.post(self.url, json=payload)
|
||||
if not response.ok:
|
||||
raise ValueError(response.text)
|
||||
except Exception as e:
|
||||
logging.error("Failed to send log to %s: %s", self.url, e)
|
||||
|
||||
class AsyncHTTPhandler(logging.Handler):
|
||||
def __init__(self, url):
|
||||
super().__init__()
|
||||
self.url = url
|
||||
|
||||
async def emit(self, record):
|
||||
log_entry = self.format(record)
|
||||
payload = {'log': log_entry}
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=120,max_redirects=5) as client:
|
||||
response = await client.post(self.url, json=payload)
|
||||
if not response.is_success:
|
||||
raise ValueError(await response.text())
|
||||
except Exception as e:
|
||||
logging.error("Failed to send log to %s: %s", self.url, e)
|
||||
|
BIN
Log4p/plugins/__init__.py
Normal file
BIN
Log4p/plugins/__init__.py
Normal file
Binary file not shown.
28
Log4p/plugins/websocketHander.py
Normal file
28
Log4p/plugins/websocketHander.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
import logging
|
||||
import asyncio
|
||||
import websockets
|
||||
|
||||
class WebsocketHandler(logging.Handler):
|
||||
def __init__(self, server_address):
|
||||
super().__init__()
|
||||
self.server_address = server_address
|
||||
|
||||
async def send_log_async(self, message):
|
||||
async with websockets.connect(self.server_address) as websocket:
|
||||
await websocket.send(message)
|
||||
|
||||
def send_log_sync(self, message):
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
loop.run_until_complete(self.send_log_async(message))
|
||||
|
||||
def emit(self, record):
|
||||
log_entry = self.format(record)
|
||||
|
||||
if asyncio.get_event_loop().is_running():
|
||||
asyncio.create_task(self.send_log_async(log_entry))
|
||||
else:
|
||||
try:
|
||||
self.send_log_sync(log_entry)
|
||||
except Exception as e:
|
||||
logging.warning("Failed to send log synchronously: %s", e)
|
8
Log4p/plugins_for_core.py
Normal file
8
Log4p/plugins_for_core.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
import logging
|
||||
import colorlog
|
||||
import os
|
||||
import asyncio
|
||||
from logging.handlers import QueueHandler, QueueListener
|
||||
from Log4p.plugins.websocketHander import WebsocketHandler
|
||||
from Log4p.plugins.HttpHander import HTTPhandler , AsyncHTTPhandler
|
||||
from Log4p.plugins.DecoratorsTools import *
|
24
Log4p/setup.py
Normal file
24
Log4p/setup.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name='Log4p',
|
||||
version='1.0.0',
|
||||
packages=find_packages(),
|
||||
author='芙宁娜',
|
||||
author_email='3072252442@qq.com',
|
||||
description='A logging library for Python',
|
||||
long_description=open('Readme.md','r',encoding='utf-8').read(),
|
||||
long_description_content_type='text/markdown',
|
||||
url='https://github.com/KOKOMI12345/Log4p',
|
||||
install_requires=[
|
||||
'requests',
|
||||
'websockets',
|
||||
'colorlog',
|
||||
'httpx',
|
||||
],
|
||||
classifiers=[
|
||||
'Programming Language :: Python :: 3',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: OS Independent',
|
||||
],
|
||||
)
|
Binary file not shown.
40
global_exc_handler.py
Normal file
40
global_exc_handler.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
import traceback,sys
|
||||
from Log4p.core import *
|
||||
|
||||
Mainlogger = LogManager().GetLogger("MainThread")
|
||||
|
||||
def format_java_stack_trace(exctype, value, tb, nested=False):
|
||||
tb_list = traceback.extract_tb(tb)
|
||||
|
||||
if nested:
|
||||
exception_info = f"{exctype.__name__}: {value}\n"
|
||||
else:
|
||||
exception_info = f"Exception has occurred: {exctype.__name__}: {value}\n"
|
||||
|
||||
for filename, lineno, funcname, line in tb_list:
|
||||
exception_info += f" at {funcname} ({filename}:{lineno})\n"
|
||||
|
||||
# 检查是否有原因和其他信息
|
||||
cause = getattr(value, '__cause__', None)
|
||||
context = getattr(value, '__context__', None)
|
||||
if cause:
|
||||
exception_info += "Caused by: "
|
||||
exception_info += format_java_stack_trace(type(cause), cause, cause.__traceback__, nested=True)
|
||||
if context:
|
||||
exception_info += "Suppressed: "
|
||||
exception_info += format_java_stack_trace(type(context), context, context.__traceback__, nested=True)
|
||||
|
||||
return exception_info
|
||||
|
||||
def exception_hook(exctype, value, tb):
|
||||
# 获取回溯信息并格式化为字符串
|
||||
tb_str = format_java_stack_trace(exctype, value, tb)
|
||||
|
||||
# 记录异常信息到日志
|
||||
exception_info = "发生异常:\n"
|
||||
exception_info += tb_str
|
||||
Mainlogger.critical(exception_info)
|
||||
|
||||
|
||||
sys.excepthook = exception_hook
|
||||
|
Loading…
Reference in New Issue
Block a user