Skip to content

Skills 日志与调试进阶

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

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


用 logging 替代 print

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

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

实例

bash

# 文件路径: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")
bash
[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 执行较慢时,需要找出耗时的瓶颈步骤。

实例

python

# 文件路径: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()
bash
[10:23:05] [INFO] load_csv 耗时 0.342 秒
[10:23:05] [INFO] calculate_stats 耗时 0.018 秒
[10:23:05] [INFO] 读取并清洗数据 耗时 0.361 秒

结构化日志:输出 JSON 格式

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

实例

python

# 文件路径: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")
json
{"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 中插入"检查点"来定位问题。

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

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

`[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,确认数据在哪一步变空

AI 思考中...

Skills 单元测试

Skills 描述优化技巧

基于 VitePress 构建,部署于 GitHub Pages