feat(gax): emit T4 attempt spans by shivanee-p · Pull Request #9496 · googleapis/google-cloud-node · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
440c2ce
feat(gax): inject active OpenTelemetry span context into gRPC and HTT…
shivanee-p Oct 1, 2026
998dc2c
docs(gax): remove instrumentation library setup from observability sa…
shivanee-p Oct 1, 2026
7cad06f
style(gax): fix prettier formatting in timeout, util, and unit tests
shivanee-p Oct 1, 2026
8577f00
fix(gax): clone plain object metadata before injecting span context
shivanee-p Oct 1, 2026
8e7e676
refactor(gax): move OpenTelemetry context propagation into TracerHelp…
shivanee-p Oct 1, 2026
ad27eba
fix(gax): resolve TypeScript narrowing error in metadataPropagation test
shivanee-p Oct 1, 2026
ce310fa
feat(gax): emit T4 per-attempt client spans directly without instrume…
shivanee-p Oct 1, 2026
c460b33
feat(gax): add resend_count to T4 spans and consolidate T4 unit tests
shivanee-p Oct 2, 2026
396dcd0
feat(gax): refine T3 and T4 span method, status code, and client attr…
shivanee-p Oct 2, 2026
0a1dd07
refactor(gax): narrow dynamicArgs and staticArgs before calling trace…
shivanee-p Oct 2, 2026
0c600fa
docs(gax): add concise comments to traceAttempt and createApiCall
shivanee-p Oct 2, 2026
41949c4
feat(gax): set url.domain on T3 spans
shivanee-p Oct 2, 2026
2707dac
Wrap function call to avoid variable mutation and memory leaks
shivanee-p Oct 2, 2026
dd3f236
refactor(gax): remove type assertions from attempt tracing wrapper in…
shivanee-p Oct 2, 2026
1fb3fac
fix: remove gcp.client.service attribute from tracer initial attributes
shivanee-p Oct 2, 2026
df61886
docs(gax): replace internal T3 and T4 tracing terms with client reque…
shivanee-p Oct 2, 2026
726eec4
fix(gax): do not increment resend_count on paginated page requests
shivanee-p Oct 2, 2026
3142302
style(gax): fix prettier formatting in apiCallable tests
shivanee-p Oct 2, 2026
bb837ee
fix(gax): update HTTP attempt span naming, set status.message on erro…
shivanee-p Oct 3, 2026
0d99934
fix(gax): wire matched url.template from REST transcoding into HTTP a…
shivanee-p Oct 3, 2026
f0845f0
fix(gax): preserve transcode call in fallbackRest for regapic spy com…
shivanee-p Oct 3, 2026
b338db3
feat(gax): set status.message attribute on T3 spans when an error occurs
shivanee-p Oct 3, 2026
27eb0e3
feat(gax): set rpc.method on T3 spans from the last T4 child span
shivanee-p Oct 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 4 additions & 17 deletions core/packages/gax/samples/observability.js
87 changes: 74 additions & 13 deletions core/packages/gax/src/createApiCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
* Provides function wrappers that implement page streaming and retrying.
*/

