Skip to content

Commit 5a033e5

Browse files
authored
fix(exporter-*-otlp-grpc)!: lazy load gRPC (#4432)
* fix(exporter-*-otlp-grpc)!: lazy load gRPC
1 parent 75bd723 commit 5a033e5

22 files changed

+1064
-572
lines changed

experimental/CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,19 @@ All notable changes to experimental packages in this project will be documented
5959
* This breaking change only affects users that are using the *experimental* `@opentelemetry/instrumentation/hook.mjs` loader hook AND Node.js 18.19 or later:
6060
* This reverts back to an older version of `import-in-the-middle` due to <https://github.com/DataDog/import-in-the-middle/issues/57>
6161
* This version does not support Node.js 18.19 or later
62+
* fix(exporter-*-otlp-grpc)!: lazy load gRPC to improve compatibility with `@opentelemetry/instrumenation-grpc` [#4432](https://github.com/open-telemetry/opentelemetry-js/pull/4432) @pichlermarc
63+
* Fixes a bug where requiring up the gRPC exporter before enabling the instrumentation from `@opentelemetry/instrumentation-grpc` would lead to missing telemetry
64+
* Breaking changes, removes several functions and properties that were used internally and were not intended for end-users
65+
* `getServiceClientType()`
66+
* this returned a static enum value that would denote the export type (`SPAN`, `METRICS`, `LOGS`)
67+
* `getServiceProtoPath()`
68+
* this returned a static enum value that would correspond to the gRPC service path
69+
* `metadata`
70+
* was used internally to access metadata, but as a side effect allowed end-users to modify metadata on runtime.
71+
* `serviceClient`
72+
* was used internally to keep track of the service client used by the exporter, as a side effect it allowed end-users to modify the gRPC service client that was used
73+
* `compression`
74+
* was used internally to keep track of the compression to use but was unintentionally exposed to the users. It allowed to read and write the value, writing, however, would have no effect.
6275

6376
### :bug: (Bug Fix)
6477

experimental/packages/exporter-logs-otlp-grpc/src/OTLPLogExporter.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@
1616

1717
import { LogRecordExporter, ReadableLogRecord } from '@opentelemetry/sdk-logs';
1818
import { baggageUtils, getEnv } from '@opentelemetry/core';
19-
import { Metadata } from '@grpc/grpc-js';
2019
import {
2120
OTLPGRPCExporterConfigNode,
2221
OTLPGRPCExporterNodeBase,
23-
ServiceClientType,
2422
validateAndNormalizeUrl,
2523
DEFAULT_COLLECTOR_URL,
24+
LogsSerializer,
2625
} from '@opentelemetry/otlp-grpc-exporter-base';
2726
import {
2827
createExportLogsServiceRequest,
2928
IExportLogsServiceRequest,
29+
IExportLogsServiceResponse,
3030
} from '@opentelemetry/otlp-transformer';
3131
import { VERSION } from './version';
3232

@@ -38,21 +38,27 @@ const USER_AGENT = {
3838
* OTLP Logs Exporter for Node
3939
*/
4040
export class OTLPLogExporter
41-
extends OTLPGRPCExporterNodeBase<ReadableLogRecord, IExportLogsServiceRequest>
41+
extends OTLPGRPCExporterNodeBase<
42+
ReadableLogRecord,
43+
IExportLogsServiceRequest,
44+
IExportLogsServiceResponse
45+
>
4246
implements LogRecordExporter
4347
{
4448
constructor(config: OTLPGRPCExporterConfigNode = {}) {
45-
super(config);
46-
const headers = {
49+
const signalSpecificMetadata = {
4750
...USER_AGENT,
4851
...baggageUtils.parseKeyPairsIntoRecord(
4952
getEnv().OTEL_EXPORTER_OTLP_LOGS_HEADERS
5053
),
5154
};
52-
this.metadata ||= new Metadata();
53-
for (const [k, v] of Object.entries(headers)) {
54-
this.metadata.set(k, v);
55-
}
55+
super(
56+
config,
57+
signalSpecificMetadata,
58+
'LogsExportService',
59+
'/opentelemetry.proto.collector.logs.v1.LogsService/Export',
60+
LogsSerializer
61+
);
5662
}
5763

5864
convert(logRecords: ReadableLogRecord[]): IExportLogsServiceRequest {
@@ -63,14 +69,6 @@ export class OTLPLogExporter
6369
return validateAndNormalizeUrl(this.getUrlFromConfig(config));
6470
}
6571

66-
getServiceClientType() {
67-
return ServiceClientType.LOGS;
68-
}
69-
70-
getServiceProtoPath(): string {
71-
return 'opentelemetry/proto/collector/logs/v1/logs_service.proto';
72-
}
73-
7472
getUrlFromConfig(config: OTLPGRPCExporterConfigNode): string {
7573
if (typeof config.url === 'string') {
7674
return config.url;

experimental/packages/exporter-logs-otlp-grpc/test/OTLPLogExporter.test.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import {
3232
} from './logsHelper';
3333
import * as core from '@opentelemetry/core';
3434
import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base';
35-
import { GrpcCompressionAlgorithm } from '@opentelemetry/otlp-grpc-exporter-base';
3635
import {
3736
IExportLogsServiceRequest,
3837
IResourceLogs,
@@ -292,7 +291,7 @@ const testCollectorExporter = (params: TestParams) => {
292291
});
293292
assert.strictEqual(
294293
collectorExporter.compression,
295-
GrpcCompressionAlgorithm.GZIP
294+
CompressionAlgorithm.GZIP
296295
);
297296
delete envSource.OTEL_EXPORTER_OTLP_COMPRESSION;
298297
});
@@ -320,44 +319,54 @@ describe('OTLPLogExporter - node (getDefaultUrl)', () => {
320319

321320
describe('when configuring via environment', () => {
322321
const envSource = process.env;
322+
323+
afterEach(function () {
324+
// Ensure we don't pollute other tests if assertions fail
325+
delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT;
326+
delete envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT;
327+
delete envSource.OTEL_EXPORTER_OTLP_HEADERS;
328+
delete envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS;
329+
sinon.restore();
330+
});
331+
323332
it('should use url defined in env', () => {
324333
envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar';
325334
const collectorExporter = new OTLPLogExporter();
326335
assert.strictEqual(collectorExporter.url, 'foo.bar');
327-
envSource.OTEL_EXPORTER_OTLP_ENDPOINT = '';
328336
});
329337
it('should override global exporter url with signal url defined in env', () => {
330338
envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar';
331339
envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = 'http://foo.logs';
332340
const collectorExporter = new OTLPLogExporter();
333341
assert.strictEqual(collectorExporter.url, 'foo.logs');
334-
envSource.OTEL_EXPORTER_OTLP_ENDPOINT = '';
335-
envSource.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT = '';
336342
});
337343
it('should include user-agent header by default', () => {
338344
const collectorExporter = new OTLPLogExporter();
339-
assert.deepStrictEqual(collectorExporter.metadata?.get('User-Agent'), [
345+
const actualMetadata =
346+
collectorExporter['_transport']['_parameters'].metadata();
347+
assert.deepStrictEqual(actualMetadata.get('User-Agent'), [
340348
`OTel-OTLP-Exporter-JavaScript/${VERSION}`,
341349
]);
342350
});
343351
it('should use headers defined via env', () => {
344352
envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar';
345353
const collectorExporter = new OTLPLogExporter();
346-
assert.deepStrictEqual(collectorExporter.metadata?.get('foo'), ['bar']);
347-
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
354+
const actualMetadata =
355+
collectorExporter['_transport']['_parameters'].metadata();
356+
assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']);
348357
});
349-
it('should override global headers config with signal headers defined via env', () => {
358+
it('should not override hard-coded headers config with headers defined via env', () => {
350359
const metadata = new grpc.Metadata();
351360
metadata.set('foo', 'bar');
352361
metadata.set('goo', 'lol');
353362
envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=jar,bar=foo';
354363
envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = 'foo=boo';
355364
const collectorExporter = new OTLPLogExporter({ metadata });
356-
assert.deepStrictEqual(collectorExporter.metadata?.get('foo'), ['boo']);
357-
assert.deepStrictEqual(collectorExporter.metadata?.get('bar'), ['foo']);
358-
assert.deepStrictEqual(collectorExporter.metadata?.get('goo'), ['lol']);
359-
envSource.OTEL_EXPORTER_OTLP_LOGS_HEADERS = '';
360-
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
365+
const actualMetadata =
366+
collectorExporter['_transport']['_parameters'].metadata();
367+
assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']);
368+
assert.deepStrictEqual(actualMetadata.get('goo'), ['lol']);
369+
assert.deepStrictEqual(actualMetadata.get('bar'), ['foo']);
361370
});
362371
});
363372

experimental/packages/exporter-trace-otlp-grpc/src/OTLPTraceExporter.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@
1616

1717
import { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base';
1818
import { baggageUtils, getEnv } from '@opentelemetry/core';
19-
import { Metadata } from '@grpc/grpc-js';
2019
import {
2120
OTLPGRPCExporterConfigNode,
2221
OTLPGRPCExporterNodeBase,
23-
ServiceClientType,
2422
validateAndNormalizeUrl,
2523
DEFAULT_COLLECTOR_URL,
24+
TraceSerializer,
2625
} from '@opentelemetry/otlp-grpc-exporter-base';
2726
import {
2827
createExportTraceServiceRequest,
2928
IExportTraceServiceRequest,
29+
IExportTraceServiceResponse,
3030
} from '@opentelemetry/otlp-transformer';
3131
import { VERSION } from './version';
3232

@@ -38,21 +38,27 @@ const USER_AGENT = {
3838
* OTLP Trace Exporter for Node
3939
*/
4040
export class OTLPTraceExporter
41-
extends OTLPGRPCExporterNodeBase<ReadableSpan, IExportTraceServiceRequest>
41+
extends OTLPGRPCExporterNodeBase<
42+
ReadableSpan,
43+
IExportTraceServiceRequest,
44+
IExportTraceServiceResponse
45+
>
4246
implements SpanExporter
4347
{
4448
constructor(config: OTLPGRPCExporterConfigNode = {}) {
45-
super(config);
46-
const headers = {
49+
const signalSpecificMetadata = {
4750
...USER_AGENT,
4851
...baggageUtils.parseKeyPairsIntoRecord(
4952
getEnv().OTEL_EXPORTER_OTLP_TRACES_HEADERS
5053
),
5154
};
52-
this.metadata ||= new Metadata();
53-
for (const [k, v] of Object.entries(headers)) {
54-
this.metadata.set(k, v);
55-
}
55+
super(
56+
config,
57+
signalSpecificMetadata,
58+
'TraceExportService',
59+
'/opentelemetry.proto.collector.trace.v1.TraceService/Export',
60+
TraceSerializer
61+
);
5662
}
5763

5864
convert(spans: ReadableSpan[]): IExportTraceServiceRequest {
@@ -63,14 +69,6 @@ export class OTLPTraceExporter
6369
return validateAndNormalizeUrl(this.getUrlFromConfig(config));
6470
}
6571

66-
getServiceClientType() {
67-
return ServiceClientType.SPANS;
68-
}
69-
70-
getServiceProtoPath(): string {
71-
return 'opentelemetry/proto/collector/trace/v1/trace_service.proto';
72-
}
73-
7472
getUrlFromConfig(config: OTLPGRPCExporterConfigNode): string {
7573
if (typeof config.url === 'string') {
7674
return config.url;

experimental/packages/exporter-trace-otlp-grpc/test/OTLPTraceExporter.test.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import {
3737
} from './traceHelper';
3838
import * as core from '@opentelemetry/core';
3939
import { CompressionAlgorithm } from '@opentelemetry/otlp-exporter-base';
40-
import { GrpcCompressionAlgorithm } from '@opentelemetry/otlp-grpc-exporter-base';
4140
import {
4241
IExportTraceServiceRequest,
4342
IResourceSpans,
@@ -302,7 +301,7 @@ const testCollectorExporter = (params: TestParams) => {
302301
});
303302
assert.strictEqual(
304303
collectorExporter.compression,
305-
GrpcCompressionAlgorithm.GZIP
304+
CompressionAlgorithm.GZIP
306305
);
307306
delete envSource.OTEL_EXPORTER_OTLP_COMPRESSION;
308307
});
@@ -330,44 +329,54 @@ describe('OTLPTraceExporter - node (getDefaultUrl)', () => {
330329

331330
describe('when configuring via environment', () => {
332331
const envSource = process.env;
332+
333+
afterEach(function () {
334+
// Ensure we don't pollute other tests if assertions fail
335+
delete envSource.OTEL_EXPORTER_OTLP_ENDPOINT;
336+
delete envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT;
337+
delete envSource.OTEL_EXPORTER_OTLP_HEADERS;
338+
delete envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS;
339+
sinon.restore();
340+
});
341+
333342
it('should use url defined in env', () => {
334343
envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar';
335344
const collectorExporter = new OTLPTraceExporter();
336345
assert.strictEqual(collectorExporter.url, 'foo.bar');
337-
envSource.OTEL_EXPORTER_OTLP_ENDPOINT = '';
338346
});
339347
it('should override global exporter url with signal url defined in env', () => {
340348
envSource.OTEL_EXPORTER_OTLP_ENDPOINT = 'http://foo.bar';
341349
envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://foo.traces';
342350
const collectorExporter = new OTLPTraceExporter();
343351
assert.strictEqual(collectorExporter.url, 'foo.traces');
344-
envSource.OTEL_EXPORTER_OTLP_ENDPOINT = '';
345-
envSource.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = '';
346352
});
347353
it('should use headers defined via env', () => {
348354
envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=bar';
349355
const collectorExporter = new OTLPTraceExporter();
350-
assert.deepStrictEqual(collectorExporter.metadata?.get('foo'), ['bar']);
351-
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
356+
const actualMetadata =
357+
collectorExporter['_transport']['_parameters'].metadata();
358+
assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']);
352359
});
353360
it('should include user agent in header', () => {
354361
const collectorExporter = new OTLPTraceExporter();
355-
assert.deepStrictEqual(collectorExporter.metadata?.get('User-Agent'), [
362+
const actualMetadata =
363+
collectorExporter['_transport']['_parameters'].metadata();
364+
assert.deepStrictEqual(actualMetadata.get('User-Agent'), [
356365
`OTel-OTLP-Exporter-JavaScript/${VERSION}`,
357366
]);
358367
});
359-
it('should override global headers config with signal headers defined via env', () => {
368+
it('should not override hard-coded headers config with headers defined via env', () => {
360369
const metadata = new grpc.Metadata();
361370
metadata.set('foo', 'bar');
362371
metadata.set('goo', 'lol');
363372
envSource.OTEL_EXPORTER_OTLP_HEADERS = 'foo=jar,bar=foo';
364373
envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = 'foo=boo';
365374
const collectorExporter = new OTLPTraceExporter({ metadata });
366-
assert.deepStrictEqual(collectorExporter.metadata?.get('foo'), ['boo']);
367-
assert.deepStrictEqual(collectorExporter.metadata?.get('bar'), ['foo']);
368-
assert.deepStrictEqual(collectorExporter.metadata?.get('goo'), ['lol']);
369-
envSource.OTEL_EXPORTER_OTLP_TRACES_HEADERS = '';
370-
envSource.OTEL_EXPORTER_OTLP_HEADERS = '';
375+
const actualMetadata =
376+
collectorExporter['_transport']['_parameters'].metadata();
377+
assert.deepStrictEqual(actualMetadata.get('foo'), ['bar']);
378+
assert.deepStrictEqual(actualMetadata.get('goo'), ['lol']);
379+
assert.deepStrictEqual(actualMetadata.get('bar'), ['foo']);
371380
});
372381
});
373382

experimental/packages/opentelemetry-exporter-metrics-otlp-grpc/src/OTLPMetricExporter.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ import { ResourceMetrics } from '@opentelemetry/sdk-metrics';
2222
import {
2323
OTLPGRPCExporterConfigNode,
2424
OTLPGRPCExporterNodeBase,
25-
ServiceClientType,
2625
validateAndNormalizeUrl,
2726
DEFAULT_COLLECTOR_URL,
27+
MetricsSerializer,
2828
} from '@opentelemetry/otlp-grpc-exporter-base';
2929
import { baggageUtils, getEnv } from '@opentelemetry/core';
30-
import { Metadata } from '@grpc/grpc-js';
3130
import {
3231
createExportMetricsServiceRequest,
3332
IExportMetricsServiceRequest,
33+
IExportMetricsServiceResponse,
3434
} from '@opentelemetry/otlp-transformer';
3535
import { VERSION } from './version';
3636

@@ -40,30 +40,24 @@ const USER_AGENT = {
4040

4141
class OTLPMetricExporterProxy extends OTLPGRPCExporterNodeBase<
4242
ResourceMetrics,
43-
IExportMetricsServiceRequest
43+
IExportMetricsServiceRequest,
44+
IExportMetricsServiceResponse
4445
> {
4546
constructor(config?: OTLPGRPCExporterConfigNode & OTLPMetricExporterOptions) {
46-
super(config);
47-
const headers = {
47+
const signalSpecificMetadata = {
4848
...USER_AGENT,
4949
...baggageUtils.parseKeyPairsIntoRecord(
5050
getEnv().OTEL_EXPORTER_OTLP_METRICS_HEADERS
5151
),
5252
...config?.headers,
5353
};
54-
55-
this.metadata ||= new Metadata();
56-
for (const [k, v] of Object.entries(headers)) {
57-
this.metadata.set(k, v);
58-
}
59-
}
60-
61-
getServiceProtoPath(): string {
62-
return 'opentelemetry/proto/collector/metrics/v1/metrics_service.proto';
63-
}
64-
65-
getServiceClientType(): ServiceClientType {
66-
return ServiceClientType.METRICS;
54+
super(
55+
config,
56+
signalSpecificMetadata,
57+
'MetricsExportService',
58+
'/opentelemetry.proto.collector.metrics.v1.MetricsService/Export',
59+
MetricsSerializer
60+
);
6761
}
6862

6963
getDefaultUrl(config: OTLPGRPCExporterConfigNode): string {

0 commit comments

Comments
 (0)