Everything you need to know to write, debug, deploy and monitor Azure Functions

Step by step guide using a light weight approach

Kevin Le
10 min readNov 1, 2018

1. Introduction

Learning how to correctly write and deploy with Azure Function beyond a “Hello Azure Function” is difficult. It is that way because although Azure documentation is plentiful but just not well organized. Conceptually it’s actually easy, if the documentation is not such a big mess. Recently I had to re-architect some code by taking the Azure Function approach. Originally these code were in the StartUp.cs of an ASP.NET WebAPI like so (simplified for illustration purpose):

public void Configure(...) {
//...
Task.Factory.StartNew(() => {
while (true) {
//Capture some data
//...

//Now waits for 15 minutes or so
Thread.Sleep(new TimeSpan(someInterval));
}
});
}

The code that captures some data is now to be moved to multiple Azure function apps. They will be triggered by timers. In this article, I want to share all lessons learned. I will show the complete process of how to go from zero to deployment and even beyond.

The goals are as follow:

  1. First, this approach will be as light weight as possible. NO massive Visual Studio 2017 installation is required.
  2. During development, since you’ll need to know how to debug and log messages, we’ll cover that. After the Azure function apps are deployed to the Azure Cloud, we certainly want to monitor log messages so that we can determine how they’re running or not running. We’ll cover that as well.
  3. In real-world situations like in my cases, we need to write and run multiple Azure function apps in production. These function apps need to share some common code such as Entity Framework data access, models, helper utilities, etc.
  4. We’ll cover how to do Dependency Injection.

Some assumptions:

  1. From this article, you will learn how to implement Azure function apps and run locally. But to get the maximum benefits when it comes to deployment on Azure, you should already have an Azure subscription. You should be familiar with portal.azure.com already.
  2. I will only show Azure function apps in C#. These functions are timer-triggered.

2. Set up NodeJS and npm packages

Everything above sounds perfect? Well, since we know nothing is perfect, so is this approach. The drawback, if you so consider, is that we need to install NodeJS (since we only do C# here). For me, it’s not a problem because I already have Node installed for many other purposes. Anyway, you can install from

https://nodejs.org/download/release/

Then we need to install the npm pckageazure-functions-core-tools. This package will be required later. If we don’t do it now, Visual Studio Code will remind us later. For now, let’s just install and get it out of the way.

$ npm install -g azure-functions-core-tools@latest

3. Install .NET Core and Visual Studio Code

  • Download the .NET Core SDK (2.1 at the time of this writing). Be sure to choose the SDK and NOT the Runtime, because we don’t want to just run the apps, we want to build the apps. You might already have this installed, in which case, this step can be ignored. Make sure when you type dotnet on a Terminal, the command is recognized.
  • NO Visual Studio 2017 is needed. But we will need to install Visual Studio Code (hereinafter referred to as Code). Code is much more lighter weight than Visual Studio 2017 (which takes up more than 20 GB easily with only a few modules installed) and Code is also a a lot more stable. It does not hang every now and then like Visual Studio 2017. So Code is what we will use. If you don’t already have Code installed, the link is below. You might already have this installed, in which case, this step can be ignored.
  • Install the Azure Functions extension in Visual Studio Code. In case you don’t know about Extensions, look for the 5th menu item in the most left gutter panel of Code. A by product of this installation is Azure Account extension is also installed.
  • There are a couple ways to create an Azure Function: using the Command Line Interface (CLI) or directly from Code. I normally prefer CLI everything, but in this case, let’s do from Code.
  • First, let’s create a new Project.
  • Select a folder, choose C#. You should see a .csproj file created. Open this file and make sure the target framework is 2.1. If you don’t see 2.1, you might see problem with deploying later on in the process. I think because at the time of this writing, 2.1 is the default on Azure. So anything other than that doesn’t work. I experienced that, but the error message doesn’t help.
<TargetFramework>netcoreapp2.1</TargetFramework>
  • Next let’s create a couple of function apps. Use the next icon.
  • Select the same folder, choose TimerTrigger. For the function name, just call them FunctionApp1 and FunctionApp2. Type any namespace you want. For this example, I name ExampleAzFuncs as the namespace.
  • Specify any timer interval you want. I specify 0 */1 * * * *(that’s 1 minute). To learn about the syntax, refer to https://en.wikipedia.org/wiki/Cron#CRON_expression
  • You might see multiple warnings from Code. If you see the one below, just click Install.
  • If you see
  • That means you have not installed NodeJS and the npm package azure-functions-core-tools as discussed in 2 above, now is the time to do. You can download node from https://nodejs.org/download/release/. After Node is installed, run the following command:

$ npm install -g azure-functions-core-tools@latest

  • If you see

For any of triggers other than HttpTrigger, AzureWebJobStorage is required (We picked TimerTrigger). Skip for Now. Just close the box. We’ll deal with this soon.

  • If you get the warning There are unresolved dependencies. Please execute the restore command to continue just go ahead and click Restore. You might also get these warning at various times, especially after adding a nuget package. Just click Restore each time.

4. Restructure the folder hierarchy

At this point you should see in your Code Explorer panel a folder hierarchy similar to this

  • You want to restructure by adding 2 folders at the top level. Name these folders FunctionApp1 and FunctionApp2. Move FunctionApp1.cs and FunctionApp2.cs inside each of them respectively. In addition, in bothFunctionApp1 and FunctionApp2 folders, create a file in each named function.json. At the same level with FunctionApp1 and FunctionApp2 folders, create a folder named SharedCode. The folder hierarchy at this point should look like:
  • Copy the following code to the function.json file. Change the name accordingly.
{
"bindings": [{
"name": "FunctionApp1or2",
"type": "timerTrigger",
"direction": "in",
"runOnStartup": true,
"schedule": "*/1 * * * * *"
}]
}
  • Right now, there’s nothing in the SharedCode folder. But this is where we will place all the shared code.

5. AzureWebJobStorage

The reason we skip the above configuration of the AzureWebJobStorage is we don’t want to point to an environment on the Azure cloud, not just yet. Since we’re still getting started and still in development, we can emulate the AzureWebJobStorage locally. There are 2 options:

a. Standalone Storage Emulator

b. Azurite

Both are easy to use. Azurite is advantageous because it’s cross-platform. For me, I choose Azurite.

Regardless of whether you choose the Standalone Storage Emulator or Azurite, locate the file local.settings.json and add the value UseDevelopmentStorage=true for the key AzureWebJobsStorage

{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet"
}
}

