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

Fix seq trailing slash, respect cancellation token, make SeqOptions.ApiKey nullable #1304

Merged
merged 13 commits into from
Jun 27, 2022
2 changes: 1 addition & 1 deletion src/HealthChecks.Publisher.Seq/SeqOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public class SeqOptions
{
public string Endpoint { get; set; } = null!;

public string ApiKey { get; set; } = null!;
public string? ApiKey { get; set; }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had a look at the official API client for seq and it seems that the ApiKey can be null so it probably makes sense for it to be null here too?

https://github.com/datalust/seq-api/blob/dev/src/Seq.Api/Client/SeqApiClient.cs#L75=

Copy link
Collaborator

Choose a reason for hiding this comment

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

yes


public SeqInputLevel DefaultInputLevel { get; set; }
}
Expand Down
42 changes: 33 additions & 9 deletions src/HealthChecks.Publisher.Seq/SeqPublisher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ namespace HealthChecks.Publisher.Seq
{
public class SeqPublisher : IHealthCheckPublisher
{

private readonly SeqOptions _options;
private readonly Func<HttpClient> _httpClientFactory;
private readonly Uri _checkUri;

public SeqPublisher(Func<HttpClient> httpClientFactory, SeqOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
_checkUri = BuildCheckUri(options);
}

public async Task PublishAsync(HealthReport report, CancellationToken cancellationToken)
Expand All @@ -33,19 +34,21 @@ public async Task PublishAsync(HealthReport report, CancellationToken cancellati
break;
}

string? assemblyName = Assembly.GetEntryAssembly()?.GetName().Name;

var events = new RawEvents
{
Events = new RawEvent[]
{
new RawEvent
{
Timestamp = DateTimeOffset.UtcNow,
MessageTemplate = $"[{Assembly.GetEntryAssembly()?.GetName().Name} - HealthCheck Result]",
MessageTemplate = $"[{assemblyName} - HealthCheck Result]",
Level = level.ToString(),
Properties = new Dictionary<string, object?>
{
{ nameof(Environment.MachineName), Environment.MachineName },
{ nameof(Assembly), Assembly.GetEntryAssembly()?.GetName().Name },
{ nameof(Assembly), assemblyName },
{ "Status", report.Status.ToString() },
{ "TimeElapsed", report.TotalDuration.TotalMilliseconds },
{ "RawReport" , JsonConvert.SerializeObject(report)}
Expand All @@ -54,29 +57,50 @@ public async Task PublishAsync(HealthReport report, CancellationToken cancellati
}
};

await PushMetricsAsync(JsonConvert.SerializeObject(events));
await PushMetricsAsync(JsonConvert.SerializeObject(events), cancellationToken);
}

private async Task PushMetricsAsync(string json)
private async Task PushMetricsAsync(string json, CancellationToken cancellationToken)
{
try
{
var httpClient = _httpClientFactory();
using var httpClient = _httpClientFactory();

using var pushMessage = new HttpRequestMessage(HttpMethod.Post, $"{_options.Endpoint}/api/events/raw?apiKey={_options.ApiKey}")
using var pushMessage = new HttpRequestMessage(HttpMethod.Post, _checkUri)
{
Content = new StringContent(json, Encoding.UTF8, "application/json")
};

using var response = await httpClient.SendAsync(pushMessage, HttpCompletionOption.ResponseHeadersRead);
using var response = await httpClient.SendAsync(
pushMessage,
HttpCompletionOption.ResponseHeadersRead,
cancellationToken);

response.EnsureSuccessStatusCode();
}
catch (Exception ex)
{
Trace.WriteLine($"Exception is throwed publishing metrics to Seq with message: {ex.Message}");
Trace.WriteLine($"Exception thrown publishing metrics to Seq with message: {ex.Message}");
}
}

private static Uri BuildCheckUri(SeqOptions options)
{
if (string.IsNullOrEmpty(options.Endpoint))
throw new ArgumentNullException(nameof(options.Endpoint));

var uriBuilder = new UriBuilder(options.Endpoint)
{
Path = "/api/events/raw",
};

// Add api key if supplied
if (!string.IsNullOrEmpty(options.ApiKey))
uriBuilder.Query = "?apiKey=" + options.ApiKey;

return uriBuilder.Uri;
}

private class RawEvents
{
public RawEvent[] Events { get; set; } = null!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ public void add_healthcheck_when_properly_configured()
services
.AddHealthChecks()
.AddSeqPublisher(setup =>
new SeqOptions()
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 funny test fix

{
ApiKey = "apiKey",
DefaultInputLevel = Seq.SeqInputLevel.Information,
Endpoint = "endpoint"
});
{
setup.Endpoint = "endpoint";
setup.DefaultInputLevel = Seq.SeqInputLevel.Information;
setup.ApiKey = "apiKey";
});

using var serviceProvider = services.BuildServiceProvider();
var publisher = serviceProvider.GetService<IHealthCheckPublisher>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Net;

namespace HealthChecks.Publisher.Seq.Tests.Functional;

public class seq_publisher_should
{
[Fact]
public async Task handle_trailing_slash_in_endpoint()
{
var options = new SeqOptions
{
ApiKey = "test-key",
Endpoint = "http://localhost:5341/"
};

var expectedUri = new Uri("http://localhost:5341/api/events/raw?apiKey=test-key");

// Setup mocks
using var handler = new MockClientHandler();
HttpClient HttpClientFactory() => new(handler);

var testReport = new HealthReport(new Dictionary<string, HealthReportEntry>(), TimeSpan.Zero);

// Create publisher and publish
var publisher = new SeqPublisher(HttpClientFactory, options);
await publisher.PublishAsync(testReport, CancellationToken.None);

handler.Request.ShouldNotBeNull();
handler.Request.RequestUri.ShouldBe(expectedUri);
}

[Fact]
public void throw_exception_when_endpoint_is_null()
{
var options = new SeqOptions
{
ApiKey = "test-key",
Endpoint = null!
};

HttpClient HttpClientFactory() => new();

var ex = Should.Throw<ArgumentNullException>(() => new SeqPublisher(HttpClientFactory, options));
ex.ParamName.ShouldBe(nameof(SeqOptions.Endpoint));
}

private class MockClientHandler : HttpClientHandler
{
public HttpRequestMessage? Request;

protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
Request = request;

var response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK
};

return Task.FromResult(response);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ namespace Microsoft.Extensions.DependencyInjection
public class SeqOptions
{
public SeqOptions() { }
public string ApiKey { get; set; }
public string? ApiKey { get; set; }
public HealthChecks.Publisher.Seq.SeqInputLevel DefaultInputLevel { get; set; }
public string Endpoint { get; set; }
}
Expand Down