'Logger becomes null in Azure Durable Function

A similar question is asked, but the OP's solution doesn't work for me.

I'm trying to create a Bar instance in an actvity function, which should has a logger injected in it. Here's the simplified code.

Func1.cs:

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.DurableTask;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;

namespace DurableFunctionTest
{
    public class Function1
    {
        private readonly IBarFactory _barFactory;

        public Function1(IBarFactory barFactory)
        {
            _barFactory = barFactory;
        }

        [FunctionName("Function1")]
        public static async Task RunOrchestrator(
            [OrchestrationTrigger] IDurableOrchestrationContext context)
        {
            var bar = await context.CallActivityAsync<Bar>(nameof(CreateBarInstance), context);
            bar.DoSomething();
        }

        [FunctionName(nameof(CreateBarInstance))]
        public async Task<Bar> CreateBarInstance([ActivityTrigger] IDurableActivityContext context)
        {
            var result = await _barFactory.CreateBarAsync();
            return result;
        }

        [FunctionName("Function1_HttpStart")]
        public static async Task<HttpResponseMessage> HttpStart(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestMessage req,
            [DurableClient] IDurableOrchestrationClient starter,
            ILogger log)
        {
            // Function input comes from the request content.
            string instanceId = await starter.StartNewAsync("Function1", null);

            log.LogInformation($"Started orchestration with ID = '{instanceId}'.");

            return starter.CreateCheckStatusResponse(req, instanceId);
        }
    }

    public interface IBarFactory
    {
       public Task<Bar> CreateBarAsync();
    }

    public class BarFactory : IBarFactory
    {
        private readonly ILoggerFactory _loggerFactory;
        private readonly ILogger<BarFactory> _logger;

        public BarFactory(ILoggerFactory loggerFactory)
        {
            _loggerFactory = loggerFactory;
            _logger = loggerFactory.CreateLogger<BarFactory>();
        }
        public async Task<Bar> CreateBarAsync()
        {
            _logger.LogInformation("Business logic here in reality to determine what derived class of Bar to create.");
            var bar = new Bar(_loggerFactory.CreateLogger<Bar>());
            await bar.InitializeAsync();
            return bar;
        }
    }


    public class Bar
    {
        private readonly ILogger<Bar> _logger;

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

        public void DoSomething()
        {
            _logger.LogInformation("Doing something");
        }

        public async Task InitializeAsync()
        {
            _logger.LogInformation("Run some initialization logic");
            await Task.Delay(500);
        }
    }
}

Startup.cs for DI:

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

[assembly: FunctionsStartup(typeof(DurableFunctionTest.Startup))]
namespace DurableFunctionTest
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddSingleton<IBarFactory, BarFactory>(p => new BarFactory(p.GetRequiredService<ILoggerFactory>()));
        }
    }
}

I can verify that the Bar's logger is correctly populated in the activity function. But when it is returned to the orchestrator, the Bar's logger becomes null. Hence an error is thrown in the Bar.DoSomething method.

As a side note, in my real code it's not just the logger that's having the issue. EF object also cannot be passed between functions. I suspect that it may be due to a same root cause.



Solution 1:[1]

This is expected. The activity function runs and populates the logger item for the bar class and it's then possible to use it. When the activity function returns however the Bar object is not passed directly to the orchestrator but instead put into an azure table (you should be able to view it). In order to store it, serialization is performed to JSON. The Logger object cannot be serialized and is ignored. When the orchestrator reruns deserialization is performed from the table. Hence logger will be null.

Usually return values from activities are just data or information about execution, they can't really return more complex things. Either pass logger to orchestrator and then as args to Bar.DoSomething or maybe register a singleton BarService that has the logic to handle the bar data.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Bjorne