Skip to content

Commit c802d5e

Browse files
authored
Redesign HealthStatus (again) (#520)
* Redesign HealthStatus (again) This change brings back the ability to return Healthy/Degraded/Unhealthy in a HealthCheckResult. We tried making this pass/fail in 2.2.0-preview3 and folks writing health checks for their own use pointed out (rightly so) that it was too limited. It's still possible for the app developer to configure the failure status of a health check, but it requires the health check author to cooperate. I also got rid of HealthStatus.Failed since it raises more questions than it answers. It's really not clear that it's valuable for a health check for behave different when throwing an unhandled exception. We would still recommend that a health check library handle exceptions that they know about and return `context.Registration.FailureStatus`.
1 parent 4c94bc2 commit c802d5e

File tree

17 files changed

+157
-154
lines changed

17 files changed

+157
-154
lines changed

samples/HealthChecksSample/DbConnectionHealthCheck.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ protected DbConnectionHealthCheck(string connectionString, string testQuery)
5151
}
5252
catch (DbException ex)
5353
{
54-
return HealthCheckResult.Failed(exception: ex);
54+
return new HealthCheckResult(status: context.Registration.FailureStatus, exception: ex);
5555
}
5656
}
5757

58-
return HealthCheckResult.Passed();
58+
return HealthCheckResult.Healthy();
5959
}
6060
}
6161
}

samples/HealthChecksSample/GCInfoHealthCheck.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,15 @@ public GCInfoHealthCheck(IOptionsMonitor<GCInfoOptions> options)
6565
{ "Gen2Collections", GC.CollectionCount(2) },
6666
};
6767

68-
// Report failure if the allocated memory is >= the threshold. Negated because true == success
69-
var result = !(allocated >= options.Threshold);
68+
// Report failure if the allocated memory is >= the threshold.
69+
//
70+
// Using context.Registration.FailureStatus means that the application developer can configure
71+
// how they want failures to appear.
72+
var result = allocated >= options.Threshold ? context.Registration.FailureStatus : HealthStatus.Healthy;
7073

7174
return Task.FromResult(new HealthCheckResult(
7275
result,
7376
description: "reports degraded status if allocated bytes >= 1gb",
74-
exception: null,
7577
data: data));
7678
}
7779
}

samples/HealthChecksSample/SlowDependencyHealthCheck.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ public SlowDependencyHealthCheck()
2121
{
2222
if (_task.IsCompleted)
2323
{
24-
return Task.FromResult(HealthCheckResult.Passed("Dependency is ready"));
24+
return Task.FromResult(HealthCheckResult.Healthy("Dependency is ready"));
2525
}
2626

27-
return Task.FromResult(HealthCheckResult.Failed("Dependency is still initializing"));
27+
return Task.FromResult(new HealthCheckResult(
28+
status: context.Registration.FailureStatus,
29+
description: "Dependency is still initializing"));
2830
}
2931
}
3032
}

src/Microsoft.AspNetCore.Diagnostics.HealthChecks/HealthCheckOptions.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,6 @@ public class HealthCheckOptions
3333
{ HealthStatus.Healthy, StatusCodes.Status200OK },
3434
{ HealthStatus.Degraded, StatusCodes.Status200OK },
3535
{ HealthStatus.Unhealthy, StatusCodes.Status503ServiceUnavailable },
36-
37-
// This means that a health check failed, so 500 is appropriate. This is an error.
38-
{ HealthStatus.Failed, StatusCodes.Status500InternalServerError },
3936
};
4037

4138
/// <summary>

src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthCheckResult.cs

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ public struct HealthCheckResult
1414
private static readonly IReadOnlyDictionary<string, object> _emptyReadOnlyDictionary = new Dictionary<string, object>();
1515

