diff --git a/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk b/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk index 90553e89770f..6b15efaa0cb9 100644 --- a/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk +++ b/core/generator/gapic-generator-typescript/templates/cjs/typescript_gapic/src/$version/$service_client.ts.njk @@ -197,6 +197,15 @@ export class {{ service.name }}Client { } {%- endif %} + {%- if api.enableTelemetryTracing %} + opts.internalTelemetryInfo = { + gcpClientService: '{{ api.loggingName }}', + gcpClientVersion: '{{ api.naming.version }}', + gcpRepo: 'googleapis/google-cloud-node', + gcpArtifact: '{{ api.publishName }}', + } + {%- endif %} + // Choose either gRPC or proto-over-HTTP implementation of google-gax. this._gaxModule = {% if not api.legacyProtoLoad %}opts.fallback ? gaxInstance.fallback : gaxInstance{% else %}gax{% endif %}; diff --git a/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk b/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk index e505db0eef11..886198d4e43d 100644 --- a/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk +++ b/core/generator/gapic-generator-typescript/templates/esm/typescript_gapic/esm/src/$version/$service_client.ts.njk @@ -203,6 +203,15 @@ export class {{ service.name }}Client { gaxInstance = gax as typeof gax; } {%- endif %} + + {%- if api.enableTelemetryTracing %} + opts.internalTelemetryInfo = { + gcpClientService: '{{ api.loggingName }}', + gcpClientVersion: '{{ api.naming.version }}', + gcpRepo: 'googleapis/google-cloud-node', + gcpArtifact: '{{ api.publishName }}', + } + {%- endif %} // Choose either gRPC or proto-over-HTTP implementation of google-gax. this._gaxModule = {% if not api.legacyProtoLoad %}opts.fallback ? gaxInstance.fallback : gaxInstance{% else %}gax{% endif %}; diff --git a/core/generator/gapic-generator-typescript/typescript/src/generator.ts b/core/generator/gapic-generator-typescript/typescript/src/generator.ts index 84a8e818781f..b66849bed378 100644 --- a/core/generator/gapic-generator-typescript/typescript/src/generator.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/generator.ts @@ -86,6 +86,7 @@ export class Generator { restNumericEnums?: boolean; mixinsOverride?: string[]; format?: string | string[]; + enableTelemetryTracing?: boolean; private root: protobuf.Root; @@ -227,6 +228,12 @@ export class Generator { } } + private readEnableTelemetryTracing() { + if (this.paramMap['enable-telemetry-tracing'] === 'true') { + this.enableTelemetryTracing = true; + } + } + private readLegacyProtoLoad() { if (this.paramMap['legacy-proto-load'] === 'true') { this.legacyProtoLoad = true; @@ -274,6 +281,7 @@ export class Generator { this.readLegacyProtoLoad(); this.readRestNumericEnums(); this.readFormat(); + this.readEnableTelemetryTracing(); } } @@ -334,6 +342,7 @@ export class Generator { legacyProtoLoad: this.legacyProtoLoad, restNumericEnums: this.restNumericEnums, mixinsOverridden: this.mixinsOverride !== undefined, + enableTelemetryTracing: this.enableTelemetryTracing, }); return api; } diff --git a/core/generator/gapic-generator-typescript/typescript/src/schema/api.ts b/core/generator/gapic-generator-typescript/typescript/src/schema/api.ts index a49572f920f2..2e8c02aadc69 100644 --- a/core/generator/gapic-generator-typescript/typescript/src/schema/api.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/schema/api.ts @@ -45,6 +45,7 @@ export class API { restNumericEnums: boolean; documentationUri: any; newIssueUri: string; + enableTelemetryTracing?: boolean; title?: string; static isIgnoredService( @@ -112,6 +113,7 @@ export class API { this.documentationUri = options.serviceYaml?.publishing?.documentation_uri ?? ''; this.newIssueUri = options.serviceYaml?.publishing?.new_issue_uri ?? ''; + this.enableTelemetryTracing = options.enableTelemetryTracing ?? false; this.title = options.serviceYaml?.title; const [allResourceDatabase, resourceDatabase] = diff --git a/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts b/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts index 0d95f68a732c..49c90c9f1b63 100644 --- a/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts +++ b/core/generator/gapic-generator-typescript/typescript/src/schema/naming.ts @@ -30,6 +30,7 @@ export interface Options { legacyProtoLoad?: boolean; restNumericEnums?: boolean; mixinsOverridden?: boolean; + enableTelemetryTracing?: boolean; } export class Naming { diff --git a/core/generator/gapic-generator-typescript/typescript/test/unit/api.ts b/core/generator/gapic-generator-typescript/typescript/test/unit/api.ts index 4be052b5bccc..0fddfb7f3e71 100644 --- a/core/generator/gapic-generator-typescript/typescript/test/unit/api.ts +++ b/core/generator/gapic-generator-typescript/typescript/test/unit/api.ts @@ -325,4 +325,25 @@ describe('src/schema/api.ts', () => { assert('.google.cloud.example.v1.MessageB' in secondCallMessages); }); }); + + it('should set enableTelemetryTracing option', () => { + const fd = {} as protos.google.protobuf.FileDescriptorProto; + fd.name = 'google/cloud/test/v1/test.proto'; + fd.package = 'google.cloud.test.v1'; + fd.service = [{} as protos.google.protobuf.ServiceDescriptorProto]; + fd.service[0].name = 'ZService'; + fd.service[0].options = { + '.google.api.defaultHost': 'hostname.example.com:443', + }; + const apiWithTracing = new API([fd], 'google.cloud.test.v1', { + grpcServiceConfig: {} as protos.grpc.service_config.ServiceConfig, + enableTelemetryTracing: true, + }); + assert.strictEqual(apiWithTracing.enableTelemetryTracing, true); + + const apiWithoutTracing = new API([fd], 'google.cloud.test.v1', { + grpcServiceConfig: {} as protos.grpc.service_config.ServiceConfig, + }); + assert.strictEqual(apiWithoutTracing.enableTelemetryTracing, false); + }); }); diff --git a/core/generator/gapic-generator-typescript/typescript/test/unit/generator.ts b/core/generator/gapic-generator-typescript/typescript/test/unit/generator.ts new file mode 100644 index 000000000000..5ec3b3e1b8a1 --- /dev/null +++ b/core/generator/gapic-generator-typescript/typescript/test/unit/generator.ts @@ -0,0 +1,399 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import assert from 'assert'; +import {describe, it, beforeEach, afterEach} from 'mocha'; +import {Generator} from '../../src/generator.js'; +import type {API} from '../../src/schema/api.js'; +import type * as protos from '../../../protos/index.js'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; + +interface TestGenerator { + getParamMap(parameter: string): void; + readGrpcServiceConfig(): Promise; + readBundleConfig(): void; + readServiceYaml(): void; + readPublishPackageName(): void; + readMainServiceName(): void; + readTemplates(): void; + readRest(): void; + readDiregapic(): void; + readHandwrittenLayer(): void; + readFormat(): void; + readEnableTelemetryTracing(): void; + readLegacyProtoLoad(): void; + readRestNumericEnums(): void; + readMixins(): void; + addProtosToResponse(): void; + buildAPIObject(): API; +} + +describe('src/generator.ts', () => { + let generator: Generator; + + beforeEach(() => { + generator = new Generator(); + }); + + function getTestGenerator(gen: Generator): TestGenerator { + return gen as unknown as TestGenerator; + } + + it('should initialize with default values', () => { + assert.deepStrictEqual(generator.request, {}); + assert.deepStrictEqual(generator.response, {}); + assert.deepStrictEqual(generator.grpcServiceConfig, {}); + assert.deepStrictEqual(generator.paramMap, {}); + assert.deepStrictEqual(generator.templates, [ + 'typescript_gapic', + 'typescript_packing_test', + 'typescript_samples', + ]); + }); + + describe('parameter parsing', () => { + it('should parse parameter options string', () => { + getTestGenerator(generator).getParamMap( + 'package-name=@google-cloud/test,transport=rest,diregapic=true', + ); + assert.strictEqual( + generator.paramMap['package-name'], + '@google-cloud/test', + ); + assert.strictEqual(generator.paramMap['transport'], 'rest'); + assert.strictEqual(generator.paramMap['diregapic'], 'true'); + }); + + it('should handle parameter without explicit value', () => { + getTestGenerator(generator).getParamMap('handwritten-layer'); + assert.strictEqual(generator.paramMap['handwritten-layer'], 'true'); + }); + + it('should handle quoted parameters', () => { + getTestGenerator(generator).getParamMap('"main-service=TestService"'); + assert.strictEqual(generator.paramMap['main-service'], 'TestService'); + }); + + it('should convert parameter keys to kebab-case', () => { + getTestGenerator(generator).getParamMap( + 'main_service=TestService,legacy_proto_load=true', + ); + assert.strictEqual(generator.paramMap['main-service'], 'TestService'); + assert.strictEqual(generator.paramMap['legacy-proto-load'], 'true'); + }); + }); + + describe('option readers', () => { + it('should read publish package name', () => { + generator.paramMap['package-name'] = '@google-cloud/speech'; + getTestGenerator(generator).readPublishPackageName(); + assert.strictEqual(generator.publishName, '@google-cloud/speech'); + }); + + it('should read main service name', () => { + generator.paramMap['main-service'] = 'Speech'; + getTestGenerator(generator).readMainServiceName(); + assert.strictEqual(generator.mainServiceName, 'Speech'); + }); + + it('should read custom templates and metadata flag', () => { + generator.paramMap['template'] = 'custom_template_1;custom_template_2'; + generator.paramMap['metadata'] = 'true'; + getTestGenerator(generator).readTemplates(); + assert.deepStrictEqual(generator.templates, [ + 'custom_template_1', + 'custom_template_2', + 'typescript_gapic_metadata', + ]); + }); + + it('should read transport rest option', () => { + generator.paramMap['transport'] = 'rest'; + getTestGenerator(generator).readRest(); + assert.strictEqual(generator.rest, true); + }); + + it('should read diregapic option', () => { + generator.paramMap['diregapic'] = 'true'; + getTestGenerator(generator).readDiregapic(); + assert.strictEqual(generator.diregapic, true); + assert.strictEqual(generator.rest, true); + }); + + it('should read handwritten-layer option', () => { + generator.paramMap['handwritten-layer'] = 'true'; + getTestGenerator(generator).readHandwrittenLayer(); + assert.strictEqual(generator.handwrittenLayer, true); + }); + + it('should read format option', () => { + generator.paramMap['format'] = 'esm;cjs'; + getTestGenerator(generator).readFormat(); + assert.deepStrictEqual(generator.format, ['esm', 'cjs']); + }); + + it('should read telemetry tracing option', () => { + generator.paramMap['enable-telemetry-tracing'] = 'true'; + getTestGenerator(generator).readEnableTelemetryTracing(); + assert.strictEqual(generator.enableTelemetryTracing, true); + }); + + it('should read legacy proto load option', () => { + generator.paramMap['legacy-proto-load'] = 'true'; + getTestGenerator(generator).readLegacyProtoLoad(); + assert.strictEqual(generator.legacyProtoLoad, true); + }); + + it('should read rest numeric enums option', () => { + generator.paramMap['rest-numeric-enums'] = 'true'; + getTestGenerator(generator).readRestNumericEnums(); + assert.strictEqual(generator.restNumericEnums, true); + }); + + it('should read mixins option', () => { + generator.paramMap['mixins'] = + 'google.iam.v1.IAMPolicy;google.longrunning.Operations'; + getTestGenerator(generator).readMixins(); + assert.deepStrictEqual(generator.mixinsOverride, [ + 'google.iam.v1.IAMPolicy', + 'google.longrunning.Operations', + ]); + }); + }); + + describe('file configurations', () => { + const tmpDir = path.join(os.tmpdir(), 'generator-test-' + Date.now()); + + beforeEach(() => { + fs.mkdirSync(tmpDir, {recursive: true}); + }); + + afterEach(() => { + fs.rmSync(tmpDir, {recursive: true, force: true}); + }); + + it('should throw when gRPC service config file does not exist', async () => { + generator.paramMap['grpc-service-config'] = path.join( + tmpDir, + 'nonexistent.json', + ); + await assert.rejects(async () => { + await getTestGenerator(generator).readGrpcServiceConfig(); + }, /ERROR: File .* cannot be opened\./); + }); + + it('should read valid gRPC service config file', async () => { + const grpcConfigPath = path.join(tmpDir, 'grpc_service_config.json'); + fs.writeFileSync( + grpcConfigPath, + JSON.stringify({ + methodConfig: [ + { + name: [{service: 'google.test.v1.TestService'}], + timeout: '60s', + }, + ], + }), + ); + generator.paramMap['grpc-service-config'] = grpcConfigPath; + await getTestGenerator(generator).readGrpcServiceConfig(); + assert.ok(generator.grpcServiceConfig.methodConfig); + assert.strictEqual(generator.grpcServiceConfig.methodConfig.length, 1); + }); + + it('should throw when bundle config file does not exist', () => { + generator.paramMap['bundle-config'] = path.join( + tmpDir, + 'nonexistent.yaml', + ); + assert.throws(() => { + getTestGenerator(generator).readBundleConfig(); + }, /ERROR: File .* cannot be opened\./); + }); + + it('should throw when service yaml file does not exist', () => { + generator.paramMap['service-yaml'] = path.join( + tmpDir, + 'nonexistent.yaml', + ); + assert.throws(() => { + getTestGenerator(generator).readServiceYaml(); + }, /ERROR: File .* cannot be opened\./); + }); + + it('should read valid service yaml file', () => { + const serviceYamlPath = path.join(tmpDir, 'service.yaml'); + fs.writeFileSync( + serviceYamlPath, + `title: Test Service +apis: + - name: google.iam.v1.IAMPolicy + - name: google.longrunning.Operations +`, + ); + generator.paramMap['service-yaml'] = serviceYamlPath; + getTestGenerator(generator).readServiceYaml(); + assert.strictEqual(generator.serviceYaml?.title, 'Test Service'); + assert.deepStrictEqual(generator.serviceYaml?.apis, [ + 'google.iam.v1.IAMPolicy', + 'google.longrunning.Operations', + ]); + }); + + it('should override mixins when mixinsOverride is set', () => { + generator.mixinsOverride = ['google.iam.v1.IAMPolicy']; + getTestGenerator(generator).readServiceYaml(); + assert.deepStrictEqual(generator.serviceYaml?.apis, [ + 'google.iam.v1.IAMPolicy', + ]); + + generator.mixinsOverride = ['none']; + getTestGenerator(generator).readServiceYaml(); + assert.deepStrictEqual(generator.serviceYaml?.apis, []); + }); + + it('should read valid bundle config file', () => { + const bundleConfigPath = path.join(tmpDir, 'bundle.yaml'); + fs.writeFileSync( + bundleConfigPath, + `interfaces: + - name: google.test.v1.TestService + methods: + - name: ListItems + batching: + thresholds: + element_count_threshold: 10 + request_byte_threshold: 100 + delay_threshold_millis: 1000 + batch_descriptor: + batched_field: items + discriminator_fields: + - parent + subresponse_field: items +`, + ); + generator.paramMap['bundle-config'] = bundleConfigPath; + getTestGenerator(generator).readBundleConfig(); + assert.strictEqual(generator.bundleConfigs.length, 1); + assert.strictEqual(generator.bundleConfigs[0].serviceName, 'TestService'); + assert.strictEqual(generator.bundleConfigs[0].methodName, 'ListItems'); + }); + }); + + describe('addProtosToResponse', () => { + it('should populate response with proto.list file', () => { + generator.request = { + protoFile: [ + {name: 'google/cloud/test/v1/test.proto'}, + {name: 'google/cloud/test/v1/other.proto'}, + ], + } as unknown as protos.google.protobuf.compiler.CodeGeneratorRequest; + generator.response = { + file: [], + } as unknown as protos.google.protobuf.compiler.CodeGeneratorResponse; + + getTestGenerator(generator).addProtosToResponse(); + + assert.strictEqual(generator.response.file?.length, 1); + assert.strictEqual(generator.response.file?.[0].name, 'proto.list'); + assert.strictEqual( + generator.response.file?.[0].content, + 'google/cloud/test/v1/test.proto\ngoogle/cloud/test/v1/other.proto\n', + ); + }); + }); + + describe('buildAPIObject', () => { + it('should throw error if protos define no services', () => { + generator.request = { + protoFile: [ + { + name: 'google/cloud/test/v1/test.proto', + package: 'google.cloud.test.v1', + }, + ], + fileToGenerate: ['google/cloud/test/v1/test.proto'], + } as protos.google.protobuf.compiler.CodeGeneratorRequest; + + assert.throws(() => { + getTestGenerator(generator).buildAPIObject(); + }, /ERROR: Protos do not define any service/); + }); + + it('should create API object when services are defined', () => { + generator.request = { + protoFile: [ + { + name: 'google/cloud/test/v1/test.proto', + package: 'google.cloud.test.v1', + service: [ + { + name: 'TestService', + options: { + '.google.api.defaultHost': 'test.googleapis.com', + }, + }, + ], + }, + ], + fileToGenerate: ['google/cloud/test/v1/test.proto'], + } as protos.google.protobuf.compiler.CodeGeneratorRequest; + + const api = getTestGenerator(generator).buildAPIObject(); + assert.ok(api); + assert.strictEqual(api.packageName, 'google.cloud.test.v1'); + }); + }); + + describe('processTemplates', () => { + it('should throw error if template location does not exist', async () => { + generator.templates = ['nonexistent_template_dir']; + const api = {} as API; + + await assert.rejects(async () => { + await generator.processTemplates(api); + }, /Template directory .* does not exist\./); + }); + }); + + describe('generate', () => { + it('should capture error message in response on failure', async () => { + const originalStdoutWrite = process.stdout.write; + let writtenBuffer: Buffer | null = null; + + process.stdout.write = (chunk: Uint8Array | string) => { + writtenBuffer = Buffer.from(chunk); + return true; + }; + + try { + generator.request = { + protoFile: [], + } as unknown as protos.google.protobuf.compiler.CodeGeneratorRequest; + + await generator.generate(); + + assert.strictEqual( + generator.response.error, + 'ERROR: Protos do not define any service, client library cannot be generated.', + ); + assert.ok(writtenBuffer); + } finally { + process.stdout.write = originalStdoutWrite; + } + }); + }); +}); diff --git a/core/generator/gapic-generator-typescript/typescript/test/unit/naming.ts b/core/generator/gapic-generator-typescript/typescript/test/unit/naming.ts index 2aec137f42ec..8ab6e94db8ff 100644 --- a/core/generator/gapic-generator-typescript/typescript/test/unit/naming.ts +++ b/core/generator/gapic-generator-typescript/typescript/test/unit/naming.ts @@ -218,4 +218,17 @@ describe('src/schema/naming.ts', () => { assert(naming); }); }); + + it('supports enableTelemetryTracing option', () => { + const descriptor = {} as protos.google.protobuf.FileDescriptorProto; + descriptor.package = 'google.namespace.service.v1beta1'; + descriptor.service = [{} as protos.google.protobuf.ServiceDescriptorProto]; + const options: Options = { + grpcServiceConfig: {} as protos.grpc.service_config.ServiceConfig, + enableTelemetryTracing: true, + }; + const naming = new Naming([descriptor], options); + assert(naming); + assert.strictEqual(naming.name, 'Service'); + }); }); diff --git a/core/packages/gax/src/clientInterface.ts b/core/packages/gax/src/clientInterface.ts index b9f3bd8b8e9c..165626715867 100644 --- a/core/packages/gax/src/clientInterface.ts +++ b/core/packages/gax/src/clientInterface.ts @@ -42,6 +42,8 @@ export interface ClientOptions // No preference; exception will be thrown if both are set to different values. universeDomain?: string; universe_domain?: string; + enableTelemetryTracing?: boolean; + internalTelemetryInfo?: InternalTelemetry; } export interface Descriptors { @@ -93,3 +95,10 @@ export interface PaginationResponse< nextPageRequest?: RequestObject; rawResponse?: ResponseObject; } + +export interface InternalTelemetry { + gcpClientService?: string; + gcpVersion?: string; + gcpRepo?: string; + gcpArtifact?: string; +} \ No newline at end of file diff --git a/core/packages/gax/src/createApiCall.ts b/core/packages/gax/src/createApiCall.ts index b3fdee987a6d..5c71300866a0 100644 --- a/core/packages/gax/src/createApiCall.ts +++ b/core/packages/gax/src/createApiCall.ts @@ -18,6 +18,7 @@ * Provides function wrappers that implement page streaming and retrying. */ +import { ClientOptions } from './clientInterface'; import {createAPICaller} from './apiCaller'; import { APICallback, @@ -27,6 +28,7 @@ import { RequestType, SimpleCallbackFunction, } from './apitypes'; +import { ClientOptions } from './clientInterface'; import {Descriptor} from './descriptor'; import {CallOptions, CallSettings, convertRetryOptions} from './gax'; import {retryable} from './normalCalls/retries'; @@ -59,6 +61,7 @@ export function createApiCall( descriptor?: Descriptor, // eslint-disable-next-line @typescript-eslint/no-unused-vars _fallback?: boolean | 'proto' | 'rest', // unused here, used in fallback.ts implementation + clientOptions?: ClientOptions ): GaxCall { // we want to be able to accept both promise resolving to a function and a // function. Currently client librares are only calling this method with a @@ -67,7 +70,12 @@ export function createApiCall( // the following apiCaller will be used for all calls of this function... const apiCaller = createAPICaller(settings, descriptor); - return ( + // Check if telemetry tracing is enabled and also check if internal telemetry information has been passed through the templates + const tracingEnabled = + clientOptions?.enableTelemetryTracing && + process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED && + clientOptions?.internalTelemetryInfo !== undefined; + const invokeCall = ( request: RequestType, callOptions?: CallOptions, callback?: APICallback, @@ -164,4 +172,12 @@ export function createApiCall( // or to cancel the ongoing call. return currentApiCaller.result(ongoingCall); }; + + if (tracingEnabled) { + console.log('tracing enabled'); + return invokeCall; + } else { + console.log('tracing disabled'); + return invokeCall; + } } diff --git a/core/packages/gax/src/observability/TracerHelper.ts b/core/packages/gax/src/observability/TracerHelper.ts new file mode 100644 index 000000000000..9e6173317623 --- /dev/null +++ b/core/packages/gax/src/observability/TracerHelper.ts @@ -0,0 +1,21 @@ +import { context, trace, Tracer } from `@opentelemetry/api`; +import * as grpc from '@grpc/grpc-js'; + +export interface StaticTraceContext { + gcpClientService?: string; + gcpVersion?: string; + gcpRepo?: string; + gcpArtifact?: string; +} + +export interface DynamicTraceContext { + clientName: string; + methodName: string; + rpcType: 'grpc' | 'http'; +} + +export function getGaxTracer(): Tracer { + return trace.getTracer('google-gax'); +} + +export async function traceAttempt() { } \ No newline at end of file diff --git a/core/packages/gax/test/unit/apiCallable.ts b/core/packages/gax/test/unit/apiCallable.ts index f341ec12f01f..71a32712445f 100644 --- a/core/packages/gax/test/unit/apiCallable.ts +++ b/core/packages/gax/test/unit/apiCallable.ts @@ -329,6 +329,30 @@ describe('createApiCall', () => { ); } }); + + describe('in regards to OpenTelemetry Tracing', () => { + let consoleSpy: sinon.SinonSpy; + beforeEach(() => { + consoleSpy = sinon.spy(console, 'log'); + }); + + afterEach(() => { + consoleSpy.restore(); + delete process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED; + }); + + it('logs "tracing enabled" when GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED and ClientOptions field is set', () => { + process.env.GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED = 'true'; + const mockClientOptions = { enableTelemetryTracing: true }; + createApiCall(() => { }, { clientOptions: mockClientOptions }); + assert(consoleSpy.calledWith('tracing enabled')); + }); + + it('logs "tracing disabled" when GOOGLE_SDK_NODE_EXPERIMENTAL_O11Y_ENABLED is not set', () => { + createApiCall(() => { }); + assert(consoleSpy.calledWith('tracing disabled')); + }); + }); }); describe('Promise', () => { diff --git a/core/packages/gax/test/unit/utils.ts b/core/packages/gax/test/unit/utils.ts index 81440541a1fb..9b1f80177332 100644 --- a/core/packages/gax/test/unit/utils.ts +++ b/core/packages/gax/test/unit/utils.ts @@ -16,6 +16,7 @@ import {GaxCallPromise} from '../../src/apitypes'; import {createApiCall as realCreateApiCall} from '../../src/createApiCall'; +import {ClientOptions} from '../../src/clientInterface'; import * as gax from '../../src/gax'; import {GoogleError} from '../../src/googleError'; import {Descriptor} from '../../src/descriptor'; @@ -42,6 +43,7 @@ export interface Options { returnCancelFunc?: boolean; cancel?: Function; deadline?: string; + clientOptions?: ClientOptions; } export function createApiCall(func: Function, opts?: Options) { @@ -78,6 +80,8 @@ export function createApiCall(func: Function, opts?: Options) { }), settings, descriptor, + undefined, + opts?.clientOptions, ) as GaxCallPromise; }