🎨 改进了对占位符和正则表达式的处理

This commit is contained in:
Nanaloveyuki
2025-07-29 14:17:22 +08:00
parent 89bc381235
commit 796a8a82f6
8 changed files with 331 additions and 206 deletions

View File

@ -7,7 +7,7 @@
"enable_console": true,
"enable_file": true,
"console_color": true,
"console_level": "INFO",
"console_level": "DEBUG",
"console_format": "{time} {levelname} | {prefix}{message}",
"console_prefix": "Auto",
"console_encoding": "utf-8",

View File

@ -11,96 +11,116 @@ from ..utils.fmt import fmt_file, fmt_message, fmt_console
from ..utils.configs import get_config, set_config
from ..utils.time import get_asctime
import pathlib
from pathlib import Path
def _get_full_path(file_path, file_name):
file_path.mkdir(parents=True, exist_ok=True)
return file_path / file_name
file_path = pathlib.Path(get_config("file_path"))
file_name = get_config("file_name")
file_format = get_config("file_format")
file_encoding = get_config("file_encoding")
is_enable_console = get_config("enable_console")
is_enable_file = get_config("enable_file")
class Logger:
def __init__(self):
if pathlib.Path(file_path).exists():
if not pathlib.Path(file_path).is_dir():
self.warn("日志文件路径不是目录,已自动自动使用默认目录")
project_root = Path(__file__).parent.parent.parent.parent
file_path = project_root / get_config("file_path")
file_path.mkdir(parents=True, exist_ok=True)
if file_path.exists():
if not file_path.is_dir():
self.warn("日志文件路径不是目录,已自动使用默认目录")
set_config("file_path", "./logs")
pathlib.Path("./logs").mkdir(parents=True, exist_ok=True)
if _get_full_path(file_path, file_name).exists():
current_file = _get_full_path(file_path, get_config("file_name"))
if current_file.exists():
from os import rename
rename(_get_full_path(file_path, file_name), _get_full_path(file_path, f"{get_asctime().replace(':', '-')}.log"))
rename(current_file, _get_full_path(file_path, f"{get_asctime().replace(':', '-')}.log"))
self.debug("日志文件已存在,已自动重命名")
def _log(self, msg, pf, lvn):
if is_enable_file:
with open(_get_full_path(file_path, file_name), "a", encoding=file_encoding) as f:
f.write(fmt_file(lvn, fmt_message(msg, no_placeholder=True), pf))
if is_enable_console:
def _log(self, msg, pf, lvn, no_file: bool = False, no_console: bool = False):
project_root = Path(__file__).parent.parent.parent.parent
file_path = project_root / get_config("file_path")
file_path.mkdir(parents=True, exist_ok=True)
file_name = get_config("file_name")
file_encoding = get_config("file_encoding")
is_enable_file = get_config("enable_file")
is_enable_console = get_config("enable_console")
if not no_file and is_enable_file:
try:
with open(_get_full_path(file_path, file_name), "a", encoding=file_encoding) as f:
f.write(fmt_file(lvn, fmt_message(msg, no_placeholder=True, no_process=True), pf))
except Exception as e:
self.error(f"日志写入失败: {str(e)}", no_file=True)
if not no_console and is_enable_console:
console_output = fmt_console(lvn, fmt_message(msg, no_placeholder=True), pf)
if console_output is not None:
print(console_output)
return fmt_console(lvn, fmt_message(msg, no_placeholder=True), pf)
def debug(self, message: Any, prefix: str | None = None, level: int = 0) -> Optional[str]:
def debug(self, message: Any, prefix: str | None = None, level: int = 0, no_file: bool = False, no_console: bool = False) -> Optional[str]:
"""
调试日志
Debug log
:param message: 消息内容 Message content
:param prefix: 前缀 Prefix
:param level: 日志级别 Log level(0~9)
:param no_file: 不写入文件 Do not write to file
:param no_console: 不输出到控制台 Do not output to console
"""
return self._log(message, prefix, level)
return self._log(message, prefix, level, no_file, no_console)
def info(self, message: Any, prefix: str | None = None, level: int = 10) -> Optional[str]:
def info(self, message: Any, prefix: str | None = None, level: int = 10, no_file: bool = False, no_console: bool = False) -> Optional[str]:
"""
信息日志
Info log
:param message: 消息内容 Message content
:param prefix: 前缀 Prefix
:param level: 日志级别 Log level(10~19)
:param no_file: 不写入文件 Do not write to file
:param no_console: 不输出到控制台 Do not output to console
"""
return self._log(message, prefix, level)
return self._log(message, prefix, level, no_file, no_console)
def warn(self, message: Any, prefix: str | None = None, level: int = 20) -> Optional[str]:
def warn(self, message: Any, prefix: str | None = None, level: int = 20, no_file: bool = False, no_console: bool = False) -> Optional[str]:
"""
警告日志
Warn log
:param message: 消息内容 Message content
:param prefix: 前缀 Prefix
:param level: 日志级别 Log level(20~29)
:param no_file: 不写入文件 Do not write to file
:param no_console: 不输出到控制台 Do not output to console
"""
return self._log(message, prefix, level)
return self._log(message, prefix, level, no_file, no_console)
def error(self, message: Any, prefix: str | None = None, level: int = 30) -> Optional[str]:
def error(self, message: Any, prefix: str | None = None, level: int = 30, no_file: bool = False, no_console: bool = False) -> Optional[str]:
"""
错误日志
Error log
:param message: 消息内容 Message content
:param prefix: 前缀 Prefix
:param level: 日志级别 Log level(30~39)
:param no_file: 不写入文件 Do not write to file
:param no_console: 不输出到控制台 Do not output to console
"""
return self._log(message, prefix, level)
return self._log(message, prefix, level, no_file, no_console)
def critical(self, message: Any, prefix: str | None = None, level: int = 40) -> Optional[str]:
def critical(self, message: Any, prefix: str | None = None, level: int = 40, no_file: bool = False, no_console: bool = False) -> Optional[str]:
"""
严重错误日志
Critical error log
:param message: 消息内容 Message content
:param prefix: 前缀 Prefix
:param level: 日志级别 Log level(40~49)
:param no_file: 不写入文件 Do not write to file
:param no_console: 不输出到控制台 Do not output to console
"""
return self._log(message, prefix, level)
return self._log(message, prefix, level, no_file, no_console)
def log(self, message: Any, prefix: str | None = None, level: int = 50) -> Optional[str]:
def log(self, message: Any, prefix: str | None = None, level: int = 50, no_file: bool = False, no_console: bool = False) -> Optional[str]:
"""
自定义日志
Custom log
:param message: 消息内容 Message content
:param prefix: 前缀 Prefix
:param level: 日志级别 Log level(50~59...)
:param no_file: 不写入文件 Do not write to file
:param no_console: 不输出到控制台 Do not output to console
"""
return self._log(message, prefix, level)
return self._log(message, prefix, level, no_file, no_console)

