<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>article Unified Logging for Databricks Notebooks and ADF with Azure Log Analytics in Technical Blog</title>
    <link>https://community.databricks.com/t5/technical-blog/unified-logging-for-databricks-notebooks-and-adf-with-azure-log/ba-p/64717</link>
    <description>&lt;P&gt;&lt;LI-TOC indent="15" liststyle="disc" maxheadinglevel="2"&gt;&lt;/LI-TOC&gt;&lt;/P&gt;
&lt;H1&gt;Introduction&lt;/H1&gt;
&lt;H2&gt;Logging in Azure Data Factory and Databricks Notebooks&lt;/H2&gt;
&lt;P&gt;Today we are looking at logging for Azure Data Factory (ADF) and Databricks Notebooks. Both of these tools separately have great solutions for logging, but they don't mesh well:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;ADF does not persist logs indefinitely unless you&amp;nbsp;specifically ask it to by &lt;A href="https://learn.microsoft.com/en-us/azure/data-factory/monitor-configure-diagnostics" target="_self"&gt;configuring diagnostic logging&lt;/A&gt;. You can send it to a &lt;FONT face="arial,helvetica,sans-serif"&gt;storage location&lt;/FONT&gt;, Log Analytics, or Event Hubs. Otherwise you can only see what happened from within ADF studio until the runs expire after 45 days.&lt;/LI&gt;
&lt;LI&gt;Databricks Notebooks just run code, so unless you set up &lt;A href="https://learn.microsoft.com/en-us/azure/databricks/compute/configure#--compute-log-delivery" target="_self"&gt;compute log delivery&lt;/A&gt; at the compute level (not very helpful in this case) which will export the &lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;stdout&lt;/FONT&gt;&lt;/STRONG&gt;, &lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;stderr&lt;/FONT&gt;&lt;/STRONG&gt;, and Spark logs, you will need some sort for custom logging solution based off &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;&lt;A href="https://logging.apache.org/log4j/2.x/" target="_self"&gt;log4j&lt;/A&gt;&lt;/STRONG&gt;&lt;/FONT&gt; or Python's &lt;STRONG&gt;&lt;A href="https://docs.python.org/3/library/logging.html" target="_self"&gt;&lt;FONT face="courier new,courier"&gt;logging&lt;/FONT&gt;&lt;/A&gt;&lt;/STRONG&gt; package.&amp;nbsp;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H2&gt;Solution Requirements&lt;/H2&gt;
&lt;P&gt;The best solution has the following requirements:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Persist all logs to the same location&lt;/LI&gt;
&lt;LI&gt;Persist all logs indefinitely&lt;/LI&gt;
&lt;LI&gt;Ensure the ADF logs for a given notebook and pipeline can be tied to the notebook logs&lt;/LI&gt;
&lt;LI&gt;Simple to set up and maintain&lt;/LI&gt;
&lt;/UL&gt;
&lt;H1&gt;Proposed Solution&lt;/H1&gt;
&lt;P&gt;The approach laid out in this article is to use ADF's native integration with Azure Log Analytics and then create a custom logging package using Python to send logs from Databricks Notebooks to Azure Log Analytics.&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Logging Overview.png" style="width: 695px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/6828iFC6FE04F0B7B078A/image-dimensions/695x534?v=v2" width="695" height="534" role="button" title="Logging Overview.png" alt="Logging Overview.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;This will enable us to note only keep track of the pipeline and notebook runs, but also link the logs in a given notebook to the corresponding logs for that notebook activity run in ADF.&lt;/P&gt;
&lt;H2&gt;Custom Logging Package&lt;/H2&gt;
&lt;P&gt;In order to send logs from Databricks Notebooks to Log Analytics we need client code running on Databricks which meets following requirements:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;Must run in Databricks notebooks&lt;/LI&gt;
&lt;LI&gt;Must be configurable for the Log Analytics instance details:
&lt;UL&gt;
&lt;LI&gt;
&lt;DIV&gt;&lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;LOGGING_WORKSPACE_ID:&lt;/STRONG&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;&amp;nbsp;the Log Analytics workspace ID&lt;/FONT&gt;&lt;/FONT&gt;&lt;/DIV&gt;
&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;&lt;FONT face="courier new,courier"&gt;&lt;SPAN&gt;&lt;STRONG&gt;LOGGING_WORKSPACE_KEY:&lt;/STRONG&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;&amp;nbsp;the Log Analytics workspace key&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/DIV&gt;
&lt;/LI&gt;
&lt;LI&gt;
&lt;DIV&gt;&lt;FONT face="courier new,courier"&gt;&lt;SPAN&gt;&lt;STRONG&gt;LOG_TABLE_NAME:&lt;/STRONG&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;&amp;nbsp;the table name logs will be pushed to in Log Analytics&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/DIV&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;/LI&gt;
&lt;LI&gt;&lt;FONT face="courier new,courier"&gt;&lt;SPAN&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;Must be configurable within a notebook for the pipeline and activity information so that we can link the notebook logs with the ADF logs for that notebook activity:&lt;BR /&gt;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/FONT&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;FONT face="courier new,courier"&gt;&lt;SPAN&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;pipeline run ID&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;FONT face="courier new,courier"&gt;&lt;SPAN&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;pipeline name&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;FONT face="courier new,courier"&gt;&lt;SPAN&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;activity name&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;FONT face="courier new,courier"&gt;&lt;SPAN&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;Prototype code that achieves this is provided in this&amp;nbsp;&lt;/FONT&gt;&lt;/SPAN&gt;&lt;/FONT&gt;&lt;A href="https://github.com/sdanielzafar/databricks_loganalytics" target="_self"&gt;Github Repo&lt;/A&gt;. Let's talk about the primary class, which is in &lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;log_analytics.py&lt;/FONT&gt;&lt;/STRONG&gt;:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="markup"&gt;from databricks_loganalytics.base_logging import log_console_output
from pyspark.sql import SparkSession, DataFrame
from databricks.sdk.runtime import *
from time import sleep
import os

