如何使用 IntelliJ 调试 nodejs / seneca 应用?
结论
Seneca 应用可以像普通 Node.js 程序一样在 IntelliJ IDEA 中调试,不需要专用调试器。问题通常出在以下两处:
- IntelliJ IDEA 15/16 使用的调试协议与当前 Node.js 版本不兼容;
- 断点所在的代码没有实际执行、由其他进程执行,或者程序正在等待 RabbitMQ 响应。
IDEA 15/16 已经比较旧。如果搭配较新的 Node.js,可能出现进程被调试器挂起、断点不命中或 Pause 无响应。最稳妥的处理方式是升级 IntelliJ IDEA,并使用 IDE 支持的 Node.js 版本。
检查运行配置
不要用普通的 JavaScript Debug 配置启动服务。应创建 Node.js Run/Debug Configuration,并检查以下设置:
JavaScript file指向应用的真实入口文件;Node interpreter指向实际使用的 Node.js;Working directory设为项目根目录;Node parameters先留空;Application parameters只填写应用需要的参数。
还要确认 Run 和 Debug 使用的是同一个 Node.js 解释器。可以分别在两种模式下输出:
console.log(process.version);
console.log(process.execPath);
console.log(process.pid);
如果必须继续使用 IDEA 15/16,需要选择与其调试器兼容的旧版 Node.js。具体兼容范围取决于 IDEA 的小版本和 Node.js 插件版本,不能只根据“15”或“16”判断。
将断点放在实际执行的位置
链式调用中的 .listen() 和 .client() 主要负责初始化 Seneca 和传输层,不适合用来确认异步消息是否到达。可以先展开回调,再把断点设在回调函数内部:
paymentService.act(
{
role: 'DatabaseService',
cmd: 'getGatewayCfg',
service: 'GatewayServices',
id: 39
},
function (err, result) {
debugger;
if (err) {
console.error('getGatewayCfg failed:', err);
return;
}
console.log('getGatewayCfg result:', result);
}
);
也可以暂时用 JavaScript 的 debugger 语句代替 IDE 行断点。只要调试器连接正常,代码执行到这里就应自动暂停。
如果 debugger 语句也无法让程序暂停,问题通常出在 Node.js 调试协议、IDE 配置或进程选择上,而不是 Seneca 本身。
等待 Seneca 就绪后再发送消息
示例代码创建 transport 后立即调用了 act()。如果 RabbitMQ 连接或远端 DatabaseService 还没准备好,请求可能会一直等到超时。当前配置是:
timeout: 99999
这个值接近 100 秒,调试时很容易让人误以为程序已经挂死。可以先显式等待 Seneca 就绪:
var seneca = require('seneca');
var paymentService = seneca({
errhandler: senecaErrorHandler,
timeout: 10000
})
.use('payment')
.use('seneca-rabbitmq-transport-v2')
.listen({
type: 'rabbitmq',
url: 'amqp://localhost',
pin: 'role:PaymentService'
})
.client({
type: 'rabbitmq',
url: 'amqp://localhost',
pin: 'role:DatabaseService,cmd:getGatewayCfg'
});
paymentService.ready(function (err) {
if (err) {
console.error('Seneca initialization failed:', err);
return;
}
console.log('Seneca is ready');
paymentService.act(
{
role: 'DatabaseService',
cmd: 'getGatewayCfg',
service: 'GatewayServices',
id: 39
},
function (err, result) {
debugger;
if (err) {
console.error(err);
return;
}
console.log(result);
}
);
});
function senecaErrorHandler(err) {
console.error(err);
return false;
}
调试期间可以暂时缩短 timeout,让 RabbitMQ、路由或远端服务的问题更快显现。
确认断点绑定的是正确进程
如果应用由进程管理器、集群模式、热重载工具或 child_process.fork() 启动,IDE 可能只连接到父进程,而业务代码实际运行在子进程中。排查时先直接运行入口文件:
node app.js
暂时不要通过 nodemon、PM2、npm 包装脚本或其他启动器调试。确认直接启动时能命中断点,再处理子进程自动附加的问题。
如果断点位于 payment 插件内部,还要确认程序加载的是预期文件:
console.log(require.resolve('payment'));
这样可以排除一种常见情况:断点设在源码目录中,但 Node.js 实际加载的是另一个副本、编译产物或全局模块。
还需要确认的事项
- RabbitMQ 正在运行,并且可以连接
amqp://localhost; - 负责
DatabaseService的进程已经启动; - Seneca 的
pin与远端 action pattern 完全匹配; - 断点所在行包含实际可执行的语句;
- 如果运行的是 TypeScript、Babel 或其他编译产物,source map 必须正确;
- 错误处理器不能静默吞掉错误,调试期间至少应输出
err.stack。
代码中的日期格式还有两个独立问题:getMonth() 从 0 开始,getDay() 返回星期几,而不是月份中的日期。应改为:
var orderDate =
now.getFullYear() + '-' +
(now.getMonth() + 1) + '-' +
now.getDate();
这个问题不会导致断点失效,但可能让后续业务数据出现异常。
备注:内容仅供参考。
