Skip to content

Commit 3c5452f

Browse files
committed
fix(logging): 日志写入改用 output_dir 并避免重复初始化
- 日志目录改为 get_output_dir()/logs/...(兼容 WECHAT_TOOL_DATA_DIR) - 清理/关闭旧 handlers,避免重复输出和 Windows 文件锁 - get_logger/get_log_file_path 未初始化时自动 setup_logging - 新增 tests/test_logging_config_data_dir.py
1 parent c7c7925 commit 3c5452f

3 files changed

Lines changed: 114 additions & 10 deletions

File tree

src/wechat_decrypt_tool/api.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@
1010
from starlette.responses import FileResponse
1111
from starlette.staticfiles import StaticFiles
1212

13-
from . import __version__ as APP_VERSION
1413
from .logging_config import setup_logging, get_logger
14+
15+
# 初始化日志系统
16+
setup_logging()
17+
logger = get_logger(__name__)
18+
19+
from . import __version__ as APP_VERSION
1520
from .path_fix import PathFixRoute
1621
from .chat_realtime_autosync import CHAT_REALTIME_AUTOSYNC
1722
from .routers.chat import router as _chat_router
@@ -30,10 +35,6 @@
3035
from .sns_stage_timing import add_sns_stage_timing_headers
3136
from .wcdb_realtime import WCDB_REALTIME, shutdown as _wcdb_shutdown
3237

