@@ -33,6 +33,7 @@ import {
3333 resolveExceptionMessage ,
3434 resolveExceptionStacktrace ,
3535 resolveExceptionType ,
36+ resolveResponsePayload ,
3637 resolveResponseStatusCode ,
3738} from './errorUtil' ;
3839import { getPromiseTarget , handlePromise , handleStream } from './TracerHelper' ;
@@ -57,6 +58,22 @@ export interface LogPayload {
5758 'exception.stacktrace' ?: string ;
5859}
5960
61+ /**
62+ * Resolves a `LogSeverity` value across `google-logging-utils` versions where
63+ * `LogSeverity` may be exported as a type only.
64+ */
65+ function resolveLogSeverity (
66+ level : 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR' ,
67+ ) : loggingUtils . LogSeverity {
68+ return (
69+ (
70+ loggingUtils as {
71+ LogSeverity ?: Record < string , loggingUtils . LogSeverity > ;
72+ }
73+ ) . LogSeverity ?. [ level ] ?? ( level as loggingUtils . LogSeverity )
74+ ) ;
75+ }
76+
6077/**
6178 * Returns the `google-logging-utils` logger for the given GCP client service,
6279 * defaulting to the `'google-gax'` namespace when no service name is provided.
@@ -125,6 +142,48 @@ export function buildLogAttributes(
125142 return attributes ;
126143}
127144
145+ /**
146+ * Executes `fn` and attaches stream, promise, callback, or synchronous error
147+ * handlers to invoke `onError` when the operation fails.
148+ */
149+ function executeLoggedOperation < T > (
150+ fn : ( loggedCallback ?: APICallback ) => T ,
151+ onError : ( err : unknown ) => void ,
152+ isStreamCall : boolean ,
153+ userCallback ?: APICallback ,
154+ ) : T {
155+ // Wrap user callback to log RPC errors before forwarding.
156+ let callbackInvoked = false ;
157+ const loggedCallback : APICallback | undefined = userCallback
158+ ? function ( this : unknown , ...args : Parameters < APICallback > ) {
159+ callbackInvoked = true ;
160+ const err = args [ 0 ] ;
161+ if ( err ) {
162+ onError ( err ) ;
163+ }
164+ userCallback . apply ( this , args ) ;
165+ }
166+ : undefined ;
167+
168+ // Execute call and attach error handlers for streams, promises, or sync throws.
169+ try {
170+ const result = fn ( loggedCallback ) ;
171+ const promiseTarget = ! isStreamCall ? getPromiseTarget ( result ) : null ;
172+ if ( isStreamCall && result instanceof EventEmitter ) {
173+ handleStream ( result , onError , ( ) => { } , Boolean ( loggedCallback ) ) ;
174+ } else if ( promiseTarget ) {
175+ handlePromise ( promiseTarget , onError , ( ) => { } ) ;
176+ }
177+ return result ;
178+ } catch ( syncErr ) {
179+ // Avoid logging errors thrown inside userCallback after RPC completion.
180+ if ( ! callbackInvoked ) {
181+ onError ( syncErr ) ;
182+ }
183+ throw syncErr ;
184+ }
185+ }
186+
128187/**
129188 * Executes an API call and emits a single `WARNING` log entry if the logical
130189 * request fails across promise, callback, stream, or synchronous execution boundaries.
@@ -179,17 +238,10 @@ export function logCall<T = GaxCallResult>(
179238 ...( stacktrace !== undefined ? { 'exception.stacktrace' : stacktrace } : { } ) ,
180239 } ;
181240
182- const warningSeverity =
183- (
184- loggingUtils as {
185- LogSeverity ?: { WARNING : loggingUtils . LogSeverity } ;
186- }
187- ) . LogSeverity ?. WARNING ?? ( 'WARNING' as loggingUtils . LogSeverity ) ;
188-
189241 // Emit structured WARNING log.
190242 logger (
191243 {
192- severity : warningSeverity ,
244+ severity : resolveLogSeverity ( 'WARNING' ) ,
193245 ...( traceId ? { telemetryTraceId : traceId } : { } ) ,
194246 ...( spanId ? { telemetrySpanId : spanId } : { } ) ,
195247 other : attributes ,
@@ -199,34 +251,65 @@ export function logCall<T = GaxCallResult>(
199251 ) ;
200252 } ;
201253
202- // Wrap user callback to log RPC errors before forwarding.
203- let callbackInvoked = false ;
204- const loggedCallback : APICallback | undefined = userCallback
205- ? function ( this : unknown , ...args : Parameters < APICallback > ) {
206- callbackInvoked = true ;
207- const err = args [ 0 ] ;
208- if ( err ) {
209- emitL3WarnLog ( err ) ;
210- }
211- userCallback . apply ( this , args ) ;
212- }
213- : undefined ;
254+ return executeLoggedOperation ( fn , emitL3WarnLog , isStreamCall , userCallback ) ;
255+ }
214256
215- // Execute call and attach error handlers for streams, promises, or sync throws.
216- try {
217- const result = fn ( loggedCallback ) ;
218- const promiseTarget = ! isStreamCall ? getPromiseTarget ( result ) : null ;
219- if ( isStreamCall && result instanceof EventEmitter ) {
220- handleStream ( result , emitL3WarnLog , ( ) => { } , Boolean ( loggedCallback ) ) ;
221- } else if ( promiseTarget ) {
222- handlePromise ( promiseTarget , emitL3WarnLog , ( ) => { } ) ;
223- }
224- return result ;
225- } catch ( syncErr ) {
226- // Avoid logging errors thrown inside userCallback after RPC completion.
227- if ( ! callbackInvoked ) {
228- emitL3WarnLog ( syncErr ) ;
257+ /**
258+ * Executes an individual RPC transport attempt and emits a `DEBUG` log entry
259+ * if the attempt fails across promise, callback, stream, or synchronous execution boundaries.
260+ *
261+ * @template T
262+ * @param {AttemptTraceContext } attemptDynamicArgs - Dynamic context for the RPC attempt.
263+ * @param {StaticTraceContext } staticArgs - Static context for the client library.
264+ * @param {function } fn - The transport attempt operation to execute. Receives the wrapped
265+ * callback when `userCallback` is supplied, otherwise `undefined`.
266+ * @param {boolean } [isStreamCall=false] - Whether the operation returns a stream (`EventEmitter`).
267+ * @param {APICallback } [userCallback] - Optional attempt callback.
268+ * @returns {T } The return value of `fn`.
269+ */
270+ export function logNetworkAttempt < T = GaxCallResult > (
271+ attemptDynamicArgs : AttemptTraceContext ,
272+ staticArgs : StaticTraceContext ,
273+ fn : ( loggedCallback ?: APICallback ) => T ,
274+ isStreamCall = false ,
275+ userCallback ?: APICallback ,
276+ ) : T {
277+ // Snapshot attempt context in case async rejection loses active context.
278+ const initialContext = context . active ( ) ;
279+
280+ const emitL4DebugLog = ( err : unknown ) => {
281+ // Resolve logger and active T4 attempt span context for this attempt.
282+ const logger = getGaxLogger ( staticArgs ?. gcpClientService ) ;
283+ const attemptSpan =
284+ getActiveTraceContext ( ) ?. attemptSpan ??
285+ getActiveTraceContext ( initialContext ) ?. attemptSpan ;
286+ const spanContext = attemptSpan ?. spanContext ( ) ;
287+ const traceId = spanContext ?. traceId ;
288+ const spanId = spanContext ?. spanId ;
289+
290+ // Build structured log attributes and attach attempt metadata payload.
291+ const attributes = buildLogAttributes ( err , attemptDynamicArgs , staticArgs ) ;
292+ const responsePayload = resolveResponsePayload (
293+ ( err as { metadata ?: unknown } | null | undefined ) ?. metadata ,
294+ ) ;
295+ if ( responsePayload !== undefined ) {
296+ attributes [ 'response.payload' ] = responsePayload ;
229297 }
230- throw syncErr ;
231- }
298+
299+ // Emit structured DEBUG log for the failed attempt.
300+ const message = resolveExceptionMessage ( err , attemptDynamicArgs . rpcType ) ;
301+ logger (
302+ {
303+ severity : resolveLogSeverity ( 'DEBUG' ) ,
304+ ...( traceId ? { telemetryTraceId : traceId } : { } ) ,
305+ ...( spanId ? { telemetrySpanId : spanId } : { } ) ,
306+ other : attributes ,
307+ } ,
308+ 'RPC attempt failed: %s' ,
309+ message || err ,
310+ ) ;
311+ } ;
312+
313+ // Intercept and dispatch execution boundaries for the attempt.
314+ return executeLoggedOperation ( fn , emitL4DebugLog , isStreamCall , userCallback ) ;
232315}
0 commit comments