Monitoring and alerting: finding faults before your customers do

A practical guide to monitoring a business-critical application: four questions to ask, how to alert without fatigue, and keeping personal data out of logs.

An operations manager in a navy fleece points out of a large window at a depot yard of white lorry trailers while two colleagues listen, a switched-off wall screen behind them

A customer rings to say the portal has been down since the morning. Or the nightly import stopped last Tuesday and nobody noticed until finance asked why the figures looked odd. In both cases the software had been telling you something was wrong, in the sense that the evidence existed. Nobody was set up to hear it.

Monitoring is the work of making sure somebody hears it first. For a small team it is easy to overdo, with hundreds of alerts that get muted within a month, or to underdo, with a single uptime ping that reports "up" while the application quietly serves errors. This guide sets out a proportionate approach for a business-critical application: what to watch, how to alert, and how to keep personal data out of what you collect. The examples use Azure, but the thinking applies anywhere. We supply Microsoft licences and know these tools well, but the right choice depends on your business. Product details are as of November 2026.

Four questions

Almost everything worth monitoring answers one of these:

  1. Is it up? Can a user reach it at all?
  2. Is it correct? Are requests succeeding, and are the results right?
  3. Is it fast enough? Is it responding in a time users will tolerate?
  4. Is anything stuck? Are queues draining, scheduled jobs finishing and integrations still talking?

Start with one or two signals for each. Add more only when a real incident shows a gap.

Is it up: synthetic checks and health endpoints

A synthetic check is a request your monitoring tool makes on a schedule, from outside, pretending to be a user. It tells you what customers would see, including DNS, certificate and network problems that internal metrics miss.

In Azure, Application Insights availability tests do this. Microsoft recommends standard tests, which check response validity, performance and TLS certificate validity, and it has deprecated the older URL ping tests. Its guidance is to use at least five test locations, so you can tell a fault in your site from a network blip, and to alert when a minimum of three of the five fail.

The check is only as good as the page it requests. A home page that returns 200 even when the database is unreachable proves very little. Give the application a health endpoint that checks the things it cannot work without. ASP.NET Core has health checks built in:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks();   // add checks for your database and key dependencies

var app = builder.Build();

app.MapHealthChecks("/healthz");

app.Run();

Keep it quick, and keep it protected: Microsoft's documentation covers restricting the endpoint by port or requiring authorisation, so it does not become a free map of your dependencies for anyone who finds it.

Is it correct: errors and structured logs

For a web application, the most useful correctness signal is the rate of failed requests, and a handful of exceptions grouped by type. Application Insights collects both, and it can be instrumented with OpenTelemetry, a vendor-neutral standard, so the code is not tied to one product.

Two habits make the logs worth reading:

  • Structured logging. Log a message template with named values, not a pre-built string, so you can search and count by field.
  • Correlation ids. Give each request or job run an identifier and attach it to every log line, so you can follow one failure across the web app, the API and the background worker.
// Illustrative: ILogger with a scope carrying a correlation id
using (logger.BeginScope(new Dictionary<string, object> { ["CorrelationId"] = correlationId }))
{
    logger.LogInformation("Import started for supplier {SupplierId}", supplierId);
    // ...
    logger.LogWarning("Import finished with {RejectedRows} rejected rows", rejectedRows);
}

Notice that it logs a supplier id and a count, not a name or an email address. That brings us to data protection.

Keep personal data out of the logs

Logs tend to live longer and be shared more widely than the database they describe. Under UK GDPR, the data minimisation principle applies to them as much as to anything else: collect what you need for the purpose, and no more.

In practice: log internal identifiers, not names or email addresses; never log request bodies or authentication headers wholesale; remove anything you do not need. Hashing or swapping an identifier for a reference is pseudonymisation, not anonymisation: if it can be linked back to a person it is still personal data, so keep the same access controls, retention limits and data protection care around it. Set retention deliberately. The NCSC's introduction to logging for security purposes is useful on the security side, covering what to record and how to protect the logs from tampering, and it recommends holding the logs you would need to investigate an incident for a minimum of six months. Reconcile that with your data protection obligations rather than choosing one or the other. This is the same reason we collect diagnostic logs only, with controls to keep personal data out of them, when we look after a system.

Is it fast enough, and is anything stuck

Response time is worth tracking as a percentile (the time within which 95% of requests complete), not an average, because averages hide the slow tail users complain about.

"Stuck" is where silent failures live, and it deserves the most care in a bespoke system:

  • Queues. Alert on message age or depth growing, not on the queue existing.
  • Scheduled jobs. A job that does not run produces no error. Watch for the absence of success instead: have the job write a "completed" event, and alert if none has arrived within its expected window.
  • Integrations. Track the last successful exchange with each external system, and alert when it gets old.

An illustrative query for a heartbeat alert, as you would write it in the Application Insights Logs view (table names differ if you query the Log Analytics workspace directly):

// Illustrative: no completed import in the last 26 hours
customEvents
| where name == "NightlyImportCompleted" and timestamp > ago(26h)
| count

Attach this to a log search alert rule that fires when the count is zero. The 26 hours gives a nightly job a little slack. Set the rule's lookback period (the time range it queries) to at least 26 hours as well: the rule's own window limits the data the query can see, so a shorter window would miss a completed import and raise a false alarm. Checking once an hour is plenty.

Alert on symptoms, and make every alert earn its place

The aim is that a person is interrupted only when a person needs to act.

  • Alert on symptoms, not causes. "Checkout failures above 5% for ten minutes" is something a customer would feel. "CPU at 85%" often is not. Keep cause-level metrics on a dashboard for diagnosis.
  • Every alert needs an action. If the answer to "what would I do if this fired?" is "nothing", delete it or turn it into a dashboard tile.
  • Decide who is woken. Separate what needs attention now (the system is down) from what can wait until morning (a job is slow). For a small team, a simple rota and a clear escalation route are enough.
  • Expect state-based alerts. Azure's availability alerts email once when an endpoint goes down and once when it recovers, not repeatedly while it stays down, which helps limit noise.
  • Review monthly. Look at what fired, what was ignored and what was missed. Prune and tune. This review is what stops alert fatigue from creeping back.

One page a director can read

Alongside the technical dashboards, keep one page for the people who answer for the system: is it up, how many errors this week compared with last, when the last incident was, and whether the scheduled jobs are running. It should make sense without a glossary. If it takes a developer to interpret, it will not get read.

A sensible first step

Pick the single most important job or integration in your system, the one whose silent failure would hurt most, and add a heartbeat alert to it this week. Then add a real health endpoint and a standard availability test from several locations. Those two changes catch many of the faults that otherwise reach customers first.

If you would like help with this, setting up monitoring is part of onboarding a system into our software care service, and cloud migration to Azure is a natural moment to design it in.