ILogger not Injected in Durable Functions v2.0 - c#

At the moment I'm trying to add an ILogger or ILogger<> to a Azure Durable Function so as to use logging in Activity Functions.
Logging in the Orchestration Function works fine and is injected in the method itself, but attempts at constructor injection for ILogger always results in a Null Exception.
builder.Services.AddLogging();
The above does not seem to work when added to the Startup file (Bootstrapper) and neither does variations on:
builder.Services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
Anyone solved this?

Remove either of these lines from your Startup file:
builder.Services.AddLogging();
builder.Services.AddSingleton(typeof(ILogger<>), typeof(Logger<>));
Then, wherever you are injecting your ILogger, add the type that your logger is being injected into using ILogger<T> i.e:
public class Function1
{
private readonly ILogger _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;
return name != null
? (ActionResult)new OkObjectResult($"Hello, {name}")
: new BadRequestObjectResult("Please pass a name on the query string or in the request body");
}
}

Related

Azure Durable Functions can't Pass HttpRequestMessage as Input for OrchestrationClient.StartNewAsync

I am trying to use Azure Durable Functions to pass an HttpRequestMessage, received from the Http Triggered function, into another function as follows:
[FunctionName("RequestHandler")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, methods: new string[] {"get", "post"}, Route = "RequestHandler")]
HttpRequestMessage request,
[DurableClient] IDurableOrchestrationClient orchestrationClient,
ILogger logger)
{
var instanceId = await orchestrationClient.StartNewAsync<HttpRequestMessage>("MakeCallout", request);
return new AcceptedResult("", "");
}
[FunctionName("MakeCallout")]
public static async Task RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context,
ILogger logger)
{
HttpRequestMessage request = context.GetInput<HttpRequestMessage>();
}
But I am getting an exception in Newtonsoft at runtime, I presume since durable functions uses the Json Seralization to pass data between functions:
System.Private.CoreLib: Exception while executing function: RequestHandler. Newtonsoft.Json: Self referencing loop detected for property 'Engine' with type 'Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine'. Path 'Properties.HttpContext.ServiceScopeFactory.Root'.
Is there a way of getting around this and passing the request message in without copying the data to another object and then passing in?
You need to read the request in your HTTP function and send a serializable object to the orchestrator.
Define a class that contains the data you need in the orchestrator.
Like TheGeneral mentioned in the comment, serializing an HttpRequestMessage is most likely impossible.
Why is this needed?
Because Durable Functions will store the input object as JSON in Table Storage.
The orchestrator function reads it from there.
You should pass a JSON-serializeable value to the Orchestrator.
[FunctionName("RequestHandler")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, methods: new string[] {"get", "post"}, Route = "RequestHandler")]
HttpRequestMessage request,
[DurableClient] IDurableOrchestrationClient orchestrationClient,
ILogger logger)
{
dynamic data = await req.Content.ReadAsAsync<object>(); // if you have some json data in your http request's body. Or use req.Content.ReadAsStringAsync()
var instanceId = await orchestrationClient.StartNewAsync("MakeCallout", data);
return new AcceptedResult("", "");
}

Azure Function, Dependency Injection, "there is no argument given that corresponds to the formal parameter", dotnet core

