[[observability]]
== Observability

The client does not provide a default logger, but instead it offers an event 
emitter interfaces to hook into internal events, such as `request` and 
`response`.

Correlating those events can be quite hard, especially if your applications have 
a large codebase with many events happening at the same time.

To help you with this, the client offers you a correlation id system and other 
features. Let's see them in action.

=== Events

The client is an event emitter, this means that you can listen for its event and 
add additional logic to your code, without need to change the client internals 
or your normal usage. You can find the events names by access the `events` key 
of the client.

[source,js]
----
const { events } = require('@elastic/elasticsearch')
console.log(events)
----


The event emitter functionality can be useful if you want to log every request, 
response and error that is happening during the use of the client.

[source,js]
----
const logger = require('my-logger')()
const { Client } = require('@elastic/elasticsearch')
const client = new Client({ node: 'http://localhost:9200' })

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


The client emits the following events:
[cols=2*]
|===
|`request`
a|Emitted before sending the actual request to {es} _(emitted multiple times in case of retries)_.
[source,js]
----
client.on('request', (err, result) => {
  console.log(err, result)
})
----

|`response`
a|Emitted once {es} response has been received and parsed.
[source,js]
----
client.on('response', (err, result) => {
  console.log(err, result)
})
----

|`sniff`
a|Emitted when the client ends a sniffing request.
[source,js]
----
client.on('sniff', (err, result) => {
  console.log(err, result)
})
----

|`resurrect`
a|Emitted if the client is able to resurrect a dead node.
[source,js]
----
client.on('resurrect', (err, result) => {
  console.log(err, result)
})
----

|===

The values of `result` in `request`, `response` and `sniff` will be:

[source,ts]
----
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;
  };
};
----


While the `result` value in `resurrect` will be:

[source,ts]
----
strategy: string;
isAlive: boolean;
connection: Connection;
name: string;
request: {
  id: any;
};
----


=== Correlation id

Correlating events can be quite hard, especially if there are many events at the 
same time. The client offers you an automatic (and configurable) system to help 
you handle this problem.

[source,js]
----
const { Client } = require('@elastic/elasticsearch')
const client = new Client({ node: 'http://localhost:9200' })

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

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

client.search({
  index: 'my-index',
  body: { foo: 'bar' }
}, (err, result) => {
  if (err) console.log(err)
})
----


By default the id is an incremental integer, but you can easily configure that 
with the `generateRequestId` option:

[source,js]
----
const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  node: 'http://localhost:9200',
  // it takes two parameters, the request parameters and options
  generateRequestId: function (params, options) {
    // your id generation logic
    // must be syncronous
    return 'id'
  }
})
----


You can also specify a custom id per request:

[source,js]
----
client.search({
  index: 'my-index',
  body: { foo: 'bar' }
}, {
  id: 'custom-id'
}, (err, result) => {
  if (err) console.log(err)
})
----


=== Context object

Sometimes, you might need to make some custom data available in your events, you 
can do that via the `context` option of a request: 

[source,js]
----
const { Client } = require('@elastic/elasticsearch')
const client = new Client({ node: 'http://localhost:9200' })

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

client.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',
  body: { foo: 'bar' }
}, {
  context: { winter: 'is coming' }
}, (err, result) => {
  if (err) console.log(err)
})
----


=== Client name

If you are using multiple instances of the client or if you are using multiple 
child clients _(which is the recommended way to have multiple instances of the 
client)_, you might need to recognize which client you are using. The `name` 
options will help you in this regard.

[source,js]
----
const { Client } = require('@elastic/elasticsearch')
const client = new Client({
  node: 'http://localhost:9200',
  name: 'parent-client' // default to 'elasticsearch-js'
})

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

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

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

client.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',
  body: { foo: 'bar' }
}, (err, result) => {
  if (err) console.log(err)
})

child.search({
  index: 'my-index',
  body: { foo: 'bar' }
}, (err, result) => {
  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)
})
----