1616
/// <summary>
17-
/// Creates a new <see cref="HealthCheckResult"/> with the specified values for <paramref name="result"/>, <paramref name="exception"/>,
18-
/// <paramref name="description"/>, and <paramref name="data"/>.
17+
/// Creates a new <see cref="HealthCheckResult"/> with the specified values for <paramref name="status"/>,
18+
/// <paramref name="exception"/>, <paramref name="description"/>, and <paramref name="data"/>.
1919
/// </summary>
20-
/// <param name="result">A value indicating the pass/fail status of the component that was checked.</param>
20+
/// <param name="status">A value indicating the status of the component that was checked.</param>
2121
/// <param name="description">A human-readable description of the status of the component that was checked.</param>
2222
/// <param name="exception">An <see cref="Exception"/> representing the exception that was thrown when checking for status (if any).</param>
2323
/// <param name="data">Additional key-value pairs describing the health of the component.</param>
24-
public HealthCheckResult(bool result, string description, Exception exception, IReadOnlyDictionary<string, object> data)
24+
public HealthCheckResult(HealthStatus status, string description = null, Exception exception = null, IReadOnlyDictionary<string, object> data = null)
2525
{
26-
Result = result;
26+
Status = status;
2727
Description = description;
2828
Exception = exception;
2929
Data = data ?? _emptyReadOnlyDictionary;
@@ -45,33 +45,44 @@ public HealthCheckResult(bool result, string description, Exception exception, I
4545
public Exception Exception { get; }
4646

4747
/// <summary>
48-
/// Gets a value indicating the pass/fail status of the component that was checked. If <c>true</c>, then the component
49-
/// is considered to have passed health validation. A <c>false</c> value will be mapped to the configured
50-
/// <see cref="HealthStatus"/> by the health check system.
48+
/// Gets a value indicating the status of the component that was checked.
5149
/// </summary>
52-
public bool Result { get; }
50+
public HealthStatus Status { get; }
5351

5452
/// <summary>
55-
/// Creates a <see cref="HealthCheckResult"/> representing a passing component.
53+
/// Creates a <see cref="HealthCheckResult"/> representing a healthy component.
5654
/// </summary>
57-
/// <returns>A <see cref="HealthCheckResult"/> representing a passing component.</returns>
5855
/// <param name="description">A human-readable description of the status of the component that was checked. Optional.</param>
5956
/// <param name="data">Additional key-value pairs describing the health of the component. Optional.</param>
60-
public static HealthCheckResult Passed(string description = null, IReadOnlyDictionary<string, object> data = null)
57+
/// <returns>A <see cref="HealthCheckResult"/> representing a healthy component.</returns>
58+
public static HealthCheckResult Healthy(string description = null, IReadOnlyDictionary<string, object> data = null)
6159
{
62-
return new HealthCheckResult(result: true, description, exception: null, data);
60+
return new HealthCheckResult(status: HealthStatus.Healthy, description, exception: null, data);
61+
}
62+
63+
64+
/// <summary>
65+
/// Creates a <see cref="HealthCheckResult"/> representing a degraded component.
66+
/// </summary>
67+
/// <param name="description">A human-readable description of the status of the component that was checked. Optional.</param>
68+
/// <param name="exception">An <see cref="Exception"/> representing the exception that was thrown when checking for status. Optional.</param>
69+
/// <param name="data">Additional key-value pairs describing the health of the component. Optional.</param>
70+
/// <returns>A <see cref="HealthCheckResult"/> representing a degraged component.</returns>
71+
public static HealthCheckResult Degraded(string description = null, Exception exception = null, IReadOnlyDictionary<string, object> data = null)
72+
{
73+
return new HealthCheckResult(status: HealthStatus.Degraded, description, exception: null, data);
6374
}
6475

6576
/// <summary>
66-
/// Creates a <see cref="HealthCheckResult"/> representing an failing component.
77+
/// Creates a <see cref="HealthCheckResult"/> representing an unhealthy component.
6778
/// </summary>
6879
/// <param name="description">A human-readable description of the status of the component that was checked. Optional.</param>
6980
/// <param name="exception">An <see cref="Exception"/> representing the exception that was thrown when checking for status. Optional.</param>
7081
/// <param name="data">Additional key-value pairs describing the health of the component. Optional.</param>
71-
/// <returns>A <see cref="HealthCheckResult"/> representing an failing component.</returns>
72-
public static HealthCheckResult Failed(string description = null, Exception exception = null, IReadOnlyDictionary<string, object> data = null)
82+
/// <returns>A <see cref="HealthCheckResult"/> representing an unhealthy component.</returns>
83+
public static HealthCheckResult Unhealthy(string description = null, Exception exception = null, IReadOnlyDictionary<string, object> data = null)
7384
{
74-
return new HealthCheckResult(result: false, description, exception, data);
85+
return new HealthCheckResult(status: HealthStatus.Unhealthy, description, exception, data);
7586
}
7687
}
7788
}