View File

@ -9,11 +9,43 @@ py-logiliteal's config settings, used to set py-logiliteal's global config
import json
from os import remove
import shutil
import os
from pathlib import Path
from typing import Union, Optional, Tuple
from logging import error
DEFAULT_CONFIG_PATH = "logger_config.json"
def get_config_path():
# 检查环境变量
env_config = os.getenv('LOGILITEAL_CONFIG')
if env_config and os.path.exists(env_config):
return env_config
# 检查当前工作目录
cwd_config = os.path.join(os.getcwd(), 'logger_config.json')
if os.path.exists(cwd_config):
return cwd_config
# 检查XDG配置目录
xdg_config_dir = os.getenv('XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))
xdg_config_path = os.path.join(xdg_config_dir, 'logiliteal', 'logger_config.json')
# 创建目录(如果不存在)
if not os.path.exists(os.path.dirname(xdg_config_path)):
try:
os.makedirs(os.path.dirname(xdg_config_path), exist_ok=True)
except Exception as e:
error(f"创建配置目录失败: {e}")
# 回退到项目根目录的配置文件
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
fallback_config = os.path.join(project_root, 'logger_config.json')
if os.path.exists(fallback_config):
return fallback_config
else:
return xdg_config_path
return xdg_config_path
DEFAULT_CONFIG_PATH = get_config_path()
DEFAULT_CONFIG = {
"file_level": "DEBUG",
"file_name": "latest.log",
@ -23,7 +55,7 @@ DEFAULT_CONFIG = {
"enable_console": True,
"enable_file": True,
"console_color": True,
"console_level": "INFO",
"console_level": "DEBUG",
"console_format": "{time} {levelname} | {prefix}{message}",
"console_prefix": "Auto",
"console_encoding": "utf-8",
@ -94,6 +126,8 @@ def get_config(select: str = None) -> Union[dict, str, bool, int, None]:
g_config_cache = json.load(f)
g_config_mtime = current_mtime
else:
# 确保目录存在
config_path.parent.mkdir(parents=True, exist_ok=True)
with open(config_path, "w", encoding="utf-8") as f:
json.dump(DEFAULT_CONFIG, f, indent=4)
g_config_cache = DEFAULT_CONFIG

View File

@ -10,7 +10,15 @@ from .configs import get_config
from typing import Any, Optional
from .time import get_asctime, get_time, get_weekday, get_date
from .styles import set_color, set_bg_color
import re
from .regex import (
process_links,
process_markdown_formats,
process_html_styles,
process_special_tags,
process_color_formatting
)
from .placeholder import process_placeholder, SafeDict
if get_config("time_color") is None:
time_color = "#28ffb6"
@ -61,29 +69,28 @@ def fmt_placeholder(message: Any, use_date_color: bool = True) -> str:
:param message: 消息内容 Message content
:return: 格式化后的消息 Formatted message
"""
class SafeDict(dict):
def __missing__(self, key):
return f'{{{key}}}'
if not isinstance(message, str):
message = str(message)
if use_date_color:
message = message.format_map(SafeDict(
asctime = set_color(get_asctime(),time_color),
time = set_color(get_time(),time_color),
weekday = set_color(get_weekday(),time_color),
date = set_color(get_date(),time_color)
))
else:
message = message.format_map(SafeDict(
asctime = get_asctime(),
time = get_time(),
weekday = get_weekday(),
date = get_date(),
))
return message
def fmt_message(message: Any, no_placeholder: bool = False, no_color: bool = False) -> str:
context = {
"asctime": set_color(get_asctime(), time_color) if use_date_color else get_asctime(),
"time": set_color(get_time(), time_color) if use_date_color else get_time(),
"weekday": set_color(get_weekday(), time_color) if use_date_color else get_weekday(),
"date": set_color(get_date(), time_color) if use_date_color else get_date(),
}
return process_placeholder(message, context)
def fmt_message(
message: Any,
no_placeholder: bool = False,
no_style: bool = False,
no_process: bool = False,
no_tags: bool = False,
no_links: bool = False,
no_markdown: bool = False,
no_html: bool = False,
) -> str:
"""
格式化消息内容
Format message content
@ -91,26 +98,29 @@ def fmt_message(message: Any, no_placeholder: bool = False, no_color: bool = Fal
:return: 格式化后的消息 Formatted message
"""
def process_color_tags(msg: str) -> str:
# 优化处理顺序,确保链接和基础格式优先处理
processed = _process_color_formatting(
_process_special_tags(
_process_html_styles(
_process_markdown_formats(
_process_links(msg)
)
)
)
def process_color_tags(msg: str, no_process: bool = False) -> str:
processed = process_color_formatting(
process_special_tags(
process_html_styles(
process_markdown_formats(
process_links(msg, no_links),
no_markdown
),
no_html
),
no_tags
),
no_process
)
return processed
if no_color:
processed_message = str(message)
else:
processed_message = process_color_tags(str(message))
if no_placeholder:
return processed_message
else:
return process_color_tags(fmt_placeholder(processed_message)) if not no_color else fmt_placeholder(processed_message)
processed_message = str(message)
if not no_placeholder:
processed_message = fmt_placeholder(processed_message)
if not no_style:
processed_message = process_color_tags(processed_message)
if no_process:
return message
return processed_message
def fmt_level_name(level_name: str) -> str:
if get_config("console_color") != True:
@ -137,7 +147,7 @@ def fmt_console(level: int, message: Any, prefix: str | None = None) -> Optional
:return: 格式化后的消息 Formatted message
"""
console_level = get_config("console_level")
if level != -1 and fmt_level(console_level) > level:
if level != -1 and level < fmt_level(console_level):
return None
fmt = get_config("console_format")
prefix = prefix or ""
@ -157,111 +167,14 @@ def fmt_file(level: int, message: Any, prefix: str | None = None) -> Optional[st
"""
fl = get_config("file_level")
fmt = get_config("file_format")
if fmt_level(fl) > level:
if level != -1 and level < fmt_level(fl):
return None
if prefix is None:
prefix = ""
fmt = fmt_placeholder(fmt, use_date_color=False)
return f"{fmt.format(
levelname = fmt_level_number(level),
prefix = fmt_message(prefix, no_placeholder=True, no_color=True),
message = fmt_message(message, no_placeholder=True, no_color=True)
prefix = fmt_message(prefix, no_placeholder=True, no_style=True),
message = fmt_message(message, no_placeholder=True, no_style=True)
)}\n"
def _process_links(text: str) -> str:
from collections import deque
link_stack = deque()
placeholder_count = 0
placeholders = {}
def replace_link(m):
nonlocal placeholder_count
placeholder_count += 1
if len(m.groups()) == 2 and m.group(2) and not m.group(1).startswith('http'):
# Markdown链接 [text](url)
url = m.group(2).strip()
text = m.group(1)
else:
url = m.group(1)
text = m.group(2)
placeholder = f"__LINK_PLACEHOLDER_{placeholder_count}__"
placeholders[placeholder] = (url if url else "#", text)
link_stack.append(placeholder)
return placeholder
text = re.sub(r'<a\s+href="([^"]+)">(.*?)</a>', replace_link, text, flags=re.DOTALL)
text = re.sub(r'<link\s+href="([^"]+)">(.*?)</link>', replace_link, text, flags=re.DOTALL)
text = re.sub(r'\[(.*?)\]\((.*?)\)', replace_link, text)
for placeholder, (url, text_content) in placeholders.items():
ansi_link = f'\033]8;;{url}\033\\{set_color("\033[4m" + text_content, "#5f93ff")}\033]8;;\033\\'
text = text.replace(placeholder, ansi_link)
return text
def _process_markdown_formats(text: str) -> str:
# Markdown粗体 (**text**)
text = re.sub(r'\*\*(.*?)\*\*', '\033[1m\\g<1>\033[22m', text)
# Markdown斜体 (*text*)
text = re.sub(r'\*(.*?)\*', '\033[3m\\g<1>\033[23m', text)
# Markdown下划线 (__text__)
text = re.sub(r'__(.*?)__', '\033[4m\\g<1>\033[24m', text)
# Markdown删除线 (~~text~~)
text = re.sub(r'~~(.*?)~~', '\033[9m\\g<1>\033[29m', text)
return text
def _process_html_styles(text: str) -> str:
text = re.sub(r'<i>([^<]*?)(</i>|$)',
lambda m: '\033[3m' + m.group(1) + '\033[23m', text, flags=re.DOTALL)
text = re.sub(r'<b>([^<]*?)</b>',
lambda m: '\033[1m' + m.group(1) + '\033[22m', text)
text = re.sub(r'<u>([^<]*?)</u>',
lambda m: '\033[4m' + m.group(1) + '\033[24m', text)
text = re.sub(r'<s>([^<]*?)(</s>|$)',
lambda m: '\033[9m' + m.group(1) + '\033[29m', text, flags=re.DOTALL)
return text
def _process_special_tags(text: str) -> str:
text = re.sub(r'<br>', '\n', text)
text = re.sub(r'<c>', '\033[0m', text)
text = re.sub(r'<p>(.*?)</p>', r'\n\033[0m\g<1>\033[0m\n', text, flags=re.DOTALL)
text = re.sub(r'<p>(.*?)(</p>|$)', r'\n\033[0m\g<1>\033[0m\n', text, flags=re.DOTALL)
text = re.sub(r'</p>', '\033[0m\n', text)
return text
def _process_color_formatting(text: str) -> str:
import re
color_pattern = r'<#([0-9a-fA-F]{6})>'
close_pattern = r'</>'
parts = re.split(f'({color_pattern}|{close_pattern})', text)
result = []
color_stack = []
for part in parts:
if part and re.fullmatch(color_pattern, part):
color = re.match(color_pattern, part).group(1)
color_stack.append(color)
continue
elif part == '</>':
if color_stack:
color_stack.pop()
continue
elif part:
if color_stack:
current_color = color_stack[-1]
r = int(current_color[0:2], 16)
g = int(current_color[2:4], 16)
b = int(current_color[4:6], 16)
ansi_code = f'\033[38;2;{r};{g};{b}m'
reset_code = '\033[0m'
result.append(f'{ansi_code}{part}{reset_code}')
else:
result.append(part)
processed_text = ''.join(result)
processed_text = re.sub(f'{color_pattern}|{close_pattern}|[0-9a-fA-F]{{6}}', '', processed_text)
return processed_text

View File

@ -0,0 +1,62 @@
"""
占位符处理工具
"""
from typing import Dict, Any, Optional
import re
class SafeDict(Dict[str, Any]):
"""安全的字典类,用于处理缺失键的占位符替换"""
def __missing__(self, key: str) -> str:
return f"{{{key}}}"
def process_placeholder(text: str, context: Optional[Dict[str, Any]] = None) -> str:
"""处理文本中的占位符替换
Args:
text: 包含占位符的原始文本
context: 用于替换占位符的上下文字典
Returns:
替换后的文本
"""
if not context:
return text
# 处理简单占位符 {key}
safe_context = SafeDict(**context)
text = text.format_map(safe_context)
# 处理条件占位符 {{if condition}}content{{endif}}
def replace_condition(match):
condition = match.group(1).strip()
content = match.group(2).strip()
# 简单条件解析仅支持key存在性检查
if condition.startswith('!'):
key = condition[1:].strip()
return '' if key in context else content
return content if condition in context else ''
text = re.sub(r'\{\{if\s+(.*?)\}\}([\s\S]*?)\{\{endif\}\}', replace_condition, text, flags=re.IGNORECASE)
# 处理循环占位符 {{for item in list}}content{{endfor}}
def replace_loop(match):
items = match.group(1).strip().split(' in ')
if len(items) != 2:
return match.group(0)
item_name, list_name = items
content = match.group(2).strip()
if list_name not in context or not isinstance(context[list_name], list):
return ''
result = []
for item in context[list_name]:
item_context = {item_name: item}
result.append(process_placeholder(content, item_context))
return ''.join(result)
text = re.sub(r'\{\{for\s+(.*?)\}\}([\s\S]*?)\{\{endfor\}\}', replace_loop, text, flags=re.IGNORECASE)
return text

View File

@ -0,0 +1,128 @@
"""
正则表达式处理工具
"""
import re
from collections import deque
from .styles import set_color, set_bg_color
def process_links(text: str, no_process: bool = False) -> str:
"""处理链接标签(HTML和Markdown格式)"""
if no_process:
return text
link_stack = deque()
placeholder_count = 0
placeholders = {}
def replace_link(m):
nonlocal placeholder_count
placeholder_count += 1
if len(m.groups()) == 2 and m.group(2) and not m.group(1).startswith('http'):
# Markdown链接 [text](url)
url = m.group(2).strip()
text = m.group(1)
else:
url = m.group(1)
text = m.group(2)
placeholder = f"__LINK_PLACEHOLDER_{placeholder_count}__"
placeholders[placeholder] = (url if url else "#", text)
link_stack.append(placeholder)
return placeholder
text = re.sub(r'<a\s+href="([^"]+)">(.*?)</a>', replace_link, text, flags=re.DOTALL)
text = re.sub(r'<link\s+href="([^"]+)">(.*?)</link>', replace_link, text, flags=re.DOTALL)
text = re.sub(r'\[(.*?)\]\((.*?)\)', replace_link, text)
for placeholder, (url, text_content) in placeholders.items():
ansi_link = f'\033]8;;{url}\033\\{set_color("\033[4m" + text_content, "#5f93ff")}\033]8;;\033\\'
text = text.replace(placeholder, ansi_link)
return text
def process_markdown_formats(text: str, no_process: bool = False) -> str:
"""处理Markdown格式"""
if no_process:
return text
# Markdown粗体 (**text**)
text = re.sub(r'\*\*(.*?)\*\*', '\033[1m\\g<1>\033[22m', text)
# Markdown斜体 (*text*)
text = re.sub(r'\*(.*?)\*', '\033[3m\\g<1>\033[23m', text)
# Markdown下划线 (__text__)
text = re.sub(r'__(.*?)__', '\033[4m\\g<1>\033[24m', text)
# Markdown删除线 (~~text~~)
text = re.sub(r'~~(.*?)~~', '\033[9m\\g<1>\033[29m', text)
return text
def process_html_styles(text: str, no_process: bool = False) -> str:
"""处理HTML样式标签"""
if no_process:
return text
# HTML斜体 <i></i>
text = re.sub(r'<i>([^<]*?)(</i>|$)',
lambda m: '\033[3m' + m.group(1) + '\033[23m', text, flags=re.DOTALL)
# HTML粗体 <b></b>
text = re.sub(r'<b>([^<]*?)</b>',
lambda m: '\033[1m' + m.group(1) + '\033[22m', text)
# HTML下划线 <u></u>
text = re.sub(r'<u>([^<]*?)</u>',
lambda m: '\033[4m' + m.group(1) + '\033[24m', text)
# HTML删除线 <s></s>
text = re.sub(r'<s>([^<]*?)(</s>|$)',
lambda m: '\033[9m' + m.group(1) + '\033[29m', text, flags=re.DOTALL)
return text
def process_special_tags(text: str, no_process: bool = False) -> str:
"""处理特殊标签(换行、重置、段落)"""
if no_process:
return text
text = re.sub(r'<br>', '\n', text)
text = re.sub(r'<c>', '\033[0m', text)
# 处理段落标签
text = re.sub(r'<p>(.*?)</p>', r'\n\033[0m\\g<1>\033[0m\n', text, flags=re.DOTALL)
text = re.sub(r'<p>(.*?)(</p>|$)', r'\n\033[0m\\g<1>\033[0m\n', text, flags=re.DOTALL)
text = re.sub(r'</p>', '\033[0m\n', text)
return text
def process_color_formatting(text: str, no_process: bool = False) -> str:
"""处理颜色标签"""
if no_process:
return text
color_pattern = r'<#([0-9a-fA-F]{6})>'
close_pattern = r'</>'
parts = re.split(f'({color_pattern}|{close_pattern})', text)
result = []
color_stack = []
for part in parts:
if part and re.fullmatch(color_pattern, part):
color = re.match(color_pattern, part).group(1)
color_stack.append(color)
continue
elif part == '</>':
if color_stack:
color_stack.pop()
continue
elif part:
if color_stack:
current_color = color_stack[-1]
r = int(current_color[0:2], 16)
g = int(current_color[2:4], 16)
b = int(current_color[4:6], 16)
ansi_code = f'\033[38;2;{r};{g};{b}m'
reset_code = '\033[0m'
result.append(f'{ansi_code}{part}{reset_code}')
else:
processed_text = part
processed_text = re.sub(r'<#([0-9a-fA-F]{6})>', lambda m: f'<{set_color(f"#{m.group(1)}")}>', processed_text)
result.append(processed_text)
processed_text = ''.join(result)
processed_text = re.sub(f'{color_pattern}|{close_pattern}', '', processed_text)
processed_text = re.sub(r'[0-9a-fA-F]{6}', '', processed_text)
return processed_text

View File

@ -1,32 +0,0 @@
{
"file_level": "DEBUG",
"file_name": "latest.log",
"file_path": "./logs",
"file_format": "{asctime} {levelname} | {prefix}{message}",
"file_encoding": "utf-8",
"enable_console": true,
"enable_file": true,
"console_color": true,
"console_level": "INFO",
"console_format": "{time} {levelname} | {prefix}{message}",
"console_prefix": "Auto",
"console_encoding": "utf-8",
"asctime_format": "%Y-%m-%d %H:%M:%S",
"time_format": "%H:%M:%S",
"date_format": "%Y-%m-%d",
"weekday_format": "%A",
"level_name": {
"DEBUG": "DEBUG",
"INFO": "INFO",
"WARN": "WARN",
"ERRO": "ERRO",
"CRIT": "CRIT"
},
"level_color": {
"DEBUG": "#c1d5ff",
"INFO": "#c1ffff",
"WARN": "#fff600",
"ERRO": "#ffa000",
"CRIT": "#ff8181"
}
}

View File

@ -8,8 +8,8 @@ from src.logiliteal import Logger
log = Logger()
log.info("测试信息日志")
log.debug("测试调试日志")
log.info("测试信息日志")
log.warn("测试警告日志")
log.error("测试错误日志")
log.critical("测试严重错误日志")