diff --git a/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj b/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj index f9e90c2..ff1eafc 100644 --- a/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj +++ b/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj @@ -10,6 +10,7 @@ evilpilaf © $([System.DateTime]::Now.Year) true LICENSE.TXT + latest @@ -49,7 +50,7 @@ - + diff --git a/src/Honeycomb.Serilog.Sink/HoneycombSerilogSink.cs b/src/Honeycomb.Serilog.Sink/HoneycombSerilogSink.cs index 232aa3a..2566aeb 100644 --- a/src/Honeycomb.Serilog.Sink/HoneycombSerilogSink.cs +++ b/src/Honeycomb.Serilog.Sink/HoneycombSerilogSink.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Net.Http; @@ -13,73 +13,74 @@ namespace Honeycomb.Serilog.Sink { - internal class HoneycombSerilogSink : PeriodicBatchingSink + internal class HoneycombSerilogSink : IBatchedLogEventSink, IDisposable { #if NETCOREAPP - private static readonly SocketsHttpHandler _socketsHttpHandler = new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(30) }; + private static SocketsHttpHandler _socketsHttpHandler; + + private static SocketsHttpHandler SocketsHttpHandler + { + get + { + return _socketsHttpHandler ??= new SocketsHttpHandler { PooledConnectionLifetime = TimeSpan.FromMinutes(30) }; + } + } + protected virtual HttpClient Client => BuildHttpClient(); #else private static readonly Lazy _clientBuilder = new Lazy(BuildHttpClient); protected virtual HttpClient Client => _clientBuilder.Value; #endif - private static readonly Uri _honeycombApiUrl = new Uri("https://api.honeycomb.io/"); - private readonly string _apiKey; - private readonly string _teamId; + private static readonly Uri _honeycombApiUrl = new Uri(HoneycombBaseUri); + + private const string JsonContentType = "application/json"; + private const string HoneycombBaseUri = "https://api.honeycomb.io/"; + private const string HoneycombBatchEndpointTemplate = "/1/batch/{0}"; + private const string HoneycombTeamIdHeaderName = "X-Honeycomb-Team"; + + private const string SelfLogMessageText = "Failure sending event to Honeycomb, received {statusCode} response with content {content}"; /// The name of the team to submit the events to /// The API key given in the Honeycomb ui - /// The maximum number of events to include in a single batch. - /// The time to wait between checking for event batches. - public HoneycombSerilogSink( - string teamId, - string apiKey, - int batchSizeLimit, - TimeSpan period) - : base(batchSizeLimit, period) + public HoneycombSerilogSink(string teamId, string apiKey) { _teamId = string.IsNullOrWhiteSpace(teamId) ? throw new ArgumentNullException(nameof(teamId)) : teamId; _apiKey = string.IsNullOrWhiteSpace(apiKey) ? throw new ArgumentNullException(nameof(apiKey)) : apiKey; } - protected override async Task EmitBatchAsync(IEnumerable events) + public async Task EmitBatchAsync(IEnumerable events) { - using (TextWriter writer = new StringWriter()) - { - BuildLogEvent(events, writer); - await SendBatchedEvents(writer.ToString()); - } + using TextWriter writer = new StringWriter(); + BuildLogEvent(events, writer); + await SendBatchedEvents(writer.ToString()).ConfigureAwait(false); } private async Task SendBatchedEvents(string events) { - var requestMessage = new HttpRequestMessage(HttpMethod.Post, $"/1/batch/{_teamId}") + using var requestMessage = new HttpRequestMessage(HttpMethod.Post, string.Format(HoneycombBatchEndpointTemplate, _teamId)) { - Content = new StringContent(events, Encoding.UTF8, "application/json"), + Content = new StringContent(events, Encoding.UTF8, JsonContentType), Version = new Version(2, 0) }; - requestMessage.Headers.Add("X-Honeycomb-Team", _apiKey); + requestMessage.Headers.Add(HoneycombTeamIdHeaderName, _apiKey); var response = await SendRequest(requestMessage).ConfigureAwait(false); if (!response.IsSuccessStatusCode) { - using (Stream contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) - using (var reader = new StreamReader(contentStream)) - { - var responseContent = await reader.ReadToEndAsync().ConfigureAwait(false); - SelfLog.WriteLine("Failure sending event to Honeycomb, received {statusCode} response with content {content}", response.StatusCode, responseContent); - } + using Stream contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + using var reader = new StreamReader(contentStream); + var responseContent = await reader.ReadToEndAsync().ConfigureAwait(false); + SelfLog.WriteLine(SelfLogMessageText, response.StatusCode, responseContent); } } private async Task SendRequest(HttpRequestMessage request) { #if NETCOREAPP - using (var client = Client) - { - return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); - } + using var client = Client; + return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); #else return await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); #endif @@ -102,7 +103,7 @@ private static HttpClient BuildHttpClient() { HttpClient client; #if NETCOREAPP - client = new HttpClient(_socketsHttpHandler, disposeHandler: false); + client = new HttpClient(SocketsHttpHandler, disposeHandler: false); #else client = new HttpClient(); #endif @@ -110,5 +111,29 @@ private static HttpClient BuildHttpClient() return client; } + + public Task OnEmptyBatchAsync() + { + return Task.CompletedTask; + } + + private void ReleaseUnmanagedResources() + { +#if NETCORE + _socketsHttpHandler?.Dispose(); + _socketsHttpHandler = null; +#endif + } + + public void Dispose() + { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + ~HoneycombSerilogSink() + { + ReleaseUnmanagedResources(); + } } } diff --git a/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs b/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs index e6da33f..54a2cc9 100644 --- a/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs +++ b/src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs @@ -1,12 +1,14 @@ -using System; +using System; using Serilog; using Serilog.Configuration; +using Serilog.Sinks.PeriodicBatching; namespace Honeycomb.Serilog.Sink { public static class HoneycombSinkExtensions { + /// /// The name of the team to submit the events to /// The API key given in the Honeycomb ui /// The maximum number of events to include in a single batch. @@ -17,7 +19,25 @@ public static LoggerConfiguration HoneycombSink(this LoggerSinkConfiguration log int batchSizeLimit, TimeSpan period) { - return loggerConfiguration.Sink(new HoneycombSerilogSink(teamId, apiKey, batchSizeLimit, period)); + var batchingOptions = new PeriodicBatchingSinkOptions + { + BatchSizeLimit = batchSizeLimit, + Period = period + }; + + return loggerConfiguration.HoneycombSink(teamId, apiKey, batchingOptions); + } + + public static LoggerConfiguration HoneycombSink(this LoggerSinkConfiguration loggerConfiguration, + string teamId, + string apiKey, + PeriodicBatchingSinkOptions batchingOptions = default) + { + var honeycombSink = new HoneycombSerilogSink(teamId, apiKey); + + var batchingSink = new PeriodicBatchingSink(honeycombSink, batchingOptions ?? new PeriodicBatchingSinkOptions()); + + return loggerConfiguration.Sink(batchingSink); } } } diff --git a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs index f8e788e..fd5f4d8 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs +++ b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; +using System.Net.Http; using System.Threading.Tasks; using Serilog.Events; @@ -11,8 +9,8 @@ internal class HoneycombSerilogSinkStub : HoneycombSerilogSink { private readonly HttpClient _client; - public HoneycombSerilogSinkStub(HttpClient client, string teamId, string apiKey, int batchSizeLimit, TimeSpan period) - : base(teamId, apiKey, batchSizeLimit, period) + public HoneycombSerilogSinkStub(HttpClient client, string teamId, string apiKey) + : base(teamId, apiKey) { _client = client; } diff --git a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs index 8cd03f1..caea00e 100644 --- a/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs +++ b/test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkTests.cs @@ -12,6 +12,7 @@ using Serilog.Events; using Serilog.Parsing; +using Serilog.Sinks.PeriodicBatching; using Xunit; @@ -187,7 +188,7 @@ public async Task Emit_GivenAMessageWithProperties_SendsThemAllAsync() private HoneycombSerilogSinkStub CreateSut(string teamId, string apiKey, HttpClient client = null) { - return new HoneycombSerilogSinkStub(client, teamId, apiKey, 1, TimeSpan.FromMilliseconds(1)); + return new HoneycombSerilogSinkStub(client, teamId, apiKey); } } }