How to build Azure Log Analytics URL with KQL Query?
Tags: azure, analytics, insights, kql
Published: 2021-12-08 05:48:10

This is a requirement I had for one of my projects where we retrieve app insights data from several azure web apps on their flow performances. I used App Insights API for posting the KQL query and the timespan to get the flow performances which are logged using custom dimensions in the specific insights' logs.

I also had a requirement where I had to give the users who are seeing the data, the ability to see the query and timespan I used for retrieving the data they are seeing. There is no direct way Azure gives you this ability. So, I researched the Log Analytics 'Share' functionality on how they are sharing the queries in the URLs.

Here is the format they are using,
https://portal.azure.com/#blade/Microsoft_Azure_Monitoring_Logs/LogsBlade/resourceId/%2Fsubscriptions%2F{subscriptionId}%2FresourceGroups%2F{rg}%2Fproviders%2Fmicrosoft.insights%2Fcomponents%2F{resourcename}/source/LogsBlade.AnalyticsShareLinkToQuery/query/{query}

The values you see inside {} are to be replaced with actuals. However, for {query}, it does not take the KQL directly.

I eventually found that they are converting the KQL into Base64 bytes and compressing them. They have this property by default set to true => isQueryBase64Compressed=true in the query string.

So, the URL would become like this,
https://portal.azure.com/#blade/Microsoft_Azure_Monitoring_Logs/LogsBlade/resourceId/%2Fsubscriptions%2F{subscriptionId}%2FresourceGroups%2F{rg}%2Fproviders%2Fmicrosoft.insights%2Fcomponents%2F{resourcename}/source/LogsBlade.AnalyticsShareLinkToQuery/query/{query}/isQueryBase64Compressed/true

OK, Here is the code for that KQL query conversion to fit into this URL.

```
var queryBytes = Encoding.UTF8.GetBytes(query);
var memoryStream = new MemoryStream();
var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress);

gZipStream.Write(queryBytes, 0, queryBytes.Length);
gZipStream.Close();

var compressedData = memoryStream.ToArray();

var encodedText = Convert.ToBase64String(compressedData);
encodedText = encodedText.Replace("/", "%2F");
encodedText = encodedText.Replace("+", "%2B");
encodedText = encodedText.Replace("=", "%3D");

var portalUrl = "https://portal.azure.com/#@sample.onmicrosoft.com/resource/subscriptions/guid-guid-guid-guid-guid/resourceGroups/rg-name/providers/microsoft.insights/components/app-insights-name/overview";

var (subscriptionId, rg, resourcename) = GetAzureResourceDetailsFromLogUrl(portalUrl);

url = "https://portal.azure.com/#blade/" + $"Microsoft_Azure_Monitoring_Logs/LogsBlade/resourceId/%2Fsubscriptions%2F{subscriptionId}%2FresourceGroups%2F{rg}%2Fproviders%2Fmicrosoft.insights%2Fcomponents%2F{resourcename}/source/LogsBlade.AnalyticsShareLinkToQuery/query/{encodedText}/isQueryBase64Compressed/true";

```

**Additional Perks**
Here is an additional perk for getting the Subscription Id, Resource Group Name, and the Resource Name from an Azure App Insights resource URL.

```

private static (string, string, string) GetAzureResourceDetailsFromLogUrl(string portalUrl)
{
  var uri = new Uri(portalUrl.Replace("#", ""));

  var subscriptionId = uri.Segments[uri.Segments.ToList().FindIndex(f => f.Equals(SubscriptionsUrlSegment, StringComparison.OrdinalIgnoreCase)) + 1].Replace("/", "");

  var rg = uri.Segments[uri.Segments.ToList().FindIndex(f => f.Equals(RGUrlSegment, StringComparison.OrdinalIgnoreCase)) + 1].Replace("/", "");

  var resourceName = uri.Segments[uri.Segments.ToList().FindIndex(f => f.Equals(ComponentUrlSegment, StringComparison.OrdinalIgnoreCase)) + 1].Replace("/", "");

  return (subscriptionId, rg, resourceName);
}
        
```