public async Task <HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default) { var itemDictionary = new Dictionary <string, object>(); HealthCheckResult retval; try { foreach (var trigger in _timerTriggers) { if (trigger.IsTimerDisabled) { itemDictionary.Add(trigger.TimerTriggerFriendlyName, new TimerTriggerHealthResult { LastCompletionTime = DateTime.MinValue, LastExpectedCompletionTime = DateTime.MinValue, IsTimerDisabled = true }); } else { //get last execution completion time var blobPath = TimerHealthCheckHelper.GetBlobPath(trigger.TimerFullTypeName, _helperOptions); var lastTimeString = await _blobStorage.DownloadFileAsTextAsync(blobPath).ConfigureAwait(false); TimerHealthCheckStatus status = JsonSerializer.Deserialize <TimerHealthCheckStatus>(lastTimeString); var lastExpectedTime = GetLastScheduledOccurrence(trigger.ScheduleExpression, _dateTimeService.CurrentDateTimeOffset.DateTime); itemDictionary.Add(trigger.TimerTriggerFriendlyName, new TimerTriggerHealthResult { LastCompletionTime = status.LastCheckpoint, LastExpectedCompletionTime = new DateTimeOffset(lastExpectedTime, status.LastCheckpoint.Offset) }); } } var badTimers = itemDictionary.Where(x => !((TimerTriggerHealthResult)x.Value).IsTimerDisabled && ((((TimerTriggerHealthResult)x.Value).LastCompletionTime) < ((TimerTriggerHealthResult)x.Value).LastExpectedCompletionTime) && _dateTimeService.CurrentDateTimeOffset > ((TimerTriggerHealthResult)x.Value).LastExpectedCompletionTime + _options.ToleranceTimeSpan).Select(x => new { TimerName = x.Key, Result = x.Value as TimerTriggerHealthResult }).ToList(); if (badTimers.Any()) { var sb = new StringBuilder(); foreach (var item in badTimers) { sb.Append($"Timer {item.TimerName} did not fire on time - LastCompletedTime = {item.Result!.LastCompletionTime} Last Expected Time = {item.Result!.LastExpectedCompletionTime}"); sb.Append("\n"); } retval = new HealthCheckResult(HealthStatus.Unhealthy, sb.ToString(), null, itemDictionary); } else { retval = new HealthCheckResult(HealthStatus.Healthy, null, null, itemDictionary); } } catch (Exception ex) { retval = new HealthCheckResult(HealthStatus.Unhealthy, "", ex, itemDictionary); } return(retval); }
/// <summary> /// Call this method at the end of your timer so it can checkpoint successful completion /// </summary> /// <typeparam name="T">The Type of your timertrigger</typeparam> /// <returns></returns> public async Task CheckpointMethodAsync <T>() { var blobContainer = _blobStorageFactory.GetBlobStorage(_options.AzureWebJobsStorageConnectionString, "azure-webjobs-hosts"); var status = new TimerHealthCheckStatus { LastCheckpoint = _dateTimeService.CurrentDateTimeOffset }; if (_options.IsProductionSlot) { var blobPath = GetBlobPath(typeof(T).FullName, _options); await blobContainer.StoreBlobAsTextAsync(blobPath, JsonSerializer.Serialize(status)).ConfigureAwait(false); if (string.IsNullOrWhiteSpace(_options.AzureWebSiteName)) { _logger.LogWarning("It appears you are running locally. Timer will be checkpointed, but if you are not running locally, make sure you include the APPSETTING_WEBSITE_SITE_NAME to make sure this timer's checkpoint is unique to this instance"); } } else { _logger.LogWarning("Timer is not running in a production slot, timer will not be checkpointed"); } }