2

We have a Azure Function v2.0 that uses a dependency that performs logging, instead of forwarding the ILogger that is injected into the function, can this component in some way get ahold of IServiceLocator so we can perform GetService() to get ahold of the current logger.

I have tried to bootstrap it in Startup, but this is not possible since the ILogger is not yet built up at that point in the lifecycle of the function app (see here: Weird exception when trying to use Dependency Injection in an Azure Function).

What is the best practice for using (singleton) dependencies in Azure Functions where we want to inject the ILogger?

EDIT: As requested I will provide som sample code to illustrate better what I am trying to achieve:

Startup {
   public override void Configure(IFunctionsHostBuilder builder)
   {
      builder.Services.AddSingleton<IContentProvider, IContentProvider>();  
   }
}

...

public class ContentProvider : IContentProvider {
    private ILogger _logger;

    public ContentProvider(ILogger logger) {
       _logger = logger;
    }

    public bool DoSomething() {
      _logger.LogInformation("Doing something..."); // BOOM: _logger is null
    } 
}

...

public class Functions {

    public StoreFunctions(
        IContentProvider contentProvider
        )
    {
        _contentProvider = contentProvider;
    }

    [FunctionName("MetadataStoreMessage")]
    public async Task ReadMessage([ServiceBusTrigger(TopicName, SubscriptionName, Connection = "MessageBusConnection")]
        string messageBody,
        ILogger logger,
        ExecutionContext context)
    {
       _contentProvider.DoSomething();
    }
} 

EDIT 2: By injecting ILogger the logger in not null, but at the same time it is not set up with logging to an additional logger (Seq), where we want all our logs to be sent:

 builder.Services
            .AddLogging(logging => logging.AddSeq(
                Environment.GetEnvironmentVariable("SeqEndpoint"),
                apiKey: Environment.GetEnvironmentVariable("SeqApiKey"))
        );
        builder.Services.AddSingleton<IContentProvider, ContentProvider>();

What do I have to set to enable the AddLogging() to apply to the injectable ILogger ?

Markus Foss
  • 335
  • 4
  • 14
  • 1
    At what point are you trying to log something? In a function? – Chris Aug 14 '19 at 15:42
  • It will be helpful if you post some sample code that demonstrates it better. Do you need to log something in the Configure method? Note that the singleton doesn't necessarily need to be instantiated in the Configure method. It just needs to be registered. And the DI will take care of constructing the objects and inject ILogger in the ones that need it. – Turbo Aug 14 '19 at 19:50

2 Answers2

1

There are a few ways to register a singleton. Let's assume a singleton MySingleton that depends on two loggers: standard ILogger, and MyCustomLogger. Also assume that my singleton is exposed using an interface ISingleton. (later we will show how to this without an interface as well).

public interface ISingleton { }

public class MySingleton : ISingleton
{
    private ILogger logger;
    private ICustomLogger customLogger;
    public MySingleton(ILogger<MySingleton> logger, ICustomLogger customLogger)
    {
        this.logger = logger;
        this.customLogger = customLogger;
    }
}

To set this up, we add these lines in Startup.Configure() method:

builder.Services.AddSingleton<ICustomLogger, CustomLogger>();
builder.Services.AddSingleton<ISingleton, MySingleton>();

If we don't have an interface, we can directly register it as well:

builder.Services.AddSingleton<MySingleton, MySingleton>();

What if I want to run some special code to instantiate my singleton? I can register a lambda.

builder.Services.AddSingleton<ICustomLogger, CustomLogger>();
// builder.Services.AddSingleton<ISingleton, MySingleton>();
builder.Services.AddSingleton<MySingleton>(serviceProvider => CreateAndInitMySingleton(serviceProvider));

And definition of CreateAndInitMySingleton:

    public MySingleton CreateAndInitMySingleton(IServiceProvider serviceProvider)
    {
        ILogger logger = serviceProvider.GetService<ILogger<MySingleton>>();
        ICustomLogger customLogger = serviceProvider.GetService<ICustomLogger>();
        MySingleton mySingleton = new MySingleton(logger, customLogger);
        return mySingleton;
    }

Note that ILogger & ICustomLogger are not available in Startup.Configure, but they will be available when the lambda runs. The lambda based approach makes it easy to use the service provider / locator, but it puts more burden on the developer to make sure things are instantiated in the right order.

Turbo
  • 2,179
  • 18
  • 38
  • Thanks for your answer. I notice the following: -When I try to inject ILogger into my constructor it is null -When I try to inject ILogger into my constructor it is not null However the ILogger logger that is injected is not setup with logging to a additional logsink (Seq), even though the following code is included in Startup: builder.Services .AddLogging(logging => logging.AddSeq( seqEndpoint, apiKey: seqApiKey) ); builder.Services.AddSingleton(); – Markus Foss Aug 15 '19 at 10:29
  • Could you give this a try: Inject `ILoggerFactory` instead of `ILogger`, and then create `ILogger` by calling `loggerFactory.CreateLogger();`. For example see this: https://stackoverflow.com/a/55931059/1250853 . And here using serilog: https://github.com/serilog/serilog-extensions-logging/issues/139#issuecomment-488661178 . It appears that logging is not fully stable / mature in azure functions yet (unfortunately) :( – Turbo Aug 16 '19 at 07:36
0

We solved the issue by injecting a ILogger into the specific classes. While the ILogger is null, the ILogger is not null, and available. Thanks for you answers

Markus Foss
  • 335
  • 4
  • 14