Skip to content

Commit 13ca1dc

Browse files
authored
feat: add debug logging support (#1903)
* feat: add debug logging support * build: add google-logging-utils in a temporary fashion * build: undo unintentional package.json changes * fix: move makeLog calls into shared classes for the most part * fix: update to anticipated release version of logger * fix: fix possible merging errors from earlier * fix: more potential merging issues * fix: more rearranging to match the older ordering * fix: merging two months of changes is sometimes trying * fix: very work in progress for switching to using interceptor and symbol based logs * fix: catch all exceptions, finish piping in method names * fix: various self-review updates * chore: put back missing comma * fix: update log name back to the original * fix: rejection interceptors must re-throw errors * chore: fix various reversions * chore: fix more linter issues * chore: remove extraneous makeLog * tests: add interceptor response shared tests * tests: add logging tests to interceptors * tests: lint fixes
1 parent a511e56 commit 13ca1dc

10 files changed

+343
-24
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"ecdsa-sig-formatter": "^1.0.11",
2222
"gaxios": "^7.0.0-rc.4",
2323
"gcp-metadata": "^7.0.0-rc.1",
24+
"google-logging-utils": "^1.0.0",
2425
"gtoken": "^8.0.0-rc.1",
2526
"jws": "^4.0.0"
2627
},

src/auth/authclient.ts

+116
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,17 @@ import {Gaxios, GaxiosOptions, GaxiosPromise, GaxiosResponse} from 'gaxios';
1717

1818
import {Credentials} from './credentials';
1919
import {OriginalAndCamel, originalOrCamelOptions} from '../util';
20+
import {log as makeLog} from 'google-logging-utils';
2021

2122
import {PRODUCT_NAME, USER_AGENT} from '../shared.cjs';
2223

24+
/**
25+
* Easy access to symbol-indexed strings on config objects.
26+
*/
27+
export type SymbolIndexString = {
28+
[key: symbol]: string | undefined;
29+
};
30+
2331
/**
2432
* Base auth configurations (e.g. from JWT or `.json` files) with conventional
2533
* camelCased options.
@@ -210,6 +218,16 @@ export abstract class AuthClient
210218
forceRefreshOnFailure = false;
211219
universeDomain = DEFAULT_UNIVERSE;
212220

221+
/**
222+
* Symbols that can be added to GaxiosOptions to specify the method name that is
223+
* making an RPC call, for logging purposes, as well as a string ID that can be
224+
* used to correlate calls and responses.
225+
*/
226+
static readonly RequestMethodNameSymbol: unique symbol = Symbol(
227+
'request method name',
228+
);
229+
static readonly RequestLogIdSymbol: unique symbol = Symbol('request log id');
230+
213231
constructor(opts: AuthClientOptions = {}) {
214232
super();
215233

@@ -229,6 +247,9 @@ export abstract class AuthClient
229247
this.transporter.interceptors.request.add(
230248
AuthClient.DEFAULT_REQUEST_INTERCEPTOR,
231249
);
250+
this.transporter.interceptors.response.add(
251+
AuthClient.DEFAULT_RESPONSE_INTERCEPTOR,
252+
);
232253
}
233254

234255
if (opts.eagerRefreshThresholdMillis) {
@@ -322,6 +343,7 @@ export abstract class AuthClient
322343
return target;
323344
}
324345

346+
static log = makeLog('auth');
325347
static readonly DEFAULT_REQUEST_INTERCEPTOR: Parameters<
326348
Gaxios['interceptors']['request']['add']
327349
>[0] = {
@@ -340,10 +362,104 @@ export abstract class AuthClient
340362
config.headers.set('User-Agent', `${userAgent} ${USER_AGENT}`);
341363
}
342364

365+
try {
366+
const symbols: SymbolIndexString =
367+
config as unknown as SymbolIndexString;
368+
const methodName = symbols[AuthClient.RequestMethodNameSymbol];
369+
370+
// This doesn't need to be very unique or interesting, it's just an aid for
371+
// matching requests to responses.
372+
const logId = `${Math.floor(Math.random() * 1000)}`;
373+
symbols[AuthClient.RequestLogIdSymbol] = logId;
374+
375+
// Boil down the object we're printing out.
376+
const logObject = {
377+
url: config.url,
378+
headers: config.headers,
379+
};
380+
if (methodName) {
381+
AuthClient.log.info(
382+
'%s [%s] request %j',
383+
methodName,
384+
logId,
385+
logObject,
386+
);
387+
} else {
388+
AuthClient.log.info('[%s] request %j', logId, logObject);
389+
}
390+
} catch (e) {
391+
// Logging must not create new errors; swallow them all.
392+
}
393+
343394
return config;
344395
},
345396
};
346397

