Skip to content

Commit 24761d6

Browse files
authoredFeb 24, 2025··
feat: add client logging with slf4j (#1586)
This Pr contains changes for adding client logging capability to auth. see [go/java-client-logging-design](http://goto.google.com/java-client-logging-design) Some logging setups are mirror of same setting in Gax ([pr 3403](googleapis/sdk-platform-java#3403)) Changes includes: - add slf4j as optional dependency, enable logging only when GOOGLE_SDK_JAVA_LOGGING=true - user app should add SLF4J + binding dependencies accordingly. For SLF4J 1x, we record extra info with MDC; for SLF4J2x, we record extra info with KeyValuePairs. More user guide to be added via README (see [draft](https://docs.google.com/document/d/1g8HJCstkEEZZc73gFlQO8uPGs07SkWKkr7NG-Yg4bvM/edit?tab=t.0#heading=h.9t58aw3up7to)). - If env var not true, or no binding present, default to no-op. - Add log for request and response made to auth endpoints for UserCredentials, ServiceAccountCredentials, ComputeEngineCredentials, ImpersonatedCredentials. For token values included, added hash in log. Note this PR mainly focuses on adding logging setups, logging statements can be added incrementally later if necessary. Here is an example logging output for access token request from UserCredentials when DEBUG level is allowed. (TO UPDATE) ``` {"@timestamp":"2024-12-04T21:10:48.596382834-05:00","@Version":"1","message":"Sending auth request to refresh access token.","logger_name":"com.google.auth.oauth2.UserCredentials","thread_name":"main","severity":"DEBUG","level_value":10000,"request.method":"POST","request.headers":{"x-goog-api-client":"gl-java/19.0.1 auth/1.32.2-SNAPSHOT cred-type/u","accept-encoding":["gzip"]},"request.url":"https://foo.com/bar","request.payload":{"refresh_token":"bae0258be92ea1d1e14f984507bee05ff4502a29104f4101d22a4a88706b0fc0","grant_type":"refresh_token","client_secret":"2d3c802ef65d75e88b098792e2268cd3f55a08bcbb8c8c4672f1195d1951d4b5","client_id":"ya29.1.AADtN_UtlxN3PuGAxrN2XQnZTVRvDyVWnYq4I6dws"}} {"@timestamp":"2024-12-04T21:10:48.624914522-05:00","@Version":"1","message":"Received auth respond for refresh access token.","logger_name":"com.google.auth.oauth2.UserCredentials","thread_name":"main","severity":"INFO","level_value":20000,"response.status":"200","response.headers":"{}"} {"@timestamp":"2024-12-04T21:10:48.627483862-05:00","@Version":"1","message":"Auth response payload.","logger_name":"com.google.auth.oauth2.UserCredentials","thread_name":"main","severity":"DEBUG","level_value":10000,"access_token":"1/MkS*****KPY2","refresh_token":"1/Tl6*****yGWY","token_type":"Bearer","expires_in":"3600"} ``` Testing setup - Currently added flavor of tests: - unit tests with no extra dependency - unit tests depending on either binding be present, or logback implementation to capture logging for test - test for log behaviors for various requests where logs are added. (see LoggingTest) Remaining issue with test scenarios: 1. when no binding present: this is tested via regular tests 2. when env var is T and binding present: tested via test-logging (profile with logback deps) 3. no SLF4J present: same logic as 1, it should be fine with coverage of 1. This is hard to setup this because SLF4J is needed at compile. 4. SLF4J 1x + binding: Hard to setup because SLF4J 2x is needed at compile
1 parent 54b4c1a commit 24761d6

23 files changed

+1733
-22
lines changed
 

‎.github/workflows/ci.yaml

+16
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@ jobs:
3636
- run: .kokoro/build.sh
3737
env:
3838
JOB_TYPE: test
39+
units-logging:
40+
runs-on: ubuntu-latest
41+
strategy:
42+
fail-fast: false
43+
matrix:
44+
java: [11, 17, 21]
45+
steps:
46+
- uses: actions/checkout@v4
47+
- uses: actions/setup-java@v4
48+
with:
49+
distribution: temurin
50+
java-version: ${{matrix.java}}
51+
- run: java -version
52+
- run: .kokoro/build.sh
53+
env:
54+
JOB_TYPE: test-logging
3955
units-java8:
4056
# Building using Java 17 and run the tests with Java 8 runtime
4157
name: "units (8)"

‎.github/workflows/sonar.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ jobs:
3838
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
3939
run: |
4040
mvn -B verify -Dcheckstyle.skip \
41+
-Djacoco.skip=true \
4142
-DenableFullTestCoverage \
4243
-Dsonar.coverage.jacoco.xmlReportPaths=oauth2_http/target/site/jacoco/jacoco.xml \
4344
org.sonarsource.scanner.maven:sonar-maven-plugin:sonar \
4445
-Dsonar.projectKey=googleapis_google-auth-library-java \
4546
-Dsonar.organization=googleapis \
4647
-Dsonar.host.url=https://sonarcloud.io
48+

‎.kokoro/build.sh

+8-2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ test)
5151
mvn test -B -ntp -Dclirr.skip=true -Denforcer.skip=true ${SUREFIRE_JVM_OPT}
5252
RETURN_CODE=$?
5353
;;
54+
test-logging)
55+
echo "SUREFIRE_JVM_OPT: ${SUREFIRE_JVM_OPT}"
56+
mvn clean test -P '!slf4j2x,slf4j2x-test' -B -ntp -Dclirr.skip=true -Denforcer.skip=true ${SUREFIRE_JVM_OPT}
57+
RETURN_CODE=$?
58+
;;
5459
lint)
5560
mvn com.coveo:fmt-maven-plugin:check -B -ntp
5661
RETURN_CODE=$?
@@ -66,6 +71,7 @@ integration)
6671
-DtrimStackTrace=false \
6772
-Dclirr.skip=true \
6873
-Denforcer.skip=true \
74+
-Djacoco.skip=true \
6975
-fae \
7076
verify
7177
RETURN_CODE=$?
@@ -74,14 +80,14 @@ graalvmA)
7480
# Run Unit and Integration Tests with Native Image
7581
bash .kokoro/populate-secrets.sh
7682
export GOOGLE_APPLICATION_CREDENTIALS="${KOKORO_GFILE_DIR}/secret_manager/java-it-service-account"
77-
mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Pnative -Pnative-test test -pl 'oauth2_http'
83+
mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Pnative -Pnative-test -Pslf4j2x test -pl 'oauth2_http'
7884
RETURN_CODE=$?
7985
;;
8086
graalvmB)
8187
# Run Unit and Integration Tests with Native Image
8288
bash .kokoro/populate-secrets.sh
8389
export GOOGLE_APPLICATION_CREDENTIALS="${KOKORO_GFILE_DIR}/secret_manager/java-it-service-account"
84-
mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Pnative -Pnative-test test -pl 'oauth2_http'
90+
mvn -B ${INTEGRATION_TEST_ARGS} -ntp -Pnative -Pnative-test -Pslf4j2x test -pl 'oauth2_http'
8591
RETURN_CODE=$?
8692
;;
8793
samples)

‎.kokoro/dependencies.sh

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ retry_with_backoff 3 10 \
5454
mvn install -B -V -ntp \
5555
-DskipTests=true \
5656
-Dmaven.javadoc.skip=true \
57+
-Djacoco.skip=true \
5758
-Dclirr.skip=true
5859

5960
mvn -B dependency:analyze -DfailOnWarning=true

‎oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java

+30
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import com.google.common.base.Joiner;
5252
import com.google.common.base.MoreObjects.ToStringHelper;
5353
import com.google.common.collect.ImmutableList;
54+
import com.google.common.collect.ImmutableMap;
5455
import com.google.common.collect.ImmutableSet;
5556
import com.google.errorprone.annotations.CanIgnoreReturnValue;
5657
import java.io.BufferedReader;
@@ -94,6 +95,8 @@ public class ComputeEngineCredentials extends GoogleCredentials
9495
static final Duration COMPUTE_REFRESH_MARGIN = Duration.ofMinutes(3).plusSeconds(45);
9596

