Skip to content

Instantly share code, notes, and snippets.

@marcduiker
Last active March 26, 2021 08:36
Show Gist options
  • Save marcduiker/f15143b79efea6a74fdf17385bd7a0d7 to your computer and use it in GitHub Desktop.
Save marcduiker/f15143b79efea6a74fdf17385bd7a0d7 to your computer and use it in GitHub Desktop.
Custom Application Insights Availability Metrics for a Function App
using System;
using System.Threading.Tasks;
using Azure.HealthCheck;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Extensions.Logging;
namespace FunctionApp.Application.HealthCheck
{
public class FunctionAppHealthCheck : HealthCheckBase
{
private readonly IHealthCheckSettings _healthCheckSettings;
public FunctionAppHealthCheck(IHealthCheckSettings healthCheckSettings,
TelemetryConfiguration telemetryConfiguration,
ILogger<FunctionAppHealthCheck> logger) : base(telemetryConfiguration, logger)
{
_healthCheckSettings = healthCheckSettings;
AddAvailabilityChecks();
SetHealthCheckName(nameof(FunctionAppHealthCheck));
}
protected override void AddAvailabilityChecks()
{
var cosmosDBAction = new Func<Task>(() => CheckCosmosDBAsync());
var blobStorageAction = new Func<Task>(() => CheckJsonBlobStorageAsync());
AvailabilityChecks.Add(cosmosDBAction);
AvailabilityChecks.Add(blobStorageAction);
}
private async Task CheckCosmosDBAsync()
{
await _healthCheckSettings.DataCosmosContainer.ReadContainerAsync();
LogHealthInformation("CosmosDB data collection is healthy.");
}
private Task CheckJsonBlobStorageAsync()
{
if (!_healthCheckSettings.JsonBlobContainerClient.Exists())
{
throw new BlobContainerNotFoundException($"BlobContainer named { _healthCheckSettings.JsonBlobContainerClient.Name } is not available.");
}
LogHealthInformation("BlobStorage JSON container is healthy.");
return Task.CompletedTask;
}
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;
using Microsoft.ApplicationInsights.Extensibility;
using Microsoft.Extensions.Logging;
namespace Azure.HealthCheck
{
public abstract class HealthCheckBase : IHealthCheck
{
private const string HealthCheckLogFormat = "{ApplicationHealthCheck} - {Message}";
private readonly TelemetryClient _telemetryClient;
private readonly ILogger _logger;
private readonly string _location;
private string _healthCheckClass;
protected HealthCheckBase(
TelemetryConfiguration telemetryConfiguration,
ILogger<HealthCheckBase> logger)
{
_telemetryClient = new TelemetryClient(telemetryConfiguration);
_logger = logger;
// REGION_NAME is a default environment variable that comes with App Services.
_location = Environment.GetEnvironmentVariable("REGION_NAME") ?? "local";
AvailabilityChecks = new List<Func<Task>>();
}
/// <summary>
/// Gets or sets the functions which check the availability of the dependencies of the application.
/// </summary>
protected List<Func<Task>> AvailabilityChecks { get; private set; }
/// <summary>
/// Set the name of the HealthCheck which will be visible in Application Insights.
/// </summary>
protected void SetHealthCheckName(string healthCheckClassName)
{
_healthCheckClass = healthCheckClassName;
}
/// <summary>
/// Method to be implemented by the class that inherits from this class to add items to the AvailabilityChecks list.
/// </summary>
protected abstract void AddAvailabilityChecks();
public async Task RunHealthCheckAsync()
{
if (string.IsNullOrEmpty(_healthCheckClass))
{
_healthCheckClass = nameof(HealthCheckBase);
_logger.LogWarning(HealthCheckLogFormat, _healthCheckClass, "HealthCheckClass name is not set.");
}
string operationId = Guid.NewGuid().ToString("N");
var availability = new AvailabilityTelemetry
{
Id = operationId,
Name = _healthCheckClass,
RunLocation = _location,
Success = false
};
var stopwatch = new Stopwatch();
stopwatch.Start();
try
{
await RunAvailabilityChecksAsync();
availability.Success = true;
}
catch (Exception exception)
{
availability.Message = exception.Message;
_logger.LogError(HealthCheckLogFormat, _healthCheckClass, exception.Message);
TrackException(_location, operationId, exception);
}
finally
{
stopwatch.Stop();
availability.Duration = stopwatch.Elapsed;
availability.Timestamp = DateTimeOffset.UtcNow;
_telemetryClient.TrackAvailability(availability);
_telemetryClient.Flush();
}
}
protected void LogHealthInformation(string message)
{
_logger.LogInformation(HealthCheckLogFormat, _healthCheckClass, message);
}
private async Task RunAvailabilityChecksAsync()
{
if (!AvailabilityChecks.Any())
{
_logger.LogWarning(HealthCheckLogFormat, _healthCheckClass, "Availability checks are missing.");
}
foreach (Func<Task> func in AvailabilityChecks)
{
await func.Invoke();
}
}
private void TrackException(string location, string operationId, Exception exception)
{
var exceptionTelemetry = new ExceptionTelemetry(exception);
exceptionTelemetry.Context.Operation.Id = operationId;
exceptionTelemetry.Properties.Add("HealthCheck", _healthCheckClass);
exceptionTelemetry.Properties.Add("Location", location);
_telemetryClient.TrackException(exceptionTelemetry);
}
}
}
using System.Threading.Tasks;
using Azure.HealthCheck;
using Microsoft.Azure.WebJobs;
namespace FunctionApp.Application.HealthCheck
{
public class HealthCheckTimerTrigger
{
private readonly IHealthCheck _healthCheck;
public HealthCheckTimerTrigger(
IHealthCheck healthCheck)
{
_healthCheck = healthCheck;
}
[FunctionName(nameof(HealthCheckTimerTrigger))]
public async Task RunAsync(
[TimerTrigger("%HealthCheckInterval%")] TimerInfo timer)
{
await _healthCheck.RunHealthCheckAsync();
}
}
}
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingExcludedTypes": "Request",
"samplingSettings": {
"isEnabled": false
}
},
"logLevel": {
"FunctionApp.Application.HealthCheck.FunctionAppHealthCheck": "Information"
}
}
}
using System.Threading.Tasks;
namespace Azure.HealthCheck
{
public interface IHealthCheck
{
/// <summary>
/// Starts the health check of the application.
/// </summary>
/// <returns></returns>
Task RunHealthCheckAsync();
}
}
using System;
using Azure.Storage.Blobs;
namespace FunctionApp.Application.HealthCheck
{
public interface IHealthCheckSettings
{
/// <summary>
/// Gets and sets the DataCosmosContainer to check is the data collection exists.
/// </summary>
public ICosmosContainer DataCosmosContainer { get; set; }
/// <summary>
/// Gets and sets the JsonBlobContainerClient to check if the blob container exists.
/// </summary>
BlobContainerClient JsonBlobContainerClient { get; set; }
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment