diff --git a/src/Serilog.Sinks.Splunk/Sinks/Splunk/CompactSplunkJsonFormatter.cs b/src/Serilog.Sinks.Splunk/Sinks/Splunk/CompactSplunkJsonFormatter.cs
new file mode 100644
index 0000000..79132fa
--- /dev/null
+++ b/src/Serilog.Sinks.Splunk/Sinks/Splunk/CompactSplunkJsonFormatter.cs
@@ -0,0 +1,137 @@
+// Copyright 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 Serilog.Events;
+using Serilog.Formatting;
+using Serilog.Formatting.Json;
+using Serilog.Parsing;
+using System;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+
+namespace Serilog.Sinks.Splunk
+{
+ ///
+ /// Renders log events into a Compact JSON format for consumption by Splunk.
+ ///
+ public class CompactSplunkJsonFormatter : ITextFormatter
+ {
+ private static readonly JsonValueFormatter ValueFormatter = new JsonValueFormatter(typeTagName: "$type");
+ private readonly string _suffix;
+ private readonly bool _renderTemplate;
+
+ ///
+ /// Construct a .
+ ///
+ /// The source of the event
+ /// The source type of the event
+ /// The host of the event
+ /// The Splunk index to log to
+ /// If true, the template used will be rendered and written to the output as a property named MessageTemplate
+ public CompactSplunkJsonFormatter(bool renderTemplate = false, string source = null, string sourceType = null, string host = null, string index = null)
+ {
+ _renderTemplate = renderTemplate;
+ var suffixWriter = new StringWriter();
+ suffixWriter.Write("}"); // Terminates "event"
+
+ if (!string.IsNullOrWhiteSpace(source))
+ {
+ suffixWriter.Write(",\"source\":");
+ JsonValueFormatter.WriteQuotedJsonString(source, suffixWriter);
+ }
+
+ if (!string.IsNullOrWhiteSpace(sourceType))
+ {
+ suffixWriter.Write(",\"sourcetype\":");
+ JsonValueFormatter.WriteQuotedJsonString(sourceType, suffixWriter);
+ }
+
+ if (!string.IsNullOrWhiteSpace(host))
+ {
+ suffixWriter.Write(",\"host\":");
+ JsonValueFormatter.WriteQuotedJsonString(host, suffixWriter);
+ }
+
+ if (!string.IsNullOrWhiteSpace(index))
+ {
+ suffixWriter.Write(",\"index\":");
+ JsonValueFormatter.WriteQuotedJsonString(index, suffixWriter);
+ }
+ suffixWriter.Write('}'); // Terminates the payload
+ _suffix = suffixWriter.ToString();
+ }
+
+ ///
+ public void Format(LogEvent logEvent, TextWriter output)
+ {
+ if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
+ if (output == null) throw new ArgumentNullException(nameof(output));
+
+ output.Write("{\"time\":\"");
+ output.Write(logEvent.Timestamp.ToEpoch().ToString(CultureInfo.InvariantCulture));
+ output.Write("\",\"event\":{\"@l\":\"");
+ output.Write(logEvent.Level);
+ output.Write('"');
+
+ if (_renderTemplate)
+ {
+ output.Write(",\"@mt\":");
+ JsonValueFormatter.WriteQuotedJsonString(logEvent.MessageTemplate.Text, 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.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.WriteLine(_suffix);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/Serilog.Sinks.Splunk.Tests/CompactSplunkJsonFormatterTests.cs b/test/Serilog.Sinks.Splunk.Tests/CompactSplunkJsonFormatterTests.cs
new file mode 100644
index 0000000..58a1ffb
--- /dev/null
+++ b/test/Serilog.Sinks.Splunk.Tests/CompactSplunkJsonFormatterTests.cs
@@ -0,0 +1,84 @@
+using Newtonsoft.Json.Linq;
+using Serilog.Sinks.Splunk.Tests.Support;
+using System;
+using System.IO;
+using Xunit;
+
+namespace Serilog.Sinks.Splunk.Tests
+{
+ public class CompactSplunkJsonFormatterTests
+ {
+ private void AssertValidJson(Action act,
+ string source = "",
+ string sourceType = "",
+ string host = "",
+ string index = "")
+ {
+ StringWriter outputRendered = new StringWriter(), output = new StringWriter();
+ var log = new LoggerConfiguration()
+ .WriteTo.Sink(new TextWriterSink(output, new CompactSplunkJsonFormatter(false, source, sourceType, host, index)))
+ .WriteTo.Sink(new TextWriterSink(outputRendered, new CompactSplunkJsonFormatter(true, source, sourceType, host, index)))
+ .CreateLogger();
+
+ act(log);
+
+ // Unfortunately this will not detect all JSON formatting issues; better than nothing however.
+ JObject.Parse(output.ToString());
+ JObject.Parse(outputRendered.ToString());
+ }
+
+ [Fact]
+ public void AnEmptyEventIsValidJson()
+ {
+ AssertValidJson(log => log.Information("No properties"));
+ }
+
+ [Fact]
+ public void AMinimalEventIsValidJson()
+ {
+ AssertValidJson(log => log.Information("One {Property}", 42));
+ }
+
+ [Fact]
+ public void MultiplePropertiesAreDelimited()
+ {
+ AssertValidJson(log => log.Information("Property {First} and {Second}", "One", "Two"));
+ }
+
+ [Fact]
+ public void ExceptionsAreFormattedToValidJson()
+ {
+ AssertValidJson(log => log.Information(new DivideByZeroException(), "With exception"));
+ }
+
+ [Fact]
+ public void ExceptionAndPropertiesAreValidJson()
+ {
+ AssertValidJson(log => log.Information(new DivideByZeroException(), "With exception and {Property}", 42));
+ }
+
+ [Fact]
+ public void AMinimalEventWithSourceIsValidJson()
+ {
+ AssertValidJson(log => log.Information("One {Property}", 42), source: "A Test Source");
+ }
+
+ [Fact]
+ public void AMinimalEventWithSourceTypeIsValidJson()
+ {
+ AssertValidJson(log => log.Information("One {Property}", 42), sourceType: "A Test SourceType");
+ }
+
+ [Fact]
+ public void AMinimalEventWithHostIsValidJson()
+ {
+ AssertValidJson(log => log.Information("One {Property}", 42), host: "A Test Host");
+ }
+
+ [Fact]
+ public void AMinimalEventWithIndexIsValidJson()
+ {
+ AssertValidJson(log => log.Information("One {Property}", 42), host: "testindex");
+ }
+ }
+}
\ No newline at end of file