diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/GrpcPlainRequestLoggingInterceptor.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/GrpcPlainRequestLoggingInterceptor.java new file mode 100644 index 000000000..7116108d7 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/GrpcPlainRequestLoggingInterceptor.java @@ -0,0 +1,154 @@ +/* + * 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 + * + * http://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.cloud.storage.it; + +import com.google.api.gax.grpc.GrpcInterceptorProvider; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.ByteString; +import com.google.protobuf.MessageOrBuilder; +import com.google.storage.v2.WriteObjectRequest; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall.SimpleForwardingClientCall; +import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Status; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Client side interceptor which will log gRPC request, response and status messages in plain text, + * rather than the byte encoded text io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler does. + * + *

This interceptor does not include the other useful information that NettyClientHandler + * provides such as headers, method names, peers etc. + */ +public final class GrpcPlainRequestLoggingInterceptor implements ClientInterceptor { + + private static final Logger LOGGER = + Logger.getLogger(GrpcPlainRequestLoggingInterceptor.class.getName()); + + private static final GrpcPlainRequestLoggingInterceptor INSTANCE = + new GrpcPlainRequestLoggingInterceptor(); + + private GrpcPlainRequestLoggingInterceptor() {} + + public static GrpcPlainRequestLoggingInterceptor getInstance() { + return INSTANCE; + } + + public static GrpcInterceptorProvider getInterceptorProvider() { + return InterceptorProvider.INSTANCE; + } + + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + ClientCall call = next.newCall(method, callOptions); + return new SimpleForwardingClientCall(call) { + @Override + public void start(Listener responseListener, Metadata headers) { + SimpleForwardingClientCallListener listener = + new SimpleForwardingClientCallListener(responseListener) { + @Override + public void onMessage(RespT message) { + LOGGER.log( + Level.CONFIG, + () -> + String.format( + "<<< %s{%n%s}", message.getClass().getSimpleName(), fmtProto(message))); + super.onMessage(message); + } + + @Override + public void onClose(Status status, Metadata trailers) { + LOGGER.log( + Level.CONFIG, + () -> + String.format( + "<<< status = %s, trailers = %s", + status.toString(), trailers.toString())); + super.onClose(status, trailers); + } + }; + super.start(listener, headers); + } + + @Override + public void sendMessage(ReqT message) { + LOGGER.log( + Level.CONFIG, + () -> + String.format( + ">>> %s{%n%s}", message.getClass().getSimpleName(), fmtProto(message))); + super.sendMessage(message); + } + }; + } + + @NonNull + static String fmtProto(@NonNull Object obj) { + if (obj instanceof WriteObjectRequest) { + return fmtProto((WriteObjectRequest) obj); + } else if (obj instanceof MessageOrBuilder) { + return fmtProto((MessageOrBuilder) obj); + } else { + return obj.toString(); + } + } + + @NonNull + static String fmtProto(@NonNull final MessageOrBuilder msg) { + return msg.toString(); + } + + @NonNull + static String fmtProto(@NonNull WriteObjectRequest msg) { + if (msg.hasChecksummedData()) { + ByteString content = msg.getChecksummedData().getContent(); + if (content.size() > 20) { + WriteObjectRequest.Builder b = msg.toBuilder(); + ByteString snip = ByteString.copyFromUtf8(String.format("", content.size())); + ByteString trim = content.substring(0, 20).concat(snip); + b.getChecksummedDataBuilder().setContent(trim); + + return b.build().toString(); + } + } + return msg.toString(); + } + + private static final class InterceptorProvider implements GrpcInterceptorProvider { + private static final InterceptorProvider INSTANCE = new InterceptorProvider(); + + private final List interceptors; + + private InterceptorProvider() { + this.interceptors = ImmutableList.of(GrpcPlainRequestLoggingInterceptor.INSTANCE); + } + + @Override + public List getInterceptors() { + return interceptors; + } + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/BackendResources.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/BackendResources.java index 5f307c761..8aab9ba66 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/BackendResources.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/BackendResources.java @@ -26,6 +26,7 @@ import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; import com.google.cloud.storage.TransportCompatibility.Transport; +import com.google.cloud.storage.it.GrpcPlainRequestLoggingInterceptor; import com.google.cloud.storage.it.runner.annotations.Backend; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; @@ -119,12 +120,17 @@ static BackendResources of(Backend backend) { new StorageInstance( backend == Backend.TEST_BENCH ? StorageOptions.grpc() + .setGrpcInterceptorProvider( + GrpcPlainRequestLoggingInterceptor.getInterceptorProvider()) .setCredentials(NoCredentials.getInstance()) // TODO: improve this .setHost(Registry.getInstance().testBench().getGRPCBaseUri()) .setProjectId("test-project-id") .build() - : StorageOptions.grpc().build(), + : StorageOptions.grpc() + .setGrpcInterceptorProvider( + GrpcPlainRequestLoggingInterceptor.getInterceptorProvider()) + .build(), protectedBucketNames)); TestRunScopedInstance bucket = TestRunScopedInstance.of(