Skip to content

Commit 46494a7

Browse files
authored
Merge pull request #18 from evilpilaf/bugs
Bugs
2 parents 4497729 + 3d8482b commit 46494a7

File tree

8 files changed

+257
-76
lines changed

8 files changed

+257
-76
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
6+
using Serilog.Events;
7+
using Serilog.Formatting;
8+
using Serilog.Formatting.Json;
9+
10+
namespace Honeycomb.Serilog.Sink.Formatters
11+
{
12+
internal class RawJsonFormatter : ITextFormatter
13+
{
14+
private static readonly JsonValueFormatter ValueFormatter = new JsonValueFormatter();
15+
16+
public void Format(LogEvent logEvent, TextWriter output)
17+
{
18+
FormatContent(logEvent, output);
19+
}
20+
21+
public static void FormatContent(LogEvent logEvent, TextWriter output)
22+
{
23+
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
24+
if (output == null) throw new ArgumentNullException(nameof(output));
25+
26+
output.Write($"{{\"time\":\"{logEvent.Timestamp:O}\",");
27+
output.Write($"\"data\":{{");
28+
output.Write($"\"level\":\"{logEvent.Level}\"");
29+
output.Write(",\"messageTemplate\":");
30+
JsonValueFormatter.WriteQuotedJsonString(logEvent.MessageTemplate.Text, output);
31+
if (logEvent.Exception != null)
32+
{
33+
output.Write(",\"exception\":");
34+
JsonValueFormatter.WriteQuotedJsonString(logEvent.Exception.ToString(), output);
35+
}
36+
37+
if (logEvent.Properties.Any())
38+
{
39+
WriteProperties(logEvent.Properties, output);
40+
}
41+
42+
output.Write('}');
43+
output.Write('}');
44+
}
45+
46+
private static void WriteProperties(IReadOnlyDictionary<string, LogEventPropertyValue> properties, TextWriter output)
47+
{
48+
var precedingDelimiter = ",";
49+
foreach (var property in properties)
50+
{
51+
output.Write(precedingDelimiter);
52+
53+
JsonValueFormatter.WriteQuotedJsonString(property.Key, output);
54+
output.Write(':');
55+
ValueFormatter.Format(property.Value, output);
56+
}
57+
}
58+
}
59+
}

src/Honeycomb.Serilog.Sink/Honeycomb.Serilog.Sink.csproj

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net461;netstandard2.0;netstandard2.1</TargetFrameworks>
4+
<TargetFrameworks>net461;netstandard2.0</TargetFrameworks>
55
<PackageLicenseExpression>MIT</PackageLicenseExpression>
66
<Title>Honeycomb Serilog sink</Title>
77
<Authors>evilpilaf</Authors>
@@ -11,12 +11,22 @@
1111
<Copyright>evilpilaf © $([System.DateTime]::Now.Year)</Copyright>
1212
</PropertyGroup>
1313

14+
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0'">
15+
<DefineConstants>NETCORE;NETSTANDARD;NETSTANDARD2_0</DefineConstants>
16+
</PropertyGroup>
17+
18+
<PropertyGroup Condition=" '$(TargetFramework)' == 'net461'">
19+
<DefineConstants>NET461;NETFULL</DefineConstants>
20+
</PropertyGroup>
21+
1422
<ItemGroup>
1523
<PackageReference Include="MinVer" Version="2.0.0">
1624
<PrivateAssets>all</PrivateAssets>
1725
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1826
</PackageReference>
27+
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
1928
<PackageReference Include="Serilog" Version="2.9.0" />
29+
<PackageReference Include="Serilog.Sinks.PeriodicBatching" Version="2.2.0" />
2030
</ItemGroup>
2131

2232
<ItemGroup Condition=" '$(TargetFramework)' == 'net461' OR '$(TargetFramework)' == 'net462' OR '$(TargetFramework)' == 'net47' OR '$(TargetFramework)' == 'net471' OR '$(TargetFramework)' == 'net472' OR '$(TargetFramework)' == 'net48' ">

src/Honeycomb.Serilog.Sink/HoneycombSerilogSink.cs

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,73 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO;
4-
using System.Linq;
54
using System.Net.Http;
65
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
using Honeycomb.Serilog.Sink.Formatters;
79

8-
using Serilog.Core;
910
using Serilog.Events;
11+
using Serilog.Sinks.PeriodicBatching;
1012

