可观测性

编辑

为了观察和衡量 Elasticsearch 客户端的使用情况,提供了几个客户端功能。

首先,从 8.15.0 版本开始,客户端原生支持 OpenTelemetry,这允许您将客户端使用数据发送到任何支持 OpenTelemetry 的端点,而无需对您的 JavaScript 代码库进行任何更改。

此外,客户端没有提供默认的日志记录器,而是提供了一个事件发射器接口来挂钩内部事件,例如 requestresponse,允许您记录您关心的事件,或者以您需要的任何方式对客户端使用情况做出反应。

关联事件可能很困难,特别是当您的应用程序具有庞大的代码库并且同时发生许多事件时。为了帮助您解决这个问题,客户端提供了一个关联 ID 系统和其他功能。

所有这些可观测性功能将在下面记录。

OpenTelemetry

编辑

客户端支持 OpenTelemetry 的 零代码检测,以启用将每个客户端请求跟踪为 OpenTelemetry span。这些 span 遵循所有 Elasticsearch 的语义 OpenTelemetry 约定,除了 db.query.text

要开始将 Elasticsearch 跟踪数据发送到您的 OpenTelemetry 端点,请按照 OpenTelemetry 的零代码检测指南,或以下步骤进行操作

  1. 安装 @opentelemetry/api@opentelemetry/auto-instrumentations-node 作为 Node.js 依赖项
  2. 导出具有适当值的以下环境变量

    • OTEL_EXPORTER_OTLP_ENDPOINT
    • OTEL_EXPORTER_OTLP_HEADERS
    • OTEL_RESOURCE_ATTRIBUTES
    • OTEL_SERVICE_NAME
  3. 在启动时 require Node.js 自动检测库
node --require '@opentelemetry/auto-instrumentations-node/register' index.js

事件

编辑

客户端是一个事件发射器。这意味着您可以监听它的事件,以便向您的代码添加额外的逻辑,而无需更改客户端的内部结构或您如何使用客户端。您可以通过访问客户端的 events 键来查找事件的名称

const { events } = require('@elastic/elasticsearch')
console.log(events)

如果您想记录客户端创建的每个请求、响应或错误,事件发射器功能可能会很有用

const logger = require('my-logger')()
const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  cloud: { id: '<cloud-id>' },
  auth: { apiKey: 'base64EncodedKey' }
})

client.diagnostic.on('response', (err, result) => {
  if (err) {
    logger.error(err)
  } else {
    logger.info(result)
  }
})

客户端会发出以下事件

序列化

在开始序列化和压缩之前发出。如果您想测量此阶段的持续时间,则应测量此事件和 request 之间经过的时间。

client.diagnostic.on('serialization', (err, result) => {
  console.log(err, result)
})

请求

在将实际请求发送到 Elasticsearch 之前发出(如果发生重试,则会多次发出)

client.diagnostic.on('request', (err, result) => {
  console.log(err, result)
})

反序列化

在开始反序列化和解压缩之前发出。如果您想测量此阶段的持续时间,则应测量此事件和 response 之间经过的时间。(在某些情况下可能不会发出此事件)

client.diagnostic.on('deserialization', (err, result) => {
  console.log(err, result)
})

响应

在收到并解析 Elasticsearch 响应后发出。

client.diagnostic.on('response', (err, result) => {
  console.log(err, result)
})

嗅探

当客户端结束嗅探请求时发出。

client.diagnostic.on('sniff', (err, result) => {
  console.log(err, result)
})

复活

如果客户端能够复活死节点,则发出。

client.diagnostic.on('resurrect', (err, result) => {
  console.log(err, result)
})

serializationrequestdeserializationresponsesniff 中的 result 的值是

body: any;
statusCode: number | null;
headers: anyObject | null;
warnings: string[] | null;
meta: {
  context: any;
  name: string;
  request: {
    params: TransportRequestParams;
    options: TransportRequestOptions;
    id: any;
  };
  connection: Connection;
  attempts: number;
  aborted: boolean;
  sniff?: {
    hosts: any[];
    reason: string;
  };
};

resurrect 中的 result 值是

strategy: string;
isAlive: boolean;
connection: Connection;
name: string;
request: {
  id: any;
};
事件顺序
编辑

事件顺序在以下图中描述,在某些极端情况下,不保证顺序。您可以在 test/acceptance/events-order.test.js 中找到该顺序如何根据情况变化。

serialization
  │
  │ (serialization and compression happens between those two events)
  │
  └─▶ request
        │
        │ (actual time spent over the wire)
        │
        └─▶ deserialization
              │
              │ (deserialization and decompression happens between those two events)
              │
              └─▶ response

关联 ID

编辑

关联事件可能很困难,特别是当同时发生许多事件时。客户端为您提供了一个自动(且可配置)的系统来帮助您处理此问题。

const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  cloud: { id: '<cloud-id>' },
  auth: { apiKey: 'base64EncodedKey' }
})

