使用 Pino 进行 ECS 日志记录编辑

此 Node.js 包为 pino 日志记录器提供了一种格式化程序,与 Elastic 通用模式 (ECS) 日志记录 兼容。结合 Filebeat 发货器,您可以在 Elastic Stack 中的同一个位置 监控所有日志。 支持 pino 6.x、7.x 和 8.x 版本。

设置编辑

步骤 1:安装编辑

$ npm install @elastic/ecs-pino-format

步骤 2:配置编辑

const { ecsFormat } = require('@elastic/ecs-pino-format');
const pino = require('pino');

const log = pino(ecsFormat(/* options */)); 
log.info('hi');
log.error({ err: new Error('boom') }, 'oops there is a problem');
// ...

这将 配置 Pino 的 formattersmessageKeytimestamp 选项。

请参见下面的用法讨论和示例。

步骤 3:配置 Filebeat编辑

将日志格式化为 ECS 后,使用 Filebeat 收集日志的最佳方法是

  1. 按照 Filebeat 快速入门
  2. 将以下配置添加到您的 filebeat.yaml 文件中。

对于 Filebeat 7.16+

filebeat.yaml。

filebeat.inputs:
- type: filestream 
  paths: /path/to/logs.json
  parsers:
    - ndjson:
      overwrite_keys: true 
      add_error_key: true 
      expand_keys: true 

processors: 
  - add_host_metadata: ~
  - add_cloud_metadata: ~
  - add_docker_metadata: ~
  - add_kubernetes_metadata: ~

使用 filestream 输入从活动日志文件中读取行。

如果发生冲突,来自解码 JSON 对象的值将覆盖 Filebeat 通常添加的字段(类型、源、偏移量等)。

Filebeat 在出现 JSON 反序列化错误的情况下会添加 "error.message" 和 "error.type: json" 密钥。

Filebeat 会递归地去除解码 JSON 中的点号密钥,并将它们扩展成一个分层对象结构。

处理器会增强您的数据。请参见 处理器 以了解更多信息。

对于 Filebeat < 7.16

filebeat.yaml。

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 参考

用法编辑

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');

请参见可用的选项 以下

运行此命令将生成类似于以下内容的日志输出

{"log.level":"info","@timestamp":"2023-10-14T02:07:47.901Z","process.pid":56645,"host.hostname":"pink.local","ecs.version":"8.10.0","message":"Hello world"}
{"log.level":"warn","@timestamp":"2023-10-14T02:07:47.901Z","process.pid":56645,"host.hostname":"pink.local","ecs.version":"8.10.0","module":"foo","message":"From child"}

错误日志记录编辑

默认情况下,格式化程序会将一个 err 字段(该字段是一个 Error 实例)转换为 ECS 错误字段。 例如

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');

将产生(为了可读性而进行了美化)

% node examples/error.js | jq .
{
  "log.level": "info",
  "@timestamp": "2021-01-26T17:02:23.697Z",
  ...
  "error": {
    "type": "Error",
    "message": "boom",
    "stack_trace": "Error: boom\n    at Object.<anonymous> (..."
  },
  "message": "oops"
}

这类似于并覆盖了 Pino 的默认 err 序列化程序。可以通过 convertErr: false 选项禁用 err 字段的特殊处理

const log = pino(ecsFormat({ convertErr: false }));

HTTP 请求和响应日志记录编辑

使用 convertReqRes: true 选项,格式化程序会自动转换 Node.js 核心 请求响应 对象(分别作为 reqres 字段传入)。(此选项替换了使用 reqres Pino 序列化程序。)

const http = require('http');
const { ecsFormat } = require('@elastic/ecs-pino-format');
const pino = require('pino');

const log = pino(ecsFormat({ convertReqRes: true })); 

const server = http.createServer(function handler (req, res) {
  res.setHeader('Foo', 'Bar');
  res.end('ok');
  log.info({ req, res }, 'handled request'); 
});

server.listen(3000, () => {
  log.info('listening at https://127.0.0.1:3000');
}

使用 convertReqRes 选项

使用 req 和/或 res 字段进行日志记录

这将使用 ECS HTTP 字段 生成包含请求和响应信息的日志。 例如

% node examples/http.js | jq .    # using jq for pretty printing
...                               # run 'curl https://127.0.0.1:3000/'
{
  "log.level": "info",
  "@timestamp": "2023-10-14T02:10:14.477Z",
  "process.pid": 56697,
  "host.hostname": "pink.local",
  "ecs.version": "8.10.0",
  "http": {
    "version": "1.1",
    "request": {
      "method": "GET",
      "headers": {
        "host": "localhost:3000",
        "user-agent": "curl/8.1.2",
        "accept": "*/*"
      }
    },
    "response": {
      "status_code": 200,
      "headers": {
        "foo": "Bar"
      }
    }
  },
  "url": {
    "full": "https://127.0.0.1:3000/",
    "path": "/"
  },
  "client": {
    "address": "::ffff:127.0.0.1",
    "ip": "::ffff:127.0.0.1",
    "port": 49504
  },
  "user_agent": {
    "original": "curl/8.1.2"
  },
  "message": "handled request"
}

examples/ 目录 显示了使用请求和响应日志记录的示例程序:使用 Express使用 pino-http 中间件包 等。

与 APM 进行日志关联编辑

此 ECS 日志格式化程序与 Elastic APM 集成。如果您的 Node 应用程序正在使用 Node.js Elastic APM 代理,那么会在日志记录中添加许多字段,以在 APM 服务或跟踪与日志记录数据之间进行关联

  • 在当前跟踪跨度存在的情况下调用的日志语句(例如 logger.info(...))将包含 跟踪字段trace.idtransaction.idspan.id
  • 由 APM 代理确定或在 APM 代理上配置的许多服务标识符字段允许在 Kibana 中的跨服务和日志之间建立交叉链接 — service.nameservice.versionservice.environmentservice.node.name
  • event.dataset 允许在 Elastic 可观测性应用程序中进行 日志速率异常检测

例如,运行 examples/http-with-elastic-apm.jscurl -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 规范 建议日志记录中的前三个字段必须是 @timestamplog.levelmessage。 Pino 没有提供将 message 字段置于前部的机制。 鉴于 ecs-logging 字段的排序是为了 人类可读性 并且不会影响互操作性,因此这并非重大的问题。

Pino 当前提供的挂钩不允许此包转换传递给 <logger>.child({ ... }) 的字段。 这意味着,即使使用 convertReqRes 选项,对 <logger>.child({ req }) 的调用也不会 req 转换为 ECS HTTP 字段。 对于使用 pino-http 的用户来说,这是一个小限制,而 pino-http 会执行此操作。

参考编辑

ecsFormat([options])编辑

  • options {type-object} 支持以下选项

    • convertErr {type-boolean} 是否将记录的 err 字段转换为 ECS 错误字段。 默认值: true
    • convertReqRes {type-boolean} 是否将记录的 reqres 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 日志记录格式输出的选项。