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

Step by step guide using a light weight approach

1. Introduction

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

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

  • 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

  • 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

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

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

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

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

8. Dependency Injection

$ 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

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

  • 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

12. Conclusion

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

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store