-
Notifications
You must be signed in to change notification settings - Fork 801
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
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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, | ||
sp => new SendGridHealthCheck(apiKey,() => sp.GetRequiredService<IHttpClientFactory>().CreateClient(registrationName)), | ||
manne marked this conversation as resolved.
Show resolved
Hide resolved
|
||
failureStatus, | ||
tags, | ||
timeout)); | ||
} | ||
} | ||
} |
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> |
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) | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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!! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How should I invoke If A. which value? AspNetCore.Diagnostics.HealthChecks/src/HealthChecks.UI/Core/Notifications/WebHookFailureNotifier.cs Line 35 in 19bb7ed
|
||||
{ | ||||
_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; | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is more natural, if ( response.IsSuccessStatusCode ) instead this comparasion There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately this can not be done, because Also the SendGrid documentation explicitly states that a public bool IsSuccessStatusCode
{
get { return ((int)_statusCode >= 200) && ((int)_statusCode <= 299); }
} Reference dotnet/runtime There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)); | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
do you mean?
Then I have to do this
builder.Services.AddHttpClient(name ?? NAME);
. Or do thisbuilder.Services.AddHttpClient();
?Or did I miss something else? Did not understand the
There was a problem hiding this comment.
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!