日志记录
启用日志记录
默认情况下,日志记录是禁用的。通过在创建 Fastify 实例时传递 { logger: true }
或 { logger: { level: 'info' } }
来启用它。请注意,如果日志记录被禁用,则无法在运行时重新启用。
为了实现这一目的使用了 abstract-logging 包。
由于 Fastify 专注于性能,因此它使用 pino 作为日志记录器,并且默认的日志级别设置为 'info'
(如果启用的话)。
基本日志配置
启用生产 JSON 日志记录:
const fastify = require('fastify')({
logger: true
})
环境特定配置
为了在本地开发、生产和测试环境中使用适当的日志记录配置,需要进行更多的配置:
const envToLogger = {
development: {
transport: {
target: 'pino-pretty',
options: {
translateTime: 'HH:MM:ss Z',
ignore: 'pid,hostname',
},
},
},
production: true,
test: false,
}
const fastify = require('fastify')({
logger: envToLogger[environment] ?? true // 如果映射中没有匹配项,则默认为 true
})
⚠️ pino-pretty
需要作为开发依赖项安装。出于性能原因,默认情况下不会包含它。
使用方法
日志记录器可以在路由处理程序中如下使用:
fastify.get('/', options, function (request, reply) {
request.log.info('关于当前请求的一些信息')
reply.send({ hello: 'world' })
})
在路由处理程序之外触发新的日志,可以使用 Fastify 实例中的 Pino 实例:
fastify.log.info('发生了重要的事情!');
传递日志记录选项
要向日志记录器传递选项,请将它们提供给 Fastify。有关可用选项的详细信息,请参阅 Pino 文档 。
要指定文件目标路径,可以使用:
const fastify = require('fastify')({
logger: {
level: 'info',
file: '/path/to/file' // 将使用 pino.destination()
}
})
fastify.get('/', options, function (request, reply) {
request.log.info('关于当前请求的一些信息')
reply.send({ hello: 'world' })
})
要向 Pino 实例传递自定义流,请在日志对象中添加 stream
字段:
const split = require('split2')
const stream = split(JSON.parse)
const fastify = require('fastify')({
logger: {
level: 'info',
stream: stream
}
})
高级日志配置
请求ID跟踪
默认情况下,Fastify 为每个请求添加一个 ID 以方便追踪。如果设置了 requestIdHeader
选项并且相应的头部存在,则使用该头部的值;否则生成一个新的增量 ID。有关自定义选项,请参阅 Fastify 工厂配置 requestIdHeader
和 Fastify 工厂配置 genReqId
。
序列化器
默认的日志记录器使用标准序列化器来处理具有 req
、res
和 err
属性的对象。req
对象是 Fastify 的 Request
对象,而 res
对象是 Fastify 的 Reply
对象。可以通过自定义序列化器来自定义此行为。
const fastify = require('fastify')({
logger: {
serializers: {
req (request) {
return { url: request.url }
}
}
}
})
例如,可以使用以下方法记录响应负载和头信息(不推荐):
const fastify = require('fastify')({
logger: {
transport: {
target: 'pino-pretty'
},
serializers: {
res (reply) {
// 默认行为
return {
statusCode: reply.statusCode
}
},
req (request) {
return {
method: request.method,
url: request.url,
path: request.routeOptions.url,
parameters: request.params,
// 在日志中包含头信息可能会违反隐私法律,例如 GDPR。使用 "redact" 选项来移除敏感字段。
headers: request.headers
};
}
}
}
});
ℹ️ 注意:在某些情况下,传递给
res
序列化器的Reply
对象可能无法完全构建。编写自定义res
序列化器时,请检查除始终存在的statusCode
属性之外的所有属性是否存在。例如,在调用之前验证getHeaders
的存在:
const fastify = require('fastify')({
logger: {
transport: {
target: 'pino-pretty'
},
serializers: {
res (reply) {
// 默认行为
return {
statusCode: reply.statusCode,
headers: typeof reply.getHeaders === 'function'
? reply.getHeaders()
: {}
}
},
}
}
});
ℹ️ 注意:
req
方法内无法序列化请求体,因为当子日志器创建时会序列化请求。此时,请求体尚未解析。
要记录 req.body
可以使用以下方法:
app.addHook('preHandler', function (req, reply, done) {
if (req.body) {
req.log.info({ body: req.body }, '已解析的请求体')
}
done()
})
ℹ️ 注意:确保序列化器永远不会抛出错误,因为这会导致 Node 进程退出。更多相关信息请参阅 Pino 文档 。
任何其他日志器(除了 Pino)都会忽略此选项。
使用自定义日志记录器
可以通过传递 loggerInstance
参数来提供一个自定义的日志实例。该日志必须符合 Pino 接口,包含以下方法:info
, error
, debug
, fatal
, warn
, trace
, silent
, child
和一个字符串属性 level
。
示例:
const log = require('pino')({ level: 'info' })
const fastify = require('fastify')({ loggerInstance: log })
log.info('不包含请求信息')
fastify.get('/', function (request, reply) {
request.log.info('包含请求信息,但与 `log` 是同一个日志实例')
reply.send({ hello: 'world' })
})
当前请求的日志实例在生命周期的每个部分都可用。
日志屏蔽
Pino 支持低开销的日志屏蔽功能,用于隐藏记录日志中特定属性的值。例如,为了安全起见,可以记录所有 HTTP 头信息,但不包括 Authorization
头:
const fastify = Fastify({
logger: {
stream: stream,
redact: ['req.headers.authorization'],
level: 'info',
serializers: {
req (request) {
return {
method: request.method,
url: request.url,
headers: request.headers,
host: request.host,
remoteAddress: request.ip,
remotePort: request.socket.remotePort
}
}
}
}
})