LabHub
学习 学习路径 课程

调试实战

traceback 要从下往上读

在 LabHub 中继续学习

一句话总结

在Trace Bag中,最下面的行告诉了原因,而上面的一堆则告诉了它是如何到达那里的。

概念图: 例外类型和原因 · 实际爆炸的代码行 · 哪一行有那个'abc'。 · 标准错误

为什么需要这个?

第一次看到Traceback时,眼睛会往上看。因为人们被训练成从上到下阅读。但是Python Traceback的结构正好相反。

Traceback (most recent call last):
  File "report.py", line 34, in <module>      ← 가장 바깥 호출
    sys.exit(main(*sys.argv[1:]))
  File "report.py", line 26, in main          ← 실제로 터진 자리
    amount = int(parts[2])
ValueError: invalid literal for int() with base 10: 'abc'   ← 무엇이 왜

最下面的行是例外类型和原因。就在上面是实际爆炸的代码行。上面是从那里到达的路线。

所以在实际工作中第一次看到的是三个。异常名称、异常信息中包含的实际值,以及直接上一个帧的文件和行号。这三个就能确定原因。ValueError: ... 'abc'意思是“应该是数字的位置里放入了abc”,那么下一个问题就只有一个——哪一行有那个'abc'。

怎么行动

这里有一个常见的错误。就是把输出保存为文件,但没有跟踪背包的情况。

python3 report.py > out.txt        # 트레이스백이 안 담긴다
python3 report.py > out.txt 2>&1   # 담긴다

Traceback不是标准输出,而是标准错误。当客户公司听到“日志中没有任何内容”时,第一个要确认的就是这个。在排布夹的Cron设置中2>&1经常遇到这种情况,因为这个漏洞,几个月来失败的原因都没有留下任何痕迹。

修改本身一般都很简单。难的是接下来的事情。

在现场相遇的样子

在修复因为一个破损的行而死亡的脚本时,有两种方式。

第一个是让你跳过那个行。脚本会回到起点,今天的问题就结束了。

第二个是让人们在跳过的时候留下为什么跳过什么。如果下周发生同样的事情,打开一次日志文件,1分钟就能结束。

第二个是测量植入。这也是处理无法再现的问题的正工法。以再现为目标的话,花几天时间做不到就结束了,但如果把目标转移到下次观测上,即使失败也会留下一些东西。即使植入的测量不是这个错误,也会在下一个错误中使用。

还有一点需要注意。因时机和竞争而产生的问题是,插入测量行为本身会改变时机,掩盖症状。在这种情况下,需要选择不会干扰执行路径的观测。可以尝试调整已经留下的日志的视角,通过抽样减少负担,或者事后导出状态。

最后,需要确认修改后的代码是否在其他输入中也正确,然后就可以结束了。今天只针对数据进行的修改不是修改,而是偶然。

每种语言的阅读方向都不一样

Trace背包每个语言的顺序相反。如果不知道这个,就会看错线。

语言 顶部 底部
Python 最外层(入口点) 出现错误的地方
Java 出现错误的位置 最外边
Go (panic) 出现错误的位置 最外边
JavaScript 出现错误的位置 最外面的

只反对Python,所以很困惑。Python是Traceback (most recent call last)说 很亲切地写着,那句话的意思就是“下面是最新的”。

一直追踪包裹的错误

框架通常掩盖原始例外。真正的原因在链条的末端

Traceback (most recent call last):
  ...
psycopg.OperationalError: connection failed

The above exception was the direct cause of the following exception:   ← __cause__
Traceback (most recent call last):
  ...
app.errors.StorageUnavailable: 저장소에 닿을 수 없습니다

Python是raise ... from e如果为“direct cause”,在处理异常时又出现异常的话 用“During handling of the above exception”进行区分。第二个通常是 错误处理代码本身的错误是这样的信号——试图处理原因又死了。

Java是Caused by:将后续连接起来,Go是errors.Unwrap按。 无论哪种方式,最里面的例外信息都是调查的起点。

用我的代码缩小的方法

框架框架几十行就滑眼了,只过滤出自己的代码。

# 스택에서 우리 패키지만
grep -E 'File "/app/' traceback.txt

# pytest — 우리 코드 프레임만 보여 준다
pytest --tb=short -p no:cacheprovider

而且最下面(以Python为标准)的我们的代码框架通常是真正的位置。 比起那个,里面是图书馆,图书馆弄错的情况很少。我们交的 价格错了。

不能再现的时候留下的东西

在运营中,我发现错误仅靠堆栈是不够的。在捕获异常的地方当时输入 一起留下。

except Exception:
    log.exception("주문 처리 실패", extra={
        "order_id": order.id,
        "payload_hash": hashlib.sha256(raw).hexdigest()[:12],   # 원문은 남기지 않는다
        "trace_id": current_trace_id(),
    })
    raise

与其说是保留原文,不如说是留下哈希。即使不把个人信息放在日志里 可以知道是否再次输入了相同的输入

下次实习要做的事情

从某一天开始,确保失败的配置脚本的跟踪包,确定异常类型和第一次失败行,按照规格修改后,种下记录跳过行的测量,最后确认是否完全归纳到不同的输入文件中。