在我的上一篇文章 “Beats:使用 Filebeat 从 Python 应用程序中提取日志” 里,我详述了如何使用 Python 来生成日志,并使用 Filebeat 来收集日志到 Elasticsearch 中。在今天的文章中,我来详细描述如何使用 Node.js 来生成 ECS 相兼容的日子。ECS 相兼容的日志符合易于 Elasticsearch 对日志进行分析并进行可视化。
介绍
Node.js ECS 记录器是你最喜欢的日志库的格式化插件。它们可轻松将你的日志格式化为与 ECS 兼容的 JSON。结合 filebeat,你可以将日志直接发送到 Elasticsearch,并利用 Kibana 的日志应用程序在一个地方检查所有日志。
Node.js ECS 日志格式化程序记录结构化的 JSON,并支持对来自 Node.js 核心和流行 Web 框架的错误对象和 HTTP 请求和响应对象进行序列化。最小日志记录包括以下字段:
{
"@timestamp": "2021-01-13T21:32:38.095Z",
"log.level": "info",
"message": "hi",
"ecs.version": "8.10.0"
}
提示:想要了解有关 ECS、ECS 日志记录和其他可用语言插件的更多信息?请参阅 ECS 日志记录指南。
有关如何生成 ECS 相兼容的日子,我们有如下的几种方法。
- Pino
- Winston
- Morgan
在今天的文章里,我们先来介绍 Pino。
使用 Pino 进行 ECS 日志记录
此 Node.js 包为 pino 记录器提供了格式化程序,与 Elastic Common Schema (ECS) 日志记录兼容。结合 Filebeat 发送器,你可以在 Elastic Stack 中的一处监控所有日志。支持 pino 6.x、7.x 和 8.x 版本。
设置
安装
npm install @elastic/ecs-pino-format
npm install pino
配置
const { ecsFormat } = require('@elastic/ecs-pino-format');
const pino = require('pino');
const log = pino(ecsFormat(/* options */)); // 1
log.info('hi');
log.error({ err: new Error('boom') }, 'oops there is a problem');
// ...
- 这将配置 Pino 的 formatters、messageKey 和 timestamp 选项。
配置 filebeat
1)Filebeat 7.16+。
filebeat.yml
filebeat.inputs:
- type: filestream # 1
paths: /path/to/logs.json
parsers:
- ndjson:
overwrite_keys: true # 2
add_error_key: true # 3
expand_keys: true # 4
processors:
- add_host_metadata: ~
- add_cloud_metadata: ~
- add_docker_metadata: ~
- add_kubernetes_metadata: ~
- 使用 filestream 输入从活动日志文件中读取行。
- 如果发生冲突,解码的 JSON 对象的值将覆盖 Filebeat 通常添加的字段(type, source, offset 等)。
- 如果发生 JSON 解组错误,Filebeat 将添加 “error.message” 和 “error.type: json” 键。
- Filebeat 将递归地从解码的 JSON 中去掉点键,并将其扩展为分层对象结构。
- 处理器可增强你的数据。请参阅 processors 以了解更多信息。
2)Filebeat < 7.16
filebeat.yml
filebeat.inputs:
- type: log
paths: /path/to/logs.json
json.keys_under_root: true
json.overwrite_keys: true
json.add_error_key: true
json.expand_keys: true
processors:
- add_host_metadata: ~
- add_cloud_metadata: ~
- add_docker_metadata: ~
- add_kubernetes_metadata: ~
有关更多信息,请参阅 Filebeat 参考。
更多关于如何配置 Filebeat,请参考文章 “Beats:使用 Filebeat 从 Python 应用程序中提取日志”。
如何使用
我们创建如下的一个简单的应用:
pino-logging.js
const { ecsFormat } = require('@elastic/ecs-pino-format');
const pino = require('pino');
const log = pino(ecsFormat(/* options */));
log.info('Hello world');
const child = log.child({ module: 'foo' });
child.warn('From child');
运行上面的代码:
node pino-logging.js
$ node pino-logging.js
{"log.level":"info","@timestamp":"2024-07-08T01:32:42.947Z","process.pid":43946,"host.hostname":"liuxgm.local","ecs.version":"8.10.0","message":"Hello world"}
{"log.level":"warn","@timestamp":"2024-07-08T01:32:42.947Z","process.pid":43946,"host.hostname":"liuxgm.local","ecs.version":"8.10.0","module":"foo","message":"From child"}
在配置 ecsFormat 时,我们在下面将会看到更多的选项。
Error logging
默认情况下,格式化程序会将 Error 实例的 err 字段转换为 ECS Error 字段。例如:
pino-logging.js
const { ecsFormat } = require('@elastic/ecs-pino-format');
const pino = require('pino');
const log = pino(ecsFormat());
const myErr = new Error('boom');
log.info({ err: myErr }, 'oops');
将产生(为了便于阅读,打印得很漂亮):
这类似于并覆盖了 Pino 的默认 err 序列化器。可以通过 convertErr: false 选项禁用对 err 字段的特殊处理:
const log = pino(ecsFormat({ convertErr: false }));
HTTP 请求和响应日志记录
使用 convertReqRes: true 选项,格式化程序将在分别作为 req 和 res 字段传递时自动转换 Node.js 核心 request 和 response 对象。(此选项取代了 req 和 res Pino 序列化器的使用。)
pino-logging.js
const http = require('http');
const { ecsFormat } = require('@elastic/ecs-pino-format');
const pino = require('pino');
const log = pino(ecsFormat({ convertReqRes: true })); // 1
const server = http.createServer(function handler (req, res) {
res.setHeader('Foo', 'Bar');
res.end('ok');
log.info({ req, res }, 'handled request'); // 2
});
server.listen(3000, () => {
log.info('listening at http://localhost:3000');
})
- 使用 convertReqRes 选项
- 使用 req 和/或 res 字段进行记录
这将使用 ECS HTTP 字段生成包含请求和响应信息的日志。例如:
为了生成上面的日子,我们需要访问本地的服务器 http://localhost:3000/.
examples/ 目录展示了使用请求和响应日志的示例程序:使用 Express、使用 pino-http 中间件包等。
使用 APM 进行日志关联
此 ECS 日志格式化程序与 Elastic APM 集成。如果你的 Node 应用正在使用 Node.js Elastic APM Agent,则会将多个字段添加到日志记录中,以关联 APM 服务或跟踪和日志数据:
- 当前跟踪 span 时调用的日志语句(例如 logger.info(...))将包括跟踪字段 — trace.id、transaction.id、span.id。
- 由 APM 代理确定或在 APM 代理上配置的多个服务标识符字段允许在 Kibana 中的服务和日志之间进行交叉链接 — service.name、service.version、service.environment、service.node.name。
- event.dataset 在 Elastic Observability 应用中启用日志率异常检测。
例如,运行 examples/http-with-elastic-apm.js 和 curl -i localhost:3000/ 会产生包含以下内容的日志记录:
% node examples/http-with-elastic-apm.js | jq .
...
"service.name": "http-with-elastic-apm",
"service.version": "1.4.0",
"service.environment": "development",
"event.dataset": "http-with-elastic-apm",
"trace.id": "9f338eae7211b7993b98929046aed21d",
"transaction.id": "2afbef5642cc7a3f",
...
这些 ID 与 APM 代理报告的跟踪数据相匹配。
可以通过 apmIntegration: false 选项明确禁用与 Elastic APM 的集成,例如:
const log = pino(ecsFormat({ apmIntegration: false }));
限制和注意事项
ecs-logging 规范建议日志记录中的前三个字段必须是 @timestamp、log.level 和 message。Pino 不提供将 message 字段放在前面的机制。鉴于 ecs-logging 字段的排序是为了便于阅读,并且不会影响互操作性,因此这并不是一个重大问题。
Pino 当前提供的钩子(hook)不允许此包转换传递给 <logger>.child({ ... }) 的字段。这意味着,即使使用 convertReqRes 选项,对 <logger>.child({ req }) 的调用也不会将该 req 转换为 ECS HTTP 字段。对于执行此操作的 pino-http 用户来说,这是一个轻微的限制。
参考
ecsFormat([options])
- options {type-object} 支持以下选项:
- convertErr {type-boolean} 是否将记录的 err 字段转换为 ECS 错误字段。默认值:true。
- convertReqRes {type-boolean} 是否将记录的 req 和 res HTTP 请求和响应字段记录到 ECS HTTP、用户代理和 URL 字段。默认值:false。
- apmIntegration {type-boolean} 是否启用 APM 代理集成。默认值:true。
- serviceName {type-string} “service.name” 值。如果指定,则覆盖来自活动 APM 代理的任何值。
- serviceVersion {type-string} “service.version” 值。如果指定,则覆盖来自活动 APM 代理的任何值。
- serviceEnvironment {type-string} “service.environment” 值。如果指定,则覆盖来自活动 APM 代理的任何值。
- serviceNodeName {type-string} “service.node.name” 值。如果指定,则将覆盖来自活动 APM 代理的任何值。
- eventDataset {type-string} “event.dataset”值。如果指定,则将覆盖使用 ${serviceVersion} 的默认设置。
为 pino(...) 创建选项,用于配置 ECS 日志记录格式输出。