Skip to content

Commit 9383d60

Browse files
Converting cosmos exception into json format (Azure#18092)
* Converting cosmos exception json * fixing test case * fixing test case * fixing test case * Fixing spotbug * resolving comments and fixing test case * Fixing test case
1 parent 07a74e4 commit 9383d60

File tree

6 files changed

+105
-33
lines changed

6 files changed

+105
-33
lines changed

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/CosmosDiagnostics.java

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.azure.cosmos.util.Beta;
99
import com.fasterxml.jackson.core.JsonProcessingException;
1010
import com.fasterxml.jackson.databind.ObjectMapper;
11+
import com.fasterxml.jackson.databind.node.ObjectNode;
1112
import org.slf4j.Logger;
1213
import org.slf4j.LoggerFactory;
1314

@@ -21,11 +22,13 @@
2122
public final class CosmosDiagnostics {
2223
private static final Logger LOGGER = LoggerFactory.getLogger(CosmosDiagnostics.class);
2324
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
25+
private static final String COSMOS_DIAGNOSTICS_KEY = "cosmosDiagnostics";
2426

2527
private ClientSideRequestStatistics clientSideRequestStatistics;
2628
private FeedResponseDiagnostics feedResponseDiagnostics;
2729

2830
static final String USER_AGENT = Utils.getUserAgent();
31+
static final String USER_AGENT_KEY = "userAgent";
2932

3033
CosmosDiagnostics(DiagnosticsClientContext diagnosticsClientContext) {
3134
this.clientSideRequestStatistics = new ClientSideRequestStatistics(diagnosticsClientContext);
@@ -52,16 +55,7 @@ CosmosDiagnostics clientSideRequestStatistics(ClientSideRequestStatistics client
5255
@Override
5356
public String toString() {
5457
StringBuilder stringBuilder = new StringBuilder();
55-
if (this.feedResponseDiagnostics != null) {
56-
stringBuilder.append("userAgent=").append(USER_AGENT).append(System.lineSeparator());
57-
stringBuilder.append(feedResponseDiagnostics);
58-
} else {
59-
try {
60-
stringBuilder.append(OBJECT_MAPPER.writeValueAsString(this.clientSideRequestStatistics));
61-
} catch (JsonProcessingException e) {
62-
LOGGER.error("Error while parsing diagnostics " + e);
63-
}
64-
}
58+
fillCosmosDiagnostics(null, stringBuilder);
6559
return stringBuilder.toString();
6660
}
6761

@@ -92,4 +86,30 @@ public Set<URI> getRegionsContacted() {
9286
FeedResponseDiagnostics getFeedResponseDiagnostics() {
9387
return feedResponseDiagnostics;
9488
}
89+
90+
void fillCosmosDiagnostics(ObjectNode parentNode, StringBuilder stringBuilder) {
91+
if (this.feedResponseDiagnostics != null) {
92+
if (parentNode != null) {
93+
parentNode.put(USER_AGENT_KEY, USER_AGENT);
94+
parentNode.putPOJO(COSMOS_DIAGNOSTICS_KEY, feedResponseDiagnostics);
95+
}
96+
97+
if (stringBuilder != null) {
98+
stringBuilder.append(USER_AGENT_KEY +"=").append(USER_AGENT).append(System.lineSeparator());
99+
stringBuilder.append(feedResponseDiagnostics);
100+
}
101+
} else {
102+
if (parentNode != null) {
103+
parentNode.putPOJO(COSMOS_DIAGNOSTICS_KEY, clientSideRequestStatistics);
104+
}
105+
106+
if (stringBuilder != null) {
107+
try {
108+
stringBuilder.append(OBJECT_MAPPER.writeValueAsString(this.clientSideRequestStatistics));
109+
} catch (JsonProcessingException e) {
110+
LOGGER.error("Error while parsing diagnostics ", e);
111+
}
112+
}
113+
}
114+
}
95115
}

sdk/cosmos/azure-cosmos/src/main/java/com/azure/cosmos/CosmosException.java

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,26 @@
55

66
import com.azure.core.exception.AzureException;
77
import com.azure.cosmos.implementation.Constants;
8+
import com.azure.cosmos.implementation.CosmosError;
89
import com.azure.cosmos.implementation.HttpConstants;
910
import com.azure.cosmos.implementation.RequestTimeline;
1011
import com.azure.cosmos.implementation.Utils;
12+
import com.azure.cosmos.implementation.apachecommons.lang.StringUtils;
1113
import com.azure.cosmos.implementation.directconnectivity.Uri;
12-
import com.azure.cosmos.implementation.CosmosError;
1314
import com.azure.cosmos.implementation.directconnectivity.rntbd.RntbdEndpointStatistics;
1415
import com.azure.cosmos.models.ModelBridgeInternal;
15-
import com.azure.cosmos.implementation.apachecommons.lang.StringUtils;
16+
import com.fasterxml.jackson.core.JsonProcessingException;
17+
import com.fasterxml.jackson.databind.ObjectMapper;
18+
import com.fasterxml.jackson.databind.node.ObjectNode;
1619

1720
import java.time.Duration;
1821
import java.util.HashMap;
1922
import java.util.List;
2023
import java.util.Map;
2124
import java.util.stream.Collectors;
2225

26+
import static com.azure.cosmos.CosmosDiagnostics.USER_AGENT_KEY;
27+
2328
/**
2429
* This class defines a custom exception type for all operations on
2530
* CosmosClient in the Azure Cosmos DB database service. Applications are
@@ -39,6 +44,7 @@
3944
public class CosmosException extends AzureException {
4045
private static final long serialVersionUID = 1L;
4146

47+
private static final ObjectMapper mapper = Utils.getSimpleObjectMapper();
4248
private final static String USER_AGENT = Utils.getUserAgent();
4349
private final int statusCode;
4450
private final Map<String, String> responseHeaders;
@@ -155,10 +161,19 @@ protected CosmosException(String message, Exception exception, Map<String, Strin
155161

156162
@Override
157163
public String getMessage() {
158-
if (cosmosDiagnostics == null) {
159-
return innerErrorMessage();
164+
try {
165+
ObjectNode messageNode = mapper.createObjectNode();
166+
messageNode.put("innerErrorMessage", innerErrorMessage());
167+
if (cosmosDiagnostics != null) {
168+
cosmosDiagnostics.fillCosmosDiagnostics(messageNode, null);
169+
}
170+
return mapper.writeValueAsString(messageNode);
171+
} catch (JsonProcessingException e) {
172+
if (cosmosDiagnostics == null) {
173+
return innerErrorMessage();
174+
}
175+
return innerErrorMessage() + ", " + cosmosDiagnostics.toString();
160176
}
161-
return innerErrorMessage() + ", " + cosmosDiagnostics.toString();
162177
}
163178

164179
/**
@@ -299,10 +314,39 @@ public double getRequestCharge() {
299314

300315
@Override
301316
public String toString() {
302-
return getClass().getSimpleName() + "{" + "userAgent=" + USER_AGENT + ", error=" + cosmosError + ", resourceAddress='"
303-
+ resourceAddress + ", statusCode=" + statusCode + ", message=" + getMessage()
304-
+ ", causeInfo=" + causeInfo() + ", responseHeaders=" + responseHeaders + ", requestHeaders="
305-
+ filterSensitiveData(requestHeaders) + '}';
317+
try {
318+
ObjectNode exceptionMessageNode = mapper.createObjectNode();
319+
exceptionMessageNode.put("ClassName", getClass().getSimpleName());
320+
exceptionMessageNode.put(USER_AGENT_KEY, USER_AGENT);
321+
exceptionMessageNode.put("statusCode", statusCode);
322+
exceptionMessageNode.put("resourceAddress", resourceAddress);
323+
if (cosmosError != null) {
324+
exceptionMessageNode.put("error", cosmosError.toJson());
325+
}
326+
327+
exceptionMessageNode.put("innerErrorMessage", innerErrorMessage());
328+
exceptionMessageNode.put("causeInfo", causeInfo());
329+
if (responseHeaders != null) {
330+
exceptionMessageNode.put("responseHeaders", responseHeaders.toString());
331+
}
332+
333+
List<Map.Entry<String, String>> filterRequestHeaders = filterSensitiveData(requestHeaders);
334+
if (filterRequestHeaders != null) {
335+
exceptionMessageNode.put("requestHeaders", filterRequestHeaders.toString());
336+
}
337+
338+
if(this.cosmosDiagnostics != null) {
339+
cosmosDiagnostics.fillCosmosDiagnostics(exceptionMessageNode, null);
340+
}
341+
342+
return mapper.writeValueAsString(exceptionMessageNode);
343+
} catch (JsonProcessingException ex) {
344+
return getClass().getSimpleName() + "{" + USER_AGENT_KEY +"=" + USER_AGENT + ", error=" + cosmosError + ", " +
345+
"resourceAddress='"
346+
+ resourceAddress + ", statusCode=" + statusCode + ", message=" + getMessage()
347+
+ ", causeInfo=" + causeInfo() + ", responseHeaders=" + responseHeaders + ", requestHeaders="
348+
+ filterSensitiveData(requestHeaders) + '}';
349+
}
306350
}
307351

308352
String innerErrorMessage() {

sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosDiagnosticsTest.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import com.azure.cosmos.models.ModelBridgeInternal;
2929
import com.azure.cosmos.models.PartitionKey;
3030
import com.azure.cosmos.rx.TestSuiteBase;
31-
import com.fasterxml.jackson.core.JsonProcessingException;
31+
import com.fasterxml.jackson.core.JsonParser;
3232
import com.fasterxml.jackson.databind.JsonNode;
3333
import com.fasterxml.jackson.databind.ObjectMapper;
3434
import com.fasterxml.jackson.databind.node.ArrayNode;
@@ -39,6 +39,7 @@
3939
import org.testng.annotations.DataProvider;
4040
import org.testng.annotations.Test;
4141

42+
import java.io.IOException;
4243
import java.lang.reflect.Field;
4344
import java.net.InetSocketAddress;
4445
import java.time.Instant;
@@ -63,7 +64,7 @@ public class CosmosDiagnosticsTest extends TestSuiteBase {
6364
private CosmosAsyncContainer cosmosAsyncContainer;
6465

6566
@BeforeClass(groups = {"simple"}, timeOut = SETUP_TIMEOUT)
66-
public void beforeClass() throws Exception {
67+
public void beforeClass() {
6768
assertThat(this.gatewayClient).isNull();
6869
gatewayClient = new CosmosClientBuilder()
6970
.endpoint(TestConfigurations.HOST)
@@ -150,7 +151,7 @@ public void gatewayDiagnostics() {
150151
// TODO: (nakumars) - Uncomment the following line after your client telemetry fix
151152
// assertThat(createResponse.getDiagnostics().getRegionsContacted()).isNotEmpty();
152153
validateTransportRequestTimelineGateway(diagnostics);
153-
validateJson(diagnostics);
154+
isValidJSON(diagnostics);
154155
} finally {
155156
if (testGatewayClient != null) {
156157
testGatewayClient.close();
@@ -172,6 +173,8 @@ public void gatewayDiagnosticsOnException() {
172173
InternalObjectNode.class);
173174
fail("request should fail as partition key is wrong");
174175
} catch (CosmosException exception) {
176+
isValidJSON(exception.toString());
177+
isValidJSON(exception.getMessage());
175178
String diagnostics = exception.getDiagnostics().toString();
176179
assertThat(exception.getStatusCode()).isEqualTo(HttpConstants.StatusCodes.NOTFOUND);
177180
assertThat(diagnostics).contains("\"connectionMode\":\"GATEWAY\"");
@@ -185,7 +188,7 @@ public void gatewayDiagnosticsOnException() {
185188
// assertThat(createResponse.getDiagnostics().getRegionsContacted()).isNotEmpty();
186189
assertThat(exception.getDiagnostics().getDuration()).isNotNull();
187190
validateTransportRequestTimelineGateway(diagnostics);
188-
validateJson(diagnostics);
191+
isValidJSON(diagnostics);
189192
}
190193
}
191194

@@ -229,7 +232,7 @@ public void directDiagnostics() {
229232
assertThat(createResponse.getDiagnostics().getRegionsContacted()).isNotEmpty();
230233
assertThat(createResponse.getDiagnostics().getDuration()).isNotNull();
231234
validateTransportRequestTimelineDirect(diagnostics);
232-
validateJson(diagnostics);
235+
isValidJSON(diagnostics);
233236

234237
// validate that on failed operation request timeline is populated
235238
try {
@@ -422,13 +425,15 @@ public void directDiagnosticsOnException() {
422425
InternalObjectNode.class);
423426
fail("request should fail as partition key is wrong");
424427
} catch (CosmosException exception) {
428+
isValidJSON(exception.toString());
429+
isValidJSON(exception.getMessage());
425430
String diagnostics = exception.getDiagnostics().toString();
426431
assertThat(exception.getStatusCode()).isEqualTo(HttpConstants.StatusCodes.NOTFOUND);
427432
assertThat(diagnostics).contains("\"connectionMode\":\"DIRECT\"");
428433
assertThat(diagnostics).doesNotContain(("\"resourceAddress\":null"));
429434
assertThat(exception.getDiagnostics().getRegionsContacted()).isNotEmpty();
430435
assertThat(exception.getDiagnostics().getDuration()).isNotNull();
431-
validateJson(diagnostics);
436+
isValidJSON(diagnostics);
432437
// TODO https://github.com/Azure/azure-sdk-for-java/issues/8035
433438
// uncomment below if above issue is fixed
434439
//validateTransportRequestTimelineDirect(diagnostics);
@@ -821,11 +826,13 @@ private void validateTransportRequestTimelineDirect(String diagnostics) {
821826
assertThat(diagnostics).contains("\"durationInMicroSec\"");
822827
}
823828

824-
private void validateJson(String jsonInString) {
829+
public void isValidJSON(final String json) {
825830
try {
826-
OBJECT_MAPPER.readTree(jsonInString);
827-
} catch(JsonProcessingException ex) {
828-
fail("Diagnostic string is not in json format");
831+
final JsonParser parser = new ObjectMapper().createParser(json);
832+
while (parser.nextToken() != null) {
833+
}
834+
} catch (IOException ex) {
835+
fail("Diagnostic string is not in json format ", ex);
829836
}
830837
}
831838

sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/CosmosExceptionTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public class CosmosExceptionTest {
5858
@Test(groups = { "unit" })
5959
public void sdkVersionPresent() {
6060
CosmosException dce = BridgeInternal.createCosmosException(0);
61-
assertThat(dce.toString()).contains("userAgent=" + Utils.getUserAgent());
61+
assertThat(dce.toString()).contains("\"userAgent\":\"" + Utils.getUserAgent());
6262
}
6363

6464
@Test(groups = { "unit" })
@@ -118,7 +118,7 @@ public void statusCodeIsCorrect(Class<CosmosException> type, int expectedStatusC
118118
constructor.setAccessible(true);
119119
final CosmosException instance = constructor.newInstance("some-message", null, "some-uri");
120120
assertEquals(instance.getStatusCode(), expectedStatusCode);
121-
assertThat(instance.toString()).contains("userAgent=" + Utils.getUserAgent());
121+
assertThat(instance.toString()).contains("\"userAgent\":\"" + Utils.getUserAgent());
122122
} catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException error) {
123123
String message = lenientFormat("could not create instance of %s due to %s", type, error);
124124
throw new AssertionError(message, error);

sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/implementation/directconnectivity/AddressSelectorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public void getPrimaryUri_NoAddress() throws Exception {
3333
}
3434

3535
@Test(groups = "unit", expectedExceptions = GoneException.class, expectedExceptionsMessageRegExp =
36-
"The requested resource is no longer available at the server. Returned addresses are \\{https://cosmos1/,https://cosmos2/\\}")
36+
".*\"innerErrorMessage\":\"The requested resource is no longer available at the server. Returned addresses are .*https://cosmos1/,https://cosmos2/}.*")
3737
public void getPrimaryUri_NoPrimaryAddress() throws Exception {
3838
RxDocumentServiceRequest request = mockDocumentServiceRequest(clientContext);
3939
Mockito.doReturn(null).when(request).getDefaultReplicaIndex();

sdk/cosmos/azure-cosmos/src/test/java/com/azure/cosmos/rx/MultiMasterConflictResolutionTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ public void conflictResolutionPolicyCRUD() {
8282
// when (e.StatusCode == HttpStatusCode.BadRequest)
8383
CosmosException dce = Utils.as(e, CosmosException.class);
8484
if (dce != null && dce.getStatusCode() == 400) {
85-
assertThat(dce.getMessage()).contains("Invalid path '\\\\\\/a\\\\\\/b' for last writer wins conflict resolution");
85+
assertThat(dce.getMessage()).contains("Invalid path '\\\\\\\\\\\\/a\\\\\\\\\\\\/b' for last writer " +
86+
"wins conflict resolution");
8687
} else {
8788
throw e;
8889
}

0 commit comments

Comments
 (0)