Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add health check for SendGrid #504

Merged
merged 5 commits into from
Apr 30, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion AspNetCore.Diagnostics.HealthChecks.sln
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HealthChecks.UI.PostgreSQL.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HealthChecks.UI.StorageProviders", "samples\HealthChecks.UI.StorageProviders\HealthChecks.UI.StorageProviders.csproj", "{E3162C66-F57D-4517-AD15-8A513278DD66}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HealthChecks.UI.MySql.Storage", "src\HealthChecks.UI.MySql.Storage\HealthChecks.UI.MySql.Storage.csproj", "{C755B50A-61F6-46D2-B5D0-51AE25CAFB70}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HealthChecks.UI.MySql.Storage", "src\HealthChecks.UI.MySql.Storage\HealthChecks.UI.MySql.Storage.csproj", "{C755B50A-61F6-46D2-B5D0-51AE25CAFB70}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HealthChecks.SendGrid", "src\HealthChecks.SendGrid\HealthChecks.SendGrid.csproj", "{945ABBC6-5280-4233-8E61-745B6E6B4D8B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down Expand Up @@ -349,6 +351,10 @@ Global
{C755B50A-61F6-46D2-B5D0-51AE25CAFB70}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C755B50A-61F6-46D2-B5D0-51AE25CAFB70}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C755B50A-61F6-46D2-B5D0-51AE25CAFB70}.Release|Any CPU.Build.0 = Release|Any CPU
{945ABBC6-5280-4233-8E61-745B6E6B4D8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{945ABBC6-5280-4233-8E61-745B6E6B4D8B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{945ABBC6-5280-4233-8E61-745B6E6B4D8B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{945ABBC6-5280-4233-8E61-745B6E6B4D8B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -407,6 +413,7 @@ Global
{62511278-8E38-4753-A6AF-5C770BC0D4DC} = {2A3FD988-2BB8-43CF-B3A2-B70E648259D4}
{E3162C66-F57D-4517-AD15-8A513278DD66} = {092533AB-7505-4EDC-8932-D40BF575D0D2}
{C755B50A-61F6-46D2-B5D0-51AE25CAFB70} = {2A3FD988-2BB8-43CF-B3A2-B70E648259D4}
{945ABBC6-5280-4233-8E61-745B6E6B4D8B} = {2A3FD988-2BB8-43CF-B3A2-B70E648259D4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2B8C62A1-11B6-469F-874C-A02443256568}
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ Install-Package AspNetCore.HealthChecks.Hangfire
Install-Package AspNetCore.HealthChecks.SignalR
Install-Package AspNetCore.HealthChecks.Kubernetes
Install-Package AspNetCore.HealthChecks.Gcp.CloudFirestore
Install-Package AspNetCore.HealthChecks.SendGrid
```

Once the package is installed you can add the HealthCheck using the **AddXXX** IServiceCollection extension methods.
Expand Down
2 changes: 2 additions & 0 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ if ($suffix -eq "") {
exec { & dotnet pack .\src\HealthChecks.SignalR\HealthChecks.SignalR.csproj -c Release -o .\artifacts --include-symbols --no-build }
exec { & dotnet pack .\src\HealthChecks.Gcp.CloudFirestore\HealthChecks.Gcp.CloudFirestore.csproj -c Release -o .\artifacts --include-symbols --no-build }
exec { & dotnet pack .\src\HealthChecks.IbmMQ\HealthChecks.IbmMQ.csproj -c Release -o .\artifacts --include-symbols --no-build }
exec { & dotnet pack .\src\HealthChecks.SendGrid\HealthChecks.SendGrid.csproj -c Release -o .\artifacts --include-symbols --no-build }
}

else {
Expand Down Expand Up @@ -157,4 +158,5 @@ else {
exec { & dotnet pack .\src\HealthChecks.SignalR\HealthChecks.SignalR.csproj -c Release -o .\artifacts --include-symbols --no-build --version-suffix=$suffix }
exec { & dotnet pack .\src\HealthChecks.Gcp.CloudFirestore\HealthChecks.Gcp.CloudFirestore.csproj -c Release -o .\artifacts --include-symbols --no-build --version-suffix=$suffix }
exec { & dotnet pack .\src\HealthChecks.IbmMQ\HealthChecks.IbmMQ.csproj -c Release -o .\artifacts --include-symbols --no-build --version-suffix=$suffix }
exec { & dotnet pack .\src\HealthChecks.SendGrid\HealthChecks.SendGrid.csproj -c Release -o .\artifacts --include-symbols --no-build --version-suffix=$suffix }
}
2 changes: 2 additions & 0 deletions build/dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
<SolrNetCore>1.0.19</SolrNetCore>
<IBMMQDotnetClient>9.1.4</IBMMQDotnetClient>
<PomeloEntityFrameworkCoreMySql>3.1.1</PomeloEntityFrameworkCoreMySql>
<SendGrid>9.14.0</SendGrid>
</PropertyGroup>

<PropertyGroup Label="CLI Tools Versions">
Expand Down Expand Up @@ -131,5 +132,6 @@
<HealthCheckIoTHub>3.1.1</HealthCheckIoTHub>
<HealthCheckIbmMQ>3.1.1</HealthCheckIbmMQ>
<HealthChecksUIK8sOperator>3.1.0-beta.7</HealthChecksUIK8sOperator>
<HealthCheckSendGrid>3.1.1-beta.1</HealthCheckSendGrid>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using HealthChecks.SendGrid;
using Microsoft.Extensions.Diagnostics.HealthChecks;

namespace Microsoft.Extensions.DependencyInjection
{
public static class SendGridHealthCheckExtensions
{
private const string NAME = "sendgrid";

/// <summary>
/// Add a health check for SendGrid.
/// </summary>
/// <param name="builder">The <see cref="IHealthChecksBuilder"/>.</param>
/// <param name="apiKey">The API key.</param>
/// <param name="name">The health check name. Optional. If <c>null</c> the type name 'sendgrid' will be used for the name.</param>
/// <param name="failureStatus">
/// The <see cref="HealthStatus"/> that should be reported when the health check fails. Optional. If <c>null</c> then
/// the default status of <see cref="HealthStatus.Unhealthy"/> will be reported.
/// </param>
/// <param name="tags">A list of tags that can be used to filter sets of health checks. Optional.</param>
/// <param name="timeout">An optional System.TimeSpan representing the timeout of the check.</param>
/// <returns>The <see cref="IHealthChecksBuilder"/>.</returns>
public static IHealthChecksBuilder AddSendGrid(this IHealthChecksBuilder builder, string apiKey, string name = default, HealthStatus? failureStatus = default, IEnumerable<string> tags = default, TimeSpan? timeout = default)
{
var registrationName = name ?? NAME;

builder.Services.AddHttpClient(registrationName);

return builder.Add(new HealthCheckRegistration(
registrationName,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we use name ?? NAME instead creating registrationName variable on the others healthchecks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @unaizorrilla I do not understand you.

variable on the others healthchecks

do you mean?

return builder.Add(new HealthCheckRegistration(
                name ?? NAME,

Then I have to do this builder.Services.AddHttpClient(name ?? NAME);. Or do this builder.Services.AddHttpClient();?

Or did I miss something else? Did not understand the

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeap, don't see this variable is used on both places! is ok!

sp => new SendGridHealthCheck(apiKey,() => sp.GetRequiredService<IHttpClientFactory>().CreateClient(registrationName)),
manne marked this conversation as resolved.
Show resolved Hide resolved
failureStatus,
tags,
timeout));
}
}
}
29 changes: 29 additions & 0 deletions src/HealthChecks.SendGrid/HealthChecks.SendGrid.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(NetStandardTargetVersion)</TargetFramework>
<PackageLicenseUrl>$(PackageLicenseUrl)</PackageLicenseUrl>
manne marked this conversation as resolved.
Show resolved Hide resolved
<PackageProjectUrl>$(PackageProjectUrl)</PackageProjectUrl>
<PackageTags>HealthCheck;SendGrid</PackageTags>
<Description>HealthChecks.SendGrid is the health check package for SendGrid.</Description>
<Version>$(HealthCheckSendGrid)</Version>
<RepositoryUrl>$(RepositoryUrl)</RepositoryUrl>
<Company>$(Company)</Company>
<Authors>$(Authors)</Authors>
<LangVersion>latest</LangVersion>
<PackageId>AspNetCore.HealthChecks.SendGrid</PackageId>
<PublishRepositoryUrl>$(PublishRepositoryUrl)</PublishRepositoryUrl>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder)</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="$(MicrosoftExtensionsDiagnosticsHealthChecks)" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="$(MicrosoftSourceLinkGithub)">
manne marked this conversation as resolved.
Show resolved Hide resolved
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Http" Version="$(MicrosoftExtensionsHttp)" />
<PackageReference Include="SendGrid" Version="$(SendGrid)" />
</ItemGroup>

</Project>
65 changes: 65 additions & 0 deletions src/HealthChecks.SendGrid/SendGridHealthCheck.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using SendGrid;
using SendGrid.Helpers.Mail;

namespace HealthChecks.SendGrid
{
public class SendGridHealthCheck : IHealthCheck
{
private const string MailAddressName = "Health Check User";
private const string MailAddress = "healthcheck@example.com";
private const string Subject = "Checking health is Fun";
private readonly string _apiKey;
private readonly Func<HttpClient> _httpClientFactory;

public SendGridHealthCheck(string apiKey, Func<HttpClient> httpClientFactory)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know others healthcheck's like ( uris ) using this pattern, but probably inject Ihttpclientfactory is more natural here!!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How should I invoke _httpClientFactory.CreateClient()
A. with a name or
B. without a name

If A. which value?
I) The name of the healthcheck passed in with AddSendGrid(...)
II) A name specific for the SendGridHealthCheck class (as in

_httpClient = httpClientFactory.CreateClient(Keys.HEALTH_CHECK_WEBHOOK_HTTP_CLIENT_NAME);
)

{
_apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey));
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
}

public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
{
try
{
var httpClient = _httpClientFactory();
var client = new SendGridClient(httpClient, _apiKey);
var from = new EmailAddress(MailAddress, MailAddressName);
var to = new EmailAddress(MailAddress, MailAddressName);
var msg = MailHelper.CreateSingleEmail(from, to, Subject, Subject, null);
msg.SetSandBoxMode(true);
var response = await client.SendEmailAsync(msg, cancellationToken);

HealthCheckResult result;
var isHealthy = response.StatusCode == HttpStatusCode.OK;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is more natural, if ( response.IsSuccessStatusCode ) instead this comparasion

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you reduce if/else

if (!response.IsSuccessStatusCode)
{
return new HealthCheckResult( ..... )
}
return HealthCheckResult.Healty()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this can not be done, because await client.SendEmailAsync(msg, cancellationToken) returns the type Task<Response> and Response is a SendGrip C# specific type.

Also the SendGrid documentation explicitly states that a 200 is returned.
Whereas IsSuccessStatusCode is

        public bool IsSuccessStatusCode
        {
            get { return ((int)_statusCode >= 200) && ((int)_statusCode <= 299); }
        }

Reference dotnet/runtime

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IsSuccessStatusCode include 200 and all 200-299 are Success response, I think is more natural!

if (isHealthy)
{
result = HealthCheckResult.Healthy();
}
else
{
result = new HealthCheckResult(
context.Registration.FailureStatus,
$"Sending an email to SendGrid using the sandbox mode is not responding with 200 OK, the current status is {response.StatusCode}",
null,
new Dictionary<string, object>
{
{ "responseStatusCode", (int)response.StatusCode }
});
}

return result;
}
catch (Exception ex)
{
return new HealthCheckResult(context.Registration.FailureStatus, exception: ex);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Linq;
using FluentAssertions;
using FluentAssertions.Execution;
using HealthChecks.SendGrid;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
using Xunit;

namespace UnitTests.HealthChecks.DependencyInjection.SendGrid
{
public class sendgrid_registrations_should
{
[Fact]
public void add_health_check_when_properly_configured()
{
var services = new ServiceCollection();
services.AddHealthChecks()
.AddSendGrid("wellformed_but_invalid_token");

var serviceProvider = services.BuildServiceProvider();
var options = serviceProvider.GetService<IOptions<HealthCheckServiceOptions>>();

var registration = options.Value.Registrations.First();
var check = registration.Factory(serviceProvider);

using (new AssertionScope())
{
registration.Name.Should().Be("sendgrid");
check.GetType().Should().Be<SendGridHealthCheck>();
}
}

[Fact]
public void add_named_health_check_when_properly_configured()
{
var services = new ServiceCollection();
services.AddHealthChecks()
.AddSendGrid("wellformed_but_invalid_token", "my-sendgrid-group");

var serviceProvider = services.BuildServiceProvider();
var options = serviceProvider.GetService<IOptions<HealthCheckServiceOptions>>();

var registration = options.Value.Registrations.First();
var check = registration.Factory(serviceProvider);

using (new AssertionScope())
{
registration.Name.Should().Be("my-sendgrid-group");
check.GetType().Should().Be(typeof(SendGridHealthCheck));
}
}
}
}
1 change: 1 addition & 0 deletions test/UnitTests/UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<ProjectReference Include="..\..\src\HealthChecks.RabbitMQ\HealthChecks.RabbitMQ.csproj" />
<ProjectReference Include="..\..\src\HealthChecks.RavenDB\HealthChecks.RavenDB.csproj" />
<ProjectReference Include="..\..\src\HealthChecks.Redis\HealthChecks.Redis.csproj" />
<ProjectReference Include="..\..\src\HealthChecks.SendGrid\HealthChecks.SendGrid.csproj" />
<ProjectReference Include="..\..\src\HealthChecks.SignalR\HealthChecks.SignalR.csproj" />
<ProjectReference Include="..\..\src\HealthChecks.Solr\HealthChecks.Solr.csproj" />
<ProjectReference Include="..\..\src\HealthChecks.Sqlite\HealthChecks.Sqlite.csproj" />
Expand Down