global workspace_id
global workspace_key

workspace_id: str = os.environ.get('LOGGING_WORKSPACE_ID')
workspace_key: str = os.environ.get('LOGGING_WORKSPACE_KEY')

spark: SparkSession = SparkSession.builder.getOrCreate()

class notebook_logger:
    def __init__(self, pipeline_run_id: str, pipeline_name: str, activity_name: str):
        self.pipeline_run_id: str = pipeline_run_id
        self.pipeline_name: str = pipeline_name
        self.activity_name: str = activity_name


    def log_info(self, *messages) -&amp;gt; None:
        message: str = " ".join([str(m) for m in messages])
        dbx_body: dict[str, str] = [
            {
                "Message": message,
                "pipelineRunId_g": self.pipeline_run_id,
                "pipeline": self.pipeline_name,
                "activity": self.activity_name,
            }
        ]
        res: str = log_console_output(
            workspace_id=workspace_id,
            workspace_key=workspace_key,
            message=dbx_body,
        )
        print(message)
        if res != 'Accepted':
            print(res)
    

    def log_notebook_output(self, output: dict):

        def log_cmd_output(output):
            if isinstance(output, DataFrame):
                try:
                    out = output.collect()
                    if out:
                        outDict = out[0].asDict()
                        match len(outDict):
                            case 2:
                                operation: str = "INSERT"
                            case 4:
                                operation: str = "MERGE"
                            case 1:
                                operation: str = "DELETE or UPDATE"
                            case _:
                                operation: str = "UKNOWN"
                        if len(out) == 1:
                            message: str = f"{operation}: " + ", ".join([f"{op}: {str(cnt)}" for op, cnt in outDict.items()])
                            self.log_info(message)
                            sleep(0.1)
                except Exception as e:
                    # Handle any exceptions that may occur
                    print(f"WARNING: {output} not logged")
            elif isinstance(output, str):
                self.log_info(output)

        [log_cmd_output(values) for _, values in output.items()]


    def log_inserted_count(self, table):
        message: str = f"INSERTED {spark.read.table(table).count()}"
        self.log_info(message)
    
