Resource-centric log queries with the Azure Monitor Query libraries

Scott Addie

In October 2021, the Azure SDK team shipped the initial stable release of the Azure Monitor Query client libraries for .NET, Java, JavaScript, and Python. As adoption of the libraries increased, customer feedback indicated demand for a Go library. In the interest of being customer-driven, the team shipped the Go library’s initial stable release in February 2023.

Beyond requests to support extra programming languages, customer feedback also provided a strong signal on shortcomings of the existing log query APIs. The libraries restricted log queries to the workspace-centric style—read-only Kusto queries that target one or more Log Analytics workspaces by workspace ID. As explained on GitHub in this .NET issue and this Python issue, there are situations in which this query style doesn’t suffice. To address this problem, resource-centric log query APIs are needed. Resource-centric queries target logs for a specific Azure resource by its resource ID.

Resource-centric log query APIs are available as of the following package versions:

Ecosystem Minimum package version
.NET 1.2.0
Go 1.1.0
Java 1.2.0
JavaScript 1.1.0
Python 1.2.0

For demonstration purposes, the .NET library is used for code samples in this blog post.

Pitfalls with workspace-centric log queries

Imagine a scenario in which you need to query incoming HTTP request logs for a specific Azure App Service resource. There are a couple things to note:

  • A Log Analytics workspace may contain data for multiple Azure resources. To handle that situation, you can filter by the standard _ResourceId column in your Kusto query. For example:
    var resourceId = _configuration["LogsQuery:ResourceId"]!;
    var workspaceId = _configuration["LogsQuery:WorkspaceId"]!;
    var query = 
        $@"AppServiceHTTPLogs
        | where _ResourceId =~ ""{resourceId}""
        | top 5 by TimeGenerated
        | project ComputerName, Result";
    
    DateTimeOffset.TryParse("5/24/2023 03:00:00 PM +00:00", out var timeStart);
    DateTimeOffset.TryParse("5/24/2023 06:00:00 PM +00:00", out var timeEnd);
    
    Response<IReadOnlyList<HttpLogEntry>> result = 
        await _logsQueryClient.QueryWorkspaceAsync<HttpLogEntry>(
            workspaceId,
            query,
            new QueryTimeRange(timeStart, timeEnd));
  • An Azure resource may send data to multiple Log Analytics workspaces. To handle that situation, you can execute the same Kusto query across multiple workspaces. For example:
    var resourceId = _configuration["LogsQuery:ResourceId"]!;
    var workspaceId1 = _configuration["LogsQuery:WorkspaceId1"]!;
    var workspaceId2 = _configuration["LogsQuery:WorkspaceId2"]!;
    var workspaceId3 = _configuration["LogsQuery:WorkspaceId3"]!;
    var query = 
        $@"AppServiceHTTPLogs
        | where _ResourceId =~ ""{resourceId}""
        | top 5 by TimeGenerated
        | project ComputerName, Result";
    
    DateTimeOffset.TryParse("5/24/2023 03:00:00 PM +00:00", out var timeStart);
    DateTimeOffset.TryParse("5/24/2023 06:00:00 PM +00:00", out var timeEnd);
    
    Response<IReadOnlyList<HttpLogEntry>> result =
        await _logsQueryClient.QueryWorkspaceAsync<HttpLogEntry>(
            workspaceId1,
            query,
            new QueryTimeRange(timeStart, timeEnd),
            new LogsQueryOptions
            {
                AdditionalWorkspaces = { workspaceId2, workspaceId3 }
            });

In many applications, access control restrictions prevent the aforementioned solutions from working. A Log Analytics administrator can query the entire workspace. However, an Azure resource owner can only query logs for their own resources. For more information, see Manage access to Log Analytics workspaces. When this workspace-centric query approach doesn’t suffice, consider a resource-centric query instead.

A primer on resource-centric log queries

The following example demonstrates using the .NET library in an ASP.NET Core 7.0 Razor Pages web app. Complete the following steps to query a single Azure resource’s logs:

  1. In the Program.cs file, invoke the AddLogsQueryClient and UseCredential methods:
    using Azure.Identity;
    using Microsoft.Extensions.Azure;
    
    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services.AddRazorPages();
    builder.Services.AddAzureClients(clientBuilder =>
    {
        clientBuilder.AddLogsQueryClient();
        clientBuilder.UseCredential(new DefaultAzureCredential());
    });
    
    var app = builder.Build();
    
    // code omitted for brevity

    NOTE: The AddLogsQueryClient method used in the preceding code sample is a new feature as of the 1.2.0 version.

    In the preceding code:

    • The AddLogsQueryClient extension method registers a LogsQueryClient instance with the dependency injection container.
    • The UseCredential method sets the Azure Active Directory token-based credential type to use when creating the LogsQueryClient. That credential type is DefaultAzureCredential from the Azure Identity client library.
  2. Query the resource via a call to QueryResource or QueryResourceAsync:
    using Azure;
    using Azure.Core;
    using Azure.Monitor.Query;
    using Microsoft.AspNetCore.Mvc.RazorPages;
    
    public class IndexModel : PageModel
    {
        private readonly IConfiguration _configuration;
        private readonly LogsQueryClient _logsQueryClient;
    
        public IndexModel(
            IConfiguration configuration,
            LogsQueryClient logsQueryClient)
        {
            _configuration = configuration;
            _logsQueryClient = logsQueryClient;
        }
    
        public async Task OnGet()
        {
            var resourceId = _configuration["LogsQuery:ResourceId"]!;
            var query = 
                @"AppServiceHTTPLogs
                | top 5 by TimeGenerated
                | project ComputerName, Result";
    
            DateTimeOffset.TryParse("5/24/2023 03:00:00 PM +00:00", out var timeStart);
            DateTimeOffset.TryParse("5/24/2023 06:00:00 PM +00:00", out var timeEnd);
    
            Response<IReadOnlyList<HttpLogEntry>> result = 
                await _client.QueryResourceAsync<HttpLogEntry>(
                    new ResourceIdentifier(resourceId),
                    query,
                    new QueryTimeRange(timeStart, timeEnd));
    
            foreach (HttpLogEntry entry in result.Value)
            {
                Console.WriteLine($"{entry.ComputerName}: {entry.Success}");
            }
    
            // code omitted for brevity
        }
    }
    
    public class HttpLogEntry
    {
        public required string ComputerName { get; set; }
        public required string Success { get; set; }
    }

    In the preceding code, the LogsQuery:ResourceId key is used to fetch the Azure resource ID from appsettings.json:

    {
      "LogsQuery": {
        "ResourceId": "/subscriptions/<subscription_id>/resourceGroups/<resource_group_name>/providers/Microsoft.Web/sites/<web_app_name>"
      },
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      },
      "AllowedHosts": "*"
    }

    To find that resource ID:

    1. Navigate to your resource’s page in the Azure portal.
    2. From the Overview blade, select the JSON View link.
    3. In the resulting window, copy the value in the Resource ID text box.

Summary

With support for both workspace- and resource-centric log queries, the Azure Monitor Query libraries provide more flexibility than ever before. Programmatically fetch your log data with ease to gain insights into what’s happening in your Azure applications.

For language-specific code samples demonstrating resource-centric log queries, see the following links:

Any feedback you have on how we can improve the libraries is greatly appreciated. Let’s have those conversations on GitHub at these locations:

0 comments

Discussion is closed.

Feedback usabilla icon