使用 Pino 进行 ECS 日志记录

编辑

此 Node.js 包为 pino 日志记录器提供了一个格式化程序,该程序与 Elastic Common Schema (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 通常添加的字段(type、source、offset 等)。

如果出现 JSON 反序列化错误,Filebeat 将添加 "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"}

错误日志记录

编辑

默认情况下,格式化程序会将作为 Error 实例的 err 字段转换为 ECS Error 字段。 例如

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 选项,当分别作为 reqres 字段传递时,格式化程序将自动转换 Node.js 核心 requestresponse 对象。(此选项替换了 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 代理确定或配置的许多服务标识符字段允许 Kibana 中服务和日志之间的交叉链接 — service.nameservice.versionservice.environmentservice.node.name
  • event.dataset 启用 Elastic Observability 应用程序中的 日志速率异常检测

例如,运行 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 的用户来说,这是一个轻微的限制,因为它会这样做。

参考

编辑

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} 的默认值。

为配置 ECS 日志格式输出的 pino(...) 创建选项。