I build security programs and defend networks. Sometimes with Python or Azure Functions.
Debugging Azure Functions can be extremely easy if you add the appropriate logging and error handling to your code. However, there are going to be times where debugging with these techniques is simply not enough. As a result, we need a robust debugging environment to trace a transaction end to end. Before I get to the environment setup, I want to first outline the high-level transaction flow (as I understand it) for Azure Functions. Please refer to the diagram below, which was posted in March 2018 in the Azure Function Python Worker wiki here:
Overall, the Azure Functions Host is the host/runtime that powers Azure Functions apps. Starting at the top of the diagram, incoming HTTP transactions are first routed to the WebHost, which is a sub-component of Azure Functions Host. The WebHost processes the HTTP request and sends the transactions to the Azure Functions Python Worker aka the Python Worker. The Python Worker then routes the transaction to the appropriate function app.
It is apparent from this diagram to appropriately debug an Azure Function; you need to insert yourself into the Azure Functions Host, which is a dotnet core app written in C#. Please note that for the purposes of this tutorial, I am focused on debugging HTTP Trigger function apps. This tutorial assumes that you have a working understanding of Azure Functions and VS Code.
There are two pre-requisites necessary to build and debug Azure Functions Host: dotnet core 2.x and Visual Studio (VS) Code.
dotnet core 2.2.x:
openssl sha512 dotnet-sdk-2.2.207-osx-x64.pkg
The following steps help create a stand-alone virtual environment where you can independently debug function apps separate from your other function app development environments. To begin, create a virtual environment and supporting folder structure:
python3 -m venv .afhvenv cd .afhvenv/ source bin/activate mkdir afdebugenv cd afdebugenv mkdir functions
As you will see later in the article, following the above folder structure will allow you to debug multiple versions of the Azure Functions Host at the same time.
Download source code of Azure Functions Host’s latest production release. Please note: you can, of course, download any branch you want from GitHub; however, I want what’s running in production since non-production branches may not build successfully. From the root of the afdebugenv run the following commands:
wget https://github.com/Azure/azure-functions-host/archive/2.0.12888.zip unzip 2.0.12888.zip
When finished, you will build the debug version of the Azure
Function Host from source using the dotnet executable from the command line:
cd azure-functions-host-2.0.12888 dotnet build -c Debug WebJobs.Script.sln
This build will take a few minutes to complete and should build with no errors (even on my MacBook Pro).
Symbol files hold a variety of data that are not actually needed when running the binaries, but which could be very useful in the debugging process. Symbol files are specific for each version of the software, so we are going to create a symbol folder in our Azure Functions Host folder. From the root of the version of Azure Functions Host that you are debugging run the following commands:
mkdir symbols wget https://github.com/Azure/azure-functions-host/releases/download/2.0.12888/Functions.Symbols.2.0.12888.win-x64.zip unzip Functions.Symbols.2.0.12888.win-x64.zip
The Azure Functions Host ships with a python worker which we will use for debugging purposes. Please note: the below path is specific to my setup (OS X), the path to your worker.py will be some variation after “
”. From the worker directory, run the following commands:
cd /azure-functions-host-2.0.12888/src/WebJobs.Script.WebHost/bin/Debug/netcoreapp2.2/workers/python/3.7/OSX/X64 wget https://gist.githubusercontent.com/gattjoe/2b2e77a14cd461a131eee9ebc1539f0d/raw/a1c52c3bfb5be87b21bcdfd1e09fbea787ae06ed/worker.config.json
After you download
to the worker directory, you have to configure the python worker to run in debug mode when it is called by VS Code. For some background on the proper format of the
file, refer to this document. To enable debugging on the worker.py process, add the following directive to
"Arguments": ["-m ptvsd --host 127.0.0.1 --port 9091"]
Launch.json and tasks.json need to be customized to properly build and debug the Azure Functions Host. I am providing example files that will allow you to get up and running much quicker, which is why I was so specific as to the naming of directories above. Once you are comfortable, feel free to customize the
file to meet your needs. At the root of the afdebugenv folder, run the following commands:
mkdir .vscode cd .vscode wget https://gist.githubusercontent.com/gattjoe/ce03f5d7ea8294246efabaf048eb1c39/raw/820109fcdbd72b407c49b2c6e7aa9d68db03318a/tasks.json wget https://gist.githubusercontent.com/gattjoe/47b9e54f25d1d21800f02dd518d86bbb/raw/177908cf773e27f2936458200ba14484d6a55d4e/launch.json
When finished, open VS Code from the afdebugenv directory. You
may get a notice that says, “Required assets to build and debug are missing
from ‘afdebugenv’. Add them?”. Say yes.
We are effectively running the entire Azure Function stack locally, so we need to install ptvsd and Azure functions. Run the following command:
pip install ptvsd azure-functions
If you made it this far, kudos to you. I thought about automating this entire process, but I figured that given the rapid pace of change in this space, it would be obsolete within a few months. Assuming you have a plain vanilla HTTP Trigger function app in your functions directory, we are going to run a short test below to ensure your environment is ready to start debugging. From the root of your afdebugenv directory, open up VS Code and perform the following steps:
”. Set a breakpoint in the left-hand gutter, which will be marked by a red dot
public async Task Invoke(HttpContext context)
Please note: Stopping the VS Code debugger will NOT kill the worker.py process spawned by Azure Functions Host. As a result, you have to manually kill the process to free ports TCP 5000 back up. Please note x2: you have to stop the VS Code debugger FIRST, otherwise, the Azure Functions Host will continually spawn up worker.py processes every time you kill it.
In future articles, I will be covering advanced debugging topics that rely on this article as a foundation. If you have any feedback, please send me a note at gattjoseph at <hotmail>.