Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update converter to follow the new 1.1 specification #7

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
31 changes: 17 additions & 14 deletions ConsoleRunner/App.config
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
</configSections>

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<extensions>
<add assembly="Gelf4NLog.Target"/>
<add assembly="Gelf4NLog.Target" />
</extensions>

<targets>
<target name="console"
xsi:type="Console"
layout="${longdate}|${level:uppercase=true}|${logger}|${machinename}|${message}${onexception:EXCEPTION OCCURRED\:${exception:format=tostring}}"
/>
<target name="graylog"
xsi:type="graylog"
hostip="192.168.1.12"
hostport="12201"
Facility="console-runner"
/>
<target name="console" xsi:type="Console" layout="${longdate}|${level:uppercase=true}|${logger}|${machinename}|${message}${onexception:EXCEPTION OCCURRED\:${exception:format=tostring}}" />
<target name="graylog" xsi:type="graylog" hostip="192.168.1.12" hostport="12201" Facility="console-runner" />
</targets>

<rules>
<logger name="*" minlevel="Debug" writeTo="console" />
<logger name="*" minlevel="Debug" writeTo="graylog" />
</rules>
</nlog>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="NLog" publicKeyToken="5120e14c03d0593c" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-3.2.0.0" newVersion="3.2.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
10 changes: 6 additions & 4 deletions ConsoleRunner/Gelf4NLog.ConsoleRunner.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@
<Reference Include="LumenWorks.Framework.IO">
<HintPath>..\packages\LumenWorks.Framework.IO\LumenWorks.Framework.IO.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.4.5.7\lib\net40\Newtonsoft.Json.dll</HintPath>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.6.0.8\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.2.0.0.2000\lib\net40\NLog.dll</HintPath>
<Reference Include="NLog, Version=3.2.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NLog.3.2.0.0\lib\net40\NLog.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
Expand Down
4 changes: 2 additions & 2 deletions ConsoleRunner/packages.config
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Gelf4NLog.Target" version="1.0.0.2" />
<package id="Newtonsoft.Json" version="4.5.7" />
<package id="NLog" version="2.0.0.2000" />
<package id="Newtonsoft.Json" version="6.0.8" targetFramework="net40-Client" />
<package id="NLog" version="3.2.0.0" targetFramework="net40-Client" />
</packages>
9 changes: 5 additions & 4 deletions Target/Gelf4NLog.Target.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.4.5.6\lib\net40\Newtonsoft.Json.dll</HintPath>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.6.0.8\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=2.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.2.0.0.2000\lib\net40\NLog.dll</HintPath>
<Reference Include="NLog">
<HintPath>..\packages\NLog.3.2.0.0\lib\net40\NLog.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
Expand All @@ -50,6 +50,7 @@
<Compile Include="GelfConverter.cs" />
<Compile Include="GelfMessage.cs" />
<Compile Include="IConverter.cs" />
<Compile Include="HostnameToIp.cs" />
<Compile Include="ITransport.cs" />
<Compile Include="ITransportClient.cs" />
<Compile Include="NLogTarget.cs" />
Expand Down
143 changes: 125 additions & 18 deletions Target/GelfConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using NLog;
using Newtonsoft.Json.Linq;

