2
2
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3
3
4
4
using System ;
5
+ using System . Collections ;
5
6
using System . Collections . Generic ;
6
7
using System . Linq ;
8
+ using System . Text ;
7
9
using System . Threading ;
8
10
using System . Threading . Tasks ;
9
11
using Microsoft . Extensions . DependencyInjection ;
@@ -31,7 +33,7 @@ public DefaultHealthCheckService(
31
33
// We're specifically going out of our way to do this at startup time. We want to make sure you
32
34
// get any kind of health-check related error as early as possible. Waiting until someone
33
35
// actually tries to **run** health checks would be real baaaaad.
34
- ValidateRegistrations ( _options . Value . Registrations ) ;
36
+ ValidateRegistrations ( _options . Value . Registrations ) ;
35
37
}
36
38
public override async Task < HealthReport > CheckHealthAsync (
37
39
Func < HealthCheckRegistration , bool > predicate ,
@@ -44,6 +46,9 @@ public override async Task<HealthReport> CheckHealthAsync(
44
46
var context = new HealthCheckContext ( ) ;
45
47
var entries = new Dictionary < string , HealthReportEntry > ( StringComparer . OrdinalIgnoreCase ) ;
46
48
49
+ var totalTime = ValueStopwatch . StartNew ( ) ;
50
+ Log . HealthCheckProcessingBegin ( _logger ) ;
51
+
47
52
foreach ( var registration in registrations )
48
53
{
49
54
if ( predicate != null && ! predicate ( registration ) )
@@ -63,7 +68,7 @@ public override async Task<HealthReport> CheckHealthAsync(
63
68
context . Registration = registration ;
64
69
65
70
Log . HealthCheckBegin ( _logger , registration ) ;
66
-
71
+
67
72
HealthReportEntry entry ;
68
73
try
69
74
{
@@ -76,6 +81,7 @@ public override async Task<HealthReport> CheckHealthAsync(
76
81
result . Data ) ;
77
82
78
83
Log . HealthCheckEnd ( _logger , registration , entry , stopwatch . GetElapsedTime ( ) ) ;
84
+ Log . HealthCheckData ( _logger , registration , entry ) ;
79
85
}
80
86
81
87
// Allow cancellation to propagate.
@@ -89,7 +95,9 @@ public override async Task<HealthReport> CheckHealthAsync(
89
95
}
90
96
}
91
97
92
- return new HealthReport ( entries ) ;
98
+ var report = new HealthReport ( entries ) ;
99
+ Log . HealthCheckProcessingEnd ( _logger , report . Status , totalTime . GetElapsedTime ( ) ) ;
100
+ return report ;
93
101
}
94
102
}
95
103
@@ -112,40 +120,144 @@ private static class Log
112
120
{
113
121
public static class EventIds
114
122
{
115
- public static readonly EventId HealthCheckBegin = new EventId ( 100 , "HealthCheckBegin" ) ;
116
- public static readonly EventId HealthCheckEnd = new EventId ( 101 , "HealthCheckEnd" ) ;
117
- public static readonly EventId HealthCheckError = new EventId ( 102 , "HealthCheckError" ) ;
123
+ public static readonly EventId HealthCheckProcessingBegin = new EventId ( 100 , "HealthCheckProcessingBegin" ) ;
124
+ public static readonly EventId HealthCheckProcessingEnd = new EventId ( 101 , "HealthCheckProcessingEnd" ) ;
125
+
126
+ public static readonly EventId HealthCheckBegin = new EventId ( 102 , "HealthCheckBegin" ) ;
127
+ public static readonly EventId HealthCheckEnd = new EventId ( 103 , "HealthCheckEnd" ) ;
128
+ public static readonly EventId HealthCheckError = new EventId ( 104 , "HealthCheckError" ) ;
129
+ public static readonly EventId HealthCheckData = new EventId ( 105 , "HealthCheckData" ) ;
118
130
}
119
131
132
+ private static readonly Action < ILogger , Exception > _healthCheckProcessingBegin = LoggerMessage . Define (
133
+ LogLevel . Debug ,
134
+ EventIds . HealthCheckProcessingBegin ,
135
+ "Running health checks" ) ;
136
+
137
+ private static readonly Action < ILogger , double , HealthStatus , Exception > _healthCheckProcessingEnd = LoggerMessage . Define < double , HealthStatus > (
138
+ LogLevel . Debug ,
139
+ EventIds . HealthCheckProcessingEnd ,
140
+ "Health check processing completed after {ElapsedMilliseconds}ms with combined status {HealthStatus}" ) ;
141
+
120
142
private static readonly Action < ILogger , string , Exception > _healthCheckBegin = LoggerMessage . Define < string > (
121
143
LogLevel . Debug ,
122
144
EventIds . HealthCheckBegin ,
123
145
"Running health check {HealthCheckName}" ) ;
124
146
125
- private static readonly Action < ILogger , string , double , HealthStatus , Exception > _healthCheckEnd = LoggerMessage . Define < string , double , HealthStatus > (
147
+ private static readonly Action < ILogger , string , double , HealthStatus , string , Exception > _healthCheckEnd = LoggerMessage . Define < string , double , HealthStatus , string > (
126
148
LogLevel . Debug ,
127
149
EventIds . HealthCheckEnd ,
128
- "Health check {HealthCheckName} completed after {ElapsedMilliseconds}ms with status {HealthCheckStatus} " ) ;
150
+ "Health check {HealthCheckName} completed after {ElapsedMilliseconds}ms with status {HealthStatus} and '{HealthCheckDescription}' " ) ;
129
151
130
152
private static readonly Action < ILogger , string , double , Exception > _healthCheckError = LoggerMessage . Define < string , double > (
131
153
LogLevel . Error ,
132
154
EventIds . HealthCheckError ,
133
155
"Health check {HealthCheckName} threw an unhandled exception after {ElapsedMilliseconds}ms" ) ;
134
156
157
+ public static void HealthCheckProcessingBegin ( ILogger logger )
158
+ {
159
+ _healthCheckProcessingBegin ( logger , null ) ;
160
+ }
161
+
162
+ public static void HealthCheckProcessingEnd ( ILogger logger , HealthStatus status , TimeSpan duration )
163
+ {
164
+ _healthCheckProcessingEnd ( logger , duration . TotalMilliseconds , status , null ) ;
165
+ }
166
+
135
167
public static void HealthCheckBegin ( ILogger logger , HealthCheckRegistration registration )
136
168
{
137
169
_healthCheckBegin ( logger , registration . Name , null ) ;
138
170
}
139
171
140
172
public static void HealthCheckEnd ( ILogger logger , HealthCheckRegistration registration , HealthReportEntry entry , TimeSpan duration )
141
173
{
142
- _healthCheckEnd ( logger , registration . Name , duration . TotalMilliseconds , entry . Status , null ) ;
174
+ _healthCheckEnd ( logger , registration . Name , duration . TotalMilliseconds , entry . Status , entry . Description , null ) ;
143
175
}
144
176
145
177
public static void HealthCheckError ( ILogger logger , HealthCheckRegistration registration , Exception exception , TimeSpan duration )
146
178
{
147
179
_healthCheckError ( logger , registration . Name , duration . TotalMilliseconds , exception ) ;
148
180
}
181
+
182
+ public static void HealthCheckData ( ILogger logger , HealthCheckRegistration registration , HealthReportEntry entry )
183
+ {
184
+ if ( entry . Data . Count > 0 && logger . IsEnabled ( LogLevel . Debug ) )
185
+ {
186
+ logger . Log (
187
+ LogLevel . Debug ,
188
+ EventIds . HealthCheckData ,
189
+ new HealthCheckDataLogValue ( registration . Name , entry . Data ) ,
190
+ null ,
191
+ ( state , ex ) => state . ToString ( ) ) ;
192
+ }
193
+ }
194
+ }
195
+
196
+ internal class HealthCheckDataLogValue : IReadOnlyList < KeyValuePair < string , object > >
197
+ {
198
+ private readonly string _name ;
199
+ private readonly List < KeyValuePair < string , object > > _values ;
200
+
201
+ private string _formatted ;
202
+
203
+ public HealthCheckDataLogValue ( string name , IReadOnlyDictionary < string , object > values )
204
+ {
205
+ _name = name ;
206
+ _values = values . ToList ( ) ;
207
+
208
+ // We add the name as a kvp so that you can filter by health check name in the logs.
209
+ // This is the same parameter name used in the other logs.
210
+ _values . Add ( new KeyValuePair < string , object > ( "HealthCheckName" , name ) ) ;
211
+ }
212
+
213
+ public KeyValuePair < string , object > this [ int index ]
214
+ {
215
+ get
216
+ {
217
+ if ( index < 0 || index >= Count )
218
+ {
219
+ throw new IndexOutOfRangeException ( nameof ( index ) ) ;
220
+ }
221
+
222
+ return _values [ index ] ;
223
+ }
224
+ }
225
+
226
+ public int Count => _values . Count ;
227
+
228
+ public IEnumerator < KeyValuePair < string , object > > GetEnumerator ( )
229
+ {
230
+ return _values . GetEnumerator ( ) ;
231
+ }
232
+
233
+ IEnumerator IEnumerable . GetEnumerator ( )
234
+ {
235
+ return _values . GetEnumerator ( ) ;
236
+ }
237
+
238
+ public override string ToString ( )
239
+ {
240
+ if ( _formatted == null )
241
+ {
242
+ var builder = new StringBuilder ( ) ;
243
+ builder . AppendLine ( $ "Health check data for { _name } :") ;
244
+
245
+ var values = _values ;
246
+ for ( var i = 0 ; i < values . Count ; i ++ )
247
+ {
248
+ var kvp = values [ i ] ;
249
+ builder . Append ( " " ) ;
250
+ builder . Append ( kvp . Key ) ;
251
+ builder . Append ( ": " ) ;
252
+
253
+ builder . AppendLine ( kvp . Value ? . ToString ( ) ) ;
254
+ }
255
+
256
+ _formatted = builder . ToString ( ) ;
257
+ }
258
+
259
+ return _formatted ;
260
+ }
149
261
}
150
262
}
151
263
}
0 commit comments