diff --git a/sdk/monitor/azure-monitor-query/README.md b/sdk/monitor/azure-monitor-query/README.md
index 42d0c7564c71a..42edb93e036a9 100644
--- a/sdk/monitor/azure-monitor-query/README.md
+++ b/sdk/monitor/azure-monitor-query/README.md
@@ -90,7 +90,127 @@ LogsAsyncClient logsAsyncClient = new LogsClientBuilder()
+ "; value = " + logsTableCell.getValueAsString()));
}
}
-}
+
+```
+### Get logs for a query and read the response as a model type
+
+```java
+
+ LogsQueryResult queryResults = logsClient
+ .queryLogs("d2d0e126-fa1e-4b0a-b647-250cdd471e68", "AppRequests", null);
+
+ // Sample to use a model type to read the results
+ for (LogsTable table : queryResults.getLogsTables()) {
+ for (LogsTableRow row : table.getTableRows()) {
+ CustomModel model = row.getRowAsObject(CustomModel.class);
+ System.out.println("Time generated " + model.getTimeGenerated() + "; success = " + model.getSuccess() +
+ "; operation name = " + model.getOperationName());
+ }
+ }
+
+
+ public class CustomModel {
+ private OffsetDateTime timeGenerated;
+ private String tenantId;
+ private String id;
+ private String source;
+ private Boolean success;
+ private Double durationMs;
+ private Object properties;
+ private Object measurements;
+ private String operationName;
+ private String operationId;
+ private Object operationLinks;
+
+
+ public OffsetDateTime getTimeGenerated() {
+ return timeGenerated;
+ }
+
+ public void setTimeGenerated(OffsetDateTime timeGenerated) {
+ this.timeGenerated = timeGenerated;
+ }
+
+ public String getTenantId() {
+ return tenantId;
+ }
+
+ public void setTenantId(String tenantId) {
+ this.tenantId = tenantId;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public void setSource(String source) {
+ this.source = source;
+ }
+
+ public Boolean getSuccess() {
+ return success;
+ }
+
+ public void setSuccess(Boolean success) {
+ this.success = success;
+ }
+
+ public Double getDurationMs() {
+ return durationMs;
+ }
+
+ public void setDurationMs(Double durationMs) {
+ this.durationMs = durationMs;
+ }
+
+ public Object getProperties() {
+ return properties;
+ }
+
+ public void setProperties(Object properties) {
+ this.properties = properties;
+ }
+
+ public Object getMeasurements() {
+ return measurements;
+ }
+
+ public void setMeasurements(Object measurements) {
+ this.measurements = measurements;
+ }
+
+ public String getOperationName() {
+ return operationName;
+ }
+
+ public void setOperationName(String operationName) {
+ this.operationName = operationName;
+ }
+
+ public String getOperationId() {
+ return operationId;
+ }
+
+ public void setOperationId(String operationId) {
+ this.operationId = operationId;
+ }
+
+ public Object getOperationLinks() {
+ return operationLinks;
+ }
+
+ public void setOperationLinks(Object operationLinks) {
+ this.operationLinks = operationLinks;
+ }
+ }
```
### Get logs for a batch of queries
@@ -272,7 +392,7 @@ client library to use the Netty HTTP client. Configuring or changing the HTTP cl
All client libraries, by default, use the Tomcat-native Boring SSL library to enable native-level performance for SSL
operations. The Boring SSL library is an uber jar containing native libraries for Linux / macOS / Windows, and provides
-better performance compared to the default SSL implementation within the JDK. For more information, including how to
+better performance compared to the default SSL com.azure.monitor.collect.metrics.implementation within the JDK. For more information, including how to
reduce the dependency size, refer to the [performance tuning][performance_tuning] section of the wiki.
## Next steps
diff --git a/sdk/monitor/azure-monitor-query/pom.xml b/sdk/monitor/azure-monitor-query/pom.xml
index 2f4074c25aa8e..9d0be9049e487 100644
--- a/sdk/monitor/azure-monitor-query/pom.xml
+++ b/sdk/monitor/azure-monitor-query/pom.xml
@@ -73,6 +73,12 @@
1.6.2
test
+
+ com.azure
+ azure-core-serializer-json-jackson
+ 1.2.3
+ test
+
diff --git a/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/LogsAsyncClient.java b/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/LogsAsyncClient.java
index 2024ba22a76ef..a845e17a7e196 100644
--- a/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/LogsAsyncClient.java
+++ b/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/LogsAsyncClient.java
@@ -13,6 +13,7 @@
import com.azure.monitor.query.log.implementation.models.BatchRequest;
import com.azure.monitor.query.log.implementation.models.BatchResponse;
import com.azure.monitor.query.log.implementation.models.ErrorInfo;
+import com.azure.monitor.query.log.implementation.models.ErrorResponseException;
import com.azure.monitor.query.log.implementation.models.LogQueryRequest;
import com.azure.monitor.query.log.implementation.models.LogQueryResponse;
import com.azure.monitor.query.log.implementation.models.LogQueryResult;
@@ -22,7 +23,9 @@
import com.azure.monitor.query.models.LogsQueryBatch;
import com.azure.monitor.query.models.LogsQueryBatchResult;
import com.azure.monitor.query.models.LogsQueryBatchResultCollection;
+import com.azure.monitor.query.models.LogsQueryError;
import com.azure.monitor.query.models.LogsQueryErrorDetails;
+import com.azure.monitor.query.models.LogsQueryException;
import com.azure.monitor.query.models.LogsQueryOptions;
import com.azure.monitor.query.models.LogsQueryResult;
import com.azure.monitor.query.models.LogsTable;
@@ -33,6 +36,7 @@
import reactor.core.publisher.Mono;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@@ -128,6 +132,14 @@ Mono> queryLogsBatchWithResponse(LogsQu
batchRequest.setRequests(requests);
return innerClient.getQueries().batchWithResponseAsync(batchRequest, context)
+ .onErrorMap(ex -> {
+ if (ex instanceof ErrorResponseException) {
+ ErrorResponseException error = (ErrorResponseException) ex;
+ ErrorInfo errorInfo = error.getValue().getError();
+ return new LogsQueryException(error.getResponse(), mapLogsQueryError(errorInfo));
+ }
+ return ex;
+ })
.map(this::convertToLogQueryBatchResult);
}
@@ -139,17 +151,39 @@ private Response convertToLogQueryBatchResult(Re
for (LogQueryResponse singleQueryResponse : batchResponse.getResponses()) {
LogsQueryBatchResult logsQueryBatchResult = new LogsQueryBatchResult(singleQueryResponse.getId(),
singleQueryResponse.getStatus(), getLogsQueryResult(singleQueryResponse.getBody()),
- mapLogsQueryBatchError(singleQueryResponse.getBody().getError()));
+ mapLogsQueryError(singleQueryResponse.getBody().getError()));
batchResults.add(logsQueryBatchResult);
}
batchResults.sort(Comparator.comparingInt(o -> Integer.parseInt(o.getId())));
return new SimpleResponse<>(response.getRequest(), response.getStatusCode(), response.getHeaders(), logsQueryBatchResultCollection);
}
- private LogsQueryErrorDetails mapLogsQueryBatchError(ErrorInfo errors) {
+ private LogsQueryErrorDetails mapLogsQueryError(ErrorInfo errors) {
if (errors != null) {
- return new LogsQueryErrorDetails(errors.getMessage(), errors.getCode(),
- errors.getDetails().get(0).getTarget());
+ List errorDetails = Collections.emptyList();
+ if (errors.getDetails() != null) {
+ errorDetails = errors.getDetails()
+ .stream()
+ .map(errorDetail -> new LogsQueryError(errorDetail.getCode(),
+ errorDetail.getMessage(),
+ errorDetail.getTarget(),
+ errorDetail.getValue(),
+ errorDetail.getResources(),
+ errorDetail.getAdditionalProperties()))
+ .collect(Collectors.toList());
+ }
+
+ ErrorInfo innerError = errors.getInnererror();
+ ErrorInfo currentError = errors.getInnererror();
+ while (currentError != null) {
+ innerError = errors.getInnererror();
+ currentError = errors.getInnererror();
+ }
+ String code = errors.getCode();
+ if (!errors.getCode().equals(innerError.getCode())) {
+ code = innerError.getCode();
+ }
+ return new LogsQueryErrorDetails(errors.getMessage(), code, errorDetails);
}
return null;
}
@@ -186,6 +220,14 @@ Mono> queryLogsWithResponse(LogsQueryOptions options,
queryBody,
preferHeader,
context)
+ .onErrorMap(ex -> {
+ if (ex instanceof ErrorResponseException) {
+ ErrorResponseException error = (ErrorResponseException) ex;
+ ErrorInfo errorInfo = error.getValue().getError();
+ return new LogsQueryException(error.getResponse(), mapLogsQueryError(errorInfo));
+ }
+ return ex;
+ })
.map(this::convertToLogQueryResult);
}
diff --git a/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/models/LogsQueryError.java b/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/models/LogsQueryError.java
new file mode 100644
index 0000000000000..61d65c40006f2
--- /dev/null
+++ b/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/models/LogsQueryError.java
@@ -0,0 +1,114 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.monitor.query.models;
+
+import java.util.List;
+
+/**
+ * Error details of a failed log query.
+ */
+
+public final class LogsQueryError {
+ /*
+ * The error's code.
+ */
+ private final String code;
+
+ /*
+ * A human readable error message.
+ */
+ private final String message;
+
+ /*
+ * Indicates which property in the request is responsible for the error.
+ */
+ private final String target;
+
+ /*
+ * Indicates which value in 'target' is responsible for the error.
+ */
+ private final String value;
+
+ /*
+ * Indicates resources which were responsible for the error.
+ */
+ private final List resources;
+
+ /*
+ * Additional properties that can be provided on the error details object
+ */
+ private final Object additionalProperties;
+
+ /**
+ * Creates an instance of ErrorDetail class.
+ * @param code the code value to set.
+ * @param message the message value to set.
+ * @param target indicates which property in the request is responsible for the error.
+ * @param value indicates which value in 'target' is responsible for the error.
+ * @param resources indicates resources which were responsible for the error.
+ * @param additionalProperties additional properties that can be provided on the error details object
+ */
+ public LogsQueryError(
+ String code,
+ String message,
+ String target,
+ String value,
+ List resources,
+ Object additionalProperties) {
+ this.code = code;
+ this.message = message;
+ this.target = target;
+ this.value = value;
+ this.resources = resources;
+ this.additionalProperties = additionalProperties;
+ }
+
+ /**
+ * Get the code property: The error's code.
+ * @return the code value.
+ */
+ public String getCode() {
+ return this.code;
+ }
+
+ /**
+ * Get the message property: A human readable error message.
+ * @return the message value.
+ */
+ public String getMessage() {
+ return this.message;
+ }
+
+ /**
+ * Get the target property: Indicates which property in the request is responsible for the error.
+ * @return the target value.
+ */
+ public String getTarget() {
+ return this.target;
+ }
+
+ /**
+ * Get the value property: Indicates which value in 'target' is responsible for the error.
+ * @return the value value.
+ */
+ public String getValue() {
+ return this.value;
+ }
+
+ /**
+ * Get the resources property: Indicates resources which were responsible for the error.
+ * @return the resources value.
+ */
+ public List getResources() {
+ return this.resources;
+ }
+
+ /**
+ * Get the additionalProperties property: Additional properties that can be provided on the error details object.
+ * @return the additionalProperties value.
+ */
+ public Object getAdditionalProperties() {
+ return this.additionalProperties;
+ }
+}
diff --git a/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/models/LogsQueryErrorDetails.java b/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/models/LogsQueryErrorDetails.java
index 90f3d434dd58d..86da66d534907 100644
--- a/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/models/LogsQueryErrorDetails.java
+++ b/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/models/LogsQueryErrorDetails.java
@@ -5,6 +5,8 @@
import com.azure.core.annotation.Immutable;
+import java.util.List;
+
/**
* The error details of a failed log query.
*/
@@ -12,19 +14,18 @@
public final class LogsQueryErrorDetails {
private final String message;
private final String code;
- private final String target;
+ private final List errors;
/**
* Creates an instance of {@link LogsQueryErrorDetails} with the failure code and target.
* @param message The error message.
* @param code The error code indicating the reason for the error.
- * @param target Indicates which property in the request is responsible for the error.
+ * @param errors The list of additional error details.
*/
- public LogsQueryErrorDetails(String message, String code, String target) {
-
+ public LogsQueryErrorDetails(String message, String code, List errors) {
this.message = message;
this.code = code;
- this.target = target;
+ this.errors = errors;
}
/**
@@ -44,10 +45,10 @@ public String getCode() {
}
/**
- * Indicates which property in the request is responsible for the error.
- * @return The property in the request that is responsible for the error.
+ * Returns the list of additional error details.
+ * @return the list of additional error details.
*/
- public String getTarget() {
- return target;
+ public List getErrors() {
+ return errors;
}
}
diff --git a/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/models/LogsQueryException.java b/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/models/LogsQueryException.java
new file mode 100644
index 0000000000000..9916ba59af887
--- /dev/null
+++ b/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/models/LogsQueryException.java
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.monitor.query.models;
+
+import com.azure.core.exception.HttpResponseException;
+import com.azure.core.http.HttpResponse;
+
+/**
+ * Exception thrown when a query to retrieve logs fails.
+ */
+public final class LogsQueryException extends HttpResponseException {
+ private final transient LogsQueryErrorDetails error;
+
+ /**
+ * Creates a new instance of this exception with the {@link HttpResponse} and {@link LogsQueryError error}
+ * information.
+ * @param response The {@link HttpResponse}.
+ * @param error The {@link LogsQueryError error} details.
+ */
+ public LogsQueryException(HttpResponse response, LogsQueryErrorDetails error) {
+ super("Failed to executed logs query", response, error);
+ this.error = error;
+ }
+
+ @Override
+ public LogsQueryErrorDetails getValue() {
+ return this.error;
+ }
+}
diff --git a/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/models/LogsTableRow.java b/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/models/LogsTableRow.java
index 600433356f173..f34b6ed096862 100644
--- a/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/models/LogsTableRow.java
+++ b/sdk/monitor/azure-monitor-query/src/main/java/com/azure/monitor/query/models/LogsTableRow.java
@@ -4,9 +4,16 @@
package com.azure.monitor.query.models;
import com.azure.core.annotation.Immutable;
+import com.azure.core.util.logging.ClientLogger;
+import com.azure.core.util.serializer.TypeReference;
+import java.lang.reflect.Field;
+import java.util.Arrays;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
import java.util.Optional;
+import java.util.stream.Collectors;
/**
* Represents a row in a {@link LogsTable} of a logs query.
@@ -15,6 +22,7 @@
public final class LogsTableRow {
private final int rowIndex;
private final List tableRow;
+ private final ClientLogger logger = new ClientLogger(LogsTableRow.class);
/**
* Creates a row in a {@link LogsTable} of a logs query.
@@ -46,12 +54,69 @@ public List getTableRow() {
* Returns the value associated with the given column name. If the column name is not found
* {@link Optional#isPresent()} evaluates to {@code false}.
* @param columnName the column name for which the value is returned.
- *
* @return The value associated with the given column name.
*/
public Optional getColumnValue(String columnName) {
return tableRow.stream()
- .filter(cell -> cell.getColumnName().equals(columnName))
- .findFirst();
+ .filter(cell -> cell.getColumnName().equals(columnName))
+ .findFirst();
+ }
+
+ /**
+ * Returns the table row as an object of the given {@code type}. The field names on the type should be
+ * reflectively accessible and the names should match the column names. If the table row contains columns that
+ * are not available on the object, they are ignored and similarly if the fields in the object are not found in
+ * the table columns, they will be null.
+ * @param type The type of the object to be returned
+ * @param The class type.
+ * @return The object that this table row is mapped to.
+ * @throws IllegalArgumentException if an instance of the object cannot be created.
+ */
+ public T getRowAsObject(Class type) {
+ try {
+ T t = type.newInstance();
+
+ Map declaredFieldMapping = Arrays.stream(type.getDeclaredFields())
+ .collect(Collectors.toMap(field -> field.getName().toLowerCase(Locale.ROOT), field -> field));
+
+ tableRow.stream()
+ .forEach(tableCell -> {
+ String columnName = tableCell.getColumnName();
+ try {
+ Field field = declaredFieldMapping.get(columnName.toLowerCase(Locale.ROOT));
+ if (field == null) {
+ return;
+ }
+ field.setAccessible(true);
+ if (tableCell.getColumnType() == ColumnDataType.BOOL) {
+ field.set(t, tableCell.getValueAsBoolean());
+ } else if (tableCell.getColumnType() == ColumnDataType.DATETIME) {
+ field.set(t, tableCell.getValueAsDateTime());
+ } else if (tableCell.getColumnType() == ColumnDataType.DYNAMIC) {
+ if (tableCell.getValueAsDynamic() != null) {
+ field.set(t,
+ tableCell.getValueAsDynamic()
+ .toObject(TypeReference.createInstance(field.getType())));
+ }
+ } else if (tableCell.getColumnType() == ColumnDataType.INT) {
+ field.set(t, tableCell.getValueAsInteger());
+ } else if (tableCell.getColumnType() == ColumnDataType.LONG) {
+ field.set(t, tableCell.getValueAsLong());
+ } else if (tableCell.getColumnType() == ColumnDataType.REAL) {
+ field.set(t, tableCell.getValueAsDouble());
+ } else if (tableCell.getColumnType() == ColumnDataType.STRING) {
+ field.set(t, tableCell.getValueAsString());
+ }
+ field.setAccessible(false);
+ } catch (IllegalAccessException ex) {
+ throw logger.logExceptionAsError(
+ new IllegalArgumentException("Failed to set column value for " + columnName, ex));
+ }
+ });
+ return t;
+ } catch (InstantiationException | IllegalAccessException ex) {
+ throw logger.logExceptionAsError(
+ new IllegalArgumentException("Cannot create an instance of class " + type.getName(), ex));
+ }
}
}
diff --git a/sdk/monitor/azure-monitor-query/src/samples/java/com/azure/monitor/query/LogsQueryWithModels.java b/sdk/monitor/azure-monitor-query/src/samples/java/com/azure/monitor/query/LogsQueryWithModels.java
new file mode 100644
index 0000000000000..9757dc91d2ace
--- /dev/null
+++ b/sdk/monitor/azure-monitor-query/src/samples/java/com/azure/monitor/query/LogsQueryWithModels.java
@@ -0,0 +1,128 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.monitor.query;
+
+import com.azure.core.util.Configuration;
+import com.azure.identity.ClientSecretCredential;
+import com.azure.identity.ClientSecretCredentialBuilder;
+import com.azure.monitor.query.models.LogsQueryResult;
+import com.azure.monitor.query.models.LogsTable;
+import com.azure.monitor.query.models.LogsTableRow;
+
+import java.time.OffsetDateTime;
+
+/**
+ * Sample to demonstrate using a custom model to read the results of a logs query.
+ */
+public class LogsQueryWithModels {
+
+ /**
+ * The main method to run the sample.
+ * @param args ignored args
+ */
+ public static void main(String[] args) {
+ ClientSecretCredential tokenCredential = new ClientSecretCredentialBuilder()
+ .clientId(Configuration.getGlobalConfiguration().get("AZURE_MONITOR_CLIENT_ID"))
+ .clientSecret(Configuration.getGlobalConfiguration().get("AZURE_MONITOR_CLIENT_SECRET"))
+ .tenantId(Configuration.getGlobalConfiguration().get("AZURE_TENANT_ID"))
+ .build();
+
+ LogsClient logsClient = new LogsClientBuilder()
+ .credential(tokenCredential)
+ .buildClient();
+
+ LogsQueryResult queryResults = logsClient
+ .queryLogs("{workspace-id}", "AppRequests", null);
+
+ // Sample to use a model type to read the results
+ for (LogsTable table : queryResults.getLogsTables()) {
+ for (LogsTableRow row : table.getTableRows()) {
+ CustomModel model = row.getRowAsObject(CustomModel.class);
+ System.out.println("Time generated " + model.getTimeGenerated() + "; success = " + model.getSuccess()
+ + "; operation name = " + model.getOperationName());
+ }
+ }
+ }
+
+ /**
+ * A custom model to read the logs query result.
+ */
+ private static class CustomModel {
+ private OffsetDateTime timeGenerated;
+ private String tenantId;
+ private String id;
+ private String source;
+ private Boolean success;
+ private Double durationMs;
+ private Object properties;
+ private String operationName;
+ private String operationId;
+
+
+ /**
+ * Returns the time the log event was generated.
+ * @return the time the log event was generated.
+ */
+ public OffsetDateTime getTimeGenerated() {
+ return timeGenerated;
+ }
+
+ /**
+ * Returns the tenant id of the resource for which this log was recorded.
+ * @return the tenant id of the resource for which this log was recorded.
+ */
+ public String getTenantId() {
+ return tenantId;
+ }
+
+ /**
+ * Returns the unique identifier of this log.
+ * @return the unique identifier of this log.
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * Returns the source of this log.
+ * @return the source of this log.
+ */
+ public String getSource() {
+ return source;
+ }
+
+ /**
+ * Returns {@code true} if the logged request returned a successful response.
+ * @return {@code true} if the logged request returned a successful response.
+ */
+ public Boolean getSuccess() {
+ return success;
+ }
+
+ /**
+ * Returns the time duration the service took to process the request.
+ * @return the time duration the service took to process the request.
+ */
+ public Double getDurationMs() {
+ return durationMs;
+ }
+
+ /**
+ * Returns additional properties of the request.
+ * @return additional properties of the request.
+ */
+ public Object getProperties() {
+ return properties;
+ }
+
+ /**
+ * Returns the name of the operation.
+ * @return the name of the operation.
+ */
+ public String getOperationName() {
+ return operationName;
+ }
+ }
+
+}