src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthReport.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ private HealthStatus CalculateAggregateStatus(IEnumerable<HealthReportEntry> ent
5454
currentValue = entry.Status;
5555
}
5656

57-
if (currentValue == HealthStatus.Failed)
57+
if (currentValue == HealthStatus.Unhealthy)
5858
{
5959
// Game over, man! Game over!
6060
// (We hit the worst possible status, so there's no need to keep iterating)

src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthReportEntry.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ public HealthReportEntry(HealthStatus status, string description, TimeSpan durat
5252
public Exception Exception { get; }
5353

5454
/// <summary>
55-
/// Gets the health status of the component that was checked. The <see cref="Status"/> is based on the pass/fail value of
56-
/// <see cref="HealthCheckResult.Passed"/> and the configured value of <see cref="HealthCheckRegistration.FailureStatus"/>.
55+
/// Gets the health status of the component that was checked.
5756
/// </summary>
5857
public HealthStatus Status { get; }
5958
}

src/Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions/HealthStatus.cs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
88
/// </summary>
99
/// <remarks>
1010
/// <para>
11-
/// A health status is derived the pass/fail result of an <see cref="IHealthCheck"/> (<see cref="HealthCheckResult.Result"/>)
12-
/// and the corresponding value of <see cref="HealthCheckRegistration.FailureStatus"/>.
13-
/// </para>
14-
/// <para>
1511
/// A status of <see cref="Unhealthy"/> should be considered the default value for a failing health check. Application
1612
/// developers may configure a health check to report a different status as desired.
1713
/// </para>
@@ -23,23 +19,19 @@ namespace Microsoft.Extensions.Diagnostics.HealthChecks
2319
public enum HealthStatus
2420
{
2521
/// <summary>
26-
/// Indicates that an unexpected exception was thrown when running the health check.
27-
/// </summary>
28-
Failed = 0,
29-
30-
/// <summary>
31-
/// Indicates that the health check determined that the component was unhealthy.
22+
/// Indicates that the health check determined that the component was unhealthy, or an unhandled
23+
/// exception was thrown while executing the health check.
3224
/// </summary>
33-
Unhealthy = 1,
25+
Unhealthy = 0,
3426

3527
/// <summary>
3628
/// Indicates that the health check determined that the component was in a degraded state.
3729
/// </summary>
38-
Degraded = 2,
30+
Degraded = 1,
3931

4032
/// <summary>
4133
/// Indicates that the health check determined that the component was healthy.
4234
/// </summary>
43-
Healthy = 3,
35+
Healthy = 2,
4436
}
4537
}

src/Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore/DbContextHealthCheck.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context
4747

4848
if (await testQuery(_dbContext, cancellationToken))
4949
{
50-
return HealthCheckResult.Passed();
50+
return HealthCheckResult.Healthy();
5151
}
5252

53-
return HealthCheckResult.Failed();
53+
return HealthCheckResult.Unhealthy();
5454
}
5555
}
5656
}

src/Microsoft.Extensions.Diagnostics.HealthChecks/DefaultHealthCheckService.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public override async Task<HealthReport> CheckHealthAsync(
7676
var duration = stopwatch.GetElapsedTime();
7777

7878
entry = new HealthReportEntry(
79-
status: result.Result ? HealthStatus.Healthy : registration.FailureStatus,
79+
status: result.Status,
8080
description: result.Description,
8181
duration: duration,
8282
exception: result.Exception,
@@ -91,7 +91,7 @@ public override async Task<HealthReport> CheckHealthAsync(
9191
{
9292
var duration = stopwatch.GetElapsedTime();
9393
entry = new HealthReportEntry(
94-
status: HealthStatus.Failed,
94+
status: HealthStatus.Unhealthy,
9595
description: ex.Message,
9696
duration: duration,
9797
exception: ex,
@@ -212,10 +212,6 @@ public static void HealthCheckEnd(ILogger logger, HealthCheckRegistration regist
212212
case HealthStatus.Unhealthy:
213213
_healthCheckEndUnhealthy(logger, registration.Name, duration.TotalMilliseconds, entry.Status, entry.Description, null);
214214
break;
215-
216-
case HealthStatus.Failed:
217-
_healthCheckEndFailed(logger, registration.Name, duration.TotalMilliseconds, entry.Status, entry.Description, null);
218-
break;
219215
}
220216
}
221217

0 commit comments

Comments
 (0)