Skip to content

Commit e277d24

Browse files
authored
test(gax): Error Logging Integration Test (#12329)
Add an automated integration test (`ITActionableErrorsLogging`) to ensure end-to-end functionality of the Actionable Errors logging framework against a live`gapic-showcase` server.
1 parent 3c5fb14 commit e277d24

File tree

2 files changed

+298
-0
lines changed

2 files changed

+298
-0
lines changed

sdk-platform-java/java-showcase/gapic-showcase/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@
336336
<testExclude>**/com/google/showcase/v1beta1/it/*.java</testExclude>
337337
<testExclude>**/com/google/showcase/v1beta1/it/logging/ITLoggingDisabled.java</testExclude>
338338
<testExclude>**/com/google/showcase/v1beta1/it/logging/ITLogging.java</testExclude>
339+
<testExclude>**/com/google/showcase/v1beta1/it/logging/ITActionableErrorsLogging.java</testExclude>
339340
</testExcludes>
340341
</configuration>
341342
</plugin>
@@ -381,6 +382,7 @@
381382
<testExclude>**/com/google/showcase/v1beta1/it/*.java</testExclude>
382383
<testExclude>**/com/google/showcase/v1beta1/it/logging/ITLogging1x.java</testExclude>
383384
<testExclude>**/com/google/showcase/v1beta1/it/logging/ITLogging.java</testExclude>
385+
<testExclude>**/com/google/showcase/v1beta1/it/logging/ITActionableErrorsLogging.java</testExclude>
384386
</testExcludes>
385387
</configuration>
386388
</plugin>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
/*
2+
* Copyright 2026 Google LLC
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+
* https://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 com.google.showcase.v1beta1.it.logging;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
import static org.junit.jupiter.api.Assertions.assertThrows;
21+
22+
import ch.qos.logback.classic.Level;
23+
import ch.qos.logback.classic.spi.ILoggingEvent;
24+
import com.google.api.client.http.LowLevelHttpRequest;
25+
import com.google.api.client.http.LowLevelHttpResponse;
26+
import com.google.api.client.testing.http.MockHttpTransport;
27+
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
28+
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
29+
import com.google.api.gax.core.NoCredentialsProvider;
30+
import com.google.api.gax.rpc.ApiException;
31+
import com.google.api.gax.tracing.LoggingTracerFactory;
32+
import com.google.api.gax.tracing.ObservabilityAttributes;
33+
import com.google.protobuf.Any;
34+
import com.google.rpc.ErrorInfo;
35+
import com.google.rpc.Status;
36+
import com.google.showcase.v1beta1.EchoClient;
37+
import com.google.showcase.v1beta1.EchoRequest;
38+
import com.google.showcase.v1beta1.EchoSettings;
39+
import com.google.showcase.v1beta1.it.util.TestClientInitializer;
40+
import java.io.IOException;
41+
import java.util.HashMap;
42+
import java.util.Map;
43+
import java.util.concurrent.TimeUnit;
44+
import org.junit.jupiter.api.AfterAll;
45+
import org.junit.jupiter.api.AfterEach;
46+
import org.junit.jupiter.api.BeforeAll;
47+
import org.junit.jupiter.api.BeforeEach;
48+
import org.junit.jupiter.api.Test;
49+
import org.slf4j.LoggerFactory;
50+
import org.slf4j.event.KeyValuePair;
51+
52+
public class ITActionableErrorsLogging {
53+
54+
private static EchoClient grpcClient;
55+
private static EchoClient httpjsonClient;
56+
private TestAppender testAppender;
57+
58+
@BeforeAll
59+
static void createClients() throws Exception {
60+
grpcClient =
61+
TestClientInitializer.createGrpcEchoClientOpentelemetry(new LoggingTracerFactory());
62+
httpjsonClient =
63+
TestClientInitializer.createHttpJsonEchoClientOpentelemetry(new LoggingTracerFactory());
64+
}
65+
66+
@AfterAll
67+
static void destroyClients() throws InterruptedException {
68+
grpcClient.close();
69+
httpjsonClient.close();
70+
71+
grpcClient.awaitTermination(TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS);
72+
httpjsonClient.awaitTermination(
73+
TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS);
74+
}
75+
76+
private TestAppender setupTestLogger(String loggerName, Level level) {
77+
TestAppender appender = new TestAppender();
78+
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+
return appender;
83+
}
84+
85+
@BeforeEach
86+
void setupTestLogger() {
87+
testAppender = setupTestLogger("com.google.api.gax.tracing.LoggingTracer", Level.DEBUG);
88+
testAppender.clearEvents();
89+
}
90+
91+
@AfterEach
92+
void teardownTestLogger() {
93+
if (testAppender != null) {
94+
testAppender.stop();
95+
}
96+
}
97+
98+
private Map<String, Object> getKvps(ILoggingEvent loggingEvent) {
99+
Map<String, Object> map = new HashMap<>();
100+
if (loggingEvent.getKeyValuePairs() != null) {
101+
for (KeyValuePair kvp : loggingEvent.getKeyValuePairs()) {
102+
map.put(kvp.key, kvp.value);
103+
}
104+
}
105+
return map;
106+
}
107+
108+
private EchoRequest buildErrorRequest() {
109+
ErrorInfo errorInfo =
110+
ErrorInfo.newBuilder()
111+
.setReason("TEST_REASON")
112+
.setDomain("test.googleapis.com")
113+
.putMetadata("test_metadata", "test_value")
114+
.build();
115+
Status status =
116+
Status.newBuilder()
117+
.setCode(3) // INVALID_ARGUMENT
118+
.setMessage("This is a test error")
119+
.addDetails(Any.pack(errorInfo))
120+
.build();
121+
return EchoRequest.newBuilder().setError(status).build();
122+
}
123+
124+
@Test
125+
void testHttpJson_logEmittedForLowLevelRequestFailure() throws Exception {
126+
MockHttpTransport mockTransport =
127+
new MockHttpTransport() {
128+
@Override
129+
public LowLevelHttpRequest buildRequest(String method, String url) throws IOException {
130+
return new MockLowLevelHttpRequest() {
131+
@Override
132+
public LowLevelHttpResponse execute() throws IOException {
133+
MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
134+
response.setStatusCode(409); // ABORTED
135+
response.setContentType("application/json");
136+
String jsonError =
137+
"{\n"
138+
+ " \"error\": {\n"
139+
+ " \"code\": 409,\n"
140+
+ " \"message\": \"This is a mock JSON error generated by the server\",\n"
141+
+ " \"status\": \"ABORTED\",\n"
142+
+ " \"details\": [\n"
143+
+ " {\n"
144+
+ " \"@type\": \"type.googleapis.com/google.rpc.ErrorInfo\",\n"
145+
+ " \"reason\": \"mock_error_reason\",\n"
146+
+ " \"domain\": \"mock.googleapis.com\",\n"
147+
+ " \"metadata\": {\"mock_key\": \"mock_value\"}\n"
148+
+ " }\n"
149+
+ " ]\n"
150+
+ " }\n"
151+
+ "}";
152+
response.setContent(jsonError);
153+
return response;
154+
}
155+
};
156+
}
157+
};
158+
159+
EchoSettings httpJsonEchoSettings =
160+
EchoSettings.newHttpJsonBuilder()
161+
.setCredentialsProvider(NoCredentialsProvider.create())
162+
.setTransportChannelProvider(
163+
EchoSettings.defaultHttpJsonTransportProviderBuilder()
164+
.setHttpTransport(mockTransport)
165+
.setEndpoint(TestClientInitializer.DEFAULT_HTTPJSON_ENDPOINT)
166+
.build())
167+
.build();
168+
169+
com.google.showcase.v1beta1.stub.EchoStubSettings echoStubSettings =
170+
(com.google.showcase.v1beta1.stub.EchoStubSettings)
171+
httpJsonEchoSettings.getStubSettings().toBuilder()
172+
.setTracerFactory(new LoggingTracerFactory())
173+
.build();
174+
com.google.showcase.v1beta1.stub.EchoStub stub = echoStubSettings.createStub();
175+
EchoClient mockHttpJsonClient = EchoClient.create(stub);
176+
177+
EchoRequest request = EchoRequest.newBuilder().build();
178+
assertThrows(ApiException.class, () -> mockHttpJsonClient.echo(request));
179+
180+
assertThat(testAppender.events.size()).isAtLeast(1);
181+
ILoggingEvent loggingEvent = testAppender.events.get(testAppender.events.size() - 1);
182+
183+
assertThat(loggingEvent.getMessage())
184+
.contains("This is a mock JSON error generated by the server");
185+
186+
Map<String, Object> kvps = getKvps(loggingEvent);
187+
assertThat(kvps).containsEntry(ObservabilityAttributes.RPC_SYSTEM_NAME_ATTRIBUTE, "http");
188+
assertThat(kvps).containsEntry(ObservabilityAttributes.HTTP_METHOD_ATTRIBUTE, "POST");
189+
assertThat(kvps)
190+
.containsEntry(ObservabilityAttributes.HTTP_URL_TEMPLATE_ATTRIBUTE, "v1beta1/echo:echo");
191+
assertThat(kvps)
192+
.containsEntry(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, "ABORTED");
193+
assertThat(kvps)
194+
.containsEntry(ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE, "mock_error_reason");
195+
assertThat(kvps)
196+
.containsEntry(ObservabilityAttributes.ERROR_DOMAIN_ATTRIBUTE, "mock.googleapis.com");
197+
assertThat(kvps)
198+
.containsEntry(
199+
ObservabilityAttributes.ERROR_METADATA_ATTRIBUTE_PREFIX + "mock_key", "mock_value");
200+
201+
mockHttpJsonClient.close();
202+
mockHttpJsonClient.awaitTermination(
203+
TestClientInitializer.AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS);
204+
}
205+
206+
@Test
207+
void testHttpJson_noLogEmittedForSuccess() {
208+
EchoRequest request = EchoRequest.newBuilder().setContent("Success").build();
209+
httpjsonClient.echo(request);
210+
assertThat(testAppender.events.size()).isEqualTo(0);
211+
}
212+
213+
@Test
214+
void testHttpJson_clientLevelFailureAttributes() throws Exception {
215+
com.google.showcase.v1beta1.stub.EchoStubSettings.Builder stubSettingsBuilder =
216+
com.google.showcase.v1beta1.stub.EchoStubSettings.newHttpJsonBuilder();
217+
stubSettingsBuilder
218+
.echoSettings()
219+
.setRetrySettings(
220+
com.google.api.gax.retrying.RetrySettings.newBuilder()
221+
.setInitialRpcTimeoutDuration(java.time.Duration.ofMillis(0))
222+
.setTotalTimeoutDuration(java.time.Duration.ofMillis(0))
223+
.setMaxAttempts(1)
224+
.build());
225+
stubSettingsBuilder.setTracerFactory(new LoggingTracerFactory());
226+
stubSettingsBuilder.setCredentialsProvider(NoCredentialsProvider.create());
227+
stubSettingsBuilder.setEndpoint("localhost:1");
228+
229+
try (com.google.showcase.v1beta1.stub.EchoStub stub = stubSettingsBuilder.build().createStub();
230+
EchoClient client = EchoClient.create(stub)) {
231+
assertThrows(ApiException.class, () -> client.echo(EchoRequest.newBuilder().build()));
232+
assertThat(testAppender.events.size()).isAtLeast(1);
233+
ILoggingEvent loggingEvent = testAppender.events.get(testAppender.events.size() - 1);
234+
Map<String, Object> kvps = getKvps(loggingEvent);
235+
assertThat(kvps).containsEntry(ObservabilityAttributes.RPC_SYSTEM_NAME_ATTRIBUTE, "http");
236+
}
237+
}
238+
239+
@Test
240+
void testGrpc_logEmittedForLowLevelRequestFailure() {
241+
EchoRequest request = buildErrorRequest();
242+
assertThrows(ApiException.class, () -> grpcClient.echo(request));
243+
244+
assertThat(testAppender.events.size()).isAtLeast(1);
245+
ILoggingEvent loggingEvent = testAppender.events.get(testAppender.events.size() - 1);
246+
assertThat(loggingEvent.getMessage()).contains("This is a test error");
247+
248+
Map<String, Object> kvps = getKvps(loggingEvent);
249+
assertThat(kvps).containsEntry(ObservabilityAttributes.RPC_SYSTEM_NAME_ATTRIBUTE, "grpc");
250+
assertThat(kvps)
251+
.containsEntry(
252+
ObservabilityAttributes.GRPC_RPC_METHOD_ATTRIBUTE, "google.showcase.v1beta1.Echo/Echo");
253+
assertThat(kvps)
254+
.containsEntry(ObservabilityAttributes.RPC_RESPONSE_STATUS_ATTRIBUTE, "INVALID_ARGUMENT");
255+
assertThat(kvps).containsEntry(ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE, "TEST_REASON");
256+
assertThat(kvps)
257+
.containsEntry(ObservabilityAttributes.ERROR_DOMAIN_ATTRIBUTE, "test.googleapis.com");
258+
assertThat(kvps)
259+
.containsEntry(
260+
ObservabilityAttributes.ERROR_METADATA_ATTRIBUTE_PREFIX + "test_metadata",
261+
"test_value");
262+
}
263+
264+
@Test
265+
void testGrpc_noLogEmittedForSuccess() {
266+
EchoRequest request = EchoRequest.newBuilder().setContent("Success").build();
267+
grpcClient.echo(request);
268+
assertThat(testAppender.events.size()).isEqualTo(0);
269+
}
270+
271+
@Test
272+
void testGrpc_clientLevelFailureAttributes() throws Exception {
273+
com.google.showcase.v1beta1.stub.EchoStubSettings.Builder stubSettingsBuilder =
274+
com.google.showcase.v1beta1.stub.EchoStubSettings.newBuilder();
275+
stubSettingsBuilder
276+
.echoSettings()
277+
.setRetrySettings(
278+
com.google.api.gax.retrying.RetrySettings.newBuilder()
279+
.setInitialRpcTimeoutDuration(java.time.Duration.ofMillis(0))
280+
.setTotalTimeoutDuration(java.time.Duration.ofMillis(0))
281+
.setMaxAttempts(1)
282+
.build());
283+
stubSettingsBuilder.setTracerFactory(new LoggingTracerFactory());
284+
stubSettingsBuilder.setCredentialsProvider(NoCredentialsProvider.create());
285+
stubSettingsBuilder.setEndpoint("localhost:1");
286+
287+
try (com.google.showcase.v1beta1.stub.EchoStub stub = stubSettingsBuilder.build().createStub();
288+
EchoClient client = EchoClient.create(stub)) {
289+
assertThrows(ApiException.class, () -> client.echo(EchoRequest.newBuilder().build()));
290+
assertThat(testAppender.events.size()).isAtLeast(1);
291+
ILoggingEvent loggingEvent = testAppender.events.get(testAppender.events.size() - 1);
292+
Map<String, Object> kvps = getKvps(loggingEvent);
293+
assertThat(kvps).containsEntry(ObservabilityAttributes.RPC_SYSTEM_NAME_ATTRIBUTE, "grpc");
294+
}
295+
}
296+
}

0 commit comments

Comments
 (0)