-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #53 from avireddy02/dev
Added new CompactSplunkJsonFormatter from #52
- Loading branch information
Showing
2 changed files
with
221 additions
and
0 deletions.
There are no files selected for viewing
137 changes: 137 additions & 0 deletions
137
src/Serilog.Sinks.Splunk/Sinks/Splunk/CompactSplunkJsonFormatter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
{ | ||
/// <summary> | ||
/// Renders log events into a Compact JSON format for consumption by Splunk. | ||
/// </summary> | ||
public class CompactSplunkJsonFormatter : ITextFormatter | ||
{ | ||
private static readonly JsonValueFormatter ValueFormatter = new JsonValueFormatter(typeTagName: "$type"); | ||
private readonly string _suffix; | ||
private readonly bool _renderTemplate; | ||
|
||
/// <summary> | ||
/// Construct a <see cref="CompactSplunkJsonFormatter"/>. | ||
/// </summary> | ||
/// <param name="source">The source of the event</param> | ||
/// <param name="sourceType">The source type of the event</param> | ||
/// <param name="host">The host of the event</param> | ||
/// <param name="index">The Splunk index to log to</param> | ||
/// <param name="renderTemplate">If true, the template used will be rendered and written to the output as a property named MessageTemplate</param> | ||
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(); | ||
} | ||
|
||
/// <inheritdoc/> | ||
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<PropertyToken>() | ||
.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); | ||
} | ||
} | ||
} |
84 changes: 84 additions & 0 deletions
84
test/Serilog.Sinks.Splunk.Tests/CompactSplunkJsonFormatterTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<ILogger> 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"); | ||
} | ||
} | ||
} |