client.diagnostic.on('request', (err, result) => {
  const { id } = result.meta.request
  if (err) {
    console.log({ error: err, reqId: id })
  }
})

client.diagnostic.on('response', (err, result) => {
  const { id } = result.meta.request
  if (err) {
    console.log({ error: err, reqId: id })
  }
})

client.search({
  index: 'my-index',
  query: { match_all: {} }
}).then(console.log, console.log)

默认情况下,ID 是一个递增的整数,但您可以使用 generateRequestId 选项进行配置

const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  cloud: { id: '<cloud-id>' },
  auth: { apiKey: 'base64EncodedKey' },
  // it takes two parameters, the request parameters and options
  generateRequestId: function (params, options) {
    // your id generation logic
    // must be syncronous
    return 'id'
  }
})

您还可以为每个请求指定自定义 ID

client.search({
  index: 'my-index',
  query: { match_all: {} }
}, {
  id: 'custom-id'
}).then(console.log, console.log)

上下文对象

编辑

有时,您可能需要在您的事件中提供一些自定义数据,您可以通过请求的 context 选项来实现

const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  cloud: { id: '<cloud-id>' },
  auth: { apiKey: 'base64EncodedKey' }
})

client.diagnostic.on('request', (err, result) => {
  const { id } = result.meta.request
  const { context } = result.meta
  if (err) {
    console.log({ error: err, reqId: id, context })
  }
})

client.diagnostic.on('response', (err, result) => {
  const { id } = result.meta.request
  const { winter } = result.meta.context
  if (err) {
    console.log({ error: err, reqId: id, winter })
  }
})

client.search({
  index: 'my-index',
  query: { match_all: {} }
}, {
  context: { winter: 'is coming' }
}).then(console.log, console.log)

上下文对象也可以在客户端配置中配置为全局选项。如果您同时提供了两者,则两个上下文对象将进行浅合并,并且 API 级别的对象将具有优先权。

const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  cloud: { id: '<cloud-id>' },
  auth: { apiKey: 'base64EncodedKey' },
  context: { winter: 'is coming' }
})

client.diagnostic.on('request', (err, result) => {
  const { id } = result.meta.request
  const { context } = result.meta
  if (err) {
    console.log({ error: err, reqId: id, context })
  }
})

client.diagnostic.on('response', (err, result) => {
  const { id } = result.meta.request
  const { winter } = result.meta.context
  if (err) {
    console.log({ error: err, reqId: id, winter })
  }
})

client.search({
  index: 'my-index',
  query: { match_all: {} }
}, {
  context: { winter: 'has come' }
}).then(console.log, console.log)

客户端名称

编辑

如果您正在使用客户端的多个实例,或者您正在使用多个子客户端(这是拥有多个客户端实例的推荐方式),您可能需要识别您正在使用的客户端。name 选项在这方面可以帮助您。

const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  cloud: { id: '<cloud-id>' },
  auth: { apiKey: 'base64EncodedKey' },
  name: 'parent-client' // default to 'elasticsearch-js'
})

const child = client.child({
  name: 'child-client'
})

console.log(client.name, child.name)

client.diagnostic.on('request', (err, result) => {
  const { id } = result.meta.request
  const { name } = result.meta
  if (err) {
    console.log({ error: err, reqId: id, name })
  }
})

client.diagnostic.on('response', (err, result) => {
  const { id } = result.meta.request
  const { name } = result.meta
  if (err) {
    console.log({ error: err, reqId: id, name })
  }
})

client.search({
  index: 'my-index',
  query: { match_all: {} }
}).then(console.log, console.log)

child.search({
  index: 'my-index',
  query: { match_all: {} }
}).then(console.log, console.log)

X-Opaque-Id 支持

编辑

为了提高可观测性,客户端提供了一种简单的方法来配置 X-Opaque-Id 标头。如果在特定请求中设置 X-Opaque-Id,这允许您在弃用日志中发现此标识符,可以帮助您识别搜索慢日志的来源以及识别正在运行的任务

X-Opaque-Id 应在每个请求中配置,为此您可以使用 opaqueId 选项,如以下示例所示。生成的标头将为 { 'X-Opaque-Id': 'my-search' }

const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  cloud: { id: '<cloud-id>' },
  auth: { apiKey: 'base64EncodedKey' }
})

client.search({
  index: 'my-index',
  body: { foo: 'bar' }
}, {
  opaqueId: 'my-search'
}).then(console.log, console.log)

有时,在需要识别特定客户端或服务器的情况下,可能需要为所有 X-Opaque-Id 标头添加特定字符串作为前缀。为此,客户端提供了一个顶级配置选项:opaqueIdPrefix。在以下示例中,生成的标头将为 { 'X-Opaque-Id': 'proxy-client::my-search' }

const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  cloud: { id: '<cloud-id>' },
  auth: { apiKey: 'base64EncodedKey' },
  opaqueIdPrefix: 'proxy-client::'
})

client.search({
  index: 'my-index',
  body: { foo: 'bar' }
}, {
  opaqueId: 'my-search'
}).then(console.log, console.log)