Skip to content

Commit

Permalink
Merge pull request #34 from evilpilaf/new-batching
Browse files Browse the repository at this point in the history
Use new batching approach
  • Loading branch information
evilpilaf authored Mar 9, 2020
2 parents f39d5bc + 6ee2f9a commit d4fa88d
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 43 deletions.
3 changes: 2 additions & 1 deletion src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
<Copyright>evilpilaf © $([System.DateTime]::Now.Year)</Copyright>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageLicenseFile>LICENSE.TXT</PackageLicenseFile>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<!-- SourceLink -->
Expand Down Expand Up @@ -49,7 +50,7 @@
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Serilog" Version="2.9.0" />
<PackageReference Include="Serilog.Sinks.PeriodicBatching" Version="2.2.0" />
<PackageReference Include="Serilog.Sinks.PeriodicBatching" Version="2.3.0" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net461' OR '$(TargetFramework)' == 'net462' OR '$(TargetFramework)' == 'net47' OR '$(TargetFramework)' == 'net471' OR '$(TargetFramework)' == 'net472' OR '$(TargetFramework)' == 'net48' ">
Expand Down
93 changes: 59 additions & 34 deletions src/Honeycomb.Serilog.Sink/HoneycombSerilogSink.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
Expand All @@ -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<HttpClient> _clientBuilder = new Lazy<HttpClient>(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}";

/// <param name="teamId">The name of the team to submit the events to</param>
/// <param name="apiKey">The API key given in the Honeycomb ui</param>
/// <param name="batchSizeLimit">The maximum number of events to include in a single batch.</param>
/// <param name="period">The time to wait between checking for event batches.</param>
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<LogEvent> events)
public async Task EmitBatchAsync(IEnumerable<LogEvent> 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<HttpResponseMessage> 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
Expand All @@ -102,13 +103,37 @@ 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
client.BaseAddress = _honeycombApiUrl;

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();
}
}
}
24 changes: 22 additions & 2 deletions src/Honeycomb.Serilog.Sink/HoneycombSinkExtensions.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <param name="loggerConfiguration"></param>
/// <param name="teamId">The name of the team to submit the events to</param>
/// <param name="apiKey">The API key given in the Honeycomb ui</param>
/// <param name="batchSizeLimit">The maximum number of events to include in a single batch.</param>
Expand All @@ -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);
}
}
}
8 changes: 3 additions & 5 deletions test/Honeycomb.Serilog.Sink.Tests/HoneycombSerilogSinkStub.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

using Serilog.Events;
using Serilog.Parsing;
using Serilog.Sinks.PeriodicBatching;

using Xunit;

Expand Down Expand Up @@ -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);
}
}
}

0 comments on commit d4fa88d

Please sign in to comment.