Node服务如何调试

写在前面

Q:Node要怎么调试?

A:啥叫调试?

一般我们所说的“调试”应该是指步过、步入、步出,盯着源码一句一句执行吧。这样的话,调试服务端的程序和一般客户端程序没什么区别,都是想要窥视执行流、调用栈、变量当前值等等,也就是说调试Node服务和调试页面中JS脚本的方式一样

当然,服务端有服务端的特殊性,很多代码与客户端环境密切相关,比如Cookie、Request Header、localStorage等等,想要一行一行调试似乎不太现实,首先,服务的输入(客户端请求)从哪里来?可以模拟请求,也可以直接模拟数据。。。再扯下去其实是测试的内容了,不是“调试”

一.Debugger

Node内置了断点调试,也就是debugger语句,如下:

// console.js
debugger;

var value = 1;

var fn1 = function() {
debugger;
    value = '2';
    console.log('fn1');
};
var fn2 = function() {
debugger;
    value = 3;
    fn1();
    console.log('fn2');
};

console.log(1);

process.nextTick(function () {
debugger;
    value = 4;
    fn2();
    console.log(2);
});

console.log(3);

其中debugger;表示设置断点,异步执行流也会被断点中断。当然,debugger只在调试环境有效,通过命令行进入debug模式:

node debug ./console.js
// 对于已经在运行的服务,向其进程发送SIGUSR1信号可以进入调试模式
// kill -s USR1 [pid]

然后可以看到提示信息和debug交互提示:

< Debugger listening on port 5858
debug> . ok
break in E:\node\learn\debug\console.js:1
> 1 debugger;
  2
  3 var value = 1;
debug> 

执行流停在了第一行的debugger;处,光标跳动等待输入调试命令,Node没有对V8调试命令提供完整支持,可用命令如下:

1.控制执行流

  • c/cont

    继续执行到下一个断点

  • n/next

    执行到下一句

  • s/step

    步进到函数内部

  • o/out

    从函数内部跳出

  • pause

    暂停执行

比如输入c会输出1和3,然后停在value = 4;的上一行。再输入n会停在value = 4;,然后先输入n再输入s就进入fn2的函数体,停在函数体第一行。。。

注意:调试命令不太友好,如果程序全部执行完毕了,再输入c,就需要kill进程结束调试了

2.设置/清除断点

  • sb()/setBreakpoint()

    在当前行设置断点

  • sb(line)/…

    在第line行设置断点

  • sb(‘fn()’)/…

    在函数体的开头设置断点

  • sb(‘script.js’, 1)/…

    在script.js文件的第一行设置断点

  • cb()/clearBeakpoint()

    清除断点

注意:这些命令很不好用,建议在源码中用debugger;语句设置断点

3.查看状态信息

  • bt/backtrace

    输出当前堆栈信息

  • list(3)

    列出当前前后各3行源码

  • watch(‘expr’)

    添加expr到观察列表

  • unwatch(‘expr’)

    从观察列表删除expr

  • watchers

    列出观察列表中所有表达式和值

  • repl

    打开调试的上下文,直接输入调试代码

这些命令挺好用,watch频繁变化的变量/表达式,或者直接repl进入调试环境中的调试环境,想输出什么都行

二.调试工具

手动输入命令比较麻烦,所以有了调试工具:node-inspector,npm全局安装,然后直接运行起来:

E:\node\learn\debug>node-inspector
Node Inspector v0.12.7
Visit http://127.0.0.1:8080/?port=5858 to start debugging.
Cannot send response - there is no front-end connection.

浏览器(浏览器必须带有Blink开发者工具,比如Chrome和Opera,而FF45不行)访问http://127.0.0.1:8080/?port=5858,啥都没有啊,等等,最后一行说没找到要调的东西,那好,我们运行程序并进入debug模式:

node debug ./console.js
// 当然,向进程发送SIGUSR1信号也可以
// kill -s USR1 [pid]

然后刷新浏览器,页面出现源码了,并且显示了熟悉的Dev Tools:

(function (exports, require, module, __filename, __dirname) { debugger;

var value = 1;

var fn1 = function() {
debugger;
    value = '2';
    console.log('fn1');
};
var fn2 = function() {
debugger;
    value = 3;
    fn1();
    console.log('fn2');
};

console.log(1);

process.nextTick(function () {
debugger;
    value = 4;
    fn2();
    console.log(2);
});

console.log(3);
});

发现代码被模块包装了,所以调试的一个优势是可以窥探node源码,想要追根溯源,无限步入就可以扯出一整条执行流。同理,也可以方便的查看第三方模块内部的具体执行流,分析源码时比较有用

P.S.这里先运行node-inspector是为了说明先后顺序无所谓,不必每次都严格地先debug执行待调试的程序,原理是node提供了TCP调试协议,通过协议可以从进程外部进行调试,node-inspector通过该协议与待调试的进程通信,执行debugger命令,并通过Blink开发者工具界面显示出来

三.总结

其实无论是用命令还是用工具,感觉都比较麻烦,而且,为什么需要调试?

理由,笔者只能想到2种情况:

  • 代码糊了(发生了奇怪的错误,单纯静态分析找不到错误原因)

    搞不清执行流了,需要捋一捋

  • 想要分析源码

    弱类型的js没有办法转到定义,通过调试分析源码是不错的选择

调试会引起执行中断,必须线下进行。开发中需要一行一行盯着走的情况极少,应该通过单元测试保证代码健壮性,而不是一出问题就调试

对于服务端程序,除单元测试(mocha, should, muk, rewire,内容比较多,我们以后再说)外,日志(pm2自带简单的错误日志)也是非常必要的。通过分析日志锁定错误原因,进而找到相应的单元测试用例,进行修复

参考资料

  • 《深入浅出NodeJS》

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code