是 「山月七八月原创计划」 中的「第四篇」文章,简述了在 Node 服务中如何打日志
写文章实在太耗时了,把我以前项目的代码片段都给翻了出来,还要处理掉敏感及无关的代码,好在离零点还有一个小时终于弄完了,如果有问题欢迎讨论
服务器应用(后端项目)中,完善并结构化的日志不仅可以更好地帮助定位问题及复现,也能够发现性能问题的端倪,甚至能够帮忙用来解决线上 CPU 及内存爆掉的问题。
本篇文章将讲解如何使用 Node 在服务端更好地打日志
产生日志后,将在下一章讲解日志的收集处理及检索
目录
日志类型
日志的基本字段
app
serverName
timestamp
requestId/traceId
userId
Node 中如何打日志: winston
日志结构化
npm scripts: 优化本地日志及筛选
请求日志: AccessLog
数据库日志: SQLLog
Redis日志: RedisLog
总结
在一个服务器应用中,或作为生产者,或作为消费者,需要与各方数据进行交互。除了最常见的与客户端交互外,还有数据库、缓存、消息队列、第三方服务。对于重要的数据交互需要打日志记录。
除了外界交互外,自身产生的异常信息、关键业务逻辑及定时任务信息,也需要打日志。
以下简述需要打日志的类型及涉及字段
AccessLog
: 这是最常见的日志类型,一般在 nginx
等方向代理中也有日志记录,但在业务系统中有时需要更详细的日志记录,如 API 耗时,详细的 request body 与 response bodySQLLog
: 关于数据库查询的日志,记录 SQL、涉及到的 table、以及执行时间,「从此可以筛选出执行过慢的SQL,也可以筛选出某条API对应的SQL条数」RequestLog
: 请求第三方服务产生的日志Exception
: 异常RedisLog
: 缓存,也有一些非缓存的操作如 zset
及分布式锁等Message Queue Log
: 记录生产消息及消费消息的日志CronLog
: 记录定时任务执行的时间以及是否成功对于所有的日志,都会有一些共用的基本字段,如在那台服务器,在那个点产生的日志
「即当前项目的命名」,在生产环境有可能多个项目的日志聚合在一起,通过 app
容易定位到当前项目
「即服务器的 hostname
」,通过它很容易定位到出问题的服务器/容器。
现已有相当多公司的生产环境应用使用 kubernetes
进行编排,而在 k8s
中每个 POD 的 hostname
如下所示,因此很容易定位到
Deployment
: 哪一个应用/项目ReplicaSet
: 哪一次上线Pod
: 哪一个 Pod# shanyue-production 指 Deployment name
# 69d9884864 指某次升级时 ReplicaSet 对应的 hash
# vt22t 指某个 Pod 对应的 hash
$ hostname
shanyue-production-69d9884864-vt22t
「即该条日志产生的时间」,使用 ISO 8601
格式有更好的人可读性与机器可读性
{
"timestamp": "2020-04-24T04:50:57.651Z",
}
「及全链路式日志中的唯一id」,通过 requestId
,可以把相关的微服务同一条日志链接起来、包括前端、后端、上游微服务、数据库及 redis
全链路式日志平台可以更好地分析一条请求在各个微服务的生命周期,目前流行的有以下几种,以下使他们的官网介绍
「即用户信息」,当然有的服务可能没有用户信息,这个要视后端服务的性质而定。当用户未登录时,以 -1 替代,方便索引。
{
"userId": 10086,
// 当用户在未状态时,以 -1 替代
"userId": -1,
}
winston[3] 是 Node 中最为流行的日志工具,支持各种各样的 Transport
,能够让你定义各种存储位置及日志格式
当然还有其它可选的方案:如 []
{
defaultMeta: {
app: 'shici-service',
serverName: os.hostname(),
label
}
}
import winston, { format } from 'winston'
import os from 'os'
import { session } from './session'
const requestId = format((info) => {
// 关于 CLS 中的 requestId
info.requestId = session.get('requestId')
return info
})
function createLogger (label: string) {
return winston.createLogger({
defaultMeta: {
serverName: os.hostname(),
label
},
format: format.combine(
// 打印时间戳
format.timestamp(),
// 打印 requestId
requestId(),
// 以 json 格式进行打印
format.json()
),
transports: [
// 存储在文件中
new winston.transports.File({
dirname: './logs',
filename: `${label}.log`,
})
]
})
}
const accessLogger = createLogger('access')
结构化的日志方便索引,而 JSON 是最容易被解析的格式,因此生产环境日志常被打印为 JSON 格式。
「那其它格式可以吗,可以,就是解析有点麻烦。当然 JSON 也有缺点,即数据冗余太多,会造成带宽的浪费。」
http {
include mime.types;
default_type application/octet-stream;
json_log_fields main 'remote_addr'
'remote_user'
'request'
'time_local'
'status'
'body_bytes_sent'
'http_user_agent'
'http_x_forwarded_for';
}
在 morgan
中可以优化日志的可读性并打印在终端
morgan(':method :url :status :res[content-length] - :response-time ms')
而以上无论生产环境还是测试环境本地环境,都使用了 json
格式,并输出到了文件中,此时的可读性是不很差?
别急,这里用 npm scripts
处理一下,不仅有更好的可读性,而且更加灵活
{
"log": "tail -f logs/api-$(date +'%Y-%m-%d').log | jq",
"log:db": "tail -f logs/db-$(date +'%Y-%m-%d').log | jq"
}
通过命令行 tail
及 jq
,做一个更棒的可视化。jq
是一款 json
处理的命令行工具,需提前下载
$ brew install jq
打印后的请求日志
因为打印日志是基于 jq
的,因此你也可以写 jq script
对日志进行筛选
$ npm run log '. | { message, req}'
只打印部分字段
「AccessLog
几乎是一个后端项目中最重要的日志」,在传统 Node 项目中常用 morgan[4],但是它对机器读并不是很友好。
以下是基于 koa
的日志中间件:
duration
字段记录该响应的执行时间body
及 query
需要做序列化(stringify)处理,「避免在 EliticSearch
或一些日志平台中索引过多及错乱」User
及一些业务相关联的数据// 创建一个 access 的 log,并存储在 ./logs/access.log 中
const accessLogger = createLogger('access')
app.use(async (ctx, next) => {
if (
// 如果是 Options 及健康检查或不重要 API,则跳过日志
ctx.req.method === 'OPTIONS' ||
_.includes(['/healthCheck', '/otherApi'], ctx.req.url)
) {
await next()
} else {
const now = Date.now()
const msg = `${ctx.req.method} ${ctx.req.url}`
await next()
apiLogger.info(msg, {
req: {
..._.pick(ctx.request, ['url', 'method', 'httpVersion', 'length']),
// body/query 进行序列化,避免索引过多
body: JSON.stringify(ctx.request.body),
query: JSON.stringify(ctx.request.query)
},
res: _.pick(ctx.response, ['status']),
// 用户信息
userId: ctx.user.id || -1,
// 一些重要的业务相关信息
businessId: ctx.business.id || -1,
duration: Date.now() - now
})
}
})
对于流行的服务器框架而言,操作数据库一般使用 ORM 操作,对于 Node,这里选择 sequelize
以下是基于 sequelize
的数据库日志及代码解释:
requestId
查得每条 API 对应的查库次数,方便定位性能问题duration
字段记录该查询的执行时间,可过滤 1s 以上数据库操作,方便发现性能问题tableNames
字段记录该查询涉及的表,方便发现性能问题// 创建一个 access 的 log,并存储在 ./logs/sql.log 中
const sqlLogger = createLogger('sql')
// 绑定 Continues LocalStorage
Sequelize.useCLS(session)
const sequelize = new Sequelize({
...options,
benchmark: true,
logging (msg, duration, context) {
sqlLogger.info(msg, {
// 记录涉及到的 table 与 type
...__.pick(context, ['tableNames', 'type']),
// 记录SQL执行的时间
duration
})
},
})
redis
日志一般来说不是很重要,如果有必要也可以记录。
如果使用 ioredis
作为 redis 操作库,可侵入 Redis.prototype.sendCommand
来打印日志
import Redis from 'ioredis'
import { redisLogger } from './logger'
const redis = new Redis()
const { sendCommand } = Redis.prototype
Redis.prototype.sendCommand = async function (...options: any[]) {
const response = await sendCommand.call(this, ...options);
// 记录查询日志
redisLogger.info(options[0].name, {
...options[0],
// 关于结果,可考虑不打印,有时数据可能过大
response
})
return response
}
export { redis }
在一个后端项目中,以下类型需要打日志记录,本篇文章介绍了如何使用 Node 来做这些处理并附有代码
AccessLog
: 这是最常见的日志类型,一般在 nginx
等方向代理中也有日志记录,但在业务系统中有时需要更详细的日志记录,如 API 耗时,详细的 request body 与 response bodySQLLog
: 关于数据库查询的日志,记录 SQL、涉及到的 table、以及执行时间,「从此可以筛选出执行过慢的SQL,也可以筛选出某条API对应的SQL条数」RequestLog
: 请求第三方服务产生的日志Exception
: 异常RedisLog
: 缓存,也有一些非缓存的操作如 zset
及分布式锁等Message Queue Log
: 记录生产消息及消费消息的日志CronLog
: 记录定时任务执行的时间以及是否成功本文由哈喽比特于4年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/PhaKErlv65eE4YxEXCeFGw
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。