&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;Here we define the class &lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;notebook_logger&lt;/FONT&gt;&lt;/STRONG&gt;, in which the constructor takes input arguments which can be used to link logs to their respective ADF activity runs. The best way to approach this is to pass in values for these as notebook widgets (ADF parameters), where the &lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;activity_name&lt;/FONT&gt;&lt;/STRONG&gt; matches the name given to the ADF notebook activity. More on this later.&lt;/P&gt;
&lt;P&gt;The three methods provided by&amp;nbsp;&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;notebook_logger&lt;/FONT&gt;&lt;/STRONG&gt;&lt;FONT face="courier new,courier"&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;&amp;nbsp;offer different ways to send logs to Log Analytics:&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;FONT face="courier new,courier"&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;&lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;log_info&lt;/STRONG&gt;&lt;/FONT&gt;: passes a string to Log Analytics. Supports multiple arguments like python's&amp;nbsp;&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;print&lt;/FONT&gt;&lt;/STRONG&gt; does.&lt;/FONT&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;FONT face="courier new,courier"&gt;&lt;FONT face="courier new,courier"&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;log_notebook_output&lt;/FONT&gt;&lt;/STRONG&gt;: intended to be run at the bottom of the notebook. if called at the bottom of the notebook like so:&lt;BR /&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;LI-CODE lang="markup"&gt;logger.log_notebook_output(Out)&lt;/LI-CODE&gt;&lt;FONT face="courier new,courier"&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;where &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;Out&lt;/STRONG&gt;&lt;/FONT&gt; is a variable Databricks Notebooks puts ipython &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;stdout&lt;/STRONG&gt;&lt;/FONT&gt;, then any ipython &lt;STRONG&gt;stdout&lt;/STRONG&gt; will be sent to &lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;log_info&lt;/FONT&gt;&lt;/STRONG&gt;. This is really intended to send off the results of &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;MERGE&lt;/STRONG&gt;&lt;/FONT&gt;, &lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;UPDATE&lt;/FONT&gt;&lt;/STRONG&gt;, &lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;INSERT&lt;/FONT&gt;&lt;/STRONG&gt;, and &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;DELETE&lt;/STRONG&gt;&lt;/FONT&gt; operations.&lt;/FONT&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;FONT face="courier new,courier"&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;log_inserted_count&lt;/FONT&gt;&lt;/STRONG&gt;: a convenience method to be used after an&amp;nbsp;&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;INSERT&lt;/FONT&gt;.&lt;/STRONG&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H1&gt;Set Up&lt;/H1&gt;
&lt;H2&gt;Prerequisites&lt;/H2&gt;
&lt;UL&gt;
&lt;LI&gt;A Log Analytics workspace accessible by ADF and Azure Databricks.&lt;/LI&gt;
&lt;LI&gt;The Workspace ID and the Workspace key for this Log Analytics workspace.&lt;/LI&gt;
&lt;LI&gt;Permissions to set environment variables on your Databricks cluster.&lt;/LI&gt;
&lt;/UL&gt;
&lt;H2&gt;Steps&lt;/H2&gt;
&lt;OL&gt;
&lt;LI&gt;Integrate ADF and Log Analytics by&amp;nbsp;&lt;A href="https://learn.microsoft.com/en-us/azure/data-factory/monitor-configure-diagnostics" target="_self"&gt;configuring diagnostic logging&lt;/A&gt;&amp;nbsp;in your ADF instance. You will set the Log Analytics workspace. Make sure to check at least &lt;STRONG&gt;ActivityRuns&lt;/STRONG&gt;, &lt;STRONG&gt;PipelineRuns&lt;/STRONG&gt;, and &lt;STRONG&gt;TriggerRuns&lt;/STRONG&gt;.&lt;/LI&gt;
&lt;LI&gt;Test that is working by running a test pipeline and then executing the following query in Log Analytics:&lt;BR /&gt;&lt;LI-CODE lang="markup"&gt;AzureDiagnostics
| where ResourceId contains "&amp;lt;your-adf-resource-id&amp;gt;"​&lt;/LI-CODE&gt;&lt;/LI&gt;
&lt;LI&gt;Add the custom logger to Databricks:
&lt;OL&gt;
&lt;LI&gt;Add the custom logger to your Databricks cluster using the cluster UI.
&lt;OL&gt;
&lt;LI&gt;Put the &lt;A href="https://github.com/sdanielzafar/databricks_loganalytics/tree/main/dist" target="_self"&gt;.whl file here&lt;/A&gt; on DBFS&lt;/LI&gt;
&lt;LI&gt;Install the .whl file on your Databricks cluster (&lt;A href="https://docs.databricks.com/en/libraries/cluster-libraries.html#install-a-library-on-a-cluster" target="_self"&gt;reference&lt;/A&gt;)&lt;/LI&gt;
&lt;LI&gt;Set environment variables on your Databricks cluster (&lt;A href="https://docs.databricks.com/en/compute/configure.html#environment-variables" target="_self"&gt;reference&lt;/A&gt;). The &lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;LOG_TABLE_NAME&lt;/FONT&gt;&lt;/STRONG&gt; can be anything you prefer.&lt;BR /&gt;&lt;LI-CODE lang="markup"&gt;LOGGING_WORKSPACE_ID="&amp;lt;your-workspace-id&amp;gt;"
LOGGING_WORKSPACE_KEY="&amp;lt;your-workspace-key&amp;gt;"
LOG_TABLE_NAME="dbrx_notebook_logs"&lt;/LI-CODE&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;/LI&gt;
&lt;LI&gt;(Alternatively) If you don't want to deal with whl files and especially if you are using Git Folders (formerly Databricks Repos), you can add the code in &lt;A href="https://github.com/sdanielzafar/databricks_loganalytics/tree/main/databricks_loganalytics" target="_self"&gt;this directory&lt;/A&gt; to your Databricks workspace.&lt;/LI&gt;
&lt;LI&gt;Ensure you can import the code by running this Python code in a notebook:&lt;BR /&gt;&lt;LI-CODE lang="markup"&gt;from databricks_loganalytics.log_analytics import notebook_logger​&lt;/LI-CODE&gt;&lt;/LI&gt;
&lt;LI&gt;To ensure everything works, run this code in a Databricks Notebook:&lt;BR /&gt;&lt;LI-CODE lang="markup"&gt;from databricks_loganalytics.log_analytics import notebook_logger​