import {context} from '@opentelemetry/api';
import {createAPICaller} from './apiCaller';
import {
APICallback,
GaxCall,
GRPCCall,
GRPCCallOtherArgs,
GRPCCallResult,
RequestType,
SimpleCallbackFunction,
} from './apitypes';
Expand All @@ -36,8 +38,10 @@ import {StreamProxy} from './streamingCalls/streaming';
import {warn} from './warnings';
import {
traceCall,
traceAttempt,
StaticTraceContext,
DynamicTraceContext,
AttemptTraceContext,
ResendRecorder,
} from './observability/TracerHelper';
import {resolveStaticTraceContext} from './observability/metadataResolver';
Expand Down Expand Up @@ -77,13 +81,35 @@ export function createApiCall(
const apiCaller = createAPICaller(settings, descriptor);

const tracingEnabled = checkTelemetryEnabled(settings);
const staticArgs: StaticTraceContext | undefined = tracingEnabled
? resolveStaticTraceContext(settings)
: undefined;
const serviceName = settings.apiName?.split('.').pop() ?? '';
const isFallback = Boolean(_fallback);
const dynamicArgs: DynamicTraceContext | undefined = tracingEnabled
? {
clientName: serviceName ? `${serviceName}Client` : '',
methodName: settings.otherArgs?.internalMethodName ?? '',
rpcType: isFallback ? 'http' : 'grpc',
}
: undefined;
// Context for per-attempt low level network CLIENT spans.
const attemptDynamicArgs: AttemptTraceContext | undefined =
tracingEnabled && dynamicArgs
? {
...dynamicArgs,
apiName: settings.apiName ?? '',
}
: undefined;

const invokeCall = (
request: RequestType,
callOptions?: CallOptions,
callback?: APICallback,
recordResend?: ResendRecorder,
) => {
// Capture the active client request span context to parent async low level network attempt spans.
const parentContext = tracingEnabled ? context.active() : undefined;
let currentApiCaller = apiCaller;

let thisSettings: CallSettings;
Expand All @@ -108,6 +134,19 @@ export function createApiCall(

const ongoingCall = currentApiCaller.init(callback);

// Track resends per logical request so auto-paginated subsequent page
// requests start at resendCount = 0 instead of being counted as retries.
let attemptResendCount = 0;
let isRetryAttempt = false;
const originalRecordResend = recordResend;
if (tracingEnabled) {
recordResend = () => {
originalRecordResend?.();
attemptResendCount++;
isRetryAttempt = true;
};
}

// Server-streaming calls retry inside the stream rather than through
// `retryable`, so the recorder is handed to the stream itself. It is the
// same span either way: the proxy outlives its resumptions, so the call
Expand All @@ -119,11 +158,42 @@ export function createApiCall(
funcPromise
.then((func: GRPCCall) => {
// Initially, the function is just what gRPC server stub contains.
func = currentApiCaller.wrap(func);
let wrappedFunc = currentApiCaller.wrap(func);

const streaming = (currentApiCaller as StreamingApiCaller).descriptor
?.streaming;

// Wrap the transport call so each attempt (initial send and retries) emits a low level network CLIENT span.
if (tracingEnabled && attemptDynamicArgs && staticArgs) {
const callerWrappedFunc = wrappedFunc;
wrappedFunc = (
argument: {},
metadata: {},
options: {},
attemptCallback: APICallback,
): GRPCCallResult => {
if (!isRetryAttempt) {
attemptResendCount = 0;
}
isRetryAttempt = false;
const resendCount = attemptResendCount;
return traceAttempt(
{...attemptDynamicArgs, resendCount},
staticArgs,
tracedAttemptCallback =>
Reflect.apply(callerWrappedFunc, undefined, [
argument,
metadata,
options,
tracedAttemptCallback ?? attemptCallback,
]),
Boolean(streaming),
attemptCallback,
parentContext,
);
};
}
Comment thread
shivanee-p marked this conversation as resolved.

const retry = thisSettings.retry;

if (streaming && retry) {
Expand Down Expand Up @@ -159,7 +229,7 @@ export function createApiCall(
retry.backoffSettings.initialRpcTimeoutMillis ??=
thisSettings.timeout;
return retryable(
func,
wrappedFunc,
thisSettings.retry!,
thisSettings.otherArgs as GRPCCallOtherArgs,
thisSettings.apiName,
Expand All @@ -168,7 +238,7 @@ export function createApiCall(
}
}
return addTimeoutArg(
func,
wrappedFunc,
thisSettings.timeout,
thisSettings.otherArgs as GRPCCallOtherArgs,
);
Expand All @@ -192,16 +262,7 @@ export function createApiCall(
return currentApiCaller.result(ongoingCall);
};

if (tracingEnabled) {
const staticArgs: StaticTraceContext = resolveStaticTraceContext(settings);

const serviceName = settings.apiName?.split('.').pop() ?? '';
const isFallback = Boolean(_fallback);
const dynamicArgs: DynamicTraceContext = {
clientName: serviceName ? `${serviceName}Client` : '',
methodName: settings.otherArgs?.internalMethodName ?? '',
rpcType: isFallback ? 'http' : 'grpc',
};
if (tracingEnabled && dynamicArgs && staticArgs) {
const isStreamingCall = apiCaller instanceof StreamingApiCaller;
return (
request: RequestType,
Expand Down
17 changes: 16 additions & 1 deletion core/packages/gax/src/fallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ export class GrpcClient {
httpRules?: Array<google.api.IHttpRule>;
numericEnums: boolean;
minifyJson: boolean;
private _servicePath?: string;
private _port?: number;

/**
* In rare cases users might need to deallocate all memory consumed by loaded protos.
Expand Down Expand Up @@ -156,6 +158,10 @@ export class GrpcClient {
this.httpRules = (options as GrpcClientOptions).httpRules;
this.numericEnums = (options as GrpcClientOptions).numericEnums ?? false;
this.minifyJson = (options as GrpcClientOptions).minifyJson ?? false;
this._servicePath =
(options as GrpcClientOptions).servicePath ||
(options as GrpcClientOptions).apiEndpoint;
this._port = (options as GrpcClientOptions).port;
}

/**
Expand Down Expand Up @@ -279,12 +285,21 @@ export class GrpcClient {
}
return metadata;
}
const otherArgs: Record<string, unknown> = {
metadataBuilder: buildMetadata,
};
if (this._servicePath) {
otherArgs.servicePath = this._servicePath;
}
if (this._port !== undefined) {
otherArgs.port = this._port;
}
return gax.constructSettings(
serviceName,
clientConfig,
configOverrides,
Status,
{metadataBuilder: buildMetadata},
otherArgs,
enableTelemetryTracing,
internalTelemetryInfo,
);
Expand Down
3 changes: 2 additions & 1 deletion core/packages/gax/src/fallbackRest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {defaultToObjectOptions} from './fallback';
import {FetchParameters, FetchParametersMethod} from './fallbackServiceStub';
import {GoogleError} from './googleError';
import {rpcCodeFromHttpStatusCode} from './status';
import {transcode} from './transcoding';
import {getMatchedUrlTemplate, transcode} from './transcoding';

export function encodeRequest(
rpc: protobuf.Method,
Expand Down Expand Up @@ -82,6 +82,7 @@ export function encodeRequest(
return {
method,
url,
urlTemplate: getMatchedUrlTemplate(transcoded),
headers,
body,
};
Expand Down
4 changes: 4 additions & 0 deletions core/packages/gax/src/fallbackServiceStub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import {StreamArrayParser} from './streamArrayParser';
import {defaultToObjectOptions} from './fallback';
import {GoogleError} from './googleError';
import {setAttemptHttpMethod} from './observability/TracerHelper';
import {rpcCodeFromHttpStatusCode, Status} from './status';
import {pipeline, PipelineSource} from 'stream';
import type {Agent as HttpAgent} from 'http';
Expand Down Expand Up @@ -68,6 +69,7 @@
body: Buffer | Uint8Array | string;
method: FetchParametersMethod;
url: string;
urlTemplate?: string;
}

// helper function used to properly format empty responses
Expand Down Expand Up @@ -302,6 +304,8 @@
};
}

setAttemptHttpMethod(fetchParameters.method, fetchParameters.urlTemplate);

const cancelController = new AbortController();
const cancelSignal = cancelController.signal as AbortSignal;
let cancelRequested = false;
Expand Down Expand Up @@ -433,7 +437,7 @@
// state, as the handlers below do.
if (err && (timedOut || !cancelRequested)) {
if (callback) {
callback(err);

Check warning on line 440 in core/packages/gax/src/fallbackServiceStub.ts

View workflow job for this annotation

GitHub Actions / lint

Avoid calling back inside of a promise
}
streamArrayParser.emit('error', err);
}
Expand All @@ -448,7 +452,7 @@
Promise.resolve(response.ok),
response.arrayBuffer(),
])
.then(([ok, buffer]: [boolean, Buffer | ArrayBuffer]) => {

Check warning on line 455 in core/packages/gax/src/fallbackServiceStub.ts

View workflow job for this annotation

GitHub Actions / lint

Avoid nesting promises
const response = responseDecoder(
rpc,
ok,
Expand All @@ -458,7 +462,7 @@
callback!(null, response);
return;
})
.catch((err: Error) => {

Check warning on line 465 in core/packages/gax/src/fallbackServiceStub.ts

View workflow job for this annotation

GitHub Actions / lint

Avoid nesting promises
// The deadline can expire after the response headers arrive but
// before the body is fully read, which rejects here rather than
// in the outer handler.
Expand All @@ -481,7 +485,7 @@
// state we recorded.
if (timedOut || !cancelRequested) {
if (callback) {
callback(callErr);

Check warning on line 488 in core/packages/gax/src/fallbackServiceStub.ts

View workflow job for this annotation

GitHub Actions / lint

Avoid calling back inside of a promise
}
streamArrayParser.emit('error', callErr);
}
Expand Down Expand Up @@ -539,12 +543,12 @@
// nobody is listening to any more.
if (timedOut || !cancelRequested) {
if (callback) {
callback(err);

Check warning on line 546 in core/packages/gax/src/fallbackServiceStub.ts

View workflow job for this annotation

GitHub Actions / lint

Avoid calling back inside of a promise
}
streamArrayParser.emit('error', err);
}
} else if (callback) {
callback(err);

Check warning on line 551 in core/packages/gax/src/fallbackServiceStub.ts

View workflow job for this annotation

GitHub Actions / lint

Avoid calling back inside of a promise
} else {
throw err;
}
Expand Down
18 changes: 17 additions & 1 deletion core/packages/gax/src/grpc.ts
Loading
Loading