现在位置: 首页 > Skills 教程 > 正文

Skills 日志与调试进阶

入门调试靠 print,进阶调试靠系统性的日志机制。

本篇介绍如何为 Skill 脚本建立结构化日志,以及在复杂执行流程中定位问题的进阶技巧。


用 logging 替代 print

print 适合快速调试,但在正式 Skill 中应改用 Python 标准库的 logging 模块。

logging 的优势是可以控制输出级别——开发时输出 DEBUG 信息,生产时只输出 WARNING 以上。

实例

# 文件路径:scripts/logger_setup.py
import logging
import sys
from datetime import datetime

def get_logger(name: str, level: str = "INFO") -> logging.Logger:
    """
    创建结构化日志记录器

    参数:
        name:  日志器名称,通常用模块名(__name__)
        level: 日志级别,DEBUG / INFO / WARNING / ERROR
    """

    logger = logging.getLogger(name)
    logger.setLevel(getattr(logging, level.upper(), logging.INFO))

    # 避免重复添加 handler
    if logger.handlers:
        return logger

    # 控制台输出:人类可读格式
    console = logging.StreamHandler(sys.stdout)
    console.setFormatter(logging.Formatter(
        "[%(asctime)s] [%(levelname)s] %(name)s - %(message)s",
        datefmt="%H:%M:%S"
    ))
    logger.addHandler(console)

    # 文件输出:写入日志文件(可选)
    log_file = f"/home/claude/skill_{datetime.now().strftime('%Y%m%d')}.log"
    file_handler = logging.FileHandler(log_file, encoding="utf-8")
    file_handler.setFormatter(logging.Formatter(
        "%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s"
    ))
    logger.addHandler(file_handler)

    return logger

# 使用示例
if __name__ == "__main__":
    log = get_logger(__name__, level="DEBUG")

    log.debug("调试信息:文件路径 = /mnt/user-data/uploads/runoob.csv")
    log.info("开始处理文件")
    log.warning("文件大小超过 50MB,处理可能较慢")
    log.error("文件读取失败:FileNotFoundError")
[10:23:01] [DEBUG]   __main__ - 调试信息:文件路径 = /mnt/user-data/uploads/runoob.csv
[10:23:01] [INFO]    __main__ - 开始处理文件
[10:23:01] [WARNING] __main__ - 文件大小超过 50MB,处理可能较慢
[10:23:01] [ERROR]   __main__ - 文件读取失败:FileNotFoundError

执行时间追踪

当 Skill 执行较慢时,需要找出耗时的瓶颈步骤。

实例

# 文件路径:scripts/timer.py
import time
import functools
import logging

log = logging.getLogger(__name__)

def timeit(func):
    """装饰器:自动记录函数执行时间"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        log.info(f"{func.__name__} 耗时 {elapsed:.3f} 秒")
        return result
    return wrapper

# 使用方式:在需要计时的函数上加 @timeit 装饰器
@timeit
def load_csv(file_path: str):
    import pandas as pd
    return pd.read_csv(file_path)

@timeit
def calculate_stats(df):
    return df.describe()

# 也可以用上下文管理器手动计时
class Timer:
    def __init__(self, label: str):
        self.label = label
    def __enter__(self):
        self.start = time.perf_counter()
        return self
    def __exit__(self, *args):
        elapsed = time.perf_counter() - self.start
        log.info(f"{self.label} 耗时 {elapsed:.3f} 秒")

# 使用 Timer 上下文管理器
if __name__ == "__main__":
    with Timer("读取并清洗数据"):
        import pandas as pd
        df = pd.read_csv("/mnt/user-data/uploads/runoob_data.csv")
        df = df.dropna()
[10:23:05] [INFO] load_csv 耗时 0.342 秒
[10:23:05] [INFO] calculate_stats 耗时 0.018 秒
[10:23:05] [INFO] 读取并清洗数据 耗时 0.361 秒

结构化日志:输出 JSON 格式

当日志需要被程序解析(而非人工阅读)时,JSON 格式更合适。

实例

# 文件路径:scripts/json_logger.py
import json
import sys
from datetime import datetime

def log_event(level: str, event: str, **context):
    """
    输出结构化 JSON 日志

    参数:
        level:   日志级别(info / warning / error)
        event:   事件名称
        context: 附加的上下文键值对
    """

    entry = {
        "timestamp": datetime.now().isoformat(),
        "level":     level,
        "event":     event,
        **context
    }
    # 使用 stderr 输出日志,避免与脚本的正常输出混合
    print(json.dumps(entry, ensure_ascii=False), file=sys.stderr)

# 使用示例
log_event("info",    "file_loaded",   file="/mnt/user-data/uploads/runoob.csv", rows=1024)
log_event("warning", "null_detected", column="score", count=12)
log_event("error",   "parse_failed",  reason="编码不是 UTF-8")
{"timestamp": "2026-05-18T10:23:05", "level": "info",    "event": "file_loaded",   "file": "/mnt/user-data/uploads/runoob.csv", "rows": 1024}
{"timestamp": "2026-05-18T10:23:05", "level": "warning", "event": "null_detected", "column": "score", "count": 12}
{"timestamp": "2026-05-18T10:23:05", "level": "error",   "event": "parse_failed",  "reason": "编码不是 UTF-8"}

调试 SKILL.md 的指令执行

当 Claude 没有按照 SKILL.md 的指令执行时,可以通过在 SKILL.md 中插入"检查点"来定位问题。

## 调试检查点(开发模式,发布前删除)

在执行每个步骤前,先以以下格式输出一行状态确认:

`[STEP N] 开始:{步骤名称},输入:{关键参数}`

示例:
`[STEP 1] 开始:读取文件,输入:/mnt/user-data/uploads/runoob.csv`
`[STEP 2] 开始:数据清洗,输入:1024 行`

这样可以在出错时快速定位到哪个步骤失败。

调试检查点是临时的。调试完成后,务必从 SKILL.md 中删除这些指令,否则正式使用时用户会看到多余的调试输出。


常见调试场景速查

场景调试方法
Skill 未触发检查 description、使用复杂提示词测试、运行 run_loop.py 优化
脚本报 ImportError运行 pip install,检查包名与 import 名称是否匹配
文件路径报错先 ls /mnt/user-data/uploads/ 确认文件名
输出乱码在文件读取时显式指定 encoding="utf-8"
步骤执行顺序不对在 SKILL.md 中用编号列表明确步骤顺序
结果为空在脚本关键节点加 print,确认数据在哪一步变空