Expand All @@ -10,7 +13,15 @@ namespace Gelf4NLog.Target
public class GelfConverter : IConverter
{
private const int ShortMessageMaxLength = 250;
private const string GelfVersion = "1.0";
private const string GelfVersion = "1.1";

private static readonly CamelCasePropertyNamesContractResolver PropertyResolver = new CamelCasePropertyNamesContractResolver();

private static readonly JsonSerializer Serializer = new JsonSerializer
{
NullValueHandling = NullValueHandling.Include,
ContractResolver = PropertyResolver
};

public JObject GetGelfJson(LogEventInfo logEventInfo, string facility)
{
Expand All @@ -21,9 +32,7 @@ public JObject GetGelfJson(LogEventInfo logEventInfo, string facility)
//If we are dealing with an exception, pass exception properties to LogEventInfo properties
if (logEventInfo.Exception != null)
{
logEventInfo.Properties.Add("ExceptionSource", logEventInfo.Exception.Source);
logEventInfo.Properties.Add("ExceptionMessage", logEventInfo.Exception.Message);
logEventInfo.Properties.Add("StackTrace", logEventInfo.Exception.StackTrace);
AddExceptionDetails(logEventInfo.Properties, logEventInfo.Exception);
}

//Figure out the short message
Expand All @@ -42,23 +51,31 @@ public JObject GetGelfJson(LogEventInfo logEventInfo, string facility)
ShortMessage = shortMessage,
FullMessage = logEventMessage,
Timestamp = logEventInfo.TimeStamp,
Level = GetSeverityLevel(logEventInfo.Level),
//Spec says: facility must be set by the client to "GELF" if empty
Facility = (string.IsNullOrEmpty(facility) ? "GELF" : facility),
Line = (logEventInfo.UserStackFrame != null)
? logEventInfo.UserStackFrame.GetFileLineNumber().ToString(
CultureInfo.InvariantCulture)
: string.Empty,
File = (logEventInfo.UserStackFrame != null)
? logEventInfo.UserStackFrame.GetFileName()
: string.Empty,
Level = GetSeverityLevel(logEventInfo.Level)
};

//Convert to JSON
var jsonObject = JObject.FromObject(gelfMessage);

//Add any other interesting data to LogEventInfo properties
logEventInfo.Properties.Add("LoggerName", logEventInfo.LoggerName);
logEventInfo.Properties.Add("loggerName", logEventInfo.LoggerName);

if (!string.IsNullOrWhiteSpace(facility))
{
logEventInfo.Properties.Add("facility", facility);
}

var line = GetLine(logEventInfo);
if (!string.IsNullOrWhiteSpace(line))
{
logEventInfo.Properties.Add("line", line);
}

var file = GetFile(logEventInfo);
if (!string.IsNullOrWhiteSpace(file))
{
logEventInfo.Properties.Add("file", file);
}

//We will persist them "Additional Fields" according to Gelf spec
foreach (var property in logEventInfo.Properties)
Expand All @@ -69,13 +86,57 @@ public JObject GetGelfJson(LogEventInfo logEventInfo, string facility)
return jsonObject;
}

private static string GetFile(LogEventInfo logEventInfo)
{
return (logEventInfo.UserStackFrame != null)
? logEventInfo.UserStackFrame.GetFileName()
: string.Empty;
}

private static string GetLine(LogEventInfo logEventInfo)
{
return (logEventInfo.UserStackFrame != null)
? logEventInfo.UserStackFrame.GetFileLineNumber().ToString(
CultureInfo.InvariantCulture)
: string.Empty;
}

private static void FlattenAndAddObject(IDictionary<string, JToken> gelfMessage, string key, IEnumerable<JToken> values)
{
foreach (var value in values)
{
if (value is JProperty)
{
var property = value as JProperty;

var flattenedKey = string.Concat(key, ".", property.Name);
if (!property.Value.HasValues)
{
gelfMessage.Add(flattenedKey, property.Value);
}

FlattenAndAddObject(gelfMessage, flattenedKey, property.Children());
}

if (value is JObject)
{
FlattenAndAddObject(gelfMessage, key, value);
}

if (value is JArray)
{
gelfMessage.Add(key, value);
}
}
}

