using System; using System.Collections.Generic; using System.Linq; using System.Text; using HeyRed.MarkdownSharp; using Umbraco.Core.Composing; using Umbraco.Core.Configuration.HealthChecks; using Umbraco.Core.Logging; namespace Umbraco.Web.HealthCheck { public class HealthCheckResults { private readonly Dictionary> _results; public readonly bool AllChecksSuccessful; private ILogger Logger => Current.Logger; // fixme inject public HealthCheckResults(IEnumerable checks) { _results = checks.ToDictionary( t => t.Name, t => { try { return t.GetStatus(); } catch (Exception ex) { Logger.Error($"Error running scheduled health check: {t.Name}", ex); var message = $"Health check failed with exception: {ex.Message}. See logs for details."; return new List { new HealthCheckStatus(message) { ResultType = StatusResultType.Error } }; } }); // find out if all checks pass or not AllChecksSuccessful = true; foreach (var result in _results) { var checkIsSuccess = result.Value.All(x => x.ResultType == StatusResultType.Success || x.ResultType == StatusResultType.Info || x.ResultType == StatusResultType.Warning); if (checkIsSuccess == false) { AllChecksSuccessful = false; break; } } } public void LogResults() { Logger.Info("Scheduled health check results:"); foreach (var result in _results) { var checkName = result.Key; var checkResults = result.Value; var checkIsSuccess = result.Value.All(x => x.ResultType == StatusResultType.Success); if (checkIsSuccess) { Logger.Info(() => $" Checks for '{checkName}' all completed succesfully."); } else { Logger.Warn($" Checks for '{checkName}' completed with errors."); } foreach (var checkResult in checkResults) { Logger.Info(() => $" Result: {checkResult.ResultType}, Message: '{checkResult.Message}'"); } } } public string ResultsAsMarkDown(HealthCheckNotificationVerbosity verbosity) { var newItem = "- "; var sb = new StringBuilder(); foreach (var result in _results) { var checkName = result.Key; var checkResults = result.Value; var checkIsSuccess = result.Value.All(x => x.ResultType == StatusResultType.Success); // add a new line if not the first check if (result.Equals(_results.First()) == false) { sb.Append(Environment.NewLine); } if (checkIsSuccess) { sb.AppendFormat("{0}Checks for '{1}' all completed succesfully.{2}", newItem, checkName, Environment.NewLine); } else { sb.AppendFormat("{0}Checks for '{1}' completed with errors.{2}", newItem, checkName, Environment.NewLine); } foreach (var checkResult in checkResults) { sb.AppendFormat("\t{0}Result: '{1}'", newItem, checkResult.ResultType); // With summary logging, only record details of warnings or errors if (checkResult.ResultType != StatusResultType.Success || verbosity == HealthCheckNotificationVerbosity.Detailed) { sb.AppendFormat(", Message: '{0}'", SimpleHtmlToMarkDown(checkResult.Message)); } sb.AppendLine(Environment.NewLine); } } return sb.ToString(); } public string ResultsAsHtml(HealthCheckNotificationVerbosity verbosity) { var mark = new Markdown(); var html = mark.Transform(ResultsAsMarkDown(verbosity)); html = ApplyHtmlHighlighting(html); return html; } private string ApplyHtmlHighlighting(string html) { html = ApplyHtmlHighlightingForStatus(html, StatusResultType.Success, "5cb85c"); html = ApplyHtmlHighlightingForStatus(html, StatusResultType.Warning, "f0ad4e"); return ApplyHtmlHighlightingForStatus(html, StatusResultType.Error, "d9534f"); } private string ApplyHtmlHighlightingForStatus(string html, StatusResultType status, string color) { return html .Replace("Result: '" + status + "'", "Result: " + status + ""); } private string SimpleHtmlToMarkDown(string html) { return html.Replace("", "**") .Replace("", "**") .Replace("", "*") .Replace("", "*"); } public Dictionary> GetResultsForStatus(StatusResultType resultType) { switch (resultType) { case StatusResultType.Success: // a check is considered a success status if all checks are successful or info var successResults = _results.Where(x => x.Value.Any(y => y.ResultType == StatusResultType.Success) && x.Value.All(y => y.ResultType == StatusResultType.Success || y.ResultType == StatusResultType.Info)); return successResults.ToDictionary(x => x.Key, x => x.Value); case StatusResultType.Warning: // a check is considered warn status if one check is warn and all others are success or info var warnResults = _results.Where(x => x.Value.Any(y => y.ResultType == StatusResultType.Warning) && x.Value.All(y => y.ResultType == StatusResultType.Warning || y.ResultType == StatusResultType.Success || y.ResultType == StatusResultType.Info)); return warnResults.ToDictionary(x => x.Key, x => x.Value); case StatusResultType.Error: // a check is considered error status if any check is error var errorResults = _results.Where(x => x.Value.Any(y => y.ResultType == StatusResultType.Error)); return errorResults.ToDictionary(x => x.Key, x => x.Value); case StatusResultType.Info: // a check is considered info status if all checks are info var infoResults = _results.Where(x => x.Value.All(y => y.ResultType == StatusResultType.Info)); return infoResults.ToDictionary(x => x.Key, x => x.Value); } return null; } } }