Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.azure.cosmos.util.Beta;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

private ClientSideRequestStatistics clientSideRequestStatistics;
private FeedResponseDiagnostics feedResponseDiagnostics;

static final String USER_AGENT = Utils.getUserAgent();
static final String USER_AGENT_KEY = "userAgent";

CosmosDiagnostics(DiagnosticsClientContext diagnosticsClientContext) {
this.clientSideRequestStatistics = new ClientSideRequestStatistics(diagnosticsClientContext);
Expand All @@ -52,16 +55,7 @@ CosmosDiagnostics clientSideRequestStatistics(ClientSideRequestStatistics client
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
if (this.feedResponseDiagnostics != null) {
stringBuilder.append("userAgent=").append(USER_AGENT).append(System.lineSeparator());
stringBuilder.append(feedResponseDiagnostics);
} else {
try {
stringBuilder.append(OBJECT_MAPPER.writeValueAsString(this.clientSideRequestStatistics));
} catch (JsonProcessingException e) {
LOGGER.error("Error while parsing diagnostics " + e);
}
}
fillCosmosDiagnostics(null, stringBuilder);
return stringBuilder.toString();
}

Expand Down Expand Up @@ -92,4 +86,30 @@ public Set<URI> getRegionsContacted() {
FeedResponseDiagnostics getFeedResponseDiagnostics() {
return feedResponseDiagnostics;
}

void fillCosmosDiagnostics(ObjectNode parentNode, StringBuilder stringBuilder) {
if (this.feedResponseDiagnostics != null) {
if (parentNode != null) {
parentNode.put(USER_AGENT_KEY, USER_AGENT);
parentNode.putPOJO(COSMOS_DIAGNOSTICS_KEY, feedResponseDiagnostics);
}

if (stringBuilder != null) {
stringBuilder.append(USER_AGENT_KEY +"=").append(USER_AGENT).append(System.lineSeparator());
stringBuilder.append(feedResponseDiagnostics);
}
} else {
if (parentNode != null) {
parentNode.putPOJO(COSMOS_DIAGNOSTICS_KEY, clientSideRequestStatistics);
}

if (stringBuilder != null) {
try {
stringBuilder.append(OBJECT_MAPPER.writeValueAsString(this.clientSideRequestStatistics));
} catch (JsonProcessingException e) {
LOGGER.error("Error while parsing diagnostics ", e);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,26 @@

import com.azure.core.exception.AzureException;
import com.azure.cosmos.implementation.Constants;
import com.azure.cosmos.implementation.CosmosError;
import com.azure.cosmos.implementation.HttpConstants;
import com.azure.cosmos.implementation.RequestTimeline;
import com.azure.cosmos.implementation.Utils;
import com.azure.cosmos.implementation.apachecommons.lang.StringUtils;
import com.azure.cosmos.implementation.directconnectivity.Uri;
import com.azure.cosmos.implementation.CosmosError;
import com.azure.cosmos.implementation.directconnectivity.rntbd.RntbdEndpointStatistics;
import com.azure.cosmos.models.ModelBridgeInternal;
import com.azure.cosmos.implementation.apachecommons.lang.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.azure.cosmos.CosmosDiagnostics.USER_AGENT_KEY;

/**
* This class defines a custom exception type for all operations on
* CosmosClient in the Azure Cosmos DB database service. Applications are
Expand All @@ -39,6 +44,7 @@
public class CosmosException extends AzureException {
private static final long serialVersionUID = 1L;

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

@Override
public String getMessage() {
if (cosmosDiagnostics == null) {
return innerErrorMessage();
try {
ObjectNode messageNode = mapper.createObjectNode();
messageNode.put("innerErrorMessage", innerErrorMessage());
if (cosmosDiagnostics != null) {
cosmosDiagnostics.fillCosmosDiagnostics(messageNode, null);
}
return mapper.writeValueAsString(messageNode);
} catch (JsonProcessingException e) {
if (cosmosDiagnostics == null) {
return innerErrorMessage();
}
return innerErrorMessage() + ", " + cosmosDiagnostics.toString();
}
return innerErrorMessage() + ", " + cosmosDiagnostics.toString();
}

/**
Expand Down Expand Up @@ -299,10 +314,39 @@ public double getRequestCharge() {

@Override
public String toString() {
return getClass().getSimpleName() + "{" + "userAgent=" + USER_AGENT + ", error=" + cosmosError + ", resourceAddress='"
+ resourceAddress + ", statusCode=" + statusCode + ", message=" + getMessage()
+ ", causeInfo=" + causeInfo() + ", responseHeaders=" + responseHeaders + ", requestHeaders="
+ filterSensitiveData(requestHeaders) + '}';
try {
ObjectNode exceptionMessageNode = mapper.createObjectNode();
exceptionMessageNode.put("ClassName", getClass().getSimpleName());
exceptionMessageNode.put(USER_AGENT_KEY, USER_AGENT);
exceptionMessageNode.put("statusCode", statusCode);
exceptionMessageNode.put("resourceAddress", resourceAddress);
if (cosmosError != null) {
exceptionMessageNode.put("error", cosmosError.toJson());
}

exceptionMessageNode.put("innerErrorMessage", innerErrorMessage());
exceptionMessageNode.put("causeInfo", causeInfo());
if (responseHeaders != null) {
exceptionMessageNode.put("responseHeaders", responseHeaders.toString());
}

List<Map.Entry<String, String>> filterRequestHeaders = filterSensitiveData(requestHeaders);
if (filterRequestHeaders != null) {
exceptionMessageNode.put("requestHeaders", filterRequestHeaders.toString());
}

if(this.cosmosDiagnostics != null) {
cosmosDiagnostics.fillCosmosDiagnostics(exceptionMessageNode, null);
}

return mapper.writeValueAsString(exceptionMessageNode);
} catch (JsonProcessingException ex) {
return getClass().getSimpleName() + "{" + USER_AGENT_KEY +"=" + USER_AGENT + ", error=" + cosmosError + ", " +
"resourceAddress='"
+ resourceAddress + ", statusCode=" + statusCode + ", message=" + getMessage()
+ ", causeInfo=" + causeInfo() + ", responseHeaders=" + responseHeaders + ", requestHeaders="
+ filterSensitiveData(requestHeaders) + '}';
}
}

String innerErrorMessage() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import com.azure.cosmos.models.ModelBridgeInternal;
import com.azure.cosmos.models.PartitionKey;
import com.azure.cosmos.rx.TestSuiteBase;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
Expand All @@ -39,6 +39,7 @@
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.time.Instant;
Expand All @@ -63,7 +64,7 @@ public class CosmosDiagnosticsTest extends TestSuiteBase {
private CosmosAsyncContainer cosmosAsyncContainer;

@BeforeClass(groups = {"simple"}, timeOut = SETUP_TIMEOUT)
public void beforeClass() throws Exception {
public void beforeClass() {
assertThat(this.gatewayClient).isNull();
gatewayClient = new CosmosClientBuilder()
.endpoint(TestConfigurations.HOST)
Expand Down Expand Up @@ -150,7 +151,7 @@ public void gatewayDiagnostics() {
// TODO: (nakumars) - Uncomment the following line after your client telemetry fix
// assertThat(createResponse.getDiagnostics().getRegionsContacted()).isNotEmpty();
validateTransportRequestTimelineGateway(diagnostics);
validateJson(diagnostics);
isValidJSON(diagnostics);
} finally {
if (testGatewayClient != null) {
testGatewayClient.close();
Expand All @@ -172,6 +173,8 @@ public void gatewayDiagnosticsOnException() {
InternalObjectNode.class);
fail("request should fail as partition key is wrong");
} catch (CosmosException exception) {
isValidJSON(exception.toString());
isValidJSON(exception.getMessage());
String diagnostics = exception.getDiagnostics().toString();
assertThat(exception.getStatusCode()).isEqualTo(HttpConstants.StatusCodes.NOTFOUND);
assertThat(diagnostics).contains("\"connectionMode\":\"GATEWAY\"");
Expand All @@ -185,7 +188,7 @@ public void gatewayDiagnosticsOnException() {
// assertThat(createResponse.getDiagnostics().getRegionsContacted()).isNotEmpty();
assertThat(exception.getDiagnostics().getDuration()).isNotNull();
validateTransportRequestTimelineGateway(diagnostics);
validateJson(diagnostics);
isValidJSON(diagnostics);
}
}

Expand Down Expand Up @@ -229,7 +232,7 @@ public void directDiagnostics() {
assertThat(createResponse.getDiagnostics().getRegionsContacted()).isNotEmpty();
assertThat(createResponse.getDiagnostics().getDuration()).isNotNull();
validateTransportRequestTimelineDirect(diagnostics);
validateJson(diagnostics);
isValidJSON(diagnostics);

// validate that on failed operation request timeline is populated
try {
Expand Down Expand Up @@ -422,13 +425,15 @@ public void directDiagnosticsOnException() {
InternalObjectNode.class);
fail("request should fail as partition key is wrong");
} catch (CosmosException exception) {
isValidJSON(exception.toString());
isValidJSON(exception.getMessage());
String diagnostics = exception.getDiagnostics().toString();
assertThat(exception.getStatusCode()).isEqualTo(HttpConstants.StatusCodes.NOTFOUND);
assertThat(diagnostics).contains("\"connectionMode\":\"DIRECT\"");
assertThat(diagnostics).doesNotContain(("\"resourceAddress\":null"));
assertThat(exception.getDiagnostics().getRegionsContacted()).isNotEmpty();
assertThat(exception.getDiagnostics().getDuration()).isNotNull();
validateJson(diagnostics);
isValidJSON(diagnostics);
// TODO https://github.com/Azure/azure-sdk-for-java/issues/8035
// uncomment below if above issue is fixed
//validateTransportRequestTimelineDirect(diagnostics);
Expand Down Expand Up @@ -821,11 +826,13 @@ private void validateTransportRequestTimelineDirect(String diagnostics) {
assertThat(diagnostics).contains("\"durationInMicroSec\"");
}

private void validateJson(String jsonInString) {
public void isValidJSON(final String json) {
try {
OBJECT_MAPPER.readTree(jsonInString);
} catch(JsonProcessingException ex) {
fail("Diagnostic string is not in json format");
final JsonParser parser = new ObjectMapper().createParser(json);
while (parser.nextToken() != null) {
}
} catch (IOException ex) {
fail("Diagnostic string is not in json format ", ex);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public class CosmosExceptionTest {
@Test(groups = { "unit" })
public void sdkVersionPresent() {
CosmosException dce = BridgeInternal.createCosmosException(0);
assertThat(dce.toString()).contains("userAgent=" + Utils.getUserAgent());
assertThat(dce.toString()).contains("\"userAgent\":\"" + Utils.getUserAgent());
}

@Test(groups = { "unit" })
Expand Down Expand Up @@ -118,7 +118,7 @@ public void statusCodeIsCorrect(Class<CosmosException> type, int expectedStatusC
constructor.setAccessible(true);
final CosmosException instance = constructor.newInstance("some-message", null, "some-uri");
assertEquals(instance.getStatusCode(), expectedStatusCode);
assertThat(instance.toString()).contains("userAgent=" + Utils.getUserAgent());
assertThat(instance.toString()).contains("\"userAgent\":\"" + Utils.getUserAgent());
} catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException error) {
String message = lenientFormat("could not create instance of %s due to %s", type, error);
throw new AssertionError(message, error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public void getPrimaryUri_NoAddress() throws Exception {
}

@Test(groups = "unit", expectedExceptions = GoneException.class, expectedExceptionsMessageRegExp =
"The requested resource is no longer available at the server. Returned addresses are \\{https://cosmos1/,https://cosmos2/\\}")
".*\"innerErrorMessage\":\"The requested resource is no longer available at the server. Returned addresses are .*https://cosmos1/,https://cosmos2/}.*")
public void getPrimaryUri_NoPrimaryAddress() throws Exception {
RxDocumentServiceRequest request = mockDocumentServiceRequest(clientContext);
Mockito.doReturn(null).when(request).getDefaultReplicaIndex();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ public void conflictResolutionPolicyCRUD() {
// when (e.StatusCode == HttpStatusCode.BadRequest)
CosmosException dce = Utils.as(e, CosmosException.class);
if (dce != null && dce.getStatusCode() == 400) {
assertThat(dce.getMessage()).contains("Invalid path '\\\\\\/a\\\\\\/b' for last writer wins conflict resolution");
assertThat(dce.getMessage()).contains("Invalid path '\\\\\\\\\\\\/a\\\\\\\\\\\\/b' for last writer " +
"wins conflict resolution");
} else {
throw e;
}
Expand Down