使用 Winston 进行 ECS 日志记录

编辑

这个 Node.js 包为 winston 日志记录器提供了一个格式化器,兼容 Elastic Common Schema (ECS) 日志记录。结合 Filebeat 托运器,您可以在 Elastic Stack 中的一个位置 监视所有日志。支持 winston 3.x 版本 >=3.3.3。

设置

编辑

步骤 1:安装

编辑
$ npm install @elastic/ecs-winston-format

步骤 2:配置

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

const logger = winston.createLogger({
  format: ecsFormat(/* options */), 
  transports: [
    new winston.transports.Console()
  ]
});

logger.info('hi');
logger.error('oops there is a problem', { err: new Error('boom') });

在此处将 ECS 格式化器传递给 winston。

步骤 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 通常添加的字段(类型、源、偏移量等)。

如果发生 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 参考

您可能想尝试使用 Winston 进行 Node.js ECS 日志记录的教程:使用 Filebeat 从 Node.js Web 应用程序摄取日志

用法

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

const logger = winston.createLogger({
  level: 'info',
  format: ecsFormat(/* options */), 
  transports: [
    new winston.transports.Console()
  ]
});

logger.info('hi');
logger.error('oops there is a problem', { foo: 'bar' });

请参阅 下方的可用选项。

运行此脚本(可在此处找到 此处)将产生类似于以下的日志输出

% node examples/basic.js
{"@timestamp":"2023-10-14T02:14:17.302Z","log.level":"info","message":"hi","ecs.version":"8.10.0"}
{"@timestamp":"2023-10-14T02:14:17.304Z","log.level":"error","message":"oops there is a problem","ecs.version":"8.10.0","foo":"bar"}

格式化器处理到 JSON 的序列化,因此您无需添加 json 格式化器。同样,时间戳由格式化器自动生成,因此您无需添加 timestamp 格式化器。

错误日志记录

编辑

默认情况下,格式化器会将作为 Error 实例的 err 元字段转换为 ECS 错误字段。例如,请参见 示例

const winston = require('winston');
const { ecsFormat } = require('@elastic/ecs-winston-format');
const logger = winston.createLogger({
  format: ecsFormat(), 
  transports: [
    new winston.transports.Console()
  ]
});

const myErr = new Error('boom');
logger.info('oops', { err: myErr }); 

将产生(为了可读性而进行漂亮打印)

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

可以通过 convertErr: false 选项禁用 err 元字段的特殊处理

...
const logger = winston.createLogger({
  format: ecsFormat({ convertErr: false }),
...

HTTP 请求和响应日志记录

编辑

使用 convertReqRes: true 选项,当分别作为 reqres 元字段传递时,格式化器会自动转换 Node.js 核心 requestresponse 对象。

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

const logger = winston.createLogger({
  level: 'info',
  format: ecsFormat({ convertReqRes: true }), 
  transports: [
    new winston.transports.Console()
  ]
});

const server = http.createServer(handler);
server.listen(3000, () => {
  logger.info('listening at https://127.0.0.1:3000')
});

function handler (req, res) {
  res.setHeader('Foo', 'Bar');
  res.end('ok');
  logger.info('handled request', { req, res }); 
}

使用 convertReqRes 选项

记录 req 和/或 res 元字段

这将使用 ECS HTTP 字段生成带有请求和响应信息的日志。例如,请参见 示例

% node examples/http.js | jq .    # using jq for pretty printing
...                               # run 'curl https://127.0.0.1:3000/'
{
  "@timestamp": "2023-10-14T02:15:54.768Z",
  "log.level": "info",
  "message": "handled request",
  "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": {
    "path": "/",
    "full": "https://127.0.0.1:3000/"
  },
  "client": {
    "address": "::ffff:127.0.0.1",
    "ip": "::ffff:127.0.0.1",
    "port": 49538
  },
  "user_agent": {
    "original": "curl/8.1.2"
  },
  "ecs.version": "8.10.0"
}

与 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": "7fd75f0f33ff49aba85d060b46dcad7e",
  "transaction.id": "6c97c7c1b468fa05"
}

这些 ID 与 APM 代理报告的跟踪数据匹配。

可以通过 apmIntegration: false 选项显式禁用与 Elastic APM 的集成,例如

const logger = winston.createLogger({
  format: ecsFormat({ apmIntegration: false }),
  // ...
})

限制和注意事项

编辑

ecs-logging 规范建议日志记录中的前三个字段应为 @timestamplog.levelmessage。从 1.5.0 版本开始,此格式化器遵循此建议。这并非不可能,但需要在每个日志记录的 ecsFields 中创建一个新的 Object。鉴于 ecs-logging 字段的排序是为了人类可读性,并不影响互操作性,因此决定优先考虑性能。

参考

编辑

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

为 winston 创建一个以 ECS 日志格式发出的格式化器。这是一个单一的格式,可以同时处理 ecsFields([options])ecsStringify([options])。以下两者是等效的

const { ecsFormat, ecsFields, ecsStringify } = require('@elastic/ecs-winston-format');
const winston = require('winston');

const logger = winston.createLogger({
  format: ecsFormat(/* options */),
  // ...
});

const logger = winston.createLogger({
  format: winston.format.combine(
    ecsFields(/* options */),
    ecsStringify()
  ),
  // ...
});

ecsFields([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} 的值。

为 winston 创建一个格式化器,该格式化器将日志记录 info 对象上的字段转换为 ECS 日志格式。

ecsStringify([options])

编辑

为 winston 创建一个格式化器,该格式化器将日志记录字符串化/序列化为 JSON。

这与 logform.json() 类似。它们都使用 safe-stable-stringify 包来生成 JSON。一些差异

  • 此字符串转换器会跳过序列化 level 字段,因为它不是 ECS 字段。
  • Winston 提供一个 replacer,将 bigint 转换为字符串。这样做的 原因JavaScript JSON 解析器在解析 bigint 时会丢失精度。反对的原因是 BigInt 会将其类型更改为字符串而不是数字。目前,此字符串转换器不会将 BitInt 转换为字符串。