private static void AddAdditionalField(IDictionary<string, JToken> jObject, KeyValuePair<object, object> property)
{
var key = property.Key as string;
var value = property.Value as string;

if (key == null) return;

key = PropertyResolver.GetResolvedPropertyName(key);

//According to the GELF spec, libraries should NOT allow to send id as additional field (_id)
//Server MUST skip the field because it could override the MongoDB _key field
if (key.Equals("id", StringComparison.OrdinalIgnoreCase))
Expand All @@ -85,7 +146,20 @@ private static void AddAdditionalField(IDictionary<string, JToken> jObject, KeyV
if (!key.StartsWith("_", StringComparison.OrdinalIgnoreCase))
key = "_" + key;

jObject.Add(key, value);
if (property.Value == null)
{
jObject.Add(key, null);
return;
}

var value = JToken.FromObject(property.Value, Serializer);
if (!value.HasValues || value is JArray)
{
jObject.Add(key, value);
return;
}

FlattenAndAddObject(jObject, key, value);
}

/// <summary>
Expand Down Expand Up @@ -118,5 +192,38 @@ private static int GetSeverityLevel(LogLevel level)

return 3; //LogLevel.Error
}

private static void AddExceptionDetails(IDictionary<object, object> properties, Exception exception)
{
var message = new StringBuilder();
var stacktrace = new StringBuilder();
var currentDepth = 0;

var currentException = exception;

do
{
message.Append(currentException.Message);

if (!string.IsNullOrWhiteSpace(currentException.StackTrace))
{
stacktrace.AppendLine(currentException.StackTrace);
}

if (currentException.InnerException != null)
{
message.Append(" - ");
stacktrace.AppendLine("--- Inner exception stack trace ---");
}

currentException = currentException.InnerException;
currentDepth++;

} while (currentException != null && currentDepth < 5);

properties.Add("ExceptionSource", exception.Source);
properties.Add("ExceptionMessage", message.Length == 0 ? null : message.ToString());
properties.Add("StackTrace", stacktrace.Length == 0 ? null : stacktrace.ToString());
}
}
}
9 changes: 0 additions & 9 deletions Target/GelfMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ namespace Gelf4NLog.Target
[JsonObject(MemberSerialization.OptIn)]
public class GelfMessage
{
[JsonProperty("facility")]
public string Facility { get; set; }

[JsonProperty("file")]
public string File { get; set; }

[JsonProperty("full_message")]
public string FullMessage { get; set; }

Expand All @@ -21,9 +15,6 @@ public class GelfMessage
[JsonProperty("level")]
public int Level { get; set; }

[JsonProperty("line")]
public string Line { get; set; }

[JsonProperty("short_message")]
public string ShortMessage { get; set; }

Expand Down
22 changes: 22 additions & 0 deletions Target/HostnameToIp.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;

namespace Gelf4NLog.Target
{
public static class HostnameToIp
{
private static readonly Regex IpAddressRegex = new Regex(@"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b");

public static IPAddress Parse(string addressOrHostname)
{
if (IpAddressRegex.IsMatch(addressOrHostname))
{
return IPAddress.Parse(addressOrHostname);
}

return Dns.GetHostAddresses(addressOrHostname)
.FirstOrDefault();
}
}
}
17 changes: 14 additions & 3 deletions Target/NLogTarget.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using NLog;
using NLog.Layouts;
using NLog.Targets;
using Newtonsoft.Json;

Expand All @@ -9,16 +10,19 @@ namespace Gelf4NLog.Target
public class NLogTarget : TargetWithLayout
{
[Required]
public string HostIp { get; set; }
public Layout HostIp { get; set; }

[Required]
public int HostPort { get; set; }
public Layout HostPort { get; set; }

public string Facility { get; set; }

public IConverter Converter { get; private set; }
public ITransport Transport { get; private set; }

private int _port;
private string _hostIp;

public NLogTarget()
{
Transport = new UdpTransport(new UdpTransportClient());
Expand All @@ -40,7 +44,14 @@ protected override void Write(LogEventInfo logEvent)
{
var jsonObject = Converter.GetGelfJson(logEvent, Facility);
if (jsonObject == null) return;
Transport.Send(HostIp, HostPort, jsonObject.ToString(Formatting.None, null));

if (string.IsNullOrWhiteSpace(_hostIp))
{
_hostIp = HostIp.Render(logEvent);
_port = int.Parse(HostPort.Render(logEvent));
}

Transport.Send(_hostIp, _port, jsonObject.ToString(Formatting.None, null));
}
}
}
Loading