1113
namespace Honeycomb.Serilog.Sink
1214
{
13-
internal class HoneycombSerilogSink : ILogEventSink
15+
internal class HoneycombSerilogSink : PeriodicBatchingSink
1416
{
15-
private static readonly Uri _honeycombApiUrl = new Uri("https://api.honeycomb.io/1/events/");
17+
private static readonly Uri _honeycombApiUrl = new Uri("https://api.honeycomb.io/");
1618

1719
private readonly string _teamId;
1820
private readonly string _apiKey;
1921

2022
private readonly Lazy<HttpClient> _clientBuilder = new Lazy<HttpClient>(BuildHttpClient);
2123
protected virtual HttpClient Client => _clientBuilder.Value;
2224

23-
public HoneycombSerilogSink(string teamId, string apiKey)
25+
/// <param name="teamId">The name of the team to submit the events to</param>
26+
/// <param name="apiKey">The API key given in the Honeycomb ui</param>
27+
/// <param name="batchSizeLimit">The maximum number of events to include in a single batch.</param>
28+
/// <param name="period">The time to wait between checking for event batches.</param>
29+
public HoneycombSerilogSink(
30+
string teamId,
31+
string apiKey,
32+
int batchSizeLimit,
33+
TimeSpan period)
34+
: base(batchSizeLimit, period)
2435
{
2536
_teamId = string.IsNullOrWhiteSpace(teamId) ? throw new ArgumentNullException(nameof(teamId)) : teamId;
2637
_apiKey = string.IsNullOrWhiteSpace(apiKey) ? throw new ArgumentNullException(nameof(apiKey)) : apiKey;
2738
}
2839

29-
public void Emit(LogEvent logEvent)
40+
protected override async Task EmitBatchAsync(IEnumerable<LogEvent> events)
3041
{
31-
using (var buffer = new StringWriter(new StringBuilder()))
42+
using (TextWriter writer = new StringWriter())
3243
{
33-
var evnt = BuildLogEvent(logEvent);
34-
var message = new HttpRequestMessage(HttpMethod.Post, $"/{_teamId}")
35-
{
36-
Content = new StringContent(evnt)
37-
};
38-
message.Headers.Add("X-Honeycomb-Team", _apiKey);
39-
Client.SendAsync(message).ConfigureAwait(false).GetAwaiter().GetResult();
44+
BuildLogEvent(events, writer);
45+
await SendBatchedEvents(writer.ToString());
4046
}
4147
}
4248

43-
private static string BuildLogEvent(LogEvent logEvent)
49+
private async Task SendBatchedEvents(string events)
4450
{
45-
var evnt = new StringBuilder("{");
46-
47-
var propertyList = new List<string>(logEvent.Properties.Count() + 4)
51+
var message = new HttpRequestMessage(HttpMethod.Post, $"/1/batch/{_teamId}")
4852
{
49-
$"\"timestamp\": \"{logEvent.Timestamp:O}\"",
50-
$"\"level\": \"{logEvent.Level}\"",
51-
$"\"messageTemplate\": \"{logEvent.MessageTemplate}\""
53+
Content = new StringContent(events, Encoding.UTF8, "application/json")
5254
};
55+
message.Headers.Add("X-Honeycomb-Team", _apiKey);
56+
var result = await Client.SendAsync(message).ConfigureAwait(false);
57+
var response = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
58+
}
5359

54-
if (logEvent.Exception != null)
55-
{
56-
propertyList.Add($"\"exception\": \"{logEvent.Exception.ToString()}\"");
57-
}
58-
59-
foreach (var prop in logEvent.Properties)
60+
private static void BuildLogEvent(IEnumerable<LogEvent> logEvents, TextWriter payload)
61+
{
62+
payload.Write("[");
63+
var eventSepparator = "";
64+
foreach (var evnt in logEvents)
6065
{
61-
propertyList.Add($"\"{prop.Key}\": {prop.Value.ToString()}");
66+
payload.Write(eventSepparator);
67+
eventSepparator = ",";
68+
RawJsonFormatter.FormatContent(evnt, payload);
6269
}
63-
64-
evnt.Append(string.Join(",", propertyList));
65-
evnt.Append("}");
66-
return evnt.ToString();
70+
payload.Write("]");
6771
}
6872

6973
private static HttpClient BuildHttpClient()
@@ -72,7 +76,6 @@ private static HttpClient BuildHttpClient()
7276
{
7377
BaseAddress = _honeycombApiUrl
7478
};
75-
client.DefaultRequestHeaders.Add("Content-Type", "application/json");
7679
return client;
7780
}
7881
}
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
1+
using System;
2+
13
using Serilog;
24
using Serilog.Configuration;
35

