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
- Producer Function: Receives an HTTP request, generates or extracts a
TrackingID
, sends a message to Service Bus with this ID as a custom property. - Consumer Function: Listens to the Service Bus queue or topic, reads the
TrackingID
, and usesBeginScope
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:
- Uses the
Azure.Messaging.ServiceBus
SDK. - Attaches
TrackingID
as a custom application property. - Logs the
TrackingID
for traceability.
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:
- Send it as a header in HTTP requests:
httpClient.DefaultRequestHeaders.Add("TrackingID", trackingId);
- Use it in telemetry or performance metrics
- Store it in databases or audit trails
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:
- Use
TrackingID
as a unique correlation ID - Forward it through Service Bus with
ApplicationProperties
- Use
BeginScope
to enrich logs at every stage
This lightweight pattern is easy to add, doesn’t impact performance, and unlocks powerful traceability across your event-driven architecture.
What’s Next?
- Integrate Application Insights for centralized tracking
- Extend this pattern to handle retries and dead-letter messages
- Use Middleware or DelegatingHandlers to forward
TrackingID
in outgoing HTTP requests automatically
Happy tracing!
#azure-functions #service-bus #tracking #logging #distributed-systems