rksh.me

End-to-End Tracking in Azure Functions

In today’s cloud-native applications, services are increasingly distributed—events trigger processes, APIs invoke background jobs, and messages flow asynchronously via queues or topics.

While this decoupling improves scalability, it makes observability more challenging. When something goes wrong, how do you trace the full journey of a single user’s request across dozens of services?

The answer: End-to-End Tracking using a correlation ID or TrackingID. In this blog, you’ll learn how to implement request tracking in Azure Functions that communicate through Azure Service Bus using the modern SDK.

Architecture at a Glance

  1. Producer Function: Receives an HTTP request, generates or extracts a TrackingID, sends a message to Service Bus with this ID as a custom property.
  2. Consumer Function: Listens to the Service Bus queue or topic, reads the TrackingID, and uses BeginScope for structured logging. It can optionally pass the ID further as needed.

1. Producer Function – Sending a Message with TrackingID

 1public static class ProducerFunction
 2{
 3    [FunctionName("ProducerFunction")]
 4    public static async Task<IActionResult> Run(
 5        [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
 6        [ServiceBus("myqueue", Connection = "ServiceBusConnection")] IAsyncCollector<ServiceBusMessage> outputMessages,
 7        ILogger log)
 8    {
 9        string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
10
11        // Extract or generate a TrackingID
12        string trackingId = req.Headers.ContainsKey("TrackingID")
13            ? req.Headers["TrackingID"].ToString()
14            : Guid.NewGuid().ToString();
15
16        // Create and send the Service Bus message with custom property
17        var message = new ServiceBusMessage(Encoding.UTF8.GetBytes(requestBody));
18        message.ApplicationProperties["TrackingID"] = trackingId;
19
20        log.LogInformation("Sending message with TrackingID: {TrackingID}", trackingId);
21
22        await outputMessages.AddAsync(message);
23
24        return new OkObjectResult(new
25        {
26            Status = "Message sent",
27            TrackingID = trackingId
28        });
29    }
30}

Highlights:

2. Consumer Function – Receiving and Logging with TrackingID

This consumer reads from the queue/topic, extracts the TrackingID, and uses BeginScope to enrich logs across the entire scope.

 1public static class ConsumerFunction
 2{
 3    [FunctionName("ConsumerFunction")]
 4    public static async Task Run(
 5        [ServiceBusTrigger("myqueue", Connection = "ServiceBusConnection")] ServiceBusReceivedMessage message,
 6        ILogger log)
 7    {
 8        // Extract TrackingID from the message properties
 9        string trackingId = message.ApplicationProperties.ContainsKey("TrackingID")
10            ? message.ApplicationProperties["TrackingID"].ToString()
11            : "N/A";
12
13        // Begin a logging scope that includes the TrackingID
14        using (log.BeginScope(new Dictionary<string, object> { ["TrackingID"] = trackingId }))
15        {
16            log.LogInformation("Received message with TrackingID: {TrackingID}", trackingId);
17
18            // Simulate message processing
19            string body = Encoding.UTF8.GetString(message.Body);
20            log.LogInformation("Processing message body: {Body}", body);
21
22            // Pass trackingId downstream if needed
23            // For example, add it to outgoing HTTP headers or service calls
24        }
25
26        await Task.CompletedTask;
27    }
28}

Scope Logging Bonus:
By using log.BeginScope, every log inside that scope gets auto-tagged with TrackingID. This is powerful with log aggregators like Application Insights, Elastic Stack, or Datadog.

Propagating TrackingID to Downstream Services

Once you’ve got the TrackingID, you can:

Keeping track of the ID across services provides unified visibility, even on large-scale systems.

Conclusion

With just a few lines of code, you can significantly improve observability in your distributed Azure solution:

This lightweight pattern is easy to add, doesn’t impact performance, and unlocks powerful traceability across your event-driven architecture.

What’s Next?

Happy tracing!

#azure-functions #service-bus #tracking #logging #distributed-systems

Reply to this post by email ↪