Debugging Azure Functions Locally
Azure Functions are great for running bits of processing on a trigger without having to worry about hosting. Recently, I needed to debug an Azure Function—I needed to hunt down a particularly evasive bug that wasn’t showing up in the unit and integration tests.
As it turns out, debugging an Azure Function isn’t as trivial as simply running the debugger in Visual Studio. Instead, it requires some setup to replicate the environment and configuration typically available in Azure.
Now that I’ve learned how to debug an Azure Function, I thought I’d simplify the process for you by walking through the steps in this blog post. This explanation will also lend itself well to running Azure Functions locally without debugging if you want to host them on-premises, as the steps to do so are very similar.
Note: I’m not covering the code behind an Azure Function, but I’ll assume you intend to create one and will need to debug it locally. In addition, this blog post is written from the perspective of using C#/.NET; you might experience minor differences in the process if you’re using other coding languages.
Debugging from Visual Studio
When running in Azure, the configuration backing an Azure Function is provided by the application configuration attached to the Azure App Service on which the Function is running. This configuration will need to be replicated using a local JSON file, but there will likely be a few differences in values.
In addition, Azure Functions require an Azure Storage account for handling triggers and logging. When running locally, the Azure Storage account can be replaced with an installed emulator. We’ll start with the emulator since it impacts the values put in configuration.
Once the configuration and storage are in place, you can debug the Azure Function from Visual Studio by setting the function project as the startup project for the Visual Studio solution.
Azure Storage Emulator
To run all Azure Functions (except HTTP triggered functions), an Azure Storage account must be available. The connection string for this storage account is stored in the AzureWebJobsStorage configuration key on the Azure Function App Service. When developing or debugging a Function, it’s common to run the Azure Storage Emulator locally instead of using a storage account. The Emulator is a Windows-only tool capable of simulating blob, queue, and table storage.
There are two ways to get the Azure Storage Emulator:
- You can install it via the Visual Studio installer. It’s included in the Azure Development workload or can be individually selected under the Cloud, database, and server section.
- You can download a standalone installer from the official Microsoft documentation here.
To connect the Function to the Emulator, replace the AzureWebJobsStorage connection string with “UseDevelopmentStorage=true” (see next section for where to configure this). The Emulator will automatically start when the project is run in Visual Studio, and no further steps should be needed to connect to it.
If you’re using Emulator, I recommend reviewing the Microsoft documentation. Although the appeal of using the Emulator is its ease of setup, it’s a feature-filled tool with quite a bit of configuration available. The documentation reviews topics such as command line control of the Emulator, configuring the SQL backend of the Emulator, and setting up authentication, all of which are beyond the scope of this blog post.
To provide the configuration values typically found in the Azure Function’s App Service configuration, a local.settings.json file is used. The file is typically added at the project root directory, and, if added to the solution in Visual Studio, is set to Build Action: None and Copy to Output Directory: Copy Always or Copy If Newer. This file isn’t typically checked into source control because of the amount of infrastructure access information it contains, so it’s recommended you add it to a .gitignore file (or equivalent if using an alternative to Git).
The full specification for the local.settings.json file can be found in the Microsoft documentation, but a sample file might look like the following:
All the settings required for the Azure Function should be included in this file under the Values section. Typically, these values would match those found in the App Service configuration but changing the values here would allow for a separate development environment if desired.
A few specific settings in this file warrant a mention.
- The “AzureWebJobs.FunctionName.Disabled” values are unique to the settings.json file and won’t appear in Azure. These values will be discussed in more detail in the next section.
- The “LocalHttpPort” value is ignored when running via Visual Studio, which will set a port as a command line argument automatically. You can set this value by going to the project’s properties and including the following line in the Application Arguments section:
host start --pause-on-error --port ####
- The “AzureWebJobsStorage” line was discussed in the previous section as a way to connect to the Azure Storage Emulator.
Running Only Specific Functions When Multiple Functions Are Defined
In the previous section, I discussed using the configuration values “AzureWebJobs.FunctionName.Disabled” to run only certain Azure Functions at a time. The pattern I’ve used for developing related Functions is to put multiple within the same project, which is then deployed as a single DLL to Azure. However, when you run the Functions locally, by default it runs all Functions. This can pose a problem when only one Function needs debugging and you’d prefer the Azure infrastructure run the rest of the Functions while you work on the problematic Function.
The “AzureWebJobs.FunctionName.Disabled” values need to be added for each Function you want to prevent from running locally; there’s no logical inverse of only running a specified Function. An example local.settings.json file with these values might look like the following:
I like to add a new line for every Function in the project, with a default value of true, to the local.settings.json file as soon as I create the Function. This helps prevent accidentally running a Function that’s already running in Azure.
But what happens when you run a Function locally that’s also still running in Azure? Requests to the Function can get routed to either running instance of the Function indeterminately, which can become an issue if you’re testing changes and have two different versions running.
Running On-Premises Without Debugging
Perhaps you want to test an Azure Function that was built in a Continuous Integration (CI) pipeline, so you need a way to run the .DLL file directly. Or you want to use the Azure Function framework but don’t want to have the processing hosted in Azure. (Although I’ve never seen such a scenario, it’s possible.) You would want to use the Azure Functions Core Tools to do so. You can find instructions for installing Azure Function Core Tools here or on NPM.
Azure Functions Core Tools are fairly simple to use, especially if you already have the local.settings.json file created. Copy the settings file to the root of the Functions project next to the DLL, open a command prompt from there, and run “func start.” The Function will run based off the same trigger it would use if hosted in Azure or it can be manually triggered for testing using a local HTTP endpoint. For all Functions using an HTTP trigger, the default port is 7071, but it can be changed via either the local.settings.json file or at the command line.
Azure Functions Core Tools include many other features useful throughout the Function development cycle, such as scaffolding new Function projects or publishing them to Azure.
In this guide, I covered the following:
- Setting up the configuration (via local.settings.json) and infrastructure (with the Azure Storage Emulator) locally to replicate the hosted Azure environment for Azure Functions.
- Customizing the Function configuration within local.settings.json, so local runs are both useful and don’t interfere with development-hosted resources
- An introduction to the Azure Functions Core Tools, which include methods to run existing Functions
I hope this walk through has been useful. And may all of your Azure Functions be performant!
Greg Peacock is a software developer at SolarWinds. His more recent areas of focus have been on the Microsoft development stack, including Azure, C#, and .NET, and he has worked on both desktop and cloud backend development. Greg enjoys pair programming and solving the puzzle of making code testable. In his free time, you’ll find him experimenting with creating new meals in the kitchen, playing basketball, reading about physics, or listening to rock and metal music.