Part 1 covered the messaging services. Part 2 covered orchestration. Part 3 covered how the whole pipeline gets secured. None of that matters if something breaks at 2am and there's no way to find out why. This part covers the actual toolkit for debugging and monitoring a real Azure integration pipeline, and deliberately ties every tool back to the specific services already built across Parts 1 through 3, the same pattern the whole series has followed.
Topic 1: Application Insights
Application Insights is Azure's Application Performance Monitoring service, covered in real depth in an earlier post on this blog. It automatically collects telemetry from your applications - every request, every dependency call, every exception, every custom log message.
Think of a flight data recorder, continuously capturing everything happening during a flight, automatically, without the pilot needing to manually log anything - so if something goes wrong, there's a complete, detailed record to investigate afterward.
Application Insights fits anywhere in the pipeline that runs actual code - Function Apps, and any custom API behind APIM - anywhere telemetry needs to be automatically captured without manual logging discipline being the only safety net.
To activate it in a Function App or any ASP.NET Core project, install the NuGet package Microsoft.ApplicationInsights.AspNetCore, then in Program.cscall AddApplicationInsightsTelemetry with the connection string, linked either directly in App Service Configuration or via a Key Vault reference, which is the same Managed Identity plus Key Vault pattern covered in Part 3. Logic Apps get enabled differently, through Diagnostic Settings instead of code, since Logic Apps don't run custom compiled code - Diagnostic settings, Add diagnostic setting, Send to Log Analytics workspace, covered next.
// Function App / any ASP.NET Core project
// 1. Install NuGet: Microsoft.ApplicationInsights.AspNetCore
// 2. In Program.cs:
builder.Services.AddApplicationInsightsTelemetry(
builder.Configuration["ApplicationInsights:ConnectionString"]
);
What kind of debugging and monitoring it does: it captures the four core telemetry types -
- requests, every HTTP call in or out;
- dependencies, every outbound call your code makes, SQL, Service Bus, an external API;
- exceptions, full stack traces;
- traces, your own ILogger output.
This is the raw material every other debugging technique in this post is actually built on top of.
How this connects to Parts 1 through 3: a Function App triggered by Service Bus, from Parts 1 and 2, automatically logs that trigger as a dependency call. A call to Key Vault for a secret, from Part 3, shows up as a dependency too. Nothing needs manual instrumentation for these - App Insights captures the entire pipeline's activity automatically, the moment it's wired in.
Topic 2: Log Analytics Workspace
A Log Analytics workspace is the actual queryable data store that Application Insights telemetry, and diagnostic logs from many other Azure services, gets written into. This is a genuinely common point of confusion worth being precise about: Application Insights is the collector; Log Analytics is the warehouse the collected data actually lives in, queried using KQL.
Think of it this way: if Application Insights is the flight data recorder capturing everything during the flight, Log Analytics is the secure archive facility where every recorder's data actually gets stored and can be pulled up for analysis later - and critically, multiple flight recorders, multiple Azure resources, can all feed into the same archive facility.
This one-workspace, multiple-sources model matters because a single Log Analytics workspace can receive telemetry from Application Insights, your Function Apps' code-level telemetry, Logic Apps diagnostic logs covering run history and trigger or action outcomes, Service Bus diagnostic logs covering message counts and dead-letter activity, Azure SQL diagnostic logs covering query performance and errors, and APIM diagnostic logs covering every gateway request. This means one KQL query, against one workspace, can correlate data across services that would otherwise live in completely separate places.
To activate it, first create a Log Analytics workspace, or use an existing one, through Azure Portal, Log Analytics workspaces, Create. Then for each service in the pipeline, point its diagnostic settings at this same workspace - Service Bus namespace, Diagnostic settings, Add, Send to Log Analytics workspace, and repeat this for Logic Apps, SQL, and APIM. Application Insights resources can also be workspace-based, meaning their data lands in this same shared Log Analytics workspace automatically rather than a separate silo.
What kind of debugging and monitoring it does: it's not itself a monitoring feature - it's the foundation that makes cross-service KQL querying possible at all. Its actual monitoring value shows up entirely through the queries run against it, covered in the next topic.
How this connects to Parts 1 through 3: a single Log Analytics workspace receiving diagnostic data from every service across the Part 2 end-to-end pipeline, APIM, Logic App, Function App, Service Bus, SQL, is what makes it possible to write one query tracing a single request across all of those services together, rather than needing to check five separate places manually.
Topic 3: KQL Query Patterns for This Pipeline
This is Kusto Query Language, covered in general depth in an earlier post on this blog, applied here specifically to the multi-service pipeline built across Parts 1 through 3, rather than a single application's logs in isolation.
Think of it the same way it was covered in the earlier KQL post - SQL for your telemetry, reading left to right through pipe operators.
KQL fits the Log Analytics workspace's Logs blade, or directly within Application Insights' own Logs view, which queries the same underlying data when workspace-based.
// Failed requests across the WHOLE pipeline, not just
// one service, in the last hour
union requests, dependencies
| where timestamp > ago(1h)
| where success == false
| project timestamp, itemType, name, resultCode, duration
| order by timestamp desc
// Service Bus dependency calls specifically, filtered
// from the general dependencies stream
dependencies
| where timestamp > ago(1h)
| where type == "Azure Service Bus"
| where success == false
| project timestamp, name, target, resultCode
// Correlate a Function App exception to the specific
// Service Bus message that triggered it
exceptions
| where timestamp > ago(1h)
| where operation_Name == "ProcessOrder"
| project timestamp, outerMessage, operation_Id
// operation_Id here is the thread to pull -
// covered fully in the next topic
What kind of debugging and monitoring it does: ad-hoc investigation, answering a specific question about what happened, when, and how often, across however many services the query is scoped to touch.
How this connects to Parts 1 through 3: every problem scenario across Parts 1 and 2, a stuck order, a failed transformation, a dead-lettered message, ultimately gets investigated by writing a KQL query shaped like the ones above, filtered to the specific service, time window, and operation involved.
Topic 4: Distributed Tracing with operation_Id
Every request entering the pipeline gets a unique operation_Id automatically. Every dependency call, log message, and exception that happens while that request is being handled, across every service it touches, shares that same identifier.
Think of a single tracking number on a package that gets handed off between multiple couriers, warehouses, and delivery trucks. Each handoff point logs an update against that same tracking number, so the package's entire journey can be reconstructed from pickup to delivery, regardless of how many different companies handled it along the way.
This is automatic once Application Insights is wired in, from Topic 1, with no additional activation step needed. The operation_Id propagates automatically across HTTP calls, Service Bus messages, and Function App executions within the same Azure telemetry ecosystem. To make it explicitly visible in your own logs, you can log it directly alongside your own structured logging.
_logger.LogInformation(
"Processing order {OrderId} with operation {OperationId}",
order.Id,
Activity.Current?.RootId ?? "unknown"
);
// Tracing ONE request's complete journey across
// every service it touched
union requests, dependencies, exceptions, traces
| where timestamp > ago(1h)
| where operation_Id == "abc123-def456"
| project timestamp, itemType, name, message, success
| order by timestamp asc
// Output reconstructs the ENTIRE path:
// 08:00:01 request POST /orders (APIM)
// 08:00:02 dependency Logic App triggered
// 08:00:03 dependency Function App: ValidateOrder
// 08:00:04 dependency Service Bus: message sent
// 08:00:05 dependency Azure SQL: INSERT
// 08:00:05 request 200 OK returned
What kind of debugging and monitoring it does: answers "what actually happened to this specific request" - the single most useful debugging technique when a specific customer or specific order is reported as broken, rather than a general pattern across many requests.
How this connects to Parts 1 through 3: this is the technique that makes the entire Part 2 complete-picture pipeline, APIM to Logic App to Function App to Service Bus to SQL, genuinely traceable end to end, exactly as referenced when that pipeline was first built - this topic is where that promise actually gets fulfilled with a real, runnable query.
Topic 5: Investigating a Failed Logic App Run
Logic Apps maintain a Run History - a literal, step-by-step record of every trigger and action for every execution, including the exact input and output of each individual step.
Think of a black-box flight recorder specifically for one single flight, showing every single instrument reading at every point during that one flight - not a general log, a complete replay of exactly what happened, action by action.
To access it, go to your Logic App's Overview page in the Azure Portal, where Runs history is shown automatically, no separate activation needed, since this is built in by default. Click any run, especially one marked Failed, to see every action in the workflow, color-coded green for succeeded or red for failed. Click the red action specifically to see its exact input, what data it received, and exact output, the error it actually threw.
The actual investigation flow looks like this: open Runs history and filter to Status Failed, click the specific failed run, visually scan for the red failed action in the sequence, click it and expand Inputs to see exactly what this action received from the previous step, expand Outputs to see the exact error message and status code returned, and use Resubmit to re-run this exact same run, with the same input, after fixing the underlying issue, without needing to wait for a new trigger.
What kind of debugging and monitoring it does: deterministic, step-by-step replay of exactly what happened in one specific Logic App execution - genuinely the fastest way to debug a Logic App failure, since it requires no KQL query at all, just clicking through the visual designer's own run history.
How this connects to Parts 1 through 3: in the Part 2 Logic Apps problem scenario, polling a partner API, validating, writing to SQL, a failure here, the partner API returning an unexpected shape, a validation condition behaving unexpectedly, is diagnosed by opening exactly this Run History view and inspecting the specific action that failed.
Topic 6: Investigating a Function App Failure
There are two complementary tools here - Live Metrics for what's happening right now, and Application Insights' Failures blade for investigating what already happened, down to the actual stack trace.
Think of Live Metrics as watching a patient's vital signs monitor in real time during a procedure. The Failures blade is the detailed medical chart reviewed afterward to understand exactly what went wrong and when.
Live Metrics, for real-time monitoring during an active incident, is accessed through your App Insights resource, Live Metrics, showing incoming request rate, failure rate, CPU and memory per instance, and exception rate, all with sub-second latency, genuinely real-time, with no activation needed beyond Application Insights already being wired in from Topic 1. The Failures blade, for after-the-fact investigation, is accessed through your App Insights resource, Failures, showing failed requests grouped by operation, exception types grouped and counted, and clicking into any one shows the full stack trace down to the exact line of code that threw.
// The KQL equivalent of what the Failures blade
// shows visually, if querying directly
exceptions
| where timestamp > ago(24h)
| where operation_Name == "ProcessOrder"
| summarize Count = count() by type, outerMessage
| order by Count desc
// Groups thousands of raw exception rows into a
// handful of actual distinct problems - the same
// technique covered in the earlier KQL post
What kind of debugging and monitoring it does: Live Metrics answers "is this actively getting worse right now" during a live incident, particularly useful right after a deployment. The Failures blade answers "what specifically broke, how often, and where in the code" after the fact.
How this connects to Parts 1 through 3: the Part 2 Function Apps problem scenario, complex order transformation logic, if that transformation throws an unexpected exception on a specific SKU format, the Failures blade's stack trace points to the exact line in the OrderTransformer class, from Part 2's example, where it happened.
Topic 7: Dead-Letter Queue Investigation
This is a direct callback to Service Bus from Part 1 - when a message exhausts its retry attempts, it moves to the Dead Letter Queue rather than disappearing, and it carries specific metadata explaining exactly why.
Think of a returned-mail bin at a post office, where every piece of undeliverable mail gets a stamped note explaining specifically why it couldn't be delivered, refused, address not found, damaged, rather than just being discarded with no explanation.
To access it through the portal, go to your Service Bus namespace, your queue, and look for the message count next to Dead-letter messages specifically, separate from the main queue's active message count. Programmatically, you create a receiver scoped to the dead-letter sub-queue directly.
var receiver = client.CreateReceiver(
"myqueue",
new ServiceBusReceiverOptions {
SubQueue = SubQueue.DeadLetter
}
);
var messages = await receiver.ReceiveMessagesAsync(10);
foreach (var msg in messages)
{
// These two fields are the actual answer to
// "why did this fail" - always check them FIRST
Console.WriteLine($"Reason: {msg.DeadLetterReason}");
Console.WriteLine($"Description: {msg.DeadLetterErrorDescription}");
Console.WriteLine($"Body: {msg.Body}");
}
The actual investigation decision, once you know why, breaks into three paths. A genuine data problem, a bad SKU format or a missing field, means fixing the source data and manually resubmitting the message. A transient issue now resolved, a downstream API briefly down during the original attempts, means simply resubmitting the message as-is, with no data change needed. An actual bug in the processing code means fixing the code, redeploying, and only then resubmitting affected messages, since resubmitting before the fix just dead-letters them again.
What kind of debugging and monitoring it does: root-cause investigation for messages that definitively failed processing - the DeadLetterReason and DeadLetterErrorDescription fields specifically exist to turn "something failed" into a precise, actionable reason.
How this connects to Parts 1 through 3: this directly extends the Part 1 Service Bus problem scenario, per-customer ordered processing with a dead-letter queue configured - this topic is literally "what do you actually do once something lands there," which the earlier scenario's strategy stopped short of covering.
Topic 8: Alerting Strategy
Alerting means proactive notification when something goes wrong, rather than only finding out when a customer complains or someone happens to check a dashboard.
Think of a smoke detector versus manually checking every room for fire periodically. Alerting means the system tells you the moment something crosses a genuinely concerning threshold, rather than you needing to remember to go looking.
To activate it, go to the specific resource, or the Log Analytics workspace for cross-service alerts, in the Azure Portal, then Alerts, Create, Alert rule. You define a signal or condition, either a metric threshold or a KQL log query returning results, an action group specifying who gets notified, email, SMS, a Teams webhook, or triggering another Function App, and a severity level.
// A genuinely useful custom log alert - fires when
// the Service Bus Dead Letter Queue has ANY messages
Custom log search alert, KQL:
AzureMetrics
| where MetricName == "DeadletteredMessages"
| where Total > 0
// A DLQ alert should almost always exist, given
// Topic 7 - a message sitting there with nobody
// notified defeats the purpose of having a DLQ
// at all instead of just losing the message silently
The key distinction worth getting right is rate versus raw count. Alerting on "5 failures" means the same rule fires identically whether that's 5 failures out of 10 requests, a genuinely broken 50% failure rate, or 5 failures out of 50,000 requests, likely normal background noise. Alerting on failure rate, not raw count, is what actually distinguishes a real incident from expected noise.
// Rate-based alert, not raw count
requests
| where timestamp > ago(5m)
| summarize Total = count(), Failed = countif(success == false)
| extend FailureRate = round(100.0 * Failed / Total, 1)
| where FailureRate > 10
// Fires only when MORE THAN 10% of requests failed
// in the last 5 minutes, not just "5 failures happened"
What kind of debugging and monitoring it does: it shifts from reactive, someone reported a problem, let's investigate, to proactive, the team knows within minutes, often before a customer notices at all.
How this connects to Parts 1 through 3: a genuinely complete pipeline built across this series would have alerts on at minimum Service Bus DLQ count greater than 0, from Topic 7, Function App failure rate exceeding a threshold, from Topic 6, and Logic App run failure count, from Topic 5 - alerting is what turns everything else in this post from tools available if you go looking into the team finding out automatically.
Putting It Together: "Something Is Broken, Walk Me Through Your Process"
This exact question shape is one of the most common in a panel interview for this topic area. Here's the structured answer, built entirely from this post's tools.
First, an alert fires, from Topic 8 - failure rate on the order-processing Function App exceeded 10% in the last 5 minutes.
Second, open the Failures blade, from Topic 6, in Application Insights, and immediately see the exception type and count driving this, say, a specific ValidationException spiking.
Third, click into one specific failed request and note its operation_Id.
Fourth, run a distributed tracing query, from Topic 4, using that operation_Id, reconstructing the full path this specific request took: APIM, Logic App, Function App, and where it actually failed.
Fifth, if the failure happened inside a Logic App action specifically, open Run History, from Topic 5, for that exact run, and inspect the failing action's input and output directly.
Sixth, if the message was ultimately dead-lettered, from Topic 7, check DeadLetterReason and DeadLetterErrorDescription for the precise cause.
Seventh, once the root cause is identified, say a partner started sending a new SKU format the validation logic doesn't recognize, fix the code, redeploy, then resubmit the affected dead-lettered messages.
Eighth, write, or confirm the existence of, a KQL-based alert, from Topic 8, that would catch this specific failure pattern earlier next time.
This is the shape of answer, naming specific tools in a specific order, each connected to a specific part of the pipeline already built across this series, that demonstrates real operational thinking, not just naming Application Insights as a one-word answer.
Key Lessons
Application Insights collects telemetry automatically; Log Analytics is the queryable store that telemetry lives in - understanding this relationship matters more than most people initially assume.
operation_Id is what makes a request genuinely traceable across every service it touches - this is the technique that fulfills the complete-picture promise from Part 2's pipeline diagram.
A Logic App's Run History gives deterministic, step-by-step replay with exact input and output per action - often faster to debug than writing a KQL query at all.
Live Metrics answers "what's happening right now"; the Failures blade answers "what already happened and exactly where in the code" - different tools for different moments in an incident.
A dead-lettered message's DeadLetterReason and DeadLetterErrorDescription should always be checked first, before any other investigation - they often directly answer why without further digging.
Alert on failure rate, not raw error count - the same absolute number means something completely different depending on total volume.
The strongest interview answer to "something is broken, walk me through it" names specific tools in a specific sequence, each tied to a specific part of a real pipeline, not a generic list of Azure service names.
The Series, Complete
This closes out the Azure integration interview prep series. Part 1 covered the messaging services - Service Bus, Storage Queues, Event Hub, Event Grid. Part 2 covered the orchestration layer - Logic Apps, Function Apps, Durable Functions. Part 3 covered how that pipeline actually gets secured - Managed Identity, Key Vault, VNet Integration, Private Endpoints, NSGs, RBAC, and token validation. Part 4 covered debugging and monitoring the pipeline once something inevitably goes wrong - Application Insights, Log Analytics, distributed tracing, and a structured process for the "something is broken" question. Four parts, one complete architecture story, from how data enters the system to how a failure gets traced back to its exact root cause.
Summary
Debugging and monitoring complete the picture built across this entire series - a pipeline that moves data reliably, from Part 1, processes it with the right orchestration tool, from Part 2, and is genuinely secured end to end, from Part 3, still needs a real answer for "how do you know when it breaks, and how do you find out why." Application Insights and Log Analytics capture and store the telemetry. operation_Id makes any single request traceable across every hop. Run History, Live Metrics, and the Failures blade each answer a different shape of investigative question. Dead-letter queues carry their own answer if you know where to look. Alerting closes the loop, turning "someone eventually notices" into "the team knows within minutes." Together, these four parts describe a complete, real Azure integration architecture, which is exactly the shape of answer a panel interview is listening for.
More from TechStack Blog: Azure: https://www.techstackblog.com/category.html?cat=azure
CS Fundamentals: https://www.techstackblog.com/category.html?cat=cs-fundamentals

Top comments (0)