Now open up the Terminal and you can run

$ func start --build

Depending on the interval you specify, every 1 or 5 second, you should see the some output like (of course you must have changed from the default log messages earlier) FunctionApp1 function executed at …

Some Troubleshooting tips

a. Missing value for AzureWebJobsStorage

If you see the error above, make sure the line "AzureWebJobsStorage": "UseDevelopmentStorage=true”, is in the local.settings.json file.

b. Emulator or Azurite not running

If you see the error above, make sure the Storage Emulator or Azurite is running

6. Logging

You saw the line of code log.LogInformation above. ILogger is injected to the Run method, which makes it very convenient

public static void Run([TimerTrigger("0 */2 * * * *")]TimerInfo myTimer, ILogger log) {
log.LogInformation("...");
}

If you play with Code Intellisense, you’ll see other methods for logging such as log.LogCritical, log.LogDebug, log.LogError, log.LogWarning, etc.

To control the log level, locate the host.json file and add the following lines in bold:

{ 
"version": "2.0",
"logging": {
"logLevel": {
"default": "Error"
}
}
}

7. Logging in other classes’ methods

Chances are nobody will write all the code in this single Run() method, because that will be such a monolithic way of programming. So if we add more classes and want to log messages in their methods, how would we need to do?

We need to inject something like ILogger (as seen in the Run method).

8. Dependency Injection

We will revisit on how to log messages in other classes’ methods. For now we will need to do Dependency Injection in Azure Function. We will use Autofac, but because we’re doing Azure Function, we need to use Azure Function Autofac. Open up the Terminal and run the command:

$ dotnet add package AzureFunctions.Autofac --version 3.0.5

Now add a new file inside the SharedCode folder and call it something like AutofacConfig.cs with the following content:

using System;
using Microsoft.Azure.WebJobs.Host.Config;
using Microsoft.Extensions.DependencyInjection;
using Autofac;
using AzureFunctions.Autofac.Configuration;
using Microsoft.Extensions.Logging;
namespace ExampleAzFuncs {
public class AutofacConfig {
public AutofacConfig(string functionName) {
DependencyInjection.Initialize(builder => {
builder.Register(ctx => new ServiceCollection()
.AddLogging()
.BuildServiceProvider()
.GetService<ILoggerFactory>())
.As<ILoggerFactory>();
}, functionName);
}
}
}

