Added x-elastic-client-meta header (#1373)

This commit is contained in:
Tomas Della Vedova
2020-12-16 10:42:26 +01:00
committed by GitHub
parent 4cace4c234
commit 61eee69424
8 changed files with 115 additions and 8 deletions

View File

@ -218,10 +218,15 @@ _Default:_ `null`
_Default:_ `{}` _Default:_ `{}`
|`context` |`context`
|`object` - A custom object that you can use for observability in yoru events. |`object` - A custom object that you can use for observability in your events.
It will be merged with the API level context option. + It will be merged with the API level context option. +
_Default:_ `null` _Default:_ `null`
|`enableMetaHeader`
|`boolean` - If true, adds an header named `'x-elastic-client-meta'`, containing some minimal telemetry data,
such as the client and platform version. +
_Default:_ `true`
|`cloud` |`cloud`
a|`object` - Custom configuration for connecting to a|`object` - Custom configuration for connecting to
https://cloud.elastic.co[Elastic Cloud]. See https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/auth-reference.html[Authentication] https://cloud.elastic.co[Elastic Cloud]. See https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/auth-reference.html[Authentication]

1
index.d.ts vendored
View File

@ -108,6 +108,7 @@ interface ClientOptions {
auth?: BasicAuth | ApiKeyAuth; auth?: BasicAuth | ApiKeyAuth;
context?: Context; context?: Context;
proxy?: string | URL; proxy?: string | URL;
enableMetaHeader?: boolean;
cloud?: { cloud?: {
id: string; id: string;
// TODO: remove username and password here in 8 // TODO: remove username and password here in 8

View File

@ -33,6 +33,8 @@ const Serializer = require('./lib/Serializer')
const errors = require('./lib/errors') const errors = require('./lib/errors')
const { ConfigurationError } = errors const { ConfigurationError } = errors
const { prepareHeaders } = Connection.internals const { prepareHeaders } = Connection.internals
const clientVersion = require('./package.json').version
const nodeVersion = process.versions.node
const kInitialOptions = Symbol('elasticsearchjs-initial-options') const kInitialOptions = Symbol('elasticsearchjs-initial-options')
const kChild = Symbol('elasticsearchjs-child') const kChild = Symbol('elasticsearchjs-child')
@ -125,13 +127,18 @@ class Client extends ESAPI {
auth: null, auth: null,
opaqueIdPrefix: null, opaqueIdPrefix: null,
context: null, context: null,
proxy: null proxy: null,
enableMetaHeader: true
}, opts) }, opts)
this[kInitialOptions] = options this[kInitialOptions] = options
this[kExtensions] = [] this[kExtensions] = []
this.name = options.name this.name = options.name
if (options.enableMetaHeader) {
options.headers['x-elastic-client-meta'] = `es=${clientVersion},js=${nodeVersion},t=${clientVersion},hc=${nodeVersion}`
}
if (opts[kChild] !== undefined) { if (opts[kChild] !== undefined) {
this.serializer = options[kChild].serializer this.serializer = options[kChild].serializer
this.connectionPool = options[kChild].connectionPool this.connectionPool = options[kChild].connectionPool
@ -179,7 +186,13 @@ class Client extends ESAPI {
/* istanbul ignore else */ /* istanbul ignore else */
if (Helpers !== null) { if (Helpers !== null) {
this.helpers = new Helpers({ client: this, maxRetries: options.maxRetries }) this.helpers = new Helpers({
client: this,
maxRetries: options.maxRetries,
metaHeader: options.enableMetaHeader
? `es=${clientVersion},js=${nodeVersion},t=${clientVersion},hc=${nodeVersion}`
: null
})
} }
} }

View File

@ -28,12 +28,14 @@ const { ResponseError, ConfigurationError } = require('./errors')
const pImmediate = promisify(setImmediate) const pImmediate = promisify(setImmediate)
const sleep = promisify(setTimeout) const sleep = promisify(setTimeout)
const kClient = Symbol('elasticsearch-client') const kClient = Symbol('elasticsearch-client')
const kMetaHeader = Symbol('meta header')
/* istanbul ignore next */ /* istanbul ignore next */
const noop = () => {} const noop = () => {}
class Helpers { class Helpers {
constructor (opts) { constructor (opts) {
this[kClient] = opts.client this[kClient] = opts.client
this[kMetaHeader] = opts.metaHeader
this.maxRetries = opts.maxRetries this.maxRetries = opts.maxRetries
} }
@ -71,6 +73,10 @@ class Helpers {
* @return {iterator} the async iterator * @return {iterator} the async iterator
*/ */
async * scrollSearch (params, options = {}) { async * scrollSearch (params, options = {}) {
if (this[kMetaHeader] !== null) {
options.headers = options.headers || {}
options.headers['x-elastic-client-meta'] = this[kMetaHeader] + ',h=s'
}
// TODO: study scroll search slices // TODO: study scroll search slices
const wait = options.wait || 5000 const wait = options.wait || 5000
const maxRetries = options.maxRetries || this.maxRetries const maxRetries = options.maxRetries || this.maxRetries
@ -99,7 +105,7 @@ class Helpers {
stop = true stop = true
await this[kClient].clearScroll( await this[kClient].clearScroll(
{ body: { scroll_id } }, { body: { scroll_id } },
{ ignore: [400] } { ignore: [400], ...options }
) )
} }
@ -414,6 +420,7 @@ class Helpers {
bulk (options) { bulk (options) {
const client = this[kClient] const client = this[kClient]
const { serialize, deserialize } = client.serializer const { serialize, deserialize } = client.serializer
const reqOptions = this[kMetaHeader] !== null ? { headers: { 'x-elastic-client-meta': this[kMetaHeader] + ',h=bp' } } : {}
const { const {
datasource, datasource,
onDocument, onDocument,
@ -676,7 +683,7 @@ class Helpers {
function tryBulk (bulkBody, callback) { function tryBulk (bulkBody, callback) {
if (shouldAbort === true) return callback(null, []) if (shouldAbort === true) return callback(null, [])
client.bulk(Object.assign({}, bulkOptions, { body: bulkBody }), (err, { body }) => { client.bulk(Object.assign({}, bulkOptions, { body: bulkBody }), reqOptions, (err, { body }) => {
if (err) return callback(err, null) if (err) return callback(err, null)
if (body.errors === false) { if (body.errors === false) {
stats.successful += body.items.length stats.successful += body.items.length

View File

@ -44,6 +44,7 @@ class Transport {
if (typeof opts.compression === 'string' && opts.compression !== 'gzip') { if (typeof opts.compression === 'string' && opts.compression !== 'gzip') {
throw new ConfigurationError(`Invalid compression: '${opts.compression}'`) throw new ConfigurationError(`Invalid compression: '${opts.compression}'`)
} }
this.emit = opts.emit this.emit = opts.emit
this.connectionPool = opts.connectionPool this.connectionPool = opts.connectionPool
this.serializer = opts.serializer this.serializer = opts.serializer

View File

@ -26,6 +26,8 @@ const intoStream = require('into-stream')
const { Client, ConnectionPool, Transport, Connection, errors } = require('../../index') const { Client, ConnectionPool, Transport, Connection, errors } = require('../../index')
const { CloudConnectionPool } = require('../../lib/pool') const { CloudConnectionPool } = require('../../lib/pool')
const { buildServer } = require('../utils') const { buildServer } = require('../utils')
const clientVersion = require('../../package.json').version
const nodeVersion = process.versions.node
test('Configure host', t => { test('Configure host', t => {
t.test('Single string', t => { t.test('Single string', t => {
@ -1300,3 +1302,62 @@ test('Content length too big (string)', t => {
t.strictEqual(result.meta.attempts, 0) t.strictEqual(result.meta.attempts, 0)
}) })
}) })
test('Meta header enabled', t => {
t.plan(2)
class MockConnection extends Connection {
request (params, callback) {
t.match(params.headers, { 'x-elastic-client-meta': `es=${clientVersion},js=${nodeVersion},t=${clientVersion},hc=${nodeVersion}` })
const stream = intoStream(JSON.stringify({ hello: 'world' }))
stream.statusCode = 200
stream.headers = {
'content-type': 'application/json;utf=8',
'content-length': '17',
connection: 'keep-alive',
date: new Date().toISOString()
}
process.nextTick(callback, null, stream)
return { abort () {} }
}
}
const client = new Client({
node: 'http://localhost:9200',
Connection: MockConnection
})
client.info((err, result) => {
t.error(err)
})
})
test('Meta header disabled', t => {
t.plan(2)
class MockConnection extends Connection {
request (params, callback) {
t.notMatch(params.headers, { 'x-elastic-client-meta': `es=${clientVersion},js=${nodeVersion},t=${clientVersion},hc=${nodeVersion}` })
const stream = intoStream(JSON.stringify({ hello: 'world' }))
stream.statusCode = 200
stream.headers = {
'content-type': 'application/json;utf=8',
'content-length': '17',
connection: 'keep-alive',
date: new Date().toISOString()
}
process.nextTick(callback, null, stream)
return { abort () {} }
}
}
const client = new Client({
node: 'http://localhost:9200',
Connection: MockConnection,
enableMetaHeader: false
})
client.info((err, result) => {
t.error(err)
})
})

View File

@ -27,6 +27,8 @@ const semver = require('semver')
const { test } = require('tap') const { test } = require('tap')
const { Client, errors } = require('../../../') const { Client, errors } = require('../../../')
const { buildServer, connection } = require('../../utils') const { buildServer, connection } = require('../../utils')
const clientVersion = require('../../../package.json').version
const nodeVersion = process.versions.node
const dataset = [ const dataset = [
{ user: 'jon', age: 23 }, { user: 'jon', age: 23 },
@ -41,7 +43,10 @@ test('bulk index', t => {
const MockConnection = connection.buildMockConnection({ const MockConnection = connection.buildMockConnection({
onRequest (params) { onRequest (params) {
t.strictEqual(params.path, '/_bulk') t.strictEqual(params.path, '/_bulk')
t.match(params.headers, { 'content-type': 'application/x-ndjson' }) t.match(params.headers, {
'content-type': 'application/x-ndjson',
'x-elastic-client-meta': `es=${clientVersion},js=${nodeVersion},t=${clientVersion},hc=${nodeVersion},h=bp`
})
const [action, payload] = params.body.split('\n') const [action, payload] = params.body.split('\n')
t.deepEqual(JSON.parse(action), { index: { _index: 'test' } }) t.deepEqual(JSON.parse(action), { index: { _index: 'test' } })
t.deepEqual(JSON.parse(payload), dataset[count++]) t.deepEqual(JSON.parse(payload), dataset[count++])
@ -84,6 +89,9 @@ test('bulk index', t => {
onRequest (params) { onRequest (params) {
t.strictEqual(params.path, '/_bulk') t.strictEqual(params.path, '/_bulk')
t.match(params.headers, { 'content-type': 'application/x-ndjson' }) t.match(params.headers, { 'content-type': 'application/x-ndjson' })
t.notMatch(params.headers, {
'x-elastic-client-meta': `es=${clientVersion},js=${nodeVersion},t=${clientVersion},hc=${nodeVersion},h=bp`
})
const [action, payload] = params.body.split('\n') const [action, payload] = params.body.split('\n')
t.deepEqual(JSON.parse(action), { index: { _index: 'test' } }) t.deepEqual(JSON.parse(action), { index: { _index: 'test' } })
t.deepEqual(JSON.parse(payload), dataset[count++]) t.deepEqual(JSON.parse(payload), dataset[count++])
@ -93,7 +101,8 @@ test('bulk index', t => {
const client = new Client({ const client = new Client({
node: 'http://localhost:9200', node: 'http://localhost:9200',
Connection: MockConnection Connection: MockConnection,
enableMetaHeader: false
}) })
const result = await client.helpers.bulk({ const result = await client.helpers.bulk({
datasource: dataset.slice(), datasource: dataset.slice(),

View File

@ -22,11 +22,17 @@
const { test } = require('tap') const { test } = require('tap')
const { Client, errors } = require('../../../') const { Client, errors } = require('../../../')
const { connection } = require('../../utils') const { connection } = require('../../utils')
const clientVersion = require('../../../package.json').version
const nodeVersion = process.versions.node
test('Scroll search', async t => { test('Scroll search', async t => {
var count = 0 var count = 0
const MockConnection = connection.buildMockConnection({ const MockConnection = connection.buildMockConnection({
onRequest (params) { onRequest (params) {
t.match(params.headers, {
'x-elastic-client-meta': `es=${clientVersion},js=${nodeVersion},t=${clientVersion},hc=${nodeVersion},h=s`
})
count += 1 count += 1
if (params.method === 'POST') { if (params.method === 'POST') {
t.strictEqual(params.querystring, 'scroll=1m') t.strictEqual(params.querystring, 'scroll=1m')
@ -73,6 +79,9 @@ test('Clear a scroll search', async t => {
var count = 0 var count = 0
const MockConnection = connection.buildMockConnection({ const MockConnection = connection.buildMockConnection({
onRequest (params) { onRequest (params) {
t.notMatch(params.headers, {
'x-elastic-client-meta': `es=${clientVersion},js=${nodeVersion},t=${clientVersion},hc=${nodeVersion},h=s`
})
if (params.method === 'DELETE') { if (params.method === 'DELETE') {
const body = JSON.parse(params.body) const body = JSON.parse(params.body)
t.strictEqual(body.scroll_id, 'id') t.strictEqual(body.scroll_id, 'id')
@ -95,7 +104,8 @@ test('Clear a scroll search', async t => {
const client = new Client({ const client = new Client({
node: 'http://localhost:9200', node: 'http://localhost:9200',
Connection: MockConnection Connection: MockConnection,
enableMetaHeader: false
}) })
const scrollSearch = client.helpers.scrollSearch({ const scrollSearch = client.helpers.scrollSearch({