9697
private static final Logger LOGGER = Logger.getLogger(ComputeEngineCredentials.class.getName());
98+
private static final LoggerProvider LOGGER_PROVIDER =
99+
LoggerProvider.forClazz(ComputeEngineCredentials.class);
97100

98101
static final String DEFAULT_METADATA_SERVER_URL = "http://metadata.google.internal";
99102

@@ -371,11 +374,14 @@ public AccessToken refreshAccessToken() throws IOException {
371374
throw new IOException(METADATA_RESPONSE_EMPTY_CONTENT_ERROR_MESSAGE);
372375
}
373376
GenericData responseData = response.parseAs(GenericData.class);
377+
LoggingUtils.logResponsePayload(
378+
responseData, LOGGER_PROVIDER, "Response payload for access token");
374379
String accessToken =
375380
OAuth2Utils.validateString(responseData, "access_token", PARSE_ERROR_PREFIX);
376381
int expiresInSeconds =
377382
OAuth2Utils.validateInt32(responseData, "expires_in", PARSE_ERROR_PREFIX);
378383
long expiresAtMilliseconds = clock.currentTimeMillis() + expiresInSeconds * 1000;
384+
379385
return new AccessToken(accessToken, new Date(expiresAtMilliseconds));
380386
}
381387

@@ -430,6 +436,12 @@ public IdToken idTokenWithAudience(String targetAudience, List<IdTokenProvider.O
430436
throw new IOException(METADATA_RESPONSE_EMPTY_CONTENT_ERROR_MESSAGE);
431437
}
432438
String rawToken = response.parseAsString();
439+
440+
LoggingUtils.log(
441+
LOGGER_PROVIDER,
442+
Level.FINE,
443+
ImmutableMap.of("idToken", rawToken),
444+
"Response Payload for ID token");
433445
return IdToken.create(rawToken);
434446
}
435447

