Setting up a Debugging Environment for Azure Functions 2.x using VS Code
Tags: python, serverless, azurefunctions, azure
Published: 2020-01-18 02:52:02

### Overview

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](https://github.com/Azure/azure-functions-python-worker/wiki/Worker-Architecture):

![Architecture](https://thepracticaldev.s3.amazonaws.com/i/2u91at3fhf98ylkqiqpu.png "Azure Functions Architecture")

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.

### Software Pre-Requisites

There are two pre-requisites necessary to build and debug Azure Functions Host: dotnet core and Visual Studio (VS) Code.
1. dotnet core 2.2.x:
  * Download dotnetcore-sdk 2.x from [here](https://dotnet.microsoft.com/download/dotnet-core/2.2)
  * Validate the checksum of the download by running:
  ```Openssl sha512 dotnet-sdk-2.2.207-osx-x64.pkg```
2. VS Code:
  * Download VS Code from [here](https://code.visualstudio.com/download)
  * Install the C# and Python extensions

### Environment Setup

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:

```bash
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.

### Azure Functions Host Source Code

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:

```bash
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:

```bash
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).

### Azure Functions Host Symbol Files

[Symbol files](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/symbols-and-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:

```bash
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
```

### Azure Functions Python Worker

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 “../netcoreapp2.2/workers..”. From the worker directory, run the following commands:

```bash
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 worker.config.json 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 worker.config.json file, refer to this [document](https://github.com/Azure/azure-functions-host/wiki/Authoring-&-Testing-Language-Extensions). To enable debugging on the worker.py process, add the following directive to worker.config.json:

```json
"Arguments": ["-m ptvsd --host 127.0.0.1 --port 9091"]
```

### VS Code Setup

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 launch.json file to meet your needs. At the root of the afdebugenv folder, run the following commands:

```bash
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.

### Install Additional Python Packages

We are effectively running the entire Azure Function stack locally, so we need to install ptvsd and Azure functions. Run the following command:

```bash
pip install ptvsd azure-functions
```

### Validating Your Environment

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:

1. In the explorer screen, navigate to the following path: “afdebugenv/azure-functions-host-2.0.12888/src/WebJobs.Script.WebHost/Middleware/FunctionInvocationMiddleware.cs”
2. In the first third of the code, you will find the following function definition: “public async Task Invoke(HttpContext context)”. Set a breakpoint in the left-hand gutter, which will be marked by a red dot
3. To invoke the debugger for Azure Functions Host, in the debug screen, under the “debug and run” heading, select “.NET Core Launch” from the drop-down and press F5
4. You will notice that the integrated terminal opened with similar startup messages appearing as when you execute “func host start”. The only main difference is that the Azure Function Host is listening on port 5000. Please refer to the screenshot below:
     ![FunctionHost](https://thepracticaldev.s3.amazonaws.com/i/bdwbtk8wxne15swpy888.png "Azure Function Host Startup")
5. To invoke the debugger for the python worker, in the debug screen, under the “debug and run” heading, select “Attach to Python Functions” from the drop-down and press F5
6. Run a test transaction against your HTTPTrigger
7. The debugger should hit the breakpoint that you set in step 2, and you can now inspect the data that was passed to the function. Please refer to the screenshot below:
     ![Breakpoint](https://thepracticaldev.s3.amazonaws.com/i/oh133pz8d52as4r4w2s6.png "Breakpoint")
8. To continue processing the transaction, hit F5. On your terminal where you executed the command, you should see your output. Additionally, the integrated terminal should reflect that you processed the transaction successfully
9. To exit the debugging process, press the red square in the debugging control panel

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/5001 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.