From 0f1746014cd31ec28478a86ec518a41c49f64f2a Mon Sep 17 00:00:00 2001 From: Tomas Della Vedova Date: Mon, 2 Dec 2019 09:21:03 +0100 Subject: [PATCH] X-Opaque-Id support (#997) * Added X-Opaque-Id support * Updated type definitions * Updated test * Updated docs --- docs/configuration.asciidoc | 5 +++ docs/observability.asciidoc | 43 +++++++++++++++++++ index.d.ts | 1 + index.js | 6 ++- lib/Transport.d.ts | 3 ++ lib/Transport.js | 7 ++++ test/unit/client.test.js | 84 +++++++++++++++++++++++++++++++++++++ 7 files changed, 147 insertions(+), 2 deletions(-) diff --git a/docs/configuration.asciidoc b/docs/configuration.asciidoc index 12e12685f..630c51142 100644 --- a/docs/configuration.asciidoc +++ b/docs/configuration.asciidoc @@ -181,6 +181,11 @@ function generateRequestId (params, options) { |`string` - The name to identify the client instance in the events. + _Default:_ `elasticsearch-js` +|`opaqueIdPrefix` +|`string` - A string that will be use to prefix any `X-Opaque-Id` header. + +See https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/observability.html#_x-opaque-id_support[`X-Opaque-Id` support] for more details. + +_Default:_ `null` + |`headers` |`object` - A set of custom headers to send in every request. + _Default:_ `{}` diff --git a/docs/observability.asciidoc b/docs/observability.asciidoc index c72c340e1..074654f38 100644 --- a/docs/observability.asciidoc +++ b/docs/observability.asciidoc @@ -248,3 +248,46 @@ child.search({ if (err) console.log(err) }) ---- + +=== X-Opaque-Id support +To improve the overall observability, the client offers an easy way to configure the `X-Opaque-Id` header. If you set the `X-Opaque-Id` in a specific request, this will allow you to discover this identifier in the https://www.elastic.co/guide/en/elasticsearch/reference/master/logging.html#deprecation-logging[deprecation logs], help you with https://www.elastic.co/guide/en/elasticsearch/reference/master/index-modules-slowlog.html#_identifying_search_slow_log_origin[identifying search slow log origin] as well as https://www.elastic.co/guide/en/elasticsearch/reference/master/tasks.html#_identifying_running_tasks[identifying running tasks]. + +The `X-Opaque-Id` should be configured in each request, for doing that you can use the `opaqueId` option, as you can see in the following example. + +The resulting header will be `{ 'X-Opaque-Id': 'my-search' }`. + +[source,js] +---- +const { Client } = require('@elastic/elasticsearch') +const client = new Client({ + node: 'http://localhost:9200' +}) + +client.search({ + index: 'my-index', + body: { foo: 'bar' } +}, { + opaqueId: 'my-search' +}, (err, result) => { + if (err) console.log(err) +}) +---- + +Sometimes it may be useful to prefix all the `X-Opaque-Id` headers with a specific string, in case you need to identify a specific client or server. For doing this, the client offers a top-level configuration option: `opaqueIdPrefix`. + +In the following example, the resulting header will be `{ 'X-Opaque-Id': 'proxy-client::my-search' }`. +[source,js] +---- +const { Client } = require('@elastic/elasticsearch') +const client = new Client({ + node: 'http://localhost:9200', + opaqueIdPrefix: 'proxy-client::' +}) + +client.search({ + index: 'my-index', + body: { foo: 'bar' } +}, { + opaqueId: 'my-search' +}, (err, result) => { + if (err) console.log(err) +}) +---- diff --git a/index.d.ts b/index.d.ts index 165c666cc..be48a86c1 100644 --- a/index.d.ts +++ b/index.d.ts @@ -94,6 +94,7 @@ interface ClientOptions { nodeFilter?: nodeFilterFn; nodeSelector?: nodeSelectorFn | string; headers?: anyObject; + opaqueIdPrefix?: string; generateRequestId?: generateRequestIdFn; name?: string; auth?: BasicAuth | ApiKeyAuth; diff --git a/index.js b/index.js index 6fc3e7708..0b3fc17c7 100644 --- a/index.js +++ b/index.js @@ -79,7 +79,8 @@ class Client extends EventEmitter { nodeSelector: 'round-robin', generateRequestId: null, name: 'elasticsearch-js', - auth: null + auth: null, + opaqueIdPrefix: null }, opts) this[kInitialOptions] = options @@ -121,7 +122,8 @@ class Client extends EventEmitter { nodeFilter: options.nodeFilter, nodeSelector: options.nodeSelector, generateRequestId: options.generateRequestId, - name: options.name + name: options.name, + opaqueIdPrefix: options.opaqueIdPrefix }) const apis = buildApi({ diff --git a/lib/Transport.d.ts b/lib/Transport.d.ts index 8099de2ea..f0fdae9d4 100644 --- a/lib/Transport.d.ts +++ b/lib/Transport.d.ts @@ -38,6 +38,7 @@ interface TransportOptions { headers?: anyObject; generateRequestId?: generateRequestIdFn; name: string; + opaqueIdPrefix?: string; } export interface RequestEvent { @@ -90,6 +91,7 @@ export interface TransportRequestOptions { id?: any; context?: any; warnings?: [string]; + opaqueId?: string; } export interface TransportRequestCallback { @@ -121,6 +123,7 @@ export default class Transport { compression: 'gzip' | false; sniffInterval: number; sniffOnConnectionFault: boolean; + opaqueIdPrefix: string | null; sniffEndpoint: string; _sniffEnabled: boolean; _nextSniff: number; diff --git a/lib/Transport.js b/lib/Transport.js index 7e5b83681..d00a2e8bb 100644 --- a/lib/Transport.js +++ b/lib/Transport.js @@ -41,6 +41,7 @@ class Transport { this.sniffEndpoint = opts.sniffEndpoint this.generateRequestId = opts.generateRequestId || generateRequestId() this.name = opts.name + this.opaqueIdPrefix = opts.opaqueIdPrefix this.nodeFilter = opts.nodeFilter || defaultNodeFilter if (typeof opts.nodeSelector === 'function') { @@ -114,6 +115,12 @@ class Transport { // TODO: make this assignment FAST const headers = Object.assign({}, this.headers, options.headers) + if (options.opaqueId !== undefined) { + headers['X-Opaque-Id'] = this.opaqueIdPrefix !== null + ? this.opaqueIdPrefix + options.opaqueId + : options.opaqueId + } + // handle json body if (params.body != null) { if (shouldSerialize(params.body) === true) { diff --git a/test/unit/client.test.js b/test/unit/client.test.js index f6a86a985..ca3649379 100644 --- a/test/unit/client.test.js +++ b/test/unit/client.test.js @@ -851,3 +851,87 @@ test('Elastic cloud config', t => { t.end() }) + +test('Opaque Id support', t => { + t.test('No opaqueId', t => { + t.plan(3) + + function handler (req, res) { + t.strictEqual(req.headers['x-opaque-id'], undefined) + res.setHeader('Content-Type', 'application/json;utf=8') + res.end(JSON.stringify({ hello: 'world' })) + } + + buildServer(handler, ({ port }, server) => { + const client = new Client({ + node: `http://localhost:${port}` + }) + + client.search({ + index: 'test', + q: 'foo:bar' + }, (err, { body }) => { + t.error(err) + t.deepEqual(body, { hello: 'world' }) + server.stop() + }) + }) + }) + + t.test('No prefix', t => { + t.plan(3) + + function handler (req, res) { + t.strictEqual(req.headers['x-opaque-id'], 'bar') + res.setHeader('Content-Type', 'application/json;utf=8') + res.end(JSON.stringify({ hello: 'world' })) + } + + buildServer(handler, ({ port }, server) => { + const client = new Client({ + node: `http://localhost:${port}` + }) + + client.search({ + index: 'test', + q: 'foo:bar' + }, { + opaqueId: 'bar' + }, (err, { body }) => { + t.error(err) + t.deepEqual(body, { hello: 'world' }) + server.stop() + }) + }) + }) + + t.test('With prefix', t => { + t.plan(3) + + function handler (req, res) { + t.strictEqual(req.headers['x-opaque-id'], 'foo-bar') + res.setHeader('Content-Type', 'application/json;utf=8') + res.end(JSON.stringify({ hello: 'world' })) + } + + buildServer(handler, ({ port }, server) => { + const client = new Client({ + node: `http://localhost:${port}`, + opaqueIdPrefix: 'foo-' + }) + + client.search({ + index: 'test', + q: 'foo:bar' + }, { + opaqueId: 'bar' + }, (err, { body }) => { + t.error(err) + t.deepEqual(body, { hello: 'world' }) + server.stop() + }) + }) + }) + + t.end() +})