# 调试日志打印debug模块
30 秒速记
- 用法:
const debug = require('debug')('app:db'),启动时DEBUG=app:* node app.js才会输出 - 不设
DEBUG时完全静默,可以放心留在代码里;Express内部也用它,DEBUG=express:*能看路由匹配过程 - 命名空间按业务领域起(
app:order、app:pay),支持通配*和排除-app:sql - 默认输出到
stderr,浏览器里用localStorage.debug = 'app:*'开启 - 它是开发调试工具,生产日志用
pino/winston做结构化输出;debug里同样别打密钥
debug 是一个按命名空间开关的调试打印工具,代码里随便写,只有通过 DEBUG 环境变量点名的命名空间才会真正输出。 比如数据库模块用 app:db,订单模块用 app:order,平时不设 DEBUG 一行都不打,排查订单问题时 DEBUG=app:order node app.js 只看订单相关的输出,还会自动带上颜色和两次输出之间的毫秒间隔。它比 console.log 好在不用删、不用注释,想看就开。不过它只适合开发和临时排查,生产环境的正式日志要用结构化的日志库,而且就算默认关着,也别往 debug 里打 token、密码这类东西。
const debug = require('debug')('app:order')
debug('create order %o', { id: 42, amount: 99 })
// DEBUG=app:* node app.js
// app:order create order { id: 42, amount: 99 } +0ms
理解「调试日志打印debug模块」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。
工程中还要验证退出码、资源清理和异常输入。属于 testing 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。
debug 模块的思路很简单:每个模块创建一个带名字的打印函数,运行时由 DEBUG 环境变量决定哪些名字被启用。
// db.js
const debug = require('debug')('app:db')
debug('connecting to %s', 'mysql://localhost')
// order.js
const debug = require('debug')('app:order')
debug('order %o', { id: 1 })
DEBUG=app:db node app.js # 只看数据库
DEBUG=app:* node app.js # 看 app 下所有模块
DEBUG=*,-express:* node app.js # 全开,但排除 express 内部
几个实用细节:
- 输出默认写到
stderr,不会和程序正常输出到stdout的内容混在一起,重定向时要注意2>。 - 每行末尾的
+12ms是距离同一命名空间上一次输出的时间间隔,粗略看耗时很方便。 - 支持
%s、%d、%j、%o、%O等占位符,其中%o/%O会在启用时才格式化对象。 - 可以用
debug.enabled判断当前命名空间是否开启,把昂贵的计算包起来。 - 浏览器端同样可用,在控制台执行
localStorage.debug = 'app:*'后刷新页面即可。 - 很多库内部都用
debug打调试信息,比如Express,设置DEBUG=express:*能看到路由匹配和中间件执行的过程,排查「为什么这个路由没命中」很有用。
debug 适合开发阶段和临时排查。生产环境需要的是分级、结构化(JSON)、可集中采集的日志,这部分交给 pino、winston 这类日志库。
面试官追问
追问 1代码里写了 debug(...),运行时什么都没打出来,说明那段代码没执行吗?
不一定,更可能是没设 DEBUG,或者命名空间写得对不上,比如代码里是 app:order,启动时写成了 DEBUG=order。先用 DEBUG=* 确认能打出来,再收窄范围。
追问 2DEBUG=* 一开日志刷屏,怎么只看自己的?
用通配加排除:DEBUG=app:*,-app:sql 打开所有 app 开头的,但去掉 SQL 那个。所以命名空间要按领域分层起名,排查时才能精确控制范围。
追问 3debug 关着的时候,debug('data %s', JSON.stringify(bigObj)) 有性能开销吗?
有,JSON.stringify 作为参数在调用前就执行了,不管开没开。用 %O 占位符直接传对象,它只在启用时才格式化;特别重的计算用 if (debug.enabled) 包一下。
追问 4Windows 上 DEBUG=app:* node app.js 报错,怎么写?
cmd 里是 set DEBUG=app:* & node app.js,PowerShell 是 $env:DEBUG='app:*'; node app.js。团队里跨平台的话在 scripts 里用 cross-env DEBUG=app:* node app.js。
追问 5线上偶发问题想临时开 debug 看一下,要注意什么?
只开相关的命名空间,限定时间和实例,看完就关;高频路径全开会明显增加 CPU 和 I/O。输出里有用户数据的话,和正式日志一样要脱敏。
# 前言
30 秒速记
debug= 带开关的console.log:代码里一直留着,默认不输出,用DEBUG环境变量按需打开- 按命名空间分类,比如
app:db、app:api,排查哪块就开哪块 - 内置替代:
util.debuglog('foo'),用NODE_DEBUG=foo打开,不用装依赖 NODE_DEBUG=http,net还能直接看Node内核模块自己的调试输出- 定位:开发期诊断工具,不是生产日志方案;线上要的是结构化日志(
pino/winston)加集中采集
debug 模块说白了就是一个能开关的 console.log,日志一直留在代码里,平时不出声,要查问题时用环境变量打开。 它最大的好处是按命名空间分类,排查数据库就只开 app:db,不会被满屏无关输出淹没,Express、Koa 这些库内部也是用它打调试信息的。Node 本身也带了一个类似的 util.debuglog(),用 NODE_DEBUG 开关,功能简单一些但零依赖。不过要分清,这俩都是开发期的诊断手段,线上要查问题还是靠结构化日志和日志平台。
在node程序开发中时,经常需要打印调试日志。用的比较多的是debug模块,比如express框架中就用到了
备注:node在0.11.3版本也加入了util.debuglog()用于打印调试日志,使用方法跟debug模块大同小异。
面试官追问
追问 1都有 console.log 了,为什么还要专门引一个 debug?
console.log 要么删要么一直打,调试完删掉,下次出问题又得加回来。debug 的日志可以常驻代码里,默认零输出,DEBUG=app:db node app.js 一开就有,还自带命名空间颜色和距上一条的耗时 +12ms。
追问 2线上请求报了 500,翻日志一行 debug 输出都没有,能说明请求没进服务吗?
不能。生产环境一般根本没设 DEBUG,debug 函数调了也是空操作。判断请求有没有进来要看访问日志、网关日志或者 APM 链路,debug 输出不是业务执行的凭据。
追问 3util.debuglog 和 debug 模块怎么选?
写给别人用的 npm 库、想零依赖,用 util.debuglog,开关是 NODE_DEBUG=mylib;业务项目已经大量用 debug 的命名空间,就别搞两套。一个项目里两种开关并存,新人光记环境变量就得踩一遍坑。
追问 4想看 http 模块内部到底在干嘛,比如连接有没有复用,怎么看?
直接 NODE_DEBUG=http,net node app.js,Node 内核模块本身就是用 debuglog 打的调试信息,socket 复用、keep-alive 这些都会打到 stderr。
追问 5debug 打出来的日志能不能直接当线上日志用?
不建议。它是纯文本、没有级别、没有请求 ID,日志平台很难检索聚合。线上用 pino 这类输出 JSON 的库,debug 只在本地或临时排障时打开。
# 基础例子
30 秒速记
require('debug')('app')返回一个日志函数,命名空间就是app- 运行时加环境变量打开:
DEBUG=app node 01.js,输出app hello +0ms +0ms是距同一命名空间上一条日志的时间差,顺手就能看出哪步慢- 输出走
stderr不走stdout,重定向时要用2> Windows上写法不同:PowerShell是$env:DEBUG='app',跨平台用cross-env
用法就两步:代码里
