How to get an ILogger from an Activity Function? - c#

I am using Durable Azure Function in a prototype for a future project.
Basically, I have a Client Azure Function triggered by an HTTP POST request that starts the Orchestrator. Then, the Orchestrator decides to trigger an Activity. Nothing complicated.
Here is a sample of what I am doing:
[FunctionName("ClientFunction")]
public static async Task<HttpResponseMessage> OnHttpTriggerAsync([HttpTrigger(AuthorizationLevel.Anonymous, "post")]
HttpRequestMessage request, [OrchestrationClient] DurableOrchestrationClient starter, ILogger logger)
{
// Triggers the orchestrator.
string instanceId = await starter.StartNewAsync("OrchestratorFunction", null);
return new HttpResponseMessage(HttpStatusCode.OK);
}
[FunctionName("OrchestratorFunction")]
public static async Task DoOrchestrationThingsAsync([OrchestrationTrigger] DurableOrchestrationContext context, ILogger logger)
{
// Triggers some serious activity.
await context.CallActivityAsync("ActivityFunction", null);
}
[FunctionName("ActivityFunction")]
public static Task DoAnAwesomeActivity([ActivityTrigger] DurableActivityContext context)
{
// Short & Sweet activity...
// Wait... Where's my logger?
}
Both the Orchestrator and the Client Functions are being given an ILogger but not the Activity Function; as stated in the documentation (either a specific parameter or the DurableActivityContext instance), the Activity function only gets one parameter. And I am not under the impression that the static class in which these methods are declared could keep a reference on that ILogger.
I understand that the Activity Function should perform one small job but I would be more comfortable if I was able to log that the activity was called with the appropriate values if something goes wrong (and it will :) ).
Question
How can the Activity access the ILogger?

It is not possible to pass multiple parameters to an activity function directly. The recommendation in this case is to pass in an array of objects or to use ValueTuples objects in .NET.
This restriction you are concerned about is talking about the parameters we pass from Orchestrator to Activity Function. It doesn't mean we could only use one parameter in Activity method signature. Feel free to add ILogger there and complete your job as needed.

Related

What is the simplest way to run a single background task from a controller in .NET Core?

