What a probe observes
AddHealthChecks with MapHealthChecks makes a health endpoint available. Without additional registrations, its response includes no database, queue or remote supplier check. Healthy means the application could respond within that scope.
If acceptance requires querying a dependency, that condition needs a deliberate check with appropriate cost and time limits. Avoid turning the probe into a heavy operation competing with the traffic it is meant to protect.
Readiness and restart
An instance can be alive while preparing data required to serve requests. Negative readiness should prevent traffic admission under the consumer contract; it does not automatically mean a restart resolves the condition. If initialization normally takes 60 seconds, negative readiness at 20 seconds can be expected.
Also define a deadline for investigating initialization that never finishes. The live probe can exclude those checks, distinguishing ability to respond from functional readiness.
Filtering and HTTP meaning
Registration tags become a filter only when the endpoint Predicate examines them. An always-false Predicate excludes all checks rather than choosing the fastest one. HTTP mapping must also match the consumer: Healthy and Degraded return 200 by default, while Unhealthy returns 503.
If the load balancer must remove a Degraded instance, explicitly change that mapping or use a consumer interpreting status under the same contract.
Freshness and external action
A Healthy body stored by a proxy represents an earlier observation, not the instance’s current ability. By default, middleware adds headers preventing response caching. AllowCachingResponses=true merely stops modifying those headers; it neither implements a cache nor guarantees revalidation.
In the example, MapHealthChecks publishes signals but does not prevent calls to /about. The external consumer configured for those probes decides traffic admission.
Logs with a defined purpose
Analyzing status and duration does not require automatically logging every header and body. Select useful fields and restrict log access. Under HTTP logging options, allowing a header name permits its value to be logged; including Authorization can expose the credential.
Body limits reduce volume, but a secret can occur in the first bytes. Redaction and classification need their own configuration and must not be inferred from a size limit.
Metric dimensions
HTTP duration can be analyzed by route, status and additional categories. Use bounded sets for those categories: web, mobile and other are more controllable than a different ID on every request. Unbounded dimensions create many combinations and can exceed collection limits.
When enriching through IHttpMetricsTagsFeature, check that the feature exists; it depends on metric observation. Missing enrichment does not establish functional request failure.
The population behind the indicator
A histogram dominated by fast probes can give a misleading picture of business-request duration. Identify the population answering the operational question. DisableHttpMetrics on the endpoint can exclude probes from that metric while preserving the health function.
It does not necessarily disable all logs or monitoring. In the diagnostic record, retain the time window, included endpoints and active version so observations can be compared with consistent meaning.
// ASP.NET Core 10 Program.cs reading example.
// Simulated readiness from startup configuration; no dependency is contacted.
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.AspNetCore.HttpLogging;
using Microsoft.Extensions.Diagnostics.HealthChecks;
var builder = WebApplication.CreateBuilder(args);
string readiness = builder.Configuration["Training:Readiness"] ?? "starting";
builder.Services.AddHealthChecks().AddCheck("training-readiness", () =>
readiness switch
{
"ready" => HealthCheckResult.Healthy(),
"degraded" => HealthCheckResult.Degraded(),
_ => HealthCheckResult.Unhealthy()
}, tags: new[] { "ready" });
builder.Services.AddHttpLogging(options =>
{
options.LoggingFields = HttpLoggingFields.ResponseStatusCode |
HttpLoggingFields.Duration;
});
var app = builder.Build();
app.UseHttpLogging();
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false
}).DisableHttpMetrics();
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready"),
ResultStatusCodes =
{
[HealthStatus.Degraded] = StatusCodes.Status503ServiceUnavailable
}
}).DisableHttpMetrics();
app.MapGet("/about", () => Results.Ok(new { purpose = "probe-reading-example" }));
app.Run();Interpret Training:Readiness as synthetic state captured at startup. With starting, live responds Healthy and ready Unhealthy; with degraded, ready returns 503 through explicit configuration. The example does not load an index, query a database or block /about: traffic removal depends on the configured external consumer.
Changing configuration after startup does not modify the captured variable.
Common pitfalls
A 200 from a basic probe does not validate dependencies. Degraded uses 200 by default. A byte limit does not anonymize a body. Tags containing unique IDs can produce excessive series. Disabling a probe metric does not disable the probe itself.
Related topics: Test scope and integration evidence · Deployment, proxies and recovery
Choose the operational decision first and its observable condition second. Check filtering, HTTP mapping, freshness and measured population before accepting the conclusion.
Reference: Basic probe boundary · ASP.NET Core 10.0 / .NET 10 / C# 14