Skip to content

Commit

Permalink
Added sample for health checks (#585)
Browse files Browse the repository at this point in the history
  • Loading branch information
mburumaxwell authored Jan 23, 2024
1 parent 8abe976 commit d2292b9
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ A number of the documents below are still a work in progress and will be added a
- [Using Amazon SQS and SNS](./samples/AmazonSqsAndSns)
- [Receive events from Azure IoT Hub](./samples/AzureIotHub)
- [Using Azure Managed Identity instead of Connection Strings](./samples/AzureManagedIdentity)
- [Health Checks for Azure Service Bus with Managed Identity](./samples/HealthCheck)

## Issues & Comments

Expand Down
7 changes: 7 additions & 0 deletions Tingle.EventBus.sln
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomEventConfigurator", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomEventSerializer", "samples\CustomEventSerializer\CustomEventSerializer.csproj", "{2C55FABC-8C94-4104-BE05-42A477D2AD9E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HealthCheck", "samples\HealthCheck\HealthCheck.csproj", "{BF5FE712-123E-4826-A6AC-C87C86D14724}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InMemoryBackgroundProcessing", "samples\InMemoryBackgroundProcessing\InMemoryBackgroundProcessing.csproj", "{C9293277-90BA-4F1A-BEA7-85CE41103B8D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MultiEventsConsumer", "samples\MultiEventsConsumer\MultiEventsConsumer.csproj", "{E9D6F25A-0586-4409-A3EB-A72B6E89A71C}"
Expand Down Expand Up @@ -176,6 +178,10 @@ Global
{2C55FABC-8C94-4104-BE05-42A477D2AD9E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C55FABC-8C94-4104-BE05-42A477D2AD9E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C55FABC-8C94-4104-BE05-42A477D2AD9E}.Release|Any CPU.Build.0 = Release|Any CPU
{BF5FE712-123E-4826-A6AC-C87C86D14724}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BF5FE712-123E-4826-A6AC-C87C86D14724}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BF5FE712-123E-4826-A6AC-C87C86D14724}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BF5FE712-123E-4826-A6AC-C87C86D14724}.Release|Any CPU.Build.0 = Release|Any CPU
{C9293277-90BA-4F1A-BEA7-85CE41103B8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C9293277-90BA-4F1A-BEA7-85CE41103B8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9293277-90BA-4F1A-BEA7-85CE41103B8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -232,6 +238,7 @@ Global
{8E115759-87CC-4F45-9679-A9EBBD59992B} = {62F603F3-FF36-4E36-AC0C-08D1883525BE}
{8C0EE13F-701F-45EF-BADF-6B7A22AA6785} = {62F603F3-FF36-4E36-AC0C-08D1883525BE}
{2C55FABC-8C94-4104-BE05-42A477D2AD9E} = {62F603F3-FF36-4E36-AC0C-08D1883525BE}
{BF5FE712-123E-4826-A6AC-C87C86D14724} = {62F603F3-FF36-4E36-AC0C-08D1883525BE}
{C9293277-90BA-4F1A-BEA7-85CE41103B8D} = {62F603F3-FF36-4E36-AC0C-08D1883525BE}
{E9D6F25A-0586-4409-A3EB-A72B6E89A71C} = {62F603F3-FF36-4E36-AC0C-08D1883525BE}
{B2043778-DDC9-4396-801C-442EFF0C7E73} = {62F603F3-FF36-4E36-AC0C-08D1883525BE}
Expand Down
38 changes: 38 additions & 0 deletions samples/HealthCheck/AzureServiceBusHealthCheck.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using Azure.Messaging.ServiceBus.Administration;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
using Tingle.EventBus.Transports.Azure.ServiceBus;

namespace HealthCheck;

internal class AzureServiceBusHealthCheck(IOptionsMonitor<AzureServiceBusTransportOptions> optionsMonitor) : IHealthCheck
{
private const string QueueName = "health-check";

private readonly AzureServiceBusTransportOptions options = optionsMonitor?.Get(AzureServiceBusDefaults.Name) ?? throw new ArgumentNullException(nameof(optionsMonitor));

public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
var cred = (AzureServiceBusTransportCredentials)options.Credentials.CurrentValue;
var managementClient = new ServiceBusAdministrationClient(
fullyQualifiedNamespace: cred.FullyQualifiedNamespace,
credential: cred.TokenCredential);

_ = await managementClient.GetQueueRuntimePropertiesAsync(QueueName, cancellationToken);

return HealthCheckResult.Healthy();
}
catch (TaskCanceledException) when (cancellationToken.IsCancellationRequested)
{
// ignore long running calls
return HealthCheckResult.Healthy();
}
catch (Exception ex)
{
return new HealthCheckResult(context.Registration.FailureStatus, exception: ex);
}
}
}

