Skip to content
This repository was archived by the owner on May 14, 2026. It is now read-only.

Commit ed367ec

Browse files
committed
feat(gax): add utilities for logging actionable errors
1 parent e8b6d50 commit ed367ec

3 files changed

Lines changed: 107 additions & 5 deletions

File tree

gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,50 @@
3636
@InternalApi
3737
public class LoggingUtils {
3838

39-
private static boolean loggingEnabled = isLoggingEnabled();
4039
static final String GOOGLE_SDK_JAVA_LOGGING = "GOOGLE_SDK_JAVA_LOGGING";
40+
static final String GOOGLE_SDK_JAVA_LOGGING_V2 = "GOOGLE_SDK_JAVA_LOGGING_V2";
4141

42-
static boolean isLoggingEnabled() {
43-
String enableLogging = System.getenv(GOOGLE_SDK_JAVA_LOGGING);
42+
private static boolean loggingEnabled = checkLoggingEnabled(GOOGLE_SDK_JAVA_LOGGING);
43+
private static boolean loggingV2Enabled = checkLoggingEnabled(GOOGLE_SDK_JAVA_LOGGING_V2);
44+
45+
/**
46+
* Returns whether client-side logging is enabled (V1 or V2).
47+
*
48+
* @return true if logging is enabled, false otherwise.
49+
*/
50+
public static boolean isLoggingEnabled() {
51+
return loggingEnabled || loggingV2Enabled;
52+
}
53+
54+
/**
55+
* Returns whether client-side logging V2 (Actionable Errors) is enabled.
56+
*
57+
* @return true if V2 logging is enabled, false otherwise.
58+
*/
59+
public static boolean isLoggingV2Enabled() {
60+
return loggingV2Enabled;
61+
}
62+
63+
/**
64+
* Sets whether client-side logging is enabled. Visible for testing.
65+
*
66+
* @param enabled true to enable logging, false to disable.
67+
*/
68+
public static void setLoggingEnabled(boolean enabled) {
69+
loggingEnabled = enabled;
70+
}
71+
72+
/**
73+
* Sets whether client-side logging V2 is enabled. Visible for testing.
74+
*
75+
* @param enabled true to enable logging, false to disable.
76+
*/
77+
public static void setLoggingV2Enabled(boolean enabled) {
78+
loggingV2Enabled = enabled;
79+
}
80+
81+
private static boolean checkLoggingEnabled(String envVar) {
82+
String enableLogging = System.getenv(envVar);
4483
return "true".equalsIgnoreCase(enableLogging);
4584
}
4685

@@ -126,6 +165,26 @@ public static <RespT> void logRequest(
126165
}
127166
}
128167

168+
/**
169+
* Logs an actionable error message with structured context at a specific log level.
170+
*
171+
* @param logContext A map containing the structured logging context (e.g., RPC service, method,
172+
* error details).
173+
* @param loggerProvider The provider used to obtain the logger.
174+
* @param level The slf4j level to log the actionable error at.
175+
* @param message The human-readable error message.
176+
*/
177+
public static void logActionableError(
178+
Map<String, Object> logContext,
179+
LoggerProvider loggerProvider,
180+
org.slf4j.event.Level level,
181+
String message) {
182+
if (loggingV2Enabled) {
183+
org.slf4j.Logger logger = loggerProvider.getLogger();
184+
Slf4jUtils.log(logger, level, logContext, message);
185+
}
186+
}
187+
129188
public static void executeWithTryCatch(ThrowingRunnable action) {
130189
try {
131190
action.run();

gax-java/gax/src/main/java/com/google/api/gax/logging/Slf4jLoggingHelpers.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,11 @@ static void logResponse(
114114
LoggingUtils.executeWithTryCatch(
115115
() -> {
116116
Logger logger = loggerProvider.getLogger();
117-
if (logger.isInfoEnabled()) {
117+
boolean isV2 = LoggingUtils.isLoggingV2Enabled();
118+
if (!isV2 && logger.isInfoEnabled()) {
118119
logDataBuilder.responseStatus(status);
119120
}
120-
if (logger.isInfoEnabled() && !logger.isDebugEnabled()) {
121+
if (!isV2 && logger.isInfoEnabled() && !logger.isDebugEnabled()) {
121122
Map<String, Object> responseData = logDataBuilder.build().toMapResponse();
122123
Slf4jUtils.log(logger, Level.INFO, responseData, "Received response");
123124
}

gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,20 @@
3333
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
3434
import static org.junit.jupiter.api.Assertions.assertEquals;
3535
import static org.junit.jupiter.api.Assertions.assertFalse;
36+
import static org.mockito.ArgumentMatchers.any;
37+
import static org.mockito.ArgumentMatchers.anyString;
38+
import static org.mockito.Mockito.mock;
39+
import static org.mockito.Mockito.never;
3640
import static org.mockito.Mockito.verify;
41+
import static org.mockito.Mockito.when;
3742

3843
import com.google.api.gax.logging.LoggingUtils.ThrowingRunnable;
44+
import java.util.Collections;
45+
import java.util.Map;
46+
import org.junit.jupiter.api.AfterEach;
3947
import org.junit.jupiter.api.Test;
4048
import org.mockito.Mockito;
49+
import org.slf4j.Logger;
4150

4251
class LoggingUtilsTest {
4352

@@ -77,4 +86,37 @@ void testExecuteWithTryCatch_WithNoSuchMethodError() throws Throwable {
7786
// Verify that the action was executed (despite the error)
7887
verify(action).run();
7988
}
89+
90+
@AfterEach
91+
void tearDown() {
92+
LoggingUtils.setLoggingEnabled(false);
93+
}
94+
95+
@Test
96+
void testLogActionableError_loggingDisabled() {
97+
LoggingUtils.setLoggingV2Enabled(false);
98+
LoggerProvider loggerProvider = mock(LoggerProvider.class);
99+
100+
LoggingUtils.logActionableError(
101+
Collections.emptyMap(), loggerProvider, org.slf4j.event.Level.INFO, "message");
102+
103+
verify(loggerProvider, never()).getLogger();
104+
}
105+
106+
@Test
107+
void testLogActionableError_success() {
108+
LoggingUtils.setLoggingV2Enabled(true);
109+
LoggerProvider loggerProvider = mock(LoggerProvider.class);
110+
Logger logger = mock(Logger.class);
111+
when(loggerProvider.getLogger()).thenReturn(logger);
112+
113+
org.slf4j.spi.LoggingEventBuilder eventBuilder = mock(org.slf4j.spi.LoggingEventBuilder.class);
114+
when(logger.atInfo()).thenReturn(eventBuilder);
115+
when(eventBuilder.addKeyValue(anyString(), any())).thenReturn(eventBuilder);
116+
117+
Map<String, Object> context = Collections.singletonMap("key", "value");
118+
LoggingUtils.logActionableError(context, loggerProvider, org.slf4j.event.Level.INFO, "message");
119+
120+
verify(loggerProvider).getLogger();
121+
}
80122
}

0 commit comments

Comments
 (0)