Now let’s say we need to write some ServiceHelper class. From this point on, it is assumed that any code that’s common to all the function apps will be placed in the SharedCode folder. Let’s write the interface called IServiceHelper and this implementation class ServiceHelper. Create 2 new files called IServiceHelper.cs and ServiceHelper.cs with the following contents respectively:

//IServiceHelper.cs
namespace ExampleAzFuncs {
public interface IServiceHelper {
string Greet(string message);
}
}

and

//ServiceHelper.cs
namespace ExampleAzFuncs {
public class ServiceHelper : IServiceHelper {
public string Greet(string message) {
//We'll add logging later
return "Hello "+ message;
}
}
}

As you can see, there’s no logging code in ServiceHelper yet.

Now we need to register with Autofac, so let’s revise the AutofacConfig constructor above:

public AutofacConfig(string functionName) {
DependencyInjection.Initialize(builder => {
builder.RegisterType<ServiceHelper>().As<IServiceHelper>();
builder.Register(ctx => new ServiceCollection()
.AddLogging()
.BuildServiceProvider()
.GetService<ILoggerFactory>())
.As<ILoggerFactory>();
}, functionName);
}

Next, install the following nuget package from the Terminal

$ dotnet add package Microsoft.Extensions.Logging.Console --version 2.1.1

(Above should be all in one line)

Inject ILoggerFactory to the constructor of ServiceHelper and start logging.

//ServiceHelper.cs
using AzureFunctions.Autofac;
using AzureFunctions.Autofac.Configuration;
using Microsoft.Extensions.Logging;

namespace ExampleAzFuncs {
[DependencyInjectionConfig(typeof(AutofacConfig))]
public class ServiceHelper : IServiceHelper {
private ILogger _logger;
public ServiceHelper([Inject] ILoggerFactory logFactory){
_logger = logFactory.AddConsole(LogLevel.Information)
.CreateLogger<ServiceHelper>();
}

public string Greet(string message) {
_logger.LogInformation($"[{System.DateTime.Now}] Message is {message}");
return "Hello "+ message;
}
}
}

Update 11–5–2018:

The above logging code does not cause messages to be streamed from Azure once it’s running in production. Replace with the following:

Don’t forget to add the package

$ dotnet add package Microsoft.Extensions.Logging.AzureAppServices — version 2.1.1

End of update 11–5–2018

Inject IServiceHelper to the Run method of the Azure Function:

using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
using AzureFunctions.Autofac;
using AzureFunctions.Autofac.Configuration;
namespace ExampleAzFuncs {
[DependencyInjectionConfig(typeof(AutofacConfig))]
public static class FunctionApp1 {
[FunctionName("FunctionApp1")]
public static void Run(
[TimerTrigger("0 */1 * * * *")]TimerInfo myTimer,
ILogger log,
[Inject] IServiceHelper serviceHelper) {

serviceHelper.Greet("Azure Function");
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
}
}
}

The result would look something like

9. Debug locally

Logging messages is great for troubleshooting but sometime debug is more preferable. Debugging an Azure Function in Code could not be easier (but as a side note took me hours to figure out). Simply cursor to the line where you want to place the breakpoint, then hit F9. To run, hit F5.

However, if inside the Run method, we need to make some asynchronous calls, that means we have to mark the calls with await, then Run has to be marked with async. Doing so, then we will no longer be able to set breakpoints and debug.

10. Deploy to Azure

My preference is to use the Continuous Deployment approach to deploy all of my Azure App Services and Azure Functions. But we’ll do directly for now.

  • Click on the up arrow in blue.
  • Create a New Function App, type in the name for the Azure Function. I typed ExampleAzFuncs.
  • Create a New Resource Group or select an existing one.
  • Create a New Storage Account or select an existing one. If you remember, during developement when we run locally, we tell the configuration in local.settings.json
"AzureWebJobsStorage": "UseDevelopmentStorage=true",

Azurite or the Storage Emulator took care of providing the storage. Now when we deploy to Azure, we need real Azure WebJobs Storage. By using the extension, we can easily configure it on Azure.

Everything should go smoothly. If you get the following error Could not zip a non-exist file path, chances are you didn’t specify 2.1 for the target framework as mentioned above.

11. Logging in Production

To view logging messages, right click on your Function app and select Start/Stop Streaming Logs.

12. Conclusion

The article might seem long, but that’s because I’m too verbose. But as you can see, it’s not that conceptually hard. If you like, please clap.

--

--

Kevin Le

Driven by passion and patience. Read my shorter posts https://dev.to/codeprototype (possibly duplicated from here but not always)