12 changes: 12 additions & 0 deletions samples/HealthCheck/HealthCheck.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">

<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.10.4" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Tingle.EventBus.Transports.Azure.ServiceBus\Tingle.EventBus.Transports.Azure.ServiceBus.csproj" />
</ItemGroup>

</Project>
33 changes: 33 additions & 0 deletions samples/HealthCheck/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Azure.Identity;
using HealthCheck;
using Tingle.EventBus.Configuration;

var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
var configuration = hostContext.Configuration;

services.AddEventBus(builder =>
{
builder.AddConsumer<VehicleTelemetryEventsConsumer>();

var credential = new DefaultAzureCredential();

// Transport specific configuration
builder.AddAzureServiceBusTransport(options =>
{
options.Credentials = new AzureServiceBusTransportCredentials
{
TokenCredential = credential,
FullyQualifiedNamespace = "{your_namespace}.servicebus.windows.net"
};
options.DefaultEntityKind = EntityKind.Queue; // required if using the basic SKU (does not support topics)
});

builder.Services.AddHealthChecks()
.AddCheck<AzureServiceBusHealthCheck>(name: "servicebus", tags: ["eventbus"]);
});
})
.Build();

await host.RunAsync();
11 changes: 11 additions & 0 deletions samples/HealthCheck/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"profiles": {
"HealthCheck": {
"commandName": "Project",
"dotnetRunMessages": true,
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development"
}
}
}
}
9 changes: 9 additions & 0 deletions samples/HealthCheck/VehicleDoorOpenedEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace HealthCheck;

public class VehicleDoorOpenedEvent
{
public string? VehicleId { get; set; }
public VehicleDoorKind Kind { get; set; }
public DateTimeOffset? Opened { get; set; }
public DateTimeOffset? Closed { get; set; }
}
35 changes: 35 additions & 0 deletions samples/HealthCheck/VehicleTelemetryEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Text.Json.Serialization;

namespace HealthCheck;

internal class VehicleTelemetryEvent
{
public string? DeviceId { get; set; }

public DateTimeOffset Timestamp { get; set; }

public string? Action { get; set; }

public VehicleDoorKind? VehicleDoorKind { get; set; }
public VehicleDoorStatus? VehicleDoorStatus { get; set; }

[JsonExtensionData]
public Dictionary<string, object>? Extras { get; set; }
}

public enum VehicleDoorStatus
{
Unknown,
Open,
Closed,
}

public enum VehicleDoorKind
{
FrontLeft,
FrontRight,
RearLeft,
ReadRight,
Hood,
Trunk,
}
35 changes: 35 additions & 0 deletions samples/HealthCheck/VehicleTelemetryEventsConsumer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace HealthCheck;

internal class VehicleTelemetryEventsConsumer(ILogger<VehicleTelemetryEventsConsumer> logger) : IEventConsumer<VehicleTelemetryEvent>
{
public async Task ConsumeAsync(EventContext<VehicleTelemetryEvent> context, CancellationToken cancellationToken)
{
var telemetry = context.Event;

var status = telemetry.VehicleDoorStatus;
if (status is not VehicleDoorStatus.Open and not VehicleDoorStatus.Closed)
{
logger.LogWarning("Vehicle Door status '{VehicleDoorStatus}' is not yet supported", status);
return;
}

var kind = telemetry.VehicleDoorKind;
if (kind is null)
{
logger.LogWarning("Vehicle Door kind '{VehicleDoorKind}' cannot be null", kind);
return;
}

var timestamp = telemetry.Timestamp;
var updateEvt = new VehicleDoorOpenedEvent
{
VehicleId = telemetry.DeviceId,
Kind = kind.Value,
Closed = status is VehicleDoorStatus.Closed ? timestamp : null,
Opened = status is VehicleDoorStatus.Open ? timestamp : null,
};

// the VehicleDoorOpenedEvent on a broadcast bus would notify all subscribers
await context.PublishAsync(updateEvt, cancellationToken: cancellationToken);
}
}
16 changes: 16 additions & 0 deletions samples/HealthCheck/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Information",
"System": "Information"
},
"Console": {
"FormatterName": "simple",
"FormatterOptions": {
"SingleLine": true,
"TimestampFormat": "HH:mm:ss "
}
}
}
}
9 changes: 9 additions & 0 deletions samples/HealthCheck/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}

0 comments on commit d2292b9

Please sign in to comment.