Overview

Logging output is always important. Whether this is to the console of an application running, streaming to log files or hooking into AppInsights being able to keep track of the health of your application is key. It is key for both production monitoring but also during development and being able to see debug statements.

As part of my investigation into Azure Functions and the fundamental concepts of using them the Logging sparked my interest. The main reason why it sparked my curiosity is when you generate a new HTTP Trigger based function it is generated as a static class with a static method with an ILogger passed into the method. This might be fine for some scenarios but having magic parameters passed in and mixing with the functionality of the function eg. the trigger bindings, didn’t sit well with me so I looked into changing the setup.

Using Dependency Injection

Using dependency injection is key to a lot of modern application development and I wanted to explore how to use an ILogger<> in my Azure Function.

Note: I’m not saying you should use DI for everything, thats another discussion. Like everything there is a time a place but it’s a concept you can’t escape in modern development.

As it turns out it’s relatively simple. Once you have removed the static nature of both the class and the method the Function still works as expected. The ILogger parameter passed into the method still functions but doesn’t feel right.

Now we have a none static class we can create a constructor which can take an argument of ILogger<Function1> as you would in ASP.NET 5 to give a class specific logger. The name Function1 in this example is the name of the class holding my function. As with any services/controllers etc. we can assign the contructor parameter to a member variable and then use it throughout the function call.

My Logging Isn’t Outputting

Now we’ve got a logger we can start logging out messages. We can replace the usages of the method injected ILogger with the member variable and remove it from the method signature. On running you will find that there is no log output!

Where’s the logging gone?

The trick is found here - https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection#ilogger-and-iloggerfactory

The host injects ILogger<T> and ILoggerFactory services into constructors. However, by default these new logging filters are filtered out of the function logs. You need to modify the host.json file to opt-in to additional filters and categories.

The logging configuration is similar to that of an ASP.NET 5 appsettings.json file but the casing appears to be different.

{
    "version": "2.0",
    "logging": {
      "applicationInsights": {
        "samplingExcludedTypes": "Request",
        "samplingSettings": {
          "isEnabled": true
        }
      },
      "logLevel": {
        "FunctionApp1.Function1": "Debug"
      }
    }
}

The above change to add in “logLevel” specifically allows for debug levels to be set at the individual level per function. You can also set a “default” level however on brief testing this appears to win over specific function levels and also seems to output the same whichever level you pick. Unsure if I’m doing something wrong or there is a bug there. If you know let me know on Twitter.

Full Updated Function

Below is the standard HTTP Trigger template function updated to allow injection of an ILogger<Function1>.

namespace FunctionApp1
{
    public class Function1
    {
        private readonly ILogger<Function1> _logger;

        public Function1(ILogger<Function1> logger)
        {
            _logger = logger;
        }

        [FunctionName("Function1")]
        public async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req)
        {
            _logger.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name = name ?? data?.name;

            string responseMessage = string.IsNullOrEmpty(name)
                ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
                : $"Hello, {name}. This HTTP triggered function executed successfully.";

            return new OkObjectResult(responseMessage);
        }
    }
}

Conclusion

In this blog post we have briefly discussed the importance of logging, how logging is initially setup in a default template HTTP Trigger Azure Function and how to update it so that the ILogger<T> instance is injected into the function class. We have also reviewed how to configure the logging levels for the none default logging setup to allow more granular control.

I’ve started looking more into Azure functionality and I hope to write more about it as I progress with my Azure journey.

Any questions/comments then please contact me on Twitter @WestDiscGolf