Node.js 应用在 Linux 上运行时,最怕的往往不是报错本身,而是错误发生后没人知道、没有记录,或者进程直接退出。要把服务跑稳,关键不在某一个技巧,而是在“捕获、记录、恢复、追踪”这几步上都补齐。
下面按实际排障链路来拆解:先看 Node.js 自身怎么抛错,再看同步和异步代码分别该怎么兜底,最后补上生产环境必需的日志、进程管理和错误上报工具。读完后,你可以判断自己当前的服务到底是“能跑”,还是已经具备基本的线上容错能力。
监听 error 事件,先把明显故障接住
在 Node.js 里,很多对象都会发射 error 事件,比如 HTTP 服务器、流对象、网络连接等。如果没有显式监听,这类错误可能直接导致进程异常退出,或者至少让排查成本迅速上升。
以 HTTP 服务为例,最基本的做法就是给服务器对象补上 error 监听器:
const http = require('http');
const server = http.createServer((req, res) => {
// ...
});
server.on('error', (err) => {
console.error(`Server error: ${err.message}`);
});
这一步看起来简单,但它解决的是最基础的问题:服务出错时,至少你能知道错误发生在什么对象、错误信息是什么。对于 Linux 上常见的端口占用、权限不足、套接字异常,这类监听通常就是第一现场。
为什么这一步不能省
很多线上故障并不是业务逻辑抛错,而是运行时资源、网络或系统调用失败。如果没有事件级别的错误处理,应用可能只表现为“突然挂了”,却没有留下足够线索。
同步代码用 try-catch 兜底
对于同步执行的代码,try-catch 仍然是最直接、最可靠的防线。它的作用不是消灭错误,而是在错误出现时阻止程序直接崩掉,并把控制权交还给你。
try {
// Code that might throw an error
} catch (err) {
console.error(`Error: ${err.message}`);
}
这类写法尤其适合包裹那些“看起来很普通、实际上很容易抛异常”的操作,比如 JSON 解析、配置读取、路径处理,或者某些同步文件读取流程。问题不在于这些代码复杂,而在于它们一旦失败,通常会在最不希望中断的位置把程序打断。
try-catch 该放在哪
实际使用时,更重要的是划清边界:不要把整段程序无差别包进一个大 try-catch,而是把它放在真正可能抛错的同步步骤周围。这样既能保留上下文,也更方便后续记录日志和判断恢复策略。
异步错误处理,重点是 Promise 和 async/await
Node.js 的异步模型决定了错误处理不能只靠同步思路。回调、Promise、async/await 分属不同阶段,如果只顾业务逻辑,不处理拒绝态或异常传播,线上问题就很容易变成“偶发且难复现”。

使用 async/await 时,最稳妥的方式是把相关的 await 操作放进同一个 try 块里:
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
// Process data
} catch (err) {
console.error(`Error fetching data: ${err.message}`);
}
}
这样无论是请求失败、响应解析失败,还是后续处理步骤抛错,都会被统一捕获。对可读性和维护性来说,这比层层嵌套回调更清晰。
不用 async/await 时,别漏掉 .catch()
如果代码还在直接使用 Promise 链,那就必须显式补上 .catch()。未处理的 Promise 拒绝是 Node.js 应用里很常见的一类隐患,早期常被忽视,到了生产环境才暴露成零散故障。
简单说,同步代码靠 try-catch,异步代码要么放进 async/await + try-catch,要么保证 Promise 链末尾有 .catch()。这不是风格问题,而是错误能不能真正被接住的问题。
生产环境里,日志比 console 更重要
开发阶段直接用 console.error() 没问题,但到了 Linux 服务器上,单纯把错误打到控制台通常不够。进程重启后上下文会丢,多个实例运行时也不方便统一检索,因此需要把错误持久化下来。
常见做法是接入 winston 或 morgan 这类日志工具。下面是一个用 winston 记录错误日志到文件的例子:
const winston = require('winston');
const logger = winston.createLogger({
level: 'error',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
],
});
// ...
try {
// Code that might throw an error
} catch (err) {
logger.error(`Error: ${err.message}`);
}
和简单输出相比,日志文件的价值在于可回放。你可以结合时间、错误级别、上下文信息去还原故障路径,而不是只看到一句一闪而过的报错。
日志里至少该留下什么
即使文章里的示例只记录了 err.message,实际生产环境也应尽量保留更多上下文,比如请求时间、接口路径、关键参数、调用阶段等。信息越完整,Linux 服务器上的排查成本就越低。
PM2、forever 这类进程管理器负责兜住服务可用性
错误处理做得再全,也不能假设应用永远不会崩。真正的线上系统还需要一层“进程级恢复”能力,这就是 PM2、forever 之类工具存在的意义。

在生产环境中,如果 Node.js 进程因为未处理错误退出,进程管理器可以自动重启应用,避免服务长时间不可用。以 PM2 为例,用它启动应用后,即使某次异常没有被代码完全兜住,PM2 也会尽快拉起新的实例。
除了自动重启,这类工具通常还带有日志聚合、负载均衡等能力,对 Linux 服务器上的长期运行场景尤其有用。它解决的不是“为什么出错”,而是“出错之后服务还能不能继续活着”。
接入 Sentry、Bugsnag,把低频错误也纳入追踪
本地日志适合排查已知问题,但对于复现率低、触发条件复杂的 bug,仅靠日志往往不够。这个时候,错误报告平台的价值就体现出来了。
像 Sentry、Bugsnag 这类工具,除了收集错误信息,还能提供堆栈跟踪、用户环境数据,甚至事件发生前后的行为序列。对那些“线上偶发、开发环境复现不出来”的问题,这些信息通常比单条日志更有用。
配置完成后,错误发生时平台会自动接收通知,并在仪表盘里集中展示。团队可以据此做归类、分派和跟踪,不用等用户反馈后再倒查。
一套更完整的 Linux 下 Node.js 错误处理思路
把这些手段串起来看,Node.js 在 Linux 上的错误处理可以分成几层:
- 事件层:监听
error,先接住运行时故障。 - 代码层:同步用
try-catch,异步用async/await或 Promise 的.catch()。 - 记录层:用
winston、morgan等工具把错误落盘。 - 恢复层:借助 PM2 或 forever 在进程退出后自动拉起服务。
- 追踪层:通过 Sentry、Bugsnag 持续收集线上异常和上下文。
单看每一项都不复杂,但真正决定线上稳定性的,往往就是这些细节有没有一起到位。如果你的服务现在只有控制台输出,而没有日志、自动重启和错误上报,那么它离“可运维”的 Node.js 应用,其实还差几步。







