analysis/log_analytics.py (67 lines of code) (raw):

# Copyright (c) Microsoft Corporation. # Licensed under the MIT license. """Functions to interact with a Log Analytics workspace""" import json import logging from dataclasses import dataclass from urllib.parse import quote import pandas as pd from azure.core.exceptions import HttpResponseError from azure.monitor.query import LogsQueryClient, LogsQueryStatus from .results import AnalysisMetadata @dataclass class LogAnalyticsWorkspace: """A Log Analytics workspace""" subscription_id: str resource_group: str workspace: str @property def resource_id(self) -> str: """Fully-qualified resource name for the Log Analytics workspace""" resource_type = "Microsoft.OperationalInsights/workspaces" return ( f"/subscriptions/{self.subscription_id}/resourceGroups/{self.resource_group}" f"/providers/{resource_type}/{self.workspace}" ) def query_df( credential, workspace: LogAnalyticsWorkspace, query: str, *, timespan ) -> pd.DataFrame: """Download the results of a Log Analytics query as a pandas DataFrame.""" client = LogsQueryClient(credential) try: response = client.query_resource( workspace.resource_id, query, timespan=timespan ) if response.status == LogsQueryStatus.SUCCESS: data = response.tables else: logging.warning(response.partial_error) data = response.partial_data except HttpResponseError as err: logging.error(err) raise return pd.DataFrame(data=data[0].rows, columns=data[0].columns) def _blade_url( base_url: str, *, extension: str, view: str, blade_inputs: dict[str, str] ): url = base_url + f"/#blade/{extension}/{view}" for k, v in blade_inputs.items(): url += f'/{k}/{quote(v, safe="")}' return url def analysis_workbook_url( workspace: LogAnalyticsWorkspace, analysis: AnalysisMetadata ) -> str: """Generate URL to the Experiment Analysis workbook under the Log Analytics workspace resource, passing workbook parameters for the specific analysis. """ workbook_name = "Experiment Analysis" workbook_params = { "Workspace": workspace.resource_id, "TimeRange": { # Time range of this analysis is known, but there might be later analyses. # Best effort is a long time range (90 days) and recognize the URL becomes stale. "durationMs": 7776000000 }, "FeatureName": analysis.feature_flag, "AllocationId": analysis.allocation_id, } return _blade_url( "https://portal.azure.com", extension="AppInsightsExtension", view="WorkbookViewerBlade", blade_inputs={ "ComponentId": workspace.resource_id, "ConfigurationId": f"Community-Workbooks/Online Experimentation/{workbook_name}", "WorkbookTemplateName": workbook_name, "NotebookParams": json.dumps(workbook_params), }, )