I am trying to call a repository class from an Azure function but I am getting an error
There is no argument given that corresponds to the formal parameter
I copied the repository class structure from a .NET Core Web API project and know this has to do with dependency injection.
The constructor of the repository class looks like this:
public CaseRepository(ILogger<CaseRepository> logger, IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
}
How can I pass this into the static method of an Azure Function as I do with the Web API call like this:
[FunctionName("TestFunction")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log, CaseRepository caseRepository)
{
// ...
}
You can define the dependency declaration in the Startup class as shown in this file and later instead of defining the function as static define it normal class function. In the class constructor inject the required dependency. See this for reference.
Startup class
[assembly: FunctionsStartup(typeof(YourNamespace.Startup))]
namespace YourNamespace
{
public class Startup : FunctionsStartup
{
builder.Services.AddSingleton<ICaseRepository, CaseRepository>();
}
}
Usage - here ICaseRepository is injected in the class containing the Azure functions.
public class TestFunction
{
private readonly ICaseRepository caseRepository;
public TestFunction(ICaseRepository caseRepository)
{
this.caseRepository= caseRepository;
}
[FunctionName("TestFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
// ... use caseRepository instance
}
}

Log message from Class library which is used in Azure function

In an Azure function I am able to add logs and it is added to function logs as expected. Below is sample code
[FunctionName("myfunction")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequestMessage req, ILogger log)
{
log.LogInformation("HTTP trigger function start"); // This works fine
MyClass class1 = new MyClass(); // this is from a class library referred in the function
class1.mymethod()
}
In the MyClass methods I want to log messages the same way I did in azure function. I tried creating a new log object of type Ilogger in my class library and calling LogInformation method with the required messages. But this is not working and I am getting error like "Value cannot be null. (Parameter 'logger') at Microsoft.Extensions.Logging.LoggerExtensions.Log"
So how we need to create new object of logger in class library so that it will log message to the same Azure function logs.
First, add a PackageReference for Microsoft.Extensions.Logging.Abstractions* to your class library project. Then, add a parameter to your class' constructor that accepts an ILogger (or an ILogger<YourClass> if you intend on using DI). Store the logger in a field and then use it when you need it.
public class MyClass {
private readonly ILogger _logger;
public MyClass(ILogger logger) =>
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
public void MyMethod(string name) {
_logger.LogInformation("Hello {Person}", name);
}
}
Then, pass the logger to your constructor.
FunctionName("myfunction")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequestMessage req, ILogger log) {
log.LogInformation("HTTP trigger function start");
var class1 = new MyClass(log);
class1.MyMethod("pinkfloydx33");
}
You can also make the logger optional by substituting NullLogger.Instance (or NullLogger<MyClass>.Instance) when one is not provided by the user:
public class MyClass {
private readonly ILogger _logger;
public MyClass(ILogger logger = null) =>
_logger = logger ?? NullLogger.Instance;
//....
}
Then you don't need to perform any null checks around your logging statements.
* Note that the latest version of Azure Functions only run on .NET Core 3.1. This means you can only reference the 3.x line of the Nuget package in your class library, and not the 5.x versions. While it will technically compile, the functions runtime/host will throw TypeInitializationException at runtime. Unfortunately as .NET5 is not LTS there won't be a new version of Azure Functions until .NET6. If you want to use your library from Azure Functions as well as "normal" projects targeted at .NET5, you'll have to cross-compile the library.

How can I reuse ILogger in several classes within c# Azure Function project?

