diff --git a/experimental/CHANGELOG.md b/experimental/CHANGELOG.md index 3d0f124c9d..48dc912ca2 100644 --- a/experimental/CHANGELOG.md +++ b/experimental/CHANGELOG.md @@ -19,6 +19,8 @@ All notable changes to experimental packages in this project will be documented ### :bug: (Bug Fix) +* fix(otlp-grpc-exporter-base): avoid TypeError on exporter shutdown [#4612](https://github.com/open-telemetry/opentelemetry-js/pull/4612) + ### :books: (Refine Doc) ### :house: (Internal) diff --git a/experimental/packages/otlp-grpc-exporter-base/src/grpc-exporter-transport.ts b/experimental/packages/otlp-grpc-exporter-base/src/grpc-exporter-transport.ts index 77038648ce..81e7ff4c4d 100644 --- a/experimental/packages/otlp-grpc-exporter-base/src/grpc-exporter-transport.ts +++ b/experimental/packages/otlp-grpc-exporter-base/src/grpc-exporter-transport.ts @@ -16,7 +16,12 @@ // NOTE: do not change these type imports to actual imports. Doing so WILL break `@opentelemetry/instrumentation-http`, // as they'd be imported before the http/https modules can be wrapped. -import type { Metadata, ServiceError, ChannelCredentials } from '@grpc/grpc-js'; +import type { + Metadata, + ServiceError, + ChannelCredentials, + Client, +} from '@grpc/grpc-js'; import { ExportResponse } from './export-response'; import { IExporterTransport } from './exporter-transport'; @@ -85,13 +90,13 @@ export interface GrpcExporterTransportParameters { } export class GrpcExporterTransport implements IExporterTransport { - private _client?: any; + private _client?: Client; private _metadata?: Metadata; constructor(private _parameters: GrpcExporterTransportParameters) {} shutdown() { - this._client?.shutdown(); + this._client?.close(); } send(data: Uint8Array): Promise { @@ -150,6 +155,8 @@ export class GrpcExporterTransport implements IExporterTransport { }); } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore The gRPC client constructor is created on runtime, so we don't have any types for the resulting client. this._client.export( buffer, this._metadata, diff --git a/experimental/packages/otlp-grpc-exporter-base/test/grpc-exporter-transport.test.ts b/experimental/packages/otlp-grpc-exporter-base/test/grpc-exporter-transport.test.ts index 965c01607c..f7e9769f45 100644 --- a/experimental/packages/otlp-grpc-exporter-base/test/grpc-exporter-transport.test.ts +++ b/experimental/packages/otlp-grpc-exporter-base/test/grpc-exporter-transport.test.ts @@ -136,9 +136,30 @@ describe('GrpcExporterTransport', function () { }); }); describe('shutdown', function () { + let shutdownHandle: () => void | undefined; + const serverTestContext: ServerTestContext = { + requests: [], + serverResponseProvider: () => { + return { error: null, buffer: Buffer.from([]) }; + }, + }; + + beforeEach(async function () { + shutdownHandle = await startServer(serverTestContext); + }); + afterEach(function () { + shutdownHandle(); + + // clear context + serverTestContext.requests = []; + serverTestContext.serverResponseProvider = () => { + return { error: null, buffer: Buffer.from([]) }; + }; + sinon.restore(); }); + it('before send() does not error', function () { const transport = new GrpcExporterTransport(simpleClientConfig); transport.shutdown(); @@ -146,27 +167,18 @@ describe('GrpcExporterTransport', function () { // no assertions, just checking that it does not throw any errors. }); - it('calls client shutdown if client is defined', function () { - // arrange - const transport = new GrpcExporterTransport({ - metadata: createEmptyMetadata, - timeoutMillis: 100, - grpcPath: 'path', - grpcName: 'name', - credentials: createInsecureCredentials, - compression: 'gzip', - address: 'localhost:1234', - }); - const shutdownStub = sinon.stub(); - transport['_client'] = { - shutdown: shutdownStub, - }; + it('calls _client.close() if client is defined', async function () { + const transport = new GrpcExporterTransport(simpleClientConfig); + // send something so that client is defined + await transport.send(Buffer.from([1, 2, 3])); + assert.ok(transport['_client'], '_client is not defined after send()'); + const closeSpy = sinon.spy(transport['_client'], 'close'); // act transport.shutdown(); // assert - sinon.assert.calledOnce(shutdownStub); + sinon.assert.calledOnce(closeSpy); }); }); describe('send', function () {