2020import static org .junit .jupiter .api .Assertions .assertThrows ;
2121
2222import ch .qos .logback .classic .Level ;
23+ import ch .qos .logback .classic .Logger ;
2324import ch .qos .logback .classic .spi .ILoggingEvent ;
2425import com .google .api .client .http .LowLevelHttpRequest ;
2526import com .google .api .client .http .LowLevelHttpResponse ;
3839import com .google .showcase .v1beta1 .EchoSettings ;
3940import com .google .showcase .v1beta1 .it .util .TestClientInitializer ;
4041import java .io .IOException ;
42+ import java .time .Duration ;
4143import java .util .HashMap ;
4244import java .util .Map ;
4345import java .util .concurrent .TimeUnit ;
46+ import org .awaitility .Awaitility ;
4447import org .junit .jupiter .api .AfterAll ;
4548import org .junit .jupiter .api .AfterEach ;
4649import org .junit .jupiter .api .BeforeAll ;
@@ -76,9 +79,9 @@ static void destroyClients() throws InterruptedException {
7679 private TestAppender setupTestLogger (String loggerName , Level level ) {
7780 TestAppender appender = new TestAppender ();
7881 appender .start ();
79- org . slf4j . Logger logger = LoggerFactory .getLogger (loggerName );
80- (( ch . qos . logback . classic . Logger ) logger ) .setLevel (level );
81- (( ch . qos . logback . classic . Logger ) logger ) .addAppender (appender );
82+ Logger logger = ( Logger ) LoggerFactory .getLogger (loggerName );
83+ logger .setLevel (level );
84+ logger .addAppender (appender );
8285 return appender ;
8386 }
8487
@@ -92,9 +95,31 @@ void setupTestLogger() {
9295 void teardownTestLogger () {
9396 if (testAppender != null ) {
9497 testAppender .stop ();
98+ Logger logger = (Logger ) LoggerFactory .getLogger ("com.google.api.gax.tracing.LoggingTracer" );
99+ logger .detachAppender (testAppender );
100+ testAppender .clearEvents ();
95101 }
96102 }
97103
104+ /**
105+ * Polls asynchronously until an ERROR logging event is appended or timeout is reached.
106+ *
107+ * <p>Logging of operation failure in TraceFinisher occurs asynchronously on a background executor
108+ * thread, so Awaitility is used to prevent test flakiness and race conditions.
109+ *
110+ * @return the first {@link ILoggingEvent} with ERROR level
111+ */
112+ private ILoggingEvent getErrorLoggingEvent () {
113+ Awaitility .await ()
114+ .atMost (Duration .ofSeconds (5 ))
115+ .until (() -> testAppender .events .stream ().anyMatch (e -> e .getLevel () == Level .ERROR ));
116+ return testAppender .events .stream ()
117+ .filter (event -> event .getLevel () == Level .ERROR )
118+ .findFirst ()
119+ .orElseThrow (
120+ () -> new AssertionError ("Expected an ERROR log event in: " + testAppender .events ));
121+ }
122+
98123 private Map <String , Object > getKvps (ILoggingEvent loggingEvent ) {
99124 Map <String , Object > map = new HashMap <>();
100125 if (loggingEvent .getKeyValuePairs () != null ) {
@@ -174,38 +199,40 @@ public LowLevelHttpResponse execute() throws IOException {
174199 .build ();
175200 com .google .showcase .v1beta1 .stub .EchoStub stub = echoStubSettings .createStub ();
176201 EchoClient mockHttpJsonClient = EchoClient .create (stub );
202+ try {
203+ EchoRequest request = EchoRequest .newBuilder ().build ();
204+ assertThrows (ApiException .class , () -> mockHttpJsonClient .echo (request ));
177205
178- EchoRequest request = EchoRequest .newBuilder ().build ();
179- assertThrows (ApiException .class , () -> mockHttpJsonClient .echo (request ));
180-
181- assertThat (testAppender .events .size ()).isAtLeast (1 );
182- ILoggingEvent loggingEvent = testAppender .events .get (testAppender .events .size () - 1 );
206+ ILoggingEvent loggingEvent = getErrorLoggingEvent ();
207+ assertThat (loggingEvent .getLevel ()).isEqualTo (Level .ERROR );
183208
184- assertThat (loggingEvent .getMessage ())
185- .contains ("This is a mock JSON error generated by the server" );
209+ assertThat (loggingEvent .getMessage ())
210+ .contains ("This is a mock JSON error generated by the server" );
186211
187- Map <String , Object > kvps = getKvps (loggingEvent );
188- assertThat (kvps ).containsEntry (ObservabilityAttributes .RPC_SYSTEM_NAME_ATTRIBUTE , "http" );
189- assertThat (kvps ).containsEntry (ObservabilityAttributes .HTTP_METHOD_ATTRIBUTE , "POST" );
190- assertThat (kvps )
191- .containsEntry (ObservabilityAttributes .HTTP_URL_TEMPLATE_ATTRIBUTE , "v1beta1/echo:echo" );
192- assertThat (kvps )
193- .containsEntry (ObservabilityAttributes .RPC_RESPONSE_STATUS_ATTRIBUTE , "ABORTED" );
194- assertThat (kvps )
195- .containsEntry (ObservabilityAttributes .ERROR_TYPE_ATTRIBUTE , "mock_error_reason" );
196- assertThat (kvps )
197- .containsEntry (ObservabilityAttributes .ERROR_DOMAIN_ATTRIBUTE , "mock.googleapis.com" );
198- assertThat (kvps )
199- .containsEntry (
200- ObservabilityAttributes .ERROR_METADATA_ATTRIBUTE_PREFIX + "mock_key" , "mock_value" );
201-
202- mockHttpJsonClient .close ();
203- mockHttpJsonClient .awaitTermination (
204- TestClientInitializer .AWAIT_TERMINATION_SECONDS , TimeUnit .SECONDS );
212+ Map <String , Object > kvps = getKvps (loggingEvent );
213+ assertThat (kvps ).containsEntry (ObservabilityAttributes .RPC_SYSTEM_NAME_ATTRIBUTE , "http" );
214+ assertThat (kvps ).containsEntry (ObservabilityAttributes .HTTP_METHOD_ATTRIBUTE , "POST" );
215+ assertThat (kvps )
216+ .containsEntry (ObservabilityAttributes .HTTP_URL_TEMPLATE_ATTRIBUTE , "v1beta1/echo:echo" );
217+ assertThat (kvps )
218+ .containsEntry (ObservabilityAttributes .RPC_RESPONSE_STATUS_ATTRIBUTE , "ABORTED" );
219+ assertThat (kvps )
220+ .containsEntry (ObservabilityAttributes .ERROR_TYPE_ATTRIBUTE , "mock_error_reason" );
221+ assertThat (kvps )
222+ .containsEntry (ObservabilityAttributes .ERROR_DOMAIN_ATTRIBUTE , "mock.googleapis.com" );
223+ assertThat (kvps )
224+ .containsEntry (
225+ ObservabilityAttributes .ERROR_METADATA_ATTRIBUTE_PREFIX + "mock_key" , "mock_value" );
226+ } finally {
227+ mockHttpJsonClient .close ();
228+ mockHttpJsonClient .awaitTermination (
229+ TestClientInitializer .AWAIT_TERMINATION_SECONDS , TimeUnit .SECONDS );
230+ }
205231 }
206232
207233 @ Test
208234 void testHttpJson_noLogEmittedForSuccess () {
235+ testAppender .clearEvents ();
209236 EchoRequest request = EchoRequest .newBuilder ().setContent ("Success" ).build ();
210237 httpjsonClient .echo (request );
211238 assertThat (testAppender .events .size ()).isEqualTo (0 );
@@ -218,22 +245,23 @@ void testHttpJson_clientLevelFailureAttributes() throws Exception {
218245 stubSettingsBuilder
219246 .echoSettings ()
220247 .setRetrySettings (
221- com .google .api .gax .retrying .RetrySettings .newBuilder ()
222- .setInitialRpcTimeoutDuration (java .time .Duration .ofMillis (0 ))
223- .setTotalTimeoutDuration (java .time .Duration .ofMillis (0 ))
224- .setMaxAttempts (1 )
225- .build ());
248+ com .google .api .gax .retrying .RetrySettings .newBuilder ().setMaxAttempts (1 ).build ());
226249 stubSettingsBuilder .setTracerFactory (new LoggingTracerFactory ());
227250 stubSettingsBuilder .setCredentialsProvider (NoCredentialsProvider .create ());
228251 stubSettingsBuilder .setEndpoint ("localhost:1" );
229252
230- try (com .google .showcase .v1beta1 .stub .EchoStub stub = stubSettingsBuilder .build ().createStub ();
231- EchoClient client = EchoClient .create (stub )) {
253+ com .google .showcase .v1beta1 .stub .EchoStub stub = stubSettingsBuilder .build ().createStub ();
254+ EchoClient client = EchoClient .create (stub );
255+ try {
232256 assertThrows (ApiException .class , () -> client .echo (EchoRequest .newBuilder ().build ()));
233- assertThat (testAppender .events .size ()).isAtLeast (1 );
234- ILoggingEvent loggingEvent = testAppender .events .get (testAppender .events .size () - 1 );
257+ ILoggingEvent loggingEvent = getErrorLoggingEvent ();
258+ assertThat (loggingEvent .getLevel ()).isEqualTo (Level .ERROR );
259+ assertThat (loggingEvent .getMessage ()).isNotEmpty ();
235260 Map <String , Object > kvps = getKvps (loggingEvent );
236261 assertThat (kvps ).containsEntry (ObservabilityAttributes .RPC_SYSTEM_NAME_ATTRIBUTE , "http" );
262+ } finally {
263+ client .shutdownNow ();
264+ client .awaitTermination (TestClientInitializer .AWAIT_TERMINATION_SECONDS , TimeUnit .SECONDS );
237265 }
238266 }
239267
@@ -242,8 +270,8 @@ void testGrpc_logEmittedForLowLevelRequestFailure() {
242270 EchoRequest request = buildErrorRequest ();
243271 assertThrows (ApiException .class , () -> grpcClient .echo (request ));
244272
245- assertThat ( testAppender . events . size ()). isAtLeast ( 1 );
246- ILoggingEvent loggingEvent = testAppender . events . get ( testAppender . events . size () - 1 );
273+ ILoggingEvent loggingEvent = getErrorLoggingEvent ( );
274+ assertThat ( loggingEvent . getLevel ()). isEqualTo ( Level . ERROR );
247275 assertThat (loggingEvent .getMessage ()).contains ("This is a test error" );
248276
249277 Map <String , Object > kvps = getKvps (loggingEvent );
@@ -264,6 +292,7 @@ void testGrpc_logEmittedForLowLevelRequestFailure() {
264292
265293 @ Test
266294 void testGrpc_noLogEmittedForSuccess () {
295+ testAppender .clearEvents ();
267296 EchoRequest request = EchoRequest .newBuilder ().setContent ("Success" ).build ();
268297 grpcClient .echo (request );
269298 assertThat (testAppender .events .size ()).isEqualTo (0 );
@@ -276,22 +305,23 @@ void testGrpc_clientLevelFailureAttributes() throws Exception {
276305 stubSettingsBuilder
277306 .echoSettings ()
278307 .setRetrySettings (
279- com .google .api .gax .retrying .RetrySettings .newBuilder ()
280- .setInitialRpcTimeoutDuration (java .time .Duration .ofMillis (0 ))
281- .setTotalTimeoutDuration (java .time .Duration .ofMillis (0 ))
282- .setMaxAttempts (1 )
283- .build ());
308+ com .google .api .gax .retrying .RetrySettings .newBuilder ().setMaxAttempts (1 ).build ());
284309 stubSettingsBuilder .setTracerFactory (new LoggingTracerFactory ());
285310 stubSettingsBuilder .setCredentialsProvider (NoCredentialsProvider .create ());
286311 stubSettingsBuilder .setEndpoint ("localhost:1" );
287312
288- try (com .google .showcase .v1beta1 .stub .EchoStub stub = stubSettingsBuilder .build ().createStub ();
289- EchoClient client = EchoClient .create (stub )) {
313+ com .google .showcase .v1beta1 .stub .EchoStub stub = stubSettingsBuilder .build ().createStub ();
314+ EchoClient client = EchoClient .create (stub );
315+ try {
290316 assertThrows (ApiException .class , () -> client .echo (EchoRequest .newBuilder ().build ()));
291- assertThat (testAppender .events .size ()).isAtLeast (1 );
292- ILoggingEvent loggingEvent = testAppender .events .get (testAppender .events .size () - 1 );
317+ ILoggingEvent loggingEvent = getErrorLoggingEvent ();
318+ assertThat (loggingEvent .getLevel ()).isEqualTo (Level .ERROR );
319+ assertThat (loggingEvent .getMessage ()).isNotEmpty ();
293320 Map <String , Object > kvps = getKvps (loggingEvent );
294321 assertThat (kvps ).containsEntry (ObservabilityAttributes .RPC_SYSTEM_NAME_ATTRIBUTE , "grpc" );
322+ } finally {
323+ client .shutdownNow ();
324+ client .awaitTermination (TestClientInitializer .AWAIT_TERMINATION_SECONDS , TimeUnit .SECONDS );
295325 }
296326 }
297327}
0 commit comments