logger = notebook_logger("test", "test", "test")
logger.log_info("This is a test message")​&lt;/LI-CODE&gt;&lt;/LI&gt;
&lt;LI&gt;If everything is working properly you should be able to find this message in Log Analytics using this Kusto query (we set this from the environment variable above):&lt;BR /&gt;&lt;LI-CODE lang="markup"&gt;dbrx_notebook_logs​&lt;/LI-CODE&gt;
&lt;P&gt;If that doesn't work, you can also try "&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;dbrx_notebook_logs_CL&lt;/FONT&gt;&lt;/STRONG&gt;​".&lt;/P&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;/LI&gt;
&lt;/OL&gt;
&lt;H1&gt;Usage&lt;/H1&gt;
&lt;P&gt;At this point, if you've gotten the tests working you probably have a good idea of what's going on. Let's clear up any uncertainty with a real example.&amp;nbsp;If you are not familiar with passing parameters between ADF and Databricks notebooks, or rusty, you can &lt;A href="https://learn.microsoft.com/en-us/azure/data-factory/transform-data-using-databricks-notebook#create-a-pipeline" target="_self"&gt;review the docs here&lt;/A&gt;.&lt;/P&gt;
&lt;H2&gt;ADF Activity Parameters&lt;/H2&gt;
&lt;P&gt;Let's say you have an ADF pipeline activity that looks like this:&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="Dan_Z_3-1711642295309.png" style="width: 200px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/6852i798F40435E9621B4/image-size/small?v=v2&amp;amp;px=200" role="button" title="Dan_Z_3-1711642295309.png" alt="Dan_Z_3-1711642295309.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;You will need to set the following parameters for the Notebook Activity:&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;PIPELINE_RUN_ID&lt;/FONT&gt;&lt;/STRONG&gt;: set to the variable&amp;nbsp;&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;@pipeline().RunId&lt;/FONT&gt;&lt;/STRONG&gt;&lt;FONT face="courier new,courier"&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt; (dynamic)&lt;/FONT&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;PIPELINE_NAME&lt;/STRONG&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;:&amp;nbsp;&lt;/FONT&gt;&lt;/FONT&gt;set to variable&amp;nbsp;&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;@pipeline().Pipeline&lt;/FONT&gt;&lt;/STRONG&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;&amp;nbsp;(dynamic)&lt;/FONT&gt;&lt;/LI&gt;
&lt;LI&gt;&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;ACTIVITY_NAME&lt;/FONT&gt;&lt;/STRONG&gt;&lt;FONT face="courier new,courier"&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;: set to the string&amp;nbsp;&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;"LoadHistoricalDataIntoSmallTables".&lt;/FONT&gt;&lt;/STRONG&gt; &lt;/FONT&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;FONT face="courier new,courier"&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;&lt;FONT color="#000000"&gt;&lt;EM&gt;&lt;FONT face="courier new,courier"&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt;Important: &lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;ACTIVITY_NAME&lt;/STRONG&gt; &lt;/FONT&gt;must match the activity name in the ADF notebook activity, otherwise a query in Log Analytics will not be able to match up the logs between the two systems.&amp;nbsp;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/EM&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;H2&gt;Databricks Notebook Widgets&lt;/H2&gt;
&lt;P&gt;Your Databricks notebooks will look something like this:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;from databricks_loganalytics.log_analytics import notebook_logger

