diff --git a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs index 9d72260abb0b..01c5204be296 100644 --- a/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs +++ b/sdk/core/Azure.Core.Experimental/api/Azure.Core.Experimental.netstandard2.0.cs @@ -199,6 +199,7 @@ public ResponseError(string? code, string? message, Azure.Core.ResponseInnerErro public Azure.Core.ResponseInnerError? InnerError { get { throw null; } } public string? Message { get { throw null; } } public string? Target { get { throw null; } } + public override string ToString() { throw null; } } public sealed partial class ResponseInnerError { @@ -206,5 +207,6 @@ internal ResponseInnerError() { } public string? Code { get { throw null; } } public Azure.Core.ResponseInnerError? InnerError { get { throw null; } } public string? Message { get { throw null; } } + public override string ToString() { throw null; } } } diff --git a/sdk/core/Azure.Core.Experimental/src/ResponseError.cs b/sdk/core/Azure.Core.Experimental/src/ResponseError.cs index 344f25f7a98b..a7137e76c23e 100644 --- a/sdk/core/Azure.Core.Experimental/src/ResponseError.cs +++ b/sdk/core/Azure.Core.Experimental/src/ResponseError.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Globalization; +using System.Text; using System.Text.Json; using System.Text.Json.Serialization; @@ -119,5 +121,35 @@ public override void Write(Utf8JsonWriter writer, ResponseError? value, JsonSeri throw new NotImplementedException(); } } + + /// + public override string ToString() + { + var builder = new StringBuilder(); + + builder.AppendFormat(CultureInfo.InvariantCulture, "{0}: {1}{2}", Code, Message, Environment.NewLine); + + if (Target != null) + { + builder.AppendFormat(CultureInfo.InvariantCulture, "Target: {0}{1}", Target, Environment.NewLine); + } + + if (InnerError != null) + { + builder.AppendLine("Inner Error:"); + builder.Append(InnerError); + } + + if (Details.Count > 0) + { + builder.AppendLine("Details:"); + foreach (var detail in Details) + { + builder.Append(detail); + } + } + + return builder.ToString(); + } } } \ No newline at end of file diff --git a/sdk/core/Azure.Core.Experimental/src/ResponseInnerError.cs b/sdk/core/Azure.Core.Experimental/src/ResponseInnerError.cs index 822bcb5dbf45..bbd6591820ee 100644 --- a/sdk/core/Azure.Core.Experimental/src/ResponseInnerError.cs +++ b/sdk/core/Azure.Core.Experimental/src/ResponseInnerError.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. using System; +using System.Globalization; +using System.Text; using System.Text.Json; using System.Text.Json.Serialization; @@ -77,5 +79,20 @@ public override void Write(Utf8JsonWriter writer, ResponseInnerError? value, Jso throw new NotImplementedException(); } } + + /// + public override string ToString() + { + var builder = new StringBuilder(); + + builder.AppendFormat(CultureInfo.InvariantCulture, "{0}: {1}{2}", Code, Message, Environment.NewLine); + if (InnerError != null) + { + builder.AppendLine("Inner Error:"); + builder.Append(InnerError); + } + + return builder.ToString(); + } } } \ No newline at end of file diff --git a/sdk/core/Azure.Core.Experimental/tests/ResponseErrorTests.cs b/sdk/core/Azure.Core.Experimental/tests/ResponseErrorTests.cs index 4e3002c574a5..11b835b37557 100644 --- a/sdk/core/Azure.Core.Experimental/tests/ResponseErrorTests.cs +++ b/sdk/core/Azure.Core.Experimental/tests/ResponseErrorTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +using System; using System.Text.Json; using NUnit.Framework; @@ -35,6 +36,11 @@ public void CanDeserializeSimple() Assert.AreEqual("MoreDetailedBadError", error.InnerError.Code); Assert.AreEqual("Inner message", error.InnerError.Message); Assert.Null(error.InnerError.InnerError); + Assert.AreEqual("BadError: Something wasn't awesome" + Environment.NewLine + + "Target: Error target" + Environment.NewLine + + "Inner Error:" + Environment.NewLine + + "MoreDetailedBadError: Inner message" + Environment.NewLine, + error.ToString()); } [Test] @@ -80,6 +86,16 @@ public void CanDeserializeComplex() Assert.AreEqual(2, error.Details.Count); Assert.Null(error.InnerError.InnerError.InnerError); + + Assert.AreEqual("BadError: Something wasn't awesome" + Environment.NewLine + + "Target: Error target" + Environment.NewLine + + "Inner Error:" + Environment.NewLine + + "MoreDetailedBadError: Inner message" + Environment.NewLine + + "Inner Error:" + Environment.NewLine + + "InnerMoreDetailedBadError: Inner Inner message" + Environment.NewLine + + "Details:" + Environment.NewLine + + "Code 1: Message 1" + Environment.NewLine + + "Code 2: Message 2" + Environment.NewLine, error.ToString()); } } } \ No newline at end of file diff --git a/sdk/monitor/Azure.Monitor.Query/README.md b/sdk/monitor/Azure.Monitor.Query/README.md index 243bb97d6c19..fa9000e7cde8 100644 --- a/sdk/monitor/Azure.Monitor.Query/README.md +++ b/sdk/monitor/Azure.Monitor.Query/README.md @@ -63,10 +63,10 @@ We guarantee that all client instance methods are thread-safe and independent of You can query logs using the `LogsClient.QueryAsync`. The result would be returned as a table with a collection of rows: ```C# Snippet:QueryLogsAsTable -Uri endpoint = new Uri("https://api.loganalytics.io"); +var endpoint = new Uri("https://api.loganalytics.io"); string workspaceId = ""; -LogsQueryClient client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); +var client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); Response response = await client.QueryAsync(workspaceId, "AzureActivity | top 10 by TimeGenerated", TimeSpan.FromDays(1)); LogsQueryResultTable table = response.Value.PrimaryTable; @@ -90,7 +90,7 @@ public class MyLogEntryModel ``` ```C# Snippet:QueryLogsAsModels -LogsQueryClient client = new LogsQueryClient(TestEnvironment.LogsEndpoint, new DefaultAzureCredential()); +var client = new LogsQueryClient(TestEnvironment.LogsEndpoint, new DefaultAzureCredential()); string workspaceId = ""; // Query TOP 10 resource groups by event count @@ -109,10 +109,10 @@ foreach (var logEntryModel in response.Value) If your query return a single column (or a single value) of a primitive type you can use `LogsClient.QueryAsync` overload to deserialize it: ```C# Snippet:QueryLogsAsPrimitive -Uri endpoint = new Uri("https://api.loganalytics.io"); +var endpoint = new Uri("https://api.loganalytics.io"); string workspaceId = ""; -LogsQueryClient client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); +var client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); // Query TOP 10 resource groups by event count Response> response = await client.QueryAsync(workspaceId, @@ -130,14 +130,15 @@ foreach (var resourceGroup in response.Value) You can execute multiple queries in on request using the `LogsClient.CreateBatchQuery`: ```C# Snippet:BatchQuery -Uri endpoint = new Uri("https://api.loganalytics.io"); +var endpoint = new Uri("https://api.loganalytics.io"); string workspaceId = ""; -LogsQueryClient client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); +var client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); // Query TOP 10 resource groups by event count // And total event count -LogsBatchQuery batch = new LogsBatchQuery(); +var batch = new LogsBatchQuery(); + string countQueryId = batch.AddQuery(workspaceId, "AzureActivity | count", TimeSpan.FromDays(1)); string topQueryId = batch.AddQuery(workspaceId, "AzureActivity | summarize Count = count() by ResourceGroup | top 10 by Count", TimeSpan.FromDays(1)); @@ -158,10 +159,10 @@ foreach (var logEntryModel in topEntries) You can also dynamically inspect the list of columns. The following example prints the result of the query as a table: ```C# Snippet:QueryLogsPrintTable -Uri endpoint = new Uri("https://api.loganalytics.io"); +var endpoint = new Uri("https://api.loganalytics.io"); string workspaceId = ""; -LogsQueryClient client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); +var client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); Response response = await client.QueryAsync(workspaceId, "AzureActivity | top 10 by TimeGenerated", TimeSpan.FromDays(1)); LogsQueryResultTable table = response.Value.PrimaryTable; @@ -190,10 +191,10 @@ foreach (var row in table.Rows) Some Logs queries take longer than 3 minutes to execute. The default server timeout is 3 minutes. You can increase the server timeout to a maximum of 10 minutes. In the following example, the `LogsQueryOptions` object's `ServerTimeout` property is used to set the server timeout to 10 minutes: ```C# Snippet:QueryLogsWithTimeout -Uri endpoint = new Uri("https://api.loganalytics.io"); +var endpoint = new Uri("https://api.loganalytics.io"); string workspaceId = ""; -LogsQueryClient client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); +var client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); // Query TOP 10 resource groups by event count Response> response = await client.QueryAsync(workspaceId, @@ -215,7 +216,7 @@ foreach (var resourceGroup in response.Value) You can query metrics using the `MetricsClient.QueryAsync`. For every requested metric a set of aggregated values would be returned inside the `TimeSeries` collection. ```C# Snippet:QueryMetrics -Uri endpoint = new Uri("https://management.azure.com"); +var endpoint = new Uri("https://management.azure.com"); string resourceId = "/subscriptions//resourceGroups//providers/Microsoft.OperationalInsights/workspaces/"; @@ -250,10 +251,10 @@ When you interact with the Azure Monitor Query client library using the .NET SDK For example, if you submit an invalid query a `400` error is returned, indicating "Bad Request". ```C# Snippet:BadRequest -Uri endpoint = new Uri("https://api.loganalytics.io"); +var endpoint = new Uri("https://api.loganalytics.io"); string workspaceId = ""; -LogsQueryClient client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); +var client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); try { diff --git a/sdk/monitor/Azure.Monitor.Query/api/Azure.Monitor.Query.netstandard2.0.cs b/sdk/monitor/Azure.Monitor.Query/api/Azure.Monitor.Query.netstandard2.0.cs index 655e95d263fc..175b7effa031 100644 --- a/sdk/monitor/Azure.Monitor.Query/api/Azure.Monitor.Query.netstandard2.0.cs +++ b/sdk/monitor/Azure.Monitor.Query/api/Azure.Monitor.Query.netstandard2.0.cs @@ -13,12 +13,12 @@ public LogsQueryClient(Azure.Core.TokenCredential credential, Azure.Monitor.Quer public LogsQueryClient(System.Uri endpoint, Azure.Core.TokenCredential credential) { } public LogsQueryClient(System.Uri endpoint, Azure.Core.TokenCredential credential, Azure.Monitor.Query.LogsQueryClientOptions options) { } public static string CreateQuery(System.FormattableString filter) { throw null; } - public virtual Azure.Response Query(string workspace, string query, Azure.Core.DateTimeRange timeRange, Azure.Monitor.Query.LogsQueryOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public virtual System.Threading.Tasks.Task> QueryAsync(string workspace, string query, Azure.Core.DateTimeRange timeRange, Azure.Monitor.Query.LogsQueryOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public virtual System.Threading.Tasks.Task>> QueryAsync(string workspace, string query, Azure.Core.DateTimeRange timeRange, Azure.Monitor.Query.LogsQueryOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public virtual Azure.Response Query(string workspaceId, string query, Azure.Core.DateTimeRange timeRange, Azure.Monitor.Query.LogsQueryOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public virtual System.Threading.Tasks.Task> QueryAsync(string workspaceId, string query, Azure.Core.DateTimeRange timeRange, Azure.Monitor.Query.LogsQueryOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public virtual System.Threading.Tasks.Task>> QueryAsync(string workspaceId, string query, Azure.Core.DateTimeRange timeRange, Azure.Monitor.Query.LogsQueryOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual Azure.Response QueryBatch(Azure.Monitor.Query.LogsBatchQuery batch, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual System.Threading.Tasks.Task> QueryBatchAsync(Azure.Monitor.Query.LogsBatchQuery batch, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - public virtual Azure.Response> Query(string workspace, string query, Azure.Core.DateTimeRange timeRange, Azure.Monitor.Query.LogsQueryOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public virtual Azure.Response> Query(string workspaceId, string query, Azure.Core.DateTimeRange timeRange, Azure.Monitor.Query.LogsQueryOptions options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } public partial class LogsQueryClientOptions : Azure.Core.ClientOptions { @@ -124,15 +124,16 @@ public partial class LogsQueryResult internal LogsQueryResult() { } public Azure.Core.ResponseError Error { get { throw null; } } public Azure.Monitor.Query.Models.LogsQueryResultTable PrimaryTable { get { throw null; } } - public System.BinaryData Statistics { get { throw null; } } public System.Collections.Generic.IReadOnlyList Tables { get { throw null; } } - public System.BinaryData Visualization { get { throw null; } } + public System.BinaryData GetStatistics() { throw null; } + public System.BinaryData GetVisualization() { throw null; } } public partial class LogsQueryResultColumn { internal LogsQueryResultColumn() { } public string Name { get { throw null; } } public Azure.Monitor.Query.Models.LogsColumnType Type { get { throw null; } } + public override string ToString() { throw null; } } public partial class LogsQueryResultRow { @@ -164,6 +165,7 @@ internal LogsQueryResultRow() { } public System.TimeSpan GetTimeSpan(string name) { throw null; } public bool IsNull(int index) { throw null; } public bool IsNull(string name) { throw null; } + public override string ToString() { throw null; } } public partial class LogsQueryResultTable { @@ -172,6 +174,7 @@ internal LogsQueryResultTable() { } public string Name { get { throw null; } } public System.Collections.Generic.IReadOnlyList Rows { get { throw null; } } public System.Collections.Generic.IReadOnlyList Deserialize() { throw null; } + public override string ToString() { throw null; } } public partial class Metric { diff --git a/sdk/monitor/Azure.Monitor.Query/src/LogsQueryClient.cs b/sdk/monitor/Azure.Monitor.Query/src/LogsQueryClient.cs index 8f45527931c8..01b0ad053425 100644 --- a/sdk/monitor/Azure.Monitor.Query/src/LogsQueryClient.cs +++ b/sdk/monitor/Azure.Monitor.Query/src/LogsQueryClient.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; @@ -79,33 +79,61 @@ protected LogsQueryClient() } /// - /// Executes the logs query. + /// Executes the logs query. Deserializes the result into a strongly typed model class or a primitive type if the query returns a single column. + /// + /// Example of querying a model: + /// + /// Response<IReadOnlyList<MyLogEntryModel>> response = await client.QueryAsync<MyLogEntryModel>(workspaceId, + /// "AzureActivity | summarize Count = count() by ResourceGroup | top 10 by Count", + /// TimeSpan.FromDays(1)); + /// + /// + /// Example of querying a primitive: + /// + /// Response<IReadOnlyList<string>> response = await client.QueryAsync<string>(workspaceId, + /// "AzureActivity | summarize Count = count() by ResourceGroup | top 10 by Count | project ResourceGroup", + /// TimeSpan.FromDays(1)); + /// /// - /// The workspace to include in the query. + /// The workspace id to include in the query (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). /// The query text to execute. /// The timespan over which to query data. Logs would be filtered to include entries produced starting at Now - timeSpan. /// The to configure the query. /// The to use. /// Query results mapped to a type . - public virtual Response> Query(string workspace, string query, DateTimeRange timeRange, LogsQueryOptions options = null, CancellationToken cancellationToken = default) + public virtual Response> Query(string workspaceId, string query, DateTimeRange timeRange, LogsQueryOptions options = null, CancellationToken cancellationToken = default) { - Response response = Query(workspace, query, timeRange, options, cancellationToken); + Response response = Query(workspaceId, query, timeRange, options, cancellationToken); return Response.FromValue(RowBinder.Shared.BindResults(response.Value.Tables), response.GetRawResponse()); } /// - /// Executes the logs query. + /// Executes the logs query. Deserializes the result into a strongly typed model class or a primitive type if the query returns a single column. + /// + /// Example of querying a model: + /// + /// Response<IReadOnlyList<MyLogEntryModel>> response = await client.QueryAsync<MyLogEntryModel>(workspaceId, + /// "AzureActivity | summarize Count = count() by ResourceGroup | top 10 by Count", + /// TimeSpan.FromDays(1)); + /// + /// + /// Example of querying a primitive: + /// + /// Response<IReadOnlyList<string>> response = await client.QueryAsync<string>(workspaceId, + /// "AzureActivity | summarize Count = count() by ResourceGroup | top 10 by Count | project ResourceGroup", + /// TimeSpan.FromDays(1)); + /// /// - /// The workspace to include in the query. + /// The workspace id to include in the query (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). /// The query text to execute. /// The timespan over which to query data. Logs would be filtered to include entries produced starting at Now - timeSpan. /// The to configure the query. /// The to use. /// Query results mapped to a type . - public virtual async Task>> QueryAsync(string workspace, string query, DateTimeRange timeRange, LogsQueryOptions options = null, CancellationToken cancellationToken = default) + public virtual async Task>> QueryAsync(string workspaceId, string query, DateTimeRange timeRange, LogsQueryOptions options = null, CancellationToken cancellationToken = default) { - Response response = await QueryAsync(workspace, query, timeRange, options, cancellationToken).ConfigureAwait(false); + Response response = await QueryAsync(workspaceId, query, timeRange, options, cancellationToken).ConfigureAwait(false); return Response.FromValue(RowBinder.Shared.BindResults(response.Value.Tables), response.GetRawResponse()); } @@ -113,19 +141,19 @@ public virtual async Task>> QueryAsync(string works /// /// Executes the logs query. /// - /// The workspace to include in the query. + /// The workspace id to include in the query (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). /// The query text to execute. /// The timespan over which to query data. Logs would be filtered to include entries produced starting at Now - timeSpan. /// The to configure the query. /// The to use. /// The containing the query results. - public virtual Response Query(string workspace, string query, DateTimeRange timeRange, LogsQueryOptions options = null, CancellationToken cancellationToken = default) + public virtual Response Query(string workspaceId, string query, DateTimeRange timeRange, LogsQueryOptions options = null, CancellationToken cancellationToken = default) { using DiagnosticScope scope = _clientDiagnostics.CreateScope($"{nameof(LogsQueryClient)}.{nameof(Query)}"); scope.Start(); try { - return ExecuteAsync(workspace, query, timeRange, options, false, cancellationToken).EnsureCompleted(); + return ExecuteAsync(workspaceId, query, timeRange, options, false, cancellationToken).EnsureCompleted(); } catch (Exception e) { @@ -137,19 +165,19 @@ public virtual Response Query(string workspace, string query, D /// /// Executes the logs query. /// - /// The workspace to include in the query. + /// The workspace id to include in the query (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx). /// The query text to execute. /// The timespan over which to query data. Logs would be filtered to include entries produced starting at Now - timeSpan. /// The to configure the query. /// The to use. /// The with the query results. - public virtual async Task> QueryAsync(string workspace, string query, DateTimeRange timeRange, LogsQueryOptions options = null, CancellationToken cancellationToken = default) + public virtual async Task> QueryAsync(string workspaceId, string query, DateTimeRange timeRange, LogsQueryOptions options = null, CancellationToken cancellationToken = default) { using DiagnosticScope scope = _clientDiagnostics.CreateScope($"{nameof(LogsQueryClient)}.{nameof(Query)}"); scope.Start(); try { - return await ExecuteAsync(workspace, query, timeRange, options, true, cancellationToken).ConfigureAwait(false); + return await ExecuteAsync(workspaceId, query, timeRange, options, true, cancellationToken).ConfigureAwait(false); } catch (Exception e) { @@ -159,7 +187,31 @@ public virtual async Task> QueryAsync(string workspace } /// - /// Submits the batch query. + /// Submits the batch query. Use the to compose a batch query. + /// + /// var endpoint = new Uri("https://api.loganalytics.io"); + /// string workspaceId = "<workspace_id>"; + /// + /// var client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); + /// + /// // Query TOP 10 resource groups by event count + /// // And total event count + /// var batch = new LogsBatchQuery(); + /// + /// string countQueryId = batch.AddQuery(workspaceId, "AzureActivity | count", TimeSpan.FromDays(1)); + /// string topQueryId = batch.AddQuery(workspaceId, "AzureActivity | summarize Count = count() by ResourceGroup | top 10 by Count", TimeSpan.FromDays(1)); + /// + /// Response<LogsBatchQueryResults> response = await client.QueryBatchAsync(batch); + /// + /// var count = response.Value.GetResult<int>(countQueryId).Single(); + /// var topEntries = response.Value.GetResult<MyLogEntryModel>(topQueryId); + /// + /// Console.WriteLine($"AzureActivity has total {count} events"); + /// foreach (var logEntryModel in topEntries) + /// { + /// Console.WriteLine($"{logEntryModel.ResourceGroup} had {logEntryModel.Count} events"); + /// } + /// /// /// The batch of queries to send. /// The to use. @@ -183,7 +235,31 @@ public virtual Response QueryBatch(LogsBatchQuery batch, } /// - /// Submits the batch query. + /// Submits the batch query. Use the to compose a batch query. + /// + /// var endpoint = new Uri("https://api.loganalytics.io"); + /// string workspaceId = "<workspace_id>"; + /// + /// var client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); + /// + /// // Query TOP 10 resource groups by event count + /// // And total event count + /// var batch = new LogsBatchQuery(); + /// + /// string countQueryId = batch.AddQuery(workspaceId, "AzureActivity | count", TimeSpan.FromDays(1)); + /// string topQueryId = batch.AddQuery(workspaceId, "AzureActivity | summarize Count = count() by ResourceGroup | top 10 by Count", TimeSpan.FromDays(1)); + /// + /// Response<LogsBatchQueryResults> response = await client.QueryBatchAsync(batch); + /// + /// var count = response.Value.GetResult<int>(countQueryId).Single(); + /// var topEntries = response.Value.GetResult<MyLogEntryModel>(topQueryId); + /// + /// Console.WriteLine($"AzureActivity has total {count} events"); + /// foreach (var logEntryModel in topEntries) + /// { + /// Console.WriteLine($"{logEntryModel.ResourceGroup} had {logEntryModel.Count} events"); + /// } + /// /// /// The batch of queries to send. /// The to use. diff --git a/sdk/monitor/Azure.Monitor.Query/src/LogsQueryOptions.cs b/sdk/monitor/Azure.Monitor.Query/src/LogsQueryOptions.cs index d64fcb665d47..ea8c602cfd8d 100644 --- a/sdk/monitor/Azure.Monitor.Query/src/LogsQueryOptions.cs +++ b/sdk/monitor/Azure.Monitor.Query/src/LogsQueryOptions.cs @@ -20,14 +20,14 @@ public class LogsQueryOptions /// /// Gets or sets the value indicating whether to include query execution statistics as part of the response. - /// Statistics can be retrieved via the property. + /// Statistics can be retrieved via the method. /// Defaults to false. /// public bool IncludeStatistics { get; set; } /// /// Gets or sets the value indicating whether to include query visualization as part of the response. - /// Visualization can be retrieved via the property. + /// Visualization can be retrieved via the method. /// Defaults to false. /// public bool IncludeVisualization { get; set; } diff --git a/sdk/monitor/Azure.Monitor.Query/src/MetricsQueryClient.cs b/sdk/monitor/Azure.Monitor.Query/src/MetricsQueryClient.cs index 28bf77ef0432..4733317509ae 100644 --- a/sdk/monitor/Azure.Monitor.Query/src/MetricsQueryClient.cs +++ b/sdk/monitor/Azure.Monitor.Query/src/MetricsQueryClient.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System; @@ -24,7 +24,7 @@ public class MetricsQueryClient /// /// Initializes a new instance of . /// - /// The service endpoint to use. + /// The resource manager service endpoint to use. For example, https://management.azure.com/ for public cloud. /// The instance to use for authentication. public MetricsQueryClient(Uri endpoint, TokenCredential credential) : this(endpoint, credential, null) { @@ -33,7 +33,7 @@ public MetricsQueryClient(Uri endpoint, TokenCredential credential) : this(endpo /// /// Initializes a new instance of . /// - /// The service endpoint to use. + /// The resource manager service endpoint to use. For example https://management.azure.com/ for public cloud. /// The instance to use for authentication. /// The instance to as client configuration. public MetricsQueryClient(Uri endpoint, TokenCredential credential, MetricsQueryClientOptions options) @@ -59,6 +59,32 @@ protected MetricsQueryClient() /// /// Queries metrics for a resource. + /// + /// var endpoint = new Uri("https://management.azure.com"); + /// string resourceId = + /// "/subscriptions/<subscription_id>/resourceGroups/<resource_group_name>/providers/Microsoft.OperationalInsights/workspaces/<workspace_name>"; + /// + /// var metricsClient = new MetricsQueryClient(endpoint, new DefaultAzureCredential()); + /// + /// Response<MetricQueryResult> results = await metricsClient.QueryAsync( + /// resourceId, + /// new[] {"Microsoft.OperationalInsights/workspaces"} + /// ); + /// + /// foreach (var metric in results.Value.Metrics) + /// { + /// Console.WriteLine(metric.Name); + /// foreach (var element in metric.TimeSeries) + /// { + /// Console.WriteLine("Dimensions: " + string.Join(",", element.Metadata)); + /// + /// foreach (var metricValue in element.Data) + /// { + /// Console.WriteLine(metricValue); + /// } + /// } + /// } + /// /// /// The resource name. /// For example: /subscriptions/[subscription_id]/resourceGroups/[resource_group_name]/providers/Microsoft.OperationalInsights/workspaces/[workspace_name]. @@ -92,6 +118,32 @@ public virtual Response Query(string resourceId, IEnumerable< /// /// Queries metrics for a resource. + /// + /// var endpoint = new Uri("https://management.azure.com"); + /// string resourceId = + /// "/subscriptions/<subscription_id>/resourceGroups/<resource_group_name>/providers/Microsoft.OperationalInsights/workspaces/<workspace_name>"; + /// + /// var metricsClient = new MetricsQueryClient(endpoint, new DefaultAzureCredential()); + /// + /// Response<MetricQueryResult> results = await metricsClient.QueryAsync( + /// resourceId, + /// new[] {"Microsoft.OperationalInsights/workspaces"} + /// ); + /// + /// foreach (var metric in results.Value.Metrics) + /// { + /// Console.WriteLine(metric.Name); + /// foreach (var element in metric.TimeSeries) + /// { + /// Console.WriteLine("Dimensions: " + string.Join(",", element.Metadata)); + /// + /// foreach (var metricValue in element.Data) + /// { + /// Console.WriteLine(metricValue); + /// } + /// } + /// } + /// /// /// The resource name. /// For example: /subscriptions/[subscription_id]/resourceGroups/[resource_group_name]/providers/Microsoft.OperationalInsights/workspaces/[workspace_name]. @@ -232,4 +284,4 @@ private static string GetAggregation(MetricsQueryOptions options) return string.Join(",", options.Aggregations); } } -} \ No newline at end of file +} diff --git a/sdk/monitor/Azure.Monitor.Query/src/Models/LogsBatchQuery.cs b/sdk/monitor/Azure.Monitor.Query/src/Models/LogsBatchQuery.cs index eaaa8497535c..77bd05b00c3b 100644 --- a/sdk/monitor/Azure.Monitor.Query/src/Models/LogsBatchQuery.cs +++ b/sdk/monitor/Azure.Monitor.Query/src/Models/LogsBatchQuery.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System.Collections.Generic; @@ -25,6 +25,15 @@ public LogsBatchQuery() /// /// Adds the specified query to the batch. Results can be retrieved after the query is submitted via the call. + /// + /// string countQueryId = batch.AddQuery(workspaceId, "AzureActivity | count", TimeSpan.FromDays(1)); + /// string topQueryId = batch.AddQuery(workspaceId, "AzureActivity | summarize Count = count() by ResourceGroup | top 10 by Count", TimeSpan.FromDays(1)); + /// + /// Response<LogsBatchQueryResults> response = await client.QueryBatchAsync(batch); + /// + /// var count = response.Value.GetResult<int>(countQueryId).Single(); + /// var topEntries = response.Value.GetResult<MyLogEntryModel>(topQueryId); + /// /// /// The workspace to include in the query. /// The query text to execute. diff --git a/sdk/monitor/Azure.Monitor.Query/src/Models/LogsBatchQueryResults.cs b/sdk/monitor/Azure.Monitor.Query/src/Models/LogsBatchQueryResults.cs index c4a630746c77..d3fe9422ebef 100644 --- a/sdk/monitor/Azure.Monitor.Query/src/Models/LogsBatchQueryResults.cs +++ b/sdk/monitor/Azure.Monitor.Query/src/Models/LogsBatchQueryResults.cs @@ -23,6 +23,15 @@ public partial class LogsBatchQueryResults /// /// Gets the result for the query that was a part of the batch. /// + /// + /// string countQueryId = batch.AddQuery(workspaceId, "AzureActivity | count", TimeSpan.FromDays(1)); + /// string topQueryId = batch.AddQuery(workspaceId, "AzureActivity | summarize Count = count() by ResourceGroup | top 10 by Count", TimeSpan.FromDays(1)); + /// + /// Response<LogsBatchQueryResults> response = await client.QueryBatchAsync(batch); + /// + /// var count = response.Value.GetResult<int>(countQueryId).Single(); + /// var topEntries = response.Value.GetResult<MyLogEntryModel>(topQueryId); + /// /// The query identifier returned from the . /// The with the query results. /// When the query with was not part of the batch. @@ -39,7 +48,8 @@ public LogsQueryResult GetResult(string queryId) if (result.Body.HasFailed) { - throw new RequestFailedException(result.Status ?? 0, result.Body.Error.Message, result.Body.Error.Code, null); + var message = $"Batch query with id '{queryId}' failed.{Environment.NewLine}{result.Body.Error}"; + throw new RequestFailedException(result.Status ?? 0, message, result.Body.Error.Code, null); } return result.Body; diff --git a/sdk/monitor/Azure.Monitor.Query/src/Models/LogsQueryResultRow.cs b/sdk/monitor/Azure.Monitor.Query/src/Models/LogsQueryResultRow.cs index 2378ff9c75ee..76d563235d85 100644 --- a/sdk/monitor/Azure.Monitor.Query/src/Models/LogsQueryResultRow.cs +++ b/sdk/monitor/Azure.Monitor.Query/src/Models/LogsQueryResultRow.cs @@ -271,5 +271,11 @@ public object GetObject(int index) public object this[string name] => GetObject(name); internal bool TryGetColumn(string name, out int column) => _columnMap.TryGetValue(name, out column); + + /// + public override string ToString() + { + return _row.ToString(); + } } } \ No newline at end of file diff --git a/sdk/monitor/Azure.Monitor.Query/src/Models/LogsQueryResultTable.cs b/sdk/monitor/Azure.Monitor.Query/src/Models/LogsQueryResultTable.cs index ce228865fb9d..6e2b119edb10 100644 --- a/sdk/monitor/Azure.Monitor.Query/src/Models/LogsQueryResultTable.cs +++ b/sdk/monitor/Azure.Monitor.Query/src/Models/LogsQueryResultTable.cs @@ -48,5 +48,11 @@ public IReadOnlyList Deserialize() { return RowBinder.Shared.BindResults(new[] { this }); } + + /// + public override string ToString() + { + return $"{Name}: {Rows.Count} rows, {Columns.Count} columns"; + } } } \ No newline at end of file diff --git a/sdk/monitor/Azure.Monitor.Query/src/Models/LogsQueryResults.cs b/sdk/monitor/Azure.Monitor.Query/src/Models/LogsQueryResults.cs index fcde12061b17..f5fce17dd131 100644 --- a/sdk/monitor/Azure.Monitor.Query/src/Models/LogsQueryResults.cs +++ b/sdk/monitor/Azure.Monitor.Query/src/Models/LogsQueryResults.cs @@ -29,12 +29,12 @@ public partial class LogsQueryResult /// /// Returns the query statistics if the is set to true. Null otherwise. /// - public BinaryData Statistics => _statistics.ValueKind == JsonValueKind.Undefined ? null : new BinaryData(_statistics.ToString()); + public BinaryData GetStatistics() => _statistics.ValueKind == JsonValueKind.Undefined ? null : new BinaryData(_statistics.ToString()); /// /// Returns the query visualization if the is set to true. Null otherwise. /// - public BinaryData Visualization => _visualization.ValueKind == JsonValueKind.Undefined ? null : new BinaryData(_visualization.ToString()); + public BinaryData GetVisualization() => _visualization.ValueKind == JsonValueKind.Undefined ? null : new BinaryData(_visualization.ToString()); /// /// Get's the error that occured during query processing. The value would be null if the query succeeds. diff --git a/sdk/monitor/Azure.Monitor.Query/src/Models/QueryResultColumn.cs b/sdk/monitor/Azure.Monitor.Query/src/Models/QueryResultColumn.cs index 4f217431104e..0e1796323e3d 100644 --- a/sdk/monitor/Azure.Monitor.Query/src/Models/QueryResultColumn.cs +++ b/sdk/monitor/Azure.Monitor.Query/src/Models/QueryResultColumn.cs @@ -8,5 +8,10 @@ namespace Azure.Monitor.Query.Models [CodeGenModel("Column")] public partial class LogsQueryResultColumn { + /// + public override string ToString() + { + return $"{Name} ({Type})"; + } } } \ No newline at end of file diff --git a/sdk/monitor/Azure.Monitor.Query/src/RowBinder.cs b/sdk/monitor/Azure.Monitor.Query/src/RowBinder.cs index 780dfffd3457..98f098faf9c8 100644 --- a/sdk/monitor/Azure.Monitor.Query/src/RowBinder.cs +++ b/sdk/monitor/Azure.Monitor.Query/src/RowBinder.cs @@ -97,7 +97,8 @@ protected override bool TryGet(BoundMemberInfo memberInfo, LogsQueryResultRow else { - throw new NotSupportedException($"The {typeof(T)} type is not supported as a deserialization target."); + throw new NotSupportedException($"The {typeof(T)} type is not supported as a deserialization target. " + + "Supported types are string, bool, long, decimal, double, object, Guid, DateTimeOffset, TimeSpan, BinaryData."); } return true; diff --git a/sdk/monitor/Azure.Monitor.Query/tests/LogsClientSamples.cs b/sdk/monitor/Azure.Monitor.Query/tests/LogsClientSamples.cs index 68e1d48d2f81..4b372eafb60f 100644 --- a/sdk/monitor/Azure.Monitor.Query/tests/LogsClientSamples.cs +++ b/sdk/monitor/Azure.Monitor.Query/tests/LogsClientSamples.cs @@ -18,14 +18,14 @@ public async Task QueryLogsAsTable() { #region Snippet:QueryLogsAsTable #if SNIPPET - Uri endpoint = new Uri("https://api.loganalytics.io"); + var endpoint = new Uri("https://api.loganalytics.io"); string workspaceId = ""; #else Uri endpoint = TestEnvironment.LogsEndpoint; string workspaceId = TestEnvironment.WorkspaceId; #endif - LogsQueryClient client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); + var client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); Response response = await client.QueryAsync(workspaceId, "AzureActivity | top 10 by TimeGenerated", TimeSpan.FromDays(1)); LogsQueryResultTable table = response.Value.PrimaryTable; @@ -44,14 +44,14 @@ public async Task QueryLogsAsTablePrintAll() #region Snippet:QueryLogsPrintTable #if SNIPPET - Uri endpoint = new Uri("https://api.loganalytics.io"); + var endpoint = new Uri("https://api.loganalytics.io"); string workspaceId = ""; #else Uri endpoint = TestEnvironment.LogsEndpoint; string workspaceId = TestEnvironment.WorkspaceId; #endif - LogsQueryClient client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); + var client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); Response response = await client.QueryAsync(workspaceId, "AzureActivity | top 10 by TimeGenerated", TimeSpan.FromDays(1)); LogsQueryResultTable table = response.Value.PrimaryTable; @@ -83,19 +83,21 @@ public async Task QueryLogsAsPrimitive() #region Snippet:QueryLogsAsPrimitive #if SNIPPET - Uri endpoint = new Uri("https://api.loganalytics.io"); + var endpoint = new Uri("https://api.loganalytics.io"); string workspaceId = ""; #else Uri endpoint = TestEnvironment.LogsEndpoint; string workspaceId = TestEnvironment.WorkspaceId; #endif - LogsQueryClient client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); + var client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); // Query TOP 10 resource groups by event count + #region Snippet:QueryLogsAsPrimitiveCall Response> response = await client.QueryAsync(workspaceId, "AzureActivity | summarize Count = count() by ResourceGroup | top 10 by Count | project ResourceGroup", TimeSpan.FromDays(1)); + #endregion foreach (var resourceGroup in response.Value) { @@ -110,7 +112,7 @@ public async Task QueryLogsAsModels() { #region Snippet:QueryLogsAsModels - LogsQueryClient client = new LogsQueryClient(TestEnvironment.LogsEndpoint, new DefaultAzureCredential()); + var client = new LogsQueryClient(TestEnvironment.LogsEndpoint, new DefaultAzureCredential()); #if SNIPPET string workspaceId = ""; #else @@ -118,9 +120,11 @@ public async Task QueryLogsAsModels() #endif // Query TOP 10 resource groups by event count + #region Snippet:QueryLogsAsModelCall Response> response = await client.QueryAsync(workspaceId, "AzureActivity | summarize Count = count() by ResourceGroup | top 10 by Count", TimeSpan.FromDays(1)); + #endregion foreach (var logEntryModel in response.Value) { @@ -136,18 +140,20 @@ public async Task BatchQuery() #region Snippet:BatchQuery #if SNIPPET - Uri endpoint = new Uri("https://api.loganalytics.io"); + var endpoint = new Uri("https://api.loganalytics.io"); string workspaceId = ""; #else Uri endpoint = TestEnvironment.LogsEndpoint; string workspaceId = TestEnvironment.WorkspaceId; #endif - LogsQueryClient client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); + var client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); // Query TOP 10 resource groups by event count // And total event count - LogsBatchQuery batch = new LogsBatchQuery(); + var batch = new LogsBatchQuery(); + + #region Snippet:BatchQueryAddAndGet string countQueryId = batch.AddQuery(workspaceId, "AzureActivity | count", TimeSpan.FromDays(1)); string topQueryId = batch.AddQuery(workspaceId, "AzureActivity | summarize Count = count() by ResourceGroup | top 10 by Count", TimeSpan.FromDays(1)); @@ -155,6 +161,7 @@ public async Task BatchQuery() var count = response.Value.GetResult(countQueryId).Single(); var topEntries = response.Value.GetResult(topQueryId); + #endregion Console.WriteLine($"AzureActivity has total {count} events"); foreach (var logEntryModel in topEntries) @@ -170,14 +177,14 @@ public async Task QueryLogsWithTimeout() { #region Snippet:QueryLogsWithTimeout #if SNIPPET - Uri endpoint = new Uri("https://api.loganalytics.io"); + var endpoint = new Uri("https://api.loganalytics.io"); string workspaceId = ""; #else Uri endpoint = TestEnvironment.LogsEndpoint; string workspaceId = TestEnvironment.WorkspaceId; #endif - LogsQueryClient client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); + var client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); // Query TOP 10 resource groups by event count Response> response = await client.QueryAsync(workspaceId, @@ -201,14 +208,14 @@ public async Task BadRequest() { #region Snippet:BadRequest #if SNIPPET - Uri endpoint = new Uri("https://api.loganalytics.io"); + var endpoint = new Uri("https://api.loganalytics.io"); string workspaceId = ""; #else Uri endpoint = TestEnvironment.LogsEndpoint; string workspaceId = TestEnvironment.WorkspaceId; #endif - LogsQueryClient client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); + var client = new LogsQueryClient(endpoint, new DefaultAzureCredential()); try { diff --git a/sdk/monitor/Azure.Monitor.Query/tests/LogsQueryClientClientLiveTests.cs b/sdk/monitor/Azure.Monitor.Query/tests/LogsQueryClientClientLiveTests.cs index 69d34e1ea6c3..bcb152418871 100644 --- a/sdk/monitor/Azure.Monitor.Query/tests/LogsQueryClientClientLiveTests.cs +++ b/sdk/monitor/Azure.Monitor.Query/tests/LogsQueryClientClientLiveTests.cs @@ -487,7 +487,7 @@ public async Task ThrowsExceptionWhenQueryFailsBatch() var exception = Assert.Throws(() => batchResult.Value.GetResult(queryId)); Assert.AreEqual("BadArgumentError", exception.ErrorCode); - StringAssert.StartsWith("The request had some invalid properties", exception.Message); + StringAssert.StartsWith("Batch query with id '0' failed.", exception.Message); } [RecordedTest] @@ -520,12 +520,12 @@ public async Task CanQueryWithStatistics(bool include) if (include) { - using JsonDocument document = JsonDocument.Parse(response.Value.Statistics); + using JsonDocument document = JsonDocument.Parse(response.Value.GetStatistics()); Assert.Greater(document.RootElement.GetProperty("query").GetProperty("executionTime").GetDouble(), 0); } else { - Assert.AreEqual(default, response.Value.Statistics); + Assert.AreEqual(default, response.Value.GetStatistics()); } } @@ -543,12 +543,12 @@ public async Task CanQueryWithVisualization(bool include) if (include) { - using JsonDocument document = JsonDocument.Parse(response.Value.Visualization); + using JsonDocument document = JsonDocument.Parse(response.Value.GetVisualization()); Assert.AreNotEqual(JsonValueKind.Undefined, document.RootElement.GetProperty("visualization").ValueKind); } else { - Assert.AreEqual(default, response.Value.Visualization); + Assert.AreEqual(default, response.Value.GetVisualization()); } } @@ -569,12 +569,12 @@ public async Task CanQueryWithStatisticsBatch(bool include) if (include) { - using JsonDocument document = JsonDocument.Parse(result.Statistics); + using JsonDocument document = JsonDocument.Parse(result.GetStatistics()); Assert.Greater(document.RootElement.GetProperty("query").GetProperty("executionTime").GetDouble(), 0); } else { - Assert.AreEqual(default, result.Statistics); + Assert.AreEqual(default, result.GetStatistics()); } } diff --git a/sdk/monitor/Azure.Monitor.Query/tests/MetricsClientSamples.cs b/sdk/monitor/Azure.Monitor.Query/tests/MetricsClientSamples.cs index 355c55042f34..9d6d32b58e1e 100644 --- a/sdk/monitor/Azure.Monitor.Query/tests/MetricsClientSamples.cs +++ b/sdk/monitor/Azure.Monitor.Query/tests/MetricsClientSamples.cs @@ -18,7 +18,7 @@ public async Task QueryMetrics() #region Snippet:QueryMetrics #if SNIPPET - Uri endpoint = new Uri("https://management.azure.com"); + var endpoint = new Uri("https://management.azure.com"); string resourceId = "/subscriptions//resourceGroups//providers/Microsoft.OperationalInsights/workspaces/"; #else