Nuitka 产物里 structlog.aexception 只剩消息,traceback 去了哪里
源码运行正常、编译后异步异常日志缺栈时,保留 Nuitka 条件。关键分支可评估已确认的同步入口,说明阻塞代价,并用最终可执行文件核对异常信息。
同一个异步函数里,log.exception() 能打印 traceback,await log.aexception() 却只有错误消息;源代码直接运行正常,Nuitka standalone 产物才缺栈。此时要保留“解释执行与编译产物不同”这个条件,不能先把日志级别或 ConsoleRenderer 全部换掉。
明确出现过的组合是 Python 3.13、structlog 25.5.0、AnyIO 启动入口与 Nuitka standalone。这个现象不能推广成 structlog 的异步日志普遍不支持异常信息,也没有足够依据把所有新 Nuitka 或 structlog 版本都标成同样受影响。
先比较真正运行的两条路径
在受控开发环境中,保留同一套 processors、handler 与 wrapper,比较源码解释运行和实际编译产物。两边应在 except 块里记录同一类错误,不能拿正常路径的日志与异常路径比较。
记录实际 Python、structlog、Nuitka 版本和编译选项。如果源码运行也缺 traceback,先检查有没有去掉 exc_info、提前转换事件或使用另一套日志配置;这些情况不需要编译器参与。
aexception() 会把日志处理交给线程执行。structlog 25.5.0 与当前 26.1.0 的正式实现都在交接前读取 sys.exc_info(),试图保住当前异常;看到异步方法最终切线程,不能据此断言它设计上就会丢 traceback。
可先让关键异常走已确认的同步入口
如果当前编译产物的同步 exception() 路径已经能保留异常,可以在关键错误分支暂时使用它。下面是已有异步函数里的片段,log 与 perform_job 沿用应用对象:
async def run_job():
try:
await perform_job()
except Exception:
log.exception("job failed")
raise
保留 raise 是为了延续示例原有的失败传播,不能因记录过日志就把失败任务算作成功。应用若本来有特定恢复流程,应保留那个流程,而不是统一改成退出。
同步记录会在当前调用中执行相应日志处理;慢速 handler 或 I/O 可能阻塞事件循环。这是临时保留诊断信息的取舍,不应把所有高频日志一律改成同步,也不能声称应用仍保持原来的异步日志性能。
如果尝试继续使用异步入口,可在原 except 中显式保存异常三元组,再按该版本 exc_info 契约传入,检查编译产物是否真正保留 traceback。但它仍需在目标产物验证,不能仅因传了 exc_info=True 就断言修复完成;默认异步入口原本就在做异常捕获。
验收对象必须包括最终可执行文件
检查实际目标文件中的错误类型、消息和关键调用栈,不只检查构建成功或普通 INFO 日志。确认异常继续走原业务失败分支,日志没有变成重复输出,也没有因为保存异常对象而被长期缓存。
准备更换依赖或编译器时,保留同一最小触发路径和应用真实配置,再比较结果。没有确认对应正式修正前,不把“升级最新版”写成确定答案。回退临时同步记录也要重新核对异常信息,避免恢复异步入口时再次只剩消息。


