Skip to content

Commit 293c689

Browse files
committed
[重构与功能增强]: 统一日志与颜色输出工具,新增哈希计算模块,优化依赖管理与项目结构
- 新增哈希计算模块(hash): 支持多种哈希算法(MD5/SHA系列/BLAKE2/BLAKE3),提供文件/文本/标准输入多种计算方式 - 重构日志系统(cli_logger): 增强为可复用包,支持多进程安全日志、自动脚本名前缀、环境变量级别控制,并统一替换各模块的自定义日志实现 - 新增工具包(utils): 提供跨平台终端颜色输出工具(Colors),统一替换各模块的颜色实现 - 优化依赖管理: 改用pyproject.toml管理依赖,新增sync_req.py同步工具自动生成requirements.txt - 增强图标生成工具(icon-maker): 优化输出信息显示 - 更新README: 补充虚拟环境使用说明和新增模块文档,优化项目结构说明
1 parent e87b623 commit 293c689

File tree

19 files changed

+376
-246
lines changed

19 files changed

+376
-246
lines changed

README.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,53 @@
77

88
## 📦 工具列表
99

10-
| 模块 | 描述 | 文件 |
10+
| 模块 | 描述 | 主要文件 |
1111
|---|---|---|
1212
| **cli_logger** | loguru 日志配置示例,控制台 + 文件双通道输出 | [`cli_logger.py`](cli_logger/cli_logger.py) |
1313
| **dirwatch** | 实时监控文件夹变化(增/删/改/重命名) | [`dirwatch.py`](dirwatch/dirwatch.py) |
14+
| **hash** | 计算文件或文本的哈希值(MD5/SHA-1/SHA-2/SHA-3/BLAKE2/BLAKE3) | [`hash.py`](hash/hash.py) |
1415
| **icon-maker** | 一键生成 macOS / Windows 应用图标(`.icns` / `.ico`| [`make_icns.py`](icon-maker/make_icns.py) / [`make_ico.py`](icon-maker/make_ico.py) |
1516
| **m3u8_download** | m3u8 下载器,自动合并 ts 为单个视频 | [`m3u8_dl.py`](m3u8_download/m3u8_dl.py) |
1617
| **procmon** | 按进程名实时监控 CPU/内存/线程/句柄 | [`procmon.py`](procmon/procmon.py) |
1718
| **resolve** | 域名解析工具,快速获取 IP、端口、协议信息 | [`resolve.py`](resolve/resolve.py) |
1819
| **syncthing** | Syncthing API 封装,监控文件夹与设备状态 | [`syncthing_monitor.py`](syncthing/syncthing_monitor.py) |
1920
| **tree** | 可视化目录树生成工具 | [`tree.py`](tree/tree.py) |
21+
| **utils** | 通用工具库(颜色输出等) | [`colors.py`](utils/colors.py) |
2022
| **webdav** | 轻量级 WebDAV 客户端,支持上传/下载/删除/移动 | [`webdav.py`](webdav/webdav.py) |
23+
| **sync_req** | 依赖同步工具,从 pyproject.toml 生成 requirements.txt | [`sync_req.py`](sync_req.py) |
2124

2225
## 🚀 快速开始
2326

2427
### 安装依赖
2528

29+
推荐使用虚拟环境:
30+
31+
```bash
32+
# 创建虚拟环境
33+
python -m venv venv
34+
35+
# 激活虚拟环境 (Linux/macOS)
36+
source venv/bin/activate
37+
38+
# 或使用提供的脚本激活
39+
source activate_venv.sh
40+
41+
# 安装依赖
42+
pip install -e . -i https://pypi.tuna.tsinghua.edu.cn/simple
43+
```
44+
45+
### 同步依赖文件
46+
47+
项目提供了 `sync_req.py` 工具,用于从 `pyproject.toml` 生成 `requirements.txt`
48+
2649
```bash
27-
pip install -r requirements.txt
50+
# 生成 requirements.txt
51+
python sync_req.py
52+
53+
# 使用生成的 requirements.txt 安装依赖
54+
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
2855
```
2956

30-
## 📖 使用说明
57+
### 使用说明
3158

3259
👉 直接 `cd` 进对应目录,`python xxx.py -h` 即可开玩!

cli_logger/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# cli_logger/__init__.py
2+
from .cli_logger import logger, log_init # 按需再导别的函数

cli_logger/cli_logger.py

Lines changed: 83 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,76 @@
11
# -*- coding: utf-8 -*-
22
"""
33
cli_logger.py
4-
~~~~~~~~~~~~~~~~
5-
一个开箱即用的 loguru 日志配置示例,支持:
4+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5+
开箱即用的 loguru 日志配置包,支持:
66
77
1. 彩色控制台输出(自动适配 Windows)。
8-
2. 自动按天分割、保留 7 天、zip 压缩的日志文件。
9-
3. 命令行参数控制日志级别,便于调试。
10-
11-
用法:
12-
# 查看帮助
13-
python cli_logger.py -h
14-
15-
# 默认 INFO 级别
16-
python cli_logger.py
17-
18-
# 调试级别
19-
python cli_logger.py -l DEBUG
8+
2. 按天分割、保留 7 天、zip 压缩、多进程安全。
9+
3. 日志文件名 = 调用脚本名(或自定义前缀)。
10+
4. 通过环境变量 LOG_LEVEL / LOG_FILE_LEVEL 动态调整级别。
11+
5. 既可当脚本直接跑,也可被其它模块 import,零侵入。
12+
13+
用法
14+
----
15+
# 1. 当作包使用(推荐)
16+
from cli_logger import log_init, logger
17+
logger.info("hello world")
18+
19+
# 2. 当作脚本运行
20+
python cli_logger.py -l DEBUG
21+
22+
# 3. 自定义日志文件名前缀
23+
from cli_logger import log_init
24+
log_init(file_prefix="web")
2025
"""
2126
from __future__ import annotations
2227

28+
import argparse
29+
import inspect
30+
import os
2331
import sys
24-
from pathlib import Path
32+
import socket
33+
import pathlib
2534

26-
import loguru
2735
from loguru import logger
2836

29-
# 如果 loguru 版本太旧,建议 pip install -U loguru
30-
assert hasattr(logger, "add"), "请升级 loguru: pip install -U loguru"
37+
38+
# ---------- 默认路径/级别 ----------
39+
_DEFAULT_LOG_DIR = pathlib.Path(__file__).parent.parent / "logs"
40+
_CONSOLE_LVL = os.getenv("LOG_LEVEL", "INFO").upper()
41+
_FILE_LVL = os.getenv("LOG_FILE_LEVEL", "DEBUG").upper()
3142

3243

3344
def log_init(
3445
*,
35-
console_level: str = "INFO",
36-
log_dir: str = "logs",
37-
file_level: str = "DEBUG",
46+
console_level: str = _CONSOLE_LVL,
47+
log_dir: str | pathlib.Path = _DEFAULT_LOG_DIR,
48+
file_level: str = _FILE_LVL,
49+
file_prefix: str | None = None,
3850
) -> None:
3951
"""
4052
初始化 loguru 日志配置。
4153
42-
参数:
43-
console_level: 控制台日志级别,默认 INFO
44-
log_dir: 日志文件存放目录,默认当前目录下的 logs/
45-
file_level: 日志文件写入级别,默认 DEBUG(最详细)
54+
参数
55+
----
56+
console_level : str
57+
控制台日志级别,默认读取环境变量 LOG_LEVEL,否则 INFO
58+
log_dir : str | Path
59+
日志文件存放目录,默认仓库根目录下的 logs/
60+
file_level : str
61+
日志文件写入级别,默认读取环境变量 LOG_FILE_LEVEL,否则 DEBUG
62+
file_prefix : str | None
63+
日志文件名前缀,默认取调用脚本名(不含扩展名)
4664
"""
47-
# 1. 移除 loguru 默认的 stderr 处理器,避免重复
65+
# 1. 移除默认 handler
4866
logger.remove()
4967

50-
# 2. 控制台:带颜色,人类友好
68+
# 2. 控制台
5169
console_fmt = (
52-
"<green>{time:YYYY-MM-DD HH:mm:ss}</green> | "
70+
"<green>{time:HH:mm:ss.SSS}</green> | "
5371
"<level>{level: <8}</level> | "
54-
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
72+
"<cyan>{thread: >5}</cyan> | " # <-- 新增
73+
"<magenta>{name}</magenta>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
5574
"<level>{message}</level>"
5675
)
5776
logger.add(
@@ -60,48 +79,60 @@ def log_init(
6079
format=console_fmt,
6180
colorize=True,
6281
backtrace=False,
63-
diagnose=False, # 生产环境可设为 False,避免泄露敏感信息
82+
diagnose=False,
6483
)
6584

6685
# 3. 日志文件:按天分割、保留 7 天、zip 压缩、多进程安全
67-
log_path = Path(log_dir)
68-
log_path.mkdir(exist_ok=True)
86+
log_path = pathlib.Path(log_dir).expanduser().resolve()
87+
log_path.mkdir(parents=True, exist_ok=True)
88+
89+
# 自动获取调用者脚本名
90+
if file_prefix is None:
91+
# 0=当前函数,1=显式调用者(业务脚本)
92+
caller_file = pathlib.Path(inspect.stack()[1].filename)
93+
file_prefix = caller_file.stem
94+
95+
prefix = file_prefix
96+
hostname = socket.gethostname()
97+
pid = os.getpid()
98+
6999
file_fmt = (
70-
"{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | "
100+
"{time:YYYY-MM-DD HH:mm:ss.SSS} | "
101+
"{level: <8} | "
102+
"{thread: >5} | "
71103
"{name}:{function}:{line} - {message}"
72104
)
73105
logger.add(
74-
log_path / "app_{time:YYYYMMDD}.log",
106+
log_path / f"{prefix}_{hostname}_{pid}_{{time:YYYYMMDD_HHmmss}}.log",
75107
level=file_level,
76108
format=file_fmt,
77-
rotation="00:00", # 每天零点新建文件
78-
retention="7 days", # 清理旧日志
79-
compression="zip", # 压缩历史日志
80-
enqueue=True, # 多进程/线程安全
81-
backtrace=True, # 记录完整堆栈
82-
diagnose=True, # 记录变量值,方便定位
109+
rotation="00:00",
110+
retention="7 days",
111+
compression="zip",
112+
enqueue=True,
113+
backtrace=True,
114+
diagnose=True,
83115
)
84116

85117
logger.info(
86-
"日志初始化完成 | 控制台级别={} | 文件级别={}", console_level, file_level
118+
f"日志初始化完成 | 控制台级别={console_level} | "
119+
f"文件级别={file_level} | 日志目录={log_path} | 前缀={prefix}"
87120
)
88121

89122

90123
def log_test() -> None:
91124
"""打印各等级日志,用于快速验证配置是否生效。"""
92125
text = "hello world"
93-
logger.debug("debug: {}", text)
94-
logger.info("info: {}", text)
95-
logger.success("success: {}", text)
96-
logger.warning("warning: {}", text)
97-
logger.error("error: {}", text)
98-
logger.critical("critical: {}", text)
99-
126+
logger.debug(f"debug: {text}")
127+
logger.info(f"info: {text}")
128+
logger.success(f"success: {text}")
129+
logger.warning(f"warning: {text}")
130+
logger.error(f"error: {text}")
131+
logger.critical(f"critical: {text}")
100132

101-
def build_cli() -> argparse.Namespace:
102-
"""构造简易命令行参数解析器。"""
103-
import argparse
104133

134+
# ---------- 脚本入口 ----------
135+
def _build_cli() -> argparse.Namespace:
105136
parser = argparse.ArgumentParser(
106137
description="loguru 日志配置示例,支持控制台与文件双通道输出。"
107138
)
@@ -116,8 +147,7 @@ def build_cli() -> argparse.Namespace:
116147

117148

118149
def main() -> None:
119-
"""CLI 入口函数。"""
120-
args = build_cli()
150+
args = _build_cli()
121151
log_init(console_level=args.level)
122152
log_test()
123153

dirwatch/dirwatch.py

Lines changed: 14 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -24,44 +24,18 @@
2424
from __future__ import annotations
2525

2626
import argparse
27-
import hashlib
28-
import logging
2927
import os
3028
import sys
3129
import time
3230
from pathlib import Path
3331
from queue import Queue
3432
from typing import Dict, List, Optional, Tuple
33+
from cli_logger import log_init, logger
3534

3635
from watchdog.events import FileSystemEventHandler
3736
from watchdog.observers import Observer
3837

3938

40-
# ---------- 彩色日志 ----------
41-
class ColoredLog:
42-
BLUE = "\033[94m"
43-
GREEN = "\033[92m"
44-
YELLOW = "\033[93m"
45-
RED = "\033[91m"
46-
RESET = "\033[0m"
47-
48-
@staticmethod
49-
def info(msg: str) -> None:
50-
print(f"{ColoredLog.BLUE}[INFO ]{ColoredLog.RESET} {msg}")
51-
52-
@staticmethod
53-
def succ(msg: str) -> None:
54-
print(f"{ColoredLog.GREEN}[SUCC ]{ColoredLog.RESET} {msg}")
55-
56-
@staticmethod
57-
def warn(msg: str) -> None:
58-
print(f"{ColoredLog.YELLOW}[WARN ]{ColoredLog.RESET} {msg}", file=sys.stderr)
59-
60-
@staticmethod
61-
def error(msg: str) -> None:
62-
print(f"{ColoredLog.RED}[ERROR]{ColoredLog.RESET} {msg}", file=sys.stderr)
63-
64-
6539
# ---------- 事件处理器 ----------
6640
class _EventHandler(FileSystemEventHandler):
6741
"""把 watchdog 事件转成简易元组推入队列"""
@@ -71,23 +45,23 @@ def __init__(self, queue: Queue) -> None:
7145

7246
def on_created(self, event) -> None:
7347
typ = "dir" if event.is_directory else "file"
74-
ColoredLog.info(f"{typ} created: {event.src_path}")
48+
logger.info(f"{typ} created: {event.src_path}")
7549
self.queue.put((event.src_path, None, "created"))
7650

7751
def on_deleted(self, event) -> None:
7852
typ = "dir" if event.is_directory else "file"
79-
ColoredLog.warn(f"{typ} deleted: {event.src_path}")
53+
logger.warning(f"{typ} deleted: {event.src_path}")
8054
self.queue.put((event.src_path, None, "deleted"))
8155

8256
def on_modified(self, event) -> None:
8357
if event.is_directory:
8458
return
85-
ColoredLog.info(f"file modified: {event.src_path}")
59+
logger.info(f"file modified: {event.src_path}")
8660
self.queue.put((event.src_path, None, "modified"))
8761

8862
def on_moved(self, event) -> None:
8963
typ = "dir" if event.is_directory else "file"
90-
ColoredLog.info(f"{typ} moved: {event.src_path} -> {event.dest_path}")
64+
logger.info(f"{typ} moved: {event.src_path} -> {event.dest_path}")
9165
self.queue.put((event.src_path, event.dest_path, "moved"))
9266

9367

@@ -112,13 +86,13 @@ def start(self) -> None:
11286
handler = _EventHandler(self._queue)
11387
self._observer.schedule(handler, str(self.path), recursive=True)
11488
self._observer.start()
115-
ColoredLog.succ(f"开始监控 {self.path} … 按 Ctrl+C 退出")
89+
logger.success(f"开始监控 {self.path} … 按 Ctrl+C 退出")
11690

11791
def stop(self) -> None:
11892
"""优雅停止"""
11993
self._observer.stop()
12094
self._observer.join()
121-
ColoredLog.info("监控已停止")
95+
logger.info("监控已停止")
12296

12397
def get_changes(self) -> List[Tuple[str, Optional[str], str]]:
12498
"""非阻塞获取当前事件队列"""
@@ -132,7 +106,7 @@ def get_changes(self) -> List[Tuple[str, Optional[str], str]]:
132106

133107
# --- 内部辅助 ---
134108
def _take_snapshot(self) -> None:
135-
ColoredLog.info("正在生成初始快照 …")
109+
logger.info("正在生成初始快照 …")
136110
for root, _, files in os.walk(self.path):
137111
for file in files:
138112
fp = Path(root) / file
@@ -141,8 +115,8 @@ def _take_snapshot(self) -> None:
141115
# 用 size+mtime 当唯一标识
142116
self._snapshot[str(fp)] = f"{st.st_size}-{st.st_mtime}"
143117
except Exception as e:
144-
ColoredLog.warn(f"跳过 {fp}: {e}")
145-
ColoredLog.succ(f"快照完成,共 {len(self._snapshot)} 个文件")
118+
logger.warning(f"跳过 {fp}: {e}")
119+
logger.success(f"快照完成,共 {len(self._snapshot)} 个文件")
146120

147121

148122
# ---------- CLI ----------
@@ -164,9 +138,9 @@ def main() -> None:
164138
time.sleep(2)
165139
changes = watch.get_changes()
166140
if changes:
167-
ColoredLog.succ(f"最近变动: {changes}")
141+
logger.success(f"最近变动: {changes}")
168142
except KeyboardInterrupt:
169-
ColoredLog.info("用户中断")
143+
logger.info("用户中断")
170144
finally:
171145
watch.stop()
172146

@@ -197,6 +171,8 @@ def test_event(self) -> None:
197171

198172

199173
if __name__ == "__main__":
174+
log_init()
175+
200176
# 若命令行含 -t 则跑单测,否则跑 CLI
201177
if "-t" in sys.argv:
202178
sys.argv.remove("-t")

0 commit comments

Comments
 (0)