Skip to content

Commit 827b16b

Browse files
committed
feat(gax): add utilities for logging actionable errors
1 parent a1b7565 commit 827b16b

File tree

2 files changed

+96
-2
lines changed

2 files changed

+96
-2
lines changed

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

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,28 @@
3636
@InternalApi
3737
public class LoggingUtils {
3838

39-
private static boolean loggingEnabled = isLoggingEnabled();
39+
private static boolean loggingEnabled = checkLoggingEnabled();
4040
static final String GOOGLE_SDK_JAVA_LOGGING = "GOOGLE_SDK_JAVA_LOGGING";
4141

42-
static boolean isLoggingEnabled() {
42+
/**
43+
* Returns whether client-side logging is enabled.
44+
*
45+
* @return true if logging is enabled, false otherwise.
46+
*/
47+
public static boolean isLoggingEnabled() {
48+
return loggingEnabled;
49+
}
50+
51+
/**
52+
* Sets whether client-side logging is enabled. Visible for testing.
53+
*
54+
* @param enabled true to enable logging, false to disable.
55+
*/
56+
public static void setLoggingEnabled(boolean enabled) {
57+
loggingEnabled = enabled;
58+
}
59+
60+
private static boolean checkLoggingEnabled() {
4361
String enableLogging = System.getenv(GOOGLE_SDK_JAVA_LOGGING);
4462
return "true".equalsIgnoreCase(enableLogging);
4563
}
@@ -126,6 +144,24 @@ public static <RespT> void logRequest(
126144
}
127145
}
128146

147+
/**
148+
* Logs an actionable error message with structured context.
149+
*
150+
* @param logContext A map containing the structured logging context (e.g., RPC service, method,
151+
* error details).
152+
* @param loggerProvider The provider used to obtain the logger.
153+
* @param message The human-readable error message.
154+
*/
155+
public static void logActionableError(
156+
Map<String, Object> logContext, LoggerProvider loggerProvider, String message) {
157+
if (loggingEnabled) {
158+
org.slf4j.Logger logger = loggerProvider.getLogger();
159+
if (logger.isInfoEnabled()) {
160+
Slf4jUtils.log(logger, org.slf4j.event.Level.INFO, logContext, message);
161+
}
162+
}
163+
}
164+
129165
public static void executeWithTryCatch(ThrowingRunnable action) {
130166
try {
131167
action.run();

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

Lines changed: 58 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,53 @@ 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.setLoggingEnabled(false);
98+
LoggerProvider loggerProvider = mock(LoggerProvider.class);
99+
100+
LoggingUtils.logActionableError(Collections.emptyMap(), loggerProvider, "message");
101+
102+
verify(loggerProvider, never()).getLogger();
103+
}
104+
105+
@Test
106+
void testLogActionableError_infoDisabled() {
107+
LoggingUtils.setLoggingEnabled(true);
108+
LoggerProvider loggerProvider = mock(LoggerProvider.class);
109+
Logger logger = mock(Logger.class);
110+
when(loggerProvider.getLogger()).thenReturn(logger);
111+
when(logger.isInfoEnabled()).thenReturn(false);
112+
113+
LoggingUtils.logActionableError(Collections.emptyMap(), loggerProvider, "message");
114+
115+
verify(loggerProvider).getLogger();
116+
verify(logger).isInfoEnabled();
117+
verify(logger, never()).info(anyString());
118+
}
119+
120+
@Test
121+
void testLogActionableError_success() {
122+
LoggingUtils.setLoggingEnabled(true);
123+
LoggerProvider loggerProvider = mock(LoggerProvider.class);
124+
Logger logger = mock(Logger.class);
125+
when(loggerProvider.getLogger()).thenReturn(logger);
126+
when(logger.isInfoEnabled()).thenReturn(true);
127+
128+
org.slf4j.spi.LoggingEventBuilder eventBuilder = mock(org.slf4j.spi.LoggingEventBuilder.class);
129+
when(logger.atInfo()).thenReturn(eventBuilder);
130+
when(eventBuilder.addKeyValue(anyString(), any())).thenReturn(eventBuilder);
131+
132+
Map<String, Object> context = Collections.singletonMap("key", "value");
133+
LoggingUtils.logActionableError(context, loggerProvider, "message");
134+
135+
verify(loggerProvider).getLogger();
136+
verify(logger).isInfoEnabled();
137+
}
80138
}

0 commit comments

Comments
 (0)