Skip to content

Commit feeaf15

Browse files
authored
Docs on how to disable OpenTelemetry (#2934)
1 parent c2272b1 commit feeaf15

File tree

4 files changed

+175
-28
lines changed

4 files changed

+175
-28
lines changed

docs/reference/observability.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,41 @@ To start sending Elasticsearch trace data to your OpenTelemetry endpoint, follow
3535
node --require '@opentelemetry/auto-instrumentations-node/register' index.js
3636
```
3737

38+
### Disabling OpenTelemetry collection [disable-otel]
39+
40+
As of `@elastic/transport` version 9.1.0—or 8.10.0 when using `@elastic/elasticsearch` 8.x—OpenTelemetry tracing can be disabled in multiple ways.
41+
42+
To entirely disable OpenTelemetry collection, you can provide a custom `Transport` at client instantiation time that sets `openTelemetry.enabled` to `false`:
43+
44+
```typescript
45+
import { Transport } from '@elastic/transport'
46+
47+
class MyTransport extends Transport {
48+
async request(params, options = {}): Promise<any> {
49+
options.openTelemetry = { enabled: false }
50+
return super.request(params, options)
51+
}
52+
}
53+
54+
const client = new Client({
55+
node: '...',
56+
auth: { ... },
57+
Transport: MyTransport
58+
})
59+
```
60+
61+
Alternatively, you can also export an environment variable `OTEL_ELASTICSEARCH_ENABLED=false` to achieve the same effect.
62+
63+
If you would not like OpenTelemetry to be disabled entirely, but would like the client to suppress tracing, you can use the option `openTelemetry.suppressInternalInstrumentation = true` instead.
64+
65+
If you would like to keep either option enabled by default, but want to disable them for a single API call, you can pass `Transport` options as a second argument to any API function call:
66+
67+
```typescript
68+
const response = await client.search({ ... }, {
69+
openTelemetry: { enabled: false }
70+
})
71+
```
72+
3873
## Events [_events]
3974

4075
The client is an event emitter. This means that you can listen for its events to add additional logic to your code, without needing to change the client’s internals or how you use the client. You can find the events' names by accessing the `events` key of the client:

docs/reference/transport.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const { Client } = require('@elastic/elasticsearch')
1212
const { Transport } = require('@elastic/transport')
1313

1414
class MyTransport extends Transport {
15-
request (params, options, callback) {
15+
request (params, options) {
1616
// your code
1717
}
1818
}
@@ -26,9 +26,9 @@ Sometimes you need to inject a small snippet of your code and then continue to u
2626

2727
```js
2828
class MyTransport extends Transport {
29-
request (params, options, callback) {
29+
request (params, options) {
3030
// your code
31-
return super.request(params, options, callback)
31+
return super.request(params, options)
3232
}
3333
}
3434
```

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
},
6060
"devDependencies": {
6161
"@elastic/request-converter": "9.1.2",
62+
"@opentelemetry/sdk-trace-base": "1.30.1",
6263
"@sinonjs/fake-timers": "14.0.0",
6364
"@types/debug": "4.1.12",
6465
"@types/ms": "2.1.0",

test/unit/client.test.ts

Lines changed: 136 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import { URL } from 'node:url'
88
import { setTimeout } from 'node:timers/promises'
99
import { test } from 'tap'
1010
import FakeTimers from '@sinonjs/fake-timers'
11+
import { Transport } from '@elastic/transport'
1112
import { buildServer, connection } from '../utils'
1213
import { Client, errors, SniffingTransport } from '../..'
1314
import * as symbols from '@elastic/transport/lib/symbols'
1415
import { BaseConnectionPool, CloudConnectionPool, WeightedConnectionPool, HttpConnection } from '@elastic/transport'
16+
import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'
1517

1618
let clientVersion: string = require('../../package.json').version // eslint-disable-line
1719
if (clientVersion.includes('-')) {
@@ -124,7 +126,7 @@ test('Basic auth', async t => {
124126
t.plan(1)
125127

126128
const Connection = connection.buildMockConnection({
127-
onRequest (opts) {
129+
onRequest(opts) {
128130
t.match(opts.headers, { authorization: 'Basic aGVsbG86d29ybGQ=' })
129131
return {
130132
statusCode: 200,
@@ -149,7 +151,7 @@ test('Basic auth via url', async t => {
149151
t.plan(1)
150152

151153
const Connection = connection.buildMockConnection({
152-
onRequest (opts) {
154+
onRequest(opts) {
153155
t.match(opts.headers, { authorization: 'Basic aGVsbG86d29ybGQ=' })
154156
return {
155157
statusCode: 200,
@@ -170,7 +172,7 @@ test('ApiKey as string', async t => {
170172
t.plan(1)
171173

172174
const Connection = connection.buildMockConnection({
173-
onRequest (opts) {
175+
onRequest(opts) {
174176
t.match(opts.headers, { authorization: 'ApiKey foobar' })
175177
return {
176178
statusCode: 200,
@@ -194,7 +196,7 @@ test('ApiKey as object', async t => {
194196
t.plan(1)
195197

196198
const Connection = connection.buildMockConnection({
197-
onRequest (opts) {
199+
onRequest(opts) {
198200
t.match(opts.headers, { authorization: 'ApiKey Zm9vOmJhcg==' })
199201
return {
200202
statusCode: 200,
@@ -221,7 +223,7 @@ test('Bearer auth', async t => {
221223
t.plan(1)
222224

223225
const Connection = connection.buildMockConnection({
224-
onRequest (opts) {
226+
onRequest(opts) {
225227
t.match(opts.headers, { authorization: 'Bearer token' })
226228
return {
227229
statusCode: 200,
@@ -245,7 +247,7 @@ test('Override authentication per request', async t => {
245247
t.plan(1)
246248

247249
const Connection = connection.buildMockConnection({
248-
onRequest (opts) {
250+
onRequest(opts) {
249251
t.match(opts.headers, { authorization: 'Basic foobar' })
250252
return {
251253
statusCode: 200,
@@ -273,7 +275,7 @@ test('Custom headers per request', async t => {
273275
t.plan(1)
274276

275277
const Connection = connection.buildMockConnection({
276-
onRequest (opts) {
278+
onRequest(opts) {
277279
t.match(opts.headers, {
278280
foo: 'bar',
279281
faz: 'bar'
@@ -301,7 +303,7 @@ test('Close the client', async t => {
301303
t.plan(1)
302304

303305
class MyConnectionPool extends BaseConnectionPool {
304-
async empty (): Promise<void> {
306+
async empty(): Promise<void> {
305307
t.pass('called')
306308
}
307309
}
@@ -336,10 +338,10 @@ test('Elastic Cloud config', t => {
336338

337339
t.test('Invalid Cloud ID will throw ConfigurationError', t => {
338340
t.throws(() => new Client({
339-
cloud : {
340-
id : 'invalidCloudIdThatIsNotBase64'
341+
cloud: {
342+
id: 'invalidCloudIdThatIsNotBase64'
341343
},
342-
auth : {
344+
auth: {
343345
username: 'elastic',
344346
password: 'changeme'
345347
}
@@ -414,7 +416,7 @@ test('Meta header enabled by default', async t => {
414416
t.plan(1)
415417

416418
const Connection = connection.buildMockConnection({
417-
onRequest (opts) {
419+
onRequest(opts) {
418420
t.match(opts.headers, { 'x-elastic-client-meta': `es=${clientVersion},js=${nodeVersion},t=${transportVersion},hc=${nodeVersion}` })
419421
return {
420422
statusCode: 200,
@@ -435,7 +437,7 @@ test('Meta header disabled', async t => {
435437
t.plan(1)
436438

437439
const Connection = connection.buildMockConnection({
438-
onRequest (opts) {
440+
onRequest(opts) {
439441
t.notOk(opts.headers?.['x-elastic-client-meta'])
440442
return {
441443
statusCode: 200,
@@ -456,39 +458,39 @@ test('Meta header disabled', async t => {
456458
test('Meta header indicates when UndiciConnection is used', async t => {
457459
t.plan(1)
458460

459-
function handler (req: http.IncomingMessage, res: http.ServerResponse) {
461+
function handler(req: http.IncomingMessage, res: http.ServerResponse) {
460462
t.equal(req.headers['x-elastic-client-meta'], `es=${clientVersion},js=${nodeVersion},t=${transportVersion},un=${nodeVersion}`)
461463
res.end('ok')
462464
}
463465

464466
const [{ port }, server] = await buildServer(handler)
467+
t.after(() => server.stop())
465468

466469
const client = new Client({
467470
node: `http://localhost:${port}`,
468471
// Connection: UndiciConnection is the default
469472
})
470473

