生产错误降低了速度,并且通常会影响释放路线图的完全轨迹。如果您有强大的错误跟踪设置可以依靠。
在本文中,我们将研究如何使Node.js应用程序中的错误更方便,自动化和安全。
让我们开始!
两个错误类型
我们构建的每个应用程序都有一个最终目标。该代码反映了该目标,并允许用户执行预期的操作。由于现实世界很复杂,因此可能出现的情况无法通过应用程序来处理。这导致了意外的和不必要的输出,击败了该应用程序的最终目标。与happy path分歧称为错误。
错误可以放入两个存储桶中:
- 操作错误
- 编程错误
操作错误在主动使用应用程序期间会出现。他们也称为运行时错误,它们可以帮助我们发现开发过程中错过的边缘案例。例如,用户添加了负年龄,在食品交付应用程序上给出负面提示,或在输入字段中输入恶意脚本等。开发人员应正确处理这些信息。
编程错误,另一方面,由于代码中缺少错误处理程序而导致错误滑倒时会出现。示例包括:
- 将字符串分配给一个数字。
- 没有抓住拒绝的承诺。
- 访问对象的不存在属性。
什么是错误跟踪?
错误跟踪是一个过程,在该过程中,观察并监视应用程序的异常行为,以在影响最终用户之前对其进行修复。错误跟踪需要根据历史使用模式进行记录,监视,创建警报和异常检测。
主动跟踪错误并解决这些错误提供了平稳的用户体验,并有助于建立信任。依靠用户报告错误不仅不可靠,而且对您的业务和品牌不利。
如何跟踪node.js中的错误
跟踪错误涉及预期错误和编写代码来处理意外/不必要的方案 - 换句话说,当您的应用程序无法正常工作时,情况。这是唯一的(正确的)操纵正确的课程并确保您的应用程序在正确的轨道上。
有多种方法可以识别node.js应用程序中的错误:
- 回调
- 承诺
- try-catch
我们将在本文中使用Try-Catch。基本想法是运行一块代码并将其包裹在TRY块中,而错误处理程序则位于捕获块中。如果您的应用程序的行为正常,则捕获块将永远不会运行。
这是一个示例片段,使用try-catch处理这种情况:
try {
operationThatMightFail();
} catch (err) {
log.error(err);
throw err;
}
现在,我们已经通过Vanilla错误处理了,让我们继续进入令人兴奋的部分。上述错误处理程序是一个不错的起点,但是我们可以做得更好。您可能已经注意到,它并没有为我们提供太多价值。查看该错误消息不一定会在事件中对我们有所帮助。有更好的方法。让我们探索。
创建自定义错误处理程序
我们希望每个错误都具有足够的上下文以使其可行。例如,如果叠加错误的堆栈轨迹,调试可能会花费不合理的时间。平衡丰富错误报告与简明语法是关键。错误处理程序不应分散我们的主要应用程序逻辑。
为了解决这个问题,我们可以设置受过教育的默认值,以避免在每个错误处理程序中传递某些细节。可以抽象各种普通配置,例如:
- 将错误上下文传递给APM(应用程序性能监视)工具
- 记录错误
- 设置在分析和监视过程中有助于帮助的自定义标志
- 将HTTP状态代码返回客户端
为此,我们可以创建一个扩展Error
并添加一些不错的默认值的自定义错误类。让我们看看一个:
const { sendError } = require("@appsignal/nodejs");
const { HTTP_STATUS_CODES } = require("../../constants");
class AppError extends Error {
constructor(message, statusCode, isOperational = true) {
super(message);
this.name = "AppError";
this.statusCode = statusCode || HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR;
this.isOperational = isOperational;
Error.captureStackTrace(this);
// send to APM
sendError(this);
}
}
现在,我们可以从技术上开始,将此自定义错误处理程序插入到处的代码中。它会起作用。但这不是必需的。我们将仅在控制器层中使用它。在服务/utils/helpers/etc。中发生的任何错误都将报告给控制器层的错误处理程序。
这利用event bubbling并避免冗余错误处理程序。此外,它有助于避免偶然出现错误堆栈痕迹。
node.js应用中的更好错误消息
虽然“某物出了问题”错误消息可能对您的最终用户足够(提示:不是),但它不能帮助开发人员从事您的应用程序。
作为开发人员,您需要一条错误消息,以至少给您根本原因(如果不绘制完整的图片)。同样,最终目标是使您的错误可行。
这是写错误消息的一些最佳实践:
- 使它们简洁
- 避免歧义
- 避免首字母缩写/语/技术术语
- 使它们成为人类可读
- 确保它们是可行的,并帮助开发人员找到根本原因
- 避免重复错误消息
良好错误消息的一些示例可能是:
- “实例没有SES:sendemail许可”。
- “无法建立数据库连接”。
- “无法上传文件,大小大于10 MB”。
处理编程错误
现在,我们可以借助我们新创建的自定义错误类跟踪各种操作错误。现在该处理编程错误了。
两个这样的错误是uncaughtException
和unhandledRejection
。
process.on("uncaughtException", (err) => {
logger.fatal(`uncaughtException error has occurred: ${err}`);
process.exit(1);
});
process.on("unhandledRejection", (err) => {
logger.fatal(`uncaughtRejection error has occurred: ${err}`);
process.exit(1);
});
这些处理程序确保即使没有操控性的错误也会被跟踪,并引起您急需的注意。这些错误必须以紧迫感(P0)采取行动,因为如果忽略了这些错误,您的服务可能会陷入出乎意料且较不可预测的状态。
如何在node.js中选择错误的日志级别
现在,我们的应用程序看起来更具弹性。本节是一个进修和一般指南,可帮助您为(错误)日志选择日志级别。每个日志级别都表示不同的目的。
痕迹
“我在这里”的日志。使用跟踪日志,您通常不想跟踪变量的状态,而是想知道特定请求的控制流。
调试
调试日志比跟踪日志更进一步:您热衷于跟踪变量的状态。例如,经过身份验证的用户详细信息,传递到服务的参数,数据库查询中设置的标志等等。
信息
这些日志是用于不需要立即采取行动或注意力的一般信息。它们不代表不必要的代码状态,因此在正常情况下可以忽略。信息日志的一些示例可以是成功初始化的装载机,成功的事件摄入和排水日志等。
警告
这些不是错误,而是不需要的状态。如果可以通过不适当的有效载荷导致请求失败,则可以使用警告日志级别。这是一个不必要的状态,但是一个预期的状态。例如,由于失败锁而引起的请求故障日志是预期的错误。
错误
与警告相反,错误日志表示意外的,不需要的代码状态。但是,如果发生这种错误,服务可以继续运行。例如,如果请求由于服务主机暂时无法到达而失败,则是错误日志。错误日志是可操作的。
致命的
这些将P0优先考虑。致命错误必须立即承认并采取行动,因为他们(通常)将服务降低。
但是,等等,我应该确切跟踪什么错误?
记录错误时有一个重要规则:仅包括基本信息。提供足够的背景很重要,它也是开发人员避免日志中噪声的责任。为什么?
每个日志都需要存储在磁盘上,并且它具有成本。处理日志需要磁盘IOPS,如果您的服务受到爆发或高持续负载,则很容易成为瓶颈。同样的IOPS配额也用于服务请求。
这是您应该跟踪的一些重要事情:
- 请求元数据
- 请求有效负载(如果太大,可以唯一标识有效载荷的钥匙)
- 在检查点上的成功/失败(在必要的上下文中)
- 最终回应
在需要时优雅地重新启动
在某些情况下,关键错误发生,没有其他选择,只需关闭并重新启动应用程序(例如,您的应用程序的内存越过其阈值)。
这样做的一种直接的(且潜在危险的方法)是直接process.exit(1)
并进行新的重新启动。但这可能会导致问题,如果您的存储层或任何其他状态组件处于尴尬境地。
更好的方法是通过关闭所有开路,优雅地重新启动。仅当每个组件准备就绪时才重新启动应用程序。
我们将在该过程中收听SIGTERM
和SIGINT
,并致电我们的实用程序以优雅地停止服务器:
process.on("SIGTERM", (signal) => loaders.close(server));
process.on("SIGINT", (signal) => loaders.close(server));
这是加载器模块:
module.exports = {
run: () => {
return new Promise((resolve, reject) => {
const mongoClient = mongoose.getConnection();
// other dependencies init goes here...
resolve({ mongoClient });
});
},
// runs before app crash/shutdown
close: async (server) => {
if (!server) {
logger.warn("Cannot stop server, it's not running");
process.exit(0);
}
// Fetching DB conn from loader as it is idempotent
const { mongoClient } = await module.exports.run();
server.close(async (err) => {
// closing connection(s)
await mongoClient.close();
if (err) process.exit(1);
process.exit(0);
});
},
};
根据您的特定用例,可以不退出该过程。另外,如果您的应用程序崩溃,则像PM2这样的过程经理允许automatic restarts。
通过appsignal从node.js错误中学习
从整体上查看您的错误可以提供更好的视角,并帮助您观察代码中的重复行为,否则可能不会引起您的注意。
AppSignal使错误跟踪主动且方便,您可以sign up for a 30-day free trial(无需信用卡详细信息)。
例如,我可以在此图上看到一群错误。我可以简单地缩小并检查这些错误是否与当天发布的发布 /供应商的停机 /其他任何可能帮助我解决根本原因并决定进一步措施的释放。< / p>
您甚至可以设置警报以通知您是否检测到任何异常:
AppSignal for Node.js还提供记录,主机和正常运行时间监控以及性能指标。
减少node.js中错误的最佳实践
跟踪和监视错误是好的,最好防止它们。
这是一些可以消除琐碎错误的最佳实践:
- 使用静态类型检查在编译阶段捕获琐碎错误(例如TypeError:无法将编号分配给字符串)。
- 在您的CI级别中添加预加入钩子和测试,以确保破坏构建不会熄灭。
- 负载测试您的服务,以观察您的应用程序在重负载下的行为。正常负载可能没有错误,但是您可以在爆发流量模式下观察到应用程序内存峰值。
- 定期审查并删除没有任何特定目的并添加噪声的日志。这也节省了磁盘空间,并在有限/小型IOPS的情况下有助于。
包起来
在本文中,我们首先定义了错误跟踪,并查看了不同类型的错误。然后,我们探索了如何以正确的方式跟踪您的node.js错误,包括创建自定义错误处理程序。我们还看到了如何避免跟踪微不足道的错误以减少警报中的噪音。
希望您学到了一些可以立即应用于项目的知识。您可能还会发现我们的Node.js Error Handling: Tips and Tricks帖子有用。
快乐编码!
P.S。如果您喜欢这篇文章,subscribe to our JavaScript Sorcery list每月深入研究更神奇的JavaScript技巧和技巧。
P.P.S。如果您需要node.js应用程序的APM,则GO和check out the AppSignal APM for Node.js。