diff --git a/CHANGELOG.md b/CHANGELOG.md
index cbc20659..7ee3c49b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](http://semver.org/) and is followi
## Unreleased
+### Added
+
+- Support for the formatting types: `FormattingType.NormalRendered`, `FormattingType.Normal`, `FormattingType.CompactRendered` and `FormattingType.Compact`. The formatting type can be configured via `Options` and `DurableOptions`.
+
## 3.0.0 2017-03-04
### Added
diff --git a/README.md b/README.md
index 95dd38a2..1ff8706f 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ The sink is batching multiple log events into a single request, and the followin
"events": [
{
"Timestamp": "2016-11-03T00:09:11.4899425+01:00",
- "Level": "Debug",
+ "Level": "Information",
"MessageTemplate": "Logging {@Heartbeat} from {Computer}",
"RenderedMessage": "Logging { UserName: \"Mike\", UserDomainName: \"Home\" } from \"Workstation\"",
"Properties": {
@@ -38,7 +38,7 @@ The sink is batching multiple log events into a single request, and the followin
},
{
"Timestamp": "2016-11-03T00:09:12.4905685+01:00",
- "Level": "Debug",
+ "Level": "Information",
"MessageTemplate": "Logging {@Heartbeat} from {Computer}",
"RenderedMessage": "Logging { UserName: \"Mike\", UserDomainName: \"Home\" } from \"Workstation\"",
"Properties": {
@@ -71,6 +71,96 @@ The log events can be sent directly to Elasticsearch using [Serilog.Sinks.Elasti
If you would like to send the log events to Logstash for further processing instead of sending them directly to Elasticsearch, this sink in combination with the [Logstash HTTP input plugin](https://www.elastic.co/blog/introducing-logstash-input-http-plugin) is the perfect match for you. It is a much better solution than having to install [Filebeat](https://www.elastic.co/products/beats/filebeat) on all your instances, mainly because it involves fewer moving parts.
+### Formatting types
+
+#### FormattingType.NormalRendered
+
+The log event is normally formatted and the message template is rendered into a message. This is the most verbose formatting type and its network load is higher than the other options.
+
+Example:
+```json
+{
+ "Timestamp": "2016-11-03T00:09:11.4899425+01:00",
+ "Level": "Information",
+ "MessageTemplate": "Logging {@Heartbeat} from {Computer}",
+ "RenderedMessage": "Logging { UserName: \"Mike\", UserDomainName: \"Home\" } from \"Workstation\"",
+ "Properties": {
+ "Heartbeat": {
+ "UserName": "Mike",
+ "UserDomainName": "Home"
+ },
+ "Computer": "Workstation"
+ }
+}
+```
+
+#### FormattingType.Normal
+
+ The log event is normally formatted and its data normalized. The lack of a rendered message means improved network load compared to `FormattingType.NormalRendered`. Often this formatting type is complemented with a log server that is capable of rendering the messages of the incoming log events.
+
+Example:
+```json
+{
+ "Timestamp": "2016-11-03T00:09:11.4899425+01:00",
+ "Level": "Information",
+ "MessageTemplate": "Logging {@Heartbeat} from {Computer}",
+ "Properties": {
+ "Heartbeat": {
+ "UserName": "Mike",
+ "UserDomainName": "Home"
+ },
+ "Computer": "Workstation"
+ }
+}
+```
+
+#### FormattingType.CompactRendered
+
+The log event is formatted with minimizing size as a priority but still render the message template into a message. This formatting type greatly reduce the network load and should be used in situations where bandwidth is of importance.
+
+The compact formatter adheres to the following rules:
+
+- Built-in field names are short and prefixed with an `@`
+- The `Properties` property is flattened
+- The Information level is omitted since it is considered to be the default
+
+Example:
+```json
+{
+ "@t": "2016-11-03T00:09:11.4899425+01:00",
+ "@mt": "Logging {@Heartbeat} from {Computer}",
+ "@m":"Logging { UserName: \"Mike\", UserDomainName: \"Home\" } from \"Workstation\"",
+ "Heartbeat": {
+ "UserName": "Mike",
+ "UserDomainName": "Home"
+ },
+ "Computer": "Workstation"
+}
+```
+
+#### FormattingType.Compact
+
+The log event is formatted with minimizing size as a priority and its data is normalized. The lack of a rendered message means even smaller network load compared to `FormattingType.CompactRendered` and should be used in situations where bandwidth is of importance. Often this formatting type is complemented with a log server that is capable of rendering the messages of the incoming log events.
+
+The compact formatter adheres to the following rules:
+
+- Built-in field names are short and prefixed with an `@`
+- The `Properties` property is flattened
+- The Information level is omitted since it is considered to be the default
+
+Example:
+```json
+{
+ "@t": "2016-11-03T00:09:11.4899425+01:00",
+ "@mt": "Logging {@Heartbeat} from {Computer}",
+ "Heartbeat": {
+ "UserName": "Mike",
+ "UserDomainName": "Home"
+ },
+ "Computer": "Workstation"
+}
+```
+
### Install via NuGet
If you want to include the HTTP POST sink in your project, you can [install it directly from NuGet](https://www.nuget.org/packages/Serilog.Sinks.Http/).
diff --git a/src/Serilog.Sinks.Http/LoggerSinkConfigurationExtensions.cs b/src/Serilog.Sinks.Http/LoggerSinkConfigurationExtensions.cs
index b9b32a1e..eb2532c4 100644
--- a/src/Serilog.Sinks.Http/LoggerSinkConfigurationExtensions.cs
+++ b/src/Serilog.Sinks.Http/LoggerSinkConfigurationExtensions.cs
@@ -17,7 +17,8 @@
using Serilog.Configuration;
using Serilog.Events;
using Serilog.Sinks.Http;
-using Serilog.Sinks.Http.Private;
+using Serilog.Sinks.Http.Private.Http;
+using Serilog.Sinks.Http.Private.Sinks;
namespace Serilog
{
diff --git a/src/Serilog.Sinks.Http/Sinks/Http/FormattingType.cs b/src/Serilog.Sinks.Http/Sinks/Http/FormattingType.cs
new file mode 100644
index 00000000..3e671a51
--- /dev/null
+++ b/src/Serilog.Sinks.Http/Sinks/Http/FormattingType.cs
@@ -0,0 +1,53 @@
+// Copyright 2015-2016 Serilog Contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+namespace Serilog.Sinks.Http
+{
+ ///
+ /// Enum defining how log events are formatted when sent over the network.
+ ///
+ public enum FormattingType
+ {
+ ///
+ /// The log event is normally formatted and the message template is rendered into a message.
+ /// This is the most verbose formatting type and its network load is higher than the other
+ /// options.
+ ///
+ NormalRendered,
+
+ ///
+ /// The log event is normally formatted and its data normalized. The lack of a rendered message
+ /// means improved network load compared to . Often this formatting
+ /// type is complemented with a log server that is capable of rendering the messages of the
+ /// incoming log events.
+ ///
+ Normal,
+
+ ///
+ /// The log event is formatted with minimizing size as a priority but still render the message
+ /// template into a message. This formatting type greatly reduce the network load and should be
+ /// used in situations where bandwidth is of importance.
+ ///
+ CompactRendered,
+
+ ///
+ /// The log event is formatted with minimizing size as a priority and its data is normalized. The
+ /// lack of a rendered message means even smaller network load compared to
+ /// and should be used in situations where bandwidth is of
+ /// importance. Often this formatting type is complemented with a log server that is capable of
+ /// rendering the messages of the incoming log events.
+ ///
+ Compact
+ }
+}
diff --git a/src/Serilog.Sinks.Http/Sinks/Http/Options.cs b/src/Serilog.Sinks.Http/Sinks/Http/Options.cs
index cfb8cd05..b1726a10 100644
--- a/src/Serilog.Sinks.Http/Sinks/Http/Options.cs
+++ b/src/Serilog.Sinks.Http/Sinks/Http/Options.cs
@@ -39,5 +39,11 @@ public class Options
/// Default value is 265 KB.
///
public long? EventBodyLimitBytes { get; set; } = 256 * 1024;
+
+ ///
+ /// Gets or sets the formatting type. Default value is
+ /// .
+ ///
+ public FormattingType FormattingType { get; set; } = FormattingType.NormalRendered;
}
}
diff --git a/src/Serilog.Sinks.Http/Sinks/Http/Private/Formatters/CompactJsonFormatter.cs b/src/Serilog.Sinks.Http/Sinks/Http/Private/Formatters/CompactJsonFormatter.cs
new file mode 100644
index 00000000..8405e8b3
--- /dev/null
+++ b/src/Serilog.Sinks.Http/Sinks/Http/Private/Formatters/CompactJsonFormatter.cs
@@ -0,0 +1,134 @@
+// Copyright 2015-2016 Serilog Contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
+using System.IO;
+using System.Linq;
+using Serilog.Debugging;
+using Serilog.Events;
+using Serilog.Formatting;
+using Serilog.Formatting.Json;
+using Serilog.Parsing;
+
+namespace Serilog.Sinks.Http.Private.Formatters
+{
+ internal class CompactJsonFormatter : ITextFormatter
+ {
+ private static readonly JsonValueFormatter ValueFormatter = new JsonValueFormatter();
+
+ private readonly bool isRenderingMessage;
+
+ public CompactJsonFormatter(bool isRenderingMessage)
+ {
+ this.isRenderingMessage = isRenderingMessage;
+ }
+
+ public void Format(LogEvent logEvent, TextWriter output)
+ {
+ try
+ {
+ var buffer = new StringWriter();
+ FormatContent(logEvent, buffer);
+
+ // If formatting was successful, write to output
+ output.WriteLine(buffer.ToString());
+ }
+ catch (Exception e)
+ {
+ LogNonFormattableEvent(logEvent, e);
+ }
+ }
+
+ private void FormatContent(LogEvent logEvent, TextWriter output)
+ {
+ if (logEvent == null)
+ throw new ArgumentNullException(nameof(logEvent));
+ if (output == null)
+ throw new ArgumentNullException(nameof(output));
+
+ output.Write("{\"@t\":\"");
+ output.Write(logEvent.Timestamp.UtcDateTime.ToString("o"));
+
+ output.Write("\",\"@mt\":");
+ JsonValueFormatter.WriteQuotedJsonString(logEvent.MessageTemplate.Text, output);
+
+ if (isRenderingMessage)
+ {
+ output.Write(",\"@m\":");
+ var message = logEvent.MessageTemplate.Render(logEvent.Properties);
+ JsonValueFormatter.WriteQuotedJsonString(message, output);
+ }
+
+ var tokensWithFormat = logEvent.MessageTemplate.Tokens
+ .OfType()
+ .Where(pt => pt.Format != null);
+
+ // Better not to allocate an array in the 99.9% of cases where this is false
+ // ReSharper disable once PossibleMultipleEnumeration
+ if (tokensWithFormat.Any())
+ {
+ output.Write(",\"@r\":[");
+ var delim = "";
+ foreach (var r in tokensWithFormat)
+ {
+ output.Write(delim);
+ delim = ",";
+ var space = new StringWriter();
+ r.Render(logEvent.Properties, space);
+ JsonValueFormatter.WriteQuotedJsonString(space.ToString(), output);
+ }
+ output.Write(']');
+ }
+
+ if (logEvent.Level != LogEventLevel.Information)
+ {
+ output.Write(",\"@l\":\"");
+ output.Write(logEvent.Level);
+ output.Write('\"');
+ }
+
+ if (logEvent.Exception != null)
+ {
+ output.Write(",\"@x\":");
+ JsonValueFormatter.WriteQuotedJsonString(logEvent.Exception.ToString(), output);
+ }
+
+ foreach (var property in logEvent.Properties)
+ {
+ var name = property.Key;
+ if (name.Length > 0 && name[0] == '@')
+ {
+ // Escape first '@' by doubling
+ name = '@' + name;
+ }
+
+ output.Write(',');
+ JsonValueFormatter.WriteQuotedJsonString(name, output);
+ output.Write(':');
+ ValueFormatter.Format(property.Value, output);
+ }
+
+ output.Write('}');
+ }
+
+ private static void LogNonFormattableEvent(LogEvent logEvent, Exception e)
+ {
+ SelfLog.WriteLine(
+ "Event at {0} with message template {1} could not be formatted into JSON and will be dropped: {2}",
+ logEvent.Timestamp.ToString("o"),
+ logEvent.MessageTemplate.Text,
+ e);
+ }
+ }
+}
diff --git a/src/Serilog.Sinks.Http/Sinks/Http/Private/Formatters/Converter.cs b/src/Serilog.Sinks.Http/Sinks/Http/Private/Formatters/Converter.cs
new file mode 100644
index 00000000..b6e4f21a
--- /dev/null
+++ b/src/Serilog.Sinks.Http/Sinks/Http/Private/Formatters/Converter.cs
@@ -0,0 +1,43 @@
+// Copyright 2015-2016 Serilog Contributors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
+using Serilog.Formatting;
+
+namespace Serilog.Sinks.Http.Private.Formatters
+{
+ internal static class Converter
+ {
+ public static ITextFormatter ToFormatter(FormattingType formattingType)
+ {
+ switch (formattingType)
+ {
+ case FormattingType.NormalRendered:
+ return new NormalJsonFormatter(true);
+
+ case FormattingType.Normal:
+ return new NormalJsonFormatter(false);
+
+ case FormattingType.CompactRendered:
+ return new CompactJsonFormatter(true);
+
+ case FormattingType.Compact:
+ return new CompactJsonFormatter(false);
+
+ default:
+ throw new ArgumentException($"Formatting type {formattingType} is not supported");
+ }
+ }
+ }
+}
diff --git a/src/Serilog.Sinks.Http/Sinks/Http/Private/HttpJsonFormatter.cs b/src/Serilog.Sinks.Http/Sinks/Http/Private/Formatters/NormalJsonFormatter.cs
similarity index 83%
rename from src/Serilog.Sinks.Http/Sinks/Http/Private/HttpJsonFormatter.cs
rename to src/Serilog.Sinks.Http/Sinks/Http/Private/Formatters/NormalJsonFormatter.cs
index 9ace5a01..363a3b2e 100644
--- a/src/Serilog.Sinks.Http/Sinks/Http/Private/HttpJsonFormatter.cs
+++ b/src/Serilog.Sinks.Http/Sinks/Http/Private/Formatters/NormalJsonFormatter.cs
@@ -22,12 +22,20 @@
using Serilog.Formatting.Json;
using Serilog.Parsing;
-namespace Serilog.Sinks.Http.Private
+namespace Serilog.Sinks.Http.Private.Formatters
{
- internal class HttpJsonFormatter : ITextFormatter
+ internal class NormalJsonFormatter : ITextFormatter
{
private static readonly JsonValueFormatter ValueFormatter = new JsonValueFormatter();
+
+ private readonly bool isRenderingMessage;
+
+ public NormalJsonFormatter(bool isRenderingMessage)
+ {
+ this.isRenderingMessage = isRenderingMessage;
+ }
+
public void Format(LogEvent logEvent, TextWriter output)
{
try
@@ -44,7 +52,7 @@ public void Format(LogEvent logEvent, TextWriter output)
}
}
- private static void FormatContent(LogEvent logEvent, TextWriter output)
+ private void FormatContent(LogEvent logEvent, TextWriter output)
{
if (logEvent == null)
throw new ArgumentNullException(nameof(logEvent));
@@ -60,6 +68,14 @@ private static void FormatContent(LogEvent logEvent, TextWriter output)
output.Write("\",\"MessageTemplate\":");
JsonValueFormatter.WriteQuotedJsonString(logEvent.MessageTemplate.Text, output);
+ if (isRenderingMessage)
+ {
+ output.Write(",\"RenderedMessage\":");
+
+ var message = logEvent.MessageTemplate.Render(logEvent.Properties);
+ JsonValueFormatter.WriteQuotedJsonString(message, output);
+ }
+
if (logEvent.Exception != null)
{
output.Write(",\"Exception\":");
@@ -71,13 +87,15 @@ private static void FormatContent(LogEvent logEvent, TextWriter output)
WriteProperties(logEvent.Properties, output);
}
+ // Better not to allocate an array in the 99.9% of cases where this is false
var tokensWithFormat = logEvent.MessageTemplate.Tokens
.OfType()
- .Where(pt => pt.Format != null)
- .ToArray();
+ .Where(pt => pt.Format != null);
+ // ReSharper disable once PossibleMultipleEnumeration
if (tokensWithFormat.Any())
{
+ // ReSharper disable once PossibleMultipleEnumeration
WriteRenderings(tokensWithFormat.GroupBy(pt => pt.PropertyName), logEvent.Properties, output);
}
diff --git a/src/Serilog.Sinks.Http/Sinks/Http/Private/HttpClientWrapper.cs b/src/Serilog.Sinks.Http/Sinks/Http/Private/Http/HttpClientWrapper.cs
similarity index 96%
rename from src/Serilog.Sinks.Http/Sinks/Http/Private/HttpClientWrapper.cs
rename to src/Serilog.Sinks.Http/Sinks/Http/Private/Http/HttpClientWrapper.cs
index 58928ec7..2408486f 100644
--- a/src/Serilog.Sinks.Http/Sinks/Http/Private/HttpClientWrapper.cs
+++ b/src/Serilog.Sinks.Http/Sinks/Http/Private/Http/HttpClientWrapper.cs
@@ -15,7 +15,7 @@
using System.Net.Http;
using System.Threading.Tasks;
-namespace Serilog.Sinks.Http.Private
+namespace Serilog.Sinks.Http.Private.Http
{
internal class HttpClientWrapper : IHttpClient
{
diff --git a/src/Serilog.Sinks.Http/Sinks/Http/Private/HttpLogShipper.cs b/src/Serilog.Sinks.Http/Sinks/Http/Private/Http/HttpLogShipper.cs
similarity index 99%
rename from src/Serilog.Sinks.Http/Sinks/Http/Private/HttpLogShipper.cs
rename to src/Serilog.Sinks.Http/Sinks/Http/Private/Http/HttpLogShipper.cs
index ef990734..a34a5013 100644
--- a/src/Serilog.Sinks.Http/Sinks/Http/Private/HttpLogShipper.cs
+++ b/src/Serilog.Sinks.Http/Sinks/Http/Private/Http/HttpLogShipper.cs
@@ -17,14 +17,15 @@
using System.Linq;
using System.Net.Http;
using System.Text;
+using System.Threading.Tasks;
using Serilog.Debugging;
+using Serilog.Sinks.Http.Private.Time;
using IOFile = System.IO.File;
-using System.Threading.Tasks;
#if HRESULTS
using System.Runtime.InteropServices;
#endif
-namespace Serilog.Sinks.Http.Private
+namespace Serilog.Sinks.Http.Private.Http
{
internal class HttpLogShipper : IDisposable
{
diff --git a/src/Serilog.Sinks.Http/Sinks/Http/Private/DurableHttpSink.cs b/src/Serilog.Sinks.Http/Sinks/Http/Private/Sinks/DurableHttpSink.cs
similarity index 90%
rename from src/Serilog.Sinks.Http/Sinks/Http/Private/DurableHttpSink.cs
rename to src/Serilog.Sinks.Http/Sinks/Http/Private/Sinks/DurableHttpSink.cs
index bb3aa602..fe43e68a 100644
--- a/src/Serilog.Sinks.Http/Sinks/Http/Private/DurableHttpSink.cs
+++ b/src/Serilog.Sinks.Http/Sinks/Http/Private/Sinks/DurableHttpSink.cs
@@ -16,9 +16,11 @@
using System.Text;
using Serilog.Core;
using Serilog.Events;
+using Serilog.Sinks.Http.Private.Formatters;
+using Serilog.Sinks.Http.Private.Http;
using Serilog.Sinks.RollingFile;
-namespace Serilog.Sinks.Http.Private
+namespace Serilog.Sinks.Http.Private.Sinks
{
internal class DurableHttpSink : ILogEventSink, IDisposable
{
@@ -43,7 +45,7 @@ public DurableHttpSink(
sink = new RollingFileSink(
options.BufferBaseFilename + "-{Date}.json",
- new HttpJsonFormatter(),
+ Converter.ToFormatter(options.FormattingType),
options.BufferFileSizeLimitBytes,
null,
Encoding.UTF8);
diff --git a/src/Serilog.Sinks.Http/Sinks/Http/Private/HttpSink.cs b/src/Serilog.Sinks.Http/Sinks/Http/Private/Sinks/HttpSink.cs
similarity index 95%
rename from src/Serilog.Sinks.Http/Sinks/Http/Private/HttpSink.cs
rename to src/Serilog.Sinks.Http/Sinks/Http/Private/Sinks/HttpSink.cs
index 651134d4..d09b7fa6 100644
--- a/src/Serilog.Sinks.Http/Sinks/Http/Private/HttpSink.cs
+++ b/src/Serilog.Sinks.Http/Sinks/Http/Private/Sinks/HttpSink.cs
@@ -21,9 +21,10 @@
using Serilog.Debugging;
using Serilog.Events;
using Serilog.Formatting;
+using Serilog.Sinks.Http.Private.Formatters;
using Serilog.Sinks.PeriodicBatching;
-namespace Serilog.Sinks.Http.Private
+namespace Serilog.Sinks.Http.Private.Sinks
{
internal class HttpSink : PeriodicBatchingSink
{
@@ -52,7 +53,7 @@ public HttpSink(
this.requestUri = requestUri;
this.options = options;
- formatter = new HttpJsonFormatter();
+ formatter = Converter.ToFormatter(options.FormattingType);
}
protected override async Task EmitBatchAsync(IEnumerable events)
diff --git a/src/Serilog.Sinks.Http/Sinks/Http/Private/ExponentialBackoffConnectionSchedule.cs b/src/Serilog.Sinks.Http/Sinks/Http/Private/Time/ExponentialBackoffConnectionSchedule.cs
similarity index 98%
rename from src/Serilog.Sinks.Http/Sinks/Http/Private/ExponentialBackoffConnectionSchedule.cs
rename to src/Serilog.Sinks.Http/Sinks/Http/Private/Time/ExponentialBackoffConnectionSchedule.cs
index 7083d898..fc0e5ba1 100644
--- a/src/Serilog.Sinks.Http/Sinks/Http/Private/ExponentialBackoffConnectionSchedule.cs
+++ b/src/Serilog.Sinks.Http/Sinks/Http/Private/Time/ExponentialBackoffConnectionSchedule.cs
@@ -14,7 +14,7 @@
using System;
-namespace Serilog.Sinks.Http.Private
+namespace Serilog.Sinks.Http.Private.Time
{
internal class ExponentialBackoffConnectionSchedule
{
diff --git a/src/Serilog.Sinks.Http/Sinks/Http/Private/PortableTimer.cs b/src/Serilog.Sinks.Http/Sinks/Http/Private/Time/PortableTimer.cs
similarity index 98%
rename from src/Serilog.Sinks.Http/Sinks/Http/Private/PortableTimer.cs
rename to src/Serilog.Sinks.Http/Sinks/Http/Private/Time/PortableTimer.cs
index 98bfb320..70c9890b 100644
--- a/src/Serilog.Sinks.Http/Sinks/Http/Private/PortableTimer.cs
+++ b/src/Serilog.Sinks.Http/Sinks/Http/Private/Time/PortableTimer.cs
@@ -16,7 +16,7 @@
using System.Threading;
using System.Threading.Tasks;
-namespace Serilog.Sinks.Http.Private
+namespace Serilog.Sinks.Http.Private.Time
{
internal class PortableTimer : IDisposable
{
diff --git a/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/BatchesController.cs b/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/BatchesController.cs
index c93c1be8..dcbf39fb 100644
--- a/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/BatchesController.cs
+++ b/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/BatchesController.cs
@@ -1,5 +1,6 @@
using System.Linq;
using Microsoft.AspNetCore.Mvc;
+using Serilog.Sinks.Http.IntegrationTests.Server.Controllers.Dtos;
namespace Serilog.Sinks.Http.IntegrationTests.Server.Controllers
{
diff --git a/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/Dtos/CompactEventDto.cs b/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/Dtos/CompactEventDto.cs
new file mode 100644
index 00000000..227f9d5d
--- /dev/null
+++ b/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/Dtos/CompactEventDto.cs
@@ -0,0 +1,26 @@
+using System;
+using Newtonsoft.Json;
+
+namespace Serilog.Sinks.Http.IntegrationTests.Server.Controllers.Dtos
+{
+ public class CompactEventDto
+ {
+ [JsonProperty("@t")]
+ public DateTime Timestamp { get; set; }
+
+ [JsonProperty("@l")]
+ public string Level { get; set; }
+
+ [JsonProperty("@mt")]
+ public string MessageTemplate { get; set; }
+
+ [JsonProperty("@m")]
+ public string RenderedMessage { get; set; }
+
+ [JsonProperty("@x")]
+ public string Exception { get; set; }
+
+ [JsonProperty("@r")]
+ public string[] Renderings { get; set; }
+ }
+}
diff --git a/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/EventBatchRequestDto.cs b/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/Dtos/EventBatchRequestDto.cs
similarity index 97%
rename from test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/EventBatchRequestDto.cs
rename to test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/Dtos/EventBatchRequestDto.cs
index 730432a5..b53e0511 100644
--- a/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/EventBatchRequestDto.cs
+++ b/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/Dtos/EventBatchRequestDto.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
-namespace Serilog.Sinks.Http.IntegrationTests.Server.Controllers
+namespace Serilog.Sinks.Http.IntegrationTests.Server.Controllers.Dtos
{
public class EventBatchRequestDto
{
diff --git a/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/EventDto.cs b/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/Dtos/EventDto.cs
similarity index 75%
rename from test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/EventDto.cs
rename to test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/Dtos/EventDto.cs
index 6cd0c2be..54bf354f 100644
--- a/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/EventDto.cs
+++ b/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/Dtos/EventDto.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
-namespace Serilog.Sinks.Http.IntegrationTests.Server.Controllers
+namespace Serilog.Sinks.Http.IntegrationTests.Server.Controllers.Dtos
{
public class EventDto
{
@@ -11,8 +11,12 @@ public class EventDto
public string MessageTemplate { get; set; }
- public Dictionary Properties { get; set; }
+ public string RenderedMessage { get; set; }
public string Exception { get; set; }
+
+ public Dictionary Properties { get; set; }
+
+ public Dictionary Renderings { get; set; }
}
}
\ No newline at end of file
diff --git a/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/Dtos/RenderingDto.cs b/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/Dtos/RenderingDto.cs
new file mode 100644
index 00000000..2e34e3c1
--- /dev/null
+++ b/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/Dtos/RenderingDto.cs
@@ -0,0 +1,26 @@
+namespace Serilog.Sinks.Http.IntegrationTests.Server.Controllers.Dtos
+{
+ public class RenderingDto
+ {
+ public string Format { get; set; }
+
+ public string Rendering { get; set; }
+
+ public override bool Equals(object obj)
+ {
+ var other = obj as RenderingDto;
+
+ if (other == null)
+ return false;
+
+ return
+ Format == other.Format &&
+ Rendering == other.Rendering;
+ }
+
+ public override int GetHashCode()
+ {
+ return 0;
+ }
+ }
+}
diff --git a/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/EventsController.cs b/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/EventsController.cs
index ee8a412f..f9f3ac4f 100644
--- a/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/EventsController.cs
+++ b/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/EventsController.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
+using Serilog.Sinks.Http.IntegrationTests.Server.Controllers.Dtos;
namespace Serilog.Sinks.Http.IntegrationTests.Server.Controllers
{
diff --git a/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/PayloadConvert.cs b/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/PayloadConvert.cs
index 06ee7818..5d6431a9 100644
--- a/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/PayloadConvert.cs
+++ b/test/Serilog.Sinks.Http.IntegrationTests.Server/Controllers/PayloadConvert.cs
@@ -1,4 +1,6 @@
-namespace Serilog.Sinks.Http.IntegrationTests.Server.Controllers
+using Serilog.Sinks.Http.IntegrationTests.Server.Controllers.Dtos;
+
+namespace Serilog.Sinks.Http.IntegrationTests.Server.Controllers
{
public static class PayloadConvert
{
@@ -9,6 +11,7 @@ public static Event FromDto(EventDto @event)
@event.Level,
@event.MessageTemplate,
@event.Properties,
+ @event.RenderedMessage,
@event.Exception);
}
@@ -20,6 +23,7 @@ public static EventDto ToDto(Event @event)
Level = @event.Level,
MessageTemplate = @event.MessageTemplate,
Properties = @event.Properties,
+ RenderedMessage = @event.RenderedMessage,
Exception = @event.Exception
};
}
diff --git a/test/Serilog.Sinks.Http.IntegrationTests.Server/Event.cs b/test/Serilog.Sinks.Http.IntegrationTests.Server/Event.cs
index d0aa81e3..e6b5cb75 100644
--- a/test/Serilog.Sinks.Http.IntegrationTests.Server/Event.cs
+++ b/test/Serilog.Sinks.Http.IntegrationTests.Server/Event.cs
@@ -10,12 +10,14 @@ public Event(
string level,
string messageTemplate,
Dictionary properties,
+ string renderedMessage,
string exception)
{
Timestamp = timestamp;
Level = level;
MessageTemplate = messageTemplate;
Properties = properties;
+ RenderedMessage = renderedMessage;
Exception = exception;
}
@@ -25,6 +27,8 @@ public Event(
public string MessageTemplate { get; }
+ public string RenderedMessage { get; set; }
+
public Dictionary Properties { get; }
public string Exception { get; set; }
diff --git a/test/Serilog.Sinks.Http.IntegrationTests.Server/project.json b/test/Serilog.Sinks.Http.IntegrationTests.Server/project.json
index e5eab286..9178f916 100644
--- a/test/Serilog.Sinks.Http.IntegrationTests.Server/project.json
+++ b/test/Serilog.Sinks.Http.IntegrationTests.Server/project.json
@@ -1,12 +1,15 @@
{
"version": "1.0.0-*",
- "dependencies": {
- "NETStandard.Library": "1.6.0",
- "Microsoft.AspNetCore.Mvc": "1.0.1",
- "Microsoft.AspNetCore.Routing": "1.0.1",
- "Microsoft.Extensions.Configuration.Json": "1.0.0",
- "Microsoft.Extensions.Logging.Debug": "1.0.0",
- "Newtonsoft.Json": "9.0.1"
+ "dependencies": {
+ "NETStandard.Library": "1.6.0",
+ "Microsoft.AspNetCore.Mvc": "1.0.1",
+ "Microsoft.AspNetCore.Routing": "1.0.1",
+ "Microsoft.Extensions.Configuration.Json": "1.0.0",
+ "Microsoft.Extensions.Logging.Debug": "1.0.0",
+ "Newtonsoft.Json": "9.0.1"
+ },
+ "buildOptions": {
+ "keyFile": "../../Serilog.snk"
},
"frameworks": {
"netstandard1.6": {
diff --git a/test/Serilog.Sinks.Http.IntegrationTests/ApiModels/ApiModel.cs b/test/Serilog.Sinks.Http.IntegrationTests/ApiModels/ApiModel.cs
index 530d2aeb..e5b87784 100644
--- a/test/Serilog.Sinks.Http.IntegrationTests/ApiModels/ApiModel.cs
+++ b/test/Serilog.Sinks.Http.IntegrationTests/ApiModels/ApiModel.cs
@@ -8,6 +8,7 @@
using Polly;
using Serilog.Sinks.Http.IntegrationTests.Server;
using Serilog.Sinks.Http.IntegrationTests.Server.Controllers;
+using Serilog.Sinks.Http.IntegrationTests.Server.Controllers.Dtos;
using Xunit;
using Xunit.Sdk;
diff --git a/test/Serilog.Sinks.Http.IntegrationTests/HttpSinkTest.cs b/test/Serilog.Sinks.Http.IntegrationTests/HttpSinkTest.cs
index 8b4f2e17..b9b0e9fe 100644
--- a/test/Serilog.Sinks.Http.IntegrationTests/HttpSinkTest.cs
+++ b/test/Serilog.Sinks.Http.IntegrationTests/HttpSinkTest.cs
@@ -3,7 +3,7 @@
namespace Serilog
{
- public class HttpSinkTest : SinkFixture
+ public class HttpSinkTest : SinkFixture
{
public HttpSinkTest()
{
diff --git a/test/Serilog.Sinks.Http.IntegrationTests/SinkFixture.cs b/test/Serilog.Sinks.Http.IntegrationTests/SinkFixture.cs
index 984a976d..aba624af 100644
--- a/test/Serilog.Sinks.Http.IntegrationTests/SinkFixture.cs
+++ b/test/Serilog.Sinks.Http.IntegrationTests/SinkFixture.cs
@@ -8,7 +8,7 @@
namespace Serilog
{
- public abstract class SinkFixture : TestServerFixture
+ public abstract class SinkFixture : TestServerFixture
{
protected SinkFixture()
{
@@ -69,6 +69,7 @@ public async Task Payload()
Assert.Equal(expected.Level.ToString(), @event.Level);
Assert.Equal(expected.MessageTemplate.Text, @event.MessageTemplate);
Assert.Equal(expected.Properties["Name"].ToString().Trim('"'), @event.Properties["Name"]);
+ Assert.Equal("Hello, \"Alice\"!", @event.RenderedMessage);
Assert.Null(@event.Exception);
}
@@ -87,6 +88,7 @@ public async Task Exception()
Assert.Equal(expected.Timestamp, @event.Timestamp);
Assert.Equal(expected.Level.ToString(), @event.Level);
Assert.Equal(expected.MessageTemplate.Text, @event.MessageTemplate);
+ Assert.Equal("Some error message", @event.RenderedMessage);
Assert.Equal(expected.Exception.ToString(), @event.Exception);
}
diff --git a/test/Serilog.Sinks.Http.Tests/Sinks/Http/Private/Formatters/CompactJsonFormatterTest.cs b/test/Serilog.Sinks.Http.Tests/Sinks/Http/Private/Formatters/CompactJsonFormatterTest.cs
new file mode 100644
index 00000000..c015e858
--- /dev/null
+++ b/test/Serilog.Sinks.Http.Tests/Sinks/Http/Private/Formatters/CompactJsonFormatterTest.cs
@@ -0,0 +1,233 @@
+using System;
+using System.IO;
+using Newtonsoft.Json;
+using Serilog.Events;
+using Serilog.Formatting;
+using Serilog.Sinks.Http.IntegrationTests.Server.Controllers.Dtos;
+using Serilog.Support;
+using Xunit;
+
+namespace Serilog.Sinks.Http.Private.Formatters
+{
+ public class CompactJsonFormatterTest
+ {
+ private readonly StringWriter output;
+
+ private ILogger logger;
+
+ public CompactJsonFormatterTest()
+ {
+ output = new StringWriter();
+ }
+
+ [Theory]
+ [InlineData(LogEventLevel.Verbose)]
+ [InlineData(LogEventLevel.Debug)]
+ [InlineData(LogEventLevel.Information)]
+ [InlineData(LogEventLevel.Warning)]
+ [InlineData(LogEventLevel.Error)]
+ [InlineData(LogEventLevel.Fatal)]
+ public void LogEventLevels(LogEventLevel level)
+ {
+ // Arrange
+ logger = CreateLogger(new CompactJsonFormatter(true));
+
+ // Act
+ logger.Write(level, "No properties");
+
+ // Assert
+ var @event = GetEvent();
+
+ if (level == LogEventLevel.Information)
+ {
+ Assert.Null(@event.Level);
+ }
+ else
+ {
+ Assert.NotNull(@event.Level);
+ }
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void EmptyEvent(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new CompactJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information("No properties");
+
+ // Assert
+ var @event = GetEvent();
+ Assert.NotNull(@event.Timestamp);
+ Assert.Null(@event.Level);
+ Assert.Equal("No properties", @event.MessageTemplate);
+ Assert.Equal(isRenderingMessage ? "No properties" : null, @event.RenderedMessage);
+ Assert.Null(@event.Exception);
+ Assert.Null(@event.Renderings);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void MinimalEvent(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new CompactJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information("One {Property}", 42);
+
+ // Assert
+ var @event = GetEvent();
+ Assert.NotNull(@event.Timestamp);
+ Assert.Equal("One {Property}", @event.MessageTemplate);
+ Assert.Equal(isRenderingMessage ? "One 42" : null, @event.RenderedMessage);
+ Assert.Null(@event.Exception);
+ Assert.Equal("42", GetProperty("Property"));
+ Assert.Null(@event.Renderings);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void MultipleProperties(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new CompactJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information("Property {First} and {Second}", "One", "Two");
+
+ // Assert
+ var @event = GetEvent();
+ Assert.NotNull(@event.Timestamp);
+ Assert.Equal("Property {First} and {Second}", @event.MessageTemplate);
+ Assert.Equal(isRenderingMessage ? "Property \"One\" and \"Two\"" : null, @event.RenderedMessage);
+ Assert.Null(@event.Exception);
+ Assert.Equal("One", GetProperty("First"));
+ Assert.Equal("Two", GetProperty("Second"));
+ Assert.Null(@event.Renderings);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void Exceptions(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new CompactJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information(new DivideByZeroException(), "With exception");
+
+ // Assert
+ var @event = GetEvent();
+ Assert.NotNull(@event.Timestamp);
+ Assert.Equal("With exception", @event.MessageTemplate);
+ Assert.Equal(isRenderingMessage ? "With exception" : null, @event.RenderedMessage);
+ Assert.NotNull(@event.Exception);
+ Assert.Null(@event.Renderings);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void ExceptionAndProperties(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new CompactJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information(new DivideByZeroException(), "With exception and {Property}", 42);
+
+ // Assert
+ var @event = GetEvent();
+ Assert.NotNull(@event.Timestamp);
+ Assert.Equal("With exception and {Property}", @event.MessageTemplate);
+ Assert.Equal(isRenderingMessage ? "With exception and 42" : null, @event.RenderedMessage);
+ Assert.NotNull(@event.Exception);
+ Assert.Equal("42", GetProperty("Property"));
+ Assert.Null(@event.Renderings);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void Renderings(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new CompactJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information("One {Rendering:x8}", 42);
+
+ // Assert
+ var @event = GetEvent();
+ Assert.NotNull(@event.Timestamp);
+ Assert.Equal("One {Rendering:x8}", @event.MessageTemplate);
+ Assert.Equal(isRenderingMessage ? "One 0000002a" : null, @event.RenderedMessage);
+ Assert.Null(@event.Exception);
+ Assert.Equal("42", GetProperty("Rendering"));
+ Assert.Equal(new[] { "0000002a" }, @event.Renderings);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void MultipleRenderings(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new CompactJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information("Rendering {First:x8} and {Second:x8}", 1, 2);
+
+ // Assert
+ var @event = GetEvent();
+ Assert.NotNull(@event.Timestamp);
+ Assert.Equal("Rendering {First:x8} and {Second:x8}", @event.MessageTemplate);
+ Assert.Equal(isRenderingMessage ? "Rendering 00000001 and 00000002" : null, @event.RenderedMessage);
+ Assert.Null(@event.Exception);
+ Assert.Equal("1", GetProperty("First"));
+ Assert.Equal("2", GetProperty("Second"));
+ Assert.Equal(new[] { "00000001", "00000002" }, @event.Renderings);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void NastyException(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new NormalJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information(new NastyException(), "With exception");
+
+ // Assert
+ Assert.Equal(string.Empty, output.ToString());
+ }
+
+ private ILogger CreateLogger(ITextFormatter formatter)
+ {
+ return new LoggerConfiguration()
+ .MinimumLevel.Verbose()
+ .WriteTo.Sink(new TextWriterSink(output, formatter))
+ .CreateLogger();
+ }
+
+ private CompactEventDto GetEvent()
+ {
+ return JsonConvert.DeserializeObject(output.ToString());
+ }
+
+ private string GetProperty(string name)
+ {
+ dynamic @event = JsonConvert.DeserializeObject(output.ToString());
+ return @event[name];
+ }
+ }
+}
diff --git a/test/Serilog.Sinks.Http.Tests/Sinks/Http/Private/Formatters/ConverterTest.cs b/test/Serilog.Sinks.Http.Tests/Sinks/Http/Private/Formatters/ConverterTest.cs
new file mode 100644
index 00000000..73f3aa7c
--- /dev/null
+++ b/test/Serilog.Sinks.Http.Tests/Sinks/Http/Private/Formatters/ConverterTest.cs
@@ -0,0 +1,21 @@
+using System;
+using Xunit;
+
+namespace Serilog.Sinks.Http.Private.Formatters
+{
+ public class ConverterTest
+ {
+ [Fact]
+ public void FormattingTypes()
+ {
+ foreach (FormattingType type in Enum.GetValues(typeof(FormattingType)))
+ {
+ // Act
+ var formatter = Converter.ToFormatter(type);
+
+ // Assert
+ Assert.NotNull(formatter);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Serilog.Sinks.Http.Tests/Sinks/Http/Private/Formatters/NormalJsonFormatterTest.cs b/test/Serilog.Sinks.Http.Tests/Sinks/Http/Private/Formatters/NormalJsonFormatterTest.cs
new file mode 100644
index 00000000..b7232eaa
--- /dev/null
+++ b/test/Serilog.Sinks.Http.Tests/Sinks/Http/Private/Formatters/NormalJsonFormatterTest.cs
@@ -0,0 +1,246 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Newtonsoft.Json;
+using Serilog.Events;
+using Serilog.Formatting;
+using Serilog.Sinks.Http.IntegrationTests.Server.Controllers.Dtos;
+using Serilog.Support;
+using Xunit;
+
+namespace Serilog.Sinks.Http.Private.Formatters
+{
+ public class NormalJsonFormatterTest
+ {
+ private readonly StringWriter output;
+
+ private ILogger logger;
+
+ public NormalJsonFormatterTest()
+ {
+ output = new StringWriter();
+ }
+
+ [Theory]
+ [InlineData(LogEventLevel.Verbose)]
+ [InlineData(LogEventLevel.Debug)]
+ [InlineData(LogEventLevel.Information)]
+ [InlineData(LogEventLevel.Warning)]
+ [InlineData(LogEventLevel.Error)]
+ [InlineData(LogEventLevel.Fatal)]
+ public void LogEventLevels(LogEventLevel level)
+ {
+ // Arrange
+ logger = CreateLogger(new NormalJsonFormatter(true));
+
+ // Act
+ logger.Write(level, "No properties");
+
+ // Assert
+ var @event = GetEvent();
+ Assert.NotNull(@event.Level);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void EmptyEvent(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new NormalJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information("No properties");
+
+ // Assert
+ var @event = GetEvent();
+ Assert.NotNull(@event.Timestamp);
+ Assert.Equal("Information", @event.Level);
+ Assert.Equal("No properties", @event.MessageTemplate);
+ Assert.Equal(isRenderingMessage ? "No properties" : null, @event.RenderedMessage);
+ Assert.Null(@event.Exception);
+ Assert.Null(@event.Properties);
+ Assert.Null(@event.Renderings);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void MinimalEvent(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new NormalJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information("One {Property}", 42);
+
+ // Assert
+ var @event = GetEvent();
+ Assert.NotNull(@event.Timestamp);
+ Assert.Equal("Information", @event.Level);
+ Assert.Equal("One {Property}", @event.MessageTemplate);
+ Assert.Equal(isRenderingMessage ? "One 42" : null, @event.RenderedMessage);
+ Assert.Null(@event.Exception);
+ Assert.Equal(new Dictionary { { "Property", "42" } }, @event.Properties);
+ Assert.Null(@event.Renderings);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void MultipleProperties(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new NormalJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information("Property {First} and {Second}", "One", "Two");
+
+ // Assert
+ var @event = GetEvent();
+ Assert.NotNull(@event.Timestamp);
+ Assert.Equal("Information", @event.Level);
+ Assert.Equal("Property {First} and {Second}", @event.MessageTemplate);
+ Assert.Equal(isRenderingMessage ? "Property \"One\" and \"Two\"" : null, @event.RenderedMessage);
+ Assert.Null(@event.Exception);
+ Assert.Equal(new Dictionary { { "First", "One" }, { "Second", "Two" } }, @event.Properties);
+ Assert.Null(@event.Renderings);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void Exceptions(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new NormalJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information(new DivideByZeroException(), "With exception");
+
+ // Assert
+ var @event = GetEvent();
+ Assert.NotNull(@event.Timestamp);
+ Assert.Equal("Information", @event.Level);
+ Assert.Equal("With exception", @event.MessageTemplate);
+ Assert.Equal(isRenderingMessage ? "With exception" : null, @event.RenderedMessage);
+ Assert.NotNull(@event.Exception);
+ Assert.Null(@event.Properties);
+ Assert.Null(@event.Renderings);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void ExceptionAndProperties(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new NormalJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information(new DivideByZeroException(), "With exception and {Property}", 42);
+
+ // Assert
+ var @event = GetEvent();
+ Assert.NotNull(@event.Timestamp);
+ Assert.Equal("Information", @event.Level);
+ Assert.Equal("With exception and {Property}", @event.MessageTemplate);
+ Assert.Equal(isRenderingMessage ? "With exception and 42" : null, @event.RenderedMessage);
+ Assert.NotNull(@event.Exception);
+ Assert.Equal(new Dictionary { { "Property", "42" } }, @event.Properties);
+ Assert.Null(@event.Renderings);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void Renderings(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new NormalJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information("One {Rendering:x8}", 42);
+
+ // Assert
+ var @event = GetEvent();
+ Assert.NotNull(@event.Timestamp);
+ Assert.Equal("Information", @event.Level);
+ Assert.Equal("One {Rendering:x8}", @event.MessageTemplate);
+ Assert.Equal(isRenderingMessage ? "One 0000002a" : null, @event.RenderedMessage);
+ Assert.Null(@event.Exception);
+ Assert.Equal(new Dictionary { { "Rendering", "42" } }, @event.Properties);
+ Assert.Equal(
+ new Dictionary
+ {
+ {
+ "Rendering",
+ new[] { new RenderingDto { Format = "x8", Rendering = "0000002a" } }
+ }
+ },
+ @event.Renderings);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void MultipleRenderings(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new NormalJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information("Rendering {First:x8} and {Second:x8}", 1, 2);
+
+ // Assert
+ var @event = GetEvent();
+ Assert.NotNull(@event.Timestamp);
+ Assert.Equal("Information", @event.Level);
+ Assert.Equal("Rendering {First:x8} and {Second:x8}", @event.MessageTemplate);
+ Assert.Equal(isRenderingMessage ? "Rendering 00000001 and 00000002" : null, @event.RenderedMessage);
+ Assert.Null(@event.Exception);
+ Assert.Equal(new Dictionary { { "First", "1" }, { "Second", "2" } }, @event.Properties);
+ Assert.Equal(
+ new Dictionary
+ {
+ {
+ "First",
+ new[] { new RenderingDto { Format = "x8", Rendering = "00000001" } }
+ },
+ {
+ "Second",
+ new[] { new RenderingDto { Format = "x8", Rendering = "00000002" } }
+ }
+ },
+ @event.Renderings);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void NastyException(bool isRenderingMessage)
+ {
+ // Arrange
+ logger = CreateLogger(new NormalJsonFormatter(isRenderingMessage));
+
+ // Act
+ logger.Information(new NastyException(), "With exception");
+
+ // Assert
+ Assert.Equal(string.Empty, output.ToString());
+ }
+
+ private ILogger CreateLogger(ITextFormatter formatter)
+ {
+ return new LoggerConfiguration()
+ .MinimumLevel.Verbose()
+ .WriteTo.Sink(new TextWriterSink(output, formatter))
+ .CreateLogger();
+ }
+
+ private EventDto GetEvent()
+ {
+ return JsonConvert.DeserializeObject(output.ToString());
+ }
+ }
+}
diff --git a/test/Serilog.Sinks.Http.Tests/Sinks/Http/Private/HttpJsonFormatterTest.cs b/test/Serilog.Sinks.Http.Tests/Sinks/Http/Private/HttpJsonFormatterTest.cs
deleted file mode 100644
index 1e3d03f5..00000000
--- a/test/Serilog.Sinks.Http.Tests/Sinks/Http/Private/HttpJsonFormatterTest.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-using System;
-using System.IO;
-using Newtonsoft.Json.Linq;
-using Serilog.Support;
-using Xunit;
-
-namespace Serilog.Sinks.Http.Private
-{
- public class HttpJsonFormatterTest
- {
- private readonly ILogger logger;
- private readonly StringWriter output;
-
- public HttpJsonFormatterTest()
- {
- output = new StringWriter();
- var formatter = new HttpJsonFormatter();
- logger = new LoggerConfiguration()
- .WriteTo.Sink(new TextWriterSink(output, formatter))
- .CreateLogger();
- }
-
- [Fact]
- public void EmptyEvent()
- {
- AssertValidJson(log => log.Information("No properties"));
- }
-
- [Fact]
- public void MinimalEvent()
- {
- AssertValidJson(log => log.Information("One {Property}", 42));
- }
-
- [Fact]
- public void MultipleProperties()
- {
- AssertValidJson(log => log.Information("Property {First} and {Second}", "One", "Two"));
- }
-
- [Fact]
- public void Exceptions()
- {
- AssertValidJson(log => log.Information(new DivideByZeroException(), "With exception"));
- }
-
- [Fact]
- public void ExceptionAndProperties()
- {
- AssertValidJson(log => log.Information(new DivideByZeroException(), "With exception and {Property}", 42));
- }
-
- [Fact]
- public void Renderings()
- {
- AssertValidJson(log => log.Information("One {Rendering:x8}", 42));
- }
-
- [Fact]
- public void MultipleRenderings()
- {
- AssertValidJson(log => log.Information("Rendering {First:x8} and {Second:x8}", 1, 2));
- }
-
- [Fact]
- public void NastyException()
- {
- AssertIsDropped(log => log.Information(new NastyException(), "With exception"));
- }
-
- private void AssertValidJson(Action act)
- {
- // Act
- act(logger);
-
- // Assert - Unfortunately this will not detect all JSON formatting issues; better than
- // nothing however
- JObject.Parse(output.ToString());
- }
-
- private void AssertIsDropped(Action act)
- {
- // Act
- act(logger);
-
- // Assert
- Assert.Equal(string.Empty, output.ToString());
- }
- }
-}
diff --git a/test/Serilog.Sinks.Http.Tests/project.json b/test/Serilog.Sinks.Http.Tests/project.json
index a822456e..88d17089 100644
--- a/test/Serilog.Sinks.Http.Tests/project.json
+++ b/test/Serilog.Sinks.Http.Tests/project.json
@@ -1,9 +1,10 @@
-{
+{
"testRunner": "xunit",
"dependencies": {
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"NETStandard.Library": "1.6.1",
"Serilog.Sinks.Http": { "target": "project" },
+ "Serilog.Sinks.Http.IntegrationTests.Server": "1.0.0-*",
"xunit": "2.2.0"
},
"buildOptions": {