diff --git a/src/main/java/org/patinanetwork/codebloom/config/GrpcClientConfiguration.java b/src/main/java/org/patinanetwork/codebloom/config/GrpcClientConfiguration.java index 916878aea..c87ecf9bd 100644 --- a/src/main/java/org/patinanetwork/codebloom/config/GrpcClientConfiguration.java +++ b/src/main/java/org/patinanetwork/codebloom/config/GrpcClientConfiguration.java @@ -1,12 +1,12 @@ package org.patinanetwork.codebloom.config; -import io.grpc.CallOptions; -import io.grpc.Channel; -import io.grpc.ClientCall; import io.grpc.ClientInterceptor; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; -import io.grpc.MethodDescriptor; +import io.micrometer.core.instrument.binder.grpc.ObservationGrpcClientInterceptor; +import io.micrometer.observation.ObservationRegistry; +import java.time.Duration; +import lombok.AllArgsConstructor; import org.patinanetwork.codebloom.grpc.leetcode.auth.v1.LeetcodeAuthStealerServiceGrpc; import org.patinanetwork.codebloom.grpc.leetcode.auth.v1.LeetcodeAuthStealerServiceGrpc.LeetcodeAuthStealerServiceBlockingStub; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -14,23 +14,47 @@ import org.springframework.context.annotation.Configuration; @Configuration +@AllArgsConstructor @EnableConfigurationProperties(LeetcodeAuthStealerGrpcClientConfiguration.class) public class GrpcClientConfiguration { + private final ObservationRegistry observationRegistry; + + @Bean + GrpcRequestIdInterceptor grpcRequestIdInterceptor() { + return new GrpcRequestIdInterceptor(); + } + @Bean - ManagedChannel lassServiceChannel(final LeetcodeAuthStealerGrpcClientConfiguration authStealerProps) { - var builder = ManagedChannelBuilder.forTarget(authStealerProps.host()).intercept(new ClientInterceptor() { - @Override - public ClientCall interceptCall( - final MethodDescriptor method, final CallOptions options, final Channel next) { - return next.newCall(method, options.withDeadlineAfter(authStealerProps.timeout())); - } - }); + ObservationGrpcClientInterceptor grpcObservationInterceptor() { + return new ObservationGrpcClientInterceptor(observationRegistry); + } + + private ClientInterceptor[] interceptors( + Duration timeout, + GrpcRequestIdInterceptor requestIdInterceptor, + ObservationGrpcClientInterceptor observationInterceptor) { + + return new ClientInterceptor[] { + new GrpcClientTimeoutInterceptor(timeout), requestIdInterceptor, observationInterceptor + }; + } + + @Bean + ManagedChannel lassServiceChannel( + LeetcodeAuthStealerGrpcClientConfiguration authStealerProps, + GrpcRequestIdInterceptor requestIdInterceptor, + ObservationGrpcClientInterceptor observationInterceptor) { + + var builder = ManagedChannelBuilder.forTarget(authStealerProps.host()) + .intercept(interceptors(authStealerProps.timeout(), requestIdInterceptor, observationInterceptor)); + return (authStealerProps.tls() ? builder.useTransportSecurity() : builder.usePlaintext()).build(); } @Bean - LeetcodeAuthStealerServiceBlockingStub lassServiceBlockingStub(final ManagedChannel lassServiceChannel) { + LeetcodeAuthStealerServiceBlockingStub lassServiceBlockingStub(ManagedChannel lassServiceChannel) { + return LeetcodeAuthStealerServiceGrpc.newBlockingStub(lassServiceChannel); } } diff --git a/src/main/java/org/patinanetwork/codebloom/config/GrpcClientTimeoutInterceptor.java b/src/main/java/org/patinanetwork/codebloom/config/GrpcClientTimeoutInterceptor.java new file mode 100644 index 000000000..3a3c35d2f --- /dev/null +++ b/src/main/java/org/patinanetwork/codebloom/config/GrpcClientTimeoutInterceptor.java @@ -0,0 +1,21 @@ +package org.patinanetwork.codebloom.config; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.MethodDescriptor; +import java.time.Duration; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class GrpcClientTimeoutInterceptor implements ClientInterceptor { + + private final Duration timeout; + + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + return next.newCall(method, callOptions.withDeadlineAfter(timeout)); + } +} diff --git a/src/main/java/org/patinanetwork/codebloom/config/GrpcRequestIdInterceptor.java b/src/main/java/org/patinanetwork/codebloom/config/GrpcRequestIdInterceptor.java new file mode 100644 index 000000000..311b10c95 --- /dev/null +++ b/src/main/java/org/patinanetwork/codebloom/config/GrpcRequestIdInterceptor.java @@ -0,0 +1,35 @@ +package org.patinanetwork.codebloom.config; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import org.slf4j.MDC; + +public class GrpcRequestIdInterceptor implements ClientInterceptor { + + private static final Metadata.Key REQUEST_ID_KEY = + Metadata.Key.of("x-request-id", Metadata.ASCII_STRING_MARSHALLER); + + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + + return new ForwardingClientCall.SimpleForwardingClientCall<>(next.newCall(method, callOptions)) { + + @Override + public void start(Listener listener, Metadata headers) { + String requestId = MDC.get("requestId"); + + if (requestId != null && !headers.containsKey(REQUEST_ID_KEY)) { + headers.put(REQUEST_ID_KEY, requestId); + } + + super.start(listener, headers); + } + }; + } +} diff --git a/src/main/java/org/patinanetwork/codebloom/utilities/RequestIdFilter.java b/src/main/java/org/patinanetwork/codebloom/utilities/RequestIdFilter.java new file mode 100644 index 000000000..344039607 --- /dev/null +++ b/src/main/java/org/patinanetwork/codebloom/utilities/RequestIdFilter.java @@ -0,0 +1,34 @@ +package org.patinanetwork.codebloom.utilities; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.UUID; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +public class RequestIdFilter extends OncePerRequestFilter { + + private static final String HEADER = "X-Request-Id"; + private static final String MDC_KEY = "requestId"; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + String requestId = UUID.randomUUID().toString(); + + try { + MDC.put(MDC_KEY, requestId); + response.setHeader(HEADER, requestId); + + filterChain.doFilter(request, response); + } finally { + MDC.remove(MDC_KEY); + } + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 4c5f0d11a..9d0a8dc1c 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,7 @@ +logging: + pattern: + console: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] [%X{requestId}] %logger{36} - %msg%n" + spring: application: name: codebloom