diff --git a/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj b/src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj index 4d5e767..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 diff --git a/src/Honeycomb.Serilog.Sink/HoneycombSerilogSink.cs b/src/Honeycomb.Serilog.Sink/HoneycombSerilogSink.cs index 6253d8e..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,10 +13,19 @@ namespace Honeycomb.Serilog.Sink { - internal class HoneycombSerilogSink : IBatchedLogEventSink + 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); @@ -24,12 +33,17 @@ internal class HoneycombSerilogSink : IBatchedLogEventSink #endif private readonly string _apiKey; private readonly string _teamId; - private static readonly Uri _honeycombApiUrl = new Uri("https://api.honeycomb.io/"); + 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) { _teamId = string.IsNullOrWhiteSpace(teamId) ? throw new ArgumentNullException(nameof(teamId)) : teamId; @@ -38,41 +52,35 @@ public HoneycombSerilogSink(string teamId, string apiKey) 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 @@ -95,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 @@ -108,5 +116,24 @@ 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(); + } } }