PollyHealthChecks 1.0.8
dotnet add package PollyHealthChecks --version 1.0.8
NuGet\Install-Package PollyHealthChecks -Version 1.0.8
<PackageReference Include="PollyHealthChecks" Version="1.0.8" />
<PackageVersion Include="PollyHealthChecks" Version="1.0.8" />
<PackageReference Include="PollyHealthChecks" />
paket add PollyHealthChecks --version 1.0.8
#r "nuget: PollyHealthChecks, 1.0.8"
#:package PollyHealthChecks@1.0.8
#addin nuget:?package=PollyHealthChecks&version=1.0.8
#tool nuget:?package=PollyHealthChecks&version=1.0.8
PollyHealthChecks
<img src="icon.png" width="100" align="right" />
ASP.NET Core health checks for Polly v8 circuit breakers, rate limiters, and timeouts — expose resilience state as /health endpoint responses so Kubernetes probes, load balancers, and monitoring dashboards can automatically react to your resilience state.
var stateProvider = new CircuitBreakerStateProvider();
services.AddResiliencePipeline("payments-api", builder =>
builder.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
StateProvider = stateProvider,
FailureRatio = 0.5,
MinimumThroughput = 5,
BreakDuration = TimeSpan.FromSeconds(30),
}));
services.AddHealthChecks()
.AddPollyCircuitBreaker("payments-api", stateProvider); // ← one line
When the circuit opens, /health returns Unhealthy — Kubernetes stops routing traffic, zero manual intervention required.
Why PollyHealthChecks?
"How do I expose my circuit breaker state in the ASP.NET Core health endpoint?" is one of the most-asked Polly questions. Without this package you must write your own IHealthCheck, wire up CircuitBreakerStateProvider, and map the four circuit states manually. PollyHealthChecks does all of that in a single method call.
| Without PollyHealthChecks | With PollyHealthChecks |
|---|---|
Write a custom IHealthCheck per circuit |
One AddPollyCircuitBreaker() call |
| Manually map all 4 circuit states | Built-in Closed→Healthy, HalfOpen→Degraded, Open→Unhealthy |
| Re-implement for every microservice | Shared package, consistent behaviour |
| Forget to update when you add circuits | AddPollyCircuitBreakers() registers them all in one call |
| No visibility into why a circuit is unhealthy | CircuitBreakerHealthTracker adds last-failure time and reason |
| Rate limiters/timeouts have no health signal | AddPollyRateLimiter / AddPollyTimeout cover those too |
Installation
dotnet add package PollyHealthChecks
Targets net6.0, net8.0, and net9.0.
Dependencies: Polly.Core 8.*, Microsoft.Extensions.Diagnostics.HealthChecks 8.*
Quick start
1. Attach a CircuitBreakerStateProvider to your pipeline
using Polly.CircuitBreaker;
using PollyHealthChecks;
var stateProvider = new CircuitBreakerStateProvider();
services.AddResiliencePipeline("downstream-api", builder =>
builder.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
StateProvider = stateProvider,
FailureRatio = 0.5,
SamplingDuration = TimeSpan.FromSeconds(10),
MinimumThroughput = 5,
BreakDuration = TimeSpan.FromSeconds(30),
}));
2. Register the health check
services.AddHealthChecks()
.AddPollyCircuitBreaker("downstream-api", stateProvider);
3. Map the health endpoint
app.MapHealthChecks("/health");
State mapping
| Circuit state | Health status | Meaning |
|---|---|---|
Closed |
Healthy |
Normal operation |
HalfOpen |
Degraded |
Testing recovery — partial traffic |
Open |
Unhealthy (configurable) |
Calls rejected — dependency down |
Isolated |
Unhealthy (configurable) |
Manually isolated |
Kubernetes liveness & readiness probes
Use tags to split circuit breaker health into separate liveness and readiness probes:
services.AddHealthChecks()
.AddPollyCircuitBreaker("payments-api", paymentsStateProvider, tags: ["ready"])
.AddPollyCircuitBreaker("inventory-api", inventoryStateProvider, tags: ["ready"])
.AddPollyCircuitBreaker("auth-api", authStateProvider, tags: ["live", "ready"]);
// Liveness — just the critical auth circuit
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("live"),
});
// Readiness — all dependency circuits
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = r => r.Tags.Contains("ready"),
});
Kubernetes deployment:
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
Multiple circuit breakers
Monitor every downstream dependency independently:
services.AddHealthChecks()
.AddPollyCircuitBreaker("payments-api", paymentsStateProvider)
.AddPollyCircuitBreaker("inventory-api", inventoryStateProvider, failureStatus: HealthStatus.Degraded)
.AddPollyCircuitBreaker("auth-api", authStateProvider, tags: ["ready", "live"])
.AddPollyCircuitBreaker("email-service", emailStateProvider, failureStatus: HealthStatus.Degraded);
Registering many circuits is a single reviewable call with AddPollyCircuitBreakers, so it's harder to forget wiring one up when a new circuit is added:
services.AddHealthChecks()
.AddPollyCircuitBreakers(
new PollyCircuitBreakerRegistration("payments-api", paymentsStateProvider),
new PollyCircuitBreakerRegistration("inventory-api", inventoryStateProvider, HealthStatus.Degraded, ["ready"]),
new PollyCircuitBreakerRegistration("auth-api", authStateProvider, Tags: ["ready", "live"]));
Custom failure status
Demote a non-critical circuit to Degraded so a single open circuit doesn't fail the entire readiness check:
services.AddHealthChecks()
// Critical — Unhealthy when open (default)
.AddPollyCircuitBreaker("payments-api", paymentsStateProvider)
// Non-critical — Degraded when open (app still serves traffic)
.AddPollyCircuitBreaker("analytics-api", analyticsStateProvider,
failureStatus: HealthStatus.Degraded);
Richer diagnostics with CircuitBreakerHealthTracker
Every health check result already includes state and checkedAtUtc in its Data. Attach a CircuitBreakerHealthTracker to also capture when the circuit last changed state and why it last failed — so an Unhealthy response explains itself instead of just reporting a status:
var tracker = new CircuitBreakerHealthTracker();
services.AddResiliencePipeline("payments-api", builder =>
builder.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
StateProvider = stateProvider,
OnOpened = tracker.OnOpened,
OnClosed = tracker.OnClosed,
OnHalfOpened = tracker.OnHalfOpened,
}));
services.AddHealthChecks()
.AddPollyCircuitBreaker("payments-api", stateProvider, tracker: tracker);
When the circuit is open, the description becomes e.g. "Circuit breaker is open. Last failure: Connection refused.", and Data gains lastStateChangeUtc, lastFailureUtc, and lastFailureReason.
Resolving the state provider from DI
If you register CircuitBreakerStateProvider in the container yourself, resolve it lazily instead of capturing a local variable:
services.AddSingleton<CircuitBreakerStateProvider>();
services.AddHealthChecks()
.AddPollyCircuitBreaker("payments-api", sp => sp.GetRequiredService<CircuitBreakerStateProvider>());
Rate limiter and timeout health checks
Polly v8 has no dedicated "bulkhead" strategy — concurrency limiting is done via a RateLimiter — so AddPollyRateLimiter covers both, reporting saturation from RateLimiter.GetStatistics():
var rateLimiter = new ConcurrencyLimiter(new ConcurrencyLimiterOptions { PermitLimit = 10, QueueLimit = 5 });
services.AddResiliencePipeline("downstream-api", builder => builder.AddRateLimiter(rateLimiter));
services.AddHealthChecks()
.AddPollyRateLimiter("downstream-api", rateLimiter);
Timeouts have no ambient state to inspect, so a TimeoutHealthTracker records recent OnTimeout events in a rolling window (5 minutes by default):
var tracker = new TimeoutHealthTracker();
services.AddResiliencePipeline("downstream-api", builder =>
builder.AddTimeout(new TimeoutStrategyOptions
{
Timeout = TimeSpan.FromSeconds(5),
OnTimeout = tracker.OnTimeout,
}));
services.AddHealthChecks()
.AddPollyTimeout("downstream-api", tracker, maxTimeoutsBeforeUnhealthy: 3);
OpenTelemetry metrics
CircuitBreakerMetrics.Track publishes circuit state as an OpenTelemetry-compatible observable gauge via System.Diagnostics.Metrics.Meter — no extra package required. Any MeterProvider that subscribes to the PollyHealthChecks meter sees the same state your /health endpoint reports:
CircuitBreakerMetrics.Track("payments-api", stateProvider);
services.AddOpenTelemetry().WithMetrics(builder => builder.AddMeter(CircuitBreakerMetrics.MeterName));
The gauge pollyhealthchecks.circuit_breaker.state reports 0=Closed, 1=Open, 2=HalfOpen, 3=Isolated, tagged with circuit_breaker.name.
HealthChecks UI integration
Works out-of-the-box with AspNetCore.HealthChecks.UI:
services.AddHealthChecksUI(opts =>
opts.AddHealthCheckEndpoint("App", "/health"))
.AddInMemoryStorage();
services.AddHealthChecks()
.AddPollyCircuitBreaker("payments-api", paymentsStateProvider)
.AddPollyCircuitBreaker("inventory-api", inventoryStateProvider);
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse,
});
app.MapHealthChecksUI();
Full ASP.NET Core example
var builder = WebApplication.CreateBuilder(args);
var paymentsStateProvider = new CircuitBreakerStateProvider();
var inventoryStateProvider = new CircuitBreakerStateProvider();
builder.Services.AddResiliencePipeline("payments-api", b =>
b.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
StateProvider = paymentsStateProvider,
FailureRatio = 0.5,
MinimumThroughput = 5,
BreakDuration = TimeSpan.FromSeconds(30),
}));
builder.Services.AddResiliencePipeline("inventory-api", b =>
b.AddCircuitBreaker(new CircuitBreakerStrategyOptions
{
StateProvider = inventoryStateProvider,
FailureRatio = 0.5,
MinimumThroughput = 5,
BreakDuration = TimeSpan.FromSeconds(30),
}));
builder.Services.AddHealthChecks()
.AddPollyCircuitBreaker("payments-api", paymentsStateProvider, tags: ["ready", "live"])
.AddPollyCircuitBreaker("inventory-api", inventoryStateProvider, tags: ["ready"]);
var app = builder.Build();
app.MapHealthChecks("/health/live", new HealthCheckOptions { Predicate = r => r.Tags.Contains("live") });
app.MapHealthChecks("/health/ready", new HealthCheckOptions { Predicate = r => r.Tags.Contains("ready") });
app.Run();
Related Packages
| Package | Downloads | Description |
|---|---|---|
| PollyOpenTelemetry | OpenTelemetry instrumentation for Polly v8 resilience pipelines | |
| PollyBackoff | Backoff delay strategies for Polly v8 resilience pipelines | |
| PollyGrpc | Polly v8 resilience interceptor for gRPC | |
| PollyEFCore | Polly v8 resilience pipelines for Entity Framework Core — wrap every EF Core query and SaveChanges with retry, timeout and circuit-breaker via a single AddPollyResilience() call | |
| PollyMailKit | Polly v8 resilience pipelines for MailKit — retry, timeout, and circuit-breaker for SmtpClient.SendAsync and any MailKit SMTP operation | |
| PollyMassTransit | Polly v8 resilience pipelines for MassTransit — retry, timeout, and circuit-breaker for IBus.Publish and ISendEndpointProvider.Send | |
| PollyOpenAI | Polly v8 resilience for OpenAI and Azure OpenAI API calls | |
| PollyAzureEventHub | Polly v8 resilience pipelines for Azure Event Hubs — retry, timeout, and circuit-breaker for EventHubProducerClient and EventHubConsumerClient | |
| PollySignalR | Polly v8 reconnect policy for SignalR | |
| PollyElasticsearch | Polly v8 resilience pipelines for Elastic.Clients.Elasticsearch 8+ — retry, timeout, and circuit-breaker for any Elasticsearch operation, plus a built-in ElasticTransientErrors predicate covering rate limiting (429), service unavailability (503), gateway timeouts (504), and connection failures | |
| PollyHangfire | Polly v8 resilience pipelines for Hangfire — retry, timeout, and circuit-breaker for IBackgroundJobClient.Enqueue and Schedule | |
| PollySendGrid | Polly v8 resilience pipelines for SendGrid — retry, timeout, and circuit-breaker for ISendGridClient.SendEmailAsync | |
| PollyMediatR | Polly v8 resilience pipelines for MediatR — add retry, timeout, circuit-breaker, rate-limiting, hedging, and chaos engineering to any MediatR request handler with a single line of DI registration | |
| PollyAzureKeyVault | Polly v8 resilience pipelines for Azure Key Vault — retry, timeout, and circuit-breaker for SecretClient, KeyClient, and CertificateClient | |
| PollyAzureQueueStorage | Polly v8 resilience pipelines for Azure Queue Storage — retry, timeout, and circuit-breaker for Azure.Storage.Queues QueueClient | |
| PollyRedis | Polly v8 resilience for StackExchange.Redis | |
| PollyAzureServiceBus | Polly v8 resilience for Azure Service Bus — retry, circuit breaker, and timeout for sending and receiving messages | |
| PollyKafka | Polly v8 resilience for Confluent.Kafka — retry, circuit breaker, and timeout for producers and consumers | |
| PollyAzureTableStorage | Polly v8 resilience pipelines for Azure Table Storage — retry, timeout, and circuit-breaker for Azure.Data.Tables TableClient | |
| PollyCaching | A caching resilience strategy for Polly v8 pipelines | |
| PollyChaos | Chaos engineering and fault-injection resilience strategies for Polly v8 pipelines | |
| PollyBulkhead | Bulkhead isolation strategy for Polly v8 resilience pipelines |
Support
If PollyHealthChecks is useful in your Kubernetes or monitoring setup, consider supporting the project:
💼 Need .NET / cloud-native help? Visit solidqualitysolutions.com for consulting and architecture services.
| PollyRabbitMQ | Polly v8 resilience for RabbitMQ.Client channels |
Also by the same author
| Package | Description |
|---|---|
| AutoLog.Generator | Compile-time high-performance logging — [Log(Level, Message)] generates LoggerMessage.Define. AOT-safe. |
| AutoHttpClient.Generator | Compile-time typed HTTP client — [HttpClient] on an interface generates a strongly-typed client. AOT-safe Refit alternative. |
| AutoDispatch.Generator | Compile-time CQRS dispatcher — [Handler] generates a strongly-typed IDispatcher. MediatR alternative. |
| AutoWire | Compile-time DI auto-registration — [Scoped]/[Singleton]/[Transient] generates IServiceCollection registration code. |
| AutoMap.Generator | Compile-time object mapping — [Map(typeof(Dto))] generates ToDto() extension methods. AutoMapper alternative. |
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 8.0.29)
- Polly.Core (>= 8.7.0)
- System.Threading.RateLimiting (>= 8.0.0)
-
net6.0
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 8.0.29)
- Polly.Core (>= 8.7.0)
- System.Threading.RateLimiting (>= 8.0.0)
-
net8.0
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 8.0.29)
- Polly.Core (>= 8.7.0)
- System.Threading.RateLimiting (>= 8.0.0)
-
net9.0
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 8.0.29)
- Polly.Core (>= 8.7.0)
- System.Threading.RateLimiting (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
1.1.0: Added AddPollyRateLimiter and AddPollyTimeout health checks, CircuitBreakerHealthTracker/TimeoutHealthTracker for richer last-failure diagnostics, bulk AddPollyCircuitBreakers registration, DI-factory overload, and CircuitBreakerMetrics for OpenTelemetry-compatible state gauges.
1.0.7: Fix package icon (was showing an incorrect/placeholder image).
1.0.6: Improved NuGet metadata, expanded tags, and overhauled README with Kubernetes probe examples, HealthChecks UI integration, and full Related packages table.
1.0.5: GitHub Sponsors and consulting CTA added to README.
1.0.2: Added net6.0 and net9.0 targets; improved discoverability tags including Kubernetes health probe keywords.