Building a C# Azure Function v2 project using Visual Studio 2017.
I defined just one function, but during the HTTP triggered execution several classes are instantiated. Most by my own code, some by JSON deserialization (so no control over how to call the constructor).
The function methods signature receives an ILogger instance that I use for logging.
What is the best way to let all my class instances make use of that same ILogger instance, without having to pass ILogger to every class instantiation?
I understand using a static var is not possible, since they are shared between different function invocations, while each invocation receives another ILogger instance.
Also, Dependency Injection seems not to be in place here, since the classes are used only internally, except for the JSON deserializable classes.
Following is my summarized code.
I could pass ILogger the the MyClass constructor, but myClass.PerformTheTask() performs complex code that uses and intantiates several classes, that all need to log stuff.
What is best way forward here?
[FunctionName("FunctionName")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "someroute{route}")] HttpRequest req,
ILogger log,
ExecutionContext context)
{
var myClass = new myClass(); //how to make myClass and its children access ILogger?
myClass.PerformTheTask();
}
#EricG,
As rightly said by Fredrik, You can use Dependency injection in Azure function V2 which is supported now.Azure Functions builds on top of the ASP.NET Core Dependency Injection features.
You can find the Azure samples here and in the git repo for reference.
It's quite easy to use and more cleaner.It uses constructor injection for injecting the dependency.
public class SampleFunction
{
private readonly MyServiceA _serviceA;
private readonly MyServiceB _serviceB;
private readonly IGlobalIdProvider _globalIdProvider;
public SampleFunction(MyServiceA serviceA, MyServiceB serviceB, IGlobalIdProvider globalIdProvider)
{
_serviceA = serviceA ?? throw new ArgumentNullException(nameof(serviceA));
_serviceB = serviceB ?? throw new ArgumentNullException(nameof(serviceB));
_globalIdProvider = globalIdProvider ?? throw new ArgumentNullException(nameof(globalIdProvider));
}
[FunctionName("SampleFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation($"Service A ID: {_serviceA.IdProvider.Id}. Service B ID: {_serviceB.IdProvider.Id}");
return new OkObjectResult(new
{
ProvidersMatch = ReferenceEquals(_serviceA.IdProvider, _serviceB.IdProvider),
GlobalId = _globalIdProvider.Id,
ServiceAId = _serviceA.IdProvider.Id,
ServiceBId = _serviceB.IdProvider.Id
});
}
}
Similarly you can do it with ILogger and use it in classes. Hope it helps.

Inject instance of ILogger in my component class Azure Functions using Autofac

I am writing a simple Azure function.
I have installed the AzureFunctions.Autofac nuget package, and would like to use this as my DI library.
I have set up the following AutofacConfig class to register my types:
public class AutofacConfig
{
public AutofacConfig(string functionName)
{
DependencyInjection.Initialize(builder =>
{
//do all of you initialization here
//db client
builder.RegisterType<EventComponent>()
.As<IComponent<EventModel>>().SingleInstance();
}, functionName);
}
}
Here is my EventComponent class, to which I would like to inject the ILogger instance provided.
public class EventComponent : IComponent<EventModel>
{
private ILogger _log;
public EventComponent(ILogger logger)
{
_log = logger;
}
}
Here is how I inject my EventComponent:
[FunctionName("AddEvent")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req, ILogger log, [Inject]IComponent<EventModel> component)
{
log.LogInformation("C# HTTP trigger function processed a request.");
await component.Add(new EventModel() { Id = Guid.NewGuid(), Description = $"Test description nr: {new Random().Next(1, 100000)}", User = "Test User" });
return req.CreateResponse(HttpStatusCode.OK);
}
The problem is, I get an exception on the above, because Autofac cannot resolve the parameter Microsoft.Extensions.Logging.ILogger.
Here is the exception message:
Exception binding parameter 'component'... Cannot resolve parameter 'Microsoft.Extensions.Logging.ILogger logger' of constructor 'Void .ctor(Microsoft.Extensions.Logging.ILogger)'. (See inner exception for details.) -> None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'Event.Function.Components.EventComponent' can be invoked with the available services and parameters:\r\nCannot resolve parameter 'Microsoft.Extensions.Logging.ILogger logger' of constructor 'Void .ctor(Microsoft.Extensions.Logging.ILogger)'.",
How can I inject the ILogger instance into my EventComponent class?
In Azure Functions V2, the ILogger is injected by default. Also, here are two very nice articles on dependency inject in Azure Functions.
https://blog.mexia.com.au/dependency-injections-on-azure-functions-v2
and http://codingsoul.de/2018/01/19/azure-function-dependency-injection-with-autofac/
I found your question when looking for the same thing. Have you found a solution?
Because I don't think that is possible. ILogger log is injected by the framework and I don't see how it could be referenced from your AutofacConfig-class.
How I resolved this was by changing the EventComponent-class to use Setter-injection instead of Constructor-injection, like this:
public class EventComponent : IComponent<EventModel>
{
public ILogger Log { get; set; }
}
and change your function to set the Log-property:
[FunctionName("AddEvent")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequestMessage req, ILogger log, [Inject]IComponent<EventModel> component)
{
log.LogInformation("C# HTTP trigger function processed a request.");
component.Log = log;
await component.Add(new EventModel() { Id = Guid.NewGuid(), Description = $"Test description nr: {new Random().Next(1, 100000)}", User = "Test User" });
return req.CreateResponse(HttpStatusCode.OK);
}
The downside is that you need to remember to set that value at the start of every function that uses that class, but the injection works.
If you want to inject the ILogger into a function app you need to do the following:
Add the correct log level and namespace to your host.json
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true
}
},
"logLevel": {
"YourNameSpace": "Information"
}
Inject ILogger<T> where T is your function app class name/type. In this sample my function app class name is Api.
public class TestService : ITestService
{
private readonly ILogger<Api> _logger;
public TestService(ILogger<Api> logger)
{
_logger = logger;
}
public void LogSomething(string message)
{
_logger.LogInformation(message);
}
}

Categories

Resources