• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.gcp.observability;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static org.mockito.ArgumentMatchers.anyString;
21 import static org.mockito.ArgumentMatchers.eq;
22 import static org.mockito.Mockito.mock;
23 import static org.mockito.Mockito.spy;
24 import static org.mockito.Mockito.times;
25 import static org.mockito.Mockito.verify;
26 import static org.mockito.Mockito.verifyNoInteractions;
27 import static org.mockito.Mockito.when;
28 
29 import com.google.common.collect.ImmutableMap;
30 import io.grpc.ManagedChannelBuilder;
31 import io.grpc.Server;
32 import io.grpc.ServerBuilder;
33 import io.grpc.StaticTestingClassLoader;
34 import io.grpc.gcp.observability.interceptors.ConfigFilterHelper;
35 import io.grpc.gcp.observability.interceptors.ConfigFilterHelper.FilterParams;
36 import io.grpc.gcp.observability.interceptors.InternalLoggingChannelInterceptor;
37 import io.grpc.gcp.observability.interceptors.InternalLoggingServerInterceptor;
38 import io.grpc.gcp.observability.interceptors.LogHelper;
39 import io.grpc.gcp.observability.logging.GcpLogSink;
40 import io.grpc.gcp.observability.logging.Sink;
41 import io.grpc.gcp.observability.logging.TraceLoggingHelper;
42 import io.grpc.observabilitylog.v1.GrpcLogRecord;
43 import io.grpc.testing.GrpcCleanupRule;
44 import io.grpc.testing.protobuf.SimpleServiceGrpc;
45 import io.opencensus.trace.SpanContext;
46 import java.io.IOException;
47 import java.util.Collections;
48 import java.util.regex.Pattern;
49 import org.junit.ClassRule;
50 import org.junit.Ignore;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 import org.junit.runners.JUnit4;
54 import org.mockito.AdditionalMatchers;
55 import org.mockito.ArgumentCaptor;
56 import org.mockito.ArgumentMatchers;
57 import org.mockito.Mockito;
58 
59 @RunWith(JUnit4.class)
60 public class LoggingTest {
61 
62   @ClassRule
63   public static final GrpcCleanupRule cleanupRule = new GrpcCleanupRule();
64 
65   private static final String PROJECT_ID = "PROJECT";
66   private static final ImmutableMap<String, String> CUSTOM_TAGS = ImmutableMap.of(
67       "KEY1", "Value1",
68       "KEY2", "VALUE2");
69 
70   private final StaticTestingClassLoader classLoader =
71       new StaticTestingClassLoader(getClass().getClassLoader(), Pattern.compile("io\\.grpc\\..*"));
72 
73   /**
74    * Cloud logging test using GlobalInterceptors.
75    *
76    * <p> Ignoring test, because it calls external Cloud Logging APIs.
77    * To test cloud logging setup locally,
78    * 1. Set up Cloud auth credentials
79    * 2. Assign permissions to service account to write logs to project specified by
80    * variable PROJECT_ID
81    * 3. Comment @Ignore annotation
82    * 4. This test is expected to pass when ran with above setup. This has been verified manually.
83    * </p>
84    */
85   @Ignore
86   @Test
clientServer_interceptorCalled_logAlways()87   public void clientServer_interceptorCalled_logAlways() throws Exception {
88     Class<?> runnable =
89         classLoader.loadClass(LoggingTest.StaticTestingClassEndtoEndLogging.class.getName());
90     ((Runnable) runnable.getDeclaredConstructor().newInstance()).run();
91   }
92 
93   @Test
clientServer_interceptorCalled_logNever()94   public void clientServer_interceptorCalled_logNever() throws Exception {
95     Class<?> runnable =
96         classLoader.loadClass(LoggingTest.StaticTestingClassLogNever.class.getName());
97     ((Runnable) runnable.getDeclaredConstructor().newInstance()).run();
98   }
99 
100   @Test
clientServer_interceptorCalled_logEvents_usingMockSink()101   public void clientServer_interceptorCalled_logEvents_usingMockSink() throws Exception {
102     Class<?> runnable =
103         classLoader.loadClass(StaticTestingClassLogEventsUsingMockSink.class.getName());
104     ((Runnable) runnable.getDeclaredConstructor().newInstance()).run();
105   }
106 
107   // UsedReflectively
108   public static final class StaticTestingClassEndtoEndLogging implements Runnable {
109 
110     @Override
run()111     public void run() {
112       ObservabilityConfig config = mock(ObservabilityConfig.class);
113       when(config.getCustomTags()).thenReturn(CUSTOM_TAGS);
114       Sink sink =
115           new GcpLogSink(
116               PROJECT_ID, config, Collections.emptySet(),
117               mock(TraceLoggingHelper.class));
118       LogHelper spyLogHelper = spy(new LogHelper(sink));
119       ConfigFilterHelper mockFilterHelper = mock(ConfigFilterHelper.class);
120       InternalLoggingChannelInterceptor.Factory channelInterceptorFactory =
121           new InternalLoggingChannelInterceptor.FactoryImpl(spyLogHelper, mockFilterHelper);
122       InternalLoggingServerInterceptor.Factory serverInterceptorFactory =
123           new InternalLoggingServerInterceptor.FactoryImpl(spyLogHelper, mockFilterHelper);
124 
125       when(config.isEnableCloudLogging()).thenReturn(true);
126       FilterParams logAlwaysFilterParams = FilterParams.create(true, 1024, 10);
127       when(mockFilterHelper.logRpcMethod(anyString(), eq(true)))
128           .thenReturn(logAlwaysFilterParams);
129       when(mockFilterHelper.logRpcMethod(anyString(), eq(false)))
130           .thenReturn(logAlwaysFilterParams);
131 
132       try (GcpObservability unused =
133           GcpObservability.grpcInit(
134               sink, config, channelInterceptorFactory, serverInterceptorFactory)) {
135         Server server =
136             ServerBuilder.forPort(0)
137                 .addService(new ObservabilityTestHelper.SimpleServiceImpl())
138                 .build()
139                 .start();
140         int port = cleanupRule.register(server).getPort();
141         SimpleServiceGrpc.SimpleServiceBlockingStub stub =
142             SimpleServiceGrpc.newBlockingStub(
143                 cleanupRule.register(
144                     ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build()));
145         assertThat(ObservabilityTestHelper.makeUnaryRpcViaClientStub("buddy", stub))
146             .isEqualTo("Hello buddy");
147         assertThat(Mockito.mockingDetails(spyLogHelper).getInvocations().size()).isGreaterThan(11);
148       } catch (IOException e) {
149         throw new AssertionError("Exception while testing logging", e);
150       }
151     }
152   }
153 
154   public static final class StaticTestingClassLogNever implements Runnable {
155 
156     @Override
run()157     public void run() {
158       Sink mockSink = mock(GcpLogSink.class);
159       ObservabilityConfig config = mock(ObservabilityConfig.class);
160       LogHelper spyLogHelper = spy(new LogHelper(mockSink));
161       ConfigFilterHelper mockFilterHelper = mock(ConfigFilterHelper.class);
162       InternalLoggingChannelInterceptor.Factory channelInterceptorFactory =
163           new InternalLoggingChannelInterceptor.FactoryImpl(spyLogHelper, mockFilterHelper);
164       InternalLoggingServerInterceptor.Factory serverInterceptorFactory =
165           new InternalLoggingServerInterceptor.FactoryImpl(spyLogHelper, mockFilterHelper);
166 
167       when(config.isEnableCloudLogging()).thenReturn(true);
168       FilterParams logNeverFilterParams = FilterParams.create(false, 0, 0);
169       when(mockFilterHelper.logRpcMethod(anyString(), eq(true)))
170           .thenReturn(logNeverFilterParams);
171       when(mockFilterHelper.logRpcMethod(anyString(), eq(false)))
172           .thenReturn(logNeverFilterParams);
173 
174       try (GcpObservability unused =
175           GcpObservability.grpcInit(
176               mockSink, config, channelInterceptorFactory, serverInterceptorFactory)) {
177         Server server =
178             ServerBuilder.forPort(0)
179                 .addService(new ObservabilityTestHelper.SimpleServiceImpl())
180                 .build()
181                 .start();
182         int port = cleanupRule.register(server).getPort();
183         SimpleServiceGrpc.SimpleServiceBlockingStub stub =
184             SimpleServiceGrpc.newBlockingStub(
185                 cleanupRule.register(
186                     ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build()));
187         assertThat(ObservabilityTestHelper.makeUnaryRpcViaClientStub("buddy", stub))
188             .isEqualTo("Hello buddy");
189         verifyNoInteractions(spyLogHelper);
190         verifyNoInteractions(mockSink);
191       } catch (IOException e) {
192         throw new AssertionError("Exception while testing logging event filter", e);
193       }
194     }
195   }
196 
197   public static final class StaticTestingClassLogEventsUsingMockSink implements Runnable {
198 
199     @Override
run()200     public void run() {
201       Sink mockSink = mock(GcpLogSink.class);
202       ObservabilityConfig config = mock(ObservabilityConfig.class);
203       LogHelper spyLogHelper = spy(new LogHelper(mockSink));
204       ConfigFilterHelper mockFilterHelper2 = mock(ConfigFilterHelper.class);
205       InternalLoggingChannelInterceptor.Factory channelInterceptorFactory =
206           new InternalLoggingChannelInterceptor.FactoryImpl(spyLogHelper, mockFilterHelper2);
207       InternalLoggingServerInterceptor.Factory serverInterceptorFactory =
208           new InternalLoggingServerInterceptor.FactoryImpl(spyLogHelper, mockFilterHelper2);
209 
210       when(config.isEnableCloudLogging()).thenReturn(true);
211       FilterParams logAlwaysFilterParams = FilterParams.create(true, 0, 0);
212       when(mockFilterHelper2.logRpcMethod(anyString(), eq(true)))
213           .thenReturn(logAlwaysFilterParams);
214       when(mockFilterHelper2.logRpcMethod(anyString(), eq(false)))
215           .thenReturn(logAlwaysFilterParams);
216 
217       try (GcpObservability observability =
218           GcpObservability.grpcInit(
219               mockSink, config, channelInterceptorFactory, serverInterceptorFactory)) {
220         Server server =
221             ServerBuilder.forPort(0)
222                 .addService(new ObservabilityTestHelper.SimpleServiceImpl())
223                 .build()
224                 .start();
225         int port = cleanupRule.register(server).getPort();
226         SimpleServiceGrpc.SimpleServiceBlockingStub stub =
227             SimpleServiceGrpc.newBlockingStub(
228                 cleanupRule.register(
229                     ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build()));
230         assertThat(ObservabilityTestHelper.makeUnaryRpcViaClientStub("buddy", stub))
231             .isEqualTo("Hello buddy");
232         // Total number of calls should have been 14 (6 from client and 6 from server)
233         // Since cancel is not invoked, it will be 12.
234         // Request message(Total count:2 (1 from client and 1 from server) and Response
235         // message(count:2)
236         // events are not in the event_types list, i.e  14 - 2(cancel) - 2(req_msg) - 2(resp_msg)
237         // = 8
238         assertThat(Mockito.mockingDetails(mockSink).getInvocations().size()).isEqualTo(12);
239         ArgumentCaptor<GrpcLogRecord> captor = ArgumentCaptor.forClass(GrpcLogRecord.class);
240         verify(mockSink, times(12)).write(captor.capture(),
241             AdditionalMatchers.or(ArgumentMatchers.isNull(),
242                 ArgumentMatchers.any(SpanContext.class)));
243         for (GrpcLogRecord record : captor.getAllValues()) {
244           assertThat(record.getType()).isInstanceOf(GrpcLogRecord.EventType.class);
245           assertThat(record.getLogger()).isInstanceOf(GrpcLogRecord.EventLogger.class);
246         }
247       } catch (IOException e) {
248         throw new AssertionError("Exception while testing logging using mock sink", e);
249       }
250     }
251   }
252 }
253