471474
await client.transport.request({ method: 'GET', path: '/' })
472-
server.stop()
473475
})
474476

475477
test('Meta header indicates when HttpConnection is used', async t => {
476478
t.plan(1)
477479

478-
function handler (req: http.IncomingMessage, res: http.ServerResponse) {
480+
function handler(req: http.IncomingMessage, res: http.ServerResponse) {
479481
t.equal(req.headers['x-elastic-client-meta'], `es=${clientVersion},js=${nodeVersion},t=${transportVersion},hc=${nodeVersion}`)
480482
res.end('ok')
481483
}
482484

483485
const [{ port }, server] = await buildServer(handler)
486+
t.after(() => server.stop())
484487

485488
const client = new Client({
486489
node: `http://localhost:${port}`,
487490
Connection: HttpConnection,
488491
})
489492

490493
await client.transport.request({ method: 'GET', path: '/' })
491-
server.stop()
492494
})
493495

494496
test('caFingerprint', t => {
@@ -503,19 +505,19 @@ test('caFingerprint', t => {
503505

504506
test('caFingerprint can\'t be configured over http / 1', t => {
505507
t.throws(() => new Client({
506-
node: 'http://localhost:9200',
507-
caFingerprint: 'FO:OB:AR'
508-
}),
508+
node: 'http://localhost:9200',
509+
caFingerprint: 'FO:OB:AR'
510+
}),
509511
errors.ConfigurationError
510512
)
511513
t.end()
512514
})
513515

514516
test('caFingerprint can\'t be configured over http / 2', t => {
515517
t.throws(() => new Client({
516-
nodes: ['http://localhost:9200'],
517-
caFingerprint: 'FO:OB:AR'
518-
}),
518+
nodes: ['http://localhost:9200'],
519+
caFingerprint: 'FO:OB:AR'
520+
}),
519521
errors.ConfigurationError
520522
)
521523
t.end()
@@ -551,7 +553,7 @@ test('Ensure new client does not time out if requestTimeout is not set', async t
551553
const clock = FakeTimers.install({ toFake: ['setTimeout'] })
552554
t.teardown(() => clock.uninstall())
553555

554-
function handler (_req: http.IncomingMessage, res: http.ServerResponse) {
556+
function handler(_req: http.IncomingMessage, res: http.ServerResponse) {
555557
setTimeout(1000 * 60 * 60).then(() => {
556558
t.ok('timeout ended')
557559
res.setHeader('content-type', 'application/json')
@@ -660,7 +662,7 @@ test('serverless defaults', t => {
660662
t.plan(1)
661663

662664
const Connection = connection.buildMockConnection({
663-
onRequest (opts) {
665+
onRequest(opts) {
664666
t.equal(opts.headers?.['elastic-api-version'], '2023-10-31')
665667
return {
666668
statusCode: 200,
@@ -686,3 +688,112 @@ test('serverless defaults', t => {
686688

687689
t.end()
688690
})
691+
692+
test('custom transport: class', async t => {
693+
t.plan(3)
694+
695+
class MyTransport extends Transport {
696+
async request(params, options): Promise<any> {
697+
t.ok(true, 'custom Transport request function should be called')
698+
return super.request(params, options)
699+
}
700+
}
701+
702+
function handler(_req: http.IncomingMessage, res: http.ServerResponse) {
703+
t.ok(true, 'handler should be called')
704+
res.end('ok')
705+
}
706+
707+
const [{ port }, server] = await buildServer(handler)
708+
t.after(() => server.stop())
709+
710+
const client = new Client({
711+
node: `http://localhost:${port}`,
712+
Transport: MyTransport
713+
})
714+
715+
t.ok(client.transport instanceof MyTransport, 'Custom transport should be used')
716+
717+
client.transport.request({ method: 'GET', path: '/' })
718+
})
719+
720+
test('custom transport: disable otel via options', async t => {
721+
const exporter = new InMemorySpanExporter()
722+
const processor = new SimpleSpanProcessor(exporter)
723+
const provider = new BasicTracerProvider({
724+
spanProcessors: [processor]
725+
})
726+
provider.register()
727+
728+
t.after(async () => {
729+
await provider.forceFlush()
730+
exporter.reset()
731+
await provider.shutdown()
732+
})
733+
734+
class MyTransport extends Transport {
735+
async request(params, options = {}): Promise<any> {
736+
// @ts-expect-error
737+
options.openTelemetry = { enabled: false }
738+
return super.request(params, options)
739+
}
740+
}
741+
742+
function handler(_req: http.IncomingMessage, res: http.ServerResponse) {
743+
res.end('ok')
744+
}
745+
746+
const [{ port }, server] = await buildServer(handler)
747+
t.after(() => server.stop())
748+
749+
const client = new Client({
750+
node: `http://localhost:${port}`,
751+
Transport: MyTransport
752+
})
753+
754+
await client.transport.request({
755+
path: '/hello',
756+
method: 'GET',
757+
meta: { name: 'hello' },
758+
})
759+
760+
t.equal(exporter.getFinishedSpans().length, 0)
761+
t.end()
762+
})
763+
764+
test('custom transport: disable otel via env var', async t => {
765+
const exporter = new InMemorySpanExporter()
766+
const processor = new SimpleSpanProcessor(exporter)
767+
const provider = new BasicTracerProvider({
768+
spanProcessors: [processor]
769+
})
770+
provider.register()
771+
772+
t.after(async () => {
773+
await provider.forceFlush()
774+
exporter.reset()
775+
await provider.shutdown()
776+
})
777+
778+
function handler(_req: http.IncomingMessage, res: http.ServerResponse) {
779+
res.end('ok')
780+
}
781+
782+
const [{ port }, server] = await buildServer(handler)
783+
t.after(() => server.stop())
784+
785+
const client = new Client({
786+
node: `http://localhost:${port}`,
787+
})
788+
789+
process.env.OTEL_ELASTICSEARCH_ENABLED = 'false'
790+
791+
await client.transport.request({
792+
path: '/hello',
793+
method: 'GET',
794+
meta: { name: 'hello' },
795+
})
796+
797+
t.equal(exporter.getFinishedSpans().length, 0)
798+
t.end()
799+
})

0 commit comments

Comments
 (0)