日志记录是每个开发人员从第一天编写代码时就要做的事情,但很少有人知道它可以产生的价值和最佳实践。
在本文中,我们将讨论以下主题:
日志是反映程序各个方面的事件,如果能够正确编写,那么它就是最简单的故障排除和诊断程序的模式。
当你启动 Node.js 服务器时,如果数据库由于某些问题而没有运行,或服务器端口已经被占用时,如果没有日志,你将永远不知道服务器失败的原因。
作为开发人员,你经常需要调试一些问题,我们很喜欢用调试器和断点来定位故障的位置和内容。
当你的程序在生产环境中运行时,你会做些什么?你能在那里附加调试器并重现 bug 吗?显然没有。因此,这是日志记录能够帮助你的地方。
在不使用调试器的情况下,你可以通过浏览日志找到问题并了解出现问题的原因和位置。
程序日志既适用于人类,也适用于机器。人类参考日志来调试问题,机器用日志生成各种图表,并通过数据分析来产生关于客户使用的各种结论。
每个日志都应包含三个最重要的部分:
日志源
当我们有一个微服务架构时,这对于了解日志的来源、服务名称、区域、主机名等信息非常重要(有关管理微服务中的公共代码的更多信息请在此处阅读)
有关源的详细元数据主要由日志 agent 进行处理,日志 agent 将日志从所有微服务推送到集中式日志系统。 ELK 栈的 Filebeat 是日志 agent 的最佳选择之一。
时间戳
事件发生或生成日志的时间非常重要。所以要确保每个日志都有时间戳,以便我们进行排序和筛选。
级别和上下文
在通过查看日志查找错误时,如果日志没有提供足够的信息,你就必须回到代码中,那将非常令人沮丧。因此在记录时我们应该传递足够的上下文
例如。没有上下文的日志将如下所示:
The operation failed!
有意义的上下文应该是是:
Failed to create user, as the user id already exist
日志方法和输入:
在调试的同时,如果我们知道调用了哪个函数以及传递了哪些参数,它就能发挥真正的作用。
import logger from '../logSetup';
getInstallment(month: number, count: number ): number {
logger.debug(`>>>> Entering getInstallment(month = ${month}, count= ${count}");
// process
const installment: number = 3;
log.debug("<<<< Exiting getIntallment()");
return installment;
}
通过日志 >>>> 和 <<<< 将给出函数输入和退出的信息。这是受到了 git merge 冲突的启发。
日志不应该评估抛出异常
在第7行中,userService.getUser() 可以返回 null,且 .getId() 可以抛出异常,所以要避免这些情况。
import logger from '../logSetup';
processLoan(...) {
logger.debug(">>>> Entering processLoan()");
// ... process
logger.debug(`Processing user loan with id ${userService.getUser().getId()}`);
// this might throw error, when getUser returns undefined
logger.debug("<<<< Exiting processLoan()");
return true;
}
你应该用 Aspect js 自动执行函数级日志。
日志不应产生副作用
日志应该是无状态的,不应产生任何副作用。例如,下面第 7 行的日志将在数据库中创建新资源。
import logger from '../logSetup';
createUser() {
logger.debug(">>>> Entering createUser");
// ... process
logger.debug("Saving user loan {}", userInfoRepository.save(userInfo)) // don't do this
return true;
}
当描述错误时,请提及尝试的内容及其失败的原因。
记录哪些是失败的和你接下来做什么。
import logger from '../logSetup';
processLoan(id: number, userId: number) {
try {
getLoanDeatilsById()
} catch(error) {
log.error(`Failed to do getLoanDetails with id ${id}, ignoring it and trying to getLoanDetailsByUserId`, error);
// good example: provide what failed, and how you are handling.
// e.g here on fail I am trying to call other function
getLoanDetailsByUserId();
}
}
如果你在 catch 部分中丢弃错误,请记录哪个操作失败并提及你正在抛出错误。
import logger from '../logSetup';
processLoan(id: number, userId: number) {
try {
getLoanDeatilsById()
} catch(error) {
log.error(`Failed to do getLoanDetails with id ${id} hence throwing error`, error);
// good example: provide what failed, and how you are handling.
// e.g here on fail I am throwing
throw error;
}
}
该系列日志应该反映用户在程序中的活动以便调试更容易,并且应该记录错误以便尽快采取措施。日志包含一些信息,例如调用哪些函数,输入的内容,发生的位置和错误等。
记录时我们必须确保不去记录用户名和密码等敏感信息,例如信用卡号、CVV 号码等财务信息。
作为开发人员,我们应该通过与产品团队沟通,来准备敏感信息的列表并在记录之前将其屏蔽。
如果生产环境下的程序具有相当多的用户事务,那么理想的日志设置可能每天会生成 GB 级别的日志,因此我们需要将日志分组为多个组。根据受众,我们可以在运行时切换日志级别,并仅获取适当的日志。
例如,如果产品经理希望在我们的日志记录仪表板中查看有多少客户交易成功或失败,则不应向他展示各种功能调用的杂乱信息,这些信息仅供开发人员使用。当生产环境中存在错误时,开发人员应该看到各种函数成功执行和失败的详细日志。这样就可以尽快发现并修复问题。
要实现这种设置,我们需要更好地了解每个日志级别。
让我们讨论最重要的级别及其用法:
这表示仅记录进度信息。
主要受众是系统操作员或监控系统。
理想情况下,生产环境下的程序应该具有接近零的错误日志。
大多数开发人员使用控制台模块作为获取日志或调试代码的第一个工具,因为它简单容易且全局可用,无需设置。在 Node.Js 中,控制台的实现方式与浏览器不同,控制台模块在使用 console.log 时会在 stdout 中打印消息,如果使用 console.error 它将打印到 stderr。
console.log、console.debug 和 console.info 都在 stdout 中打印,因此我们将无法关闭或打开调试和及信息。同样,`console.warn 和 console.error 都在 stderr 中打印。
生产环境程序很难切换各种级别。
我们还需要不同类型的配置,如标准格式、把JSON 输出格式发送到 ELK 栈,这些在开箱即用的控制台中不可用。
要克服所有这些问题,可以使用 Winston 日志框架,还有其他一些选项,如Bunyan,Pino等。
在上一节中我们讨论了控制台的一些缺陷,让我们列出 Winston 提供的一些重要功能:
如果需要,你也可以创建自定义级别。
// log setup
import winston from 'winston';
const transports = {
console: new winston.transports.Console({ level: 'warn' }),
};
const logger = winston.createLogger({
transports: [transports.console, transports.file]
});
logger.info('This will not be logged in console transport because warn is set!');
transports.console.level = 'info'; // changed the level
logger.info('This will be logged in now!');
export default {logger, transport}
我们还可以公开 api 动态更改级别,公开 REST API 并在处理程序中执行第 13 行以更改级别。
import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
//
new winston.transports.File({ filename: 'stdout.log' })
]
});
export default logger;
通过配置 Winston 将我们的日志写入文件,以便任何日志托运代理都可以将日志推送到集中式系统。但是,这超出了本文的范围,我们会在另一篇文章中详细讨论。
如果程序写日志的频率很高,则可能直接影响程序性能。
DEBUG 和 INFO 级别的日志可占到整体的 95% 以上,这就是为什么应该只启用 ERROR 和 WARN 级别,并在想要找出问题时将级别更改为DEBUG,之后再将其切换回 ERROR 。
当应用程序出现问题时,日志就是救星。如果你当前还没有很好的使用日志,请实施日志记录实践并将日志添加到代码审查核对表中。
作者:Mahesh Haldar
翻译:疯狂的技术宅
原文:https://blog.bitsrc.io/logging-best-practices-for-node-js-applications-8a0a5969b94c
在前端应用越来复杂的今天,为了监控前端应用是否正常运行,通常会在前端收集一些错误与性能等数据,最终我们会将这些数据上报到服务端。上报的方式有很多,理论上我们只要能把数据发给服务端就行了
如果想统计网站的访问来源信息,可以用 php 获取信息,记录到数据库的形式,也可以直接使用 nginx 提供的访问日志,来记录网站的访问详情,管理员可以通过分析 nginx 的访问日志,来分析用户的访问来源,访问行为详情
随着程序的增长,日志记录成为跟踪所有内容的关键部分。它对于调试目的尤为重要。现在已经有了 npm 的日志记录模块。这些模块可以将日志存储在不同格式或级别的文件中。我们将使用流行的ORM Mongoose
平常都使用console来打印 node 脚本执行时需要看到的信息,但这些信息也就只能在控制台查看。假如你希望将打印的信息记录到文件查看的话,那就往下看看吧。使用 node.js 对日志进行存储,就一定会对本地文件的增删改查,那么我们需要用到fs。
通常我们在写Node.js程序时,都习惯使用console.log打印日志信息,但这也仅限于控制台输出,有时候我们需要将信息输出到日志文件中,实际上利用console也可以达到这个目的的,今天就来简单介绍一下。
在项目中,我们会频繁用到 console.log() 来输出一些关键信息到控制台中,有助于开发调试,以及问题的排查,待项目上线后,这些调试日志又得及时清除。
nodeJs 自己的log 输出一堆讯息,要从这么多又杂的讯息中找出问题,是件非常辛苦的事情。log4js 是一款基于Node 环境下较好用的log 模组,本篇简单介绍log4js 的使用方式。
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!