33-
# 初始化日志系统
34-
setup_logging()
35-
logger = get_logger(__name__)
36-
3738
app = FastAPI(
3839
title="微信数据库解密工具",
3940
description="现代化的微信数据库解密工具,支持微信信息检测和数据库解密功能",

src/wechat_decrypt_tool/logging_config.py

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ def __new__(cls):
5353
return cls._instance
5454

5555
def __init__(self):
56-
if not self._initialized:
57-
self.setup_logging()
58-
WeChatLogger._initialized = True
56+
# Lazy-init in `setup_logging()` / accessors to avoid double-initialization when
57+
# callers instantiate the manager and then call `setup_logging()` again.
58+
pass
5959

6060
def setup_logging(self, log_level: str = "INFO"):
6161
"""设置日志配置"""
@@ -66,7 +66,9 @@ def setup_logging(self, log_level: str = "INFO"):
6666

6767
# 创建日志目录
6868
now = datetime.now()
69-
log_dir = Path("output/logs") / str(now.year) / f"{now.month:02d}" / f"{now.day:02d}"
69+
from .app_paths import get_output_dir
70+
71+
log_dir = get_output_dir() / "logs" / str(now.year) / f"{now.month:02d}" / f"{now.day:02d}"
7072
log_dir.mkdir(parents=True, exist_ok=True)
7173

7274
# 设置日志文件名
@@ -77,6 +79,10 @@ def setup_logging(self, log_level: str = "INFO"):
7779
root_logger = logging.getLogger()
7880
for handler in root_logger.handlers[:]:
7981
root_logger.removeHandler(handler)
82+
try:
83+
handler.close()
84+
except Exception:
85+
pass
8086

8187
# 配置日志格式
8288
# 文件格式(无颜色)
@@ -109,22 +115,48 @@ def setup_logging(self, log_level: str = "INFO"):
109115

110116
# 只为uvicorn日志器添加文件处理器,保持其原有的控制台处理器(带颜色)
111117
uvicorn_logger = logging.getLogger("uvicorn")
118+
for handler in uvicorn_logger.handlers[:]:
119+
if isinstance(handler, logging.FileHandler):
120+
uvicorn_logger.removeHandler(handler)
121+
try:
122+
handler.close()
123+
except Exception:
124+
pass
112125
uvicorn_logger.addHandler(file_handler)
113126
uvicorn_logger.setLevel(level)
114127

115128
# 只为uvicorn.access日志器添加文件处理器
116129
uvicorn_access_logger = logging.getLogger("uvicorn.access")
130+
for handler in uvicorn_access_logger.handlers[:]:
131+
if isinstance(handler, logging.FileHandler):
132+
uvicorn_access_logger.removeHandler(handler)
133+
try:
134+
handler.close()
135+
except Exception:
136+
pass
117137
uvicorn_access_logger.addHandler(file_handler)
118138
uvicorn_access_logger.setLevel(level)
119139

120140
# 只为uvicorn.error日志器添加文件处理器
121141
uvicorn_error_logger = logging.getLogger("uvicorn.error")
142+
for handler in uvicorn_error_logger.handlers[:]:
143+
if isinstance(handler, logging.FileHandler):
144+
uvicorn_error_logger.removeHandler(handler)
145+
try:
146+
handler.close()
147+
except Exception:
148+
pass
122149
uvicorn_error_logger.addHandler(file_handler)
123150
uvicorn_error_logger.setLevel(level)
124151

125152
# 配置FastAPI日志器
126153
fastapi_logger = logging.getLogger("fastapi")
127-
fastapi_logger.handlers = []
154+
for handler in fastapi_logger.handlers[:]:
155+
fastapi_logger.removeHandler(handler)
156+
try:
157+
handler.close()
158+
except Exception:
159+
pass
128160
fastapi_logger.addHandler(file_handler)
129161
fastapi_logger.addHandler(console_handler)
130162
fastapi_logger.setLevel(level)
@@ -136,6 +168,8 @@ def setup_logging(self, log_level: str = "INFO"):
136168
logger.info(f"日志文件: {self.log_file}")
137169
logger.info(f"日志级别: {logging.getLevelName(level)}")
138170
logger.info("=" * 60)
171+
172+
WeChatLogger._initialized = True
139173

140174
return self.log_file
141175

@@ -145,6 +179,8 @@ def get_logger(self, name: str) -> logging.Logger:
145179

146180
def get_log_file_path(self) -> Path:
147181
"""获取当前日志文件路径"""
182+
if not hasattr(self, "log_file"):
183+
self.setup_logging()
148184
return self.log_file
149185

150186

@@ -157,10 +193,14 @@ def setup_logging(log_level: str = "INFO") -> Path:
157193
def get_logger(name: str) -> logging.Logger:
158194
"""获取日志器的便捷函数"""
159195
logger_manager = WeChatLogger()
196+
if not WeChatLogger._initialized:
197+
logger_manager.setup_logging()
160198
return logger_manager.get_logger(name)
161199

162200

163201
def get_log_file_path() -> Path:
164202
"""获取当前日志文件路径的便捷函数"""
165203
logger_manager = WeChatLogger()
204+
if not WeChatLogger._initialized:
205+
logger_manager.setup_logging()
166206
return logger_manager.get_log_file_path()
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import os
2+
import sys
3+
import unittest
4+
import importlib
5+
from pathlib import Path
6+
from tempfile import TemporaryDirectory
7+
8+
9+
ROOT = Path(__file__).resolve().parents[1]
10+
sys.path.insert(0, str(ROOT / "src"))
11+
12+
13+
def _close_logging_handlers() -> None:
14+
# Close handlers to avoid Windows temp dir cleanup failures (FileHandler holds a lock).
15+
import logging
16+
17+
for logger_name in ("", "uvicorn", "uvicorn.access", "uvicorn.error", "fastapi"):
18+
lg = logging.getLogger(logger_name)
19+
for h in lg.handlers[:]:
20+
try:
21+
h.close()
22+
except Exception:
23+
pass
24+
try:
25+
lg.removeHandler(h)
26+
except Exception:
27+
pass
28+
29+
30+
class TestLoggingConfigDataDir(unittest.TestCase):
31+
def setUp(self):
32+
self._prev_data_dir = os.environ.get("WECHAT_TOOL_DATA_DIR")
33+
self._td = TemporaryDirectory()
34+
os.environ["WECHAT_TOOL_DATA_DIR"] = self._td.name
35+
36+
import wechat_decrypt_tool.app_paths as app_paths
37+
import wechat_decrypt_tool.logging_config as logging_config
38+
39+
importlib.reload(app_paths)
40+
importlib.reload(logging_config)
41+
42+
self.logging_config = logging_config
43+
44+
def tearDown(self):
45+
_close_logging_handlers()
46+
47+
if self._prev_data_dir is None:
48+
os.environ.pop("WECHAT_TOOL_DATA_DIR", None)
49+
else:
50+
os.environ["WECHAT_TOOL_DATA_DIR"] = self._prev_data_dir
51+
self._td.cleanup()
52+
53+
def test_setup_logging_uses_wechat_tool_data_dir(self):
54+
log_file = self.logging_config.setup_logging()
55+
56+
base = Path(self._td.name) / "output" / "logs"
57+
self.assertTrue(log_file.is_relative_to(base))
58+
self.assertTrue(log_file.exists())
59+
60+
61+
if __name__ == "__main__":
62+
unittest.main()
63+

0 commit comments

Comments
 (0)