Skip to content

Commit

Permalink
Added support for structured logging properties (IncludeEventProperti…
Browse files Browse the repository at this point in the history
…es = true)
  • Loading branch information
snakefoot committed May 24, 2018
1 parent b4f2bc4 commit 0fb9403
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 118 deletions.
10 changes: 1 addition & 9 deletions examples/netFx/NLogGraylogHttp.Example.NetFx/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<nlog throwExceptions="true" xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<extensions>
Expand All @@ -25,12 +25,4 @@
<logger name="*" minlevel="Trace" appendTo="coloredConsole" />
</rules>
</nlog>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<OutputType>Exe</OutputType>
<RootNamespace>NLogGraylogHttp.Example.NetFx</RootNamespace>
<AssemblyName>NLogGraylogHttp.Example.NetFx</AssemblyName>
<TargetFrameworkVersion>v4.6</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
Expand Down Expand Up @@ -44,18 +44,11 @@
<Reference Include="System.Net.Http">
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http.Extensions, Version=2.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\..\Packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.Primitives, Version=4.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\..\Packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath>
</Reference>
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
Expand Down
2 changes: 1 addition & 1 deletion examples/netFx/NLogGraylogHttp.Example.NetFx/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace NLogGraylogHttp.Example.NetFx
{
class Program
static class Program
{
static void Main(string[] args)
{
Expand Down
8 changes: 4 additions & 4 deletions examples/netFx/NLogGraylogHttp.Example.NetFx/packages.config
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="net46" />
<package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net46" />
<package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net46" />
<package id="NLog" version="4.5.4" targetFramework="net46" />
<package id="Microsoft.Bcl" version="1.1.10" targetFramework="net45" />
<package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net45" />
<package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" />
<package id="NLog" version="4.5.4" targetFramework="net45" />
</packages>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace NLogGraylogHttp.Example.NetCore
{
class Program
static class Program
{
public static void Main(string[] args)
{
Expand Down
124 changes: 101 additions & 23 deletions src/NLog.Targets.GraylogHttp/GraylogHttpTarget.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using NLog.Common;
using NLog.Config;

namespace NLog.Targets.GraylogHttp
{
[Target("GraylogHttp")]
public class GraylogHttpTarget : TargetWithLayout
public class GraylogHttpTarget : TargetWithContext
{
public GraylogHttpTarget()
{
this.Host = Environment.GetEnvironmentVariable("COMPUTERNAME") ?? Environment.GetEnvironmentVariable("HOSTNAME");
this.Parameters = new List<GraylogParameterInfo>();
this.ContextProperties = new List<TargetPropertyWithContext>();
}

[RequiredParameter]
Expand All @@ -28,40 +27,119 @@ public GraylogHttpTarget()

public string Host { get; set; }

[ArrayParameter(typeof(GraylogParameterInfo), "parameter")]
public IList<GraylogParameterInfo> Parameters { get; private set; }
[ArrayParameter(typeof(TargetPropertyWithContext), "parameter")]
public override IList<TargetPropertyWithContext> ContextProperties { get; }

HttpClient _httpClient;

protected override void InitializeTarget()
{
if (string.IsNullOrEmpty(this.Host))
this.Host = GetMachineName();

_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri(string.Format("{0}:{1}/gelf", this.GraylogServer, this.GraylogPort));
_httpClient.DefaultRequestHeaders.ExpectContinue = false; // Expect (100) Continue breaks the graylog server

// Prefix the custom properties with underscore upfront, so we dont have to do it for each logevent
for (int i = 0; i < this.ContextProperties.Count; ++i)
{
var p = this.ContextProperties[i];
if (!p.Name.StartsWith("_", StringComparison.Ordinal))
p.Name = string.Concat("_", p.Name);
}

base.InitializeTarget();
}

protected override void Write(LogEventInfo logEvent)
{
GraylogMessageBuilder messageBuilder = new GraylogMessageBuilder()
.WithCustomProperty("facility", this.Facility)
.WithCustomProperty("_facility", this.Facility)
.WithProperty("short_message", logEvent.Message)
.WithProperty("host", this.Host)
.WithLevel(logEvent.Level)
.WithCustomProperty("logger_name", logEvent.LoggerName);
.WithCustomProperty("_logger_name", logEvent.LoggerName);

if (this.Parameters != null && this.Parameters.Any())
{
Dictionary<string, string> paramsDictionary = this.Parameters
.Select(p => new KeyValuePair<string, string>(p.Name, p.Layout.Render(logEvent)))
.ToDictionary(pair => pair.Key, pair => pair.Value);

messageBuilder.WithCustomPropertyRange(paramsDictionary);
}
var properties = GetAllProperties(logEvent);
foreach (var property in properties)
{
try
{
if (!string.IsNullOrEmpty(property.Key))
{
if (Convert.GetTypeCode(property.Value) != TypeCode.Object)
messageBuilder.WithCustomProperty(property.Key, property.Value);
else
messageBuilder.WithCustomProperty(property.Key, property.Value?.ToString());
}
}
catch (Exception ex)
{
InternalLogger.Error(ex, "GraylogHttp(Name={0}): Fail to handle LogEvent Properties", Name);
}
}

if (logEvent.Exception != null)
{
if (!string.IsNullOrEmpty(logEvent.Exception.Message))
messageBuilder.WithCustomProperty("exception_message", logEvent.Exception.Message);
messageBuilder.WithCustomProperty("_exception_message", logEvent.Exception.Message);
if (!string.IsNullOrEmpty(logEvent.Exception.StackTrace))
messageBuilder.WithCustomProperty("exception_stack_trace", logEvent.Exception.StackTrace);
messageBuilder.WithCustomProperty("_exception_stack_trace", logEvent.Exception.StackTrace);
}

using (var httpClient = new HttpClient())
try
{
// Ensure to reuse the same HttpClient. See also: https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
HttpResponseMessage httpResponse = _httpClient.PostAsync("", new StringContent(messageBuilder.Render(), Encoding.UTF8, "application/json")).Result;
if (httpResponse?.IsSuccessStatusCode ?? false)
return;

string responseText = null;
try
{
responseText = httpResponse?.Content?.ReadAsStringAsync()?.Result ?? null;
}
catch
{
}
finally
{
InternalLogger.Debug("GraylogHttp(Name={0}): Response Status Code is not success: {1} - {2}", Name, httpResponse?.StatusCode, responseText);
}
}
catch (AggregateException ex)
{
foreach (var inner in ex.Flatten().InnerExceptions)
InternalLogger.Error(inner, "GraylogHttp(Name={0}): Fail to post LogEvents", Name);
}
catch (Exception ex)
{
InternalLogger.Error(ex, "GraylogHttp(Name={0}): Fail to post LogEvents", Name);
}
}

/// <summary>
/// Gets the machine name
/// </summary>
private static string GetMachineName()
{
return TryLookupValue(() => Environment.GetEnvironmentVariable("COMPUTERNAME"), "COMPUTERNAME")
?? TryLookupValue(() => Environment.GetEnvironmentVariable("HOSTNAME"), "HOSTNAME")
?? TryLookupValue(() => Dns.GetHostName(), "DnsHostName");
}

private static string TryLookupValue(Func<string> lookupFunc, string lookupType)
{
try
{
string lookupValue = lookupFunc()?.Trim();
return string.IsNullOrEmpty(lookupValue) ? null : lookupValue;
}
catch (Exception ex)
{
httpClient.BaseAddress = new Uri(string.Format("{0}:{1}/gelf", this.GraylogServer, this.GraylogPort));
httpClient.DefaultRequestHeaders.ExpectContinue = false; // Expect (100) Continue breaks the graylog server
HttpResponseMessage httpResponseMessage = httpClient.PostAsync("", new StringContent(messageBuilder.Render(), Encoding.UTF8, "application/json")).Result;
InternalLogger.Warn(ex, "GraylogHttp: Failed to lookup {0}", lookupType);
return null;
}
}
}
Expand Down
52 changes: 23 additions & 29 deletions src/NLog.Targets.GraylogHttp/GraylogMessageBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace NLog.Targets.GraylogHttp
{
Expand All @@ -10,20 +8,22 @@ internal class GraylogMessageBuilder

public GraylogMessageBuilder WithLevel(LogLevel level)
{
GelfLevel graylogLevel;
object graylogLevel;

if (level == LogLevel.Debug)
graylogLevel = GelfLevel.Debug;
else if (level == LogLevel.Fatal)
graylogLevel = GelfLevel.Critical;
if (level == LogLevel.Trace)
graylogLevel = GelfLevel_Debug;
else if (level == LogLevel.Debug)
graylogLevel = GelfLevel_Debug;
else if (level == LogLevel.Info)
graylogLevel = GelfLevel.Informational;
else if (level == LogLevel.Trace)
graylogLevel = GelfLevel.Informational;
graylogLevel = GelfLevel_Informational;
else if (level == LogLevel.Warn)
graylogLevel = GelfLevel_Warning;
else if (level == LogLevel.Fatal)
graylogLevel = GelfLevel_Critical;
else
graylogLevel = level == LogLevel.Warn ? GelfLevel.Warning : GelfLevel.Error;
graylogLevel = GelfLevel_Error;

return this.WithProperty("level", (int)graylogLevel);
return this.WithProperty("level", graylogLevel);
}

public GraylogMessageBuilder WithProperty(string propertyName, object value)
Expand All @@ -34,12 +34,9 @@ public GraylogMessageBuilder WithProperty(string propertyName, object value)

public GraylogMessageBuilder WithCustomProperty(string propertyName, object value)
{
return this.WithProperty(string.Format("_{0}", propertyName), value);
}

public GraylogMessageBuilder WithCustomPropertyRange(Dictionary<string, string> properties)
{
return properties.Aggregate(this, (builder, pair) => builder.WithCustomProperty(pair.Key, pair.Value));
if (!propertyName.StartsWith("_", StringComparison.Ordinal))
propertyName = string.Concat("_", propertyName);
return this.WithProperty(propertyName, value);
}

public string Render()
Expand All @@ -49,16 +46,13 @@ public string Render()
return _graylogMessage.ToString();
}

public enum GelfLevel
{
Emergency = 0,
Alert = 1,
Critical = 2,
Error = 3,
Warning = 4,
Notice = 5,
Informational = 6,
Debug = 7
}
readonly static object GelfLevel_Emergency = 0;
readonly static object GelfLevel_Alert = 1;
readonly static object GelfLevel_Critical = 2;
readonly static object GelfLevel_Error = 3;
readonly static object GelfLevel_Warning = 4;
readonly static object GelfLevel_Notice = 5;
readonly static object GelfLevel_Informational = 6;
readonly static object GelfLevel_Debug = 7;
}
}
22 changes: 20 additions & 2 deletions src/NLog.Targets.GraylogHttp/NLog.Targets.GraylogHttp.csproj
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<TargetFrameworks>netstandard1.3;net45</TargetFrameworks>

<Version>1.0.0</Version>
<Title>NLog.Targets.GraylogHttp</Title>
<Product>NLog.Targets.GraylogHttp</Product>
<Authors>Dustin Chilson</Authors>
<Description>NLog target that pushes log messages to Graylog using the Http input</Description>
<Copyright>Copyright © 2015</Copyright>

<PackageTags>NLog;Graylog;Log;Logging</PackageTags>
<PackageProjectUrl>https://github.com/dustinchilson/NLog.Targets.GraylogHttp</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/dustinchilson/NLog.Targets.GraylogHttp/blob/master/LICENSE</PackageLicenseUrl>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>git://github.com/dustinchilson/NLog.Targets.GraylogHttp</RepositoryUrl>

</PropertyGroup>

<ItemGroup>
<PackageReference Include="NLog" Version="4.5.4" />
<PackageReference Include="SimpleJson" Version="0.38.0" />
<PackageReference Include="System.Runtime.Serialization.Primitives" Version="4.3.0" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<Reference Include="System.Net.Http" />
</ItemGroup>
</Project>
15 changes: 0 additions & 15 deletions src/NLog.Targets.GraylogHttp/NLog.Targets.GraylogHttp.nuspec

This file was deleted.

Loading

0 comments on commit 0fb9403

Please sign in to comment.