LLM 调试日志刷屏怎么办?用 tee 边看边存

前言

最近在调试 LLM 应用时,我遇到了一个特别影响效率的问题:

程序一跑起来,终端日志就像瀑布一样疯狂滚动。等我想找刚才的报错堆栈时,关键信息早已被顶出屏幕,往上翻半天也找不到。

给项目接入正规的日志模块当然更完善,但在快速验证想法、跑 Demo 的阶段,为了保存一次输出就去改代码,未免有些麻烦。

好消息是,这种情况不用改代码,一个 Linux、macOS 等系统自带的命令就能帮上忙:tee

它可以把同一份输出一边显示在终端,一边保存到文件。通常不影响终端观察,也方便运行结束后搜索和回看。

一条命令,边看边存

以 npm 脚本为例:

bash 复制代码
mkdir -p ./tmp
npm run --silent demo:04 -- "这个项目有哪些文件?" 2>&1 | tee ./tmp/demo.log

核心就是第二行。日志会照常显示在终端,同时悄悄写入 ./tmp/demo.log。等程序跑完,再打开文件慢慢找报错,不用和滚动的屏幕"赛跑"了。

其中:

  • 2>&1:把错误输出合并到正常输出,避免漏掉报错;
  • |:把合并后的输出交给下一个命令;
  • tee ./tmp/demo.log:在终端显示输出,同时保存到文件;
  • --silent:减少 npm 自身的提示信息,可按需省略。

有个小提醒:tee 不会自动创建上级目录,所以示例先用 mkdir -p ./tmp 把目录准备好。

tee:终端里的"三通管"

tee 这个名字来自水管中的"三通管"(T 形管件)。它会接收标准输入,然后把内容分成两路:

  • 一路继续输出到终端;
  • 一路写入指定文件。
text 复制代码
命令输出 ──► tee ──► 终端显示
                 └──► 写入文件

先来看一个最简单的例子:

bash 复制代码
echo "hello llm" | tee ./tmp/debug.log

终端会显示 hello llm,文件中也会保存同样的内容。

关键一步:别漏掉错误日志

每个命令运行时,默认都有三个标准数据流:

编号 名称 缩写 作用
0 标准输入 stdin 接收键盘或其他命令传来的输入
1 标准输出 stdout 输出正常结果
2 标准错误 stderr 输出错误和警告

终端默认会同时显示 stdoutstderr,所以平时看起来没什么区别。可实际上,它们走的是两条不同的通道。

坑就在这里:管道 | 默认只把 stdout 交给下一个命令。

bash 复制代码
npm run demo | tee ./tmp/demo.log

如果程序同时产生正常输出和报错,tee 只能收到正常输出。报错仍会显示在终端,却不会写进日志文件。

所以,调试时更推荐这样写:

bash 复制代码
npm run demo 2>&1 | tee ./tmp/demo.log

第一次看到 2>&1,是不是有点像乱码?别担心,拆开看就简单了:

  • 2 代表 stderr
  • &1 代表 stdout 当前指向的位置。

合起来就是:stderrstdout 走同一条通道 。这样再接管道,正常信息和报错就都能进入 tee

这里的 & 很重要:它表示后面的 1 是文件描述符编号,而不是文件名。

bash 复制代码
# 把 stderr 合并到 stdout
command 2>&1

# 把 stderr 写进一个名为"1"的文件,并不是同一回事
command 2>1

不想记文件描述符也没关系,只要记住一句话:2>&1 就是把错误输出并入正常输出。

三个顺手又实用的小技巧

追加写入,保留历史日志

tee 默认会覆盖同名文件。如果想保留之前的日志,增加 -a

bash 复制代码
npm run demo 2>&1 | tee -a ./tmp/demo.log

文件名加时间,方便回溯

调试不同版本时,可以把时间写进文件名:

bash 复制代码
npm run demo 2>&1 | tee "./tmp/demo-$(date +%Y%m%d-%H%M%S).log"

运行后会得到类似 demo-20260823-143022.log 的文件,方便对比和回溯。

文件存完整,屏幕看重点

tee 会继续把内容传给后面的命令,因此可以一边保存完整日志,一边筛选终端显示内容:

bash 复制代码
npm run demo 2>&1 | tee ./tmp/demo.log | rg "报错|工具调用"

如果没有安装 rg,也可以使用 grep

bash 复制代码
npm run demo 2>&1 | tee ./tmp/demo.log | grep -E "报错|工具调用"

注意:这里过滤的只是终端最终显示的内容,demo.log 中仍然保留完整日志。

只保存需要的输出

大多数时候,stdoutstderr 一起保存最省心。但如果正常输出特别长,或者某些警告实在太吵,也可以只留其中一种。

bash 复制代码
# 只保存 stdout,丢弃 stderr
npm run demo 2>/dev/null | tee ./tmp/demo.log

# 只保存 stderr,丢弃 stdout
npm run demo 2>&1 1>/dev/null | tee ./tmp/demo.log

这里出现了一个新面孔:/dev/null。它是 Linux、macOS 等 Unix 系统中的特殊设备文件,可以把它理解成一个"黑洞":任何写进去的内容都会被直接丢弃,不会显示,也不会保存。

