From 59b5d739cbb0269325ab92643bcedf65147a4892 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Tue, 19 Nov 2024 11:16:25 -0500 Subject: [PATCH 01/45] poc: add log in grpc interceptor. --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 74 +++++++ .../InstantiatingGrpcChannelProvider.java | 1 + gax-java/gax/pom.xml | 6 + .../com/google/api/gax/logging/Logger.java | 193 ++++++++++++++++++ .../google/api/gax/logging/LoggingUtils.java | 48 +++++ .../com/google/api/gax/rpc/ClientContext.java | 8 + pom.xml | 1 + showcase/gapic-showcase/pom.xml | 24 +++ .../src/test/resources/logback-test.xml | 19 ++ 9 files changed, 374 insertions(+) create mode 100644 gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/logging/Logger.java create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java create mode 100644 showcase/gapic-showcase/src/test/resources/logback-test.xml diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java new file mode 100644 index 0000000000..ff21d3ebf7 --- /dev/null +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -0,0 +1,74 @@ +package com.google.api.gax.grpc; + +import com.google.api.gax.logging.LoggingUtils; +import com.google.gson.Gson; +import io.grpc.*; +import org.slf4j.Logger; +import org.slf4j.MDC; + +public class GrpcLoggingInterceptor implements ClientInterceptor { + + private static final Logger logger = LoggingUtils.getLogger(GrpcLoggingInterceptor.class); + private static final Gson gson = new Gson(); + + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + + return new ForwardingClientCall.SimpleForwardingClientCall( + next.newCall(method, callOptions)) { + + @Override + public void start(Listener responseListener, Metadata headers) { + if (LoggingUtils.isLoggingEnabled()) { + // Capture request details + String serviceName = method.getServiceName(); + String methodName = method.getFullMethodName(); + + // Add request details to MDC// Example system + MDC.put("serviceName", serviceName); + MDC.put("rpcName", methodName); + // Capture and log headers + headers + .keys() + .forEach( + key -> { + Metadata.Key metadataKey = + Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); + String headerValue = headers.get(metadataKey); + MDC.put("request.headers:" + key, headerValue); + }); + + logger.debug("Sending gRPC request"); + } + + super.start( + new ForwardingClientCallListener.SimpleForwardingClientCallListener( + responseListener) { + @Override + public void onMessage(RespT message) { + MDC.put("response.payload", gson.toJson(message)); + logger.debug("Received gRPC response."); + + super.onMessage(message); + } + + @Override + public void onClose(Status status, Metadata trailers) { + MDC.put("response.status", status.getCode().name()); + logger.info("gRPC request finished with status: {}", status); + MDC.clear(); // Clear MDC after the request + super.onClose(status, trailers); + } + }, + headers); + } + + @Override + public void sendMessage(ReqT message) { + MDC.put("request.payload", gson.toJson(message)); + super.sendMessage(message); + } + }; + } +} diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java index ae4d7f9e51..1f23868cd5 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java @@ -467,6 +467,7 @@ private ManagedChannel createSingleChannel() throws IOException { builder = builder .intercept(new GrpcChannelUUIDInterceptor()) + .intercept(new GrpcLoggingInterceptor()) .intercept(headerInterceptor) .intercept(metadataHandlerInterceptor) .userAgent(headerInterceptor.getUserAgentHeader()) diff --git a/gax-java/gax/pom.xml b/gax-java/gax/pom.xml index d2e40b6a48..1c919e09d5 100644 --- a/gax-java/gax/pom.xml +++ b/gax-java/gax/pom.xml @@ -69,6 +69,12 @@ opentelemetry-api true + + org.slf4j + slf4j-api + 2.0.16 + + diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/Logger.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/Logger.java new file mode 100644 index 0000000000..9f5d7fc15c --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/Logger.java @@ -0,0 +1,193 @@ +package com.google.api.gax.logging; + +import java.util.function.Supplier; +import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; + +public final class Logger { + private final org.slf4j.Logger log; + + Logger(org.slf4j.Logger log) { + this.log = log; + } + + public org.slf4j.Logger logger() { + return log; + } + + /** + * Checks if info is enabled and if so logs the supplied message + * + * @param msg - supplier for the log message + */ + public void info(Supplier msg) { + if (log.isInfoEnabled()) { + log.info(msg.get()); + } + } + + /** + * Checks if info is enabled and if so logs the supplied message and exception + * + * @param msg - supplier for the log message + * @param throwable - a throwable to log + */ + public void info(Supplier msg, Throwable throwable) { + if (log.isInfoEnabled()) { + log.info(msg.get(), throwable); + } + } + + /** + * Checks if error is enabled and if so logs the supplied message + * + * @param msg - supplier for the log message + */ + public void error(Supplier msg) { + if (log.isErrorEnabled()) { + log.error(msg.get()); + } + } + + /** + * Checks if error is enabled and if so logs the supplied message and exception + * + * @param msg - supplier for the log message + * @param throwable - a throwable to log + */ + public void error(Supplier msg, Throwable throwable) { + if (log.isErrorEnabled()) { + log.error(msg.get(), throwable); + } + } + + /** + * Checks if debug is enabled and if so logs the supplied message + * + * @param msg - supplier for the log message + */ + public void debug(Supplier msg) { + if (log.isDebugEnabled()) { + log.debug(msg.get()); + } + } + + /** + * Checks if debug is enabled and if so logs the supplied message and exception + * + * @param msg - supplier for the log message + * @param throwable - a throwable to log + */ + public void debug(Supplier msg, Throwable throwable) { + if (log.isDebugEnabled()) { + log.debug(msg.get(), throwable); + } + } + + /** + * Checks if warn is enabled and if so logs the supplied message + * + * @param msg - supplier for the log message + */ + public void warn(Supplier msg) { + if (log.isWarnEnabled()) { + log.warn(msg.get()); + } + } + + /** + * Checks if warn is enabled and if so logs the supplied message and exception + * + * @param msg - supplier for the log message + * @param throwable - a throwable to log + */ + public void warn(Supplier msg, Throwable throwable) { + if (log.isWarnEnabled()) { + log.warn(msg.get(), throwable); + } + } + + /** + * Checks if trace is enabled and if so logs the supplied message + * + * @param msg - supplier for the log message + */ + public void trace(Supplier msg) { + if (log.isTraceEnabled()) { + log.trace(msg.get()); + } + } + + /** + * Checks if trace is enabled and if so logs the supplied message and exception + * + * @param msg - supplier for the log message + * @param throwable - a throwable to log + */ + public void trace(Supplier msg, Throwable throwable) { + if (log.isTraceEnabled()) { + log.trace(msg.get(), throwable); + } + } + + /** + * Determines if the provided log-level is enabled. + * + * @param logLevel the SLF4J log level enum + * @return whether that level is enabled + */ + public boolean isLoggingLevelEnabled(Level logLevel) { + switch (logLevel) { + case TRACE: + return log.isTraceEnabled(); + case DEBUG: + return log.isDebugEnabled(); + case INFO: + return log.isInfoEnabled(); + case WARN: + return log.isWarnEnabled(); + case ERROR: + return log.isErrorEnabled(); + default: + throw new IllegalStateException("Unsupported log level: " + logLevel); + } + } + + /** + * Log a message at the given log level (if it is enabled). + * + * @param logLevel the SLF4J log level + * @param msg supplier for the log message + */ + public void log(Level logLevel, Supplier msg) { + switch (logLevel) { + case TRACE: + trace(msg); + break; + case DEBUG: + debug(msg); + break; + case INFO: + info(msg); + break; + case WARN: + warn(msg); + break; + case ERROR: + error(msg); + break; + default: + throw new IllegalStateException("Unsupported log level: " + logLevel); + } + } + + /** + * Static factory to get a logger instance for a given class + * + * @param clz - class to get the logger for + * @return a Logger instance + */ + public static Logger loggerFor(Class clz) { + return new Logger(LoggerFactory.getLogger(clz)); + } +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java new file mode 100644 index 0000000000..10b7b2425e --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java @@ -0,0 +1,48 @@ +package com.google.api.gax.logging; + +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.slf4j.event.Level; + +public class LoggingUtils { + + public static Logger getLogger(Class clazz) { + return LoggerFactory.getLogger(clazz); + } + + public static boolean isLoggingEnabled() { + String enableLogging = System.getenv("GOOGLE_SDK_JAVA_LOGGING"); + return "true".equalsIgnoreCase(enableLogging); + } + + public static void log(Logger logger, Level level, String msg, Map contextMap) { + + if (LoggingUtils.isLoggingEnabled()) { + MDC.setContextMap(contextMap); + switch (level) { + case TRACE: + logger.trace(msg); + break; + case DEBUG: + logger.debug(msg); + break; + case INFO: + logger.info(msg); + break; + case WARN: + logger.warn(msg); + break; + case ERROR: + logger.error(msg); + break; + default: + logger.info(msg); + // Default to INFO level if level is invalid + break; + } + MDC.clear(); + } + } +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java index 5bce1ac6bb..4801d02b4c 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java @@ -40,6 +40,7 @@ import com.google.api.gax.core.BackgroundResource; import com.google.api.gax.core.ExecutorAsBackgroundResource; import com.google.api.gax.core.ExecutorProvider; +import com.google.api.gax.logging.LoggingUtils; import com.google.api.gax.rpc.internal.QuotaProjectIdHidingCredentials; import com.google.api.gax.tracing.ApiTracerFactory; import com.google.api.gax.tracing.BaseApiTracerFactory; @@ -63,6 +64,8 @@ import java.util.concurrent.ScheduledExecutorService; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.event.Level; /** * Encapsulates client state, including executor, credentials, and transport channel. @@ -72,6 +75,8 @@ */ @AutoValue public abstract class ClientContext { + + private static final Logger LOGGER = LoggingUtils.getLogger(ClientContext.class); private static final String QUOTA_PROJECT_ID_HEADER_KEY = "x-goog-user-project"; /** @@ -170,6 +175,9 @@ public static ClientContext create(ClientSettings settings) throws IOException { * settings. */ public static ClientContext create(StubSettings settings) throws IOException { + if (LoggingUtils.isLoggingEnabled()) { + LoggingUtils.log(LOGGER, Level.INFO, "a dummy message", Collections.emptyMap()); + } ApiClock clock = settings.getClock(); ExecutorProvider backgroundExecutorProvider = settings.getBackgroundExecutorProvider(); diff --git a/pom.xml b/pom.xml index d207468e77..d4379722a8 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ gapic-generator-java-bom java-shared-dependencies sdk-platform-java-config + showcase diff --git a/showcase/gapic-showcase/pom.xml b/showcase/gapic-showcase/pom.xml index 2549eb199f..0cea523ccd 100644 --- a/showcase/gapic-showcase/pom.xml +++ b/showcase/gapic-showcase/pom.xml @@ -215,5 +215,29 @@ opentelemetry-sdk-testing test + + + org.slf4j + slf4j-api + 2.0.16 + + + ch.qos.logback + logback-classic + 1.5.11 + test + + + net.logstash.logback + logstash-logback-encoder + 7.2 + test + + + org.codehaus.janino + janino + 3.1.9 + test + diff --git a/showcase/gapic-showcase/src/test/resources/logback-test.xml b/showcase/gapic-showcase/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..533c9edb64 --- /dev/null +++ b/showcase/gapic-showcase/src/test/resources/logback-test.xml @@ -0,0 +1,19 @@ + + + + + + + + logger.equals("com.google.api.gax.grpc.GrpcLoggingInterceptor") + + + ACCEPT + DENY + + + + + + + \ No newline at end of file From d34ec88de5940c7810a1285d72f220058ce2af4c Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Wed, 20 Nov 2024 10:50:49 -0500 Subject: [PATCH 02/45] try out basics with httpjson interceptor. --- .../httpjson/HttpJsonLoggingInterceptor.java | 60 +++++++++++++++++++ .../InstantiatingHttpJsonChannelProvider.java | 1 + .../com/google/api/gax/rpc/ClientContext.java | 11 ++-- .../src/test/resources/logback-test.xml | 1 + 4 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java new file mode 100644 index 0000000000..4195d9c386 --- /dev/null +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -0,0 +1,60 @@ +package com.google.api.gax.httpjson; + +import com.google.api.gax.httpjson.ForwardingHttpJsonClientCall.SimpleForwardingHttpJsonClientCall; +import com.google.gson.Gson; +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; + +public class HttpJsonLoggingInterceptor implements HttpJsonClientInterceptor { + + private static final Logger logger = LoggerFactory.getLogger(HttpJsonLoggingInterceptor.class); + private static final Gson gson = new Gson(); + + @Override + public HttpJsonClientCall interceptCall( + ApiMethodDescriptor method, + HttpJsonCallOptions callOptions, + HttpJsonChannel next) { + + return new SimpleForwardingHttpJsonClientCall(next.newCall(method, callOptions)) { + @Override + public void start( + HttpJsonClientCall.Listener responseListener, HttpJsonMetadata headers) { + // Capture request details + String methodName = method.getFullMethodName(); + + // Add request details to MDC + MDC.put("method", methodName); + + // Capture and log headers + for (Map.Entry header : headers.getHeaders().entrySet()) { + MDC.put("header." + header.getKey(), header.getValue().toString()); + } + + logger.info("Sending HTTP request"); + + super.start( + new HttpJsonClientCall.Listener() { + @Override + public void onMessage(RespT message) { + MDC.put("response.payload", gson.toJson(message)); + logger.debug("Received HTTP response."); + + responseListener.onMessage(message); + } + + @Override + public void onClose(int status, HttpJsonMetadata trailers) { + MDC.put("response.status", String.valueOf(status)); + logger.info("HTTP request finished with status: {}", status); + MDC.clear(); // Clear MDC after the request + responseListener.onClose(status, trailers); + } + }, + headers); + } + }; + } +} diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java index f92bdf299c..1912bc5e29 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java @@ -196,6 +196,7 @@ private HttpJsonTransportChannel createChannel() throws IOException, GeneralSecu HttpJsonClientInterceptor headerInterceptor = new HttpJsonHeaderInterceptor(headerProvider.getHeaders()); + channel = new ManagedHttpJsonInterceptorChannel(channel, new HttpJsonLoggingInterceptor()); channel = new ManagedHttpJsonInterceptorChannel(channel, headerInterceptor); if (interceptorProvider != null && interceptorProvider.getInterceptors() != null) { for (HttpJsonClientInterceptor interceptor : interceptorProvider.getInterceptors()) { diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java index 4801d02b4c..aab38b8162 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java @@ -40,7 +40,6 @@ import com.google.api.gax.core.BackgroundResource; import com.google.api.gax.core.ExecutorAsBackgroundResource; import com.google.api.gax.core.ExecutorProvider; -import com.google.api.gax.logging.LoggingUtils; import com.google.api.gax.rpc.internal.QuotaProjectIdHidingCredentials; import com.google.api.gax.tracing.ApiTracerFactory; import com.google.api.gax.tracing.BaseApiTracerFactory; @@ -64,8 +63,6 @@ import java.util.concurrent.ScheduledExecutorService; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.slf4j.Logger; -import org.slf4j.event.Level; /** * Encapsulates client state, including executor, credentials, and transport channel. @@ -76,7 +73,7 @@ @AutoValue public abstract class ClientContext { - private static final Logger LOGGER = LoggingUtils.getLogger(ClientContext.class); + // private static final Logger LOGGER = LoggingUtils.getLogger(ClientContext.class); private static final String QUOTA_PROJECT_ID_HEADER_KEY = "x-goog-user-project"; /** @@ -175,9 +172,9 @@ public static ClientContext create(ClientSettings settings) throws IOException { * settings. */ public static ClientContext create(StubSettings settings) throws IOException { - if (LoggingUtils.isLoggingEnabled()) { - LoggingUtils.log(LOGGER, Level.INFO, "a dummy message", Collections.emptyMap()); - } + // if (LoggingUtils.isLoggingEnabled()) { + // LoggingUtils.log(LOGGER, Level.INFO, "a dummy message", Collections.emptyMap()); + // } ApiClock clock = settings.getClock(); ExecutorProvider backgroundExecutorProvider = settings.getBackgroundExecutorProvider(); diff --git a/showcase/gapic-showcase/src/test/resources/logback-test.xml b/showcase/gapic-showcase/src/test/resources/logback-test.xml index 533c9edb64..02b2596516 100644 --- a/showcase/gapic-showcase/src/test/resources/logback-test.xml +++ b/showcase/gapic-showcase/src/test/resources/logback-test.xml @@ -6,6 +6,7 @@ logger.equals("com.google.api.gax.grpc.GrpcLoggingInterceptor") + logger.equals("com.google.api.gax.httpjson.HttpJsonLoggingInterceptor") ACCEPT From b6417ae719dde3927fa59e1ad0179a0476091285 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Mon, 25 Nov 2024 16:45:08 -0500 Subject: [PATCH 03/45] implement conditional logging, use JUL when no binding. log formatting needs more work. --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 95 +++-- gax-java/gax/pom.xml | 2 +- .../com/google/api/gax/logging/Logger.java | 193 ---------- .../google/api/gax/logging/LoggingUtils.java | 363 ++++++++++++++++-- .../src/test/resources/logging.properties | 4 + 5 files changed, 406 insertions(+), 251 deletions(-) delete mode 100644 gax-java/gax/src/main/java/com/google/api/gax/logging/Logger.java create mode 100644 showcase/gapic-showcase/src/test/resources/logging.properties diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index ff21d3ebf7..9a92921987 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -2,14 +2,18 @@ import com.google.api.gax.logging.LoggingUtils; import com.google.gson.Gson; +import com.google.gson.JsonObject; import io.grpc.*; +import java.util.HashMap; +import java.util.Map; import org.slf4j.Logger; -import org.slf4j.MDC; public class GrpcLoggingInterceptor implements ClientInterceptor { private static final Logger logger = LoggingUtils.getLogger(GrpcLoggingInterceptor.class); private static final Gson gson = new Gson(); + private JsonObject serviceAndRpc = new JsonObject(); + private JsonObject requestLogData = new JsonObject(); @Override public ClientCall interceptCall( @@ -20,44 +24,69 @@ public ClientCall interceptCall( @Override public void start(Listener responseListener, Metadata headers) { - if (LoggingUtils.isLoggingEnabled()) { - // Capture request details - String serviceName = method.getServiceName(); - String methodName = method.getFullMethodName(); - - // Add request details to MDC// Example system - MDC.put("serviceName", serviceName); - MDC.put("rpcName", methodName); - // Capture and log headers - headers - .keys() - .forEach( - key -> { - Metadata.Key metadataKey = - Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); - String headerValue = headers.get(metadataKey); - MDC.put("request.headers:" + key, headerValue); - }); - - logger.debug("Sending gRPC request"); - } + // if (LoggingUtils.isLoggingEnabled()) { + // Capture request details + String serviceName = method.getServiceName(); + String methodName = method.getFullMethodName(); + + serviceAndRpc.addProperty("serviceName", serviceName); + serviceAndRpc.addProperty("rpcName", methodName); + + JsonObject responseLogData = new JsonObject(); + // Add request details to MDC// Example system + // MDC.put("serviceName", serviceName); + // MDC.put("rpcName", methodName); + // Capture and log headers + headers + .keys() + .forEach( + key -> { + Metadata.Key metadataKey = + Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); + String headerValue = headers.get(metadataKey); + // MDC.put("request.headers:" + key, headerValue); + requestLogData.addProperty("request.headers:" + key, headerValue); + }); + + requestLogData.addProperty("message", "Sending gRPC request"); + // logger.debug("Sending gRPC request"); + // } super.start( new ForwardingClientCallListener.SimpleForwardingClientCallListener( responseListener) { @Override public void onMessage(RespT message) { - MDC.put("response.payload", gson.toJson(message)); - logger.debug("Received gRPC response."); + responseLogData.addProperty("response.payload", gson.toJson(message)); super.onMessage(message); } @Override public void onClose(Status status, Metadata trailers) { - MDC.put("response.status", status.getCode().name()); - logger.info("gRPC request finished with status: {}", status); - MDC.clear(); // Clear MDC after the request + responseLogData.addProperty("response.status", status.getCode().name()); + responseLogData.addProperty( + "message", String.format("gRPC request finished with status: %s", status)); + + // Create a JsonObject for response headers + JsonObject responseHeaders = new JsonObject(); + + // Access and add response headers to the JsonObject + trailers + .keys() + .forEach( + key -> { + Metadata.Key metadataKey = + Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); + String headerValue = trailers.get(metadataKey); + responseHeaders.addProperty(key, headerValue); + }); + responseLogData.add("response.headers", responseHeaders); + + logger.debug( + gson.toJson(LoggingUtils.mergeJsonObject(serviceAndRpc, responseLogData))); + // logger.info("gRPC request finished with status: {}", status); + // MDC.clear(); // Clear MDC after the request super.onClose(status, trailers); } }, @@ -66,7 +95,17 @@ public void onClose(Status status, Metadata trailers) { @Override public void sendMessage(ReqT message) { - MDC.put("request.payload", gson.toJson(message)); + // MDC.put("request.payload", gson.toJson(message)); + requestLogData.addProperty("request.payload", gson.toJson(message)); + + Map map = new HashMap<>(); + serviceAndRpc + .entrySet() + .forEach(entry -> map.put(entry.getKey(), entry.getValue().getAsString())); + // MDC.setContextMap(map); + // logger.info(new MapMessage(map)); + logger.info(gson.toJson(LoggingUtils.mergeJsonObject(serviceAndRpc, requestLogData))); + // MDC.clear(); super.sendMessage(message); } }; diff --git a/gax-java/gax/pom.xml b/gax-java/gax/pom.xml index 1c919e09d5..5ada305a25 100644 --- a/gax-java/gax/pom.xml +++ b/gax-java/gax/pom.xml @@ -69,11 +69,11 @@ opentelemetry-api true + org.slf4j slf4j-api 2.0.16 - diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/Logger.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/Logger.java deleted file mode 100644 index 9f5d7fc15c..0000000000 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/Logger.java +++ /dev/null @@ -1,193 +0,0 @@ -package com.google.api.gax.logging; - -import java.util.function.Supplier; -import org.slf4j.LoggerFactory; -import org.slf4j.event.Level; - -public final class Logger { - private final org.slf4j.Logger log; - - Logger(org.slf4j.Logger log) { - this.log = log; - } - - public org.slf4j.Logger logger() { - return log; - } - - /** - * Checks if info is enabled and if so logs the supplied message - * - * @param msg - supplier for the log message - */ - public void info(Supplier msg) { - if (log.isInfoEnabled()) { - log.info(msg.get()); - } - } - - /** - * Checks if info is enabled and if so logs the supplied message and exception - * - * @param msg - supplier for the log message - * @param throwable - a throwable to log - */ - public void info(Supplier msg, Throwable throwable) { - if (log.isInfoEnabled()) { - log.info(msg.get(), throwable); - } - } - - /** - * Checks if error is enabled and if so logs the supplied message - * - * @param msg - supplier for the log message - */ - public void error(Supplier msg) { - if (log.isErrorEnabled()) { - log.error(msg.get()); - } - } - - /** - * Checks if error is enabled and if so logs the supplied message and exception - * - * @param msg - supplier for the log message - * @param throwable - a throwable to log - */ - public void error(Supplier msg, Throwable throwable) { - if (log.isErrorEnabled()) { - log.error(msg.get(), throwable); - } - } - - /** - * Checks if debug is enabled and if so logs the supplied message - * - * @param msg - supplier for the log message - */ - public void debug(Supplier msg) { - if (log.isDebugEnabled()) { - log.debug(msg.get()); - } - } - - /** - * Checks if debug is enabled and if so logs the supplied message and exception - * - * @param msg - supplier for the log message - * @param throwable - a throwable to log - */ - public void debug(Supplier msg, Throwable throwable) { - if (log.isDebugEnabled()) { - log.debug(msg.get(), throwable); - } - } - - /** - * Checks if warn is enabled and if so logs the supplied message - * - * @param msg - supplier for the log message - */ - public void warn(Supplier msg) { - if (log.isWarnEnabled()) { - log.warn(msg.get()); - } - } - - /** - * Checks if warn is enabled and if so logs the supplied message and exception - * - * @param msg - supplier for the log message - * @param throwable - a throwable to log - */ - public void warn(Supplier msg, Throwable throwable) { - if (log.isWarnEnabled()) { - log.warn(msg.get(), throwable); - } - } - - /** - * Checks if trace is enabled and if so logs the supplied message - * - * @param msg - supplier for the log message - */ - public void trace(Supplier msg) { - if (log.isTraceEnabled()) { - log.trace(msg.get()); - } - } - - /** - * Checks if trace is enabled and if so logs the supplied message and exception - * - * @param msg - supplier for the log message - * @param throwable - a throwable to log - */ - public void trace(Supplier msg, Throwable throwable) { - if (log.isTraceEnabled()) { - log.trace(msg.get(), throwable); - } - } - - /** - * Determines if the provided log-level is enabled. - * - * @param logLevel the SLF4J log level enum - * @return whether that level is enabled - */ - public boolean isLoggingLevelEnabled(Level logLevel) { - switch (logLevel) { - case TRACE: - return log.isTraceEnabled(); - case DEBUG: - return log.isDebugEnabled(); - case INFO: - return log.isInfoEnabled(); - case WARN: - return log.isWarnEnabled(); - case ERROR: - return log.isErrorEnabled(); - default: - throw new IllegalStateException("Unsupported log level: " + logLevel); - } - } - - /** - * Log a message at the given log level (if it is enabled). - * - * @param logLevel the SLF4J log level - * @param msg supplier for the log message - */ - public void log(Level logLevel, Supplier msg) { - switch (logLevel) { - case TRACE: - trace(msg); - break; - case DEBUG: - debug(msg); - break; - case INFO: - info(msg); - break; - case WARN: - warn(msg); - break; - case ERROR: - error(msg); - break; - default: - throw new IllegalStateException("Unsupported log level: " + logLevel); - } - } - - /** - * Static factory to get a logger instance for a given class - * - * @param clz - class to get the logger for - * @return a Logger instance - */ - public static Logger loggerFor(Class clz) { - return new Logger(LoggerFactory.getLogger(clz)); - } -} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java index 10b7b2425e..cf5f4ffccd 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java @@ -1,48 +1,353 @@ package com.google.api.gax.logging; -import java.util.Map; +import com.google.gson.JsonObject; +import java.util.logging.Level; +import org.slf4j.ILoggerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.MDC; -import org.slf4j.event.Level; +import org.slf4j.Marker; +import org.slf4j.helpers.FormattingTuple; +import org.slf4j.helpers.MessageFormatter; public class LoggingUtils { + private static final java.util.logging.Logger LOGGER = + java.util.logging.Logger.getLogger(LoggingUtils.class.getName()); + + private LoggingUtils() {} + public static Logger getLogger(Class clazz) { - return LoggerFactory.getLogger(clazz); + + Logger logger; + + if (isLoggingEnabled()) { + + ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); + if (loggerFactory != null && !(loggerFactory instanceof org.slf4j.helpers.NOPLoggerFactory)) { + // An SLF4j binding is present + // You can get the logger and use it: + logger = LoggerFactory.getLogger(clazz); + logger.debug("SLF4J BINDING FOUND!!!!!"); + // ... + } else { + // No SLF4j binding found + // Implement your fallback logic here + logger = new JulWrapperLogger(clazz.getName()); + logger.info("No SLF4J providers were found, fall back to JUL."); + } + } else { + // use SLF4j's NOP logger regardless of bindings + logger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); + } + return logger; } public static boolean isLoggingEnabled() { String enableLogging = System.getenv("GOOGLE_SDK_JAVA_LOGGING"); + LOGGER.info("GOOGLE_SDK_JAVA_LOGGING=" + enableLogging); return "true".equalsIgnoreCase(enableLogging); } - public static void log(Logger logger, Level level, String msg, Map contextMap) { - - if (LoggingUtils.isLoggingEnabled()) { - MDC.setContextMap(contextMap); - switch (level) { - case TRACE: - logger.trace(msg); - break; - case DEBUG: - logger.debug(msg); - break; - case INFO: - logger.info(msg); - break; - case WARN: - logger.warn(msg); - break; - case ERROR: - logger.error(msg); - break; - default: - logger.info(msg); - // Default to INFO level if level is invalid - break; + public static JsonObject mergeJsonObject(JsonObject jsonObject1, JsonObject jsonObject2) { + JsonObject mergedObject = jsonObject1.deepCopy(); + jsonObject2.entrySet().forEach(entry -> mergedObject.add(entry.getKey(), entry.getValue())); + return mergedObject; + } + // JulWrapperLogger implementation + static class JulWrapperLogger implements Logger { + + private final java.util.logging.Logger julLogger; + + public JulWrapperLogger(String name) { + this.julLogger = java.util.logging.Logger.getLogger(name); + } + + @Override + public String getName() { + return julLogger.getName(); + } + + @Override + public boolean isTraceEnabled() { + return julLogger.isLoggable(java.util.logging.Level.FINEST); + } + + @Override + public void trace(String msg) { + julLogger.log(java.util.logging.Level.FINEST, msg); + } + + @Override + public void trace(String s, Object o) {} + + @Override + public void trace(String s, Object o, Object o1) {} + + @Override + public void trace(String s, Object... objects) {} + + @Override + public void trace(String s, Throwable throwable) {} + + @Override + public boolean isTraceEnabled(Marker marker) { + return false; + } + + @Override + public void trace(Marker marker, String s) {} + + @Override + public void trace(Marker marker, String s, Object o) {} + + @Override + public void trace(Marker marker, String s, Object o, Object o1) {} + + @Override + public void trace(Marker marker, String s, Object... objects) {} + + @Override + public void trace(Marker marker, String s, Throwable throwable) {} + + @Override + public boolean isDebugEnabled() { + return julLogger.isLoggable(Level.FINE); + } + + @Override + public void debug(String msg) { + + if (isDebugEnabled()) { + julLogger.log(java.util.logging.Level.FINE, msg); + } + } + + @Override + public void debug(String format, Object arg) { + if (isDebugEnabled()) { + FormattingTuple ft = MessageFormatter.format(format, arg); + julLogger.log(Level.FINE, ft.getMessage()); + } + } + + @Override + public void debug(String s, Object o, Object o1) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void debug(String s, Object... objects) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void debug(String s, Throwable throwable) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public boolean isDebugEnabled(Marker marker) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void debug(Marker marker, String s) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void debug(Marker marker, String s, Object o) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void debug(Marker marker, String s, Object o, Object o1) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void debug(Marker marker, String s, Object... objects) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void debug(Marker marker, String s, Throwable throwable) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public boolean isInfoEnabled() { + return julLogger.isLoggable(Level.INFO); + } + + @Override + public void info(String msg) { + if (isInfoEnabled()) { + julLogger.log(java.util.logging.Level.INFO, msg); + } + } + + @Override + public void info(String format, Object arg) { + if (isInfoEnabled()) { + FormattingTuple ft = MessageFormatter.format(format, arg); + julLogger.log(java.util.logging.Level.INFO, ft.getMessage()); } - MDC.clear(); } + + @Override + public void info(String s, Object o, Object o1) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void info(String s, Object... objects) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void info(String s, Throwable throwable) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public boolean isInfoEnabled(Marker marker) { + return true; + } + + @Override + public void info(Marker marker, String s) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void info(Marker marker, String s, Object o) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void info(Marker marker, String s, Object o, Object o1) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void info(Marker marker, String s, Object... objects) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void info(Marker marker, String s, Throwable throwable) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public boolean isWarnEnabled() { + return true; + } + + @Override + public void warn(String msg) { + julLogger.log(Level.WARNING, msg); + } + + @Override + public void warn(String s, Object o) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void warn(String s, Object... objects) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void warn(String s, Object o, Object o1) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void warn(String s, Throwable throwable) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public boolean isWarnEnabled(Marker marker) { + return false; + } + + @Override + public void warn(Marker marker, String s) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void warn(Marker marker, String s, Object o) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void warn(Marker marker, String s, Object o, Object o1) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void warn(Marker marker, String s, Object... objects) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void warn(Marker marker, String s, Throwable throwable) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public boolean isErrorEnabled() { + return false; + } + + @Override + public void error(String s) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void error(String s, Object o) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void error(String s, Object o, Object o1) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void error(String s, Object... objects) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public void error(String s, Throwable throwable) { + throw new UnsupportedOperationException("This method is not supported."); + } + + @Override + public boolean isErrorEnabled(Marker marker) { + return false; + } + + @Override + public void error(Marker marker, String s) {} + + @Override + public void error(Marker marker, String s, Object o) {} + + @Override + public void error(Marker marker, String s, Object o, Object o1) {} + + @Override + public void error(Marker marker, String s, Object... objects) {} + + @Override + public void error(Marker marker, String s, Throwable throwable) {} } } diff --git a/showcase/gapic-showcase/src/test/resources/logging.properties b/showcase/gapic-showcase/src/test/resources/logging.properties new file mode 100644 index 0000000000..ea11473c27 --- /dev/null +++ b/showcase/gapic-showcase/src/test/resources/logging.properties @@ -0,0 +1,4 @@ +handlers=java.util.logging.ConsoleHandler + +java.util.logging.ConsoleHandler.level=ALL +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter From 0c169ad9a3fdac797fb74576d650992445aeeb62 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Mon, 25 Nov 2024 16:45:32 -0500 Subject: [PATCH 04/45] temp manual testing with showcase. --- showcase/gapic-showcase/pom.xml | 47 +++++++++-------- .../showcase/v1beta1/it/ITUnaryCallable.java | 52 +++++++++++++++++++ .../src/test/resources/logback-test.xml | 5 +- 3 files changed, 79 insertions(+), 25 deletions(-) diff --git a/showcase/gapic-showcase/pom.xml b/showcase/gapic-showcase/pom.xml index 0cea523ccd..8968145955 100644 --- a/showcase/gapic-showcase/pom.xml +++ b/showcase/gapic-showcase/pom.xml @@ -216,28 +216,29 @@ test - - org.slf4j - slf4j-api - 2.0.16 - - - ch.qos.logback - logback-classic - 1.5.11 - test - - - net.logstash.logback - logstash-logback-encoder - 7.2 - test - - - org.codehaus.janino - janino - 3.1.9 - test - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java index 4d6018f6fc..c9ec84b798 100644 --- a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java +++ b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java @@ -22,12 +22,22 @@ import com.google.api.gax.grpc.GrpcStatusCode; import com.google.api.gax.rpc.CancelledException; import com.google.api.gax.rpc.StatusCode; +import com.google.gson.Gson; +import com.google.gson.JsonObject; import com.google.rpc.Status; import com.google.showcase.v1beta1.EchoClient; import com.google.showcase.v1beta1.EchoRequest; import com.google.showcase.v1beta1.EchoResponse; import com.google.showcase.v1beta1.it.util.TestClientInitializer; import java.util.concurrent.TimeUnit; +import java.util.logging.ConsoleHandler; +import java.util.logging.Filter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogManager; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -37,6 +47,7 @@ class ITUnaryCallable { private static EchoClient grpcClient; private static EchoClient httpjsonClient; + static final Logger LOGGER = Logger.getLogger(ITUnaryCallable.class.getName()); @BeforeAll static void createClients() throws Exception { @@ -44,6 +55,33 @@ static void createClients() throws Exception { grpcClient = TestClientInitializer.createGrpcEchoClient(); // Create Http JSON Echo Client httpjsonClient = TestClientInitializer.createHttpJsonEchoClient(); + + // Get the root logger + Logger rootLogger = + LogManager.getLogManager().getLogger(""); + // Set the root logger's level to ALL or FINEST to see DEBUG messages + // rootLogger.setLevel(Level.ALL); // or rootLogger.setLevel(Level.FINEST); + // Remove any existing handlers (if needed) + for (Handler handler : rootLogger.getHandlers()) { + rootLogger.removeHandler(handler); + } + + // Create a ConsoleHandler + ConsoleHandler consoleHandler = new ConsoleHandler(); + consoleHandler.setLevel(Level.ALL); + consoleHandler.setFormatter(new SimpleFormatter()); + // String targetClassName = "com.google.api.gax.logging.LoggingUtils$JulWrapperLogger"; + // Filter filter = new Filter() { + // @Override + // public boolean isLoggable(LogRecord record) { + // return record.getLoggerName().equals(targetClassName); + // } + // }; + // consoleHandler.setFilter(filter); + + // Add the ConsoleHandler to the root logger + rootLogger.addHandler(consoleHandler); + LOGGER.log(Level.INFO, "This is log message directly from JUL. Clients created."); } @AfterAll @@ -55,9 +93,23 @@ static void destroyClients() throws InterruptedException { httpjsonClient.awaitTermination( TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); } + @Test + void test() { + Gson gson = new Gson(); + JsonObject jsonObject1 = gson.fromJson("{\"name\":\"John\", \"age\":30}", JsonObject.class); + JsonObject jsonObject2 = gson.fromJson("{\"city\":\"New York\", \"country\":\"USA\"}", JsonObject.class); + + JsonObject mergedObject = jsonObject1.deepCopy(); + mergedObject.entrySet().forEach(entry -> jsonObject2.add(entry.getKey(), entry.getValue())); + + System.out.println(jsonObject2); // Output: {"city":"New York", "country":"USA", "name":"John", "age":30} + } @Test void testGrpc_receiveContent() { + LOGGER.log( + Level.INFO, + "This is log message directly from JUL. Starting test: testGrpc_receiveContent."); assertThat(echoGrpc("grpc-echo?")).isEqualTo("grpc-echo?"); assertThat(echoGrpc("grpc-echo!")).isEqualTo("grpc-echo!"); } diff --git a/showcase/gapic-showcase/src/test/resources/logback-test.xml b/showcase/gapic-showcase/src/test/resources/logback-test.xml index 02b2596516..df5a14382a 100644 --- a/showcase/gapic-showcase/src/test/resources/logback-test.xml +++ b/showcase/gapic-showcase/src/test/resources/logback-test.xml @@ -5,8 +5,9 @@ - logger.equals("com.google.api.gax.grpc.GrpcLoggingInterceptor") - logger.equals("com.google.api.gax.httpjson.HttpJsonLoggingInterceptor") + logger.equals("com.google.api.gax.grpc.GrpcLoggingInterceptor") || + logger.equals("com.google.api.gax.httpjson.HttpJsonLoggingInterceptor") || + logger.equals("com.google.api.gax.tracing.MetricsTracer") ACCEPT From 9ced17581257fc17aadcd5c41c22334df446da1b Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Mon, 25 Nov 2024 17:19:19 -0500 Subject: [PATCH 05/45] simplify logic for conditional logger. --- .../google/api/gax/logging/LoggingUtils.java | 66 +++++++++++++------ 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java index cf5f4ffccd..8aa9f5e60d 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java @@ -1,5 +1,36 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + package com.google.api.gax.logging; +import com.google.api.core.InternalApi; import com.google.gson.JsonObject; import java.util.logging.Level; import org.slf4j.ILoggerFactory; @@ -9,6 +40,7 @@ import org.slf4j.helpers.FormattingTuple; import org.slf4j.helpers.MessageFormatter; +@InternalApi public class LoggingUtils { private static final java.util.logging.Logger LOGGER = @@ -17,34 +49,25 @@ public class LoggingUtils { private LoggingUtils() {} public static Logger getLogger(Class clazz) { - - Logger logger; - - if (isLoggingEnabled()) { - - ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); - if (loggerFactory != null && !(loggerFactory instanceof org.slf4j.helpers.NOPLoggerFactory)) { - // An SLF4j binding is present - // You can get the logger and use it: - logger = LoggerFactory.getLogger(clazz); - logger.debug("SLF4J BINDING FOUND!!!!!"); - // ... - } else { - // No SLF4j binding found - // Implement your fallback logic here - logger = new JulWrapperLogger(clazz.getName()); - logger.info("No SLF4J providers were found, fall back to JUL."); - } - } else { + if (!isLoggingEnabled()) { // use SLF4j's NOP logger regardless of bindings - logger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); + return LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); } + + ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); + if (loggerFactory != null && !(loggerFactory instanceof org.slf4j.helpers.NOPLoggerFactory)) { + // Use SLF4j binding when present + return LoggerFactory.getLogger(clazz); + } + // No SLF4j binding found, use JUL as fallback + Logger logger = new JulWrapperLogger(clazz.getName()); + logger.info("No SLF4J providers were found, fall back to JUL."); return logger; } public static boolean isLoggingEnabled() { String enableLogging = System.getenv("GOOGLE_SDK_JAVA_LOGGING"); - LOGGER.info("GOOGLE_SDK_JAVA_LOGGING=" + enableLogging); + LOGGER.info("GOOGLE_SDK_JAVA_LOGGING=" + enableLogging); // log for debug now, remove it. return "true".equalsIgnoreCase(enableLogging); } @@ -53,6 +76,7 @@ public static JsonObject mergeJsonObject(JsonObject jsonObject1, JsonObject json jsonObject2.entrySet().forEach(entry -> mergedObject.add(entry.getKey(), entry.getValue())); return mergedObject; } + // JulWrapperLogger implementation static class JulWrapperLogger implements Logger { From 681f7d55b8238e9e4a7ac3f9f01ff01d152a6349 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Tue, 26 Nov 2024 17:35:43 -0500 Subject: [PATCH 06/45] implement logging for basic case. --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 109 +++++++--------- .../httpjson/HttpJsonLoggingInterceptor.java | 71 ++++++++--- .../gax/httpjson/ManagedHttpJsonChannel.java | 4 + .../gax/logging/JsonContextMapHandler.java | 45 +++++++ .../google/api/gax/logging/LoggingUtils.java | 119 +++++++++++++++--- 5 files changed, 257 insertions(+), 91 deletions(-) create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/logging/JsonContextMapHandler.java diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index 9a92921987..9fea7f1200 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -6,14 +6,16 @@ import io.grpc.*; import java.util.HashMap; import java.util.Map; +import java.util.UUID; import org.slf4j.Logger; +import org.slf4j.event.Level; public class GrpcLoggingInterceptor implements ClientInterceptor { private static final Logger logger = LoggingUtils.getLogger(GrpcLoggingInterceptor.class); private static final Gson gson = new Gson(); - private JsonObject serviceAndRpc = new JsonObject(); - private JsonObject requestLogData = new JsonObject(); + private Map serviceAndRpc = new HashMap<>(); + private Map requestLogData = new HashMap<>(); @Override public ClientCall interceptCall( @@ -24,33 +26,21 @@ public ClientCall interceptCall( @Override public void start(Listener responseListener, Metadata headers) { - // if (LoggingUtils.isLoggingEnabled()) { - // Capture request details + String requestId = UUID.randomUUID().toString(); String serviceName = method.getServiceName(); String methodName = method.getFullMethodName(); - serviceAndRpc.addProperty("serviceName", serviceName); - serviceAndRpc.addProperty("rpcName", methodName); - - JsonObject responseLogData = new JsonObject(); - // Add request details to MDC// Example system - // MDC.put("serviceName", serviceName); - // MDC.put("rpcName", methodName); - // Capture and log headers - headers - .keys() - .forEach( - key -> { - Metadata.Key metadataKey = - Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); - String headerValue = headers.get(metadataKey); - // MDC.put("request.headers:" + key, headerValue); - requestLogData.addProperty("request.headers:" + key, headerValue); - }); - - requestLogData.addProperty("message", "Sending gRPC request"); - // logger.debug("Sending gRPC request"); - // } + serviceAndRpc.put("serviceName", serviceName); + serviceAndRpc.put("rpcName", methodName); + serviceAndRpc.put("requestId", requestId); + + requestLogData.putAll(serviceAndRpc); + + LoggingUtils.logWithMDC(logger, Level.INFO, serviceAndRpc, "Sending gRPC request"); + + Map responseLogData = new HashMap<>(); + JsonObject requestHeaders = mapHeadersToJsonObject(headers); + requestLogData.put("request.headers", gson.toJson(requestHeaders)); super.start( new ForwardingClientCallListener.SimpleForwardingClientCallListener( @@ -58,35 +48,24 @@ public void start(Listener responseListener, Metadata headers) { @Override public void onMessage(RespT message) { - responseLogData.addProperty("response.payload", gson.toJson(message)); + responseLogData.put("response.payload", gson.toJson(message)); super.onMessage(message); } @Override public void onClose(Status status, Metadata trailers) { - responseLogData.addProperty("response.status", status.getCode().name()); - responseLogData.addProperty( - "message", String.format("gRPC request finished with status: %s", status)); - - // Create a JsonObject for response headers - JsonObject responseHeaders = new JsonObject(); - - // Access and add response headers to the JsonObject - trailers - .keys() - .forEach( - key -> { - Metadata.Key metadataKey = - Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); - String headerValue = trailers.get(metadataKey); - responseHeaders.addProperty(key, headerValue); - }); - responseLogData.add("response.headers", responseHeaders); - - logger.debug( - gson.toJson(LoggingUtils.mergeJsonObject(serviceAndRpc, responseLogData))); - // logger.info("gRPC request finished with status: {}", status); - // MDC.clear(); // Clear MDC after the request + serviceAndRpc.put("response.status", status.getCode().name()); + responseLogData.putAll(serviceAndRpc); + + LoggingUtils.logWithMDC(logger, Level.INFO, serviceAndRpc, "Received response."); + + // Access and add response headers + JsonObject responseHeaders = mapHeadersToJsonObject(trailers); + responseLogData.put("response.headers", gson.toJson(responseHeaders)); + + LoggingUtils.logWithMDC( + logger, Level.DEBUG, responseLogData, "Received response header and payload."); + super.onClose(status, trailers); } }, @@ -95,19 +74,27 @@ public void onClose(Status status, Metadata trailers) { @Override public void sendMessage(ReqT message) { - // MDC.put("request.payload", gson.toJson(message)); - requestLogData.addProperty("request.payload", gson.toJson(message)); - - Map map = new HashMap<>(); - serviceAndRpc - .entrySet() - .forEach(entry -> map.put(entry.getKey(), entry.getValue().getAsString())); - // MDC.setContextMap(map); - // logger.info(new MapMessage(map)); - logger.info(gson.toJson(LoggingUtils.mergeJsonObject(serviceAndRpc, requestLogData))); - // MDC.clear(); + + requestLogData.put("request.payload", gson.toJson(message)); + + LoggingUtils.logWithMDC( + logger, Level.DEBUG, requestLogData, "grpc request header and payload."); super.sendMessage(message); } }; } + + private static JsonObject mapHeadersToJsonObject(Metadata headers) { + JsonObject jsonHeaders = new JsonObject(); + headers + .keys() + .forEach( + key -> { + Metadata.Key metadataKey = + Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER); + String headerValue = headers.get(metadataKey); + jsonHeaders.addProperty(key, headerValue); + }); + return jsonHeaders; + } } diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java index 4195d9c386..05df206970 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -1,16 +1,21 @@ package com.google.api.gax.httpjson; import com.google.api.gax.httpjson.ForwardingHttpJsonClientCall.SimpleForwardingHttpJsonClientCall; +import com.google.api.gax.logging.LoggingUtils; import com.google.gson.Gson; +import com.google.gson.JsonObject; +import java.util.HashMap; import java.util.Map; +import java.util.UUID; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.MDC; +import org.slf4j.event.Level; public class HttpJsonLoggingInterceptor implements HttpJsonClientInterceptor { - private static final Logger logger = LoggerFactory.getLogger(HttpJsonLoggingInterceptor.class); + private static final Logger logger = LoggingUtils.getLogger(HttpJsonLoggingInterceptor.class); private static final Gson gson = new Gson(); + private Map serviceAndRpc = new HashMap<>(); + private Map requestLogData = new HashMap<>(); @Override public HttpJsonClientCall interceptCall( @@ -18,43 +23,79 @@ public HttpJsonClientCall interceptCall( HttpJsonCallOptions callOptions, HttpJsonChannel next) { + String endpoint = ((ManagedHttpJsonChannel) next).getEndpoint(); return new SimpleForwardingHttpJsonClientCall(next.newCall(method, callOptions)) { @Override public void start( HttpJsonClientCall.Listener responseListener, HttpJsonMetadata headers) { + String requestId = UUID.randomUUID().toString(); // Capture request details String methodName = method.getFullMethodName(); + String httpMethod = method.getHttpMethod(); - // Add request details to MDC - MDC.put("method", methodName); + serviceAndRpc.put("rpcName", methodName); + serviceAndRpc.put("requestId", requestId); + requestLogData.putAll(serviceAndRpc); + requestLogData.put("request.url", endpoint); + requestLogData.put("request.method", httpMethod); // Capture and log headers - for (Map.Entry header : headers.getHeaders().entrySet()) { - MDC.put("header." + header.getKey(), header.getValue().toString()); - } - - logger.info("Sending HTTP request"); + JsonObject jsonHeaders = new JsonObject(); + headers + .getHeaders() + .entrySet() + .forEach( + entry -> { + jsonHeaders.addProperty(entry.getKey(), entry.getValue().toString()); + }); + requestLogData.put("request.headers", gson.toJson(jsonHeaders)); + LoggingUtils.logWithMDC(logger, Level.INFO, serviceAndRpc, "Sending HTTP request"); + Map responseLogData = new HashMap<>(); super.start( new HttpJsonClientCall.Listener() { @Override public void onMessage(RespT message) { - MDC.put("response.payload", gson.toJson(message)); - logger.debug("Received HTTP response."); + responseLogData.put("response.payload", gson.toJson(message)); responseListener.onMessage(message); } @Override public void onClose(int status, HttpJsonMetadata trailers) { - MDC.put("response.status", String.valueOf(status)); - logger.info("HTTP request finished with status: {}", status); - MDC.clear(); // Clear MDC after the request + serviceAndRpc.put("response.status", String.valueOf(status)); + responseLogData.putAll(serviceAndRpc); + LoggingUtils.logWithMDC( + logger, Level.INFO, serviceAndRpc, "HTTP request finished."); + + JsonObject jsonHeaders = new JsonObject(); + headers + .getHeaders() + .entrySet() + .forEach( + entry -> { + jsonHeaders.addProperty(entry.getKey(), entry.getValue().toString()); + }); + responseLogData.put("request.headers", gson.toJson(jsonHeaders)); + + LoggingUtils.logWithMDC( + logger, Level.DEBUG, responseLogData, "Received response header and payload."); + responseListener.onClose(status, trailers); } }, headers); } + + @Override + public void sendMessage(ReqT message) { + requestLogData.put("request.payload", gson.toJson(message)); + + LoggingUtils.logWithMDC( + logger, Level.DEBUG, requestLogData, "HTTP request header and payload."); + + super.sendMessage(message); + } }; } } diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ManagedHttpJsonChannel.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ManagedHttpJsonChannel.java index 7a2e7a2f26..bd3bed8556 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ManagedHttpJsonChannel.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/ManagedHttpJsonChannel.java @@ -57,6 +57,10 @@ protected ManagedHttpJsonChannel() { this(null, true, null, null); } + String getEndpoint() { + return endpoint; + } + private ManagedHttpJsonChannel( Executor executor, boolean usingDefaultExecutor, diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/JsonContextMapHandler.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/JsonContextMapHandler.java new file mode 100644 index 0000000000..74c7d30e13 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/JsonContextMapHandler.java @@ -0,0 +1,45 @@ +package com.google.api.gax.logging; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +public class JsonContextMapHandler extends Handler { + + private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + @Override + public void publish(LogRecord record) { + Object[] params = record.getParameters(); + if (params != null && params.length > 0 && params[0] instanceof Map) { + @SuppressWarnings("unchecked") + Map contextMap = (Map) params[0]; + + // Create a map to hold all log data + Map logData = new HashMap<>(); + logData.put("message", record.getMessage()); + logData.put("severity", record.getLevel().getName()); + logData.put("timestamp", record.getMillis()); + logData.put("logger", record.getLoggerName()); + + // Add all context data to the top level + logData.putAll(contextMap); + + // Convert to JSON + String jsonLog = gson.toJson(logData); + System.out.println(jsonLog); + } else { + // Handle cases where the context map is not present + System.out.println("Log message without context: " + record.getMessage()); + } + } + + @Override + public void flush() {} + + @Override + public void close() throws SecurityException {} +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java index 8aa9f5e60d..034358bdb5 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java @@ -32,10 +32,13 @@ import com.google.api.core.InternalApi; import com.google.gson.JsonObject; +import java.util.Map; import java.util.logging.Level; +import java.util.logging.LogRecord; import org.slf4j.ILoggerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.MDC; import org.slf4j.Marker; import org.slf4j.helpers.FormattingTuple; import org.slf4j.helpers.MessageFormatter; @@ -77,6 +80,60 @@ public static JsonObject mergeJsonObject(JsonObject jsonObject1, JsonObject json return mergedObject; } + public static Level mapToJulLevel(org.slf4j.event.Level slf4jLevel) { + switch (slf4jLevel) { + case ERROR: + return Level.SEVERE; + case WARN: + return Level.WARNING; + case INFO: + return Level.INFO; + case DEBUG: + return Level.FINE; + case TRACE: + return Level.FINEST; + default: + return Level.INFO; + } + } + + public static void logWithMDC( + Logger logger, org.slf4j.event.Level level, Map contextMap, String message) { + + if (logger instanceof JulWrapperLogger) { + // Simulate MDC behavior for JUL + LogRecord record = new LogRecord(mapToJulLevel(level), message); + // Add context map to the LogRecord + record.setParameters(new Object[] {contextMap}); + ((JulWrapperLogger) logger).getJulLogger().log(record); + return; + } + contextMap.forEach(MDC::put); + + switch (level) { + case TRACE: + logger.trace(message); + break; + case DEBUG: + logger.debug(message); + break; + case INFO: + logger.info(message); + break; + case WARN: + logger.warn(message); + break; + case ERROR: + logger.error(message); + break; + default: + logger.info(message); + // Default to INFO level + } + + MDC.clear(); + } + // JulWrapperLogger implementation static class JulWrapperLogger implements Logger { @@ -86,6 +143,10 @@ public JulWrapperLogger(String name) { this.julLogger = java.util.logging.Logger.getLogger(name); } + public java.util.logging.Logger getJulLogger() { + return julLogger; + } + @Override public String getName() { return julLogger.getName(); @@ -102,16 +163,24 @@ public void trace(String msg) { } @Override - public void trace(String s, Object o) {} + public void trace(String s, Object o) { + throw new UnsupportedOperationException("This method is not supported."); + } @Override - public void trace(String s, Object o, Object o1) {} + public void trace(String s, Object o, Object o1) { + throw new UnsupportedOperationException("This method is not supported."); + } @Override - public void trace(String s, Object... objects) {} + public void trace(String s, Object... objects) { + throw new UnsupportedOperationException("This method is not supported."); + } @Override - public void trace(String s, Throwable throwable) {} + public void trace(String s, Throwable throwable) { + throw new UnsupportedOperationException("This method is not supported."); + } @Override public boolean isTraceEnabled(Marker marker) { @@ -119,19 +188,29 @@ public boolean isTraceEnabled(Marker marker) { } @Override - public void trace(Marker marker, String s) {} + public void trace(Marker marker, String s) { + throw new UnsupportedOperationException("This method is not supported."); + } @Override - public void trace(Marker marker, String s, Object o) {} + public void trace(Marker marker, String s, Object o) { + throw new UnsupportedOperationException("This method is not supported."); + } @Override - public void trace(Marker marker, String s, Object o, Object o1) {} + public void trace(Marker marker, String s, Object o, Object o1) { + throw new UnsupportedOperationException("This method is not supported."); + } @Override - public void trace(Marker marker, String s, Object... objects) {} + public void trace(Marker marker, String s, Object... objects) { + throw new UnsupportedOperationException("This method is not supported."); + } @Override - public void trace(Marker marker, String s, Throwable throwable) {} + public void trace(Marker marker, String s, Throwable throwable) { + throw new UnsupportedOperationException("This method is not supported."); + } @Override public boolean isDebugEnabled() { @@ -356,22 +435,32 @@ public void error(String s, Throwable throwable) { @Override public boolean isErrorEnabled(Marker marker) { - return false; + throw new UnsupportedOperationException("This method is not supported."); } @Override - public void error(Marker marker, String s) {} + public void error(Marker marker, String s) { + throw new UnsupportedOperationException("This method is not supported."); + } @Override - public void error(Marker marker, String s, Object o) {} + public void error(Marker marker, String s, Object o) { + throw new UnsupportedOperationException("This method is not supported."); + } @Override - public void error(Marker marker, String s, Object o, Object o1) {} + public void error(Marker marker, String s, Object o, Object o1) { + throw new UnsupportedOperationException("This method is not supported."); + } @Override - public void error(Marker marker, String s, Object... objects) {} + public void error(Marker marker, String s, Object... objects) { + throw new UnsupportedOperationException("This method is not supported."); + } @Override - public void error(Marker marker, String s, Throwable throwable) {} + public void error(Marker marker, String s, Throwable throwable) { + throw new UnsupportedOperationException("This method is not supported."); + } } } From 5c01ef24cd922bfca0a8975613b09777a3cd28de Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Tue, 26 Nov 2024 20:53:46 -0500 Subject: [PATCH 07/45] add a JsonContextMapHandler to format in JUL case. --- .../gax/logging/JsonContextMapHandler.java | 41 ++++++++++++++++++- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/JsonContextMapHandler.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/JsonContextMapHandler.java index 74c7d30e13..870cb62ae5 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/JsonContextMapHandler.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/JsonContextMapHandler.java @@ -1,9 +1,40 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + package com.google.api.gax.logging; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.HashMap; import java.util.Map; +import java.util.logging.ConsoleHandler; import java.util.logging.Handler; import java.util.logging.LogRecord; @@ -14,6 +45,8 @@ public class JsonContextMapHandler extends Handler { @Override public void publish(LogRecord record) { Object[] params = record.getParameters(); + + ConsoleHandler consoleHandler = new ConsoleHandler(); if (params != null && params.length > 0 && params[0] instanceof Map) { @SuppressWarnings("unchecked") Map contextMap = (Map) params[0]; @@ -30,10 +63,14 @@ public void publish(LogRecord record) { // Convert to JSON String jsonLog = gson.toJson(logData); - System.out.println(jsonLog); + + LogRecord jsonRecord = new LogRecord(record.getLevel(), jsonLog); + consoleHandler.publish(jsonRecord); } else { // Handle cases where the context map is not present - System.out.println("Log message without context: " + record.getMessage()); + LogRecord messageRecord = + new LogRecord(record.getLevel(), "Log message without context: " + record.getMessage()); + consoleHandler.publish(messageRecord); } } From 6605ef3878164544bfdf14ac0699e4824fb76877 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Wed, 27 Nov 2024 11:12:04 -0500 Subject: [PATCH 08/45] fix response payload for streaming. Add if guards to bypass log when not enabled. --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 84 ++++++++------ .../httpjson/HttpJsonLoggingInterceptor.java | 109 ++++++++++-------- 2 files changed, 113 insertions(+), 80 deletions(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index 9fea7f1200..e599c9eb68 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -2,6 +2,7 @@ import com.google.api.gax.logging.LoggingUtils; import com.google.gson.Gson; +import com.google.gson.JsonArray; import com.google.gson.JsonObject; import io.grpc.*; import java.util.HashMap; @@ -14,57 +15,73 @@ public class GrpcLoggingInterceptor implements ClientInterceptor { private static final Logger logger = LoggingUtils.getLogger(GrpcLoggingInterceptor.class); private static final Gson gson = new Gson(); - private Map serviceAndRpc = new HashMap<>(); - private Map requestLogData = new HashMap<>(); @Override public ClientCall interceptCall( MethodDescriptor method, CallOptions callOptions, Channel next) { + Map serviceAndRpc = new HashMap<>(); + Map requestLogData = new HashMap<>(); + Map responseLogData = new HashMap<>(); + + // Initialize a JsonArray to hold all responses + JsonArray responsePayloads = new JsonArray(); return new ForwardingClientCall.SimpleForwardingClientCall( next.newCall(method, callOptions)) { @Override public void start(Listener responseListener, Metadata headers) { - String requestId = UUID.randomUUID().toString(); - String serviceName = method.getServiceName(); - String methodName = method.getFullMethodName(); - - serviceAndRpc.put("serviceName", serviceName); - serviceAndRpc.put("rpcName", methodName); - serviceAndRpc.put("requestId", requestId); - - requestLogData.putAll(serviceAndRpc); - - LoggingUtils.logWithMDC(logger, Level.INFO, serviceAndRpc, "Sending gRPC request"); - - Map responseLogData = new HashMap<>(); - JsonObject requestHeaders = mapHeadersToJsonObject(headers); - requestLogData.put("request.headers", gson.toJson(requestHeaders)); + if (logger.isInfoEnabled()) { + String requestId = UUID.randomUUID().toString(); + String serviceName = method.getServiceName(); + String methodName = method.getFullMethodName(); + + serviceAndRpc.put("serviceName", serviceName); + serviceAndRpc.put("rpcName", methodName); + serviceAndRpc.put("requestId", requestId); + } + if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { + LoggingUtils.logWithMDC(logger, Level.INFO, serviceAndRpc, "Sending gRPC request"); + } + + if (logger.isDebugEnabled()) { + requestLogData.putAll(serviceAndRpc); + + JsonObject requestHeaders = mapHeadersToJsonObject(headers); + requestLogData.put("request.headers", gson.toJson(requestHeaders)); + } super.start( new ForwardingClientCallListener.SimpleForwardingClientCallListener( responseListener) { @Override public void onMessage(RespT message) { - - responseLogData.put("response.payload", gson.toJson(message)); + if (logger.isDebugEnabled()) { + // Add each message to the array + responsePayloads.add(gson.toJsonTree(message)); + } super.onMessage(message); } @Override public void onClose(Status status, Metadata trailers) { - serviceAndRpc.put("response.status", status.getCode().name()); - responseLogData.putAll(serviceAndRpc); - - LoggingUtils.logWithMDC(logger, Level.INFO, serviceAndRpc, "Received response."); - - // Access and add response headers - JsonObject responseHeaders = mapHeadersToJsonObject(trailers); - responseLogData.put("response.headers", gson.toJson(responseHeaders)); - - LoggingUtils.logWithMDC( - logger, Level.DEBUG, responseLogData, "Received response header and payload."); + if (logger.isInfoEnabled()) { + serviceAndRpc.put("response.status", status.getCode().name()); + responseLogData.putAll(serviceAndRpc); + } + if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { + LoggingUtils.logWithMDC(logger, Level.INFO, serviceAndRpc, "Received response."); + } + if (logger.isDebugEnabled()) { + // Access and add response headers + JsonObject responseHeaders = mapHeadersToJsonObject(trailers); + responseLogData.put("response.headers", gson.toJson(responseHeaders)); + // Add the array of payloads to the responseLogData + responseLogData.put("response.payload", gson.toJson(responsePayloads)); + + LoggingUtils.logWithMDC( + logger, Level.DEBUG, responseLogData, "Received response."); + } super.onClose(status, trailers); } @@ -75,10 +92,11 @@ public void onClose(Status status, Metadata trailers) { @Override public void sendMessage(ReqT message) { - requestLogData.put("request.payload", gson.toJson(message)); + if (logger.isDebugEnabled()) { + requestLogData.put("request.payload", gson.toJson(message)); + LoggingUtils.logWithMDC(logger, Level.DEBUG, requestLogData, "Sending gRPC request."); + } - LoggingUtils.logWithMDC( - logger, Level.DEBUG, requestLogData, "grpc request header and payload."); super.sendMessage(message); } }; diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java index 05df206970..10d1c1021c 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -3,6 +3,7 @@ import com.google.api.gax.httpjson.ForwardingHttpJsonClientCall.SimpleForwardingHttpJsonClientCall; import com.google.api.gax.logging.LoggingUtils; import com.google.gson.Gson; +import com.google.gson.JsonArray; import com.google.gson.JsonObject; import java.util.HashMap; import java.util.Map; @@ -14,72 +15,85 @@ public class HttpJsonLoggingInterceptor implements HttpJsonClientInterceptor { private static final Logger logger = LoggingUtils.getLogger(HttpJsonLoggingInterceptor.class); private static final Gson gson = new Gson(); - private Map serviceAndRpc = new HashMap<>(); - private Map requestLogData = new HashMap<>(); @Override public HttpJsonClientCall interceptCall( ApiMethodDescriptor method, HttpJsonCallOptions callOptions, HttpJsonChannel next) { + Map requestLogData = new HashMap<>(); + // Initialize a JsonArray to hold all responses + JsonArray responsePayloads = new JsonArray(); String endpoint = ((ManagedHttpJsonChannel) next).getEndpoint(); return new SimpleForwardingHttpJsonClientCall(next.newCall(method, callOptions)) { @Override public void start( HttpJsonClientCall.Listener responseListener, HttpJsonMetadata headers) { - String requestId = UUID.randomUUID().toString(); - // Capture request details - String methodName = method.getFullMethodName(); - String httpMethod = method.getHttpMethod(); - serviceAndRpc.put("rpcName", methodName); - serviceAndRpc.put("requestId", requestId); - requestLogData.putAll(serviceAndRpc); - requestLogData.put("request.url", endpoint); - requestLogData.put("request.method", httpMethod); - - // Capture and log headers - JsonObject jsonHeaders = new JsonObject(); - headers - .getHeaders() - .entrySet() - .forEach( - entry -> { - jsonHeaders.addProperty(entry.getKey(), entry.getValue().toString()); - }); - requestLogData.put("request.headers", gson.toJson(jsonHeaders)); - - LoggingUtils.logWithMDC(logger, Level.INFO, serviceAndRpc, "Sending HTTP request"); + Map serviceAndRpc = new HashMap<>(); + if (logger.isInfoEnabled()) { + String requestId = UUID.randomUUID().toString(); + // Capture request details + String methodName = method.getFullMethodName(); + String httpMethod = method.getHttpMethod(); + serviceAndRpc.put("rpcName", methodName); + serviceAndRpc.put("requestId", requestId); + requestLogData.putAll(serviceAndRpc); + requestLogData.put("request.url", endpoint); + requestLogData.put("request.method", httpMethod); + } + if (logger.isDebugEnabled()) { + // Capture and log headers + JsonObject jsonHeaders = new JsonObject(); + headers + .getHeaders() + .forEach((key, value) -> jsonHeaders.addProperty(key, value.toString())); + requestLogData.put("request.headers", gson.toJson(jsonHeaders)); + } + + if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { + LoggingUtils.logWithMDC(logger, Level.INFO, serviceAndRpc, "Sending HTTP request"); + } Map responseLogData = new HashMap<>(); super.start( new HttpJsonClientCall.Listener() { @Override public void onMessage(RespT message) { - responseLogData.put("response.payload", gson.toJson(message)); - + if (logger.isDebugEnabled()) { + // Add each message to the array + responsePayloads.add(gson.toJsonTree(message)); + } responseListener.onMessage(message); } @Override public void onClose(int status, HttpJsonMetadata trailers) { - serviceAndRpc.put("response.status", String.valueOf(status)); - responseLogData.putAll(serviceAndRpc); - LoggingUtils.logWithMDC( - logger, Level.INFO, serviceAndRpc, "HTTP request finished."); - - JsonObject jsonHeaders = new JsonObject(); - headers - .getHeaders() - .entrySet() - .forEach( - entry -> { - jsonHeaders.addProperty(entry.getKey(), entry.getValue().toString()); - }); - responseLogData.put("request.headers", gson.toJson(jsonHeaders)); - - LoggingUtils.logWithMDC( - logger, Level.DEBUG, responseLogData, "Received response header and payload."); + if (logger.isInfoEnabled()) { + + serviceAndRpc.put("response.status", String.valueOf(status)); + responseLogData.putAll(serviceAndRpc); + } + if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { + LoggingUtils.logWithMDC( + logger, Level.INFO, serviceAndRpc, "HTTP request finished."); + } + if (logger.isDebugEnabled()) { + + JsonObject jsonHeaders = new JsonObject(); + headers + .getHeaders() + .forEach((key, value) -> jsonHeaders.addProperty(key, value.toString())); + responseLogData.put("response.headers", gson.toJson(jsonHeaders)); + + // Add the array of payloads to the responseLogData + responseLogData.put("response.payload", gson.toJson(responsePayloads)); + LoggingUtils.logWithMDC( + logger, + Level.DEBUG, + responseLogData, + "Received response header and payload."); + } responseListener.onClose(status, trailers); } @@ -89,10 +103,11 @@ public void onClose(int status, HttpJsonMetadata trailers) { @Override public void sendMessage(ReqT message) { - requestLogData.put("request.payload", gson.toJson(message)); - - LoggingUtils.logWithMDC( - logger, Level.DEBUG, requestLogData, "HTTP request header and payload."); + if (logger.isDebugEnabled()) { + requestLogData.put("request.payload", gson.toJson(message)); + LoggingUtils.logWithMDC( + logger, Level.DEBUG, requestLogData, "HTTP request header and payload."); + } super.sendMessage(message); } From ebbae154a94e7875f688a8963c5d6bd90f1e48d8 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Wed, 27 Nov 2024 16:52:20 -0500 Subject: [PATCH 09/45] when logging disabled, assign NOPLogger directly. --- .../src/main/java/com/google/api/gax/logging/LoggingUtils.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java index 034358bdb5..436a9e35c8 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java @@ -42,6 +42,7 @@ import org.slf4j.Marker; import org.slf4j.helpers.FormattingTuple; import org.slf4j.helpers.MessageFormatter; +import org.slf4j.helpers.NOPLogger; @InternalApi public class LoggingUtils { @@ -54,7 +55,7 @@ private LoggingUtils() {} public static Logger getLogger(Class clazz) { if (!isLoggingEnabled()) { // use SLF4j's NOP logger regardless of bindings - return LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); + return LoggerFactory.getLogger(NOPLogger.class); } ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); From 543298d0c94eaf62b320fc1bf69dd0497a2dfcfc Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Mon, 2 Dec 2024 11:42:36 -0500 Subject: [PATCH 10/45] some temp settings for testing with showcase. --- .../com/google/api/gax/rpc/ClientContext.java | 5 -- showcase/gapic-showcase/pom.xml | 51 ++++++++++--------- .../showcase/v1beta1/it/ITUnaryCallable.java | 40 +++++---------- .../src/test/resources/logback-test.xml | 3 ++ .../src/test/resources/logging.properties | 3 +- 5 files changed, 45 insertions(+), 57 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java index aab38b8162..5bce1ac6bb 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java @@ -72,8 +72,6 @@ */ @AutoValue public abstract class ClientContext { - - // private static final Logger LOGGER = LoggingUtils.getLogger(ClientContext.class); private static final String QUOTA_PROJECT_ID_HEADER_KEY = "x-goog-user-project"; /** @@ -172,9 +170,6 @@ public static ClientContext create(ClientSettings settings) throws IOException { * settings. */ public static ClientContext create(StubSettings settings) throws IOException { - // if (LoggingUtils.isLoggingEnabled()) { - // LoggingUtils.log(LOGGER, Level.INFO, "a dummy message", Collections.emptyMap()); - // } ApiClock clock = settings.getClock(); ExecutorProvider backgroundExecutorProvider = settings.getBackgroundExecutorProvider(); diff --git a/showcase/gapic-showcase/pom.xml b/showcase/gapic-showcase/pom.xml index 8968145955..48d8212834 100644 --- a/showcase/gapic-showcase/pom.xml +++ b/showcase/gapic-showcase/pom.xml @@ -217,28 +217,33 @@ - - - - - - - - - - - - - - - - - - - - - - - + + org.slf4j + slf4j-api + 2.0.16 + + + ch.qos.logback + logback-classic + 1.5.11 + test + + + ch.qos.logback + logback-core + 1.5.11 + + + net.logstash.logback + logstash-logback-encoder + 7.2 + test + + + org.codehaus.janino + janino + 3.1.9 + test + diff --git a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java index c9ec84b798..edb98facdb 100644 --- a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java +++ b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java @@ -20,10 +20,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.api.gax.grpc.GrpcStatusCode; +import com.google.api.gax.logging.JsonContextMapHandler; import com.google.api.gax.rpc.CancelledException; import com.google.api.gax.rpc.StatusCode; -import com.google.gson.Gson; -import com.google.gson.JsonObject; import com.google.rpc.Status; import com.google.showcase.v1beta1.EchoClient; import com.google.showcase.v1beta1.EchoRequest; @@ -31,11 +30,9 @@ import com.google.showcase.v1beta1.it.util.TestClientInitializer; import java.util.concurrent.TimeUnit; import java.util.logging.ConsoleHandler; -import java.util.logging.Filter; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogManager; -import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; import org.junit.jupiter.api.AfterAll; @@ -56,28 +53,26 @@ static void createClients() throws Exception { // Create Http JSON Echo Client httpjsonClient = TestClientInitializer.createHttpJsonEchoClient(); + // Settings for JUL as fallback // Get the root logger - Logger rootLogger = - LogManager.getLogManager().getLogger(""); - // Set the root logger's level to ALL or FINEST to see DEBUG messages - // rootLogger.setLevel(Level.ALL); // or rootLogger.setLevel(Level.FINEST); - // Remove any existing handlers (if needed) + Logger rootLogger = LogManager.getLogManager().getLogger(""); + // Set the root logger's level to ALL to see DEBUG messages + // rootLogger.setLevel(Level.ALL); + // Remove any existing handlers for (Handler handler : rootLogger.getHandlers()) { rootLogger.removeHandler(handler); } + // Create and add your ContextMapHandler + JsonContextMapHandler contextHandler = new JsonContextMapHandler(); + contextHandler.setLevel(Level.ALL); // Set the desired level + // Add a formatter if needed (optional) + // contextHandler.setFormatter(...); + rootLogger.addHandler(contextHandler); // Create a ConsoleHandler ConsoleHandler consoleHandler = new ConsoleHandler(); consoleHandler.setLevel(Level.ALL); consoleHandler.setFormatter(new SimpleFormatter()); - // String targetClassName = "com.google.api.gax.logging.LoggingUtils$JulWrapperLogger"; - // Filter filter = new Filter() { - // @Override - // public boolean isLoggable(LogRecord record) { - // return record.getLoggerName().equals(targetClassName); - // } - // }; - // consoleHandler.setFilter(filter); // Add the ConsoleHandler to the root logger rootLogger.addHandler(consoleHandler); @@ -93,17 +88,6 @@ static void destroyClients() throws InterruptedException { httpjsonClient.awaitTermination( TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); } - @Test - void test() { - Gson gson = new Gson(); - JsonObject jsonObject1 = gson.fromJson("{\"name\":\"John\", \"age\":30}", JsonObject.class); - JsonObject jsonObject2 = gson.fromJson("{\"city\":\"New York\", \"country\":\"USA\"}", JsonObject.class); - - JsonObject mergedObject = jsonObject1.deepCopy(); - mergedObject.entrySet().forEach(entry -> jsonObject2.add(entry.getKey(), entry.getValue())); - - System.out.println(jsonObject2); // Output: {"city":"New York", "country":"USA", "name":"John", "age":30} - } @Test void testGrpc_receiveContent() { diff --git a/showcase/gapic-showcase/src/test/resources/logback-test.xml b/showcase/gapic-showcase/src/test/resources/logback-test.xml index df5a14382a..f216becaa9 100644 --- a/showcase/gapic-showcase/src/test/resources/logback-test.xml +++ b/showcase/gapic-showcase/src/test/resources/logback-test.xml @@ -2,6 +2,9 @@ + + severity + diff --git a/showcase/gapic-showcase/src/test/resources/logging.properties b/showcase/gapic-showcase/src/test/resources/logging.properties index ea11473c27..ce394f934c 100644 --- a/showcase/gapic-showcase/src/test/resources/logging.properties +++ b/showcase/gapic-showcase/src/test/resources/logging.properties @@ -1,4 +1,5 @@ -handlers=java.util.logging.ConsoleHandler +handlers=java.util.logging.ConsoleHandler, com.google.api.gax.logging.JsonContextMapHandler java.util.logging.ConsoleHandler.level=ALL java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +com.google.api.gax.logging.JsonContextMapHandler.level=ALL From aac66f2acde15e78cf0678db2c68ad31e41102e4 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Thu, 5 Dec 2024 16:25:22 -0500 Subject: [PATCH 11/45] remove JUL wrapper for now. refactor getLogger(). Add tests for LoggingUtils. --- gax-java/gax/pom.xml | 14 + .../google/api/gax/logging/LoggingUtils.java | 414 ++---------------- .../internal/SystemEnvironmentProvider.java | 50 +++ .../api/gax/logging/LoggingUtilsTest.java | 130 ++++++ 4 files changed, 224 insertions(+), 384 deletions(-) create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/rpc/internal/SystemEnvironmentProvider.java create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java diff --git a/gax-java/gax/pom.xml b/gax-java/gax/pom.xml index 5ada305a25..2f86586d82 100644 --- a/gax-java/gax/pom.xml +++ b/gax-java/gax/pom.xml @@ -75,6 +75,20 @@ slf4j-api 2.0.16 + + + ch.qos.logback + logback-classic + + 1.3.14 + test + + + ch.qos.logback + logback-core + 1.3.14 + test + diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java index 436a9e35c8..6eba4a3430 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java @@ -31,86 +31,47 @@ package com.google.api.gax.logging; import com.google.api.core.InternalApi; -import com.google.gson.JsonObject; +import com.google.api.gax.rpc.internal.EnvironmentProvider; +import com.google.api.gax.rpc.internal.SystemEnvironmentProvider; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.LogRecord; import org.slf4j.ILoggerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; -import org.slf4j.Marker; -import org.slf4j.helpers.FormattingTuple; -import org.slf4j.helpers.MessageFormatter; -import org.slf4j.helpers.NOPLogger; @InternalApi public class LoggingUtils { - private static final java.util.logging.Logger LOGGER = - java.util.logging.Logger.getLogger(LoggingUtils.class.getName()); + private static EnvironmentProvider environmentProvider = SystemEnvironmentProvider.getInstance(); + private static final Logger NO_OP_LOGGER = org.slf4j.helpers.NOPLogger.NOP_LOGGER; + private static boolean loggingEnabled = isLoggingEnabled(); + static final String GOOGLE_SDK_JAVA_LOGGING = "GOOGLE_SDK_JAVA_LOGGING"; + // expose this setter for testing purposes + static void setEnvironmentProvider(EnvironmentProvider provider) { + environmentProvider = provider; + // Recalculate LOGGING_ENABLED after setting the new provider + loggingEnabled = isLoggingEnabled(); + } private LoggingUtils() {} public static Logger getLogger(Class clazz) { - if (!isLoggingEnabled()) { - // use SLF4j's NOP logger regardless of bindings - return LoggerFactory.getLogger(NOPLogger.class); - } - - ILoggerFactory loggerFactory = LoggerFactory.getILoggerFactory(); - if (loggerFactory != null && !(loggerFactory instanceof org.slf4j.helpers.NOPLoggerFactory)) { - // Use SLF4j binding when present - return LoggerFactory.getLogger(clazz); - } - // No SLF4j binding found, use JUL as fallback - Logger logger = new JulWrapperLogger(clazz.getName()); - logger.info("No SLF4J providers were found, fall back to JUL."); - return logger; - } - - public static boolean isLoggingEnabled() { - String enableLogging = System.getenv("GOOGLE_SDK_JAVA_LOGGING"); - LOGGER.info("GOOGLE_SDK_JAVA_LOGGING=" + enableLogging); // log for debug now, remove it. - return "true".equalsIgnoreCase(enableLogging); - } - - public static JsonObject mergeJsonObject(JsonObject jsonObject1, JsonObject jsonObject2) { - JsonObject mergedObject = jsonObject1.deepCopy(); - jsonObject2.entrySet().forEach(entry -> mergedObject.add(entry.getKey(), entry.getValue())); - return mergedObject; + return getLogger(clazz, new DefaultLoggerFactoryProvider()); } - public static Level mapToJulLevel(org.slf4j.event.Level slf4jLevel) { - switch (slf4jLevel) { - case ERROR: - return Level.SEVERE; - case WARN: - return Level.WARNING; - case INFO: - return Level.INFO; - case DEBUG: - return Level.FINE; - case TRACE: - return Level.FINEST; - default: - return Level.INFO; + // constructor with LoggerFactoryProvider to make testing easier + static Logger getLogger(Class clazz, LoggerFactoryProvider factoryProvider) { + if (loggingEnabled) { + return factoryProvider.getLoggerFactory().getLogger(clazz.getName()); + } else { + // use SLF4j's NOP logger regardless of bindings + return NO_OP_LOGGER; } } public static void logWithMDC( Logger logger, org.slf4j.event.Level level, Map contextMap, String message) { - - if (logger instanceof JulWrapperLogger) { - // Simulate MDC behavior for JUL - LogRecord record = new LogRecord(mapToJulLevel(level), message); - // Add context map to the LogRecord - record.setParameters(new Object[] {contextMap}); - ((JulWrapperLogger) logger).getJulLogger().log(record); - return; - } contextMap.forEach(MDC::put); - switch (level) { case TRACE: logger.trace(message); @@ -131,337 +92,22 @@ public static void logWithMDC( logger.info(message); // Default to INFO level } - MDC.clear(); } - // JulWrapperLogger implementation - static class JulWrapperLogger implements Logger { - - private final java.util.logging.Logger julLogger; - - public JulWrapperLogger(String name) { - this.julLogger = java.util.logging.Logger.getLogger(name); - } - - public java.util.logging.Logger getJulLogger() { - return julLogger; - } - - @Override - public String getName() { - return julLogger.getName(); - } - - @Override - public boolean isTraceEnabled() { - return julLogger.isLoggable(java.util.logging.Level.FINEST); - } - - @Override - public void trace(String msg) { - julLogger.log(java.util.logging.Level.FINEST, msg); - } - - @Override - public void trace(String s, Object o) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void trace(String s, Object o, Object o1) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void trace(String s, Object... objects) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void trace(String s, Throwable throwable) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public boolean isTraceEnabled(Marker marker) { - return false; - } - - @Override - public void trace(Marker marker, String s) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void trace(Marker marker, String s, Object o) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void trace(Marker marker, String s, Object o, Object o1) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void trace(Marker marker, String s, Object... objects) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void trace(Marker marker, String s, Throwable throwable) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public boolean isDebugEnabled() { - return julLogger.isLoggable(Level.FINE); - } - - @Override - public void debug(String msg) { - - if (isDebugEnabled()) { - julLogger.log(java.util.logging.Level.FINE, msg); - } - } - - @Override - public void debug(String format, Object arg) { - if (isDebugEnabled()) { - FormattingTuple ft = MessageFormatter.format(format, arg); - julLogger.log(Level.FINE, ft.getMessage()); - } - } - - @Override - public void debug(String s, Object o, Object o1) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void debug(String s, Object... objects) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void debug(String s, Throwable throwable) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public boolean isDebugEnabled(Marker marker) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void debug(Marker marker, String s) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void debug(Marker marker, String s, Object o) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void debug(Marker marker, String s, Object o, Object o1) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void debug(Marker marker, String s, Object... objects) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void debug(Marker marker, String s, Throwable throwable) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public boolean isInfoEnabled() { - return julLogger.isLoggable(Level.INFO); - } - - @Override - public void info(String msg) { - if (isInfoEnabled()) { - julLogger.log(java.util.logging.Level.INFO, msg); - } - } - - @Override - public void info(String format, Object arg) { - if (isInfoEnabled()) { - FormattingTuple ft = MessageFormatter.format(format, arg); - julLogger.log(java.util.logging.Level.INFO, ft.getMessage()); - } - } - - @Override - public void info(String s, Object o, Object o1) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void info(String s, Object... objects) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void info(String s, Throwable throwable) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public boolean isInfoEnabled(Marker marker) { - return true; - } - - @Override - public void info(Marker marker, String s) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void info(Marker marker, String s, Object o) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void info(Marker marker, String s, Object o, Object o1) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void info(Marker marker, String s, Object... objects) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void info(Marker marker, String s, Throwable throwable) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public boolean isWarnEnabled() { - return true; - } - - @Override - public void warn(String msg) { - julLogger.log(Level.WARNING, msg); - } - - @Override - public void warn(String s, Object o) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void warn(String s, Object... objects) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void warn(String s, Object o, Object o1) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void warn(String s, Throwable throwable) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public boolean isWarnEnabled(Marker marker) { - return false; - } - - @Override - public void warn(Marker marker, String s) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void warn(Marker marker, String s, Object o) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void warn(Marker marker, String s, Object o, Object o1) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void warn(Marker marker, String s, Object... objects) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void warn(Marker marker, String s, Throwable throwable) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public boolean isErrorEnabled() { - return false; - } - - @Override - public void error(String s) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void error(String s, Object o) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void error(String s, Object o, Object o1) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void error(String s, Object... objects) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void error(String s, Throwable throwable) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public boolean isErrorEnabled(Marker marker) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void error(Marker marker, String s) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void error(Marker marker, String s, Object o) { - throw new UnsupportedOperationException("This method is not supported."); - } - - @Override - public void error(Marker marker, String s, Object o, Object o1) { - throw new UnsupportedOperationException("This method is not supported."); - } + static boolean isLoggingEnabled() { + String enableLogging = environmentProvider.getenv(GOOGLE_SDK_JAVA_LOGGING); + return "true".equalsIgnoreCase(enableLogging); + } - @Override - public void error(Marker marker, String s, Object... objects) { - throw new UnsupportedOperationException("This method is not supported."); - } + interface LoggerFactoryProvider { + ILoggerFactory getLoggerFactory(); + } + static class DefaultLoggerFactoryProvider implements LoggerFactoryProvider { @Override - public void error(Marker marker, String s, Throwable throwable) { - throw new UnsupportedOperationException("This method is not supported."); + public ILoggerFactory getLoggerFactory() { + return LoggerFactory.getILoggerFactory(); } } } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/rpc/internal/SystemEnvironmentProvider.java b/gax-java/gax/src/main/java/com/google/api/gax/rpc/internal/SystemEnvironmentProvider.java new file mode 100644 index 0000000000..29d45ba3c5 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/rpc/internal/SystemEnvironmentProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.rpc.internal; + +import com.google.api.core.InternalApi; + +/** Represents the default system environment provider. */ +@InternalApi +public class SystemEnvironmentProvider implements EnvironmentProvider { + static final SystemEnvironmentProvider INSTANCE = new SystemEnvironmentProvider(); + + private SystemEnvironmentProvider() {} + + @Override + public String getenv(String name) { + return System.getenv(name); + } + + public static SystemEnvironmentProvider getInstance() { + return INSTANCE; + } +} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java b/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java new file mode 100644 index 0000000000..d07a00439b --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java @@ -0,0 +1,130 @@ +package com.google.api.gax.logging; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import ch.qos.logback.classic.LoggerContext; +import ch.qos.logback.classic.encoder.PatternLayoutEncoder; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.ConsoleAppender; +import com.google.api.gax.logging.LoggingUtils.LoggerFactoryProvider; +import com.google.api.gax.rpc.internal.EnvironmentProvider; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.mockito.Mockito; +import org.slf4j.ILoggerFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.helpers.NOPLogger; + +public class LoggingUtilsTest { + + private EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class); + + @BeforeEach + public void setup() { + EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class); + + // need to setup a ConsoleAppender and attach to root logger because TestAppender + // does not correctly capture MDC info + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + + PatternLayoutEncoder patternLayoutEncoder = new PatternLayoutEncoder(); + patternLayoutEncoder.setPattern("%-4relative [%thread] %-5level %logger{35} - %msg%n"); + patternLayoutEncoder.setContext(loggerContext); + + patternLayoutEncoder.start(); + + ConsoleAppender consoleAppender = new ConsoleAppender<>(); + consoleAppender.setEncoder(patternLayoutEncoder); + + consoleAppender.setContext(loggerContext); + consoleAppender.setName("CONSOLE"); + + consoleAppender.start(); + + ch.qos.logback.classic.Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); + rootLogger.addAppender(consoleAppender); + } + + @AfterEach + public void tearDown() { + LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); + loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).detachAppender("CONSOLE"); + } + + @org.junit.Test + public void testGetLogger_loggingEnabled_slf4jBindingPresent() { + Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("true"); + LoggingUtils.setEnvironmentProvider(envProvider); + Logger logger = LoggingUtils.getLogger(LoggingUtilsTest.class); + assertTrue(logger instanceof org.slf4j.Logger); + assertNotEquals(logger.getClass(), NOPLogger.class); + } + + @org.junit.Test + public void testGetLogger_loggingDisabled() { + Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("false"); + LoggingUtils.setEnvironmentProvider(envProvider); + + Logger logger = LoggingUtils.getLogger(LoggingUtilsTest.class); + assertEquals(NOPLogger.class, logger.getClass()); + } + + @org.junit.Test + public void testGetLogger_loggingEnabled_noBinding() { + Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("true"); + LoggingUtils.setEnvironmentProvider(envProvider); + // Create a mock LoggerFactoryProvider + LoggerFactoryProvider mockLoggerFactoryProvider = mock(LoggerFactoryProvider.class); + ILoggerFactory mockLoggerFactory = mock(ILoggerFactory.class); + when(mockLoggerFactoryProvider.getLoggerFactory()).thenReturn(mockLoggerFactory); + when(mockLoggerFactory.getLogger(anyString())) + .thenReturn(org.slf4j.helpers.NOPLogger.NOP_LOGGER); + + // Use the mock LoggerFactoryProvider in getLogger() + Logger logger = LoggingUtils.getLogger(LoggingUtilsTest.class, mockLoggerFactoryProvider); + + // Assert that the returned logger is a NOPLogger + assertTrue(logger instanceof org.slf4j.helpers.NOPLogger); + } + + @org.junit.Test + public void testIsLoggingEnabled_true() { + Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("true"); + LoggingUtils.setEnvironmentProvider(envProvider); + assertTrue(LoggingUtils.isLoggingEnabled()); + Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("TRUE"); + LoggingUtils.setEnvironmentProvider(envProvider); + assertTrue(LoggingUtils.isLoggingEnabled()); + Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("True"); + LoggingUtils.setEnvironmentProvider(envProvider); + assertTrue(LoggingUtils.isLoggingEnabled()); + } + + @org.junit.Test + public void testIsLoggingEnabled_defaultToFalse() { + LoggingUtils.setEnvironmentProvider(envProvider); + assertFalse(LoggingUtils.isLoggingEnabled()); + } + + // @Test + // public void testLogWithMDC_slf4jLogger() { + // TestAppender.clearEvents(); + // Map contextMap = new HashMap<>(); + // contextMap.put("key", "value"); + // LoggingUtils.logWithMDC(LOGGER, org.slf4j.event.Level.DEBUG, contextMap, "test message"); + // + // assertEquals(1, TestAppender.events.size()); + // assertEquals("test message", TestAppender.events.get(0).getFormattedMessage()); + // + // // Verify MDC content + // ILoggingEvent event = TestAppender.events.get(0); + // assertEquals("value", event.getMDCPropertyMap().get("key")); + // } +} From 487614cd16c2c088b300906bafd70e060ec890f3 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Thu, 5 Dec 2024 16:31:01 -0500 Subject: [PATCH 12/45] minor clean up.: --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 10 ++- .../gax/logging/JsonContextMapHandler.java | 82 ------------------- 2 files changed, 9 insertions(+), 83 deletions(-) delete mode 100644 gax-java/gax/src/main/java/com/google/api/gax/logging/JsonContextMapHandler.java diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index e599c9eb68..3b3adbd968 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -4,7 +4,15 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import io.grpc.*; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.ForwardingClientCallListener; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Status; import java.util.HashMap; import java.util.Map; import java.util.UUID; diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/JsonContextMapHandler.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/JsonContextMapHandler.java deleted file mode 100644 index 870cb62ae5..0000000000 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/JsonContextMapHandler.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.google.api.gax.logging; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.ConsoleHandler; -import java.util.logging.Handler; -import java.util.logging.LogRecord; - -public class JsonContextMapHandler extends Handler { - - private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); - - @Override - public void publish(LogRecord record) { - Object[] params = record.getParameters(); - - ConsoleHandler consoleHandler = new ConsoleHandler(); - if (params != null && params.length > 0 && params[0] instanceof Map) { - @SuppressWarnings("unchecked") - Map contextMap = (Map) params[0]; - - // Create a map to hold all log data - Map logData = new HashMap<>(); - logData.put("message", record.getMessage()); - logData.put("severity", record.getLevel().getName()); - logData.put("timestamp", record.getMillis()); - logData.put("logger", record.getLoggerName()); - - // Add all context data to the top level - logData.putAll(contextMap); - - // Convert to JSON - String jsonLog = gson.toJson(logData); - - LogRecord jsonRecord = new LogRecord(record.getLevel(), jsonLog); - consoleHandler.publish(jsonRecord); - } else { - // Handle cases where the context map is not present - LogRecord messageRecord = - new LogRecord(record.getLevel(), "Log message without context: " + record.getMessage()); - consoleHandler.publish(messageRecord); - } - } - - @Override - public void flush() {} - - @Override - public void close() throws SecurityException {} -} From cc3465bd54128d32c9a1b3731ea66f74c06a7180 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Thu, 5 Dec 2024 17:06:29 -0500 Subject: [PATCH 13/45] fix test after adding interceptor for logging for http. --- ...nstantiatingHttpJsonChannelProviderTest.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java index 2e46157534..641469aa17 100644 --- a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java +++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java @@ -118,8 +118,11 @@ void managedChannelUsesDefaultChannelExecutor() throws IOException { // By default, the channel will be wrapped with ManagedHttpJsonInterceptorChannel ManagedHttpJsonInterceptorChannel interceptorChannel = (ManagedHttpJsonInterceptorChannel) httpJsonTransportChannel.getManagedChannel(); - ManagedHttpJsonChannel managedHttpJsonChannel = interceptorChannel.getChannel(); - assertThat(managedHttpJsonChannel.getExecutor()).isNotNull(); + // call getChannel() twice because interceptors are chained in layers by recursive construction + // inside com.google.api.gax.httpjson.InstantiatingHttpJsonChannelProvider.createChannel + ManagedHttpJsonInterceptorChannel managedHttpJsonChannel = (ManagedHttpJsonInterceptorChannel) interceptorChannel.getChannel(); + ManagedHttpJsonChannel channel = managedHttpJsonChannel.getChannel(); + assertThat(channel.getExecutor()).isNotNull(); // Clean up the resources (executor, deadlineScheduler, httpTransport) instantiatingHttpJsonChannelProvider.getTransportChannel().shutdownNow(); @@ -146,9 +149,13 @@ void managedChannelUsesCustomExecutor() throws IOException { // By default, the channel will be wrapped with ManagedHttpJsonInterceptorChannel ManagedHttpJsonInterceptorChannel interceptorChannel = (ManagedHttpJsonInterceptorChannel) httpJsonTransportChannel.getManagedChannel(); - ManagedHttpJsonChannel managedHttpJsonChannel = interceptorChannel.getChannel(); - assertThat(managedHttpJsonChannel.getExecutor()).isNotNull(); - assertThat(managedHttpJsonChannel.getExecutor()).isEqualTo(executor); + // call getChannel() twice because interceptors are chained in layers by recursive construction + // inside com.google.api.gax.httpjson.InstantiatingHttpJsonChannelProvider.createChannel + ManagedHttpJsonInterceptorChannel managedHttpJsonChannel = (ManagedHttpJsonInterceptorChannel) interceptorChannel.getChannel(); + ManagedHttpJsonChannel channel = managedHttpJsonChannel.getChannel(); + + assertThat(channel.getExecutor()).isNotNull(); + assertThat(channel.getExecutor()).isEqualTo(executor); // Clean up the resources (executor, deadlineScheduler, httpTransport) instantiatingHttpJsonChannelProvider.getTransportChannel().shutdownNow(); From 211a3d174c62d6f2116a7bbdcff12f8278498b56 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Thu, 5 Dec 2024 21:49:33 -0500 Subject: [PATCH 14/45] cleanup and fix response header. --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 26 +++++++---- .../httpjson/HttpJsonLoggingInterceptor.java | 44 ++++++++++++------- ...tantiatingHttpJsonChannelProviderTest.java | 6 ++- 3 files changed, 48 insertions(+), 28 deletions(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index 3b3adbd968..91b41c6af0 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -9,7 +9,7 @@ import io.grpc.ClientCall; import io.grpc.ClientInterceptor; import io.grpc.ForwardingClientCall; -import io.grpc.ForwardingClientCallListener; +import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener; import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.Status; @@ -59,9 +59,19 @@ public void start(Listener responseListener, Metadata headers) { requestLogData.put("request.headers", gson.toJson(requestHeaders)); } - super.start( - new ForwardingClientCallListener.SimpleForwardingClientCallListener( - responseListener) { + SimpleForwardingClientCallListener loggingListener = + new SimpleForwardingClientCallListener(responseListener) { + @Override + public void onHeaders(Metadata headers) { + + if (logger.isDebugEnabled()) { + // Access and add response headers + JsonObject responseHeaders = mapHeadersToJsonObject(headers); + responseLogData.put("response.headers", gson.toJson(responseHeaders)); + } + super.onHeaders(headers); + } + @Override public void onMessage(RespT message) { if (logger.isDebugEnabled()) { @@ -81,9 +91,6 @@ public void onClose(Status status, Metadata trailers) { LoggingUtils.logWithMDC(logger, Level.INFO, serviceAndRpc, "Received response."); } if (logger.isDebugEnabled()) { - // Access and add response headers - JsonObject responseHeaders = mapHeadersToJsonObject(trailers); - responseLogData.put("response.headers", gson.toJson(responseHeaders)); // Add the array of payloads to the responseLogData responseLogData.put("response.payload", gson.toJson(responsePayloads)); @@ -93,8 +100,9 @@ public void onClose(Status status, Metadata trailers) { super.onClose(status, trailers); } - }, - headers); + }; + + super.start(loggingListener, headers); } @Override diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java index 10d1c1021c..9c683cc2c5 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -1,11 +1,13 @@ package com.google.api.gax.httpjson; import com.google.api.gax.httpjson.ForwardingHttpJsonClientCall.SimpleForwardingHttpJsonClientCall; +import com.google.api.gax.httpjson.ForwardingHttpJsonClientCallListener.SimpleForwardingHttpJsonClientCallListener; import com.google.api.gax.logging.LoggingUtils; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; import org.slf4j.Logger; @@ -56,22 +58,38 @@ public void start( LoggingUtils.logWithMDC(logger, Level.INFO, serviceAndRpc, "Sending HTTP request"); } Map responseLogData = new HashMap<>(); - super.start( - new HttpJsonClientCall.Listener() { + Listener forwardingResponseListener = + new SimpleForwardingHttpJsonClientCallListener(responseListener) { + @Override + public void onHeaders(HttpJsonMetadata responseHeaders) { + + if (logger.isDebugEnabled()) { + + Map> map = new HashMap<>(); + responseHeaders + .getHeaders() + .forEach((key, value) -> map.put(key, (List) value)); + responseLogData.put("response.headers", gson.toJson(map)); + } + super.onHeaders(responseHeaders); + } + @Override public void onMessage(RespT message) { + if (logger.isDebugEnabled()) { // Add each message to the array responsePayloads.add(gson.toJsonTree(message)); } - responseListener.onMessage(message); + super.onMessage(message); } @Override - public void onClose(int status, HttpJsonMetadata trailers) { + public void onClose(int statusCode, HttpJsonMetadata trailers) { + if (logger.isInfoEnabled()) { - serviceAndRpc.put("response.status", String.valueOf(status)); + serviceAndRpc.put("response.status", String.valueOf(statusCode)); responseLogData.putAll(serviceAndRpc); } if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { @@ -79,13 +97,6 @@ public void onClose(int status, HttpJsonMetadata trailers) { logger, Level.INFO, serviceAndRpc, "HTTP request finished."); } if (logger.isDebugEnabled()) { - - JsonObject jsonHeaders = new JsonObject(); - headers - .getHeaders() - .forEach((key, value) -> jsonHeaders.addProperty(key, value.toString())); - responseLogData.put("response.headers", gson.toJson(jsonHeaders)); - // Add the array of payloads to the responseLogData responseLogData.put("response.payload", gson.toJson(responsePayloads)); LoggingUtils.logWithMDC( @@ -94,11 +105,11 @@ public void onClose(int status, HttpJsonMetadata trailers) { responseLogData, "Received response header and payload."); } - - responseListener.onClose(status, trailers); + super.onClose(statusCode, trailers); } - }, - headers); + }; + + super.start(forwardingResponseListener, headers); } @Override @@ -108,7 +119,6 @@ public void sendMessage(ReqT message) { LoggingUtils.logWithMDC( logger, Level.DEBUG, requestLogData, "HTTP request header and payload."); } - super.sendMessage(message); } }; diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java index 641469aa17..3e6b2d56d1 100644 --- a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java +++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProviderTest.java @@ -120,7 +120,8 @@ void managedChannelUsesDefaultChannelExecutor() throws IOException { (ManagedHttpJsonInterceptorChannel) httpJsonTransportChannel.getManagedChannel(); // call getChannel() twice because interceptors are chained in layers by recursive construction // inside com.google.api.gax.httpjson.InstantiatingHttpJsonChannelProvider.createChannel - ManagedHttpJsonInterceptorChannel managedHttpJsonChannel = (ManagedHttpJsonInterceptorChannel) interceptorChannel.getChannel(); + ManagedHttpJsonInterceptorChannel managedHttpJsonChannel = + (ManagedHttpJsonInterceptorChannel) interceptorChannel.getChannel(); ManagedHttpJsonChannel channel = managedHttpJsonChannel.getChannel(); assertThat(channel.getExecutor()).isNotNull(); @@ -151,7 +152,8 @@ void managedChannelUsesCustomExecutor() throws IOException { (ManagedHttpJsonInterceptorChannel) httpJsonTransportChannel.getManagedChannel(); // call getChannel() twice because interceptors are chained in layers by recursive construction // inside com.google.api.gax.httpjson.InstantiatingHttpJsonChannelProvider.createChannel - ManagedHttpJsonInterceptorChannel managedHttpJsonChannel = (ManagedHttpJsonInterceptorChannel) interceptorChannel.getChannel(); + ManagedHttpJsonInterceptorChannel managedHttpJsonChannel = + (ManagedHttpJsonInterceptorChannel) interceptorChannel.getChannel(); ManagedHttpJsonChannel channel = managedHttpJsonChannel.getChannel(); assertThat(channel.getExecutor()).isNotNull(); From 29702105c0eedbc3b0c8e7eff1c51f464e5bc4f1 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Thu, 5 Dec 2024 22:10:08 -0500 Subject: [PATCH 15/45] lint --- .../java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index 91b41c6af0..ee3e23df20 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -34,6 +34,7 @@ public ClientCall interceptCall( // Initialize a JsonArray to hold all responses JsonArray responsePayloads = new JsonArray(); + return new ForwardingClientCall.SimpleForwardingClientCall( next.newCall(method, callOptions)) { @@ -51,7 +52,6 @@ public void start(Listener responseListener, Metadata headers) { if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { LoggingUtils.logWithMDC(logger, Level.INFO, serviceAndRpc, "Sending gRPC request"); } - if (logger.isDebugEnabled()) { requestLogData.putAll(serviceAndRpc); From 32f8b7cd790aa4cf7413db9702cd853b5fa339ca Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Fri, 6 Dec 2024 09:34:45 -0500 Subject: [PATCH 16/45] lint: license. --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 30 +++++++++++++++++++ .../httpjson/HttpJsonLoggingInterceptor.java | 30 +++++++++++++++++++ .../api/gax/logging/LoggingUtilsTest.java | 30 +++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index ee3e23df20..bc22ef9a6d 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -1,3 +1,33 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + package com.google.api.gax.grpc; import com.google.api.gax.logging.LoggingUtils; diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java index 9c683cc2c5..1d9a1c621a 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -1,3 +1,33 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + package com.google.api.gax.httpjson; import com.google.api.gax.httpjson.ForwardingHttpJsonClientCall.SimpleForwardingHttpJsonClientCall; diff --git a/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java b/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java index d07a00439b..fddc565df9 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java @@ -1,3 +1,33 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + package com.google.api.gax.logging; import static org.junit.Assert.assertEquals; From 245c14d68e10a32877725c7035d55ad93272761e Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Fri, 6 Dec 2024 10:36:00 -0500 Subject: [PATCH 17/45] refactor GrpcLoggingInterceptor for readability, intro LogData. --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 144 ++++++++++-------- .../com/google/api/gax/logging/LogData.java | 139 +++++++++++++++++ 2 files changed, 223 insertions(+), 60 deletions(-) create mode 100644 gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index bc22ef9a6d..ca70ae2c7f 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -30,9 +30,9 @@ package com.google.api.gax.grpc; +import com.google.api.gax.logging.LogData; import com.google.api.gax.logging.LoggingUtils; import com.google.gson.Gson; -import com.google.gson.JsonArray; import com.google.gson.JsonObject; import io.grpc.CallOptions; import io.grpc.Channel; @@ -43,7 +43,6 @@ import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.Status; -import java.util.HashMap; import java.util.Map; import java.util.UUID; import org.slf4j.Logger; @@ -58,96 +57,121 @@ public class GrpcLoggingInterceptor implements ClientInterceptor { public ClientCall interceptCall( MethodDescriptor method, CallOptions callOptions, Channel next) { - Map serviceAndRpc = new HashMap<>(); - Map requestLogData = new HashMap<>(); - Map responseLogData = new HashMap<>(); - - // Initialize a JsonArray to hold all responses - JsonArray responsePayloads = new JsonArray(); + LogData.Builder logDataBuilder = LogData.builder(); return new ForwardingClientCall.SimpleForwardingClientCall( next.newCall(method, callOptions)) { @Override public void start(Listener responseListener, Metadata headers) { - if (logger.isInfoEnabled()) { - String requestId = UUID.randomUUID().toString(); - String serviceName = method.getServiceName(); - String methodName = method.getFullMethodName(); - - serviceAndRpc.put("serviceName", serviceName); - serviceAndRpc.put("rpcName", methodName); - serviceAndRpc.put("requestId", requestId); - } - if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { - LoggingUtils.logWithMDC(logger, Level.INFO, serviceAndRpc, "Sending gRPC request"); - } - if (logger.isDebugEnabled()) { - requestLogData.putAll(serviceAndRpc); - - JsonObject requestHeaders = mapHeadersToJsonObject(headers); - requestLogData.put("request.headers", gson.toJson(requestHeaders)); - } - - SimpleForwardingClientCallListener loggingListener = + logRequest(method, logDataBuilder, headers); + SimpleForwardingClientCallListener responseLoggingListener = new SimpleForwardingClientCallListener(responseListener) { @Override public void onHeaders(Metadata headers) { - - if (logger.isDebugEnabled()) { - // Access and add response headers - JsonObject responseHeaders = mapHeadersToJsonObject(headers); - responseLogData.put("response.headers", gson.toJson(responseHeaders)); - } + recordResponseHeaders(headers, logDataBuilder); super.onHeaders(headers); } @Override public void onMessage(RespT message) { - if (logger.isDebugEnabled()) { - // Add each message to the array - responsePayloads.add(gson.toJsonTree(message)); - } + recordResponsePayload(message, logDataBuilder); super.onMessage(message); } @Override public void onClose(Status status, Metadata trailers) { - if (logger.isInfoEnabled()) { - serviceAndRpc.put("response.status", status.getCode().name()); - responseLogData.putAll(serviceAndRpc); - } - if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { - LoggingUtils.logWithMDC(logger, Level.INFO, serviceAndRpc, "Received response."); - } - if (logger.isDebugEnabled()) { - // Add the array of payloads to the responseLogData - responseLogData.put("response.payload", gson.toJson(responsePayloads)); - - LoggingUtils.logWithMDC( - logger, Level.DEBUG, responseLogData, "Received response."); - } - + logResponse(status, logDataBuilder); super.onClose(status, trailers); } }; - super.start(loggingListener, headers); + super.start(responseLoggingListener, headers); } @Override public void sendMessage(ReqT message) { - - if (logger.isDebugEnabled()) { - requestLogData.put("request.payload", gson.toJson(message)); - LoggingUtils.logWithMDC(logger, Level.DEBUG, requestLogData, "Sending gRPC request."); - } - + logResponseDetails(message, logDataBuilder); super.sendMessage(message); } }; } + // --- Helper methods for logging --- + private void logRequest( + MethodDescriptor method, LogData.Builder logDataBuilder, Metadata headers) { + + if (logger.isInfoEnabled()) { + String requestId = UUID.randomUUID().toString(); + logDataBuilder + .serviceName(method.getServiceName()) + .rpcName(method.getFullMethodName()) + .requestId(requestId); + // serviceAndRpc.put("serviceName", method.getServiceName()); + // serviceAndRpc.put("rpcName", method.getFullMethodName()); + // serviceAndRpc.put("requestId", requestId); + + if (!logger.isDebugEnabled()) { + LoggingUtils.logWithMDC( + logger, + Level.INFO, + logDataBuilder.build().serviceAndRpcToMap(), + "Sending gRPC request"); + } + } + if (logger.isDebugEnabled()) { + // requestLogData.putAll(serviceAndRpc); + JsonObject requestHeaders = mapHeadersToJsonObject(headers); + // requestLogData.put("request.headers", gson.toJson(requestHeaders)); + logDataBuilder.requestHeaders(gson.toJson(requestHeaders)); + } + } + + private void recordResponseHeaders(Metadata headers, LogData.Builder logDataBuilder) { + if (logger.isDebugEnabled()) { + // Access and add response headers + JsonObject responseHeaders = mapHeadersToJsonObject(headers); + // responseLogData.put("response.headers", gson.toJson(responseHeaders)); + logDataBuilder.responseHeaders(gson.toJson(responseHeaders)); + } + } + + private void recordResponsePayload(RespT message, LogData.Builder logDataBuilder) { + if (logger.isDebugEnabled()) { + // Add each message to the array + // responsePayloads.add(gson.toJsonTree(message)); + logDataBuilder.responsePayload(gson.toJsonTree(message)); + } + } + + private void logResponse(Status status, LogData.Builder logDataBuilder) { + if (logger.isInfoEnabled()) { + // serviceAndRpc.put("response.status", status.getCode().name()); + // responseLogData.putAll(serviceAndRpc); + logDataBuilder.responseStatus(status.getCode().name()); + } + if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { + Map responseData = logDataBuilder.build().responseInfoToMap(); + LoggingUtils.logWithMDC(logger, Level.INFO, responseData, "Received response."); + } + if (logger.isDebugEnabled()) { + // Add the array of payloads to the responseLogData + // responseLogData.put("response.payload", gson.toJson(responsePayloads)); + // logDataBuilder.responsePayload(gson.toJson(responsePayloads)); + Map responsedDetailsMap = logDataBuilder.build().responseDetailsToMap(); + LoggingUtils.logWithMDC(logger, Level.DEBUG, responsedDetailsMap, "Received response."); + } + } + + private void logResponseDetails(RespT message, LogData.Builder logDataBuilder) { + if (logger.isDebugEnabled()) { + // requestLogData.put("request.payload", gson.toJson(message)); + logDataBuilder.requestPayload(gson.toJson(message)); + Map requestDetailsMap = logDataBuilder.build().requestDetailsToMap(); + LoggingUtils.logWithMDC(logger, Level.DEBUG, requestDetailsMap, "Sending gRPC request."); + } + } + private static JsonObject mapHeadersToJsonObject(Metadata headers) { JsonObject jsonHeaders = new JsonObject(); headers diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java new file mode 100644 index 0000000000..8008cab6d8 --- /dev/null +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java @@ -0,0 +1,139 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.logging; + +import com.google.api.core.InternalApi; +import com.google.auto.value.AutoValue; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; + +@InternalApi +@AutoValue +public abstract class LogData { + private static final Gson gson = new Gson(); + + public abstract String serviceName(); + + public abstract String rpcName(); + + @Nullable + public abstract String requestId(); + + @Nullable + public abstract String requestHeaders(); + + @Nullable + public abstract String requestPayload(); + + @Nullable + public abstract String responseStatus(); + + @Nullable + public abstract String responseHeaders(); + + @Nullable + public abstract JsonElement responsePayload(); + + public static Builder builder() { + return new AutoValue_LogData.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder serviceName(String serviceName); + + public abstract Builder rpcName(String rpcName); + + public abstract Builder requestId(String requestId); + + public abstract Builder requestHeaders(String requestHeaders); + + public abstract Builder requestPayload(String requestPayload); + + public abstract Builder responseStatus(String responseStatus); + + public abstract Builder responseHeaders(String responseHeaders); + + public abstract Builder responsePayload(JsonElement responsePayload); + + public abstract LogData build(); + } + + public Map serviceAndRpcToMap() { + Map map = new HashMap<>(); + map.put("serviceName", serviceName()); + map.put("rpcName", rpcName()); + return map; + } + + public Map requestDetailsToMap() { + Map map = new HashMap<>(); + map.put("serviceName", serviceName()); + map.put("rpcName", rpcName()); + map.put("request.headers", requestHeaders()); + map.put("request.payload", requestPayload()); + return map; + } + + public Map responseInfoToMap() { + Map map = new HashMap<>(); + map.put("serviceName", serviceName()); + map.put("rpcName", rpcName()); + map.put("response.status", responseStatus()); + return map; + } + + public Map responseDetailsToMap() { + Map map = new HashMap<>(); + map.put("serviceName", serviceName()); + map.put("rpcName", rpcName()); + map.put("response.status", responseStatus()); + map.put("response.payload", gson.toJson(responsePayload())); + map.put("response.headers", responseHeaders()); + return map; + } + + public Map toMap() { + Map map = new HashMap<>(); + map.put("serviceName", serviceName()); + map.put("rpcName", rpcName()); + map.put("requestId", requestId()); + map.put("request.headers", requestHeaders()); + map.put("request.payload", requestPayload()); + map.put("response.status", responseStatus()); + map.put("response.headers", responseHeaders()); + map.put("response.payload", responsePayload().toString()); + return map; + } +} From 8358497907d35db5d9b06a50313e4b6d35d9bd7c Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Fri, 6 Dec 2024 13:27:40 -0500 Subject: [PATCH 18/45] minor cleanups. --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 30 +++++-------------- .../com/google/api/gax/logging/LogData.java | 2 ++ .../showcase/v1beta1/it/ITUnaryCallable.java | 28 ----------------- .../src/test/resources/logback-test.xml | 11 +++++-- 4 files changed, 18 insertions(+), 53 deletions(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index ca70ae2c7f..931d45bce1 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -64,7 +64,8 @@ public ClientCall interceptCall( @Override public void start(Listener responseListener, Metadata headers) { - logRequest(method, logDataBuilder, headers); + logRequestInfo(method, logDataBuilder); + recordRequestHeaders(logDataBuilder, headers); SimpleForwardingClientCallListener responseLoggingListener = new SimpleForwardingClientCallListener(responseListener) { @Override @@ -85,31 +86,25 @@ public void onClose(Status status, Metadata trailers) { super.onClose(status, trailers); } }; - super.start(responseLoggingListener, headers); } - @Override public void sendMessage(ReqT message) { - logResponseDetails(message, logDataBuilder); + logRequestDetails(message, logDataBuilder); super.sendMessage(message); } }; } // --- Helper methods for logging --- - private void logRequest( - MethodDescriptor method, LogData.Builder logDataBuilder, Metadata headers) { - + private void logRequestInfo( + MethodDescriptor method, LogData.Builder logDataBuilder) { if (logger.isInfoEnabled()) { String requestId = UUID.randomUUID().toString(); logDataBuilder .serviceName(method.getServiceName()) .rpcName(method.getFullMethodName()) .requestId(requestId); - // serviceAndRpc.put("serviceName", method.getServiceName()); - // serviceAndRpc.put("rpcName", method.getFullMethodName()); - // serviceAndRpc.put("requestId", requestId); if (!logger.isDebugEnabled()) { LoggingUtils.logWithMDC( @@ -119,19 +114,17 @@ private void logRequest( "Sending gRPC request"); } } + } + private void recordRequestHeaders(LogData.Builder logDataBuilder, Metadata headers) { if (logger.isDebugEnabled()) { - // requestLogData.putAll(serviceAndRpc); JsonObject requestHeaders = mapHeadersToJsonObject(headers); - // requestLogData.put("request.headers", gson.toJson(requestHeaders)); logDataBuilder.requestHeaders(gson.toJson(requestHeaders)); } } - private void recordResponseHeaders(Metadata headers, LogData.Builder logDataBuilder) { if (logger.isDebugEnabled()) { // Access and add response headers JsonObject responseHeaders = mapHeadersToJsonObject(headers); - // responseLogData.put("response.headers", gson.toJson(responseHeaders)); logDataBuilder.responseHeaders(gson.toJson(responseHeaders)); } } @@ -139,15 +132,12 @@ private void recordResponseHeaders(Metadata headers, LogData.Builder logDataBuil private void recordResponsePayload(RespT message, LogData.Builder logDataBuilder) { if (logger.isDebugEnabled()) { // Add each message to the array - // responsePayloads.add(gson.toJsonTree(message)); logDataBuilder.responsePayload(gson.toJsonTree(message)); } } private void logResponse(Status status, LogData.Builder logDataBuilder) { if (logger.isInfoEnabled()) { - // serviceAndRpc.put("response.status", status.getCode().name()); - // responseLogData.putAll(serviceAndRpc); logDataBuilder.responseStatus(status.getCode().name()); } if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { @@ -155,17 +145,13 @@ private void logResponse(Status status, LogData.Builder logDataBuilder) { LoggingUtils.logWithMDC(logger, Level.INFO, responseData, "Received response."); } if (logger.isDebugEnabled()) { - // Add the array of payloads to the responseLogData - // responseLogData.put("response.payload", gson.toJson(responsePayloads)); - // logDataBuilder.responsePayload(gson.toJson(responsePayloads)); Map responsedDetailsMap = logDataBuilder.build().responseDetailsToMap(); LoggingUtils.logWithMDC(logger, Level.DEBUG, responsedDetailsMap, "Received response."); } } - private void logResponseDetails(RespT message, LogData.Builder logDataBuilder) { + private void logRequestDetails(RespT message, LogData.Builder logDataBuilder) { if (logger.isDebugEnabled()) { - // requestLogData.put("request.payload", gson.toJson(message)); logDataBuilder.requestPayload(gson.toJson(message)); Map requestDetailsMap = logDataBuilder.build().requestDetailsToMap(); LoggingUtils.logWithMDC(logger, Level.DEBUG, requestDetailsMap, "Sending gRPC request."); diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java index 8008cab6d8..b3be9f0222 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java @@ -90,6 +90,8 @@ public abstract static class Builder { public abstract LogData build(); } + // helper functions to convert to map for logging + // todo: error handling? public Map serviceAndRpcToMap() { Map map = new HashMap<>(); map.put("serviceName", serviceName()); diff --git a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java index edb98facdb..76a78e7ff9 100644 --- a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java +++ b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java @@ -20,7 +20,6 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.api.gax.grpc.GrpcStatusCode; -import com.google.api.gax.logging.JsonContextMapHandler; import com.google.api.gax.rpc.CancelledException; import com.google.api.gax.rpc.StatusCode; import com.google.rpc.Status; @@ -29,12 +28,8 @@ import com.google.showcase.v1beta1.EchoResponse; import com.google.showcase.v1beta1.it.util.TestClientInitializer; import java.util.concurrent.TimeUnit; -import java.util.logging.ConsoleHandler; -import java.util.logging.Handler; import java.util.logging.Level; -import java.util.logging.LogManager; import java.util.logging.Logger; -import java.util.logging.SimpleFormatter; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -53,29 +48,6 @@ static void createClients() throws Exception { // Create Http JSON Echo Client httpjsonClient = TestClientInitializer.createHttpJsonEchoClient(); - // Settings for JUL as fallback - // Get the root logger - Logger rootLogger = LogManager.getLogManager().getLogger(""); - // Set the root logger's level to ALL to see DEBUG messages - // rootLogger.setLevel(Level.ALL); - // Remove any existing handlers - for (Handler handler : rootLogger.getHandlers()) { - rootLogger.removeHandler(handler); - } - - // Create and add your ContextMapHandler - JsonContextMapHandler contextHandler = new JsonContextMapHandler(); - contextHandler.setLevel(Level.ALL); // Set the desired level - // Add a formatter if needed (optional) - // contextHandler.setFormatter(...); - rootLogger.addHandler(contextHandler); - // Create a ConsoleHandler - ConsoleHandler consoleHandler = new ConsoleHandler(); - consoleHandler.setLevel(Level.ALL); - consoleHandler.setFormatter(new SimpleFormatter()); - - // Add the ConsoleHandler to the root logger - rootLogger.addHandler(consoleHandler); LOGGER.log(Level.INFO, "This is log message directly from JUL. Clients created."); } diff --git a/showcase/gapic-showcase/src/test/resources/logback-test.xml b/showcase/gapic-showcase/src/test/resources/logback-test.xml index f216becaa9..2fabc5467a 100644 --- a/showcase/gapic-showcase/src/test/resources/logback-test.xml +++ b/showcase/gapic-showcase/src/test/resources/logback-test.xml @@ -8,10 +8,15 @@ - logger.equals("com.google.api.gax.grpc.GrpcLoggingInterceptor") || + + ((logger.equals("com.google.api.gax.grpc.GrpcLoggingInterceptor") || logger.equals("com.google.api.gax.httpjson.HttpJsonLoggingInterceptor") || - logger.equals("com.google.api.gax.tracing.MetricsTracer") - + logger.equals("com.google.api.gax.tracing.MetricsTracer")) + && (mdc.get("serviceName").equals("google.showcase.v1beta1.Echo"))) + + + + ACCEPT DENY From 97087b22582774397ed34612187b540b774c9d6d Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Fri, 6 Dec 2024 14:55:06 -0500 Subject: [PATCH 19/45] GrpcLoggingInterceptor fix for thread safe, separate out debug logging entries, add request id to track. --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 110 ++++++++++-------- .../com/google/api/gax/logging/LogData.java | 4 +- 2 files changed, 67 insertions(+), 47 deletions(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index 931d45bce1..82fd8bc9f3 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -56,18 +56,19 @@ public class GrpcLoggingInterceptor implements ClientInterceptor { @Override public ClientCall interceptCall( MethodDescriptor method, CallOptions callOptions, Channel next) { - - LogData.Builder logDataBuilder = LogData.builder(); - return new ForwardingClientCall.SimpleForwardingClientCall( next.newCall(method, callOptions)) { + // Generate request ID here + String requestId = UUID.randomUUID().toString(); + @Override public void start(Listener responseListener, Metadata headers) { - logRequestInfo(method, logDataBuilder); - recordRequestHeaders(logDataBuilder, headers); + logRequestInfoAndHeaders(method, headers, requestId); SimpleForwardingClientCallListener responseLoggingListener = new SimpleForwardingClientCallListener(responseListener) { + LogData.Builder logDataBuilder = LogData.builder(); + @Override public void onHeaders(Metadata headers) { recordResponseHeaders(headers, logDataBuilder); @@ -82,48 +83,56 @@ public void onMessage(RespT message) { @Override public void onClose(Status status, Metadata trailers) { - logResponse(status, logDataBuilder); + try { + logResponse(status, logDataBuilder, requestId); + } finally { + logDataBuilder = null; // release resource + } super.onClose(status, trailers); } }; super.start(responseLoggingListener, headers); } + @Override public void sendMessage(ReqT message) { - logRequestDetails(message, logDataBuilder); + logRequestDetails(message, requestId); super.sendMessage(message); } }; } // --- Helper methods for logging --- - private void logRequestInfo( - MethodDescriptor method, LogData.Builder logDataBuilder) { - if (logger.isInfoEnabled()) { - String requestId = UUID.randomUUID().toString(); - logDataBuilder - .serviceName(method.getServiceName()) - .rpcName(method.getFullMethodName()) - .requestId(requestId); - - if (!logger.isDebugEnabled()) { - LoggingUtils.logWithMDC( - logger, - Level.INFO, - logDataBuilder.build().serviceAndRpcToMap(), - "Sending gRPC request"); + private void logRequestInfoAndHeaders( + MethodDescriptor method, Metadata headers, String requestId) { + try { + if (logger.isInfoEnabled()) { + LogData.Builder logDataBuilder = LogData.builder(); + logDataBuilder + .serviceName(method.getServiceName()) + .rpcName(method.getFullMethodName()) + .requestId(requestId); + + if (logger.isDebugEnabled()) { + JsonObject requestHeaders = mapHeadersToJsonObject(headers); + logDataBuilder.requestHeaders(gson.toJson(requestHeaders)); + LoggingUtils.logWithMDC( + logger, Level.DEBUG, logDataBuilder.build().toMap(), "Sending gRPC request."); + } else { + LoggingUtils.logWithMDC( + logger, + Level.INFO, + logDataBuilder.build().serviceAndRpcToMap(), + "Sending gRPC request"); + } } + } catch (Exception e) { + logger.error("Error logging request info (and headers)", e); } } - private void recordRequestHeaders(LogData.Builder logDataBuilder, Metadata headers) { - if (logger.isDebugEnabled()) { - JsonObject requestHeaders = mapHeadersToJsonObject(headers); - logDataBuilder.requestHeaders(gson.toJson(requestHeaders)); - } - } + private void recordResponseHeaders(Metadata headers, LogData.Builder logDataBuilder) { if (logger.isDebugEnabled()) { - // Access and add response headers JsonObject responseHeaders = mapHeadersToJsonObject(headers); logDataBuilder.responseHeaders(gson.toJson(responseHeaders)); } @@ -131,30 +140,39 @@ private void recordResponseHeaders(Metadata headers, LogData.Builder logDataBuil private void recordResponsePayload(RespT message, LogData.Builder logDataBuilder) { if (logger.isDebugEnabled()) { - // Add each message to the array logDataBuilder.responsePayload(gson.toJsonTree(message)); } } - private void logResponse(Status status, LogData.Builder logDataBuilder) { - if (logger.isInfoEnabled()) { - logDataBuilder.responseStatus(status.getCode().name()); - } - if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { - Map responseData = logDataBuilder.build().responseInfoToMap(); - LoggingUtils.logWithMDC(logger, Level.INFO, responseData, "Received response."); - } - if (logger.isDebugEnabled()) { - Map responsedDetailsMap = logDataBuilder.build().responseDetailsToMap(); - LoggingUtils.logWithMDC(logger, Level.DEBUG, responsedDetailsMap, "Received response."); + private void logResponse(Status status, LogData.Builder logDataBuilder, String requestId) { + try { + if (logger.isInfoEnabled()) { + logDataBuilder.responseStatus(status.getCode().name()).requestId(requestId); + } + if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { + Map responseData = logDataBuilder.build().toMap(); + LoggingUtils.logWithMDC(logger, Level.INFO, responseData, "Received response."); + } + if (logger.isDebugEnabled()) { + Map responsedDetailsMap = logDataBuilder.build().toMap(); + LoggingUtils.logWithMDC(logger, Level.DEBUG, responsedDetailsMap, "Received response."); + } + } catch (Exception e) { + logger.error("Error logging request response", e); } } - private void logRequestDetails(RespT message, LogData.Builder logDataBuilder) { - if (logger.isDebugEnabled()) { - logDataBuilder.requestPayload(gson.toJson(message)); - Map requestDetailsMap = logDataBuilder.build().requestDetailsToMap(); - LoggingUtils.logWithMDC(logger, Level.DEBUG, requestDetailsMap, "Sending gRPC request."); + private void logRequestDetails(RespT message, String requestId) { + try { + if (logger.isDebugEnabled()) { + LogData.Builder logDataBuilder = LogData.builder(); + logDataBuilder.requestPayload(gson.toJson(message)).requestId(requestId); + Map requestDetailsMap = logDataBuilder.build().toMap(); + LoggingUtils.logWithMDC( + logger, Level.DEBUG, requestDetailsMap, "Sending gRPC request: request payload."); + } + } catch (Exception e) { + logger.error("Error logging request details", e); } } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java index b3be9f0222..ecb97414eb 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java @@ -43,8 +43,10 @@ public abstract class LogData { private static final Gson gson = new Gson(); + @Nullable public abstract String serviceName(); + @Nullable public abstract String rpcName(); @Nullable @@ -135,7 +137,7 @@ public Map toMap() { map.put("request.payload", requestPayload()); map.put("response.status", responseStatus()); map.put("response.headers", responseHeaders()); - map.put("response.payload", responsePayload().toString()); + map.put("response.payload", gson.toJson(responsePayload())); return map; } } From 727a3e90ff4cd6aeb1d33fa8ea3a7ddcdf226987 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Fri, 6 Dec 2024 16:36:07 -0500 Subject: [PATCH 20/45] minor changes + add showcase it test.(need to run with env var, setup not done yet.) --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 13 +- .../httpjson/HttpJsonLoggingInterceptor.java | 2 +- .../com/google/api/gax/logging/LogData.java | 66 +++----- showcase/gapic-showcase/pom.xml | 6 + .../google/showcase/v1beta1/it/ITLogging.java | 146 ++++++++++++++++++ .../v1beta1/it/util/TestAppender.java | 40 +++++ .../src/test/resources/logback-test.xml | 64 +++++--- 7 files changed, 261 insertions(+), 76 deletions(-) create mode 100644 showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITLogging.java create mode 100644 showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestAppender.java diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index 82fd8bc9f3..5fceaf81b2 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -117,13 +117,10 @@ private void logRequestInfoAndHeaders( JsonObject requestHeaders = mapHeadersToJsonObject(headers); logDataBuilder.requestHeaders(gson.toJson(requestHeaders)); LoggingUtils.logWithMDC( - logger, Level.DEBUG, logDataBuilder.build().toMap(), "Sending gRPC request."); + logger, Level.DEBUG, logDataBuilder.build().toMap(), "Sending gRPC request"); } else { LoggingUtils.logWithMDC( - logger, - Level.INFO, - logDataBuilder.build().serviceAndRpcToMap(), - "Sending gRPC request"); + logger, Level.INFO, logDataBuilder.build().toMap(), "Sending gRPC request"); } } } catch (Exception e) { @@ -151,11 +148,11 @@ private void logResponse(Status status, LogData.Builder logDataBuilder, String r } if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { Map responseData = logDataBuilder.build().toMap(); - LoggingUtils.logWithMDC(logger, Level.INFO, responseData, "Received response."); + LoggingUtils.logWithMDC(logger, Level.INFO, responseData, "Received Grpc response"); } if (logger.isDebugEnabled()) { Map responsedDetailsMap = logDataBuilder.build().toMap(); - LoggingUtils.logWithMDC(logger, Level.DEBUG, responsedDetailsMap, "Received response."); + LoggingUtils.logWithMDC(logger, Level.DEBUG, responsedDetailsMap, "Received Grpc response"); } } catch (Exception e) { logger.error("Error logging request response", e); @@ -169,7 +166,7 @@ private void logRequestDetails(RespT message, String requestId) { logDataBuilder.requestPayload(gson.toJson(message)).requestId(requestId); Map requestDetailsMap = logDataBuilder.build().toMap(); LoggingUtils.logWithMDC( - logger, Level.DEBUG, requestDetailsMap, "Sending gRPC request: request payload."); + logger, Level.DEBUG, requestDetailsMap, "Sending gRPC request: request payload"); } } catch (Exception e) { logger.error("Error logging request details", e); diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java index 1d9a1c621a..43df5775d0 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -124,7 +124,7 @@ public void onClose(int statusCode, HttpJsonMetadata trailers) { } if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { LoggingUtils.logWithMDC( - logger, Level.INFO, serviceAndRpc, "HTTP request finished."); + logger, Level.INFO, serviceAndRpc, "received HTTP response"); } if (logger.isDebugEnabled()) { // Add the array of payloads to the responseLogData diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java index ecb97414eb..67ceb8057e 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java @@ -94,50 +94,32 @@ public abstract static class Builder { // helper functions to convert to map for logging // todo: error handling? - public Map serviceAndRpcToMap() { - Map map = new HashMap<>(); - map.put("serviceName", serviceName()); - map.put("rpcName", rpcName()); - return map; - } - - public Map requestDetailsToMap() { - Map map = new HashMap<>(); - map.put("serviceName", serviceName()); - map.put("rpcName", rpcName()); - map.put("request.headers", requestHeaders()); - map.put("request.payload", requestPayload()); - return map; - } - - public Map responseInfoToMap() { - Map map = new HashMap<>(); - map.put("serviceName", serviceName()); - map.put("rpcName", rpcName()); - map.put("response.status", responseStatus()); - return map; - } - - public Map responseDetailsToMap() { - Map map = new HashMap<>(); - map.put("serviceName", serviceName()); - map.put("rpcName", rpcName()); - map.put("response.status", responseStatus()); - map.put("response.payload", gson.toJson(responsePayload())); - map.put("response.headers", responseHeaders()); - return map; - } - public Map toMap() { Map map = new HashMap<>(); - map.put("serviceName", serviceName()); - map.put("rpcName", rpcName()); - map.put("requestId", requestId()); - map.put("request.headers", requestHeaders()); - map.put("request.payload", requestPayload()); - map.put("response.status", responseStatus()); - map.put("response.headers", responseHeaders()); - map.put("response.payload", gson.toJson(responsePayload())); + if (serviceName() != null) { + map.put("serviceName", serviceName()); + } + if (rpcName() != null) { + map.put("rpcName", rpcName()); + } + if (requestId() != null) { + map.put("requestId", requestId()); + } + if (requestHeaders() != null) { + map.put("request.headers", requestHeaders()); + } + if (requestPayload() != null) { + map.put("request.payload", requestPayload()); + } + if (responseStatus() != null) { + map.put("response.status", responseStatus()); + } + if (responseHeaders() != null) { + map.put("response.headers", responseHeaders()); + } + if (responsePayload() != null) { + map.put("response.payload", gson.toJson(responsePayload())); + } return map; } } diff --git a/showcase/gapic-showcase/pom.xml b/showcase/gapic-showcase/pom.xml index 48d8212834..cbf15d5269 100644 --- a/showcase/gapic-showcase/pom.xml +++ b/showcase/gapic-showcase/pom.xml @@ -245,5 +245,11 @@ 3.1.9 test + + org.mockito + mockito-core + 4.11.0 + test + diff --git a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITLogging.java b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITLogging.java new file mode 100644 index 0000000000..41d4993dec --- /dev/null +++ b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITLogging.java @@ -0,0 +1,146 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.showcase.v1beta1.it; + +import static com.google.common.truth.Truth.assertThat; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import com.google.api.gax.grpc.GrpcLoggingInterceptor; +import com.google.api.gax.httpjson.HttpJsonLoggingInterceptor; +import com.google.api.gax.logging.LoggingUtils; +import com.google.showcase.v1beta1.EchoClient; +import com.google.showcase.v1beta1.EchoRequest; +import com.google.showcase.v1beta1.EchoResponse; +import com.google.showcase.v1beta1.it.util.TestAppender; +import com.google.showcase.v1beta1.it.util.TestClientInitializer; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +// This test needs to run with GOOGLE_SDK_JAVA_LOGGING=true +public class ITLogging { + private static EchoClient grpcClient; + + private static EchoClient httpjsonClient; + static final Logger LOGGER = Logger.getLogger(ITUnaryCallable.class.getName()); + + @BeforeAll + static void createClients() throws Exception { + // Create gRPC Echo Client + grpcClient = TestClientInitializer.createGrpcEchoClient(); + // Create Http JSON Echo Client + httpjsonClient = TestClientInitializer.createHttpJsonEchoClient(); + + // EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class); + // Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("true"); + // LoggingUtils.setEnvironmentProvider(envProvider); + LOGGER.log(Level.INFO, "This is log message directly from JUL. Clients created."); + } + + @AfterAll + static void destroyClients() throws InterruptedException { + grpcClient.close(); + httpjsonClient.close(); + + grpcClient.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); + httpjsonClient.awaitTermination( + TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); + } + + @Test + void testGrpc_receiveContent_logDebug() { + LOGGER.log( + Level.INFO, + "This is log message directly from JUL. Starting test: testGrpc_receiveContent."); + + TestAppender.clearEvents(); + assertThat(echoGrpc("grpc-echo?")).isEqualTo("grpc-echo?"); + assertThat(TestAppender.events.size()).isEqualTo(3); + assertThat(TestAppender.events.get(0).getMessage()).isEqualTo("Sending gRPC request"); + assertThat(TestAppender.events.get(0).getLevel()).isEqualTo(ch.qos.logback.classic.Level.DEBUG); + assertThat(TestAppender.events.get(1).getMessage()) + .isEqualTo("Sending gRPC request: request payload"); + assertThat(TestAppender.events.get(2).getMessage()).isEqualTo("Received Grpc response"); + } + + @Test + void testGrpc_receiveContent_logInfo() { + ch.qos.logback.classic.Logger logger = + (ch.qos.logback.classic.Logger) LoggingUtils.getLogger(GrpcLoggingInterceptor.class); + ch.qos.logback.classic.Level originalLevel = logger.getLevel(); + try { + logger.setLevel(ch.qos.logback.classic.Level.INFO); + assertThat(logger.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); + + TestAppender.clearEvents(); + assertThat(echoGrpc("grpc-echo?")).isEqualTo("grpc-echo?"); + assertThat(TestAppender.events.size()).isEqualTo(2); + ILoggingEvent loggingEvent1 = TestAppender.events.get(0); + assertThat(loggingEvent1.getMessage()).isEqualTo("Sending gRPC request"); + assertThat(loggingEvent1.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); + assertThat(loggingEvent1.getMDCPropertyMap()).hasSize(3); + assertThat(loggingEvent1.getMDCPropertyMap()) + .containsEntry("serviceName", "google.showcase.v1beta1.Echo"); + assertThat(loggingEvent1.getMDCPropertyMap()) + .containsEntry("rpcName", "google.showcase.v1beta1.Echo/Echo"); + assertThat(TestAppender.events.get(1).getMessage()).isEqualTo("Received Grpc response"); + assertThat(TestAppender.events.get(1).getLevel()) + .isEqualTo(ch.qos.logback.classic.Level.INFO); + } finally { + logger.setLevel(originalLevel); + } + } + + @Test + void testHttpJson_receiveContent() { + + ch.qos.logback.classic.Logger logger = + (ch.qos.logback.classic.Logger) LoggingUtils.getLogger(HttpJsonLoggingInterceptor.class); + ch.qos.logback.classic.Level originalLevel = logger.getLevel(); + try { + logger.setLevel(ch.qos.logback.classic.Level.INFO); + assertThat(logger.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); + + TestAppender.clearEvents(); + assertThat(echoHttpJson("http-echo?")).isEqualTo("http-echo?"); + assertThat(TestAppender.events.size()).isEqualTo(2); + ILoggingEvent loggingEvent1 = TestAppender.events.get(0); + assertThat(loggingEvent1.getMessage()).isEqualTo("Sending HTTP request"); + assertThat(loggingEvent1.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); + assertThat(loggingEvent1.getMDCPropertyMap()).hasSize(2); + assertThat(loggingEvent1.getMDCPropertyMap()) + .containsEntry("rpcName", "google.showcase.v1beta1.Echo/Echo"); + assertThat(TestAppender.events.get(1).getMessage()).isEqualTo("received HTTP response"); + // assertThat(TestAppender.events.get(1).getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); + } finally { + logger.setLevel(originalLevel); + } + } + + private String echoGrpc(String value) { + EchoResponse response = grpcClient.echo(EchoRequest.newBuilder().setContent(value).build()); + return response.getContent(); + } + + private String echoHttpJson(String value) { + EchoResponse response = httpjsonClient.echo(EchoRequest.newBuilder().setContent(value).build()); + return response.getContent(); + } +} diff --git a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestAppender.java b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestAppender.java new file mode 100644 index 0000000000..4ed5439fc7 --- /dev/null +++ b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestAppender.java @@ -0,0 +1,40 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.showcase.v1beta1.it.util; + + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import java.util.ArrayList; +import java.util.List; + +/** Logback appender used to set up tests. */ +public class TestAppender extends AppenderBase { + public static List events = new ArrayList<>(); + + @Override + protected void append(ILoggingEvent eventObject) { + // triggering Logback to capture the current MDC context and store it with the log event + eventObject.getMDCPropertyMap(); + + events.add(eventObject); + } + public static void clearEvents() { + events.clear(); + } +} + diff --git a/showcase/gapic-showcase/src/test/resources/logback-test.xml b/showcase/gapic-showcase/src/test/resources/logback-test.xml index 2fabc5467a..c38d09e794 100644 --- a/showcase/gapic-showcase/src/test/resources/logback-test.xml +++ b/showcase/gapic-showcase/src/test/resources/logback-test.xml @@ -1,29 +1,43 @@ - - - - - severity - - - - - - ((logger.equals("com.google.api.gax.grpc.GrpcLoggingInterceptor") || - logger.equals("com.google.api.gax.httpjson.HttpJsonLoggingInterceptor") || - logger.equals("com.google.api.gax.tracing.MetricsTracer")) - && (mdc.get("serviceName").equals("google.showcase.v1beta1.Echo"))) - + + + + + + + + + + + + + + + + + + + + + + + - + - - ACCEPT - DENY - - + + + + + - - - - \ No newline at end of file + + + + + + + + + + \ No newline at end of file From 23bc1110864fa61fc83f0a0b5bb068879a4ab075 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Fri, 6 Dec 2024 17:54:24 -0500 Subject: [PATCH 21/45] refactor HttpJsonLoggingInterceptor. remove public modifier from logging interceptors. --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 13 +- .../httpjson/HttpJsonLoggingInterceptor.java | 163 +++++++++++------- .../com/google/api/gax/logging/LogData.java | 16 ++ .../google/showcase/v1beta1/it/ITLogging.java | 127 ++++++++------ 4 files changed, 191 insertions(+), 128 deletions(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index 5fceaf81b2..ccced9a7d6 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -48,7 +48,7 @@ import org.slf4j.Logger; import org.slf4j.event.Level; -public class GrpcLoggingInterceptor implements ClientInterceptor { +class GrpcLoggingInterceptor implements ClientInterceptor { private static final Logger logger = LoggingUtils.getLogger(GrpcLoggingInterceptor.class); private static final Gson gson = new Gson(); @@ -59,7 +59,6 @@ public ClientCall interceptCall( return new ForwardingClientCall.SimpleForwardingClientCall( next.newCall(method, callOptions)) { - // Generate request ID here String requestId = UUID.randomUUID().toString(); @Override @@ -84,7 +83,7 @@ public void onMessage(RespT message) { @Override public void onClose(Status status, Metadata trailers) { try { - logResponse(status, logDataBuilder, requestId); + logResponse(status.getCode().value(), logDataBuilder, requestId); } finally { logDataBuilder = null; // release resource } @@ -102,7 +101,8 @@ public void sendMessage(ReqT message) { }; } - // --- Helper methods for logging --- + // Helper methods for logging + // some duplications with http equivalent to avoid exposing as public method private void logRequestInfoAndHeaders( MethodDescriptor method, Metadata headers, String requestId) { try { @@ -141,10 +141,11 @@ private void recordResponsePayload(RespT message, LogData.Builder logDat } } - private void logResponse(Status status, LogData.Builder logDataBuilder, String requestId) { + private void logResponse(int statusCode, LogData.Builder logDataBuilder, String requestId) { try { + if (logger.isInfoEnabled()) { - logDataBuilder.responseStatus(status.getCode().name()).requestId(requestId); + logDataBuilder.responseStatus(String.valueOf(statusCode)).requestId(requestId); } if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { Map responseData = logDataBuilder.build().toMap(); diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java index 43df5775d0..21481ff7b9 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -32,9 +32,9 @@ import com.google.api.gax.httpjson.ForwardingHttpJsonClientCall.SimpleForwardingHttpJsonClientCall; import com.google.api.gax.httpjson.ForwardingHttpJsonClientCallListener.SimpleForwardingHttpJsonClientCallListener; +import com.google.api.gax.logging.LogData; import com.google.api.gax.logging.LoggingUtils; import com.google.gson.Gson; -import com.google.gson.JsonArray; import com.google.gson.JsonObject; import java.util.HashMap; import java.util.List; @@ -43,7 +43,7 @@ import org.slf4j.Logger; import org.slf4j.event.Level; -public class HttpJsonLoggingInterceptor implements HttpJsonClientInterceptor { +class HttpJsonLoggingInterceptor implements HttpJsonClientInterceptor { private static final Logger logger = LoggingUtils.getLogger(HttpJsonLoggingInterceptor.class); private static final Gson gson = new Gson(); @@ -53,104 +53,135 @@ public HttpJsonClientCall interceptCall( ApiMethodDescriptor method, HttpJsonCallOptions callOptions, HttpJsonChannel next) { - Map requestLogData = new HashMap<>(); - // Initialize a JsonArray to hold all responses - JsonArray responsePayloads = new JsonArray(); + String requestId = UUID.randomUUID().toString(); String endpoint = ((ManagedHttpJsonChannel) next).getEndpoint(); + return new SimpleForwardingHttpJsonClientCall(next.newCall(method, callOptions)) { @Override public void start( HttpJsonClientCall.Listener responseListener, HttpJsonMetadata headers) { - Map serviceAndRpc = new HashMap<>(); - if (logger.isInfoEnabled()) { - String requestId = UUID.randomUUID().toString(); - // Capture request details - String methodName = method.getFullMethodName(); - String httpMethod = method.getHttpMethod(); - serviceAndRpc.put("rpcName", methodName); - serviceAndRpc.put("requestId", requestId); - requestLogData.putAll(serviceAndRpc); - requestLogData.put("request.url", endpoint); - requestLogData.put("request.method", httpMethod); - } - if (logger.isDebugEnabled()) { - // Capture and log headers - JsonObject jsonHeaders = new JsonObject(); - headers - .getHeaders() - .forEach((key, value) -> jsonHeaders.addProperty(key, value.toString())); - requestLogData.put("request.headers", gson.toJson(jsonHeaders)); - } + logRequestInfoAndHeaders(method, headers, endpoint, requestId); - if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { - LoggingUtils.logWithMDC(logger, Level.INFO, serviceAndRpc, "Sending HTTP request"); - } - Map responseLogData = new HashMap<>(); Listener forwardingResponseListener = new SimpleForwardingHttpJsonClientCallListener(responseListener) { + LogData.Builder logDataBuilder = LogData.builder(); + @Override public void onHeaders(HttpJsonMetadata responseHeaders) { - - if (logger.isDebugEnabled()) { - - Map> map = new HashMap<>(); - responseHeaders - .getHeaders() - .forEach((key, value) -> map.put(key, (List) value)); - responseLogData.put("response.headers", gson.toJson(map)); - } + recordResponseHeaders(responseHeaders, logDataBuilder); super.onHeaders(responseHeaders); } @Override public void onMessage(RespT message) { - - if (logger.isDebugEnabled()) { - // Add each message to the array - responsePayloads.add(gson.toJsonTree(message)); - } + recordResponsePayload(message, logDataBuilder); super.onMessage(message); } @Override public void onClose(int statusCode, HttpJsonMetadata trailers) { - - if (logger.isInfoEnabled()) { - - serviceAndRpc.put("response.status", String.valueOf(statusCode)); - responseLogData.putAll(serviceAndRpc); - } - if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { - LoggingUtils.logWithMDC( - logger, Level.INFO, serviceAndRpc, "received HTTP response"); - } - if (logger.isDebugEnabled()) { - // Add the array of payloads to the responseLogData - responseLogData.put("response.payload", gson.toJson(responsePayloads)); - LoggingUtils.logWithMDC( - logger, - Level.DEBUG, - responseLogData, - "Received response header and payload."); + try { + logResponse(statusCode, logDataBuilder, requestId); + } finally { + logDataBuilder = null; // release resource } super.onClose(statusCode, trailers); } }; - super.start(forwardingResponseListener, headers); } @Override public void sendMessage(ReqT message) { + logRequestDetails(message, requestId); + super.sendMessage(message); + } + }; + } + + // Helper methods for logging, + // some duplications with grpc equivalent to avoid exposing as public method + private void logRequestInfoAndHeaders( + ApiMethodDescriptor method, + HttpJsonMetadata headers, + String endpoint, + String requestId) { + try { + if (logger.isInfoEnabled()) { + LogData.Builder logDataBuilder = LogData.builder(); + logDataBuilder + .rpcName(method.getFullMethodName()) + .httpMethod(method.getHttpMethod()) + .httpUrl(endpoint) + .requestId(requestId); + if (logger.isDebugEnabled()) { - requestLogData.put("request.payload", gson.toJson(message)); + JsonObject requestHeaders = new JsonObject(); + headers + .getHeaders() + .forEach((key, value) -> requestHeaders.addProperty(key, value.toString())); + logDataBuilder.requestHeaders(gson.toJson(requestHeaders)); + LoggingUtils.logWithMDC( + logger, Level.DEBUG, logDataBuilder.build().toMap(), "Sending HTTP request"); + } else { LoggingUtils.logWithMDC( - logger, Level.DEBUG, requestLogData, "HTTP request header and payload."); + logger, Level.INFO, logDataBuilder.build().toMap(), "Sending HTTP request"); } - super.sendMessage(message); } - }; + } catch (Exception e) { + logger.error("Error logging request info (and headers)", e); + } + } + + private void recordResponseHeaders( + HttpJsonMetadata responseHeaders, LogData.Builder logDataBuilder) { + + if (logger.isDebugEnabled()) { + + Map> map = new HashMap<>(); + responseHeaders.getHeaders().forEach((key, value) -> map.put(key, (List) value)); + logDataBuilder.responseHeaders(gson.toJson(map)); + } + } + + private void recordResponsePayload(RespT message, LogData.Builder logDataBuilder) { + if (logger.isDebugEnabled()) { + logDataBuilder.responsePayload(gson.toJsonTree(message)); + } + } + + private void logResponse(int statusCode, LogData.Builder logDataBuilder, String requestId) { + try { + + if (logger.isInfoEnabled()) { + logDataBuilder.responseStatus(String.valueOf(statusCode)).requestId(requestId); + } + if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { + Map responseData = logDataBuilder.build().toMap(); + LoggingUtils.logWithMDC(logger, Level.INFO, responseData, "Received HTTP response"); + } + if (logger.isDebugEnabled()) { + Map responsedDetailsMap = logDataBuilder.build().toMap(); + LoggingUtils.logWithMDC(logger, Level.DEBUG, responsedDetailsMap, "Received HTTP response"); + } + } catch (Exception e) { + logger.error("Error logging request response", e); + } + } + + private void logRequestDetails(RespT message, String requestId) { + try { + if (logger.isDebugEnabled()) { + LogData.Builder logDataBuilder = LogData.builder(); + logDataBuilder.requestPayload(gson.toJson(message)).requestId(requestId); + Map requestDetailsMap = logDataBuilder.build().toMap(); + LoggingUtils.logWithMDC( + logger, Level.DEBUG, requestDetailsMap, "Sending HTTP request: request payload"); + } + } catch (Exception e) { + logger.error("Error logging request details", e); + } } } diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java index 67ceb8057e..87768c0ccf 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java @@ -67,6 +67,12 @@ public abstract class LogData { @Nullable public abstract JsonElement responsePayload(); + @Nullable + public abstract String httpMethod(); + + @Nullable + public abstract String httpUrl(); + public static Builder builder() { return new AutoValue_LogData.Builder(); } @@ -89,6 +95,10 @@ public abstract static class Builder { public abstract Builder responsePayload(JsonElement responsePayload); + public abstract Builder httpMethod(String httpMethod); + + public abstract Builder httpUrl(String httpUrl); + public abstract LogData build(); } @@ -120,6 +130,12 @@ public Map toMap() { if (responsePayload() != null) { map.put("response.payload", gson.toJson(responsePayload())); } + if (httpMethod() != null) { + map.put("request.method", httpMethod()); + } + if (httpUrl() != null) { + map.put("request.url", httpUrl()); + } return map; } } diff --git a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITLogging.java b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITLogging.java index 41d4993dec..a0fb67e2fe 100644 --- a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITLogging.java +++ b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITLogging.java @@ -19,9 +19,6 @@ import static com.google.common.truth.Truth.assertThat; import ch.qos.logback.classic.spi.ILoggingEvent; -import com.google.api.gax.grpc.GrpcLoggingInterceptor; -import com.google.api.gax.httpjson.HttpJsonLoggingInterceptor; -import com.google.api.gax.logging.LoggingUtils; import com.google.showcase.v1beta1.EchoClient; import com.google.showcase.v1beta1.EchoRequest; import com.google.showcase.v1beta1.EchoResponse; @@ -48,9 +45,6 @@ static void createClients() throws Exception { // Create Http JSON Echo Client httpjsonClient = TestClientInitializer.createHttpJsonEchoClient(); - // EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class); - // Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("true"); - // LoggingUtils.setEnvironmentProvider(envProvider); LOGGER.log(Level.INFO, "This is log message directly from JUL. Clients created."); } @@ -80,60 +74,81 @@ void testGrpc_receiveContent_logDebug() { assertThat(TestAppender.events.get(2).getMessage()).isEqualTo("Received Grpc response"); } - @Test - void testGrpc_receiveContent_logInfo() { - ch.qos.logback.classic.Logger logger = - (ch.qos.logback.classic.Logger) LoggingUtils.getLogger(GrpcLoggingInterceptor.class); - ch.qos.logback.classic.Level originalLevel = logger.getLevel(); - try { - logger.setLevel(ch.qos.logback.classic.Level.INFO); - assertThat(logger.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); - - TestAppender.clearEvents(); - assertThat(echoGrpc("grpc-echo?")).isEqualTo("grpc-echo?"); - assertThat(TestAppender.events.size()).isEqualTo(2); - ILoggingEvent loggingEvent1 = TestAppender.events.get(0); - assertThat(loggingEvent1.getMessage()).isEqualTo("Sending gRPC request"); - assertThat(loggingEvent1.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); - assertThat(loggingEvent1.getMDCPropertyMap()).hasSize(3); - assertThat(loggingEvent1.getMDCPropertyMap()) - .containsEntry("serviceName", "google.showcase.v1beta1.Echo"); - assertThat(loggingEvent1.getMDCPropertyMap()) - .containsEntry("rpcName", "google.showcase.v1beta1.Echo/Echo"); - assertThat(TestAppender.events.get(1).getMessage()).isEqualTo("Received Grpc response"); - assertThat(TestAppender.events.get(1).getLevel()) - .isEqualTo(ch.qos.logback.classic.Level.INFO); - } finally { - logger.setLevel(originalLevel); - } - } + // @Test + // void testGrpc_receiveContent_logInfo() { + // ch.qos.logback.classic.Logger logger = + // (ch.qos.logback.classic.Logger) LoggingUtils.getLogger(GrpcLoggingInterceptor.class); + // ch.qos.logback.classic.Level originalLevel = logger.getLevel(); + // try { + // logger.setLevel(ch.qos.logback.classic.Level.INFO); + // assertThat(logger.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); + // + // TestAppender.clearEvents(); + // assertThat(echoGrpc("grpc-echo?")).isEqualTo("grpc-echo?"); + // assertThat(TestAppender.events.size()).isEqualTo(2); + // ILoggingEvent loggingEvent1 = TestAppender.events.get(0); + // assertThat(loggingEvent1.getMessage()).isEqualTo("Sending gRPC request"); + // assertThat(loggingEvent1.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); + // assertThat(loggingEvent1.getMDCPropertyMap()).hasSize(3); + // assertThat(loggingEvent1.getMDCPropertyMap()) + // .containsEntry("serviceName", "google.showcase.v1beta1.Echo"); + // assertThat(loggingEvent1.getMDCPropertyMap()) + // .containsEntry("rpcName", "google.showcase.v1beta1.Echo/Echo"); + // assertThat(TestAppender.events.get(1).getMessage()).isEqualTo("Received Grpc response"); + // assertThat(TestAppender.events.get(1).getLevel()) + // .isEqualTo(ch.qos.logback.classic.Level.INFO); + // } finally { + // logger.setLevel(originalLevel); + // } + // } @Test - void testHttpJson_receiveContent() { - - ch.qos.logback.classic.Logger logger = - (ch.qos.logback.classic.Logger) LoggingUtils.getLogger(HttpJsonLoggingInterceptor.class); - ch.qos.logback.classic.Level originalLevel = logger.getLevel(); - try { - logger.setLevel(ch.qos.logback.classic.Level.INFO); - assertThat(logger.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); - - TestAppender.clearEvents(); - assertThat(echoHttpJson("http-echo?")).isEqualTo("http-echo?"); - assertThat(TestAppender.events.size()).isEqualTo(2); - ILoggingEvent loggingEvent1 = TestAppender.events.get(0); - assertThat(loggingEvent1.getMessage()).isEqualTo("Sending HTTP request"); - assertThat(loggingEvent1.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); - assertThat(loggingEvent1.getMDCPropertyMap()).hasSize(2); - assertThat(loggingEvent1.getMDCPropertyMap()) - .containsEntry("rpcName", "google.showcase.v1beta1.Echo/Echo"); - assertThat(TestAppender.events.get(1).getMessage()).isEqualTo("received HTTP response"); - // assertThat(TestAppender.events.get(1).getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); - } finally { - logger.setLevel(originalLevel); - } + void testHttpJson_receiveContent_logDebug() { + TestAppender.clearEvents(); + assertThat(echoHttpJson("http-echo?")).isEqualTo("http-echo?"); + assertThat(TestAppender.events.size()).isEqualTo(3); + ILoggingEvent loggingEvent1 = TestAppender.events.get(0); + assertThat(loggingEvent1.getMessage()).isEqualTo("Sending HTTP request"); + assertThat(loggingEvent1.getLevel()).isEqualTo(ch.qos.logback.classic.Level.DEBUG); + assertThat(loggingEvent1.getMDCPropertyMap()).hasSize(5); + assertThat(loggingEvent1.getMDCPropertyMap()).containsKey("request.headers"); + assertThat(TestAppender.events.get(1).getMessage()) + .isEqualTo("Sending HTTP request: request payload"); + assertThat(TestAppender.events.get(2).getMessage()).isEqualTo("Received HTTP response"); } + // @Test + // void testHttpJson_receiveContent_logInfo() { + // + // ch.qos.logback.classic.Logger logger = + // (ch.qos.logback.classic.Logger) LoggingUtils.getLogger(HttpJsonLoggingInterceptor.class); + // ch.qos.logback.classic.Level originalLevel = logger.getLevel(); + // try { + // logger.setLevel(ch.qos.logback.classic.Level.INFO); + // assertThat(logger.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); + // + // TestAppender.clearEvents(); + // assertThat(echoHttpJson("http-echo?")).isEqualTo("http-echo?"); + // assertThat(TestAppender.events.size()).isEqualTo(2); + // ILoggingEvent loggingEvent1 = TestAppender.events.get(0); + // assertThat(loggingEvent1.getMessage()).isEqualTo("Sending HTTP request"); + // assertThat(loggingEvent1.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); + // assertThat(loggingEvent1.getMDCPropertyMap()).hasSize(4); + // assertThat(loggingEvent1.getMDCPropertyMap()) + // .containsEntry("rpcName", "google.showcase.v1beta1.Echo/Echo"); + // assertThat(loggingEvent1.getMDCPropertyMap()).containsEntry("request.method", "POST"); + // assertThat(loggingEvent1.getMDCPropertyMap()) + // .containsEntry("request.url", "http://localhost:7469"); + // assertThat(TestAppender.events.get(1).getMessage()).isEqualTo("Received HTTP response"); + // assertThat(TestAppender.events.get(1).getLevel()) + // .isEqualTo(ch.qos.logback.classic.Level.INFO); + // assertThat(TestAppender.events.get(1).getMDCPropertyMap()) + // .containsEntry("response.status", "200"); + // } finally { + // logger.setLevel(originalLevel); + // } + // } + private String echoGrpc(String value) { EchoResponse response = grpcClient.echo(EchoRequest.newBuilder().setContent(value).build()); return response.getContent(); From bbe0700ff751f7e721326794a6ee2102f3855a00 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Sat, 7 Dec 2024 21:41:28 -0500 Subject: [PATCH 22/45] add GrpcLoggingInterceptorTest and test interceptor structure. --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 13 +- .../gax/grpc/GrpcLoggingInterceptorTest.java | 116 ++++++++++++++++++ 2 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index ccced9a7d6..44de135a43 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -53,6 +53,8 @@ class GrpcLoggingInterceptor implements ClientInterceptor { private static final Logger logger = LoggingUtils.getLogger(GrpcLoggingInterceptor.class); private static final Gson gson = new Gson(); + ClientCall.Listener currentListener; + @Override public ClientCall interceptCall( MethodDescriptor method, CallOptions callOptions, Channel next) { @@ -90,6 +92,7 @@ public void onClose(Status status, Metadata trailers) { super.onClose(status, trailers); } }; + currentListener = responseLoggingListener; super.start(responseLoggingListener, headers); } @@ -103,7 +106,7 @@ public void sendMessage(ReqT message) { // Helper methods for logging // some duplications with http equivalent to avoid exposing as public method - private void logRequestInfoAndHeaders( + void logRequestInfoAndHeaders( MethodDescriptor method, Metadata headers, String requestId) { try { if (logger.isInfoEnabled()) { @@ -128,20 +131,20 @@ private void logRequestInfoAndHeaders( } } - private void recordResponseHeaders(Metadata headers, LogData.Builder logDataBuilder) { + void recordResponseHeaders(Metadata headers, LogData.Builder logDataBuilder) { if (logger.isDebugEnabled()) { JsonObject responseHeaders = mapHeadersToJsonObject(headers); logDataBuilder.responseHeaders(gson.toJson(responseHeaders)); } } - private void recordResponsePayload(RespT message, LogData.Builder logDataBuilder) { + void recordResponsePayload(RespT message, LogData.Builder logDataBuilder) { if (logger.isDebugEnabled()) { logDataBuilder.responsePayload(gson.toJsonTree(message)); } } - private void logResponse(int statusCode, LogData.Builder logDataBuilder, String requestId) { + void logResponse(int statusCode, LogData.Builder logDataBuilder, String requestId) { try { if (logger.isInfoEnabled()) { @@ -160,7 +163,7 @@ private void logResponse(int statusCode, LogData.Builder logDataBuilder, String } } - private void logRequestDetails(RespT message, String requestId) { + void logRequestDetails(RespT message, String requestId) { try { if (logger.isDebugEnabled()) { LogData.Builder logDataBuilder = LogData.builder(); diff --git a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java new file mode 100644 index 0000000000..3729d2931d --- /dev/null +++ b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.grpc; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.api.gax.grpc.testing.FakeMethodDescriptor; +import com.google.api.gax.logging.LogData; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptors; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Status; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +class GrpcLoggingInterceptorTest { + @Mock private Channel channel; + + @Mock private ClientCall call; + + private static final MethodDescriptor method = FakeMethodDescriptor.create(); + + /** Sets up mocks. */ + @BeforeEach + void setUp() { + MockitoAnnotations.initMocks(this); + when(channel.newCall(Mockito.>any(), any(CallOptions.class))) + .thenReturn(call); + } + + @Test + void testInterceptor_basic() { + GrpcLoggingInterceptor interceptor = new GrpcLoggingInterceptor(); + Channel intercepted = ClientInterceptors.intercept(channel, interceptor); + @SuppressWarnings("unchecked") + ClientCall.Listener listener = mock(ClientCall.Listener.class); + ClientCall interceptedCall = intercepted.newCall(method, CallOptions.DEFAULT); + // Simulate starting the call + interceptedCall.start(listener, new Metadata()); + // Verify that the underlying call's start() method is invoked + verify(call).start(any(ClientCall.Listener.class), any(Metadata.class)); + + // Simulate sending a message + String requestMessage = "test request"; + interceptedCall.sendMessage(requestMessage); + // Verify that the underlying call's sendMessage() method is invoked + verify(call).sendMessage(requestMessage); + } + + @Test + void testInterceptor_responseListener() { + GrpcLoggingInterceptor interceptor = spy(new GrpcLoggingInterceptor()); + Channel intercepted = ClientInterceptors.intercept(channel, interceptor); + @SuppressWarnings("unchecked") + ClientCall.Listener listener = mock(ClientCall.Listener.class); + ClientCall interceptedCall = intercepted.newCall(method, CallOptions.DEFAULT); + interceptedCall.start(listener, new Metadata()); + + // Simulate respond interceptor calls + Metadata responseHeaders = new Metadata(); + responseHeaders.put( + Metadata.Key.of("test-header", Metadata.ASCII_STRING_MARSHALLER), "header-value"); + interceptor.currentListener.onHeaders(responseHeaders); + + interceptor.currentListener.onMessage(null); + + Status status = Status.OK; + interceptor.currentListener.onClose(status, new Metadata()); + + // --- Verify that the response listener's methods were called --- + verify(interceptor).recordResponseHeaders(eq(responseHeaders), any(LogData.Builder.class)); + verify(interceptor).recordResponsePayload(any(), any(LogData.Builder.class)); + verify(interceptor) + .logResponse(eq(status.getCode().value()), any(LogData.Builder.class), anyString()); + } +} From 3d7c7f9962ad703678f929dd4bec8ad69eab12bd Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Mon, 9 Dec 2024 22:37:16 -0500 Subject: [PATCH 23/45] fix status code '0'. --- .../com/google/api/gax/grpc/GrpcLoggingInterceptor.java | 6 +++--- .../com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index 44de135a43..e79639bf28 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -53,7 +53,7 @@ class GrpcLoggingInterceptor implements ClientInterceptor { private static final Logger logger = LoggingUtils.getLogger(GrpcLoggingInterceptor.class); private static final Gson gson = new Gson(); - ClientCall.Listener currentListener; + ClientCall.Listener currentListener; // expose for test setup @Override public ClientCall interceptCall( @@ -85,7 +85,7 @@ public void onMessage(RespT message) { @Override public void onClose(Status status, Metadata trailers) { try { - logResponse(status.getCode().value(), logDataBuilder, requestId); + logResponse(status.getCode().toString(), logDataBuilder, requestId); } finally { logDataBuilder = null; // release resource } @@ -144,7 +144,7 @@ void recordResponsePayload(RespT message, LogData.Builder logDataBuilder } } - void logResponse(int statusCode, LogData.Builder logDataBuilder, String requestId) { + void logResponse(String statusCode, LogData.Builder logDataBuilder, String requestId) { try { if (logger.isInfoEnabled()) { diff --git a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java index 3729d2931d..eeeab58d55 100644 --- a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java +++ b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java @@ -111,6 +111,6 @@ void testInterceptor_responseListener() { verify(interceptor).recordResponseHeaders(eq(responseHeaders), any(LogData.Builder.class)); verify(interceptor).recordResponsePayload(any(), any(LogData.Builder.class)); verify(interceptor) - .logResponse(eq(status.getCode().value()), any(LogData.Builder.class), anyString()); + .logResponse(eq(status.getCode().toString()), any(LogData.Builder.class), anyString()); } } From b870d81a83a597af43b33907bcc48b834187383a Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Mon, 9 Dec 2024 23:18:01 -0500 Subject: [PATCH 24/45] merge logs in interceptors, record log data. remove request id. --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 61 ++++++++--------- .../gax/grpc/GrpcLoggingInterceptorTest.java | 4 +- .../httpjson/HttpJsonLoggingInterceptor.java | 68 ++++++++++--------- .../HttpJsonLoggingInterceptorTest.java | 43 ++++++++++++ .../com/google/api/gax/logging/LogData.java | 49 +++++++++++++ 5 files changed, 158 insertions(+), 67 deletions(-) create mode 100644 gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index e79639bf28..f96a873312 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -44,7 +44,6 @@ import io.grpc.MethodDescriptor; import io.grpc.Status; import java.util.Map; -import java.util.UUID; import org.slf4j.Logger; import org.slf4j.event.Level; @@ -58,18 +57,17 @@ class GrpcLoggingInterceptor implements ClientInterceptor { @Override public ClientCall interceptCall( MethodDescriptor method, CallOptions callOptions, Channel next) { + return new ForwardingClientCall.SimpleForwardingClientCall( next.newCall(method, callOptions)) { - - String requestId = UUID.randomUUID().toString(); + LogData.Builder logDataBuilder = LogData.builder(); @Override public void start(Listener responseListener, Metadata headers) { - logRequestInfoAndHeaders(method, headers, requestId); + logRequestInfo(method, logDataBuilder); + recordRequestHeaders(headers, logDataBuilder); SimpleForwardingClientCallListener responseLoggingListener = new SimpleForwardingClientCallListener(responseListener) { - LogData.Builder logDataBuilder = LogData.builder(); - @Override public void onHeaders(Metadata headers) { recordResponseHeaders(headers, logDataBuilder); @@ -85,7 +83,7 @@ public void onMessage(RespT message) { @Override public void onClose(Status status, Metadata trailers) { try { - logResponse(status.getCode().toString(), logDataBuilder, requestId); + logResponse(status.getCode().toString(), logDataBuilder); } finally { logDataBuilder = null; // release resource } @@ -98,7 +96,7 @@ public void onClose(Status status, Metadata trailers) { @Override public void sendMessage(ReqT message) { - logRequestDetails(message, requestId); + logRequestDetails(message, logDataBuilder); super.sendMessage(message); } }; @@ -106,24 +104,15 @@ public void sendMessage(ReqT message) { // Helper methods for logging // some duplications with http equivalent to avoid exposing as public method - void logRequestInfoAndHeaders( - MethodDescriptor method, Metadata headers, String requestId) { + void logRequestInfo( + MethodDescriptor method, LogData.Builder logDataBuilder) { try { if (logger.isInfoEnabled()) { - LogData.Builder logDataBuilder = LogData.builder(); - logDataBuilder - .serviceName(method.getServiceName()) - .rpcName(method.getFullMethodName()) - .requestId(requestId); - - if (logger.isDebugEnabled()) { - JsonObject requestHeaders = mapHeadersToJsonObject(headers); - logDataBuilder.requestHeaders(gson.toJson(requestHeaders)); - LoggingUtils.logWithMDC( - logger, Level.DEBUG, logDataBuilder.build().toMap(), "Sending gRPC request"); - } else { + logDataBuilder.serviceName(method.getServiceName()).rpcName(method.getFullMethodName()); + + if (!logger.isDebugEnabled()) { LoggingUtils.logWithMDC( - logger, Level.INFO, logDataBuilder.build().toMap(), "Sending gRPC request"); + logger, Level.INFO, logDataBuilder.build().toMapRequest(), "Sending gRPC request"); } } } catch (Exception e) { @@ -131,6 +120,17 @@ void logRequestInfoAndHeaders( } } + private void recordRequestHeaders(Metadata headers, LogData.Builder logDataBuilder) { + try { + if (logger.isDebugEnabled()) { + JsonObject requestHeaders = mapHeadersToJsonObject(headers); + logDataBuilder.requestHeaders(gson.toJson(requestHeaders)); + } + } catch (Exception e) { + logger.error("Error recording request headers", e); + } + } + void recordResponseHeaders(Metadata headers, LogData.Builder logDataBuilder) { if (logger.isDebugEnabled()) { JsonObject responseHeaders = mapHeadersToJsonObject(headers); @@ -144,18 +144,18 @@ void recordResponsePayload(RespT message, LogData.Builder logDataBuilder } } - void logResponse(String statusCode, LogData.Builder logDataBuilder, String requestId) { + void logResponse(String statusCode, LogData.Builder logDataBuilder) { try { if (logger.isInfoEnabled()) { - logDataBuilder.responseStatus(String.valueOf(statusCode)).requestId(requestId); + logDataBuilder.responseStatus(statusCode); } if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { - Map responseData = logDataBuilder.build().toMap(); + Map responseData = logDataBuilder.build().toMapResponse(); LoggingUtils.logWithMDC(logger, Level.INFO, responseData, "Received Grpc response"); } if (logger.isDebugEnabled()) { - Map responsedDetailsMap = logDataBuilder.build().toMap(); + Map responsedDetailsMap = logDataBuilder.build().toMapResponse(); LoggingUtils.logWithMDC(logger, Level.DEBUG, responsedDetailsMap, "Received Grpc response"); } } catch (Exception e) { @@ -163,12 +163,11 @@ void logResponse(String statusCode, LogData.Builder logDataBuilder, String reque } } - void logRequestDetails(RespT message, String requestId) { + void logRequestDetails(RespT message, LogData.Builder logDataBuilder) { try { if (logger.isDebugEnabled()) { - LogData.Builder logDataBuilder = LogData.builder(); - logDataBuilder.requestPayload(gson.toJson(message)).requestId(requestId); - Map requestDetailsMap = logDataBuilder.build().toMap(); + logDataBuilder.requestPayload(gson.toJson(message)); + Map requestDetailsMap = logDataBuilder.build().toMapRequest(); LoggingUtils.logWithMDC( logger, Level.DEBUG, requestDetailsMap, "Sending gRPC request: request payload"); } diff --git a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java index eeeab58d55..d3fe5e5881 100644 --- a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java +++ b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java @@ -31,7 +31,6 @@ package com.google.api.gax.grpc; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -110,7 +109,6 @@ void testInterceptor_responseListener() { // --- Verify that the response listener's methods were called --- verify(interceptor).recordResponseHeaders(eq(responseHeaders), any(LogData.Builder.class)); verify(interceptor).recordResponsePayload(any(), any(LogData.Builder.class)); - verify(interceptor) - .logResponse(eq(status.getCode().toString()), any(LogData.Builder.class), anyString()); + verify(interceptor).logResponse(eq(status.getCode().toString()), any(LogData.Builder.class)); } } diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java index 21481ff7b9..fcaeff1fc6 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -39,7 +39,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.UUID; import org.slf4j.Logger; import org.slf4j.event.Level; @@ -54,19 +53,21 @@ public HttpJsonClientCall interceptCall( HttpJsonCallOptions callOptions, HttpJsonChannel next) { - String requestId = UUID.randomUUID().toString(); String endpoint = ((ManagedHttpJsonChannel) next).getEndpoint(); return new SimpleForwardingHttpJsonClientCall(next.newCall(method, callOptions)) { + + LogData.Builder logDataBuilder = LogData.builder(); + @Override public void start( HttpJsonClientCall.Listener responseListener, HttpJsonMetadata headers) { - logRequestInfoAndHeaders(method, headers, endpoint, requestId); + logRequestInfo(method, endpoint, logDataBuilder); + recordRequestHeaders(headers, logDataBuilder); Listener forwardingResponseListener = new SimpleForwardingHttpJsonClientCallListener(responseListener) { - LogData.Builder logDataBuilder = LogData.builder(); @Override public void onHeaders(HttpJsonMetadata responseHeaders) { @@ -83,7 +84,7 @@ public void onMessage(RespT message) { @Override public void onClose(int statusCode, HttpJsonMetadata trailers) { try { - logResponse(statusCode, logDataBuilder, requestId); + logResponse(statusCode, logDataBuilder); } finally { logDataBuilder = null; // release resource } @@ -95,7 +96,7 @@ public void onClose(int statusCode, HttpJsonMetadata trailers) { @Override public void sendMessage(ReqT message) { - logRequestDetails(message, requestId); + logRequestDetails(message, logDataBuilder); super.sendMessage(message); } }; @@ -103,31 +104,18 @@ public void sendMessage(ReqT message) { // Helper methods for logging, // some duplications with grpc equivalent to avoid exposing as public method - private void logRequestInfoAndHeaders( - ApiMethodDescriptor method, - HttpJsonMetadata headers, - String endpoint, - String requestId) { + private void logRequestInfo( + ApiMethodDescriptor method, String endpoint, LogData.Builder logDataBuilder) { try { if (logger.isInfoEnabled()) { - LogData.Builder logDataBuilder = LogData.builder(); logDataBuilder .rpcName(method.getFullMethodName()) .httpMethod(method.getHttpMethod()) - .httpUrl(endpoint) - .requestId(requestId); - - if (logger.isDebugEnabled()) { - JsonObject requestHeaders = new JsonObject(); - headers - .getHeaders() - .forEach((key, value) -> requestHeaders.addProperty(key, value.toString())); - logDataBuilder.requestHeaders(gson.toJson(requestHeaders)); - LoggingUtils.logWithMDC( - logger, Level.DEBUG, logDataBuilder.build().toMap(), "Sending HTTP request"); - } else { + .httpUrl(endpoint); + + if (!logger.isDebugEnabled()) { LoggingUtils.logWithMDC( - logger, Level.INFO, logDataBuilder.build().toMap(), "Sending HTTP request"); + logger, Level.INFO, logDataBuilder.build().toMapRequest(), "Sending HTTP request"); } } } catch (Exception e) { @@ -135,6 +123,21 @@ private void logRequestInfoAndHeaders( } } + private void recordRequestHeaders(HttpJsonMetadata headers, LogData.Builder logDataBuilder) { + try { + if (logger.isDebugEnabled()) { + JsonObject requestHeaders = new JsonObject(); + headers + .getHeaders() + .forEach((key, value) -> requestHeaders.addProperty(key, value.toString())); + logDataBuilder.requestHeaders(gson.toJson(requestHeaders)); + logDataBuilder.requestHeaders(gson.toJson(requestHeaders)); + } + } catch (Exception e) { + logger.error("Error recording request headers", e); + } + } + private void recordResponseHeaders( HttpJsonMetadata responseHeaders, LogData.Builder logDataBuilder) { @@ -152,18 +155,18 @@ private void recordResponsePayload(RespT message, LogData.Builder logDat } } - private void logResponse(int statusCode, LogData.Builder logDataBuilder, String requestId) { + private void logResponse(int statusCode, LogData.Builder logDataBuilder) { try { if (logger.isInfoEnabled()) { - logDataBuilder.responseStatus(String.valueOf(statusCode)).requestId(requestId); + logDataBuilder.responseStatus(String.valueOf(statusCode)); } if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { - Map responseData = logDataBuilder.build().toMap(); + Map responseData = logDataBuilder.build().toMapResponse(); LoggingUtils.logWithMDC(logger, Level.INFO, responseData, "Received HTTP response"); } if (logger.isDebugEnabled()) { - Map responsedDetailsMap = logDataBuilder.build().toMap(); + Map responsedDetailsMap = logDataBuilder.build().toMapResponse(); LoggingUtils.logWithMDC(logger, Level.DEBUG, responsedDetailsMap, "Received HTTP response"); } } catch (Exception e) { @@ -171,12 +174,11 @@ private void logResponse(int statusCode, LogData.Builder logDataBuilder, String } } - private void logRequestDetails(RespT message, String requestId) { + private void logRequestDetails(RespT message, LogData.Builder logDataBuilder) { try { if (logger.isDebugEnabled()) { - LogData.Builder logDataBuilder = LogData.builder(); - logDataBuilder.requestPayload(gson.toJson(message)).requestId(requestId); - Map requestDetailsMap = logDataBuilder.build().toMap(); + logDataBuilder.requestPayload(gson.toJson(message)); + Map requestDetailsMap = logDataBuilder.build().toMapRequest(); LoggingUtils.logWithMDC( logger, Level.DEBUG, requestDetailsMap, "Sending HTTP request: request payload"); } diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java new file mode 100644 index 0000000000..e8ca5f0c6f --- /dev/null +++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java @@ -0,0 +1,43 @@ +package com.google.api.gax.httpjson; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.api.gax.httpjson.ApiMethodDescriptor.MethodType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +class HttpJsonLoggingInterceptorTest { + + @Mock private HttpJsonChannel channel; + + @Mock private HttpJsonClientCall call; + + private static final ApiMethodDescriptor method = + ApiMethodDescriptor.newBuilder() + .setType(MethodType.UNARY) + .setRequestFormatter(mock(HttpRequestFormatter.class)) + .setRequestFormatter(mock(HttpRequestFormatter.class)) + .setFullMethodName("FakeClient/fake-method") + .build(); + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + when(channel.newCall( + Mockito.>any(), any(HttpJsonCallOptions.class))) + .thenReturn(call); + } + + @Test + void testInterceptor_basic() { + + HttpJsonLoggingInterceptor interceptor = new HttpJsonLoggingInterceptor(); + // HttpJsonChannel intercepted = HttpJsonClientInterceptor.intercept() + } +} diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java index 87768c0ccf..feccab6441 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java @@ -138,4 +138,53 @@ public Map toMap() { } return map; } + + public Map toMapRequest() { + Map map = new HashMap<>(); + if (serviceName() != null) { + map.put("serviceName", serviceName()); + } + if (rpcName() != null) { + map.put("rpcName", rpcName()); + } + if (requestId() != null) { + map.put("requestId", requestId()); + } + if (requestHeaders() != null) { + map.put("request.headers", requestHeaders()); + } + if (requestPayload() != null) { + map.put("request.payload", requestPayload()); + } + if (httpMethod() != null) { + map.put("request.method", httpMethod()); + } + if (httpUrl() != null) { + map.put("request.url", httpUrl()); + } + return map; + } + + public Map toMapResponse() { + Map map = new HashMap<>(); + if (serviceName() != null) { + map.put("serviceName", serviceName()); + } + if (rpcName() != null) { + map.put("rpcName", rpcName()); + } + if (requestId() != null) { + map.put("requestId", requestId()); + } + if (responseStatus() != null) { + map.put("response.status", responseStatus()); + } + if (responseHeaders() != null) { + map.put("response.headers", responseHeaders()); + } + if (responsePayload() != null) { + map.put("response.payload", gson.toJson(responsePayload())); + } + return map; + } } From 56a2870ecce95f422cb61c2336638df5e1f67335 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Mon, 9 Dec 2024 23:18:36 -0500 Subject: [PATCH 25/45] replace txt message to json message with all fields duplicated. --- .../com/google/api/gax/logging/LoggingUtils.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java index 6eba4a3430..c738426aa9 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/LoggingUtils.java @@ -33,6 +33,7 @@ import com.google.api.core.InternalApi; import com.google.api.gax.rpc.internal.EnvironmentProvider; import com.google.api.gax.rpc.internal.SystemEnvironmentProvider; +import com.google.gson.Gson; import java.util.Map; import org.slf4j.ILoggerFactory; import org.slf4j.Logger; @@ -46,6 +47,7 @@ public class LoggingUtils { private static final Logger NO_OP_LOGGER = org.slf4j.helpers.NOPLogger.NOP_LOGGER; private static boolean loggingEnabled = isLoggingEnabled(); static final String GOOGLE_SDK_JAVA_LOGGING = "GOOGLE_SDK_JAVA_LOGGING"; + private static final Gson gson = new Gson(); // expose this setter for testing purposes static void setEnvironmentProvider(EnvironmentProvider provider) { environmentProvider = provider; @@ -71,7 +73,11 @@ static Logger getLogger(Class clazz, LoggerFactoryProvider factoryProvider) { public static void logWithMDC( Logger logger, org.slf4j.event.Level level, Map contextMap, String message) { - contextMap.forEach(MDC::put); + if (!contextMap.isEmpty()) { + contextMap.forEach(MDC::put); + contextMap.put("message", message); + message = gson.toJson(contextMap); + } switch (level) { case TRACE: logger.trace(message); @@ -92,7 +98,9 @@ public static void logWithMDC( logger.info(message); // Default to INFO level } - MDC.clear(); + if (!contextMap.isEmpty()) { + MDC.clear(); + } } static boolean isLoggingEnabled() { From 83eedf07fd0ad6c19fa029cbb2b0ecca830cf3d4 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Tue, 10 Dec 2024 11:51:29 -0500 Subject: [PATCH 26/45] add try catch to logging methods --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 20 ++++++++++++----- .../httpjson/HttpJsonLoggingInterceptor.java | 22 ++++++++++++------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index f96a873312..248663635e 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -103,7 +103,7 @@ public void sendMessage(ReqT message) { } // Helper methods for logging - // some duplications with http equivalent to avoid exposing as public method + // some duplications with http equivalent to avoid exposing as public method for now void logRequestInfo( MethodDescriptor method, LogData.Builder logDataBuilder) { try { @@ -132,15 +132,23 @@ private void recordRequestHeaders(Metadata headers, LogData.Builder logDataBuild } void recordResponseHeaders(Metadata headers, LogData.Builder logDataBuilder) { - if (logger.isDebugEnabled()) { - JsonObject responseHeaders = mapHeadersToJsonObject(headers); - logDataBuilder.responseHeaders(gson.toJson(responseHeaders)); + try { + if (logger.isDebugEnabled()) { + JsonObject responseHeaders = mapHeadersToJsonObject(headers); + logDataBuilder.responseHeaders(gson.toJson(responseHeaders)); + } + } catch (Exception e) { + logger.error("Error recording response headers", e); } } void recordResponsePayload(RespT message, LogData.Builder logDataBuilder) { - if (logger.isDebugEnabled()) { - logDataBuilder.responsePayload(gson.toJsonTree(message)); + try { + if (logger.isDebugEnabled()) { + logDataBuilder.responsePayload(gson.toJsonTree(message)); + } + } catch (Exception e) { + logger.error("Error recording response payload", e); } } diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java index fcaeff1fc6..b1167109eb 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -140,24 +140,30 @@ private void recordRequestHeaders(HttpJsonMetadata headers, LogData.Builder logD private void recordResponseHeaders( HttpJsonMetadata responseHeaders, LogData.Builder logDataBuilder) { + try { + if (logger.isDebugEnabled()) { - if (logger.isDebugEnabled()) { - - Map> map = new HashMap<>(); - responseHeaders.getHeaders().forEach((key, value) -> map.put(key, (List) value)); - logDataBuilder.responseHeaders(gson.toJson(map)); + Map> map = new HashMap<>(); + responseHeaders.getHeaders().forEach((key, value) -> map.put(key, (List) value)); + logDataBuilder.responseHeaders(gson.toJson(map)); + } + } catch (Exception e) { + logger.error("Error recording response headers", e); } } private void recordResponsePayload(RespT message, LogData.Builder logDataBuilder) { - if (logger.isDebugEnabled()) { - logDataBuilder.responsePayload(gson.toJsonTree(message)); + try { + if (logger.isDebugEnabled()) { + logDataBuilder.responsePayload(gson.toJsonTree(message)); + } + } catch (Exception e) { + logger.error("Error recording response payload", e); } } private void logResponse(int statusCode, LogData.Builder logDataBuilder) { try { - if (logger.isInfoEnabled()) { logDataBuilder.responseStatus(String.valueOf(statusCode)); } From d85c8487d900ac8ac942a9c1d14853ad9a470d9a Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Tue, 10 Dec 2024 17:38:42 -0500 Subject: [PATCH 27/45] add showcase tests. expose logging interceptors as public to setup tests. --- .github/workflows/ci.yaml | 9 ++ .../api/gax/grpc/GrpcLoggingInterceptor.java | 2 +- .../httpjson/HttpJsonLoggingInterceptor.java | 2 +- .../api/gax/logging/LoggingUtilsTest.java | 27 ++-- showcase/gapic-showcase/pom.xml | 24 +--- .../google/showcase/v1beta1/it/ITLogging.java | 120 +++++------------- .../showcase/v1beta1/it/ITUnaryCallable.java | 8 -- .../v1beta1/it/util/TestAppender.java | 4 +- .../src/test/resources/logback-test.xml | 39 +----- showcase/pom.xml | 44 +++++++ 10 files changed, 107 insertions(+), 172 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index cbe7bd9bd6..6af6c36f7a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -255,6 +255,15 @@ jobs: -P enable-integration-tests \ --batch-mode \ --no-transfer-progress + # The `envVarLoggingTest` profile runs tests that require an environment variable + - name: Showcase integration tests - Logging + run: | + verify -P '!showcase,enable-integration-tests,envVarLoggingTest' + --batch-mode \ + --no-transfer-progress + # Set the Env Var for this step only + env: + GOOGLE_SDK_JAVA_LOGGING: true showcase-native: runs-on: ubuntu-22.04 diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index 248663635e..e8c2f8cf15 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -47,7 +47,7 @@ import org.slf4j.Logger; import org.slf4j.event.Level; -class GrpcLoggingInterceptor implements ClientInterceptor { +public class GrpcLoggingInterceptor implements ClientInterceptor { private static final Logger logger = LoggingUtils.getLogger(GrpcLoggingInterceptor.class); private static final Gson gson = new Gson(); diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java index b1167109eb..a898df79e9 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -42,7 +42,7 @@ import org.slf4j.Logger; import org.slf4j.event.Level; -class HttpJsonLoggingInterceptor implements HttpJsonClientInterceptor { +public class HttpJsonLoggingInterceptor implements HttpJsonClientInterceptor { private static final Logger logger = LoggingUtils.getLogger(HttpJsonLoggingInterceptor.class); private static final Gson gson = new Gson(); diff --git a/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java b/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java index fddc565df9..486677c7ab 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java @@ -46,18 +46,19 @@ import com.google.api.gax.rpc.internal.EnvironmentProvider; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.slf4j.ILoggerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.helpers.NOPLogger; -public class LoggingUtilsTest { +class LoggingUtilsTest { private EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class); @BeforeEach - public void setup() { + void setup() { EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class); // need to setup a ConsoleAppender and attach to root logger because TestAppender @@ -83,13 +84,13 @@ public void setup() { } @AfterEach - public void tearDown() { + void tearDown() { LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); loggerContext.getLogger(Logger.ROOT_LOGGER_NAME).detachAppender("CONSOLE"); } - @org.junit.Test - public void testGetLogger_loggingEnabled_slf4jBindingPresent() { + @Test + void testGetLogger_loggingEnabled_slf4jBindingPresent() { Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("true"); LoggingUtils.setEnvironmentProvider(envProvider); Logger logger = LoggingUtils.getLogger(LoggingUtilsTest.class); @@ -97,8 +98,8 @@ public void testGetLogger_loggingEnabled_slf4jBindingPresent() { assertNotEquals(logger.getClass(), NOPLogger.class); } - @org.junit.Test - public void testGetLogger_loggingDisabled() { + @Test + void testGetLogger_loggingDisabled() { Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("false"); LoggingUtils.setEnvironmentProvider(envProvider); @@ -106,8 +107,8 @@ public void testGetLogger_loggingDisabled() { assertEquals(NOPLogger.class, logger.getClass()); } - @org.junit.Test - public void testGetLogger_loggingEnabled_noBinding() { + @Test + void testGetLogger_loggingEnabled_noBinding() { Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("true"); LoggingUtils.setEnvironmentProvider(envProvider); // Create a mock LoggerFactoryProvider @@ -124,8 +125,8 @@ public void testGetLogger_loggingEnabled_noBinding() { assertTrue(logger instanceof org.slf4j.helpers.NOPLogger); } - @org.junit.Test - public void testIsLoggingEnabled_true() { + @Test + void testIsLoggingEnabled_true() { Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("true"); LoggingUtils.setEnvironmentProvider(envProvider); assertTrue(LoggingUtils.isLoggingEnabled()); @@ -137,8 +138,8 @@ public void testIsLoggingEnabled_true() { assertTrue(LoggingUtils.isLoggingEnabled()); } - @org.junit.Test - public void testIsLoggingEnabled_defaultToFalse() { + @Test + void testIsLoggingEnabled_defaultToFalse() { LoggingUtils.setEnvironmentProvider(envProvider); assertFalse(LoggingUtils.isLoggingEnabled()); } diff --git a/showcase/gapic-showcase/pom.xml b/showcase/gapic-showcase/pom.xml index cbf15d5269..f4d9ade8ba 100644 --- a/showcase/gapic-showcase/pom.xml +++ b/showcase/gapic-showcase/pom.xml @@ -216,12 +216,7 @@ test - - - org.slf4j - slf4j-api - 2.0.16 - + ch.qos.logback logback-classic @@ -232,23 +227,6 @@ ch.qos.logback logback-core 1.5.11 - - - net.logstash.logback - logstash-logback-encoder - 7.2 - test - - - org.codehaus.janino - janino - 3.1.9 - test - - - org.mockito - mockito-core - 4.11.0 test diff --git a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITLogging.java b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITLogging.java index a0fb67e2fe..51e7145e99 100644 --- a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITLogging.java +++ b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITLogging.java @@ -19,24 +19,32 @@ import static com.google.common.truth.Truth.assertThat; import ch.qos.logback.classic.spi.ILoggingEvent; +import com.google.api.gax.grpc.GrpcLoggingInterceptor; +import com.google.api.gax.httpjson.HttpJsonLoggingInterceptor; import com.google.showcase.v1beta1.EchoClient; import com.google.showcase.v1beta1.EchoRequest; import com.google.showcase.v1beta1.EchoResponse; import com.google.showcase.v1beta1.it.util.TestAppender; import com.google.showcase.v1beta1.it.util.TestClientInitializer; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.slf4j.LoggerFactory; // This test needs to run with GOOGLE_SDK_JAVA_LOGGING=true public class ITLogging { private static EchoClient grpcClient; private static EchoClient httpjsonClient; - static final Logger LOGGER = Logger.getLogger(ITUnaryCallable.class.getName()); + + private TestAppender setupTestLogger(Class clazz) { + TestAppender testAppender = new TestAppender(); + testAppender.start(); + org.slf4j.Logger logger = LoggerFactory.getLogger(clazz); + ((ch.qos.logback.classic.Logger) logger).addAppender(testAppender); + return testAppender; + } @BeforeAll static void createClients() throws Exception { @@ -44,8 +52,6 @@ static void createClients() throws Exception { grpcClient = TestClientInitializer.createGrpcEchoClient(); // Create Http JSON Echo Client httpjsonClient = TestClientInitializer.createHttpJsonEchoClient(); - - LOGGER.log(Level.INFO, "This is log message directly from JUL. Clients created."); } @AfterAll @@ -60,95 +66,37 @@ static void destroyClients() throws InterruptedException { @Test void testGrpc_receiveContent_logDebug() { - LOGGER.log( - Level.INFO, - "This is log message directly from JUL. Starting test: testGrpc_receiveContent."); - - TestAppender.clearEvents(); + TestAppender testAppender = setupTestLogger(GrpcLoggingInterceptor.class); assertThat(echoGrpc("grpc-echo?")).isEqualTo("grpc-echo?"); - assertThat(TestAppender.events.size()).isEqualTo(3); - assertThat(TestAppender.events.get(0).getMessage()).isEqualTo("Sending gRPC request"); - assertThat(TestAppender.events.get(0).getLevel()).isEqualTo(ch.qos.logback.classic.Level.DEBUG); - assertThat(TestAppender.events.get(1).getMessage()) - .isEqualTo("Sending gRPC request: request payload"); - assertThat(TestAppender.events.get(2).getMessage()).isEqualTo("Received Grpc response"); + assertThat(testAppender.events.size()).isEqualTo(2); + assertThat(testAppender.events.get(0).getMessage()) + .isEqualTo( + "{\"serviceName\":\"google.showcase.v1beta1.Echo\",\"message\":\"Sending gRPC request\",\"rpcName\":\"google.showcase.v1beta1.Echo/Echo\"}"); + assertThat(testAppender.events.get(0).getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); + assertThat(testAppender.events.get(1).getMessage()) + .isEqualTo( + "{\"serviceName\":\"google.showcase.v1beta1.Echo\",\"response.status\":\"OK\",\"message\":\"Received Grpc response\",\"rpcName\":\"google.showcase.v1beta1.Echo/Echo\"}"); + testAppender.stop(); } - // @Test - // void testGrpc_receiveContent_logInfo() { - // ch.qos.logback.classic.Logger logger = - // (ch.qos.logback.classic.Logger) LoggingUtils.getLogger(GrpcLoggingInterceptor.class); - // ch.qos.logback.classic.Level originalLevel = logger.getLevel(); - // try { - // logger.setLevel(ch.qos.logback.classic.Level.INFO); - // assertThat(logger.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); - // - // TestAppender.clearEvents(); - // assertThat(echoGrpc("grpc-echo?")).isEqualTo("grpc-echo?"); - // assertThat(TestAppender.events.size()).isEqualTo(2); - // ILoggingEvent loggingEvent1 = TestAppender.events.get(0); - // assertThat(loggingEvent1.getMessage()).isEqualTo("Sending gRPC request"); - // assertThat(loggingEvent1.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); - // assertThat(loggingEvent1.getMDCPropertyMap()).hasSize(3); - // assertThat(loggingEvent1.getMDCPropertyMap()) - // .containsEntry("serviceName", "google.showcase.v1beta1.Echo"); - // assertThat(loggingEvent1.getMDCPropertyMap()) - // .containsEntry("rpcName", "google.showcase.v1beta1.Echo/Echo"); - // assertThat(TestAppender.events.get(1).getMessage()).isEqualTo("Received Grpc response"); - // assertThat(TestAppender.events.get(1).getLevel()) - // .isEqualTo(ch.qos.logback.classic.Level.INFO); - // } finally { - // logger.setLevel(originalLevel); - // } - // } - @Test void testHttpJson_receiveContent_logDebug() { - TestAppender.clearEvents(); + TestAppender testAppender = setupTestLogger(HttpJsonLoggingInterceptor.class); assertThat(echoHttpJson("http-echo?")).isEqualTo("http-echo?"); - assertThat(TestAppender.events.size()).isEqualTo(3); - ILoggingEvent loggingEvent1 = TestAppender.events.get(0); - assertThat(loggingEvent1.getMessage()).isEqualTo("Sending HTTP request"); - assertThat(loggingEvent1.getLevel()).isEqualTo(ch.qos.logback.classic.Level.DEBUG); - assertThat(loggingEvent1.getMDCPropertyMap()).hasSize(5); - assertThat(loggingEvent1.getMDCPropertyMap()).containsKey("request.headers"); - assertThat(TestAppender.events.get(1).getMessage()) - .isEqualTo("Sending HTTP request: request payload"); - assertThat(TestAppender.events.get(2).getMessage()).isEqualTo("Received HTTP response"); + assertThat(testAppender.events.size()).isEqualTo(2); + ILoggingEvent loggingEvent1 = testAppender.events.get(0); + assertThat(loggingEvent1.getMessage()) + .isEqualTo( + "{\"request.method\":\"POST\",\"request.url\":\"http://localhost:7469\",\"message\":\"Sending HTTP request\",\"rpcName\":\"google.showcase.v1beta1.Echo/Echo\"}"); + assertThat(loggingEvent1.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); + assertThat(loggingEvent1.getMDCPropertyMap()).hasSize(3); + assertThat(loggingEvent1.getMDCPropertyMap()).containsKey("rpcName"); + assertThat(testAppender.events.get(1).getMessage()) + .isEqualTo( + "{\"response.status\":\"200\",\"message\":\"Received HTTP response\",\"rpcName\":\"google.showcase.v1beta1.Echo/Echo\"}"); + testAppender.stop(); } - // @Test - // void testHttpJson_receiveContent_logInfo() { - // - // ch.qos.logback.classic.Logger logger = - // (ch.qos.logback.classic.Logger) LoggingUtils.getLogger(HttpJsonLoggingInterceptor.class); - // ch.qos.logback.classic.Level originalLevel = logger.getLevel(); - // try { - // logger.setLevel(ch.qos.logback.classic.Level.INFO); - // assertThat(logger.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); - // - // TestAppender.clearEvents(); - // assertThat(echoHttpJson("http-echo?")).isEqualTo("http-echo?"); - // assertThat(TestAppender.events.size()).isEqualTo(2); - // ILoggingEvent loggingEvent1 = TestAppender.events.get(0); - // assertThat(loggingEvent1.getMessage()).isEqualTo("Sending HTTP request"); - // assertThat(loggingEvent1.getLevel()).isEqualTo(ch.qos.logback.classic.Level.INFO); - // assertThat(loggingEvent1.getMDCPropertyMap()).hasSize(4); - // assertThat(loggingEvent1.getMDCPropertyMap()) - // .containsEntry("rpcName", "google.showcase.v1beta1.Echo/Echo"); - // assertThat(loggingEvent1.getMDCPropertyMap()).containsEntry("request.method", "POST"); - // assertThat(loggingEvent1.getMDCPropertyMap()) - // .containsEntry("request.url", "http://localhost:7469"); - // assertThat(TestAppender.events.get(1).getMessage()).isEqualTo("Received HTTP response"); - // assertThat(TestAppender.events.get(1).getLevel()) - // .isEqualTo(ch.qos.logback.classic.Level.INFO); - // assertThat(TestAppender.events.get(1).getMDCPropertyMap()) - // .containsEntry("response.status", "200"); - // } finally { - // logger.setLevel(originalLevel); - // } - // } - private String echoGrpc(String value) { EchoResponse response = grpcClient.echo(EchoRequest.newBuilder().setContent(value).build()); return response.getContent(); diff --git a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java index 76a78e7ff9..4d6018f6fc 100644 --- a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java +++ b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/ITUnaryCallable.java @@ -28,8 +28,6 @@ import com.google.showcase.v1beta1.EchoResponse; import com.google.showcase.v1beta1.it.util.TestClientInitializer; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -39,7 +37,6 @@ class ITUnaryCallable { private static EchoClient grpcClient; private static EchoClient httpjsonClient; - static final Logger LOGGER = Logger.getLogger(ITUnaryCallable.class.getName()); @BeforeAll static void createClients() throws Exception { @@ -47,8 +44,6 @@ static void createClients() throws Exception { grpcClient = TestClientInitializer.createGrpcEchoClient(); // Create Http JSON Echo Client httpjsonClient = TestClientInitializer.createHttpJsonEchoClient(); - - LOGGER.log(Level.INFO, "This is log message directly from JUL. Clients created."); } @AfterAll @@ -63,9 +58,6 @@ static void destroyClients() throws InterruptedException { @Test void testGrpc_receiveContent() { - LOGGER.log( - Level.INFO, - "This is log message directly from JUL. Starting test: testGrpc_receiveContent."); assertThat(echoGrpc("grpc-echo?")).isEqualTo("grpc-echo?"); assertThat(echoGrpc("grpc-echo!")).isEqualTo("grpc-echo!"); } diff --git a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestAppender.java b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestAppender.java index 4ed5439fc7..0032b09d9d 100644 --- a/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestAppender.java +++ b/showcase/gapic-showcase/src/test/java/com/google/showcase/v1beta1/it/util/TestAppender.java @@ -24,7 +24,7 @@ /** Logback appender used to set up tests. */ public class TestAppender extends AppenderBase { - public static List events = new ArrayList<>(); + public List events = new ArrayList<>(); @Override protected void append(ILoggingEvent eventObject) { @@ -33,7 +33,7 @@ protected void append(ILoggingEvent eventObject) { events.add(eventObject); } - public static void clearEvents() { + public void clearEvents() { events.clear(); } } diff --git a/showcase/gapic-showcase/src/test/resources/logback-test.xml b/showcase/gapic-showcase/src/test/resources/logback-test.xml index c38d09e794..66ed2652ea 100644 --- a/showcase/gapic-showcase/src/test/resources/logback-test.xml +++ b/showcase/gapic-showcase/src/test/resources/logback-test.xml @@ -1,43 +1,6 @@ - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/showcase/pom.xml b/showcase/pom.xml index 6cbcdf92ab..b26d06521e 100644 --- a/showcase/pom.xml +++ b/showcase/pom.xml @@ -117,6 +117,50 @@ org.apache.maven.plugins maven-failsafe-plugin + + + **/ITLogging.java + + + + + + + org.apache.maven.surefire + surefire-junit-platform + ${surefire.version} + + + + + org.codehaus.mojo + flatten-maven-plugin + + + + + + + envVarLoggingTest + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.0 + + sponge_log + ${skipUnitTests} + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + **/ITLogging.java + + From 1919809ecebafc982540791480f7d12e75a59435 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Tue, 10 Dec 2024 17:40:26 -0500 Subject: [PATCH 28/45] add internal api annotation. --- .../java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java | 2 ++ .../com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index e8c2f8cf15..73f3d7e1a5 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -30,6 +30,7 @@ package com.google.api.gax.grpc; +import com.google.api.core.InternalApi; import com.google.api.gax.logging.LogData; import com.google.api.gax.logging.LoggingUtils; import com.google.gson.Gson; @@ -47,6 +48,7 @@ import org.slf4j.Logger; import org.slf4j.event.Level; +@InternalApi public class GrpcLoggingInterceptor implements ClientInterceptor { private static final Logger logger = LoggingUtils.getLogger(GrpcLoggingInterceptor.class); diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java index a898df79e9..07fa52372a 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -30,6 +30,7 @@ package com.google.api.gax.httpjson; +import com.google.api.core.InternalApi; import com.google.api.gax.httpjson.ForwardingHttpJsonClientCall.SimpleForwardingHttpJsonClientCall; import com.google.api.gax.httpjson.ForwardingHttpJsonClientCallListener.SimpleForwardingHttpJsonClientCallListener; import com.google.api.gax.logging.LogData; @@ -42,6 +43,7 @@ import org.slf4j.Logger; import org.slf4j.event.Level; +@InternalApi public class HttpJsonLoggingInterceptor implements HttpJsonClientInterceptor { private static final Logger logger = LoggingUtils.getLogger(HttpJsonLoggingInterceptor.class); From 613b6c83ba2f138e4501d8ae942a44ceeb416132 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Tue, 10 Dec 2024 17:56:11 -0500 Subject: [PATCH 29/45] lint --- .../HttpJsonLoggingInterceptorTest.java | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java index e8ca5f0c6f..48bc6b9a97 100644 --- a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java +++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java @@ -1,6 +1,35 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + package com.google.api.gax.httpjson; -import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -38,6 +67,5 @@ void setUp() { void testInterceptor_basic() { HttpJsonLoggingInterceptor interceptor = new HttpJsonLoggingInterceptor(); - // HttpJsonChannel intercepted = HttpJsonClientInterceptor.intercept() } } From 1169eaaa200db35507a1131debf4151f6057665f Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Tue, 10 Dec 2024 20:33:08 -0500 Subject: [PATCH 30/45] fix typo --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6af6c36f7a..94f3ccd223 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -258,7 +258,7 @@ jobs: # The `envVarLoggingTest` profile runs tests that require an environment variable - name: Showcase integration tests - Logging run: | - verify -P '!showcase,enable-integration-tests,envVarLoggingTest' + mvn verify -P '!showcase,enable-integration-tests,envVarLoggingTest' --batch-mode \ --no-transfer-progress # Set the Env Var for this step only From a6b5433e7516386a836f757531ffce50edf0db30 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Tue, 10 Dec 2024 20:49:07 -0500 Subject: [PATCH 31/45] remove unused test class. --- .../HttpJsonLoggingInterceptorTest.java | 71 ------------------- 1 file changed, 71 deletions(-) delete mode 100644 gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java deleted file mode 100644 index 48bc6b9a97..0000000000 --- a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.google.api.gax.httpjson; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.google.api.gax.httpjson.ApiMethodDescriptor.MethodType; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; - -class HttpJsonLoggingInterceptorTest { - - @Mock private HttpJsonChannel channel; - - @Mock private HttpJsonClientCall call; - - private static final ApiMethodDescriptor method = - ApiMethodDescriptor.newBuilder() - .setType(MethodType.UNARY) - .setRequestFormatter(mock(HttpRequestFormatter.class)) - .setRequestFormatter(mock(HttpRequestFormatter.class)) - .setFullMethodName("FakeClient/fake-method") - .build(); - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - when(channel.newCall( - Mockito.>any(), any(HttpJsonCallOptions.class))) - .thenReturn(call); - } - - @Test - void testInterceptor_basic() { - - HttpJsonLoggingInterceptor interceptor = new HttpJsonLoggingInterceptor(); - } -} From fb0966e0b48e8023f8309a15e7c19a7c55b7f2fb Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Tue, 10 Dec 2024 20:55:43 -0500 Subject: [PATCH 32/45] try to exclude test from showcase-native. --- showcase/pom.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/showcase/pom.xml b/showcase/pom.xml index b26d06521e..bdfccf08f4 100644 --- a/showcase/pom.xml +++ b/showcase/pom.xml @@ -95,6 +95,15 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + **/ITLogging.java + + + From 77939feaa9dc0a78647219ddfca22b855568bd67 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Tue, 10 Dec 2024 21:00:10 -0500 Subject: [PATCH 33/45] fix logback version. --- showcase/gapic-showcase/pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/showcase/gapic-showcase/pom.xml b/showcase/gapic-showcase/pom.xml index f4d9ade8ba..583ab17219 100644 --- a/showcase/gapic-showcase/pom.xml +++ b/showcase/gapic-showcase/pom.xml @@ -220,13 +220,14 @@ ch.qos.logback logback-classic - 1.5.11 + + 1.3.14 test ch.qos.logback logback-core - 1.5.11 + 1.3.14 test From 04ef7748e1a135857d02fb6275718acb49b69265 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Tue, 10 Dec 2024 21:05:31 -0500 Subject: [PATCH 34/45] revert accidental change. --- pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/pom.xml b/pom.xml index d4379722a8..d207468e77 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,6 @@ gapic-generator-java-bom java-shared-dependencies sdk-platform-java-config - showcase From 439e0711fd54d7083d5cc74ed9a162db069d60f6 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Tue, 10 Dec 2024 21:08:28 -0500 Subject: [PATCH 35/45] fix typo in ci. --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 94f3ccd223..10e224318a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -258,7 +258,7 @@ jobs: # The `envVarLoggingTest` profile runs tests that require an environment variable - name: Showcase integration tests - Logging run: | - mvn verify -P '!showcase,enable-integration-tests,envVarLoggingTest' + mvn verify -P '!showcase,enable-integration-tests,envVarLoggingTest' \ --batch-mode \ --no-transfer-progress # Set the Env Var for this step only From 673c9fe9b75dbdfbc1b92072d1ba53ce51674826 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Tue, 10 Dec 2024 22:21:15 -0500 Subject: [PATCH 36/45] cleanup gax pom. add tests. --- gapic-generator-java-pom-parent/pom.xml | 1 + gax-java/gax/pom.xml | 15 ---- .../com/google/api/gax/logging/LogData.java | 35 -------- .../google/api/gax/logging/LogDataTest.java | 85 +++++++++++++++++++ .../api/gax/logging/LoggingUtilsTest.java | 42 ++++++--- .../google/api/gax/logging/TestAppender.java | 52 ++++++++++++ gax-java/pom.xml | 19 +++++ 7 files changed, 185 insertions(+), 64 deletions(-) create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/logging/LogDataTest.java create mode 100644 gax-java/gax/src/test/java/com/google/api/gax/logging/TestAppender.java diff --git a/gapic-generator-java-pom-parent/pom.xml b/gapic-generator-java-pom-parent/pom.xml index 1991f17d01..425dd9ed37 100644 --- a/gapic-generator-java-pom-parent/pom.xml +++ b/gapic-generator-java-pom-parent/pom.xml @@ -38,6 +38,7 @@ 3.0.0 1.7.0 5.11.2 + 2.0.16 diff --git a/gax-java/gax/pom.xml b/gax-java/gax/pom.xml index 2f86586d82..2f7080e817 100644 --- a/gax-java/gax/pom.xml +++ b/gax-java/gax/pom.xml @@ -73,21 +73,6 @@ org.slf4j slf4j-api - 2.0.16 - - - - ch.qos.logback - logback-classic - - 1.3.14 - test - - - ch.qos.logback - logback-core - 1.3.14 - test diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java index feccab6441..db4767eb20 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java @@ -104,41 +104,6 @@ public abstract static class Builder { // helper functions to convert to map for logging // todo: error handling? - public Map toMap() { - Map map = new HashMap<>(); - if (serviceName() != null) { - map.put("serviceName", serviceName()); - } - if (rpcName() != null) { - map.put("rpcName", rpcName()); - } - if (requestId() != null) { - map.put("requestId", requestId()); - } - if (requestHeaders() != null) { - map.put("request.headers", requestHeaders()); - } - if (requestPayload() != null) { - map.put("request.payload", requestPayload()); - } - if (responseStatus() != null) { - map.put("response.status", responseStatus()); - } - if (responseHeaders() != null) { - map.put("response.headers", responseHeaders()); - } - if (responsePayload() != null) { - map.put("response.payload", gson.toJson(responsePayload())); - } - if (httpMethod() != null) { - map.put("request.method", httpMethod()); - } - if (httpUrl() != null) { - map.put("request.url", httpUrl()); - } - return map; - } - public Map toMapRequest() { Map map = new HashMap<>(); if (serviceName() != null) { diff --git a/gax-java/gax/src/test/java/com/google/api/gax/logging/LogDataTest.java b/gax-java/gax/src/test/java/com/google/api/gax/logging/LogDataTest.java new file mode 100644 index 0000000000..d19c74591a --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/logging/LogDataTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.logging; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.JsonParser; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class LogDataTest { + + @Test + void toMapResponse_correctlyConvertsData() { + LogData logData = + LogData.builder() + .serviceName("MyService") + .rpcName("myMethod") + .requestHeaders("fake header") + .requestId("abcd") + .responsePayload(JsonParser.parseString("{\"key\": \"value\"}")) + .build(); + + Map expectedMap = + ImmutableMap.of( + "serviceName", "MyService", + "rpcName", "myMethod", + "response.payload", "{\"key\":\"value\"}", + "requestId", "abcd"); + + assertThat(logData.toMapResponse()).isEqualTo(expectedMap); + } + + @Test + void toMapRequest_correctlyConvertsData() { + LogData logData = + LogData.builder() + .serviceName("MyService") + .rpcName("myMethod") + .requestHeaders("fake header") + .requestId("abcd") + .httpUrl("url") + .responsePayload(JsonParser.parseString("{\"key\": \"value\"}")) + .build(); + + Map expectedMap = + ImmutableMap.of( + "serviceName", "MyService", + "rpcName", "myMethod", + "request.headers", "fake header", + "requestId", "abcd", + "request.url", "url"); + + assertThat(logData.toMapRequest()).isEqualTo(expectedMap); + } +} diff --git a/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java b/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java index 486677c7ab..722a2d5a22 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java @@ -44,6 +44,8 @@ import ch.qos.logback.core.ConsoleAppender; import com.google.api.gax.logging.LoggingUtils.LoggerFactoryProvider; import com.google.api.gax.rpc.internal.EnvironmentProvider; +import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -55,6 +57,7 @@ class LoggingUtilsTest { + private static final Logger LOGGER = LoggerFactory.getLogger(LoggingUtilsTest.class); private EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class); @BeforeEach @@ -144,18 +147,29 @@ void testIsLoggingEnabled_defaultToFalse() { assertFalse(LoggingUtils.isLoggingEnabled()); } - // @Test - // public void testLogWithMDC_slf4jLogger() { - // TestAppender.clearEvents(); - // Map contextMap = new HashMap<>(); - // contextMap.put("key", "value"); - // LoggingUtils.logWithMDC(LOGGER, org.slf4j.event.Level.DEBUG, contextMap, "test message"); - // - // assertEquals(1, TestAppender.events.size()); - // assertEquals("test message", TestAppender.events.get(0).getFormattedMessage()); - // - // // Verify MDC content - // ILoggingEvent event = TestAppender.events.get(0); - // assertEquals("value", event.getMDCPropertyMap().get("key")); - // } + private TestAppender setupTestLogger(Class clazz) { + TestAppender testAppender = new TestAppender(); + testAppender.start(); + Logger logger = LoggerFactory.getLogger(clazz); + ((ch.qos.logback.classic.Logger) logger).addAppender(testAppender); + return testAppender; + } + + @Test + public void testLogWithMDC_slf4jLogger() { + TestAppender testAppender = setupTestLogger(LoggingUtilsTest.class); + Map contextMap = new HashMap<>(); + contextMap.put("key", "value"); + LoggingUtils.logWithMDC(LOGGER, org.slf4j.event.Level.DEBUG, contextMap, "test message"); + + assertEquals(1, testAppender.events.size()); + assertEquals( + "{\"message\":\"test message\",\"key\":\"value\"}", + testAppender.events.get(0).getFormattedMessage()); + + // Verify MDC content + ILoggingEvent event = testAppender.events.get(0); + assertEquals("value", event.getMDCPropertyMap().get("key")); + testAppender.stop(); + } } diff --git a/gax-java/gax/src/test/java/com/google/api/gax/logging/TestAppender.java b/gax-java/gax/src/test/java/com/google/api/gax/logging/TestAppender.java new file mode 100644 index 0000000000..1206553ee6 --- /dev/null +++ b/gax-java/gax/src/test/java/com/google/api/gax/logging/TestAppender.java @@ -0,0 +1,52 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.logging; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import java.util.ArrayList; +import java.util.List; + +/** Logback appender used to set up tests. */ +public class TestAppender extends AppenderBase { + public List events = new ArrayList<>(); + + @Override + protected void append(ILoggingEvent eventObject) { + // triggering Logback to capture the current MDC context and store it with the log event + eventObject.getMDCPropertyMap(); + events.add(eventObject); + } + + public void clearEvents() { + events.clear(); + } +} diff --git a/gax-java/pom.xml b/gax-java/pom.xml index 1963fd0b3f..007a4c099b 100644 --- a/gax-java/pom.xml +++ b/gax-java/pom.xml @@ -134,6 +134,11 @@ graal-sdk ${graal-sdk.version} + + org.slf4j + slf4j-api + ${slf4j.version} + com.google.http-client google-http-client-bom @@ -205,6 +210,20 @@ test + + + ch.qos.logback + logback-classic + + 1.3.14 + test + + + ch.qos.logback + logback-core + 1.3.14 + test + From c2b607ba654978d7d081b43ffe067af149468f7f Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Tue, 10 Dec 2024 22:43:16 -0500 Subject: [PATCH 37/45] add test. --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 4 +- .../gax/grpc/GrpcLoggingInterceptorTest.java | 29 +++++++++++ .../com/google/api/gax/grpc/TestAppender.java | 52 +++++++++++++++++++ .../src/test/resources/logback-test.xml | 6 +++ 4 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/TestAppender.java create mode 100644 gax-java/gax-grpc/src/test/resources/logback-test.xml diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index 73f3d7e1a5..4d48d7f663 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -66,7 +66,7 @@ public ClientCall interceptCall( @Override public void start(Listener responseListener, Metadata headers) { - logRequestInfo(method, logDataBuilder); + logRequestInfo(method, logDataBuilder, logger); recordRequestHeaders(headers, logDataBuilder); SimpleForwardingClientCallListener responseLoggingListener = new SimpleForwardingClientCallListener(responseListener) { @@ -107,7 +107,7 @@ public void sendMessage(ReqT message) { // Helper methods for logging // some duplications with http equivalent to avoid exposing as public method for now void logRequestInfo( - MethodDescriptor method, LogData.Builder logDataBuilder) { + MethodDescriptor method, LogData.Builder logDataBuilder, Logger logger) { try { if (logger.isInfoEnabled()) { logDataBuilder.serviceName(method.getServiceName()).rpcName(method.getFullMethodName()); diff --git a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java index d3fe5e5881..423221db2d 100644 --- a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java +++ b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java @@ -30,6 +30,7 @@ package com.google.api.gax.grpc; +import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -37,6 +38,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import ch.qos.logback.classic.Level; import com.google.api.gax.grpc.testing.FakeMethodDescriptor; import com.google.api.gax.logging.LogData; import io.grpc.CallOptions; @@ -46,11 +48,14 @@ import io.grpc.Metadata; import io.grpc.MethodDescriptor; import io.grpc.Status; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; class GrpcLoggingInterceptorTest { @Mock private Channel channel; @@ -59,6 +64,7 @@ class GrpcLoggingInterceptorTest { private static final MethodDescriptor method = FakeMethodDescriptor.create(); + private static final Logger LOGGER = LoggerFactory.getLogger(GrpcLoggingInterceptorTest.class); /** Sets up mocks. */ @BeforeEach void setUp() { @@ -111,4 +117,27 @@ void testInterceptor_responseListener() { verify(interceptor).recordResponsePayload(any(), any(LogData.Builder.class)); verify(interceptor).logResponse(eq(status.getCode().toString()), any(LogData.Builder.class)); } + + @Test + void testLogRequestInfo() { + + TestAppender testAppender = setupTestLogger(GrpcLoggingInterceptorTest.class); + GrpcLoggingInterceptor interceptor = new GrpcLoggingInterceptor(); + interceptor.logRequestInfo(method, LogData.builder(), LOGGER); + + Assertions.assertEquals(1, testAppender.events.size()); + assertEquals(Level.INFO, testAppender.events.get(0).getLevel()); + assertEquals( + "{\"serviceName\":\"FakeClient\",\"message\":\"Sending gRPC request\",\"rpcName\":\"FakeClient/fake-method\"}", + testAppender.events.get(0).getMessage()); + testAppender.stop(); + } + + private TestAppender setupTestLogger(Class clazz) { + TestAppender testAppender = new TestAppender(); + testAppender.start(); + Logger logger = LoggerFactory.getLogger(clazz); + ((ch.qos.logback.classic.Logger) logger).addAppender(testAppender); + return testAppender; + } } diff --git a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/TestAppender.java b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/TestAppender.java new file mode 100644 index 0000000000..0aa34ebb5a --- /dev/null +++ b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/TestAppender.java @@ -0,0 +1,52 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.grpc; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import java.util.ArrayList; +import java.util.List; + +/** Logback appender used to set up tests. */ +public class TestAppender extends AppenderBase { + public List events = new ArrayList<>(); + + @Override + protected void append(ILoggingEvent eventObject) { + // triggering Logback to capture the current MDC context and store it with the log event + eventObject.getMDCPropertyMap(); + events.add(eventObject); + } + + public void clearEvents() { + events.clear(); + } +} diff --git a/gax-java/gax-grpc/src/test/resources/logback-test.xml b/gax-java/gax-grpc/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..802d0e18a7 --- /dev/null +++ b/gax-java/gax-grpc/src/test/resources/logback-test.xml @@ -0,0 +1,6 @@ + + + + + + From 4190dc72454b1b1515f76fd742640078e88cff9b Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Wed, 11 Dec 2024 09:38:49 -0500 Subject: [PATCH 38/45] rm redundant assignment. --- .../com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java | 1 - 1 file changed, 1 deletion(-) diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java index 07fa52372a..fa4bcc366b 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -133,7 +133,6 @@ private void recordRequestHeaders(HttpJsonMetadata headers, LogData.Builder logD .getHeaders() .forEach((key, value) -> requestHeaders.addProperty(key, value.toString())); logDataBuilder.requestHeaders(gson.toJson(requestHeaders)); - logDataBuilder.requestHeaders(gson.toJson(requestHeaders)); } } catch (Exception e) { logger.error("Error recording request headers", e); From fefb436c3b4a0243aeed13db03a4f74c99e2fa32 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Wed, 11 Dec 2024 09:49:50 -0500 Subject: [PATCH 39/45] add logging test setup in httpjson. --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 44 +++++----- .../httpjson/HttpJsonLoggingInterceptor.java | 51 ++++++------ .../HttpJsonLoggingInterceptorTest.java | 81 +++++++++++++++++++ .../google/api/gax/httpjson/TestAppender.java | 52 ++++++++++++ .../src/test/resources/logback-test.xml | 6 ++ 5 files changed, 188 insertions(+), 46 deletions(-) create mode 100644 gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java create mode 100644 gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/TestAppender.java create mode 100644 gax-java/gax-httpjson/src/test/resources/logback-test.xml diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index 4d48d7f663..133b4dcc90 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -51,8 +51,8 @@ @InternalApi public class GrpcLoggingInterceptor implements ClientInterceptor { - private static final Logger logger = LoggingUtils.getLogger(GrpcLoggingInterceptor.class); - private static final Gson gson = new Gson(); + private static final Logger LOGGER = LoggingUtils.getLogger(GrpcLoggingInterceptor.class); + private static final Gson GSON = new Gson(); ClientCall.Listener currentListener; // expose for test setup @@ -66,7 +66,7 @@ public ClientCall interceptCall( @Override public void start(Listener responseListener, Metadata headers) { - logRequestInfo(method, logDataBuilder, logger); + logRequestInfo(method, logDataBuilder, LOGGER); recordRequestHeaders(headers, logDataBuilder); SimpleForwardingClientCallListener responseLoggingListener = new SimpleForwardingClientCallListener(responseListener) { @@ -124,65 +124,65 @@ void logRequestInfo( private void recordRequestHeaders(Metadata headers, LogData.Builder logDataBuilder) { try { - if (logger.isDebugEnabled()) { + if (LOGGER.isDebugEnabled()) { JsonObject requestHeaders = mapHeadersToJsonObject(headers); - logDataBuilder.requestHeaders(gson.toJson(requestHeaders)); + logDataBuilder.requestHeaders(GSON.toJson(requestHeaders)); } } catch (Exception e) { - logger.error("Error recording request headers", e); + LOGGER.error("Error recording request headers", e); } } void recordResponseHeaders(Metadata headers, LogData.Builder logDataBuilder) { try { - if (logger.isDebugEnabled()) { + if (LOGGER.isDebugEnabled()) { JsonObject responseHeaders = mapHeadersToJsonObject(headers); - logDataBuilder.responseHeaders(gson.toJson(responseHeaders)); + logDataBuilder.responseHeaders(GSON.toJson(responseHeaders)); } } catch (Exception e) { - logger.error("Error recording response headers", e); + LOGGER.error("Error recording response headers", e); } } void recordResponsePayload(RespT message, LogData.Builder logDataBuilder) { try { - if (logger.isDebugEnabled()) { - logDataBuilder.responsePayload(gson.toJsonTree(message)); + if (LOGGER.isDebugEnabled()) { + logDataBuilder.responsePayload(GSON.toJsonTree(message)); } } catch (Exception e) { - logger.error("Error recording response payload", e); + LOGGER.error("Error recording response payload", e); } } void logResponse(String statusCode, LogData.Builder logDataBuilder) { try { - if (logger.isInfoEnabled()) { + if (LOGGER.isInfoEnabled()) { logDataBuilder.responseStatus(statusCode); } - if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { + if (LOGGER.isInfoEnabled() && !LOGGER.isDebugEnabled()) { Map responseData = logDataBuilder.build().toMapResponse(); - LoggingUtils.logWithMDC(logger, Level.INFO, responseData, "Received Grpc response"); + LoggingUtils.logWithMDC(LOGGER, Level.INFO, responseData, "Received Grpc response"); } - if (logger.isDebugEnabled()) { + if (LOGGER.isDebugEnabled()) { Map responsedDetailsMap = logDataBuilder.build().toMapResponse(); - LoggingUtils.logWithMDC(logger, Level.DEBUG, responsedDetailsMap, "Received Grpc response"); + LoggingUtils.logWithMDC(LOGGER, Level.DEBUG, responsedDetailsMap, "Received Grpc response"); } } catch (Exception e) { - logger.error("Error logging request response", e); + LOGGER.error("Error logging request response", e); } } void logRequestDetails(RespT message, LogData.Builder logDataBuilder) { try { - if (logger.isDebugEnabled()) { - logDataBuilder.requestPayload(gson.toJson(message)); + if (LOGGER.isDebugEnabled()) { + logDataBuilder.requestPayload(GSON.toJson(message)); Map requestDetailsMap = logDataBuilder.build().toMapRequest(); LoggingUtils.logWithMDC( - logger, Level.DEBUG, requestDetailsMap, "Sending gRPC request: request payload"); + LOGGER, Level.DEBUG, requestDetailsMap, "Sending gRPC request: request payload"); } } catch (Exception e) { - logger.error("Error logging request details", e); + LOGGER.error("Error logging request details", e); } } diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java index fa4bcc366b..cb21e54056 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -46,8 +46,8 @@ @InternalApi public class HttpJsonLoggingInterceptor implements HttpJsonClientInterceptor { - private static final Logger logger = LoggingUtils.getLogger(HttpJsonLoggingInterceptor.class); - private static final Gson gson = new Gson(); + private static final Logger LOGGER = LoggingUtils.getLogger(HttpJsonLoggingInterceptor.class); + private static final Gson GSON = new Gson(); @Override public HttpJsonClientCall interceptCall( @@ -65,7 +65,7 @@ public HttpJsonClientCall interceptCall( public void start( HttpJsonClientCall.Listener responseListener, HttpJsonMetadata headers) { - logRequestInfo(method, endpoint, logDataBuilder); + logRequestInfo(method, endpoint, logDataBuilder, LOGGER); recordRequestHeaders(headers, logDataBuilder); Listener forwardingResponseListener = @@ -106,8 +106,11 @@ public void sendMessage(ReqT message) { // Helper methods for logging, // some duplications with grpc equivalent to avoid exposing as public method - private void logRequestInfo( - ApiMethodDescriptor method, String endpoint, LogData.Builder logDataBuilder) { + void logRequestInfo( + ApiMethodDescriptor method, + String endpoint, + LogData.Builder logDataBuilder, + Logger logger) { try { if (logger.isInfoEnabled()) { logDataBuilder @@ -127,70 +130,70 @@ private void logRequestInfo( private void recordRequestHeaders(HttpJsonMetadata headers, LogData.Builder logDataBuilder) { try { - if (logger.isDebugEnabled()) { + if (LOGGER.isDebugEnabled()) { JsonObject requestHeaders = new JsonObject(); headers .getHeaders() .forEach((key, value) -> requestHeaders.addProperty(key, value.toString())); - logDataBuilder.requestHeaders(gson.toJson(requestHeaders)); + logDataBuilder.requestHeaders(GSON.toJson(requestHeaders)); } } catch (Exception e) { - logger.error("Error recording request headers", e); + LOGGER.error("Error recording request headers", e); } } private void recordResponseHeaders( HttpJsonMetadata responseHeaders, LogData.Builder logDataBuilder) { try { - if (logger.isDebugEnabled()) { + if (LOGGER.isDebugEnabled()) { Map> map = new HashMap<>(); responseHeaders.getHeaders().forEach((key, value) -> map.put(key, (List) value)); - logDataBuilder.responseHeaders(gson.toJson(map)); + logDataBuilder.responseHeaders(GSON.toJson(map)); } } catch (Exception e) { - logger.error("Error recording response headers", e); + LOGGER.error("Error recording response headers", e); } } private void recordResponsePayload(RespT message, LogData.Builder logDataBuilder) { try { - if (logger.isDebugEnabled()) { - logDataBuilder.responsePayload(gson.toJsonTree(message)); + if (LOGGER.isDebugEnabled()) { + logDataBuilder.responsePayload(GSON.toJsonTree(message)); } } catch (Exception e) { - logger.error("Error recording response payload", e); + LOGGER.error("Error recording response payload", e); } } private void logResponse(int statusCode, LogData.Builder logDataBuilder) { try { - if (logger.isInfoEnabled()) { + if (LOGGER.isInfoEnabled()) { logDataBuilder.responseStatus(String.valueOf(statusCode)); } - if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { + if (LOGGER.isInfoEnabled() && !LOGGER.isDebugEnabled()) { Map responseData = logDataBuilder.build().toMapResponse(); - LoggingUtils.logWithMDC(logger, Level.INFO, responseData, "Received HTTP response"); + LoggingUtils.logWithMDC(LOGGER, Level.INFO, responseData, "Received HTTP response"); } - if (logger.isDebugEnabled()) { + if (LOGGER.isDebugEnabled()) { Map responsedDetailsMap = logDataBuilder.build().toMapResponse(); - LoggingUtils.logWithMDC(logger, Level.DEBUG, responsedDetailsMap, "Received HTTP response"); + LoggingUtils.logWithMDC(LOGGER, Level.DEBUG, responsedDetailsMap, "Received HTTP response"); } } catch (Exception e) { - logger.error("Error logging request response", e); + LOGGER.error("Error logging request response", e); } } private void logRequestDetails(RespT message, LogData.Builder logDataBuilder) { try { - if (logger.isDebugEnabled()) { - logDataBuilder.requestPayload(gson.toJson(message)); + if (LOGGER.isDebugEnabled()) { + logDataBuilder.requestPayload(GSON.toJson(message)); Map requestDetailsMap = logDataBuilder.build().toMapRequest(); LoggingUtils.logWithMDC( - logger, Level.DEBUG, requestDetailsMap, "Sending HTTP request: request payload"); + LOGGER, Level.DEBUG, requestDetailsMap, "Sending HTTP request: request payload"); } } catch (Exception e) { - logger.error("Error logging request details", e); + LOGGER.error("Error logging request details", e); } } } diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java new file mode 100644 index 0000000000..76546d9d1d --- /dev/null +++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.httpjson; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +import ch.qos.logback.classic.Level; +import com.google.api.gax.httpjson.ApiMethodDescriptor.MethodType; +import com.google.api.gax.logging.LogData; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class HttpJsonLoggingInterceptorTest { + + private static final Logger LOGGER = + LoggerFactory.getLogger(HttpJsonLoggingInterceptorTest.class); + + @SuppressWarnings("unchecked") + private static final ApiMethodDescriptor method = + ApiMethodDescriptor.newBuilder() + .setType(MethodType.UNARY) + .setRequestFormatter(mock(HttpRequestFormatter.class)) + .setRequestFormatter(mock(HttpRequestFormatter.class)) + .setFullMethodName("FakeClient/fake-method") + .setResponseParser(mock(HttpResponseParser.class)) + .build(); + + @Test + void testLogRequestInfo() { + + TestAppender testAppender = setupTestLogger(HttpJsonLoggingInterceptorTest.class); + HttpJsonLoggingInterceptor interceptor = new HttpJsonLoggingInterceptor(); + interceptor.logRequestInfo(method, "fake.endpoint", LogData.builder(), LOGGER); + + Assertions.assertEquals(1, testAppender.events.size()); + assertEquals(Level.INFO, testAppender.events.get(0).getLevel()); + assertEquals( + "{\"request.url\":\"fake.endpoint\",\"message\":\"Sending HTTP request\",\"rpcName\":\"FakeClient/fake-method\"}", + testAppender.events.get(0).getMessage()); + testAppender.stop(); + } + + private TestAppender setupTestLogger(Class clazz) { + TestAppender testAppender = new TestAppender(); + testAppender.start(); + Logger logger = LoggerFactory.getLogger(clazz); + ((ch.qos.logback.classic.Logger) logger).addAppender(testAppender); + return testAppender; + } +} diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/TestAppender.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/TestAppender.java new file mode 100644 index 0000000000..c89ebfb3b7 --- /dev/null +++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/TestAppender.java @@ -0,0 +1,52 @@ +/* + * Copyright 2024 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.google.api.gax.httpjson; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import java.util.ArrayList; +import java.util.List; + +/** Logback appender used to set up tests. */ +public class TestAppender extends AppenderBase { + public List events = new ArrayList<>(); + + @Override + protected void append(ILoggingEvent eventObject) { + // triggering Logback to capture the current MDC context and store it with the log event + eventObject.getMDCPropertyMap(); + events.add(eventObject); + } + + public void clearEvents() { + events.clear(); + } +} diff --git a/gax-java/gax-httpjson/src/test/resources/logback-test.xml b/gax-java/gax-httpjson/src/test/resources/logback-test.xml new file mode 100644 index 0000000000..8a6dc9b23d --- /dev/null +++ b/gax-java/gax-httpjson/src/test/resources/logback-test.xml @@ -0,0 +1,6 @@ + + + + + + From 7901d8a31c5d68d7736d55a25e7ce1ce9fdeed64 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Wed, 11 Dec 2024 10:01:40 -0500 Subject: [PATCH 40/45] add log response test to interceptors. --- .../api/gax/grpc/GrpcLoggingInterceptor.java | 18 +++++++++--------- .../gax/grpc/GrpcLoggingInterceptorTest.java | 16 +++++++++++++++- .../httpjson/HttpJsonLoggingInterceptor.java | 16 ++++++++-------- .../HttpJsonLoggingInterceptorTest.java | 15 +++++++++++++++ 4 files changed, 47 insertions(+), 18 deletions(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index 133b4dcc90..9756acaee1 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -85,7 +85,7 @@ public void onMessage(RespT message) { @Override public void onClose(Status status, Metadata trailers) { try { - logResponse(status.getCode().toString(), logDataBuilder); + logResponse(status, logDataBuilder, LOGGER); } finally { logDataBuilder = null; // release resource } @@ -154,22 +154,22 @@ void recordResponsePayload(RespT message, LogData.Builder logDataBuilder } } - void logResponse(String statusCode, LogData.Builder logDataBuilder) { + void logResponse(Status status, LogData.Builder logDataBuilder, Logger logger) { try { - if (LOGGER.isInfoEnabled()) { - logDataBuilder.responseStatus(statusCode); + if (logger.isInfoEnabled()) { + logDataBuilder.responseStatus(status.getCode().toString()); } - if (LOGGER.isInfoEnabled() && !LOGGER.isDebugEnabled()) { + if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { Map responseData = logDataBuilder.build().toMapResponse(); - LoggingUtils.logWithMDC(LOGGER, Level.INFO, responseData, "Received Grpc response"); + LoggingUtils.logWithMDC(logger, Level.INFO, responseData, "Received Grpc response"); } - if (LOGGER.isDebugEnabled()) { + if (logger.isDebugEnabled()) { Map responsedDetailsMap = logDataBuilder.build().toMapResponse(); - LoggingUtils.logWithMDC(LOGGER, Level.DEBUG, responsedDetailsMap, "Received Grpc response"); + LoggingUtils.logWithMDC(logger, Level.DEBUG, responsedDetailsMap, "Received Grpc response"); } } catch (Exception e) { - LOGGER.error("Error logging request response", e); + logger.error("Error logging request response", e); } } diff --git a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java index 423221db2d..e15a69db2d 100644 --- a/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java +++ b/gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLoggingInterceptorTest.java @@ -115,7 +115,7 @@ void testInterceptor_responseListener() { // --- Verify that the response listener's methods were called --- verify(interceptor).recordResponseHeaders(eq(responseHeaders), any(LogData.Builder.class)); verify(interceptor).recordResponsePayload(any(), any(LogData.Builder.class)); - verify(interceptor).logResponse(eq(status.getCode().toString()), any(LogData.Builder.class)); + verify(interceptor).logResponse(eq(status), any(LogData.Builder.class), any(Logger.class)); } @Test @@ -133,6 +133,20 @@ void testLogRequestInfo() { testAppender.stop(); } + @Test + void TestLogResponseInfo() { + TestAppender testAppender = setupTestLogger(GrpcLoggingInterceptorTest.class); + GrpcLoggingInterceptor interceptor = new GrpcLoggingInterceptor(); + interceptor.logResponse(Status.CANCELLED, LogData.builder(), LOGGER); + + Assertions.assertEquals(1, testAppender.events.size()); + assertEquals(Level.INFO, testAppender.events.get(0).getLevel()); + assertEquals( + "{\"response.status\":\"CANCELLED\",\"message\":\"Received Grpc response\"}", + testAppender.events.get(0).getMessage()); + testAppender.stop(); + } + private TestAppender setupTestLogger(Class clazz) { TestAppender testAppender = new TestAppender(); testAppender.start(); diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java index cb21e54056..9b44a971c8 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -86,7 +86,7 @@ public void onMessage(RespT message) { @Override public void onClose(int statusCode, HttpJsonMetadata trailers) { try { - logResponse(statusCode, logDataBuilder); + logResponse(statusCode, logDataBuilder, LOGGER); } finally { logDataBuilder = null; // release resource } @@ -166,21 +166,21 @@ private void recordResponsePayload(RespT message, LogData.Builder logDat } } - private void logResponse(int statusCode, LogData.Builder logDataBuilder) { + void logResponse(int statusCode, LogData.Builder logDataBuilder, Logger logger) { try { - if (LOGGER.isInfoEnabled()) { + if (logger.isInfoEnabled()) { logDataBuilder.responseStatus(String.valueOf(statusCode)); } - if (LOGGER.isInfoEnabled() && !LOGGER.isDebugEnabled()) { + if (logger.isInfoEnabled() && !logger.isDebugEnabled()) { Map responseData = logDataBuilder.build().toMapResponse(); - LoggingUtils.logWithMDC(LOGGER, Level.INFO, responseData, "Received HTTP response"); + LoggingUtils.logWithMDC(logger, Level.INFO, responseData, "Received HTTP response"); } - if (LOGGER.isDebugEnabled()) { + if (logger.isDebugEnabled()) { Map responsedDetailsMap = logDataBuilder.build().toMapResponse(); - LoggingUtils.logWithMDC(LOGGER, Level.DEBUG, responsedDetailsMap, "Received HTTP response"); + LoggingUtils.logWithMDC(logger, Level.DEBUG, responsedDetailsMap, "Received HTTP response"); } } catch (Exception e) { - LOGGER.error("Error logging request response", e); + logger.error("Error logging request response", e); } } diff --git a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java index 76546d9d1d..0966407fba 100644 --- a/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java +++ b/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptorTest.java @@ -71,6 +71,21 @@ void testLogRequestInfo() { testAppender.stop(); } + @Test + void testLogResponseInfo() { + + TestAppender testAppender = setupTestLogger(HttpJsonLoggingInterceptorTest.class); + HttpJsonLoggingInterceptor interceptor = new HttpJsonLoggingInterceptor(); + interceptor.logResponse(200, LogData.builder(), LOGGER); + + Assertions.assertEquals(1, testAppender.events.size()); + assertEquals(Level.INFO, testAppender.events.get(0).getLevel()); + assertEquals( + "{\"response.status\":\"200\",\"message\":\"Received HTTP response\"}", + testAppender.events.get(0).getMessage()); + testAppender.stop(); + } + private TestAppender setupTestLogger(Class clazz) { TestAppender testAppender = new TestAppender(); testAppender.start(); From 20f9f5a7334fd480bd8ea11746212612cefe4d0c Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Wed, 11 Dec 2024 10:09:45 -0500 Subject: [PATCH 41/45] remove redundant code. --- .../com/google/api/gax/grpc/GrpcLoggingInterceptor.java | 7 +------ .../api/gax/httpjson/HttpJsonLoggingInterceptor.java | 6 +----- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index 9756acaee1..4950478158 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -84,11 +84,7 @@ public void onMessage(RespT message) { @Override public void onClose(Status status, Metadata trailers) { - try { - logResponse(status, logDataBuilder, LOGGER); - } finally { - logDataBuilder = null; // release resource - } + logResponse(status, logDataBuilder, LOGGER); super.onClose(status, trailers); } }; @@ -156,7 +152,6 @@ void recordResponsePayload(RespT message, LogData.Builder logDataBuilder void logResponse(Status status, LogData.Builder logDataBuilder, Logger logger) { try { - if (logger.isInfoEnabled()) { logDataBuilder.responseStatus(status.getCode().toString()); } diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java index 9b44a971c8..cdf4521f9c 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -85,11 +85,7 @@ public void onMessage(RespT message) { @Override public void onClose(int statusCode, HttpJsonMetadata trailers) { - try { - logResponse(statusCode, logDataBuilder, LOGGER); - } finally { - logDataBuilder = null; // release resource - } + logResponse(statusCode, logDataBuilder, LOGGER); super.onClose(statusCode, trailers); } }; From 172701dfb642312366b693a4be37cdf8b68b5efb Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Wed, 11 Dec 2024 10:19:33 -0500 Subject: [PATCH 42/45] add slf4j and test dep to dependencies.properties. --- gax-java/dependencies.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gax-java/dependencies.properties b/gax-java/dependencies.properties index 12224344a4..16690f92e6 100644 --- a/gax-java/dependencies.properties +++ b/gax-java/dependencies.properties @@ -76,6 +76,7 @@ maven.com_google_http_client_google_http_client_gson=com.google.http-client:goog maven.org_codehaus_mojo_animal_sniffer_annotations=org.codehaus.mojo:animal-sniffer-annotations:1.24 maven.javax_annotation_javax_annotation_api=javax.annotation:javax.annotation-api:1.3.2 maven.org_graalvm_sdk=org.graalvm.sdk:graal-sdk:22.3.5 +maven_org_slf4j_slf4j_api=org.slf4j:slf4j-api:2.0.16 # Testing maven artifacts maven.junit_junit=junit:junit:4.13.2 @@ -88,3 +89,5 @@ maven.net_bytebuddy_byte_buddy=net.bytebuddy:byte-buddy:1.15.10 maven.org_objenesis_objenesis=org.objenesis:objenesis:2.6 maven.org_junit_jupiter_junit_jupiter_api=org.junit.jupiter:junit-jupiter-api:5.11.3 maven.org_junit_jupiter_junit_jupiter_params=org.junit.jupiter:junit-jupiter-params:5.11.3 +maven.ch_qos_logback_logback_classic=ch.qos.logback:logback-classic:1.3.14 +maven.ch_qos_logback_logback_core=ch.qos.logback:logback-core:1.3.14 \ No newline at end of file From a8d2f207da99876b72795ef0868ec53d836a5687 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Wed, 11 Dec 2024 13:05:38 -0500 Subject: [PATCH 43/45] make requestPayload also as JsonElement in LogData. --- .../com/google/api/gax/grpc/GrpcLoggingInterceptor.java | 2 +- .../google/api/gax/httpjson/HttpJsonLoggingInterceptor.java | 2 +- .../src/main/java/com/google/api/gax/logging/LogData.java | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java index 4950478158..2a9953a91b 100644 --- a/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java +++ b/gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/GrpcLoggingInterceptor.java @@ -171,7 +171,7 @@ void logResponse(Status status, LogData.Builder logDataBuilder, Logger logger) { void logRequestDetails(RespT message, LogData.Builder logDataBuilder) { try { if (LOGGER.isDebugEnabled()) { - logDataBuilder.requestPayload(GSON.toJson(message)); + logDataBuilder.requestPayload(GSON.toJsonTree(message)); Map requestDetailsMap = logDataBuilder.build().toMapRequest(); LoggingUtils.logWithMDC( LOGGER, Level.DEBUG, requestDetailsMap, "Sending gRPC request: request payload"); diff --git a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java index cdf4521f9c..a465ab471a 100644 --- a/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java +++ b/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpJsonLoggingInterceptor.java @@ -183,7 +183,7 @@ void logResponse(int statusCode, LogData.Builder logDataBuilder, Logger logger) private void logRequestDetails(RespT message, LogData.Builder logDataBuilder) { try { if (LOGGER.isDebugEnabled()) { - logDataBuilder.requestPayload(GSON.toJson(message)); + logDataBuilder.requestPayload(GSON.toJsonTree(message)); Map requestDetailsMap = logDataBuilder.build().toMapRequest(); LoggingUtils.logWithMDC( LOGGER, Level.DEBUG, requestDetailsMap, "Sending HTTP request: request payload"); diff --git a/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java index db4767eb20..57751dc6d0 100644 --- a/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java +++ b/gax-java/gax/src/main/java/com/google/api/gax/logging/LogData.java @@ -56,7 +56,7 @@ public abstract class LogData { public abstract String requestHeaders(); @Nullable - public abstract String requestPayload(); + public abstract JsonElement requestPayload(); @Nullable public abstract String responseStatus(); @@ -87,7 +87,7 @@ public abstract static class Builder { public abstract Builder requestHeaders(String requestHeaders); - public abstract Builder requestPayload(String requestPayload); + public abstract Builder requestPayload(JsonElement requestPayload); public abstract Builder responseStatus(String responseStatus); @@ -119,7 +119,7 @@ public Map toMapRequest() { map.put("request.headers", requestHeaders()); } if (requestPayload() != null) { - map.put("request.payload", requestPayload()); + map.put("request.payload", gson.toJson(requestPayload())); } if (httpMethod() != null) { map.put("request.method", httpMethod()); From df97834e51882bf0f9ceb946248785d07988b1b6 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Thu, 12 Dec 2024 17:38:01 -0500 Subject: [PATCH 44/45] fix typo in dependencies.properties. --- gax-java/dependencies.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gax-java/dependencies.properties b/gax-java/dependencies.properties index 16690f92e6..e1a3acdcc1 100644 --- a/gax-java/dependencies.properties +++ b/gax-java/dependencies.properties @@ -76,7 +76,7 @@ maven.com_google_http_client_google_http_client_gson=com.google.http-client:goog maven.org_codehaus_mojo_animal_sniffer_annotations=org.codehaus.mojo:animal-sniffer-annotations:1.24 maven.javax_annotation_javax_annotation_api=javax.annotation:javax.annotation-api:1.3.2 maven.org_graalvm_sdk=org.graalvm.sdk:graal-sdk:22.3.5 -maven_org_slf4j_slf4j_api=org.slf4j:slf4j-api:2.0.16 +maven.org_slf4j_slf4j_api=org.slf4j:slf4j-api:2.0.16 # Testing maven artifacts maven.junit_junit=junit:junit:4.13.2 From 32dbd0eeba8aa5303a508d3f45a75c2f59809df1 Mon Sep 17 00:00:00 2001 From: Min Zhu Date: Thu, 12 Dec 2024 17:45:10 -0500 Subject: [PATCH 45/45] add test assertion to verify logger not enabled at levels when env not set. also some cleanups. --- .../api/gax/logging/LoggingUtilsTest.java | 60 +++++-------------- 1 file changed, 15 insertions(+), 45 deletions(-) diff --git a/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java b/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java index 722a2d5a22..d53bd0bc02 100644 --- a/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java +++ b/gax-java/gax/src/test/java/com/google/api/gax/logging/LoggingUtilsTest.java @@ -30,24 +30,18 @@ package com.google.api.gax.logging; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import ch.qos.logback.classic.LoggerContext; -import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.ConsoleAppender; import com.google.api.gax.logging.LoggingUtils.LoggerFactoryProvider; import com.google.api.gax.rpc.internal.EnvironmentProvider; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.slf4j.ILoggerFactory; @@ -60,32 +54,6 @@ class LoggingUtilsTest { private static final Logger LOGGER = LoggerFactory.getLogger(LoggingUtilsTest.class); private EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class); - @BeforeEach - void setup() { - EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class); - - // need to setup a ConsoleAppender and attach to root logger because TestAppender - // does not correctly capture MDC info - LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); - - PatternLayoutEncoder patternLayoutEncoder = new PatternLayoutEncoder(); - patternLayoutEncoder.setPattern("%-4relative [%thread] %-5level %logger{35} - %msg%n"); - patternLayoutEncoder.setContext(loggerContext); - - patternLayoutEncoder.start(); - - ConsoleAppender consoleAppender = new ConsoleAppender<>(); - consoleAppender.setEncoder(patternLayoutEncoder); - - consoleAppender.setContext(loggerContext); - consoleAppender.setName("CONSOLE"); - - consoleAppender.start(); - - ch.qos.logback.classic.Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME); - rootLogger.addAppender(consoleAppender); - } - @AfterEach void tearDown() { LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); @@ -97,8 +65,8 @@ void testGetLogger_loggingEnabled_slf4jBindingPresent() { Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("true"); LoggingUtils.setEnvironmentProvider(envProvider); Logger logger = LoggingUtils.getLogger(LoggingUtilsTest.class); - assertTrue(logger instanceof org.slf4j.Logger); - assertNotEquals(logger.getClass(), NOPLogger.class); + Assertions.assertInstanceOf(Logger.class, logger); + Assertions.assertNotEquals(NOPLogger.class, logger.getClass()); } @Test @@ -107,7 +75,9 @@ void testGetLogger_loggingDisabled() { LoggingUtils.setEnvironmentProvider(envProvider); Logger logger = LoggingUtils.getLogger(LoggingUtilsTest.class); - assertEquals(NOPLogger.class, logger.getClass()); + Assertions.assertEquals(NOPLogger.class, logger.getClass()); + Assertions.assertFalse(logger.isInfoEnabled()); + Assertions.assertFalse(logger.isDebugEnabled()); } @Test @@ -125,26 +95,26 @@ void testGetLogger_loggingEnabled_noBinding() { Logger logger = LoggingUtils.getLogger(LoggingUtilsTest.class, mockLoggerFactoryProvider); // Assert that the returned logger is a NOPLogger - assertTrue(logger instanceof org.slf4j.helpers.NOPLogger); + Assertions.assertInstanceOf(NOPLogger.class, logger); } @Test void testIsLoggingEnabled_true() { Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("true"); LoggingUtils.setEnvironmentProvider(envProvider); - assertTrue(LoggingUtils.isLoggingEnabled()); + Assertions.assertTrue(LoggingUtils.isLoggingEnabled()); Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("TRUE"); LoggingUtils.setEnvironmentProvider(envProvider); - assertTrue(LoggingUtils.isLoggingEnabled()); + Assertions.assertTrue(LoggingUtils.isLoggingEnabled()); Mockito.when(envProvider.getenv(LoggingUtils.GOOGLE_SDK_JAVA_LOGGING)).thenReturn("True"); LoggingUtils.setEnvironmentProvider(envProvider); - assertTrue(LoggingUtils.isLoggingEnabled()); + Assertions.assertTrue(LoggingUtils.isLoggingEnabled()); } @Test void testIsLoggingEnabled_defaultToFalse() { LoggingUtils.setEnvironmentProvider(envProvider); - assertFalse(LoggingUtils.isLoggingEnabled()); + Assertions.assertFalse(LoggingUtils.isLoggingEnabled()); } private TestAppender setupTestLogger(Class clazz) { @@ -156,20 +126,20 @@ private TestAppender setupTestLogger(Class clazz) { } @Test - public void testLogWithMDC_slf4jLogger() { + void testLogWithMDC_slf4jLogger() { TestAppender testAppender = setupTestLogger(LoggingUtilsTest.class); Map contextMap = new HashMap<>(); contextMap.put("key", "value"); LoggingUtils.logWithMDC(LOGGER, org.slf4j.event.Level.DEBUG, contextMap, "test message"); - assertEquals(1, testAppender.events.size()); - assertEquals( + Assertions.assertEquals(1, testAppender.events.size()); + Assertions.assertEquals( "{\"message\":\"test message\",\"key\":\"value\"}", testAppender.events.get(0).getFormattedMessage()); // Verify MDC content ILoggingEvent event = testAppender.events.get(0); - assertEquals("value", event.getMDCPropertyMap().get("key")); + Assertions.assertEquals("value", event.getMDCPropertyMap().get("key")); testAppender.stop(); } }