Skip to content

Commit

Permalink
Merge pull request #25 from nblumhardt/f-clef
Browse files Browse the repository at this point in the history
Added `useCompactFormat` flag to support posting events using Serilog.Formatting.Compact
  • Loading branch information
nblumhardt authored Jul 7, 2016
2 parents 7edded5 + c401163 commit a20505b
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 32 deletions.
2 changes: 2 additions & 0 deletions sample/Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public static void Main(string[] args)
Thread.Sleep(1000);
Log.Debug("Loop iteration done");
}

Log.CloseAndFlush();
}
}
}
9 changes: 7 additions & 2 deletions src/Serilog.Sinks.Seq/SeqLoggerConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ public static class SeqLoggerConfigurationExtensions
/// <param name="retainedInvalidPayloadsLimitBytes">A soft limit for the number of bytes to use for storing failed requests.
/// The limit is soft in that it can be exceeded by any single error payload, but in that case only that single error
/// payload will be retained.</param>
/// <param name="useCompactFormat">Use the compact log event format defined by
/// <a href="https://github.com/serilog/serilog-formatting-compact">Serilog.Formatting.Compact</a>. Has no effect on
/// durable log shipping. Requires Seq 3.3+.</param>
/// <returns>Logger configuration, allowing configuration to continue.</returns>
/// <exception cref="ArgumentNullException">A required parameter is null.</exception>
public static LoggerConfiguration Seq(
Expand All @@ -65,7 +68,8 @@ public static LoggerConfiguration Seq(
long? eventBodyLimitBytes = 256 * 1024,
LoggingLevelSwitch controlLevelSwitch = null,
HttpMessageHandler messageHandler = null,
long? retainedInvalidPayloadsLimitBytes = null)
long? retainedInvalidPayloadsLimitBytes = null,
bool useCompactFormat = false)
{
if (loggerSinkConfiguration == null) throw new ArgumentNullException(nameof(loggerSinkConfiguration));
if (serverUrl == null) throw new ArgumentNullException(nameof(serverUrl));
Expand All @@ -84,7 +88,8 @@ public static LoggerConfiguration Seq(
defaultedPeriod,
eventBodyLimitBytes,
controlLevelSwitch,
messageHandler);
messageHandler,
useCompactFormat);
}
else
{
Expand Down
7 changes: 2 additions & 5 deletions src/Serilog.Sinks.Seq/Sinks/Seq/HttpLogShipper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ namespace Serilog.Sinks.Seq
{
class HttpLogShipper : IDisposable
{
const string ApiKeyHeaderName = "X-Seq-ApiKey";
const string BulkUploadResource = "api/events/raw";

static readonly TimeSpan RequiredLevelCheckInterval = TimeSpan.FromMinutes(2);

readonly string _apiKey;
Expand Down Expand Up @@ -206,9 +203,9 @@ void OnTick()

var content = new StringContent(payload, Encoding.UTF8, "application/json");
if (!string.IsNullOrWhiteSpace(_apiKey))
content.Headers.Add(ApiKeyHeaderName, _apiKey);
content.Headers.Add(SeqApi.ApiKeyHeaderName, _apiKey);

var result = _httpClient.PostAsync(BulkUploadResource, content).Result;
var result = _httpClient.PostAsync(SeqApi.BulkUploadResource, content).Result;
if (result.IsSuccessStatusCode)
{
_connectionSchedule.MarkSuccess();
Expand Down
5 changes: 5 additions & 0 deletions src/Serilog.Sinks.Seq/Sinks/Seq/SeqApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ namespace Serilog.Sinks.Seq
{
class SeqApi
{
public const string BulkUploadResource = "api/events/raw";
public const string ApiKeyHeaderName = "X-Seq-ApiKey";
public const string RawEventFormatMimeType = "application/json";
public const string CompactLogEventFormatMimeType = "application/vnd.serilog.clef";

// Why not use a JSON parser here? For a very small case, it's not
// worth taking on the extra payload/dependency management issues that
// a full-fledged parser will entail. If things get more sophisticated
Expand Down
95 changes: 74 additions & 21 deletions src/Serilog.Sinks.Seq/Sinks/Seq/SeqSink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
using Serilog.Core;
using Serilog.Debugging;
using Serilog.Events;
using Serilog.Formatting.Compact;
using Serilog.Formatting.Json;
using Serilog.Sinks.PeriodicBatching;

namespace Serilog.Sinks.Seq
Expand All @@ -31,12 +33,13 @@ class SeqSink : PeriodicBatchingSink
readonly string _apiKey;
readonly long? _eventBodyLimitBytes;
readonly HttpClient _httpClient;
const string BulkUploadResource = "api/events/raw";
const string ApiKeyHeaderName = "X-Seq-ApiKey";

static readonly JsonValueFormatter JsonValueFormatter = new JsonValueFormatter();

// If non-null, then background level checks will be performed; set either through the constructor
// or in response to a level specification from the server. Never set to null after being made non-null.
LoggingLevelSwitch _levelControlSwitch;
readonly bool _useCompactFormat;
static readonly TimeSpan RequiredLevelCheckInterval = TimeSpan.FromMinutes(2);
DateTime _nextRequiredLevelCheckUtc = DateTime.UtcNow.Add(RequiredLevelCheckInterval);

Expand All @@ -50,13 +53,15 @@ public SeqSink(
TimeSpan period,
long? eventBodyLimitBytes,
LoggingLevelSwitch levelControlSwitch,
HttpMessageHandler messageHandler)
HttpMessageHandler messageHandler,
bool useCompactFormat)
: base(batchPostingLimit, period)
{
if (serverUrl == null) throw new ArgumentNullException(nameof(serverUrl));
_apiKey = apiKey;
_eventBodyLimitBytes = eventBodyLimitBytes;
_levelControlSwitch = levelControlSwitch;
_useCompactFormat = useCompactFormat;

var baseUri = serverUrl;
if (!baseUri.EndsWith("/"))
Expand Down Expand Up @@ -89,13 +94,23 @@ protected override async Task EmitBatchAsync(IEnumerable<LogEvent> events)
{
_nextRequiredLevelCheckUtc = DateTime.UtcNow.Add(RequiredLevelCheckInterval);

var payload = FormatPayload(events, _eventBodyLimitBytes);
string payload, payloadContentType;
if (_useCompactFormat)
{
payloadContentType = SeqApi.CompactLogEventFormatMimeType;
payload = FormatCompactPayload(events, _eventBodyLimitBytes);
}
else
{
payloadContentType = SeqApi.RawEventFormatMimeType;
payload = FormatRawPayload(events, _eventBodyLimitBytes);
}

var content = new StringContent(payload, Encoding.UTF8, "application/json");
var content = new StringContent(payload, Encoding.UTF8, payloadContentType);
if (!string.IsNullOrWhiteSpace(_apiKey))
content.Headers.Add(ApiKeyHeaderName, _apiKey);
content.Headers.Add(SeqApi.ApiKeyHeaderName, _apiKey);

var result = await _httpClient.PostAsync(BulkUploadResource, content).ConfigureAwait(false);
var result = await _httpClient.PostAsync(SeqApi.BulkUploadResource, content).ConfigureAwait(false);
if (!result.IsSuccessStatusCode)
throw new LoggingFailedException($"Received failed result {result.StatusCode} when posting events to Seq");

Expand All @@ -115,39 +130,56 @@ protected override async Task EmitBatchAsync(IEnumerable<LogEvent> events)
}
}

internal static string FormatPayload(IEnumerable<LogEvent> events, long? eventBodyLimitBytes)
internal static string FormatCompactPayload(IEnumerable<LogEvent> events, long? eventBodyLimitBytes)
{
var payload = new StringWriter();
payload.Write("{\"Events\":[");

var delimStart = "";
foreach (var logEvent in events)
{
var buffer = new StringWriter();

try
{
RawJsonFormatter.FormatContent(logEvent, buffer);
CompactJsonFormatter.FormatEvent(logEvent, buffer, JsonValueFormatter);
}
catch (Exception ex)
{
SelfLog.WriteLine(
"Event at {0} with message template {1} could not be formatted into JSON for Seq and will be dropped: {2}",
logEvent.Timestamp.ToString("o"), logEvent.MessageTemplate.Text, ex);

LogNonFormattableEvent(logEvent, ex);
continue;
}

var json = buffer.ToString();
if (CheckEventBodySize(json, eventBodyLimitBytes))
{
payload.WriteLine(json);
}
}

return payload.ToString();
}

if (eventBodyLimitBytes.HasValue &&
Encoding.UTF8.GetByteCount(json) > eventBodyLimitBytes.Value)
internal static string FormatRawPayload(IEnumerable<LogEvent> events, long? eventBodyLimitBytes)
{
var payload = new StringWriter();
payload.Write("{\"Events\":[");

var delimStart = "";
foreach (var logEvent in events)
{
var buffer = new StringWriter();

try
{
SelfLog.WriteLine(
"Event JSON representation exceeds the byte size limit of {0} set for this Seq sink and will be dropped; data: {1}",
eventBodyLimitBytes, json);
RawJsonFormatter.FormatContent(logEvent, buffer);
}
else
catch (Exception ex)
{
LogNonFormattableEvent(logEvent, ex);
continue;
}

var json = buffer.ToString();
if (CheckEventBodySize(json, eventBodyLimitBytes))
{
payload.Write(delimStart);
payload.Write(json);
Expand All @@ -165,5 +197,26 @@ protected override bool CanInclude(LogEvent evt)
return levelControlSwitch == null ||
(int)levelControlSwitch.MinimumLevel <= (int)evt.Level;
}

static bool CheckEventBodySize(string json, long? eventBodyLimitBytes)
{
if (eventBodyLimitBytes.HasValue &&
Encoding.UTF8.GetByteCount(json) > eventBodyLimitBytes.Value)
{
SelfLog.WriteLine(
"Event JSON representation exceeds the byte size limit of {0} set for this Seq sink and will be dropped; data: {1}",
eventBodyLimitBytes, json);
return false;
}

return true;
}

static void LogNonFormattableEvent(LogEvent logEvent, Exception ex)
{
SelfLog.WriteLine(
"Event at {0} with message template {1} could not be formatted into JSON for Seq and will be dropped: {2}",
logEvent.Timestamp.ToString("o"), logEvent.MessageTemplate.Text, ex);
}
}
}
5 changes: 3 additions & 2 deletions src/Serilog.Sinks.Seq/project.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "2.0.1-*",
"version": "3.0.0-*",
"description": "Serilog sink that writes to the Seq event server over HTTP/S.",
"authors": [ "Serilog Contributors" ],
"packOptions": {
Expand All @@ -10,7 +10,8 @@
},
"dependencies": {
"Serilog": "2.0.0",
"Serilog.Sinks.PeriodicBatching": "2.0.0"
"Serilog.Sinks.PeriodicBatching": "2.0.0",
"Serilog.Formatting.Compact": "1.0.0"
},
"buildOptions": {
"keyFile": "../../assets/Serilog.snk"
Expand Down
20 changes: 18 additions & 2 deletions test/Serilog.Sinks.Seq.Tests/SeqSinkTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,32 @@ public class SeqSinkTests
public void EventsAreFormattedIntoJsonPayloads()
{
var evt = Some.LogEvent("Hello, {Name}!", "Alice");
var json = SeqSink.FormatPayload(new[] {evt}, null);
var json = SeqSink.FormatRawPayload(new[] {evt}, null);
Assert.Contains("Name\":\"Alice", json);
}

[Fact]
public void EventsAreDroppedWhenJsonRenderingFails()
{
var evt = Some.LogEvent(new NastyException(), "Hello, {Name}!", "Alice");
var json = SeqSink.FormatPayload(new[] { evt }, null);
var json = SeqSink.FormatRawPayload(new[] { evt }, null);
Assert.Contains("[]", json);
}

[Fact]
public void EventsAreFormattedIntoCompactJsonPayloads()
{
var evt = Some.LogEvent("Hello, {Name}!", "Alice");
var json = SeqSink.FormatCompactPayload(new[] { evt }, null);
Assert.Contains("Name\":\"Alice", json);
}

[Fact]
public void EventsAreDroppedWhenCompactJsonRenderingFails()
{
var evt = Some.LogEvent(new NastyException(), "Hello, {Name}!", "Alice");
var json = SeqSink.FormatCompactPayload(new[] { evt }, null);
Assert.Empty(json);
}
}
}

0 comments on commit a20505b

Please sign in to comment.