dbutils.widgets.text("PIPELINE_RUN_ID","","PIPELINE_RUN_ID")
dbutils.widgets.text("PIPELINE_NAME","","PIPELINE_NAME")
dbutils.widgets.text("ACTIVITY_NAME","","ACTIVITY_NAME")
PIPELINE_RUN_ID=dbutils.widgets.get("PIPELINE_RUN_ID")
PIPELINE_NAME=dbutils.widgets.get("PIPELINE_NAME")
ACTIVITY_NAME=dbutils.widgets.get("ACTIVITY_NAME")

logger = notebook_logger(PIPELINE_RUN_ID, PIPELINE_NAME, ACTIVITY_NAME)

# &amp;lt;some notebook code&amp;gt;

logger.log_info("some log message")

# &amp;lt;more notebook code&amp;gt;

# last cell
logger.log_notebook_output(Out)&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;EM&gt;Important note:&amp;nbsp;&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;Out&lt;/FONT&gt;&lt;/STRONG&gt; is a variable that Databricks Notebooks populates automatically, you will not need to define it and make sure not to overwrite it!&lt;/EM&gt;&lt;/P&gt;
&lt;H2&gt;Example Log Analytics Query&lt;/H2&gt;
&lt;P&gt;Here is a Kusto query you can run in Log Analytics to get all the logs for a given pipeline run and child pipeline activities. Make sure to fill out the&amp;nbsp;&lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;ResourceId&lt;/STRONG&gt;&lt;/FONT&gt; and a pipeline&amp;nbsp;&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;RunId&lt;/FONT&gt;&lt;/STRONG&gt;&lt;FONT face="arial,helvetica,sans-serif"&gt; (e.g. &lt;FONT face="courier new,courier"&gt;92d3f580-3cba-4feb-a1c1-c65bddd83b2f&lt;/FONT&gt;) &lt;/FONT&gt;with your own. Also we used&amp;nbsp;&lt;STRONG&gt;&lt;FONT face="courier new,courier"&gt;dbrx_notebook_logs&lt;/FONT&gt;&lt;/STRONG&gt;, but make sure to change it if you set the&amp;nbsp;&lt;FONT face="courier new,courier"&gt;&lt;STRONG&gt;LOG_TABLE_NAME&lt;/STRONG&gt;&lt;/FONT&gt; env variable to something else.&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="javascript"&gt;// --------------------------------------------------------
// Enter the run id below to get the related and child runs
// --------------------------------------------------------
let runid = "&amp;lt;a pipeline runid&amp;gt;";
// get the child pipeline run IDs
let relatedRunIds = AzureDiagnostics 
| where ResourceId contains "&amp;lt;your ResourceId&amp;gt;"
and Category == "PipelineRuns"
| mv-expand pred = todynamic(Predecessors_s)
| project rootRunId = coalesce(pred.PipelineRunId, runId_g), runId_g
| where rootRunId == runid
| union (print runId_g = runid)
| distinct runId_g;
// Run the main query
AzureDiagnostics 
| where ResourceId contains "&amp;lt;your ResourceId&amp;gt;"
and pipelineRunId_g in (relatedRunIds)
and (status_s  == "Succeeded" or status_s == "Failed") 
| join kind=leftouter dbrx_notebook_logs
on $left.pipelineRunId_g == $right.pipelineRunId_g_g 
    and $left.pipelineName_s == $right.pipeline_s
    and $left.activityName_s == $right.activity_s
