skip to Main Content

I would like to insert a middleware to Azure Durable Functions v4 (.NET 6) which gets a correlation id from the HttpTrigger and registers that into logger factory so that it is visible in application insights. Also vice versa; attaches correlation id to all outgoing requests. I do have multiple Azure Functions (some call each other) so I want to track a particular request by its CorrelationId.

I have tried guides here and here. However all of them has Program.cs class and register middleware by using that class. I only have startup and it looks like this:

public class Startup : FunctionsStartup
{
   public override void Configure(IFunctionsHostBuilder builder)
   {
       builder.Services
          .AddLogging()
          .AddHttpClient();
   }
}

How do I create a solution which fetches/attaches correlation id to requests/responses?

Something like: ...UseMiddleware<CorrelationIdFactory>()

2

Answers


  1. Chosen as BEST ANSWER

    Middlewares can only be registered in Azure Isolated Functions but not durable functions. Because host builder cannot be configured in Durable functions. However, MS team has just released support for Isolated Durable functions which can register middlewares and configure the host builder.

    Here is the released library for Azure Isolated Durable functions.

    This SDK can be used to build Durable Functions apps that run in the Azure Functions .NET Isolated worker process.

    Here is how to create an Azure Isolated Durable function which sets correlationId on every http request. Program.cs file:

    var host = new HostBuilder()
        .ConfigureFunctionsWorkerDefaults(workerApplication =>
            {
               // Register our custom middlewares with the worker
    
               workerApplication.UseWhen<StampHttpHeaderMiddleware>((context) =>
               {
                   // We want to use this middleware only for http trigger invocations.
                   return context.FunctionDefinition.InputBindings.Values
                             .First(a => a.Type.EndsWith("Trigger")).Type == "httpTrigger";
               });
           })
        .Build();
            //</docsnippet_middleware_register>
    
    host.Run();
    

    StampHttpHeaderMiddleware.cs file:

    internal sealed class StampHttpHeaderMiddleware : IFunctionsWorkerMiddleware
    {
        public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
        {
            var requestData = await context.GetHttpRequestDataAsync();
    
            string correlationId;
            if (requestData!.Headers.TryGetValues("x-correlationId", out var values))
            {
                correlationId = values.First();
            }
            else
            {
                correlationId = Guid.NewGuid().ToString();
            }
    
            await next(context);
    
            context.GetHttpResponseData()?.Headers.Add("x-correlationId", correlationId);
        }
    }
    

    And this is our Http Trigger which you can get/set correlationId into:

    static class HelloSequenceUntyped
    {
    [Function(nameof(StartHelloCitiesUntyped))]
    public static async Task<HttpResponseData> StartHelloCitiesUntyped(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
        [DurableClient] DurableTaskClient client,
        FunctionContext executionContext)
    {
        ILogger logger = executionContext.GetLogger(nameof(StartHelloCitiesUntyped));
    
        var correlationId = req.Headers.GetValues("x-correlationId").FirstOrDefault();
        string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(nameof(HelloCitiesUntyped));
        logger.LogInformation("Created new orchestration with instance ID = {instanceId}", instanceId);
    
        return client.CreateCheckStatusResponse(req, instanceId);
    }
    
    [Function(nameof(HelloCitiesUntyped))]
    public static async Task<string> HelloCitiesUntyped([OrchestrationTrigger] TaskOrchestrationContext context)
    {
        string result = "";
        result += await context.CallActivityAsync<string>(nameof(SayHelloUntyped), "Tokyo") + " ";
        result += await context.CallActivityAsync<string>(nameof(SayHelloUntyped), "London") + " ";
        result += await context.CallActivityAsync<string>(nameof(SayHelloUntyped), "Seattle");
        return result;
    }
    
    [Function(nameof(SayHelloUntyped))]
    public static string SayHelloUntyped([ActivityTrigger] string cityName, FunctionContext executionContext)
    {
        ILogger logger = executionContext.GetLogger(nameof(SayHelloUntyped));
        logger.LogInformation("Saying hello to {name}", cityName);
        return $"Hello, {cityName}!";
    }
    }
    

  2. To insert a middleware to Azure Durable Functions that attaches a correlation ID to the logs.

    1. Create a custom middleware class that implements the middleware logic you want to insert.
    2. The middleware should take in a Func as input and return a Task.
    3. Register the middleware in the startup class of your Durable Functions app, use the Use method on the IApplicationBuilder instance to register your custom middleware
    public class CorrelationIdMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<CorrelationIdMiddleware> _logger;
    
        public CorrelationIdMiddleware(RequestDelegate next, ILogger<CorrelationIdMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }
    
        public async Task InvokeAsync(HttpContext context)
        {
            var correlationId = context.Request.Headers["CorrelationId"].FirstOrDefault();
            if (string.IsNullOrEmpty(correlationId))
            {
                correlationId = Guid.NewGuid().ToString();
            }
    
            using (_logger.BeginScope(new Dictionary<string, object>
            {
                ["CorrelationId"] = correlationId
            }))
            {
                context.Items["CorrelationId"] = correlationId;
                _logger.LogInformation("Correlation Id: {CorrelationId}", correlationId);
                await _next(context);
            }
        }
    }
    
    

    To use the middleware you need to register it in the Startup.cs file

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
    
        app.UseMiddleware<CorrelationIdMiddleware>();
    
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapFunctions();
        });
    }
    

    You can then access the correlation ID in your functions as shown below

    
    [FunctionName("Function1")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
        ILogger log,
        ExecutionContext context)
    {
        var correlationId = req.HttpContext.Items["CorrelationId"].ToString();
        log.LogInformation("Correlation Id: {CorrelationId}", correlationId);
    
    }
    

    For the requests, you can add the correlation ID to the headers as shown below.

    using var client = new HttpClient();
    client.DefaultRequestHeaders.Add("CorrelationId", correlationId);
    var response = await client.GetAsync("https://sample.com");
    

    References taken from

    Durable Functions

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search