Ранее я писал о создании Проверки работоспособности для Microsoft Orleans, но ответ в формате JSON был слишком минимальным. В этом посте мы увидим, как украсить этот вывод!
В предыдущем посте мы немного узнали о проверках работоспособности, о том, как их создавать и просматривать их «состояние» с точки зрения Microsoft Orleans. Конечным результатом был ответ из одного слова «Здоровый», «Деградированный» или «Нездоровый»; не очень увлекательная вещь.
В этом посте я хотел бы быстро рассказать, как вы будете не только сообщать об «общем статусе», но и предоставлять подробную информацию об отдельных проверках работоспособности, которые составляют этот всеобъемлющий статус.
(Примечание: у меня действительно была проблема с лейблом hacktoberfest, за которую я получил PR, но в конце концов я хотел пойти немного другим путем, хотя я и сделал интегрируйте его в мастер на некоторое время.)
В Документации по проверке работоспособности есть некоторые подробности о том, как выполнить предварительную проверку работоспособности, но я не большой поклонник ручного написания JSON; вместо этого я выбрал анонимный объект.
Несколько новых вещей, о которых я хочу сообщить из ответа на конечную точку проверки работоспособности GET:
- Имя проверки работоспособности
- Описание проверки работоспособности
- Индивидуальный статус проверки здоровья
- Некоторая дополнительная информация, относящаяся к проверке работоспособности, которая описывает, почему проверка работоспособности возвращает «Ухудшение состояния» или «Неработоспособность».
К счастью, вся эта информация может быть доступна нам через HealthReport
, сгенерированный как часть проверки работоспособности.
Мы собираемся представить новый метод, который записывает настраиваемый ответ для нашей конечной точки проверки работоспособности. При запуске мы хотим предоставить пользовательский ResponseWriter
в MapHealthChecks
:
app.UseEndpoints(endpoints => { endpoints.MapHealthChecks( "/health", new HealthCheckOptions { AllowCachingResponses = false, ResponseWriter = HealthCheckResponseWriter.WriteResponse }) .WithMetadata(new AllowAnonymousAttribute()); endpoints.MapControllers(); });
Где указанный HealthCheckResponseWriter
является новым статическим классом, который мы представим далее.
ResponseWriter
ожидает метод со следующей сигнатурой:
Вы заметили выше, что метод получает HttpContext
, а также HealthReport. Это HealthReport
предоставит нам несколько фрагментов данных, о которых мы можем сообщить, специфичных для каждой отдельной проверки работоспособности.
Что касается нашей фактической реализации модуля записи ответов, то вот оригинал, который был объединен с мастером из PR L-Dogg:
private static Task WriteResponse(HttpContext context, HealthReport result) { context.Response.ContentType = "application/json; charset=utf-8"; var options = new JsonWriterOptions { Indented = true }; using var stream = new MemoryStream(); using (var writer = new Utf8JsonWriter(stream, options)) { writer.WriteStartObject(); writer.WriteString("status", result.Status.ToString()); writer.WriteStartObject("results"); foreach (var entry in result.Entries) { writer.WriteStartObject(entry.Key); writer.WriteString("status", entry.Value.Status.ToString()); writer.WriteString("description", entry.Value.Description); writer.WriteStartObject("data"); foreach (var item in entry.Value.Data) { writer.WritePropertyName(item.Key); JsonSerializer.Serialize( writer, item.Value, item.Value?.GetType() ?? typeof(object)); } writer.WriteEndObject(); writer.WriteEndObject(); } writer.WriteEndObject(); writer.WriteEndObject(); } var json = Encoding.UTF8.GetString(stream.ToArray()); return context.Response.WriteAsync(json); }
Вышеупомянутое определенно работает, но я не очень люблю писать json «вручную» (если это имеет смысл). Я все равно хотел написать об этом еще один пост в блоге, так как у меня уже была ветка (и на самом деле я не ожидал пиара :O), так что вот мое решение:
internal static class HealthCheckResponseWriter { public static Task WriteResponse(HttpContext context, HealthReport healthReport) { context.Response.ContentType = "application/json; charset=utf-8"; var result = JsonConvert.SerializeObject(new { status = healthReport.Status.ToString(), details = healthReport.Entries.Select(e => new { key = e.Key, description = e.Value.Description, status = e.Value.Status.ToString(), data = e.Value.Data }) }, Formatting.Indented); return context.Response.WriteAsync(result); } }
Я считаю, что работа с анонимным объектом будет немного более лаконичной.
В настоящее время мы не генерируем «данные» из проверок работоспособности, которыми мог бы воспользоваться HealthCheckResponseWriter
, так что давайте посмотрим, что мы можем там сделать.
Мое намерение в отношении свойства «данные» анонимного объекта состоит в том, чтобы описать, что заставит конкретную проверку работоспособности вернуть «Ухудшенный» или «Неработоспособный», все, кроме этих двух статусов, можно считать «Здоровым».
Если вы помните, мы уже встроили пороговые значения в проверки работоспособности для представления деградированных и неработоспособных статусов, теперь нам просто нужно предоставить те, которые доступны для отчета о работоспособности.
Взглянем на класс HealthCheckResult
:
вы увидите, что этот метод принимает необязательный IReadOnlyDictionary<string, object> data = null
, который является элементом данных, который мы обязательно вернули из нашего метода WriteResponse
в предыдущем разделе поста.
Мы будем использовать этот IReadonlyDictionary
для предоставления нашей «пороговой» информации для каждого зерна. Я буду помещать эту пороговую информацию как в процессор, так и в память, но в качестве примера вот как будет выглядеть один из них:
[StatelessWorker(1)] public class CpuHealthCheckGrain : Grain, ICpuHealthCheckGrain { private const float UnhealthyThreshold = 90; private const float DegradedThreshold = 70; private readonly ReadOnlyDictionary<string, object> HealthCheckData = new ReadOnlyDictionary<string, object>( new Dictionary<string, object>() { { "Unhealthy Threshold", UnhealthyThreshold}, { "Degraded Threshold", DegradedThreshold} }); private readonly IHostEnvironmentStatistics _hostEnvironmentStatistics; public CpuHealthCheckGrain(IHostEnvironmentStatistics hostEnvironmentStatistics) { _hostEnvironmentStatistics = hostEnvironmentStatistics; } public Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = new CancellationToken()) { if (_hostEnvironmentStatistics.CpuUsage == null) { return Task.FromResult(HealthCheckResult.Unhealthy("Could not determine CPU usage.", data: HealthCheckData)); } if (_hostEnvironmentStatistics.CpuUsage > UnhealthyThreshold) { return Task.FromResult(HealthCheckResult.Unhealthy( $"CPU utilization is unhealthy at {_hostEnvironmentStatistics.CpuUsage:0.00}%.", data: HealthCheckData)); } if (_hostEnvironmentStatistics.CpuUsage > DegradedThreshold) { return Task.FromResult(HealthCheckResult.Degraded( $"CPU utilization is degraded at {_hostEnvironmentStatistics.CpuUsage:0.00}%.", data: HealthCheckData)); } return Task.FromResult(HealthCheckResult.Healthy( $"CPU utilization is healthy at {_hostEnvironmentStatistics.CpuUsage:0.00}%.", data: HealthCheckData)); } }
Вы должны заметить, что в приведенном выше примере мы ввели ReadOnlyDictionary
с пороговыми значениями для деградировавших и неработоспособных, а затем передали это ReadOnlyDictionary
параметру data
статического метода в HealthCheckResult
.
Осталось только протестировать! Возможно, вы видели изображение обложки, которое содержало спойлеры, но просто чтобы подвести итог, вот как это выглядит при нажатии на конечную точку «/health» после наших изменений:
использованная литература
- Код в начале поста
- Код в конце поста
- PR с изменениями из этого поста
- Microsoft Orleans — проверка работоспособности
- Другие мои посты в Орлеане
- Документация о состоянии здоровья
- Класс HealthReport
Первоначально опубликовано на https://blog.kritner.com 4 декабря 2020 г.