// Copyright (c) Umbraco. // See LICENSE for more details. using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.Extensions; using Umbraco.Core.Configuration.Models; using Umbraco.Core.HealthChecks; using Umbraco.Core.HealthChecks.NotificationMethods; using Umbraco.Core.Logging; using Umbraco.Core.Scoping; using Umbraco.Core.Sync; namespace Umbraco.Infrastructure.HostedServices { /// /// Hosted service implementation for recurring health check notifications. /// public class HealthCheckNotifier : RecurringHostedServiceBase { private readonly HealthChecksSettings _healthChecksSettings; private readonly HealthCheckCollection _healthChecks; private readonly HealthCheckNotificationMethodCollection _notifications; private readonly IRuntimeState _runtimeState; private readonly IServerRoleAccessor _serverRegistrar; private readonly IMainDom _mainDom; private readonly IScopeProvider _scopeProvider; private readonly ILogger _logger; private readonly IProfilingLogger _profilingLogger; /// /// Initializes a new instance of the class. /// /// The configuration for health check settings. /// The collection of healthchecks. /// The collection of healthcheck notification methods. /// Representation of the state of the Umbraco runtime. /// Provider of server registrations to the distributed cache. /// Representation of the main application domain. /// Provides scopes for database operations. /// The typed logger. /// The profiling logger. /// Parser of crontab expressions. public HealthCheckNotifier( IOptions healthChecksSettings, HealthCheckCollection healthChecks, HealthCheckNotificationMethodCollection notifications, IRuntimeState runtimeState, IServerRoleAccessor serverRegistrar, IMainDom mainDom, IScopeProvider scopeProvider, ILogger logger, IProfilingLogger profilingLogger, ICronTabParser cronTabParser) : base( healthChecksSettings.Value.Notification.Period, healthChecksSettings.Value.GetNotificationDelay(cronTabParser, DateTime.Now, DefaultDelay)) { _healthChecksSettings = healthChecksSettings.Value; _healthChecks = healthChecks; _notifications = notifications; _runtimeState = runtimeState; _serverRegistrar = serverRegistrar; _mainDom = mainDom; _scopeProvider = scopeProvider; _logger = logger; _profilingLogger = profilingLogger; } internal override async Task PerformExecuteAsync(object state) { if (_healthChecksSettings.Notification.Enabled == false) { return; } if (_runtimeState.Level != RuntimeLevel.Run) { return; } switch (_serverRegistrar.CurrentServerRole) { case ServerRole.Replica: _logger.LogDebug("Does not run on replica servers."); return; case ServerRole.Unknown: _logger.LogDebug("Does not run on servers with unknown role."); return; } // Ensure we do not run if not main domain, but do NOT lock it if (_mainDom.IsMainDom == false) { _logger.LogDebug("Does not run if not MainDom."); return; } // Ensure we use an explicit scope since we are running on a background thread and plugin health // checks can be making service/database calls so we want to ensure the CallContext/Ambient scope // isn't used since that can be problematic. using (IScope scope = _scopeProvider.CreateScope()) using (_profilingLogger.DebugDuration("Health checks executing", "Health checks complete")) { // Don't notify for any checks that are disabled, nor for any disabled just for notifications. Guid[] disabledCheckIds = _healthChecksSettings.Notification.DisabledChecks .Select(x => x.Id) .Union(_healthChecksSettings.DisabledChecks .Select(x => x.Id)) .Distinct() .ToArray(); IEnumerable checks = _healthChecks .Where(x => disabledCheckIds.Contains(x.Id) == false); var results = await HealthCheckResults.Create(checks); results.LogResults(); // Send using registered notification methods that are enabled. foreach (IHealthCheckNotificationMethod notificationMethod in _notifications.Where(x => x.Enabled)) { await notificationMethod.SendAsync(results); } } } } }