398+
static readonly DEFAULT_RESPONSE_INTERCEPTOR: Parameters<
399+
Gaxios['interceptors']['response']['add']
400+
>[0] = {
401+
resolved: async response => {
402+
try {
403+
const symbols: SymbolIndexString =
404+
response.config as unknown as SymbolIndexString;
405+
const methodName = symbols[AuthClient.RequestMethodNameSymbol];
406+
const logId = symbols[AuthClient.RequestLogIdSymbol];
407+
if (methodName) {
408+
AuthClient.log.info(
409+
'%s [%s] response %j',
410+
methodName,
411+
logId,
412+
response.data,
413+
);
414+
} else {
415+
AuthClient.log.info('[%s] response %j', logId, response.data);
416+
}
417+
} catch (e) {
418+
// Logging must not create new errors; swallow them all.
419+
}
420+
421+
return response;
422+
},
423+
rejected: async error => {
424+
try {
425+
const symbols: SymbolIndexString =
426+
error.config as unknown as SymbolIndexString;
427+
const methodName = symbols[AuthClient.RequestMethodNameSymbol];
428+
const logId = symbols[AuthClient.RequestLogIdSymbol];
429+
if (methodName) {
430+
AuthClient.log.info(
431+
'%s [%s] error %j',
432+
methodName,
433+
logId,
434+
error.response?.data,
435+
);
436+
} else {
437+
AuthClient.log.error('[%s] error %j', logId, error.response?.data);
438+
}
439+
} catch (e) {
440+
// Logging must not create new errors; swallow them all.
441+
}
442+
443+
// Re-throw the error.
444+
throw error;
445+
},
446+
};
447+
448+
/**
449+
* Sets the method name that is making a Gaxios request, so that logging may tag
450+
* log lines with the operation.
451+
* @param config A Gaxios request config
452+
* @param methodName The method name making the call
453+
*/
454+
static setMethodName(config: GaxiosOptions, methodName: string) {
455+
try {
456+
const symbols: SymbolIndexString = config as unknown as SymbolIndexString;
457+
symbols[AuthClient.RequestMethodNameSymbol] = methodName;
458+
} catch (e) {
459+
// Logging must not create new errors; swallow them all.
460+
}
461+
}
462+
347463
/**
348464
* Retry config for Auth-related requests.
349465
*

src/auth/baseexternalclient.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -472,11 +472,13 @@ export abstract class BaseExternalAccountClient extends AuthClient {
472472
} else if (projectNumber) {
473473
// Preferable not to use request() to avoid retrial policies.
474474
const headers = await this.getRequestHeaders();
475-
const response = await this.transporter.request<ProjectInfo>({
475+
const opts: GaxiosOptions = {
476476
...BaseExternalAccountClient.RETRY_CONFIG,
477477
headers,
478478
url: `${this.cloudResourceManagerURL.toString()}${projectNumber}`,
479-
});
479+
};
480+
AuthClient.setMethodName(opts, 'getProjectId');
481+
const response = await this.transporter.request<ProjectInfo>(opts);
480482
this.projectId = response.data.projectId;
481483
return this.projectId;
482484
}
@@ -667,6 +669,7 @@ export abstract class BaseExternalAccountClient extends AuthClient {
667669
lifetime: this.serviceAccountImpersonationLifetime + 's',
668670
},
669671
};
672+
AuthClient.setMethodName(opts, 'getImpersonatedAccessToken');
670673
const response =
671674
await this.transporter.request<IamGenerateAccessTokenResponse>(opts);
672675
const successResponse = response.data;

src/auth/defaultawssecuritycredentialssupplier.ts

+9-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {ExternalAccountSupplierContext} from './baseexternalclient';
1616
import {Gaxios, GaxiosOptions} from 'gaxios';
1717
import {AwsSecurityCredentialsSupplier} from './awsclient';
1818
import {AwsSecurityCredentials} from './awsrequestsigner';
19+
import {AuthClient} from './authclient';
1920

2021
/**
2122
* Interface defining the AWS security-credentials endpoint response.
@@ -128,6 +129,7 @@ export class DefaultAwsSecurityCredentialsSupplier
128129
method: 'GET',
129130
headers: metadataHeaders,
130131
};
132+
AuthClient.setMethodName(opts, 'getAwsRegion');
131133
const response = await context.transporter.request<string>(opts);
132134
// Remove last character. For example, if us-east-2b is returned,
133135
// the region would be us-east-2.
@@ -191,6 +193,7 @@ export class DefaultAwsSecurityCredentialsSupplier
191193
method: 'PUT',
192194
headers: {'x-aws-ec2-metadata-token-ttl-seconds': '300'},
193195
};
196+
AuthClient.setMethodName(opts, '#getImdsV2SessionToken');
194197
const response = await transporter.request<string>(opts);
195198
return response.data;
196199
}
@@ -217,6 +220,7 @@ export class DefaultAwsSecurityCredentialsSupplier
217220
method: 'GET',
218221
headers: headers,
219222
};
223+
AuthClient.setMethodName(opts, '#getAwsRoleName');
220224
const response = await transporter.request<string>(opts);
221225
return response.data;
222226
}
@@ -235,11 +239,14 @@ export class DefaultAwsSecurityCredentialsSupplier
235239
headers: Headers,
236240
transporter: Gaxios,
237241
): Promise<AwsSecurityCredentialsResponse> {
238-
const response = await transporter.request<AwsSecurityCredentialsResponse>({
242+
const opts = {
239243
...this.additionalGaxiosOptions,
240244
url: `${this.securityCredentialsUrl}/${roleName}`,
241245
headers: headers,
242-
});
246+
} as GaxiosOptions;
247+
AuthClient.setMethodName(opts, '#retrieveAwsSecurityCredentials');
248+
const response =
249+
await transporter.request<AwsSecurityCredentialsResponse>(opts);
243250
return response.data;
244251
}
245252

src/auth/externalAccountAuthorizedUserClient.ts

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ class ExternalAccountAuthorizedUserHandler extends OAuthClientAuthHandler {
121121
refresh_token: refreshToken,
122122
}),
123123
};
124+
AuthClient.setMethodName(opts, 'refreshToken');
124125

125126
// Apply OAuth client authentication.
126127
this.applyClientAuthenticationOptions(opts);

src/auth/oauth2client.ts

+20-9
Original file line numberDiff line numberDiff line change
@@ -749,13 +749,16 @@ export class OAuth2Client extends AuthClient {
749749
if (this.clientAuthentication === ClientAuthentication.ClientSecretPost) {
750750
values.client_secret = this._clientSecret;
751751
}
752-
const res = await this.transporter.request<CredentialRequest>({
752+
753+
const opts = {
753754
...OAuth2Client.RETRY_CONFIG,
754755
method: 'POST',
755756
url,
756757
data: new URLSearchParams(values as {}),
757758
headers,
758-
});
759+
};
760+
AuthClient.setMethodName(opts, 'getTokenAsync');
761+
const res = await this.transporter.request<CredentialRequest>(opts);
759762
const tokens = res.data as Credentials;
760763
if (res.data && res.data.expires_in) {
761764
tokens.expiry_date = new Date().getTime() + res.data.expires_in * 1000;
@@ -813,13 +816,16 @@ export class OAuth2Client extends AuthClient {
813816
let res: GaxiosResponse<CredentialRequest>;
814817

815818
try {
816-
// request for new token
817-
res = await this.transporter.request<CredentialRequest>({
819+
const opts: GaxiosOptions = {
818820
...OAuth2Client.RETRY_CONFIG,
819821
method: 'POST',
820822
url,
821823
data: new URLSearchParams(data),
822-
});
824+
};
825+
AuthClient.setMethodName(opts, 'refreshTokenNoCache');
826+
827+
// request for new token
828+
res = await this.transporter.request<CredentialRequest>(opts);
823829
} catch (e) {
824830
if (
825831
e instanceof GaxiosError &&
@@ -1041,6 +1047,7 @@ export class OAuth2Client extends AuthClient {
10411047
url: this.getRevokeTokenURL(token).toString(),
10421048
method: 'POST',
10431049
};
1050+
AuthClient.setMethodName(opts, 'revokeToken');
10441051
if (callback) {
10451052
this.transporter
10461053
.request<RevokeCredentialsResult>(opts)
@@ -1306,10 +1313,12 @@ export class OAuth2Client extends AuthClient {
13061313
throw new Error(`Unsupported certificate format ${format}`);
13071314
}
13081315
try {
1309-
res = await this.transporter.request({
1316+
const opts = {
13101317
...OAuth2Client.RETRY_CONFIG,
13111318
url,
1312-
});
1319+
};
1320+
AuthClient.setMethodName(opts, 'getFederatedSignonCertsAsync');
1321+
res = await this.transporter.request(opts);
13131322
} catch (e) {
13141323
if (e instanceof Error) {
13151324
e.message = `Failed to retrieve verification certificates: ${e.message}`;
@@ -1377,10 +1386,12 @@ export class OAuth2Client extends AuthClient {
13771386
const url = this.endpoints.oauth2IapPublicKeyUrl.toString();
13781387

13791388
try {
1380-
res = await this.transporter.request({
1389+
const opts = {
13811390
...OAuth2Client.RETRY_CONFIG,
13821391
url,
1383-
});
1392+
};
1393+
AuthClient.setMethodName(opts, 'getIapPublicKeysAsync');
1394+
res = await this.transporter.request(opts);
13841395
} catch (e) {
13851396
if (e instanceof Error) {
13861397
e.message = `Failed to retrieve verification certificates: ${e.message}`;

src/auth/refreshclient.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
OAuth2Client,
2020
OAuth2ClientOptions,
2121
} from './oauth2client';
22+
import {AuthClient} from './authclient';
23+
import {GaxiosOptions} from 'gaxios';
2224

2325
export const USER_REFRESH_ACCOUNT_TYPE = 'authorized_user';
2426

@@ -96,7 +98,7 @@ export class UserRefreshClient extends OAuth2Client {
9698
}
9799

98100
async fetchIdToken(targetAudience: string): Promise<string> {
99-
const res = await this.transporter.request<CredentialRequest>({
101+
const opts: GaxiosOptions = {
100102
...UserRefreshClient.RETRY_CONFIG,
101103
url: this.endpoints.oauth2TokenUrl,
102104
method: 'POST',
@@ -107,8 +109,10 @@ export class UserRefreshClient extends OAuth2Client {
107109
refresh_token: this._refreshToken,
108110
target_audience: targetAudience,
109111
} as {}),
110-
});
112+
};
113+
AuthClient.setMethodName(opts, 'fetchIdToken');
111114

115+
const res = await this.transporter.request<CredentialRequest>(opts);
112116
return res.data.id_token!;
113117
}
114118

src/auth/stscredentials.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// limitations under the License.
1414

1515
import {GaxiosError, GaxiosOptions, GaxiosResponse} from 'gaxios';
16-
import {HeadersInit} from './authclient';
16+
import {AuthClient, HeadersInit} from './authclient';
1717
import {
1818
ClientAuthentication,
1919
OAuthClientAuthHandler,
@@ -218,6 +218,8 @@ export class StsCredentials extends OAuthClientAuthHandler {
218218
headers,
219219
data: new URLSearchParams(payload),
220220
};
221+
AuthClient.setMethodName(opts, 'exchangeToken');
222+
221223
// Apply OAuth client authentication.
222224
this.applyClientAuthenticationOptions(opts);
223225

src/auth/urlsubjecttokensupplier.ts

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
SubjectTokenJsonResponse,
2020
SubjectTokenSupplier,
2121
} from './identitypoolclient';
22+
import {AuthClient} from './authclient';
2223

2324
/**
2425
* Interface that defines options used to build a {@link UrlSubjectTokenSupplier}
@@ -88,6 +89,8 @@ export class UrlSubjectTokenSupplier implements SubjectTokenSupplier {
8889
method: 'GET',
8990
headers: this.headers,
9091
};
92+
AuthClient.setMethodName(opts, 'getSubjectToken');
93+
9194
let subjectToken: string | undefined;
9295
if (this.formatType === 'text') {
9396
const response = await context.transporter.request<string>(opts);
@@ -102,6 +105,7 @@ export class UrlSubjectTokenSupplier implements SubjectTokenSupplier {
102105
'Unable to parse the subject_token from the credential_source URL',
103106
);
104107
}
108+
105109
return subjectToken;
106110
}
107111
}

0 commit comments

Comments
 (0)