所以上面两条命令分别表示:

  • 2>/dev/null:丢弃 stderr,只让 stdout 进入 tee
  • 2>&1 1>/dev/null:先让 stderr 指向原本的 stdout 通道,再把 stdout 改到 /dev/null,最终只有 stderr 进入 tee

重定向会从左到右执行,因此第二条命令的顺序不能颠倒。看到这里觉得有点绕也很正常------如果暂时不需要区分两种输出,记住最常用的 2>&1 | tee 就够用了。

使用前要留意的三个细节

如果只是日常调试,前面的用法已经够用;下面三个细节主要面向脚本和自动化场景。

日志为什么不实时了?

用了管道后,有些程序的日志会变慢,甚至攒一批后才一起出现。

这通常不是 tee 在故意拖延,而是上游程序或它使用的运行库发现 stdout 不再直接连接终端,于是改变了缓冲策略。Linux、macOS 等系统都可能遇到这种情况。

如果程序支持关闭缓冲或强制刷新,优先使用它自身提供的选项。在 Linux 上,也可以视情况尝试:

bash 复制代码
stdbuf -oL -eL command 2>&1 | tee ./tmp/demo.log

如果程序必须在真实终端环境中才会及时输出,还可以用 script 模拟终端。不过 macOS 和 Linux 的参数不同:

bash 复制代码
# macOS 常见写法
script -q ./tmp/demo.log command

# Linux(util-linux)常见写法
script -q -c 'command' ./tmp/demo.log

script 记录的是终端会话,日志里可能带有颜色控制符、终端信息或头尾记录,因此它更适合作为缓冲问题的备选方案,而不是所有场景下都替代 tee

命令失败,状态却是成功?

执行下面的命令时:

bash 复制代码
command 2>&1 | tee ./tmp/demo.log

Shell 默认关注管道中最后一个命令,也就是 tee 的退出状态。即使前面的 command 失败,只要 tee正常结束,整条命令仍可能表现为成功。平时手动调试不一定会注意到它,但放进自动化脚本或 CI,就很容易造成误判。

Bash 和 Zsh 可以提前启用 pipefail

bash 复制代码
set -o pipefail
command 2>&1 | tee ./tmp/demo.log

这样,只要管道中的任意命令失败,整条管道就会返回非零状态。

日志里的奇怪字符是什么?

一些命令会输出 ANSI 颜色代码。保存后直接打开日志,可能看到类似 ^[ 的字符。可以优先关闭上游命令的彩色输出;具体参数因工具而异,常见形式包括 --no-color

另外,如果进程被 kill -9 强制终止,上游程序尚未刷新的少量日志仍可能丢失。

Windows 平台怎么用

环境 使用方式 说明
Linux / macOS / Unix tee 通常开箱即用
Windows PowerShell Tee-Object PowerShell 内置的等价命令
Windows Git Bash / WSL tee 可以直接使用 Unix 写法
极简容器镜像 视镜像而定 scratch 等镜像可能不包含该命令

PowerShell 示例:

powershell 复制代码
npm run --silent demo:04 -- "..." 2>&1 | Tee-Object -FilePath C:\temp\demo.log

需要注意,PowerShell 的流和重定向规则与 POSIX Shell 并不完全相同。上面的写法适合日常记录输出;复杂脚本应按所用 PowerShell 版本进一步确认。

一张表快速记住 tee

需求 命令
边看边保存 stdout `command
同时保存正常输出和报错 `command 2>&1
追加到已有日志 `command 2>&1
只保存 stdout `command 2>/dev/null
只保存 stderr `command 2>&1 1>/dev/null
保存完整日志、终端只看重点 `command 2>&1

写在最后

调试 LLM 应用时,日志就是定位问题的线索。

下次再遇到终端刷屏,先别急着和鼠标滚轮较劲。试着在命令后面加上:

bash 复制代码
2>&1 | tee ./tmp/debug.log

让终端负责实时显示,让日志文件负责完整记录。等程序跑完,再用编辑器或 rg 慢慢搜索。

一个小小的 tee,不会替代正规的日志系统,却很适合那些"先跑起来看看"的时刻。少翻几次屏幕,多留一份线索,调试也能从容不少。

相关推荐
DYWorker0011 小时前
搭建嵌入式Linux虚拟机开发环境
linux
weipt2 小时前
从ollama到llama.cpp
linux·服务器·llama
加油码2 小时前
动静态库的链接底层原理(进阶版)
linux·c++
H_oRIZoN_3 小时前
Linux入门DAY23(C语言树形结构)
linux·c语言·数据结构
kdxiaojie3 小时前
Linux USB驱动阅读笔记(1)
linux·笔记·学习·usb
JacksonMx3 小时前
Java CompletableFuture 异步编程实战:从入门到架构师避坑指南
linux·数据库·python
byte轻骑兵4 小时前
【BlueZ 】蓝牙 SDAP 协议入门:BlueZ 中设备服务发现的基础逻辑
linux·人工智能·bluez·电脑蓝牙·嵌入式蓝牙
mounter6254 小时前
GCCRS 突破与 Rust 入核:Linux 工具链的重大变革
linux·开发语言·rust·linux kernel·kernel
lxw20230271166 小时前
k8s安装部署(利用ansible)
linux·kubernetes·ansible