4

In an Azure function the run method creates an ILogger which in my main class I can use to log errors and information, however, I am using helper classes to run my Graph API and Cosmos DB queries.

What is the best way to log errors and information in the separate classes, should I be passing the log in all my classes or methods? or is there a better way to make the ILogger available to all classes at runtime?

public class AzureFunction
{
    public Task Run([TimerTrigger("0 0 1 * * *")] TimerInfo myTimer, ILogger log)
    {
        log.LogInformation("This message logs correctly");
        GraphHelper graphHelper = new GraphHelper();
        graphHelper.GetGraphData();
    }
}
 
public class GraphHelper
{
    public void GetGraphData()
    {
        try 
        {
            asyncTask() 
        }
        catch (Exception ex) 
        {
            log.LogInformation($"{ex.Message} - How can I log this without passing ILogger to every class or method?");
        }
    }
}
     
CKelly-Cook
  • 195
  • 3
  • 10
  • Use dependency injection, register and inject GraphHelper instead of calling `new` on it. See https://stackoverflow.com/questions/54876798/how-can-i-use-the-new-di-to-inject-an-ilogger-into-an-azure-function-using-iwebj – Ian Mercer Aug 02 '21 at 16:00

1 Answers1

6

The right way (IMHO) is through dependency injection.

using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Text;

namespace FunctionApp1
{
   public class HelperClass : IHelperClass
    {
        private static ILogger<IHelperClass> _logger;
        public HelperClass(ILogger<IHelperClass> logger)
        {

            _logger = logger;
        }
        public void Dowork()
        {
            _logger.LogInformation("Dowork: Execution Started");
            /* rest of the functionality below
                .....
                .....
            */
            _logger.LogInformation("Dowork: Execution Completed");
        }
    }
}

host.json

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

Helper class

using System;
using System.Collections.Generic;
using System.Text;

namespace FunctionApp1
{
    public interface IHelperClass
    {
        void Dowork();
    }
}

MainFunction

using System;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;

namespace FunctionApp1
{
    public class MainFunction // Ensure class is not static (which comes by default)
    {
        private IHelperClass _helper;
     
        public MainFunction(IHelperClass helper)
        {
            _helper = helper;
        }

        [FunctionName("MainFunction")]
        public void Run([TimerTrigger("0 */1 * * * *")]TimerInfo myTimer, ILogger log)
        {
            log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
            // call helper 
            _helper.Dowork();
        }
    }
}

Startup.cs

using Microsoft.Azure.Functions.Extensions.DependencyInjection; // install nuget - "Microsoft.Azure.Functions.Extensions"
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Text;

[assembly: FunctionsStartup(typeof(FunctionApp1.Startup))]

namespace FunctionApp1
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {           
            builder.Services.AddSingleton<IHelperClass,HelperClass>();            

        }
    }
}

A Full sample can be found on https://gist.github.com/nareshnagpal06/82c6b4df2a987087425c32adb58312c2

Thiago Custodio
  • 17,332
  • 6
  • 45
  • 90
  • 1
    thanks but I am still pretty confused, how does the new class receive ILogger logger in its constructor, what if my constructor has other parameters? – CKelly-Cook Aug 06 '21 at 19:03
  • are you talking about my example or yours? All you need to do is inject ILogger rather than ILogger where Foo is the class you've created – Thiago Custodio Aug 06 '21 at 19:18
  • Hi, yes I got it working by creating singletons in the Configure and injecting the ILogger in the constructor but originally in my code my Constructors took parameters and I was creating them within the Function, these were just app.settings anyway so I added them as properties instead and let the singleton do the construction now everything is working. – CKelly-Cook Aug 06 '21 at 19:58
  • @ThiagoCustodio Why does it have to be ILogger and not just ILogger in the Helper Class? And I also don't see clearly how the Helper Class gets passed the ILogger upon instantiation of the Helper Class. Is it something that the runtime does for us magically. Is this what is called FactoryPattern and is performed in the background? – Oliver Nilsen Feb 23 '22 at 13:40
  • @ThiagoCustodio Another question. How would you send the ILogger another level down from a "Service" class down to some Helper classes. So I would have my Function which calls the service class and it is injected an ILogger, but how would accomplish that from the service class call the third Helper class which would also need to log messages? Do you have any code examples of that? – Oliver Nilsen Feb 23 '22 at 14:16
  • @OliverNilsen for the first question, Helper class is being injected through Dependency injection. For sub classes you can pass the instance from Helper class, or if you want, you can also register on Statup.cs and whatever the class you want – Thiago Custodio Feb 23 '22 at 14:20
  • Works wonderfully, thank you. Just pointing out for others, the importance of setting the `logLevel` in the `host.json` file. It could even just be that you set "Default" to "Information" or whatever level you are logging to (although that is really busy!) – Bron Thulke Feb 28 '22 at 02:37
  • The solution does not work for me. Created another post with my version for help - https://stackoverflow.com/questions/74767622/azure-function-unable-to-log-message-to-application-insights-from-service-clas – Tarun Bhatt Dec 12 '22 at 07:01