@@ -451,7 +463,23 @@ private HttpResponse getMetadataResponse(
451463
request.setThrowExceptionOnExecuteError(false);
452464
HttpResponse response;
453465
try {
466+
String requestMessage;
467+
String responseMessage;
468+
if (requestType.equals(RequestType.ID_TOKEN_REQUEST)) {
469+
requestMessage = "Sending request to get ID token";
470+
responseMessage = "Received response for ID token request";
471+
} else if (requestType.equals(RequestType.ACCESS_TOKEN_REQUEST)) {
472+
requestMessage = "Sending request to refresh access token";
473+
responseMessage = "Received response for refresh access token";
474+
} else {
475+
// TODO: this includes get universe domain and get default sa.
476+
// refactor for more clear logging message.
477+
requestMessage = "Sending request for universe domain/default service account";
478+
responseMessage = "Received response for universe domain/default service account";
479+
}
480+
LoggingUtils.logRequest(request, LOGGER_PROVIDER, requestMessage);
454481
response = request.execute();
482+
LoggingUtils.logResponse(response, LOGGER_PROVIDER, responseMessage);
455483
} catch (UnknownHostException exception) {
456484
throw new IOException(
457485
"ComputeEngineCredentials cannot find the metadata server. This is"
@@ -730,6 +758,8 @@ private String getDefaultServiceAccount() throws IOException {
730758
throw new IOException(METADATA_RESPONSE_EMPTY_CONTENT_ERROR_MESSAGE);
731759
}
732760
GenericData responseData = response.parseAs(GenericData.class);
761+
LoggingUtils.logResponsePayload(
762+
responseData, LOGGER_PROVIDER, "Received default service account payload");
733763
Map<String, Object> defaultAccount =
734764
OAuth2Utils.validateMap(responseData, "default", PARSE_ERROR_ACCOUNT);
735765
return OAuth2Utils.validateString(defaultAccount, "email", PARSE_ERROR_ACCOUNT);

‎oauth2_http/java/com/google/auth/oauth2/IamUtils.java

+12
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class IamUtils {
7272
"https://iamcredentials.%s/v1/projects/-/serviceAccounts/%s:signBlob";
7373
private static final String PARSE_ERROR_MESSAGE = "Error parsing error message response. ";
7474
private static final String PARSE_ERROR_SIGNATURE = "Error parsing signature response. ";
75+
private static final LoggerProvider LOGGER_PROVIDER = LoggerProvider.forClazz(IamUtils.class);
7576

7677
// Following guidance for IAM retries:
7778
// https://cloud.google.com/iam/docs/retry-strategy#errors-to-retry
@@ -154,7 +155,11 @@ private static String getSignature(
154155
IamUtils.IAM_RETRYABLE_STATUS_CODES.contains(response.getStatusCode())));
155156
request.setIOExceptionHandler(new HttpBackOffIOExceptionHandler(backoff));
156157

158+
LoggingUtils.logRequest(
159+
request, LOGGER_PROVIDER, "Sending request to get signature to sign the blob");
157160
HttpResponse response = request.execute();
161+
LoggingUtils.logResponse(
162+
response, LOGGER_PROVIDER, "Received response for signature to sign the blob");
158163
int statusCode = response.getStatusCode();
159164
if (statusCode >= 400 && statusCode < HttpStatusCodes.STATUS_CODE_SERVER_ERROR) {
160165
GenericData responseError = response.parseAs(GenericData.class);
@@ -181,6 +186,8 @@ private static String getSignature(
181186
}
182187

183188
GenericData responseData = response.parseAs(GenericData.class);
189+
LoggingUtils.logResponsePayload(
190+
responseData, LOGGER_PROVIDER, "Response payload for sign blob");
184191
return OAuth2Utils.validateString(responseData, "signedBlob", PARSE_ERROR_SIGNATURE);
185192
}
186193

@@ -234,7 +241,10 @@ static IdToken getIdToken(
234241
MetricsUtils.getGoogleCredentialsMetricsHeader(
235242
RequestType.ID_TOKEN_REQUEST, credentialTypeForMetrics));
236243

244+
LoggingUtils.logRequest(request, LOGGER_PROVIDER, "Sending request to get ID token");
237245
HttpResponse response = request.execute();
246+
247+
LoggingUtils.logResponse(response, LOGGER_PROVIDER, "Received response for ID token request");
238248
int statusCode = response.getStatusCode();
239249
if (statusCode >= 400 && statusCode < HttpStatusCodes.STATUS_CODE_SERVER_ERROR) {
240250
GenericData responseError = response.parseAs(GenericData.class);
@@ -259,6 +269,8 @@ static IdToken getIdToken(
259269
}
260270

261271
GenericJson responseData = response.parseAs(GenericJson.class);
272+
LoggingUtils.logResponsePayload(
273+
responseData, LOGGER_PROVIDER, "Response payload for ID token request");
262274
String rawToken = OAuth2Utils.validateString(responseData, "token", PARSE_ERROR_MESSAGE);
263275
return IdToken.create(rawToken);
264276
}

‎oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java

+7
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ public class ImpersonatedCredentials extends GoogleCredentials
110110
private int lifetime;
111111
private String iamEndpointOverride;
112112
private final String transportFactoryClassName;
113+
private static final LoggerProvider LOGGER_PROVIDER =
114+
LoggerProvider.forClazz(ImpersonatedCredentials.class);
113115

114116
private transient HttpTransportFactory transportFactory;
115117

@@ -553,12 +555,17 @@ public AccessToken refreshAccessToken() throws IOException {
553555

554556
HttpResponse response = null;
555557
try {
558+
LoggingUtils.logRequest(request, LOGGER_PROVIDER, "Sending request to refresh access token");
556559
response = request.execute();
560+
LoggingUtils.logResponse(
561+
response, LOGGER_PROVIDER, "Received response for refresh access token");
557562
} catch (IOException e) {
558563
throw new IOException("Error requesting access token", e);
559564
}
560565

561566
GenericData responseData = response.parseAs(GenericData.class);
567+
LoggingUtils.logResponsePayload(
568+
responseData, LOGGER_PROVIDER, "Response payload for access token");
562569
response.disconnect();
563570

564571
String accessToken =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
*
15+
* * Neither the name of Google LLC nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package com.google.auth.oauth2;
33+
34+
import org.slf4j.Logger;
35+
36+
class LoggerProvider {
37+
38+
private Logger logger;
39+
private final Class<?> clazz;
40+
41+
private LoggerProvider(Class<?> clazz) {
42+
this.clazz = clazz;
43+
}
44+
45+
static LoggerProvider forClazz(Class<?> clazz) {
46+
return new LoggerProvider(clazz);
47+
}
48+
49+
Logger getLogger() {
50+
if (logger == null) {
51+
this.logger = Slf4jUtils.getLogger(clazz);
52+
}
53+
return logger;
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
*
15+
* * Neither the name of Google LLC nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package com.google.auth.oauth2;
33+
34+
import com.google.api.client.http.HttpRequest;
35+
import com.google.api.client.http.HttpResponse;
36+
import com.google.api.client.util.GenericData;
37+
import java.util.Map;
38+
import java.util.logging.Level;
39+
40+
class LoggingUtils {
41+
42+
static final String GOOGLE_SDK_JAVA_LOGGING = "GOOGLE_SDK_JAVA_LOGGING";
43+
private static EnvironmentProvider environmentProvider =
44+
SystemEnvironmentProvider.getInstance(); // this may be reset for testing purpose
45+
46+
private static boolean loggingEnabled = isLoggingEnabled();
47+
48+
// expose this setter only for testing purposes
49+
static void setEnvironmentProvider(EnvironmentProvider provider) {
50+
environmentProvider = provider;
51+
// Recalculate LOGGING_ENABLED after setting the new provider
52+
loggingEnabled = isLoggingEnabled();
53+
}
54+
55+
static boolean isLoggingEnabled() {
56+
String enableLogging = environmentProvider.getEnv(GOOGLE_SDK_JAVA_LOGGING);
57+
return "true".equalsIgnoreCase(enableLogging);
58+
}
59+
60+
static void logRequest(HttpRequest request, LoggerProvider loggerProvider, String message) {
61+
if (loggingEnabled) {
62+
Slf4jLoggingHelpers.logRequest(request, loggerProvider, message);
63+
}
64+
}
65+
66+
static void logResponse(HttpResponse response, LoggerProvider loggerProvider, String message) {
67+
if (loggingEnabled) {
68+
Slf4jLoggingHelpers.logResponse(response, loggerProvider, message);
69+
}
70+
}
71+
72+
static void logResponsePayload(
73+
GenericData genericData, LoggerProvider loggerProvider, String message) {
74+
if (loggingEnabled) {
75+
Slf4jLoggingHelpers.logResponsePayload(genericData, loggerProvider, message);
76+
}
77+
}
78+
79+
// generic log method to use when not logging standard request, response and payload
80+
static void log(
81+
LoggerProvider loggerProvider, Level level, Map<String, Object> contextMap, String message) {
82+
if (loggingEnabled) {
83+
Slf4jLoggingHelpers.log(loggerProvider, level, contextMap, message);
84+
}
85+
}
86+
87+
private LoggingUtils() {}
88+
}

‎oauth2_http/java/com/google/auth/oauth2/ServiceAccountCredentials.java

+20-3
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ public class ServiceAccountCredentials extends GoogleCredentials
9696
private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. ";
9797
private static final int TWELVE_HOURS_IN_SECONDS = 43200;
9898
private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600;
99+
private static final LoggerProvider LOGGER_PROVIDER =
100+
LoggerProvider.forClazz(ServiceAccountCredentials.class);
99101

100102
private final String clientId;
101103
private final String clientEmail;
@@ -503,6 +505,11 @@ boolean isConfiguredForDomainWideDelegation() {
503505
return serviceAccountUser != null && serviceAccountUser.length() > 0;
504506
}
505507

508+
private GenericData parseResponseAs(HttpResponse response) throws IOException {
509+
GenericData genericData = response.parseAs(GenericData.class);
510+
LoggingUtils.logResponsePayload(genericData, LOGGER_PROVIDER, "Response payload");
511+
return genericData;
512+
}
506513
/**
507514
* Refreshes the OAuth2 access token by getting a new access token using a JSON Web Token (JWT).
508515
*/
@@ -531,6 +538,7 @@ public AccessToken refreshAccessToken() throws IOException {
531538
}
532539
request.setParser(new JsonObjectParser(jsonFactory));
533540

541+
LoggingUtils.logRequest(request, LOGGER_PROVIDER, "Sending request to refresh access token");
534542
ExponentialBackOff backoff =
535543
new ExponentialBackOff.Builder()
536544
.setInitialIntervalMillis(OAuth2Utils.INITIAL_RETRY_INTERVAL_MILLIS)
@@ -553,6 +561,8 @@ public AccessToken refreshAccessToken() throws IOException {
553561

554562
try {
555563
response = request.execute();
564+
LoggingUtils.logResponse(
565+
response, LOGGER_PROVIDER, "Received response for refresh access token");
556566
} catch (HttpResponseException re) {
557567
String message = String.format(errorTemplate, re.getMessage(), getIssuer());
558568
throw GoogleAuthException.createWithTokenEndpointResponseException(re, message);
@@ -561,7 +571,7 @@ public AccessToken refreshAccessToken() throws IOException {
561571
e, String.format(errorTemplate, e.getMessage(), getIssuer()));
562572
}
563573

564-
GenericData responseData = response.parseAs(GenericData.class);
574+
GenericData responseData = parseResponseAs(response);
565575
String accessToken =
566576
OAuth2Utils.validateString(responseData, "access_token", PARSE_ERROR_PREFIX);
567577
int expiresInSeconds =
@@ -611,9 +621,12 @@ private IdToken getIdTokenOauthEndpoint(String targetAudience) throws IOExceptio
611621
MetricsUtils.getGoogleCredentialsMetricsHeader(
612622
RequestType.ID_TOKEN_REQUEST, getMetricsCredentialType()));
613623

624+
LoggingUtils.logRequest(request, LOGGER_PROVIDER, "Sending request to get ID token");
614625
HttpResponse httpResponse = executeRequest(request);
615626

616-
GenericData responseData = httpResponse.parseAs(GenericData.class);
627+
LoggingUtils.logResponse(
628+
httpResponse, LOGGER_PROVIDER, "Received response for ID token request");
629+
GenericData responseData = parseResponseAs(httpResponse);
617630
String rawToken = OAuth2Utils.validateString(responseData, "id_token", PARSE_ERROR_PREFIX);
618631
return IdToken.create(rawToken);
619632
}
@@ -654,9 +667,13 @@ private IdToken getIdTokenIamEndpoint(String targetAudience) throws IOException
654667
HttpRequest request = buildIdTokenRequest(iamIdTokenUri, transportFactory, content);
655668
// Use the Access Token from the SSJWT to request the ID Token from IAM Endpoint
656669
request.setHeaders(new HttpHeaders().set(AuthHttpConstants.AUTHORIZATION, accessToken));
670+
671+
LoggingUtils.logRequest(request, LOGGER_PROVIDER, "Sending request to get ID token");
657672
HttpResponse httpResponse = executeRequest(request);
673+
LoggingUtils.logResponse(
674+
httpResponse, LOGGER_PROVIDER, "Received response for ID token request");
658675

659-
GenericData responseData = httpResponse.parseAs(GenericData.class);
676+
GenericData responseData = parseResponseAs(httpResponse);
660677
// IAM Endpoint returns `token` instead of `id_token`
661678
String rawToken = OAuth2Utils.validateString(responseData, "token", PARSE_ERROR_PREFIX);
662679
return IdToken.create(rawToken);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
*
15+
* * Neither the name of Google LLC nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package com.google.auth.oauth2;
33+
34+
import com.google.api.client.http.HttpRequest;
35+
import com.google.api.client.http.HttpResponse;
36+
import com.google.api.client.http.UrlEncodedContent;
37+
import com.google.api.client.http.json.JsonHttpContent;
38+
import com.google.api.client.util.GenericData;
39+
import com.google.gson.Gson;
40+
import java.nio.charset.StandardCharsets;
41+
import java.security.MessageDigest;
42+
import java.security.NoSuchAlgorithmException;
43+
import java.util.Arrays;
44+
import java.util.HashMap;
45+
import java.util.Map;
46+
import java.util.Set;
47+
import java.util.TreeSet;
48+
import java.util.logging.Level;
49+
import org.slf4j.Logger;
50+
51+
/** Contains helper methods to log auth requests and responses */
52+
class Slf4jLoggingHelpers {
53+
private static final Gson gson = new Gson();
54+
private static final Set<String> SENSITIVE_KEYS = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
55+
56+
static {
57+
SENSITIVE_KEYS.addAll(
58+
Arrays.asList(
59+
"token",
60+
"assertion",
61+
"access_token",
62+
"client_secret",
63+
"refresh_token",
64+
"signedBlob",
65+
"authorization"));
66+
}
67+
68+
static void logRequest(HttpRequest request, LoggerProvider loggerProvider, String message) {
69+
try {
70+
Logger logger = loggerProvider.getLogger();
71+
if (logger.isInfoEnabled()) {
72+
Map<String, Object> loggingDataMap = new HashMap<>();
73+
loggingDataMap.put("request.method", request.getRequestMethod());
74+
loggingDataMap.put("request.url", request.getUrl().toString());
75+
76+
Map<String, Object> headers = new HashMap<>();
77+
request
78+
.getHeaders()
79+
.forEach(
80+
(key, val) -> {
81+
if (SENSITIVE_KEYS.contains(key)) {
82+
String hashedVal = calculateSHA256Hash(String.valueOf(val));
83+
headers.put(key, hashedVal);
84+
} else {
85+
headers.put(key, val);
86+
}
87+
});
88+
loggingDataMap.put("request.headers", gson.toJson(headers));
89+
90+
if (request.getContent() != null && logger.isDebugEnabled()) {
91+
// are payload always GenericData? If so, can parse and store in json
92+
if (request.getContent() instanceof UrlEncodedContent) {
93+
// this is parsed to GenericData because that is how it is constructed.
94+
GenericData data = (GenericData) ((UrlEncodedContent) request.getContent()).getData();
95+
Map<String, Object> contextMap = parseGenericData(data);
96+
loggingDataMap.put("request.payload", gson.toJson(contextMap));
97+
} else if (request.getContent() instanceof JsonHttpContent) {
98+
String jsonData = gson.toJson(((JsonHttpContent) request.getContent()).getData());
99+
loggingDataMap.put("request.payload", jsonData);
100+
}
101+
102+
Slf4jUtils.log(logger, org.slf4j.event.Level.DEBUG, loggingDataMap, message);
103+
} else {
104+
105+
Slf4jUtils.log(logger, org.slf4j.event.Level.INFO, loggingDataMap, message);
106+
}
107+
}
108+
} catch (Exception e) {
109+
// let logging fail silently
110+
}
111+
}
112+
113+
/** Logs response status code, status.message, and headers */
114+
static void logResponse(HttpResponse response, LoggerProvider loggerProvider, String message) {
115+
try {
116+
Logger logger = loggerProvider.getLogger();
117+
if (logger.isInfoEnabled()) {
118+
Map<String, Object> responseLogDataMap = new HashMap<>();
119+
responseLogDataMap.put("response.status", String.valueOf(response.getStatusCode()));
120+
responseLogDataMap.put("response.status.message", response.getStatusMessage());
121+
122+
Map<String, Object> headers = new HashMap<>(response.getHeaders());
123+
responseLogDataMap.put("response.headers", headers.toString());
124+
Slf4jUtils.log(logger, org.slf4j.event.Level.INFO, responseLogDataMap, message);
125+
}
126+
} catch (Exception e) {
127+
// let logging fail silently
128+
}
129+
}
130+
131+
/** Logs parsed response payload */
132+
static void logResponsePayload(
133+
GenericData genericData, LoggerProvider loggerProvider, String message) {
134+
try {
135+
136+
Logger logger = loggerProvider.getLogger();
137+
if (logger.isDebugEnabled()) {
138+
Map<String, Object> contextMap = parseGenericData(genericData);
139+
Slf4jUtils.log(logger, org.slf4j.event.Level.DEBUG, contextMap, message);
140+
}
141+
} catch (Exception e) {
142+
// let logging fail silently
143+
}
144+
}
145+
146+
static void log(
147+
LoggerProvider loggerProvider, Level level, Map<String, Object> contextMap, String message) {
148+
try {
149+
Logger logger = loggerProvider.getLogger();
150+
org.slf4j.event.Level slf4jLevel = matchUtilLevelToSLF4JLevel(level);
151+
Slf4jUtils.log(logger, slf4jLevel, contextMap, message);
152+
} catch (Exception e) {
153+
// let logging fail silently
154+
}
155+
}
156+
157+
static org.slf4j.event.Level matchUtilLevelToSLF4JLevel(Level level) {
158+
if (level == Level.SEVERE) {
159+
return org.slf4j.event.Level.ERROR;
160+
} else if (level == Level.WARNING) {
161+
return org.slf4j.event.Level.WARN;
162+
} else if (level == Level.INFO) {
163+
return org.slf4j.event.Level.INFO;
164+
} else if (level == Level.FINE) {
165+
return org.slf4j.event.Level.DEBUG;
166+
} else {
167+
return org.slf4j.event.Level.TRACE;
168+
}
169+
}
170+
171+
private static Map<String, Object> parseGenericData(GenericData genericData) {
172+
Map<String, Object> contextMap = new HashMap<>();
173+
genericData.forEach(
174+
(key, val) -> {
175+
if (SENSITIVE_KEYS.contains(key)) {
176+
String secretString = String.valueOf(val);
177+
String hashedVal = calculateSHA256Hash(secretString);
178+
contextMap.put(key, hashedVal);
179+
} else {
180+
contextMap.put(key, val.toString());
181+
}
182+
});
183+
return contextMap;
184+
}
185+
186+
// calculate SHA256Hash so we do not print secrets directly to log
187+
private static String calculateSHA256Hash(String data) {
188+
try {
189+
MessageDigest digest = MessageDigest.getInstance("SHA-256");
190+
byte[] inputBytes = data.getBytes(StandardCharsets.UTF_8);
191+
byte[] hashBytes = digest.digest(inputBytes);
192+
return bytesToHex(hashBytes);
193+
} catch (NoSuchAlgorithmException e) {
194+
return "Error calculating SHA-256 hash."; // do not fail for logging failures
195+
}
196+
}
197+
198+
private static String bytesToHex(byte[] hash) {
199+
StringBuilder hexString = new StringBuilder(2 * hash.length);
200+
for (byte b : hash) {
201+
String hex = Integer.toHexString(0xff & b);
202+
if (hex.length() == 1) {
203+
hexString.append('0');
204+
}
205+
hexString.append(hex);
206+
}
207+
return hexString.toString();
208+
}
209+
210+
private Slf4jLoggingHelpers() {}
211+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
*
15+
* * Neither the name of Google LLC nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package com.google.auth.oauth2;
33+
34+
import com.google.gson.Gson;
35+
import java.util.Map;
36+
import java.util.Map.Entry;
37+
import org.slf4j.ILoggerFactory;
38+
import org.slf4j.Logger;
39+
import org.slf4j.LoggerFactory;
40+
import org.slf4j.MDC;
41+
import org.slf4j.spi.LoggingEventBuilder;
42+
43+
/** Contains util methods to get SLF4J logger and log conditionally based SLF4J major version */
44+
class Slf4jUtils {
45+
46+
private static final Logger NO_OP_LOGGER = org.slf4j.helpers.NOPLogger.NOP_LOGGER;
47+
private static final Gson gson = new Gson();
48+
private static boolean isSLF4J2x;
49+
50+
static {
51+
// this class was added as part of the Fluent API introduced since v2.0.0
52+
// (https://www.slf4j.org/manual.html#fluent), not available in v1.7.36
53+
// see
54+
// https://github.com/qos-ch/slf4j/commits/v_2.0.0/slf4j-api/src/main/java/org/slf4j/event/KeyValuePair.java
55+
isSLF4J2x = checkIfClazzAvailable("org.slf4j.event.KeyValuePair");
56+
}
57+
58+
static boolean checkIfClazzAvailable(String clazzName) {
59+
try {
60+
Class.forName(clazzName);
61+
return true;
62+
} catch (ClassNotFoundException e) {
63+
return false;
64+
}
65+
}
66+
67+
private Slf4jUtils() {}
68+
69+
static Logger getLogger(Class<?> clazz) {
70+
return getLogger(clazz, new DefaultLoggerFactoryProvider());
71+
}
72+
73+
// constructor with LoggerFactoryProvider to make testing easier
74+
static Logger getLogger(Class<?> clazz, LoggerFactoryProvider factoryProvider) {
75+
if (LoggingUtils.isLoggingEnabled()) {
76+
ILoggerFactory loggerFactory = factoryProvider.getLoggerFactory();
77+
return loggerFactory.getLogger(clazz.getName());
78+
} else {
79+
// use SLF4j's NOP logger regardless of bindings
80+
return NO_OP_LOGGER;
81+
}
82+
}
83+
84+
static void log(
85+
Logger logger, org.slf4j.event.Level level, Map<String, Object> contextMap, String message) {
86+
if (isSLF4J2x) {
87+
logWithKeyValuePair(logger, level, contextMap, message);
88+
} else {
89+
logWithMDC(logger, level, contextMap, message);
90+
}
91+
}
92+
93+
// exposed for testing
94+
static void logWithMDC(
95+
Logger logger, org.slf4j.event.Level level, Map<String, Object> contextMap, String message) {
96+
if (!contextMap.isEmpty()) {
97+
for (Entry<String, Object> entry : contextMap.entrySet()) {
98+
String key = entry.getKey();
99+
Object value = entry.getValue();
100+
101+
MDC.put(key, value instanceof String ? (String) value : gson.toJson(value));
102+
}
103+
}
104+
switch (level) {
105+
case TRACE:
106+
logger.trace(message);
107+
break;
108+
case DEBUG:
109+
logger.debug(message);
110+
break;
111+
case INFO:
112+
logger.info(message);
113+
break;
114+
case WARN:
115+
logger.warn(message);
116+
break;
117+
case ERROR:
118+
logger.error(message);
119+
break;
120+
default:
121+
logger.debug(message);
122+
// Default to DEBUG level
123+
}
124+
if (!contextMap.isEmpty()) {
125+
// MDC carries contextual information in log messages.
126+
// It is tied to thread, and is safer to clear it as we intend to tie info to log entries.
127+
MDC.clear();
128+
}
129+
}
130+
131+
private static void logWithKeyValuePair(
132+
Logger logger, org.slf4j.event.Level level, Map<String, Object> contextMap, String message) {
133+
LoggingEventBuilder loggingEventBuilder;
134+
switch (level) {
135+
case TRACE:
136+
loggingEventBuilder = logger.atTrace();
137+
break;
138+
case DEBUG:
139+
loggingEventBuilder = logger.atDebug();
140+
break;
141+
case INFO:
142+
loggingEventBuilder = logger.atInfo();
143+
break;
144+
case WARN:
145+
loggingEventBuilder = logger.atWarn();
146+
break;
147+
case ERROR:
148+
loggingEventBuilder = logger.atError();
149+
break;
150+
default:
151+
loggingEventBuilder = logger.atDebug();
152+
// Default to DEBUG level
153+
}
154+
contextMap.forEach(loggingEventBuilder::addKeyValue);
155+
loggingEventBuilder.log(message);
156+
}
157+
158+
interface LoggerFactoryProvider {
159+
ILoggerFactory getLoggerFactory();
160+
}
161+
162+
static class DefaultLoggerFactoryProvider implements LoggerFactoryProvider {
163+
@Override
164+
public ILoggerFactory getLoggerFactory() {
165+
return LoggerFactory.getILoggerFactory();
166+
}
167+
}
168+
}

‎oauth2_http/java/com/google/auth/oauth2/UserCredentials.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ public class UserCredentials extends GoogleCredentials implements IdTokenProvide
6868
private static final String GRANT_TYPE = "refresh_token";
6969
private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. ";
7070
private static final long serialVersionUID = -4800758775038679176L;
71+
private static final LoggerProvider LOGGER_PROVIDER =
72+
LoggerProvider.forClazz(UserCredentials.class);
7173

7274
private final String clientId;
7375
private final String clientSecret;
@@ -283,14 +285,20 @@ private GenericData doRefreshAccessToken() throws IOException {
283285
HttpResponse response;
284286

285287
try {
288+
LoggingUtils.logRequest(request, LOGGER_PROVIDER, "Sending request to refresh access token");
286289
response = request.execute();
290+
LoggingUtils.logResponse(
291+
response, LOGGER_PROVIDER, "Received response for refresh access token");
287292
} catch (HttpResponseException re) {
288293
throw GoogleAuthException.createWithTokenEndpointResponseException(re);
289294
} catch (IOException e) {
290295
throw GoogleAuthException.createWithTokenEndpointIOException(e);
291296
}
292297

293-
return response.parseAs(GenericData.class);
298+
GenericData data = response.parseAs(GenericData.class);
299+
300+
LoggingUtils.logResponsePayload(data, LOGGER_PROVIDER, "Response payload for access token");
301+
return data;
294302
}
295303

296304
/**

‎oauth2_http/javatests/com/google/auth/oauth2/ImpersonatedCredentialsTest.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,8 @@ public class ImpersonatedCredentialsTest extends BaseSerializationTest {
120120
private static final String PROJECT_ID = "project-id";
121121
public static final String IMPERSONATED_CLIENT_EMAIL =
122122
"impersonated-account@iam.gserviceaccount.com";
123-
private static final List<String> IMMUTABLE_SCOPES_LIST = ImmutableList.of("scope1", "scope2");
124-
private static final int VALID_LIFETIME = 300;
123+
static final List<String> IMMUTABLE_SCOPES_LIST = ImmutableList.of("scope1", "scope2");
124+
static final int VALID_LIFETIME = 300;
125125
private static final int INVALID_LIFETIME = 43210;
126126
private static JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
127127

@@ -164,7 +164,7 @@ public void setup() throws IOException {
164164
mockTransportFactory = new MockIAMCredentialsServiceTransportFactory();
165165
}
166166

167-
private GoogleCredentials getSourceCredentials() throws IOException {
167+
static GoogleCredentials getSourceCredentials() throws IOException {
168168
MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
169169
PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(SA_PRIVATE_KEY_PKCS8);
170170
ServiceAccountCredentials sourceCredentials =

‎oauth2_http/javatests/com/google/auth/oauth2/LoggingTest.java

+522
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
*
15+
* * Neither the name of Google LLC nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package com.google.auth.oauth2;
33+
34+
import static org.junit.Assert.assertFalse;
35+
import static org.junit.Assert.assertTrue;
36+
37+
import org.junit.Before;
38+
import org.junit.Test;
39+
40+
public class LoggingUtilsTest {
41+
42+
private TestEnvironmentProvider testEnvironmentProvider;
43+
44+
@Before
45+
public void setup() {
46+
testEnvironmentProvider = new TestEnvironmentProvider();
47+
}
48+
49+
@Test
50+
public void testIsLoggingEnabled_true() {
51+
testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "true");
52+
LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
53+
assertTrue(LoggingUtils.isLoggingEnabled());
54+
testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "TRUE");
55+
LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
56+
assertTrue(LoggingUtils.isLoggingEnabled());
57+
testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "True");
58+
LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
59+
assertTrue(LoggingUtils.isLoggingEnabled());
60+
}
61+
62+
@Test
63+
public void testIsLoggingEnabled_defaultToFalse() {
64+
LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
65+
assertFalse(LoggingUtils.isLoggingEnabled());
66+
}
67+
}

‎oauth2_http/javatests/com/google/auth/oauth2/ServiceAccountCredentialsTest.java

+10-9
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@
8787
@RunWith(JUnit4.class)
8888
public class ServiceAccountCredentialsTest extends BaseSerializationTest {
8989

90-
private static final String CLIENT_EMAIL =
90+
static final String CLIENT_EMAIL =
9191
"36680232662-vrd7ji19qe3nelgchd0ah2csanun6bnr@developer.gserviceaccount.com";
9292
private static final String CLIENT_ID =
9393
"36680232662-vrd7ji19qe3nelgchd0ah2csanun6bnr.apps.googleusercontent.com";
@@ -105,14 +105,14 @@ public class ServiceAccountCredentialsTest extends BaseSerializationTest {
105105
+ "gidhycxS86dxpEljnOMCw8CKoUBd5I880IUahEiUltk7OLJYS/Ts1wbn3kPOVX3wyJs8WBDtBkFrDHW2ezth2QJ"
106106
+ "ADj3e1YhMVdjJW5jqwlD/VNddGjgzyunmiZg0uOXsHXbytYmsA545S8KRQFaJKFXYYFo2kOjqOiC1T2cAzMDjCQ"
107107
+ "==\n-----END PRIVATE KEY-----\n";
108-
private static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2";
109-
private static final Collection<String> SCOPES = Collections.singletonList("dummy.scope");
108+
static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2";
109+
static final Collection<String> SCOPES = Collections.singletonList("dummy.scope");
110110
private static final Collection<String> DEFAULT_SCOPES =
111111
Collections.singletonList("dummy.default.scope");
112112
private static final String USER = "user@example.com";
113113
private static final String PROJECT_ID = "project-id";
114114
private static final Collection<String> EMPTY_SCOPES = Collections.emptyList();
115-
private static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo");
115+
static final URI CALL_URI = URI.create("http://googleapis.com/testapi/v1/foo");
116116
private static final String JWT_AUDIENCE = "http://googleapis.com/";
117117
private static final HttpTransportFactory DUMMY_TRANSPORT_FACTORY =
118118
new MockTokenServerTransportFactory();
@@ -127,19 +127,20 @@ public class ServiceAccountCredentialsTest extends BaseSerializationTest {
127127
private static final int INVALID_LIFETIME = 43210;
128128
private static final String JWT_ACCESS_PREFIX = "Bearer ";
129129

130-
private ServiceAccountCredentials.Builder createDefaultBuilderWithToken(String accessToken)
130+
static ServiceAccountCredentials.Builder createDefaultBuilderWithToken(String accessToken)
131131
throws IOException {
132132
MockTokenServerTransportFactory transportFactory = new MockTokenServerTransportFactory();
133133
transportFactory.transport.addServiceAccount(CLIENT_EMAIL, accessToken);
134134
return createDefaultBuilder().setHttpTransportFactory(transportFactory);
135135
}
136136

137-
private ServiceAccountCredentials.Builder createDefaultBuilderWithScopes(
137+
private static ServiceAccountCredentials.Builder createDefaultBuilderWithScopes(
138138
Collection<String> scopes) throws IOException {
139139
return createDefaultBuilder().setScopes(scopes);
140140
}
141141

142-
private ServiceAccountCredentials.Builder createDefaultBuilderWithKey(PrivateKey privateKey) {
142+
private static ServiceAccountCredentials.Builder createDefaultBuilderWithKey(
143+
PrivateKey privateKey) {
143144
ServiceAccountCredentials.Builder builder =
144145
ServiceAccountCredentials.newBuilder()
145146
.setClientId(CLIENT_ID)
@@ -153,7 +154,7 @@ private ServiceAccountCredentials.Builder createDefaultBuilderWithKey(PrivateKey
153154
return builder;
154155
}
155156

156-
private ServiceAccountCredentials.Builder createDefaultBuilder() throws IOException {
157+
static ServiceAccountCredentials.Builder createDefaultBuilder() throws IOException {
157158
PrivateKey privateKey = OAuth2Utils.privateKeyFromPkcs8(PRIVATE_KEY_PKCS8);
158159
return createDefaultBuilderWithKey(privateKey);
159160
}
@@ -884,7 +885,7 @@ public void idTokenWithAudience_oauthFlow_targetAudienceMatchesAudClaim() throws
884885
targetAudience,
885886
tokenCredential.getIdToken().getJsonWebSignature().getPayload().getAudience());
886887

887-
// verify id token request metrics headers
888+
// verify ID token request metrics headers
888889
Map<String, List<String>> idTokenRequestHeader =
889890
transportFactory.transport.getRequest().getHeaders();
890891
com.google.auth.oauth2.TestUtils.validateMetricsHeader(idTokenRequestHeader, "it", "sa");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
*
15+
* * Neither the name of Google LLC nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package com.google.auth.oauth2;
33+
34+
import static org.junit.Assert.assertEquals;
35+
import static org.junit.Assert.assertTrue;
36+
import static org.mockito.Mockito.mock;
37+
import static org.mockito.Mockito.when;
38+
39+
import ch.qos.logback.classic.spi.ILoggingEvent;
40+
import com.google.api.client.http.GenericUrl;
41+
import com.google.api.client.http.HttpRequest;
42+
import com.google.api.client.http.HttpRequestFactory;
43+
import com.google.api.client.http.UrlEncodedContent;
44+
import com.google.api.client.util.GenericData;
45+
import com.google.gson.JsonParser;
46+
import com.google.gson.JsonSyntaxException;
47+
import java.io.IOException;
48+
import java.util.HashMap;
49+
import java.util.List;
50+
import java.util.Map;
51+
import org.junit.Before;
52+
import org.junit.Test;
53+
import org.slf4j.Logger;
54+
import org.slf4j.LoggerFactory;
55+
import org.slf4j.event.KeyValuePair;
56+
import org.slf4j.event.Level;
57+
58+
// part of Slf4jUtils test that needs logback dependency
59+
public class Slf4jUtilsLogbackTest {
60+
61+
private static final Logger LOGGER = LoggerFactory.getLogger(Slf4jUtilsLogbackTest.class);
62+
63+
private TestEnvironmentProvider testEnvironmentProvider;
64+
65+
@Before
66+
public void setup() {
67+
testEnvironmentProvider = new TestEnvironmentProvider();
68+
}
69+
70+
@Test
71+
public void testLogWithMDC_slf4jLogger() {
72+
73+
TestAppender testAppender = setupTestLogger();
74+
75+
Map<String, Object> contextMap = new HashMap<>();
76+
contextMap.put("key1", "value1");
77+
contextMap.put("key2", "value2");
78+
Slf4jUtils.logWithMDC(LOGGER, Level.DEBUG, contextMap, "test message");
79+
80+
assertEquals(1, testAppender.events.size());
81+
assertEquals("test message", testAppender.events.get(0).getMessage());
82+
83+
// Verify MDC content
84+
ILoggingEvent event = testAppender.events.get(0);
85+
assertEquals(2, event.getMDCPropertyMap().size());
86+
assertEquals(ch.qos.logback.classic.Level.DEBUG, event.getLevel());
87+
assertEquals("value1", event.getMDCPropertyMap().get("key1"));
88+
assertEquals("value2", event.getMDCPropertyMap().get("key2"));
89+
90+
testAppender.stop();
91+
}
92+
93+
@Test
94+
public void testLogWithMDC_INFO() {
95+
TestAppender testAppender = setupTestLogger();
96+
Slf4jUtils.logWithMDC(LOGGER, Level.INFO, new HashMap<>(), "test message");
97+
98+
assertEquals(1, testAppender.events.size());
99+
assertEquals(ch.qos.logback.classic.Level.INFO, testAppender.events.get(0).getLevel());
100+
testAppender.stop();
101+
}
102+
103+
@Test
104+
public void testLogWithMDC_TRACE_notEnabled() {
105+
TestAppender testAppender = setupTestLogger();
106+
Slf4jUtils.logWithMDC(LOGGER, Level.TRACE, new HashMap<>(), "test message");
107+
108+
assertEquals(0, testAppender.events.size());
109+
testAppender.stop();
110+
}
111+
112+
@Test
113+
public void testLogWithMDC_WARN() {
114+
TestAppender testAppender = setupTestLogger();
115+
Slf4jUtils.logWithMDC(LOGGER, Level.WARN, new HashMap<>(), "test message");
116+
117+
assertEquals(1, testAppender.events.size());
118+
assertEquals(ch.qos.logback.classic.Level.WARN, testAppender.events.get(0).getLevel());
119+
testAppender.stop();
120+
}
121+
122+
@Test
123+
public void testLogWithMDC_ERROR() {
124+
TestAppender testAppender = setupTestLogger();
125+
Slf4jUtils.logWithMDC(LOGGER, Level.ERROR, new HashMap<>(), "test message");
126+
127+
assertEquals(1, testAppender.events.size());
128+
assertEquals(ch.qos.logback.classic.Level.ERROR, testAppender.events.get(0).getLevel());
129+
testAppender.stop();
130+
}
131+
132+
@Test
133+
public void testLogGenericData() {
134+
// mimic GOOGLE_SDK_JAVA_LOGGING = true
135+
testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "true");
136+
LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
137+
138+
TestAppender testAppender = setupTestLogger();
139+
140+
GenericData data = new GenericData();
141+
data.put("key1", "value1");
142+
data.put("token", "value2");
143+
144+
LoggerProvider loggerProvider = mock(LoggerProvider.class);
145+
when(loggerProvider.getLogger()).thenReturn(LOGGER);
146+
LoggingUtils.logResponsePayload(data, loggerProvider, "test generic data");
147+
148+
assertEquals(1, testAppender.events.size());
149+
List<KeyValuePair> keyValuePairs = testAppender.events.get(0).getKeyValuePairs();
150+
assertEquals(2, keyValuePairs.size());
151+
for (KeyValuePair kvp : keyValuePairs) {
152+
153+
assertTrue(
154+
"Key should be either 'key1' or 'token'",
155+
kvp.key.equals("key1") || kvp.key.equals("token"));
156+
}
157+
158+
testAppender.stop();
159+
}
160+
161+
@Test
162+
public void testLogRequest() throws IOException {
163+
// mimic GOOGLE_SDK_JAVA_LOGGING = true
164+
testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "true");
165+
LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
166+
167+
TestAppender testAppender = setupTestLogger();
168+
169+
GenericData tokenRequest = new GenericData();
170+
tokenRequest.set("client_id", "clientId");
171+
tokenRequest.set("client_secret", "clientSecret");
172+
UrlEncodedContent content = new UrlEncodedContent(tokenRequest);
173+
174+
MockHttpTransportFactory mockHttpTransportFactory = new MockHttpTransportFactory();
175+
176+
HttpRequestFactory requestFactory = mockHttpTransportFactory.create().createRequestFactory();
177+
HttpRequest request =
178+
requestFactory.buildPostRequest(new GenericUrl(OAuth2Utils.TOKEN_SERVER_URI), content);
179+
180+
LoggerProvider loggerProvider = mock(LoggerProvider.class);
181+
when(loggerProvider.getLogger()).thenReturn(LOGGER);
182+
LoggingUtils.logRequest(request, loggerProvider, "test log request");
183+
184+
assertEquals(1, testAppender.events.size());
185+
assertEquals("test log request", testAppender.events.get(0).getMessage());
186+
List<KeyValuePair> keyValuePairs = testAppender.events.get(0).getKeyValuePairs();
187+
assertEquals(4, keyValuePairs.size());
188+
for (KeyValuePair kvp : testAppender.events.get(0).getKeyValuePairs()) {
189+
assertTrue(
190+
kvp.key.equals("request.headers")
191+
|| kvp.key.equals("request.payload")
192+
|| kvp.key.equals("request.method")
193+
|| kvp.key.equals("request.url"));
194+
if (kvp.key.equals("request.headers") || kvp.key.equals("request.payload")) {
195+
assertTrue(isValidJson((String) kvp.value));
196+
}
197+
}
198+
testAppender.stop();
199+
}
200+
201+
boolean isValidJson(String jsonString) {
202+
try {
203+
JsonParser.parseString(jsonString);
204+
return true;
205+
} catch (JsonSyntaxException e) {
206+
return false;
207+
}
208+
}
209+
210+
private TestAppender setupTestLogger() {
211+
TestAppender testAppender = new TestAppender();
212+
testAppender.start();
213+
((ch.qos.logback.classic.Logger) LOGGER).addAppender(testAppender);
214+
return testAppender;
215+
}
216+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
*
15+
* * Neither the name of Google LLC nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package com.google.auth.oauth2;
33+
34+
import static org.junit.Assert.*;
35+
import static org.mockito.ArgumentMatchers.anyString;
36+
import static org.mockito.Mockito.mock;
37+
import static org.mockito.Mockito.when;
38+
39+
import com.google.auth.oauth2.Slf4jUtils.LoggerFactoryProvider;
40+
import java.util.logging.Level;
41+
import org.junit.Before;
42+
import org.junit.Test;
43+
import org.slf4j.ILoggerFactory;
44+
import org.slf4j.Logger;
45+
import org.slf4j.helpers.NOPLogger;
46+
47+
public class Slf4jUtilsTest {
48+
49+
private TestEnvironmentProvider testEnvironmentProvider;
50+
51+
@Before
52+
public void setup() {
53+
testEnvironmentProvider = new TestEnvironmentProvider();
54+
}
55+
// This test mimics GOOGLE_SDK_JAVA_LOGGING != true
56+
@Test
57+
public void testGetLogger_loggingDisabled_shouldGetNOPLogger() {
58+
testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "false");
59+
LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
60+
Logger logger = Slf4jUtils.getLogger(Slf4jUtilsTest.class);
61+
62+
assertEquals(NOPLogger.class, logger.getClass());
63+
assertFalse(logger.isInfoEnabled());
64+
assertFalse(logger.isDebugEnabled());
65+
}
66+
67+
// This test require binding (e.g. logback) be present
68+
@Test
69+
public void testGetLogger_loggingEnabled_slf4jBindingPresent() {
70+
testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "true");
71+
LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
72+
Logger logger = Slf4jUtils.getLogger(LoggingUtilsTest.class);
73+
assertNotNull(logger);
74+
assertNotEquals(NOPLogger.class, logger.getClass());
75+
}
76+
77+
@Test
78+
public void testGetLogger_loggingEnabled_noBinding() {
79+
testEnvironmentProvider.setEnv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING, "true");
80+
LoggingUtils.setEnvironmentProvider(testEnvironmentProvider);
81+
// Create a mock LoggerFactoryProvider
82+
LoggerFactoryProvider mockLoggerFactoryProvider = mock(LoggerFactoryProvider.class);
83+
ILoggerFactory mockLoggerFactory = mock(ILoggerFactory.class);
84+
when(mockLoggerFactoryProvider.getLoggerFactory()).thenReturn(mockLoggerFactory);
85+
when(mockLoggerFactory.getLogger(anyString()))
86+
.thenReturn(org.slf4j.helpers.NOPLogger.NOP_LOGGER);
87+
88+
// Use the mock LoggerFactoryProvider in getLogger()
89+
Logger logger = Slf4jUtils.getLogger(LoggingUtilsTest.class, mockLoggerFactoryProvider);
90+
91+
// Assert that the returned logger is a NOPLogger
92+
assertTrue(logger instanceof org.slf4j.helpers.NOPLogger);
93+
}
94+
95+
@Test
96+
public void testCheckIfClazzAvailable() {
97+
assertFalse(Slf4jUtils.checkIfClazzAvailable("fake.class.should.not.be.in.classpath"));
98+
assertTrue(Slf4jUtils.checkIfClazzAvailable("org.slf4j.event.KeyValuePair"));
99+
}
100+
101+
@Test
102+
public void testMatchLevelSevere() {
103+
assertEquals(
104+
org.slf4j.event.Level.ERROR, Slf4jLoggingHelpers.matchUtilLevelToSLF4JLevel(Level.SEVERE));
105+
assertEquals(
106+
org.slf4j.event.Level.WARN, Slf4jLoggingHelpers.matchUtilLevelToSLF4JLevel(Level.WARNING));
107+
assertEquals(
108+
org.slf4j.event.Level.INFO, Slf4jLoggingHelpers.matchUtilLevelToSLF4JLevel(Level.INFO));
109+
assertEquals(
110+
org.slf4j.event.Level.DEBUG, Slf4jLoggingHelpers.matchUtilLevelToSLF4JLevel(Level.FINE));
111+
assertEquals(
112+
org.slf4j.event.Level.TRACE, Slf4jLoggingHelpers.matchUtilLevelToSLF4JLevel(Level.FINER));
113+
}
114+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2025 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
*
15+
* * Neither the name of Google LLC nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package com.google.auth.oauth2;
33+
34+
import ch.qos.logback.classic.spi.ILoggingEvent;
35+
import ch.qos.logback.core.AppenderBase;
36+
import java.util.ArrayList;
37+
import java.util.List;
38+
39+
/** Logback appender used to set up tests. */
40+
public class TestAppender extends AppenderBase<ILoggingEvent> {
41+
public List<ILoggingEvent> events = new ArrayList<>();
42+
43+
@Override
44+
protected void append(ILoggingEvent eventObject) {
45+
// triggering Logback to capture the current MDC context and store it with the log event
46+
eventObject.getMDCPropertyMap();
47+
events.add(eventObject);
48+
}
49+
50+
public void clearEvents() {
51+
events.clear();
52+
}
53+
}

‎oauth2_http/javatests/com/google/auth/oauth2/UserCredentialsTest.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@
7171
@RunWith(JUnit4.class)
7272
public class UserCredentialsTest extends BaseSerializationTest {
7373

74-
private static final String CLIENT_SECRET = "jakuaL9YyieakhECKL2SwZcu";
75-
private static final String CLIENT_ID = "ya29.1.AADtN_UtlxN3PuGAxrN2XQnZTVRvDyVWnYq4I6dws";
76-
private static final String REFRESH_TOKEN = "1/Tl6awhpFjkMkSJoj1xsli0H2eL5YsMgU_NKPY2TyGWY";
74+
static final String CLIENT_SECRET = "jakuaL9YyieakhECKL2SwZcu";
75+
static final String CLIENT_ID = "ya29.1.AADtN_UtlxN3PuGAxrN2XQnZTVRvDyVWnYq4I6dws";
76+
static final String REFRESH_TOKEN = "1/Tl6awhpFjkMkSJoj1xsli0H2eL5YsMgU_NKPY2TyGWY";
7777
private static final String ACCESS_TOKEN = "1/MkSJoj1xsli0AccessToken_NKPY2";
7878
private static final String QUOTA_PROJECT = "sample-quota-project-id";
7979
private static final Collection<String> SCOPES = Collections.singletonList("dummy.scope");
@@ -767,7 +767,7 @@ public void IdTokenCredentials_WithUserEmailScope_success() throws IOException {
767767
assertEquals(DEFAULT_ID_TOKEN, tokenCredential.getAccessToken().getTokenValue());
768768
assertEquals(DEFAULT_ID_TOKEN, tokenCredential.getIdToken().getTokenValue());
769769

770-
// verify id token request metrics headers, same as access token request
770+
// verify ID token request metrics headers, same as access token request
771771
Map<String, List<String>> idTokenRequestHeader =
772772
transportFactory.transport.getRequest().getHeaders();
773773
com.google.auth.oauth2.TestUtils.validateMetricsHeader(idTokenRequestHeader, "untracked", "u");

‎oauth2_http/pom.xml

+86
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,94 @@
6868
</includes>
6969
</configuration>
7070
</plugin>
71+
<plugin>
72+
<artifactId>maven-compiler-plugin</artifactId>
73+
<version>3.13.0</version>
74+
<configuration>
75+
<source>1.8</source>
76+
<target>1.8</target>
77+
<encoding>UTF-8</encoding>
78+
<!-- excluding tests depending on logback/env var by default -->
79+
<testExcludes>
80+
<testExclude>**/Slf4jUtilsLogbackTest.java</testExclude>
81+
<testExclude>**/Slf4jUtils1xTest.java</testExclude>
82+
<testExclude>**/Slf4jUtilsTest.java</testExclude>
83+
<testExclude>**/TestAppender.java</testExclude>
84+
<testExclude>**/LoggingTest.java</testExclude>
85+
</testExcludes>
86+
</configuration>
87+
</plugin>
88+
</plugins>
89+
</build>
90+
</profile>
91+
<profile>
92+
<id>slf4j2x</id>
93+
<activation>
94+
<activeByDefault>true</activeByDefault>
95+
</activation>
96+
<build>
97+
<plugins>
98+
<plugin>
99+
<artifactId>maven-compiler-plugin</artifactId>
100+
<version>3.13.0</version>
101+
<configuration>
102+
<source>1.8</source>
103+
<target>1.8</target>
104+
<encoding>UTF-8</encoding>
105+
106+
<!-- excluding tests depending on logback/env var by default -->
107+
<testExcludes>
108+
<testExclude>**/Slf4jUtilsLogbackTest.java</testExclude>
109+
<testExclude>**/Slf4jUtils1xTest.java</testExclude>
110+
<testExclude>**/Slf4jUtilsTest.java</testExclude>
111+
<testExclude>**/TestAppender.java</testExclude>
112+
<testExclude>**/LoggingTest.java</testExclude>
113+
</testExcludes>
114+
</configuration>
115+
</plugin>
71116
</plugins>
72117
</build>
118+
<dependencies>
119+
<dependency>
120+
<groupId>org.slf4j</groupId>
121+
<artifactId>slf4j-api</artifactId>
122+
<version>${project.slf4j.version}</version>
123+
<optional>true</optional>
124+
</dependency>
125+
<dependency>
126+
<groupId>com.google.code.gson</groupId>
127+
<artifactId>gson</artifactId>
128+
<version>${project.gson.version}</version>
129+
</dependency>
130+
</dependencies>
131+
</profile>
132+
<profile>
133+
<id>slf4j2x-test</id>
134+
<!-- This profile is for logging test only,
135+
use instead of the default slf4j2x.
136+
This should merge with the profile with the same name
137+
in the parent module -->
138+
<!-- Run test with `mvn clean test -P '!slf4j2x,slf4j2x-test'` -->
139+
<dependencies>
140+
<dependency>
141+
<groupId>org.slf4j</groupId>
142+
<artifactId>slf4j-api</artifactId>
143+
<version>${project.slf4j.version}</version>
144+
</dependency>
145+
<!-- Logback for testing -->
146+
<dependency>
147+
<groupId>ch.qos.logback</groupId>
148+
<artifactId>logback-classic</artifactId>
149+
<version>1.5.16</version>
150+
<scope>test</scope>
151+
</dependency>
152+
<dependency>
153+
<groupId>ch.qos.logback</groupId>
154+
<artifactId>logback-core</artifactId>
155+
<version>1.5.16</version>
156+
<scope>test</scope>
157+
</dependency>
158+
</dependencies>
73159
</profile>
74160
</profiles>
75161

‎pom.xml

+31
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@
8484
<project.protobuf.version>3.25.5</project.protobuf.version>
8585
<project.cel.version>0.9.0-proto3</project.cel.version>
8686
<project.tink.version>1.15.0</project.tink.version>
87+
<project.slf4j.version>2.0.16</project.slf4j.version>
88+
<project.gson.version>2.11.0</project.gson.version>
8789
</properties>
8890

8991
<dependencyManagement>
@@ -419,6 +421,35 @@
419421
</reporting>
420422

421423
<profiles>
424+
<profile>
425+
<id>slf4j2x-test</id>
426+
<!-- This profile is for logging test only, use instead of the default slf4j2x -->
427+
<!-- testInclude for compiler is in this parent pom because it affects
428+
test classes from other modules -->
429+
<!-- To run logging tests, use this in addition to `slf4j2x-test`profile
430+
in oauth2_http module -->
431+
<build>
432+
<plugins>
433+
<plugin>
434+
<artifactId>maven-compiler-plugin</artifactId>
435+
<version>3.13.0</version>
436+
<configuration>
437+
<source>1.8</source>
438+
<target>1.8</target>
439+
<encoding>UTF-8</encoding>
440+
<!-- excluding tests depending on logback/env var by default -->
441+
<testIncludes>
442+
<testInclude>**/Slf4jUtilsLogbackTest.java</testInclude>
443+
<testInclude>**/Slf4jUtils1xTest.java</testInclude>
444+
<testInclude>**/Slf4jUtilsTest.java</testInclude>
445+
<testInclude>**/TestAppender.java</testInclude>
446+
<testInclude>**/LoggingTest.java</testInclude>
447+
</testIncludes>
448+
</configuration>
449+
</plugin>
450+
</plugins>
451+
</build>
452+
</profile>
422453
<profile>
423454
<id>release-sign-artifacts</id>
424455
<activation>

0 commit comments

Comments
 (0)
Please sign in to comment.