1515 */
1616
1717import { Status } from '../status' ;
18+ import { trace , metrics , SpanStatusCode } from '@opentelemetry/api' ;
1819
1920import {
2021 APICallback ,
@@ -29,6 +30,12 @@ import {GoogleError} from '../googleError';
2930
3031import { addTimeoutArg } from './timeout' ;
3132
33+ const meter = metrics . getMeter ( 'google-gax' ) ;
34+ const attemptDurationRecorder = meter . createHistogram ( 'gcp-e.attempt_duration' , {
35+ description : 'Duration of a single gRPC attempt' ,
36+ unit : 'ms' ,
37+ } ) ;
38+
3239/**
3340 * Creates a function equivalent to func, but that retries on certain
3441 * exceptions.
@@ -143,36 +150,57 @@ export function retryable(
143150 retries ++ ;
144151 let lastError = err ;
145152 const toCall = addTimeoutArg ( func , timeout ! , otherArgs ) ;
146- canceller = toCall ( argument , ( err , response , next , rawResponse ) => {
147- if ( err ) {
148- lastError = err ;
149- }
150- if ( ! err ) {
151- callback ( null , response , next , rawResponse ) ;
152- return ;
153- }
154- canceller = null ;
155- if (
156- retry . retryCodes . length > 0 &&
157- retry . retryCodes . indexOf ( err ! . code ! ) < 0
158- ) {
159- err . note =
160- 'Exception occurred in retry method that was ' +
161- 'not classified as transient' ;
162- callback ( err ) ;
163- } else {
164- const toSleep = Math . random ( ) * delay ;
165- timeoutId = setTimeout ( ( ) => {
166- now = new Date ( ) ;
167- delay = Math . min ( delay * delayMult , maxDelay ) ;
168- const timeoutCal =
169- timeout && timeoutMult ? timeout * timeoutMult : 0 ;
170- const rpcTimeout = maxTimeout ? maxTimeout : 0 ;
171- const newDeadline = deadline ? deadline - now . getTime ( ) : Infinity ;
172- timeout = Math . min ( timeoutCal , rpcTimeout , newDeadline ) ;
173- repeat ( lastError ) ;
174- } , toSleep ) ;
175- }
153+
154+ const tracer = trace . getTracer ( 'google-gax' ) ;
155+ const spanName = apiName ? `grpc.attempt.${ apiName } ` : 'grpc.attempt' ;
156+ const startTime = Date . now ( ) ;
157+
158+ canceller = tracer . startActiveSpan ( spanName , ( span ) => {
159+ const result = toCall ( argument , ( err , response , next , rawResponse ) => {
160+ const duration = Date . now ( ) - startTime ;
161+ attemptDurationRecorder . record ( duration , {
162+ 'rpc.method' : apiName || 'unknown' ,
163+ 'rpc.grpc.status_code' : err ? err . code || - 1 : 0 , // 0 is OK
164+ } ) ;
165+
166+ if ( err ) {
167+ lastError = err ;
168+ span . setStatus ( { code : SpanStatusCode . ERROR , message : err . message } ) ;
169+ console . error ( `[GAPIC Attempt Error] in ${ spanName } :` , err ) ;
170+ }
171+ if ( ! err ) {
172+ span . setStatus ( { code : SpanStatusCode . OK } ) ;
173+ }
174+ span . end ( ) ;
175+
176+ if ( ! err ) {
177+ callback ( null , response , next , rawResponse ) ;
178+ return ;
179+ }
180+ canceller = null ;
181+ if (
182+ retry . retryCodes . length > 0 &&
183+ retry . retryCodes . indexOf ( err ! . code ! ) < 0
184+ ) {
185+ err . note =
186+ 'Exception occurred in retry method that was ' +
187+ 'not classified as transient' ;
188+ callback ( err ) ;
189+ } else {
190+ const toSleep = Math . random ( ) * delay ;
191+ timeoutId = setTimeout ( ( ) => {
192+ now = new Date ( ) ;
193+ delay = Math . min ( delay * delayMult , maxDelay ) ;
194+ const timeoutCal =
195+ timeout && timeoutMult ? timeout * timeoutMult : 0 ;
196+ const rpcTimeout = maxTimeout ? maxTimeout : 0 ;
197+ const newDeadline = deadline ? deadline - now . getTime ( ) : Infinity ;
198+ timeout = Math . min ( timeoutCal , rpcTimeout , newDeadline ) ;
199+ repeat ( lastError ) ;
200+ } , toSleep ) ;
201+ }
202+ } ) ;
203+ return result ;
176204 } ) ;
177205 if ( canceller instanceof Promise ) {
178206 canceller . catch ( err => {
0 commit comments