46
namespace Honeycomb.Serilog.Sink
57
{
68
public static class HoneycombSinkExtensions
79
{
10+
/// <param name="teamId">The name of the team to submit the events to</param>
11+
/// <param name="apiKey">The API key given in the Honeycomb ui</param>
12+
/// <param name="batchSizeLimit">The maximum number of events to include in a single batch.</param>
13+
/// <param name="period">The time to wait between checking for event batches.</param>
814
public static LoggerConfiguration HoneycombSink(this LoggerSinkConfiguration loggerConfiguration,
915
string teamId,
10-
string apiKey)
16+
string apiKey,
17+
int batchSizeLimit,
18+
TimeSpan period)
1119
{
12-
return loggerConfiguration.Sink(new HoneycombSerilogSink(teamId, apiKey));
20+
return loggerConfiguration.Sink(new HoneycombSerilogSink(teamId, apiKey, batchSizeLimit, period));
1321
}
1422
}
1523
}

test/Honeycomb.Serilog.Sink.Tests/Helpers/HttpMessageHandlerStub.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
1515
{
1616
RequestMessage = request;
1717
RequestContent = await request.Content.ReadAsStringAsync();
18-
return new HttpResponseMessage(_statusCodeToReturn);
18+
return new HttpResponseMessage(_statusCodeToReturn)
19+
{
20+
Content = new StringContent("")
21+
};
1922
}
2023

2124
public void ReturnsStatusCode(HttpStatusCode statusCode)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
using Serilog;
5+
using Serilog.Events;
6+
7+
using Xunit.Sdk;
8+
9+
namespace Honeycomb.Serilog.Sink.Tests.Helpers
10+
{
11+
static class Some
12+
{
13+
public static LogEvent LogEvent(string messageTemplate, params object[] propertyValues)
14+
{
15+
return LogEvent(null, messageTemplate, propertyValues);
16+
}
17+
18+
public static LogEvent LogEvent(Exception exception, string messageTemplate, params object[] propertyValues)
19+
{
20+
return LogEvent(LogEventLevel.Information, exception, messageTemplate, propertyValues);
21+
}
22+
23+
public static LogEvent LogEvent(LogEventLevel level, Exception exception, string messageTemplate, params object[] propertyValues)
24+
{
25+
var log = new LoggerConfiguration().CreateLogger();
26+
MessageTemplate template;
27+
IEnumerable<LogEventProperty> properties;
28+
#pragma warning disable Serilog004 // Constant MessageTemplate verifier
29+
if (!log.BindMessageTemplate(messageTemplate, propertyValues, out template, out properties))
30+
#pragma warning restore Serilog004 // Constant MessageTemplate verifier
31+
{
32+
throw new XunitException("Template could not be bound.");
33+
}
34+
return new LogEvent(DateTimeOffset.Now, level, exception, template, properties);
35+
}
36+
37+
public static LogEvent LogEvent(LogEventLevel level, string messageTemplate, params object[] propertyValues)
38+
{
39+
var log = new LoggerConfiguration().CreateLogger();
40+
41+
#pragma warning disable Serilog004 // Constant MessageTemplate verifier
42+
if (!log.BindMessageTemplate(messageTemplate, propertyValues, out var template, out var properties))
43+
#pragma warning restore Serilog004 // Constant MessageTemplate verifier
44+
{
45+
throw new XunitException("Template could not be bound.");
46+
}
47+
return new LogEvent(DateTimeOffset.Now, level, null, template, properties);
48+
}
49+
50+
public static LogEvent DebugEvent()
51+
{
52+
return LogEvent(LogEventLevel.Debug, null, "Debug event");
53+
}
54+
55+
public static LogEvent InformationEvent()
56+
{
57+
return LogEvent(LogEventLevel.Information, null, "Information event");
58+
}
59+
60+
public static LogEvent ErrorEvent()
61+
{
62+
return LogEvent(LogEventLevel.Error, null, "Error event");
63+
}
64+
65+
public static string String()
66+
{
67+
return Guid.NewGuid().ToString("n");
68+
}
69+
}
70+
}
Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1-
using System.Net.Http;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Net.Http;
4+
using System.Threading.Tasks;
5+
6+
using Serilog.Events;
27

38
namespace Honeycomb.Serilog.Sink.Tests
49
{
510
internal class HoneycombSerilogSinkStub : HoneycombSerilogSink
611
{
712
private readonly HttpClient _client;
813

9-
public HoneycombSerilogSinkStub(HttpClient client, string teamId, string apiKey)
10-
: base(teamId, apiKey)
14+
public HoneycombSerilogSinkStub(HttpClient client, string teamId, string apiKey, int batchSizeLimit, TimeSpan period)
15+
: base(teamId, apiKey, batchSizeLimit, period)
1116
{
1217
_client = client;
1318
}
1419

1520
protected override HttpClient Client => _client;
21+
22+
public Task EmitTestable(params LogEvent[] events)
23+
{
24+
return EmitBatchAsync(events);
25+
}
1626
}
1727
}

0 commit comments

Comments
 (0)