Mastodon

Deep linking to queries in Application Insights from Python

Deep linking to queries in Application Insights with Python

I recently wanted to be able to create deep links to a specific query in Application Insights.

For example, you might want to share a specific query with a colleague or include a link in some system documentation. In these scenarios the portal has a built-in Share features that is useful:

alt text

This gives you a link in this form: https://portal.azure.com#@<TENANT_ID>/blade/Microsoft_OperationsManagementSuite_Workspace/ Logs.ReactView/resourceId/%2Fsubscriptions%2F<SUBSCRIPTION_ID>%2F resourceGroups%2F<RESOURCE_GROUP>%2Fproviders%2Fmicrosoft.insights%2Fcomponents%2F <APP_INSIGHTS_NAME>/source/LogsBlade.AnalyticsShareLinkToQuery/q/ H4sIAAAAAAAAA0suLS7Jz%252FVNLSnKTC7mqlFIySwuycxLLlHIS8xNBQDgfzhlHQAAAA%253D%253D/timespan/P1D

In my case, I wanted to dynamically generate these links from some Python code that I was writing. I found this StackOverflow post where someone helpfully suggested that there are two approaches to this: a plain text query and a compressed query. (I also found this blog post which shows how to implement one of these approaches in C#)

Simple solution - “Plain text”

The simplest way to generate a deep link is to use the plain text query option. Actually, it’s not quite “plain text”, rather it is a URL-encoded version of the query embedded in the URL.

The following Python function will generate a deep link for you:

import urllib.parse

def get_simple_link(
        tenant_id: str,
        subscription_id: str,
        resource_group_name: str,
        app_insights_name: str,
        query: str,
        timespan: str = "P1D"
        ):
    # Set safe to empty string to ensure slashes are encoded
    encodedQuery = urllib.parse.quote(query, safe="")
    return (f"https://portal.azure.com#@{tenant_id}/blade/Microsoft_Azure_Monitoring_Logs/" +
            f"LogsBlade/resourceId/%2Fsubscriptions%2F{subscription_id}%2FresourceGroups%2F" +
            f"{resource_group_name}%2Fproviders%2Fmicrosoft.insights%2Fcomponents%2F" +
            f"{app_insights_name}/source/LogsBlade.AnalyticsShareLinkToQuery/query/{encodedQuery}" +
            f"/timespan/{timespan}")

To call this function we need to pass in the various parameters that are needed to construct the URL (e.g. the Tenant ID).

Here is an example of how you might use this function:

# Assuming tenant_id, subscription_id etc are defined as variables...

query= """customMetrics
| where timestamp > ago(15m) and name == "my.custom.metric"
| summarize mean = sum(valueSum) / sum(valueCount) by bin(timestamp, 1m), cloud_RoleName
| render timechart 

customMetrics | count
"""

link = get_simple_link(tenant_id, subscription_id, resource_group_name, app_insights_name, query)

# Use the link as needed...

One thing you may have noticed is that the query text that the above example is creating a link for actually contains 2 queries. Even if you have multiple queries in the editor in the Portal, the “Copy link to query” option in the portl only generates a link for the currently selected query. By generating the link ourselves we can include multiple queries in the link, which can be useful in some scenarios.

Compressed queries

If you try out the links generated by the approach above, you might notice that the links get large quite quickly (especially compared to the links that the portal generates). The second option that was mentioned in the StackOverflow post is to use a compressed query. In this case, we GZip the query before base64 encoding it which results in a shorter URL.

Here is a Python function that will generate a deep link using this approach:

import base64
import io
import urllib.parse
from gzip import GzipFile

def get_compressed_link(
        tenant_id: str,
        subscription_id: str,
        resource_group_name: str,
        app_insights_name: str,
        query: str,
        timespan: str = "P1D"
):
    # Get the UTF8 bytes for the query
    query_bytes = query.encode('utf-8')

    # GZip the query bytes
    bio_out = io.BytesIO()
    with GzipFile(mode='wb', fileobj=bio_out) as gzip:
        gzip.write(query_bytes)

    # Base64 encode the result
    zipped_bytes = bio_out.getvalue()
    base64_query = base64.b64encode(zipped_bytes)

    # URL encode the base64 encoded query
    encodedQuery = urllib.parse.quote(base64_query, safe="")

    return (f"https://portal.azure.com#@{tenant_id}/blade/Microsoft_Azure_Monitoring_Logs/" + 
            f"LogsBlade/resourceId/%2Fsubscriptions%2F{subscription_id}%2FresourceGroups%2F" + 
            f"{resource_group_name}%2Fproviders%2Fmicrosoft.insights%2Fcomponents%2F" + 
            f"{app_insights_name}/source/LogsBlade.AnalyticsShareLinkToQuery/q/{encodedQuery}"+
            f"/timespan/{timespan}")

We can call this function in the same way as the previous one:

# Assuming tenant_id, subscription_id etc are defined as variables...

query= """customMetrics
| where timestamp > ago(15m) and name == "my.custom.metric"
| summarize mean = sum(valueSum) / sum(valueCount) by bin(timestamp, 1m), cloud_RoleName
| render timechart 

customMetrics | count
"""

link = get_compressed_link(tenant_id, subscription_id, resource_group_name, app_insights_name, query)

# Use the link as needed...

For the query examples shown, the query portion of the URL is 307 characters for the uncompressed query and 237 for the compressed one. It’s not a huge saving, but as the queries get longer I imagine the difference would increase.

Conclusion

In this post, we’ve seen two approaches for creating deep links to queries in Application Insights. This can be useful if you want to dynamically generate queries in a script or your code and then make it easy for someone to open and run it.