I have an ASP.NET Core web app, with WebAPI controllers. All I am trying to do is, in some of the controllers, be able to kick off a process that would run in the background, but the controller should go ahead and return before that process is done. I don't want the consumers of the service to have to wait for this job to finish.
I have seen all of the posts about IHostedService and BackgroundService, but none of them seem to be what I want. Also, all these examples show you how to set things up, but not how to actually call it, or I am not understanding some of it.
I tried these, but when you register an IHostedService in Startup, it runs immediately at that point in time. This is not what I want. I don't want to run the task at startup, I want to be able to call it from a controller when it needs to. Also, I may have several different ones, so just registering services.AddHostedService() won't work because I might have a MyServiceB and MyServiceC, so how do I get the right one from the controller (I can't just inject IHostedService)?
Ultimately, everything I have seen has been a huge, convoluted mess of code for something that seems like it should be such a simple thing to do. What am I missing?
You have the following options:
IHostedService classes can be long running methods that run in the background for the lifetime of your app. In order to make them to handle some sort of background task, you need to implement some sort of "global" queue system in your app for the controllers to store the data/events. This queue system can be as simple as a Singleton class with a ConcurrentQueue that you pass in to your controller, or something like an IDistributedCache or more complex external pub/sub systems. Then you can just poll the queue in your IHostedService and run certain operations based on it. Here is a microsoft example of IHostedService implementation for handling queues https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio#queued-background-tasks
Note that the Singleton class approach can cause issues in multi-server environments.
Example implementation of the Singleton approach can be like:
// Needs to be registered as a Singleton in your Startup.cs
public class BackgroundJobs {
public ConcurrentQueue<string> BackgroundTasks {get; set;} = new ConcurrentQueue<string>();
}
public class MyController : ControllerBase{
private readonly BackgroundJobs _backgroundJobs;
public MyController(BackgroundJobs backgroundJobs) {
_backgroundJobs = backgroundJobs;
}
public async Task<ActionResult> FireAndForgetEndPoint(){
_backgroundJobs.BackgroundTasks.Enqueue("SomeJobIdentifier");
}
}
public class MyBackgroundService : IHostedService {
private readonly BackgroundJobs _backgroundJobs;
public MyBackgroundService(BackgroundJobs backgroundJobs)
{
_backgroundJobs = backgroundJobs;
}
public void StartAsync(CancellationToken ct)
{
while(!ct.IsCancellationRequested)
{
if(_backgroundJobs.BackgroundTasks.TryDequeue(out var jobId))
{
// Code to do long running operation
}
Task.Delay(TimeSpan.FromSeconds(1)); // You really don't want an infinite loop here without having any sort of delays.
}
}
}
Create a method that returns a Task, pass in a IServiceProvider to that method and create a new Scope in there to make sure ASP.NET would not kill the task when the controller Action completes. Something like
IServiceProvider _serviceProvider;
public async Task<ActionResult> FireAndForgetEndPoint()
{
// Do stuff
_ = FireAndForgetOperation(_serviceProvider);
Return Ok();
}
public async Task FireAndForgetOperation(IServiceProvider serviceProvider)
{
using (var scope = _serviceProvider.CreateScope()){
await Task.Delay(1000);
//... Long running tasks
}
}
Update: Here is the Microsoft example of doing something similar: https://learn.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-3.1#do-not-capture-services-injected-into-the-controllers-on-background-threads
As I understand from your question you want to create a fire and forget task like logging to database. In this scenario you don't have to wait for log to be inserted database. It also took much of my time to discover an easily implementable solution. Here is what I have found:
In your controller parameters, add IServiceScopeFactory. This will not effect the request body or header. After that create a scope and call your service over it.
[HttpPost]
public IActionResult MoveRecordingToStorage([FromBody] StreamingRequestModel req, [FromServices] IServiceScopeFactory serviceScopeFactory)
{
// Move record to Azure storage in the background
Task.Run(async () =>
{
try
{
using var scope = serviceScopeFactory.CreateScope();
var repository = scope.ServiceProvider.GetRequiredService<ICloudStorage>();
await repository.UploadFileToAzure(req.RecordedPath, key, req.Id, req.RecordCode);
}
catch(Exception e)
{
Console.WriteLine(e);
}
});
return Ok("In progress..");
}
After posting your request, you will immediately receive In Progress.. text but your task will run in the background.
One more thing, If you don't create your task in this way and try to call database operations you will receive an error like this which means your database object is already dead and you are trying to access it;
Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.\r\nObject name: 'DBContext'.
My code is based on Repository pattern. You should not forget to inject service class in your Startup.cs
services.AddScoped<ICloudStorage, AzureCloudStorage>();
Find the detailed documentation here.
What is the simplest way to run a single background task from a controller in .NET Core?
I don't want the consumers of the service to have to wait for this job to finish.
Ultimately, everything I have seen has been a huge, convoluted mess of code for something that seems like it should be such a simple thing to do. What am I missing?
The problem is that ASP.NET is a framework for writing web services, which are applications that respond to requests. But as soon as your code says "I don't want the consumers of the service to have to wait", then you're talking about running code outside of a request (i.e., request-extrinsic code). This is why all solutions are complex: your code has to bypass/extend the framework itself in an attempt to force it to do something it wasn't designed to do.
The only proper solution for request-extrinsic code is to have a durable queue with a separate background process. Anything in-process (e.g., ConcurrentQueue with an IHostedService) will have reliability problems; in particular, those solutions will occasionally lose work.

Azure Durable Function error handling: Is there a way to identify which retry you are on?

The Durable Functions documentation specifies the following pattern to set up automatic handling of retries when an exception is raised within an activity function:
public static async Task Run(DurableOrchestrationContext context)
{
var retryOptions = new RetryOptions(
firstRetryInterval: TimeSpan.FromSeconds(5),
maxNumberOfAttempts: 3);
await ctx.CallActivityWithRetryAsync("FlakyFunction", retryOptions, "ABC");
// ...
}
However I can't see a way to check which retry you're up to within the activity function:
[FunctionName("FlakyFunction")]
public static string[] MyFlakyFunction(
[ActivityTrigger] string id,
ILogger log)
{
// Is there a built-in way to tell what retry attempt I'm up to here?
var retry = ??
DoFlakyStuffThatMayCauseException();
}
EDIT: I know it can probably be handled by mangling some sort of count into the RetryOptions.Handle delegate, but that's a horrible solution. It can be handled manually by maintaining an external state each time it's executed, but given that there's an internal count of retries I'm just wondering if there's any way to access that. Primary intended use is debugging and logging, but I can think of many other uses.
There does not seem to be a way to identify the retry. Activity functions are unaware of state and retries. When the CallActivityWithRetryAsync call is made the DurableOrchestrationContext calls the ScheduleWithRetry method of the OrchestrationContext class inside the DurableTask framework:
public virtual Task<T> ScheduleWithRetry<T>(string name, string version, RetryOptions retryOptions, params object[] parameters)
{
Task<T> RetryCall() => ScheduleTask<T>(name, version, parameters);
var retryInterceptor = new RetryInterceptor<T>(this, retryOptions, RetryCall);
return retryInterceptor.Invoke();
}
There the Invoke method on the RetryInterceptor class is called and that does a foreach loop over the maximum number of retries. This class does not expose properties or methods to obtain the number of retries.
Another workaround to help with debugging could be logging statements inside the activity function. And when you're running it locally you can put in a breakpoint there to see how often it stops there. Note that there is already a feature request to handle better logging for retries. You could add your feedback there or raise a new issue if you feel that's more appropriate.
To be honest, I think it's good that an activity is unaware of state and retries. That should be the responsibility of the orchestrator. It would be useful however if you could get some statistics on retries to see if there is a trend in performance degradation.

Benefits of an async ServiceBusTrigger

I'm working on microservices (using Azure Function Apps) that contain ServiceBusTrigger-based Azure Functions that trigger when a message is inserted into a Service Bus Queue.
I'm trying to determine the best way of binding output values to multiple targets (e.g. CosmosDB and IoT Hub). Whether or not the method is marked as async will determine how I should approach this problem.
As far as I am aware, the way that you would typically handle output binding with an async function is by using the [return: ...] annotation; however, in my use case, I need to return two different values to two separate targets (e.g. CosmosDb and IoT Hub). I don't think that this is something that I can achieve with return value binding or output variable binding, since you can't have an out param with an async method and you can define multiple return values with the [return: ...] approach.
It would seem that my only option (if I went the async route) would be to manually invoke SDK methods in the Azure Function to call the services independent of any output values. I'm trying to avoid doing that, seeing as output binding is the preferred approach.
An observation that I have made when creating a brand new ServiceBusTrigger-based Azure Function is that the generated method signature is not marked as async by default.
This is different than an HttpTrigger, which is marked as async out-of-box.
Can someone help me understand the reasoning for this? What are the scaling implications associated with one vs. the other?
I understand in a traditional sense why you typically mark an HttpTrigger as async; however, I don't understand the reasoning as to why the ServiceBusTrigger is not async
I need to understand this bit before I can move on with solidifying my approach to outputs.
I don't think templates with/without async functions have any reasoning to them as such. And depending on your code, your function may be more efficient.
Read this thread for more details on async/await in functions.
As for your main question, you just have to bind to different objects for the CosmosDB and IoT Hub output bindings.
For CosmosDB, you will have to bind to IAsyncCollector instead as shown in the docs
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace CosmosDBSamplesV2
{
public static class WriteDocsIAsyncCollector
{
[FunctionName("WriteDocsIAsyncCollector")]
public static async Task Run(
[QueueTrigger("todoqueueforwritemulti")] ToDoItem[] toDoItemsIn,
[CosmosDB(
databaseName: "ToDoItems",
collectionName: "Items",
ConnectionStringSetting = "CosmosDBConnection")]
IAsyncCollector<ToDoItem> toDoItemsOut,
ILogger log)
{
log.LogInformation($"C# Queue trigger function processed {toDoItemsIn?.Length} items");
foreach (ToDoItem toDoItem in toDoItemsIn)
{
log.LogInformation($"Description={toDoItem.Description}");
await toDoItemsOut.AddAsync(toDoItem);
}
}
}
}
For Event Hub, you will have to bind to IAsyncCollector instead as shown in the docs
[FunctionName("EH2EH")]
public static async Task Run(
[EventHubTrigger("source", Connection = "EventHubConnectionAppSetting")] EventData[] events,
[EventHub("dest", Connection = "EventHubConnectionAppSetting")]IAsyncCollector<string> outputEvents,
ILogger log)
{
foreach (EventData eventData in events)
{
// do some processing:
var myProcessedEvent = DoSomething(eventData);
// then send the message
await outputEvents.AddAsync(JsonConvert.SerializeObject(myProcessedEvent));
}
}

Re-use scoped ILogger instance from Azure Function

I'm not sure what is the best way to achieve what I am trying to accomplish so let me give you an example.
I am using Azure Functions, which are stateless, with the following signature.
public static Task Run(Message message, ILogger logger)
{
var controller = Main.Container.GetInstance<ConsumerController>();
// How can I attach the passed in logger instance so the rest of
// the services for the current flow re-use this instance?
return controller.Execute(message);
}
As you can see, the azure function framework passes me an instance of the ILogger already configured and initialized for this function call only.
I read through the documentation and I think I need a new scope here but I'm not sure. I only want this ILogger instance to be used during the async execution of this one method call. Each function call will use their own.
And just to be clear, the controller is only one of possibly many services (services, repositories, request handlers) involved in the execution of the task.
Any help would be great?
You can do the following:
Create a Proxy (e.g. ProxyLogger) implementation that implements ILogger, contains a ILogger Logger property, and forwards any call to that property.
Register that Proxy both as ILogger and ProxyLogger as Lifestyle.Scoped.
Resolve ProxyLogger within your function.
Set ProxyLogger.Logger using the function's supplied ILogger.
Resolve the root object and use it.
Create a Proxy:
public class ProxyLogger : ILogger
{
public ILogger Logger { get; set; }
public void Log<TState>(LogLevel l, EventId id, TState s, Exception ex,
Func<TState,Exception,String> f) =>
this.Logger.Log<TState>(l, id, s, ex, f);
// Implement other functions
}
Register that Proxy:
container.Register<ProxyLogger>(Lifestyle.Scoped);
container.Register<ILogger, ProxyLogger>(Lifestyle.Scoped);
Resolve ProxyLogger within your function, set ProxyLogger.Logger using the function's supplied ILogger, and resolve the root object and use it.
public static Task Run(Message message, ILogger logger)
{
using (AsyncScopedLifestyle.BeginScope(Main.Container)
{
Main.Container.GetInstance<ProxyLogger>().Logger = logger;
var controller = Main.Container.GetInstance<ConsumerController>();
return controller.Execute(message);
}
}
I do think, however, that this model leads to a very large amount of infrastructural code. Preferably you wish to keep this infrastructure to the absolute minimum. Instead, you could try keeping your Azure Functions small Humble Objects, as described here. That might not completely solve your initial problem, but you might not need to have a method specific logger anyway. But if, on the other hand, you need that, you might be able to mix that Humble Object approach with the use of C#'s CallerMemberName attribute and the ProxyLogger approach. You don't really need the Azure injected ILogger to do that for you.

Dynamically enable/ disable a triggered function in Azure WebJob

We have an azure web job that has two methods in the Functions.cs file. Both jobs are triggered off different Topics in Azure Service Bus.
As this uses reflection at run time to determine the functions that are to be run/triggered by messages hitting the topics, there are no references to these methods in code.
public static async Task DoWork([ServiceBusTrigger("topic-one", "%environmentVar%")] BrokeredMessage brokeredMessage, TextWriter log) {}
public static async Task DoOtherWork([ServiceBusTrigger("topic-two", "%environmentVar2%")] BrokeredMessage brokeredMessage, TextWriter log) {}
I have a need to have this web job run either both methods, or just one of them, based on a variable set at run time (it won't change one the job is running, but it is read in when the job starts). I can't simply wrap the internals of the methods in an if() based on the variable, as that would read and destroy the message.
Is it possible to use the JobHostConfiguration (an IServiceProvider) to achieve this, as that is built at run time. Is that was the JobHostConfiguration.IJobActivator can be used for?
Triggered Functions can be disabled when the Webjob starts.
You can have a look at this issue: Dynamic Enable/ Disable a function.
So the Webjob SDK provided a DisableAttribute`:
Can be applied at the Parameter/Method/Class level
Only affects triggered functions
[Disable("setting")] - If a config/environment value exists for the specified setting name, and its value is "1" or "True" (case insensitive), the function will be disabled.
[Disable(typeof(DisableProvider))] - custom Type declaring a function of signature bool IsDisabled(MethodInfo method). We'll call this method to determine if the function should be disabled.
This is a startup time only check. For disabled triggered functions, we simply skip starting of the function listener. However, when you update app settings bound to these attributes, your WebJob will automaticallly restart and your settings will take effect.
Setting names can include binding parameters (e.g. {MethodName}, {MethodShortName}, %test%, etc.)
In your case you need to use the DisableAttribute with a DisableProvider.
public class DoWorkDisableProvider
{
public bool IsDisabled(MethodInfo method)
{
// check if the function should be disable
// return true or false
return true;
}
}
public class DoOtherWorkkDisableProvider
{
public bool IsDisabled(MethodInfo method)
{
// check if the function should be disable
// return true or false
return true;
}
}
And your functions should be decorated with the disable attribute
[Disable(typeof(DoWorkDisableProvider))]
public static async Task DoWork([ServiceBusTrigger("topic-one", "%environmentVar%")] BrokeredMessage brokeredMessage, TextWriter log) {}
[Disable(typeof(DoOtherWorkkDisableProvider))]
public static async Task DoOtherWork([ServiceBusTrigger("topic-two", "%environmentVar2%")] BrokeredMessage brokeredMessage, TextWriter log) {}
Otherwise the JobHostConfiguration.IJobActivator is designed to inject dependencies into your functions. you can have a look at these posts related to:
Dependency injection using Azure WebJobs SDK?
Azure Triggered Webjobs Scope for Dependency Injection

Categories

Resources