Skip to content

Commit 860d5ac

Browse files
committed
add debug log
1 parent c3859b6 commit 860d5ac

25 files changed

Lines changed: 656 additions & 60 deletions

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ _✨一站式鼠标指针转换与管理工具_
5959
- [从系统中卸载 ImageMagick](#从系统中卸载-imagemagick)
6060
- [更新 Ani2xcur CLI](#更新-ani2xcur-cli)
6161
- [查看 Ani2xcur CLI 使用的环境变量](#查看-ani2xcur-cli-使用的环境变量)
62+
- [调试日志](#调试日志)
6263
- [查看 Ani2xcur CLI 版本信息](#查看-ani2xcur-cli-版本信息)
6364
- [使用的项目](#使用的项目)
6465
- [许可证](#许可证)
@@ -253,6 +254,22 @@ ani2xcur env
253254
```
254255

255256

257+
## 调试日志
258+
临时排查转换、安装或桌面刷新问题时,可以使用全局 `--debug` 选项输出关键链路日志:
259+
260+
```bash
261+
ani2xcur --debug cursor set size 32 > log.txt 2>&1
262+
```
263+
264+
也可以继续使用环境变量开启 debug 日志:
265+
266+
```bash
267+
ANI2XCUR_LOGGER_LEVEL=10 ani2xcur cursor set theme Blue > log.txt 2>&1
268+
```
269+
270+
调试日志会记录命令、会话类型、Xcursor 相关环境变量和转换摘要,不会输出完整环境变量或光标文件二进制内容。
271+
272+
256273
## 查看 Ani2xcur CLI 版本信息
257274
```bash
258275
ani2xcur version

ani2xcur/cli/app.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
"""入口文件"""
22

3+
import logging
34
import sys
45
import traceback
6+
from typing import Annotated
57

68
import typer
79
from click.exceptions import Abort, ClickException, Exit
@@ -44,13 +46,34 @@
4446
)
4547

4648

49+
def _enable_debug_logging() -> None:
50+
"""启用调试日志输出。"""
51+
logging.getLogger(LOGGER_NAME).setLevel(logging.DEBUG)
52+
logger.debug("已启用 debug 日志")
53+
54+
4755
def get_app() -> typer.Typer:
4856
"""获取 Ani2xcur 命令行应用
4957
5058
Returns:
5159
typer.Typer: Ani2xcur 命令行应用
5260
"""
5361
app = typer_factory("鼠标指针转换、管理和 ImageMagick 辅助管理的命令行工具")
62+
63+
@app.callback()
64+
def _main_callback(
65+
debug: Annotated[
66+
bool,
67+
typer.Option(
68+
"--debug",
69+
help="输出调试日志, 便于排查转换、安装和桌面刷新问题",
70+
),
71+
] = False,
72+
) -> None:
73+
"""处理全局命令行选项。"""
74+
if debug:
75+
_enable_debug_logging()
76+
5477
app.command(help="显示 Ani2xcur 和其他组件的当前版本", name="version")(version)
5578
app.command(help="更新 Ani2xcur", name="update")(update)
5679
app.command(help="列出 Ani2xcur 所使用的环境变量", name="env")(env)

ani2xcur/cmd.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@
5959
color=LOGGER_COLOR,
6060
)
6161

62+
DEBUG_ENV_KEYS = (
63+
"DISPLAY",
64+
"WAYLAND_DISPLAY",
65+
"XDG_SESSION_TYPE",
66+
"XCURSOR_THEME",
67+
"XCURSOR_SIZE",
68+
)
69+
"""命令调试日志中允许展示的环境变量键。"""
70+
6271

6372
def preprocess_command(
6473
command: list[str] | str,
@@ -89,6 +98,40 @@ def preprocess_command(
8998
return command
9099

91100

101+
def _debug_env_summary(
102+
env: dict[str, str],
103+
) -> dict[str, str]:
104+
"""提取命令调试日志中允许展示的环境变量。
105+
106+
Args:
107+
env (dict[str, str]): 进程环境变量。
108+
Returns:
109+
dict[str, str]: 允许写入日志的环境变量摘要。
110+
"""
111+
return {key: env[key] for key in DEBUG_ENV_KEYS if key in env}
112+
113+
114+
def _debug_text_summary(
115+
text: str | None,
116+
limit: int = 500,
117+
) -> str | None:
118+
"""生成命令输出的短摘要。
119+
120+
Args:
121+
text (str | None): 命令输出内容。
122+
limit (int): 最大保留长度。
123+
Returns:
124+
str | None: 输出摘要。
125+
"""
126+
if text is None:
127+
return None
128+
129+
text = text.strip()
130+
if len(text) <= limit:
131+
return text
132+
return f"{text[:limit]}...<已截断 {len(text) - limit} 个字符>"
133+
134+
92135
def run_cmd(
93136
command: str | list[str],
94137
custom_env: dict[str, str] | None = None,
@@ -132,9 +175,23 @@ def run_cmd(
132175
if not live:
133176
kwargs["stdout"] = kwargs["stderr"] = subprocess.PIPE
134177

135-
logger.debug("执行命令的参数: %s", kwargs)
178+
logger.debug(
179+
"执行命令: command=%s, cwd=%s, shell=%s, live=%s, check=%s, env=%s",
180+
command_to_exec,
181+
cwd,
182+
shell,
183+
live,
184+
check,
185+
_debug_env_summary(custom_env),
186+
)
136187

137188
result: subprocess.CompletedProcess[str] = subprocess.run(**kwargs) # pylint: disable=subprocess-run-check
189+
logger.debug(
190+
"命令执行完成: returncode=%s, stdout=%r, stderr=%r",
191+
result.returncode,
192+
_debug_text_summary(result.stdout),
193+
_debug_text_summary(result.stderr),
194+
)
138195

139196
if check and result.returncode != 0:
140197
errors = [

ani2xcur/cursor_conversion/convert.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ def win_cursor_to_x11(
109109

110110
# 转换鼠标指针文件
111111
logger.debug("要进行转换的鼠标指针列表: %s", win2x_path_list)
112+
logger.debug(
113+
"Windows -> Linux 转换计划: convert=%s, complete=%s, links=%s",
114+
len(win2x_path_list),
115+
len(completed_cursor_list),
116+
len(link_file_list),
117+
)
112118
for name, src, dst in tqdm(win2x_path_list, desc="转换鼠标指针文件"):
113119
win2x_args["input_file"] = src
114120
win2x_args["output_path"] = cursors_dir
@@ -206,18 +212,43 @@ def _normalize_xcursor_theme_files(
206212
xcursor_sizes (list[int] | None): 目标名义尺寸列表。
207213
"""
208214
target_sizes = DEFAULT_XCURSOR_SIZES if xcursor_sizes is None else xcursor_sizes
215+
normalized_count = 0
216+
skipped_count = 0
217+
logger.debug("开始补齐主题 Xcursor 尺寸: cursors_dir='%s', target_sizes=%s", cursors_dir, target_sizes)
209218
for cursor_file in cursors_dir.iterdir():
210-
if cursor_file.is_symlink() or not cursor_file.is_file():
219+
if cursor_file.is_symlink():
220+
skipped_count += 1
221+
logger.debug("跳过 Xcursor 尺寸补齐软链接: '%s'", cursor_file)
222+
continue
223+
if not cursor_file.is_file():
224+
skipped_count += 1
225+
logger.debug("跳过 Xcursor 尺寸补齐非文件路径: '%s'", cursor_file)
211226
continue
212227

213228
try:
214229
frames = parse_blob(cursor_file.read_bytes())
230+
source_sizes = sorted({cursor.nominal for frame in frames for cursor in frame.images})
215231
normalize_xcursor_sizes(frames, target_sizes)
216232
except ValueError as e:
233+
skipped_count += 1
217234
logger.debug("跳过无法补齐尺寸的光标文件: '%s', 原因: %s", cursor_file, e)
218235
continue
219236

220237
cursor_file.write_bytes(to_xcursor(frames))
238+
normalized_count += 1
239+
target_summary = sorted({cursor.nominal for frame in frames for cursor in frame.images})
240+
logger.debug(
241+
"补齐 Xcursor 文件尺寸完成: '%s', source_sizes=%s, target_sizes=%s",
242+
cursor_file,
243+
source_sizes,
244+
target_summary,
245+
)
246+
logger.debug(
247+
"主题 Xcursor 尺寸补齐完成: cursors_dir='%s', normalized=%s, skipped=%s",
248+
cursors_dir,
249+
normalized_count,
250+
skipped_count,
251+
)
221252

222253

223254
def x11_cursor_to_win(
@@ -262,6 +293,7 @@ def x11_cursor_to_win(
262293

263294
# 转换鼠标指针文件
264295
logger.debug("要进行转换的鼠标指针列表: %s", x2win_path_list)
296+
logger.debug("Linux -> Windows 转换计划: convert=%s", len(x2win_path_list))
265297
for name, src, dst in tqdm(x2win_path_list, desc="转换鼠标指针文件"):
266298
x2win_args["input_file"] = src
267299
x2win_args["output_path"] = cursors_dir

ani2xcur/cursor_conversion/native_cursor/parsers.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@
99

1010
from PIL import Image
1111

12+
from ani2xcur.config import LOGGER_COLOR, LOGGER_LEVEL, LOGGER_NAME
1213
from ani2xcur.cursor_conversion.native_cursor.models import CursorFrame, CursorImage
14+
from ani2xcur.logger import get_logger
15+
16+
logger = get_logger(
17+
name=LOGGER_NAME,
18+
level=LOGGER_LEVEL,
19+
color=LOGGER_COLOR,
20+
)
1321

1422
CUR_MAGIC = b"\x00\x00\x02\x00"
1523
PNG_MAGIC = b"\x89PNG\r\n\x1a\n"
@@ -41,10 +49,13 @@ def parse_blob(blob: bytes) -> list[CursorFrame]:
4149
ValueError: 光标文件格式不受支持或文件内容损坏时抛出。
4250
"""
4351
if blob.startswith(CUR_MAGIC):
52+
logger.debug("识别到 Windows CUR 光标文件")
4453
return parse_cur(blob)
4554
if _is_ani(blob):
55+
logger.debug("识别到 Windows ANI 光标文件")
4656
return parse_ani(blob)
4757
if blob.startswith(XCURSOR_MAGIC):
58+
logger.debug("识别到 Xcursor 光标文件")
4859
return parse_xcursor(blob)
4960
raise ValueError("Unsupported cursor file format")
5061

@@ -67,6 +78,7 @@ def parse_cur(blob: bytes) -> list[CursorFrame]:
6778
raise ValueError("Not a CUR file")
6879
if image_count <= 0:
6980
raise ValueError("CUR file does not contain images")
81+
logger.debug("解析 CUR 文件: image_count=%s", image_count)
7082

7183
entries_start = ICON_DIR.size
7284
entries_end = entries_start + image_count * ICON_DIR_ENTRY.size
@@ -94,6 +106,7 @@ def parse_cur(blob: bytes) -> list[CursorFrame]:
94106
)
95107
)
96108

109+
logger.debug("CUR 文件解析完成: sizes=%s", [image.image.size for image in images])
97110
return [CursorFrame(images=images)]
98111

99112

@@ -147,6 +160,8 @@ def parse_ani(blob: bytes) -> list[CursorFrame]:
147160

148161
frame_count = frame_count or len(icon_frames)
149162
step_count = step_count or frame_count
163+
has_order_chunk = order is not None
164+
has_delay_chunk = delays is not None
150165
order = list(range(frame_count)) if order is None else order
151166
delays = [display_rate for _ in range(step_count)] if delays is None else delays
152167

@@ -155,6 +170,14 @@ def parse_ani(blob: bytes) -> list[CursorFrame]:
155170
if len(delays) != step_count:
156171
raise ValueError(f"Wrong animation rate size: {len(delays)}, expected {step_count}")
157172

173+
logger.debug(
174+
"解析 ANI 文件: frame_count=%s, step_count=%s, icon_frames=%s, has_seq=%s, has_rate=%s",
175+
frame_count,
176+
step_count,
177+
len(icon_frames),
178+
has_order_chunk,
179+
has_delay_chunk,
180+
)
158181
sequence: list[CursorFrame] = []
159182
for frame_index, delay in zip(order, delays):
160183
if frame_index >= len(icon_frames):
@@ -163,6 +186,7 @@ def parse_ani(blob: bytes) -> list[CursorFrame]:
163186
frame.delay = delay / 60
164187
sequence.append(frame)
165188

189+
logger.debug("ANI 文件解析完成: sequence_frames=%s", len(sequence))
166190
return sequence
167191

168192

@@ -194,6 +218,7 @@ def parse_xcursor(blob: bytes) -> list[CursorFrame]:
194218
for _ in range(toc_size):
195219
chunks.append(XCURSOR_TOC_CHUNK.unpack_from(blob, offset))
196220
offset += XCURSOR_TOC_CHUNK.size
221+
logger.debug("解析 Xcursor 文件: toc_size=%s", toc_size)
197222

198223
images_by_size: dict[int, list[tuple[CursorImage, float]]] = defaultdict(list)
199224
for chunk_type, chunk_subtype, position in chunks:
@@ -224,6 +249,7 @@ def parse_xcursor(blob: bytes) -> list[CursorFrame]:
224249

225250
if not images_by_size:
226251
raise ValueError("Xcursor file does not contain images")
252+
logger.debug("Xcursor 文件尺寸集合: %s", sorted(images_by_size))
227253

228254
frame_counts = {len(images) for images in images_by_size.values()}
229255
if len(frame_counts) != 1:
@@ -238,6 +264,7 @@ def parse_xcursor(blob: bytes) -> list[CursorFrame]:
238264
raise ValueError("Xcursor animations must use the same delay for every size in a frame")
239265
frames.append(CursorFrame(images=images, delay=frame_items[0][1]))
240266

267+
logger.debug("Xcursor 文件解析完成: frame_count=%s", len(frames))
241268
return frames
242269

243270

0 commit comments

Comments
 (0)