Skip to content

Commit

Permalink
feat: add event metadata property
Browse files Browse the repository at this point in the history
  • Loading branch information
FantasticFiasco committed Dec 28, 2020
1 parent 2e094f7 commit b1e7cbb
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 4 deletions.
155 changes: 155 additions & 0 deletions serilog/MetadataTextFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Serilog.Debugging;
using Serilog.Events;
using Serilog.Formatting;
using Serilog.Formatting.Json;
using Serilog.Parsing;

namespace SerilogExample
{
public class MetadataTextFormatter : ITextFormatter
{
private static readonly JsonValueFormatter ValueFormatter = new JsonValueFormatter();

/// <summary>
/// Format the log event into the output.
/// </summary>
/// <param name="logEvent">The event to format.</param>
/// <param name="output">The output.</param>
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("{\"Timestamp\":\"");
output.Write(logEvent.Timestamp.ToString("o"));

output.Write("\",\"Level\":\"");
output.Write(logEvent.Level);

output.Write("\",\"MessageTemplate\":");
JsonValueFormatter.WriteQuotedJsonString(logEvent.MessageTemplate.Text, output);

output.Write(",\"RenderedMessage\":");

var message = logEvent.MessageTemplate.Render(logEvent.Properties);
JsonValueFormatter.WriteQuotedJsonString(message, output);

if (logEvent.Exception != null)
{
output.Write(",\"Exception\":");
JsonValueFormatter.WriteQuotedJsonString(logEvent.Exception.ToString(), output);
}

if (logEvent.Properties.Count != 0)
{
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<PropertyToken>()
.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);
}

// Add a @metadata property that will be used by Logstash to separate logs from
// multiple applications
output.Write(", \"@metadata\":{\"app\":\"my-app\",\"version\":\"1.2.3\"}");

output.Write('}');
}

private static void WriteProperties(
IReadOnlyDictionary<string, LogEventPropertyValue> properties,
TextWriter output)
{
output.Write(",\"Properties\":{");

var precedingDelimiter = "";

foreach (var property in properties)
{
output.Write(precedingDelimiter);
precedingDelimiter = ",";

JsonValueFormatter.WriteQuotedJsonString(property.Key, output);
output.Write(':');
ValueFormatter.Format(property.Value, output);
}

output.Write('}');
}

private static void WriteRenderings(
IEnumerable<IGrouping<string, PropertyToken>> tokensWithFormat,
IReadOnlyDictionary<string, LogEventPropertyValue> properties,
TextWriter output)
{
output.Write(",\"Renderings\":{");

var rdelim = "";
foreach (var ptoken in tokensWithFormat)
{
output.Write(rdelim);
rdelim = ",";

JsonValueFormatter.WriteQuotedJsonString(ptoken.Key, output);
output.Write(":[");

var fdelim = "";
foreach (var format in ptoken)
{
output.Write(fdelim);
fdelim = ",";

output.Write("{\"Format\":");
JsonValueFormatter.WriteQuotedJsonString(format.Format, output);

output.Write(",\"Rendering\":");
var sw = new StringWriter();
format.Render(properties, sw);
JsonValueFormatter.WriteQuotedJsonString(sw.ToString(), output);
output.Write('}');
}

output.Write(']');
}

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);
}
}
}
7 changes: 4 additions & 3 deletions serilog/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ public class Program
static void Main()
{
ILogger logger = new LoggerConfiguration()
.WriteTo.DurableHttpUsingFileSizeRolledBuffers(
.WriteTo.Http(
requestUri: "http://logstash:31311",
textFormatter: new MetadataTextFormatter(),
batchFormatter: new ArrayBatchFormatter())
.WriteTo.Console()
.CreateLogger()
Expand All @@ -26,9 +27,9 @@ static void Main()
{
var customer = customerGenerator.Generate();
var order = orderGenerator.Generate();

logger.Information("{@customer} placed {@order}", customer, order);

Thread.Sleep(1000);
}
}
Expand Down
2 changes: 1 addition & 1 deletion serilog/serilog-example.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Bogus" Version="32.0.2" />
Expand Down

0 comments on commit b1e7cbb

Please sign in to comment.