| project TimeGenerated,pipelineRunId = coalesce(pipelineRunId_g, groupId_g),
  pipelineName_s, activityName_s, status_s,
  Message = coalesce(Message1, Message)
| where Message != "Accepted"
| order by TimeGenerated asc&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;The above query will:&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;Find all the child pipeline run IDs associated with the pipeline run ID you passed.&lt;/LI&gt;
&lt;LI&gt;Find all the ADF logs associated with those run IDs.&lt;/LI&gt;
&lt;LI&gt;Join them with the Databricks notebook logs.&lt;/LI&gt;
&lt;LI&gt;Massage them for a clean and ordered output.&lt;/LI&gt;
&lt;/OL&gt;
&lt;H1&gt;Conclusion&lt;/H1&gt;
&lt;P&gt;Congrats! You've set up logging! This solution is more of a proof-of-concept and the code provided will not be maintained long-term. Feel free to fork the code provided or copy it over to your codebase and add methods that achieve your own ends. I rolled everything into a .whl file for convenience, but plan to get your hands dirty!&amp;nbsp;According to the needs of your organization, expect to customize this custom logging code and make it your own.&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;BR /&gt;&lt;LI-TOC indent="15" liststyle="disc" maxheadinglevel="2"&gt;&lt;/LI-TOC&gt;&lt;/P&gt;</description>
    <pubDate>Mon, 13 May 2024 17:26:00 GMT</pubDate>
    <dc:creator>Dan_Z</dc:creator>
    <dc:date>2024-05-13T17:26:00Z</dc:date>
    <item>
      <title>Unified Logging for Databricks Notebooks and ADF with Azure Log Analytics</title>
      <link>https://community.databricks.com/t5/technical-blog/unified-logging-for-databricks-notebooks-and-adf-with-azure-log/ba-p/64717</link>
      <description>&lt;P&gt;How to get all your Azure Data Factory (ADF) and Databricks Notebooks logs in the same place for once!&lt;/P&gt;</description>
      <pubDate>Mon, 13 May 2024 17:26:00 GMT</pubDate>
      <guid>https://community.databricks.com/t5/technical-blog/unified-logging-for-databricks-notebooks-and-adf-with-azure-log/ba-p/64717</guid>
      <dc:creator>Dan_Z</dc:creator>
      <dc:date>2024-05-13T17:26:00Z</dc:date>
    </item>
  </channel>
</rss>

