Skip to content

Adds support for NLog to application logging #1087

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

Merged
merged 15 commits into from
May 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions FullAgent.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32319.34
# Visual Studio Version 16
VisualStudioVersion = 16.0.32228.343
MinimumVisualStudioVersion = 15.0.26228.04
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "src\Agent\NewRelic\Agent\Core\Core.csproj", "{D6E22195-EE69-4320-B08B-E68229FB69AB}"
EndProject
Expand Down Expand Up @@ -207,6 +207,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SerilogLogging", "src\Agent
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Log4NetLogging", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\Log4NetLogging\Log4NetLogging.csproj", "{2E6CF650-CB50-453D-830A-D00F0540FC2C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NLogLogging", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\NLogLogging\NLogLogging.csproj", "{3D69B4C9-FD16-461F-95AF-6FCA6EAA914E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -433,6 +435,10 @@ Global
{2E6CF650-CB50-453D-830A-D00F0540FC2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E6CF650-CB50-453D-830A-D00F0540FC2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E6CF650-CB50-453D-830A-D00F0540FC2C}.Release|Any CPU.Build.0 = Release|Any CPU
{3D69B4C9-FD16-461F-95AF-6FCA6EAA914E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3D69B4C9-FD16-461F-95AF-6FCA6EAA914E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3D69B4C9-FD16-461F-95AF-6FCA6EAA914E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3D69B4C9-FD16-461F-95AF-6FCA6EAA914E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -501,6 +507,7 @@ Global
{710C7C7A-CD27-4F94-B2D3-9804BD848D57} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A}
{0C3E92D7-F16C-4575-9CBB-EB7349A4C5E2} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A}
{2E6CF650-CB50-453D-830A-D00F0540FC2C} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A}
{3D69B4C9-FD16-461F-95AF-6FCA6EAA914E} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.2\lib\NET35
Expand Down
2 changes: 2 additions & 0 deletions src/Agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### New Features

* Adds support for logging metrics, forwarding application logs, and enriching application logs written to disk or standard out for NLog versions v5 and v4. [#1087](https://github.com/newrelic/newrelic-dotnet-agent/pull/1087)

### Fixes

## [9.8.0] - 2022-05-05
Expand Down
12 changes: 12 additions & 0 deletions src/Agent/MsiInstaller/Installer/Product.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,9 @@ SPDX-License-Identifier: Apache-2.0
<Component Id="SerilogLoggingWrapperComponent" Guid="{6E0C2C78-762F-4022-880A-D5833C265884}">
<File Id="SerilogLoggingWrapperFile" Name="NewRelic.Providers.Wrapper.SerilogLogging.dll" KeyPath="yes" Source="$(var.HomeFolderPath)\extensions\NewRelic.Providers.Wrapper.SerilogLogging.dll"/>
</Component>
<Component Id="NLogLoggingWrapperComponent" Guid="{31EA2456-A5E3-4B04-AD4B-B010594F494D}">
<File Id="NLogLoggingWrapperFile" Name="NewRelic.Providers.Wrapper.NLogLogging.dll" KeyPath="yes" Source="$(var.HomeFolderPath)\extensions\NewRelic.Providers.Wrapper.NLogLogging.dll"/>
</Component>

<!-- Reference libraries -->
<Component Id="NewRelicCoreReferenceComponent" Guid="{C196ED1E-0FBA-4D36-9C34-E969B0C9E8C9}">
Expand Down Expand Up @@ -504,6 +507,9 @@ SPDX-License-Identifier: Apache-2.0
<Component Id="CoreSerilogLoggingWrapperComponent" Guid="{9B0099B2-96B8-4491-924E-9B43343B3809}">
<File Id="CoreSerilogLoggingWrapperFile" Name="NewRelic.Providers.Wrapper.SerilogLogging.dll" KeyPath="yes" Source="$(var.HomeFolderPath)_coreclr\extensions\NewRelic.Providers.Wrapper.SerilogLogging.dll"/>
</Component>
<Component Id="CoreNLogLoggingWrapperComponent" Guid="{897C07B8-AF97-4BA3-B6F3-26395A02147E}">
<File Id="CoreNLogLoggingWrapperFile" Name="NewRelic.Providers.Wrapper.NLogLogging.dll" KeyPath="yes" Source="$(var.HomeFolderPath)_coreclr\extensions\NewRelic.Providers.Wrapper.NLogLogging.dll"/>
</Component>

<!-- Reference libraries -->
<Component Id="CoreNewRelicCoreReferenceComponent" Guid="{DD2BE979-7D4B-47EA-9FBE-F6B381D70E0B}">
Expand Down Expand Up @@ -597,6 +603,9 @@ SPDX-License-Identifier: Apache-2.0
<Component Id="SerilogLoggingInstrumentationComponent" Guid="{E3E48218-BAC8-428E-8D1A-088D2A5F0637}">
<File Id="SerilogLoggingInstrumentationFile" Name="NewRelic.Providers.Wrapper.SerilogLogging.Instrumentation.xml" KeyPath="yes" Source="$(var.HomeFolderPath)\extensions\NewRelic.Providers.Wrapper.SerilogLogging.Instrumentation.xml"/>
</Component>
<Component Id="NLogLoggingInstrumentationComponent" Guid="{C803621B-EC47-4559-8B7D-2FD836A36DCD}">
<File Id="NLogLoggingInstrumentationFile" Name="NewRelic.Providers.Wrapper.NLogLogging.Instrumentation.xml" KeyPath="yes" Source="$(var.HomeFolderPath)\extensions\NewRelic.Providers.Wrapper.NLogLogging.Instrumentation.xml"/>
</Component>
</ComponentGroup>

<ComponentGroup Id="CoreNewRelic.Agent.Extensions.Instrumentation" Directory="CoreExtensionsFolder">
Expand Down Expand Up @@ -630,6 +639,9 @@ SPDX-License-Identifier: Apache-2.0
<Component Id="CoreSerilogLoggingInstrumentationComponent" Guid="{5F7C6A8F-8958-46B1-BC56-5EC197213BC5}">
<File Id="CoreSerilogLoggingInstrumentationFile" Name="NewRelic.Providers.Wrapper.SerilogLogging.Instrumentation.xml" KeyPath="yes" Source="$(var.HomeFolderPath)_coreclr\extensions\NewRelic.Providers.Wrapper.SerilogLogging.Instrumentation.xml"/>
</Component>
<Component Id="CoreNLogLoggingInstrumentationComponent" Guid="{D9A87E74-C43B-4ACD-BF6C-DC082FEDD777}">
<File Id="CoreNLogLoggingInstrumentationFile" Name="NewRelic.Providers.Wrapper.NLogLogging.Instrumentation.xml" KeyPath="yes" Source="$(var.HomeFolderPath)_coreclr\extensions\NewRelic.Providers.Wrapper.NLogLogging.Instrumentation.xml"/>
</Component>
</ComponentGroup>

<!-- Extensions XSD-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@ public static class LogProviders
public static readonly List<string> Log4NetProviderNames = new List<string> { "Microsoft.Extensions.Logging.Log4NetProvider", "log4net.Extensions.Logging.Log4NetProvider" };

public static readonly List<string> SerilogProviderNames = new List<string> { "Microsoft.Extensions.Logging.SerilogLoggerProvider", "Serilog.Extensions.Logging.SerilogLoggerProvider" };

public static readonly List<string> NLogProviderNames = new List<string> { "Microsoft.Extensions.Logging.NLogLoggerProvider", "NLog.Extensions.Logging.NLogLoggerProvider" };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,37 +35,38 @@ public CanWrapResponse CanWrap(InstrumentedMethodInfo methodInfo)
public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction)
{
var logEvent = instrumentedMethodCall.MethodCall.MethodArguments[0];
var logEventType = logEvent.GetType();

RecordLogMessage(logEvent, agent);
RecordLogMessage(logEvent, logEventType, agent);

DecorateLogMessage(logEvent, agent);
DecorateLogMessage(logEvent, logEventType, agent);

return Delegates.NoOp;
}

private void RecordLogMessage(object logEvent, IAgent agent)
private void RecordLogMessage(object logEvent, Type logEventType, IAgent agent)
{
var getLogLevelFunc = _getLogLevel ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<object>(logEvent.GetType(), "Level");
var getLogLevelFunc = _getLogLevel ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<object>(logEventType, "Level");

// RenderedMessage is get only
var getRenderedMessageFunc = _getRenderedMessage ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<string>(logEvent.GetType(), "RenderedMessage");
var getRenderedMessageFunc = _getRenderedMessage ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<string>(logEventType, "RenderedMessage");

// Older versions of log4net only allow access to a timestamp in local time
var getTimestampFunc = _getTimestamp ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<DateTime>(logEvent.GetType(), "TimeStamp");
var getTimestampFunc = _getTimestamp ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<DateTime>(logEventType, "TimeStamp");

// This will either add the log message to the transaction or directly to the aggregator
var xapi = agent.GetExperimentalApi();
xapi.RecordLogMessage(WrapperName, logEvent, getTimestampFunc, getLogLevelFunc, getRenderedMessageFunc, agent.TraceMetadata.SpanId, agent.TraceMetadata.TraceId);
}

private void DecorateLogMessage(object logEvent, IAgent agent)
private void DecorateLogMessage(object logEvent, Type logEventType, IAgent agent)
{
if (!agent.Configuration.LogDecoratorEnabled)
{
return;
}

var getProperties = _getProperties ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<IDictionary>(logEvent.GetType(), "Properties");
var getProperties = _getProperties ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<IDictionary>(logEventType, "Properties");
var propertiesDictionary = getProperties(logEvent);

if (propertiesDictionary == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins
LogProviders.RegisteredLogProvider[(int)LogProvider.Serilog] = true;
agent.Logger.Log(Level.Info, "Detected Serilog provider in use with Microsoft.Extensions.Logging, disabling Serilog instrumentation.");
}
else if (LogProviders.NLogProviderNames.Contains(provider))
{
LogProviders.RegisteredLogProvider[(int)LogProvider.NLog] = true;
agent.Logger.Log(Level.Info, "Detected NLog provider in use with Microsoft.Extensions.Logging, disabling NLog instrumentation.");
}

return Delegates.NoOp;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ private AfterWrappedMethodDelegate DecorateLogMessage(MEL.ILogger logger, IAgent
return Delegates.NoOp;
}

// NLog can alter the message so we want to skip MEL decoration for NLog
if (LogProviders.RegisteredLogProvider[(int)LogProvider.NLog])
{
return Delegates.NoOp;
}

// uses the foratted metadata to make a single entry
var formattedMetadata = LoggingHelpers.GetFormattedLinkingMetadata(agent);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2020 New Relic Corporation. All rights reserved.
SPDX-License-Identifier: Apache-2.0
-->
<extension xmlns="urn:newrelic-extension">
<instrumentation>

<!-- NLog v5, v4 -->
<tracerFactory name="nlog">
<match assemblyName="NLog" className="NLog.LoggerImpl">
<exactMethodMatcher methodName="Write" />
</match>
</tracerFactory>

</instrumentation>
</extension>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">

<PropertyGroup>
<TargetFrameworks>net45;netstandard2.0</TargetFrameworks>
<RootNamespace>NewRelic.Providers.Wrapper.NLogLogging</RootNamespace>
<AssemblyName>NewRelic.Providers.Wrapper.NLogLogging</AssemblyName>
<Description>NLog Logging Wrapper Provider for New Relic .NET Agent</Description>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net45'">
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Content Include="Instrumentation.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(RootProjectDirectory)\src\NewRelic.Core\NewRelic.Core.csproj" />
<ProjectReference Include="..\..\..\NewRelic.Agent.Extensions\NewRelic.Agent.Extensions.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System;
using NewRelic.Agent.Api;
using NewRelic.Agent.Api.Experimental;
using NewRelic.Agent.Extensions.Logging;
using NewRelic.Agent.Extensions.Providers.Wrapper;
using NewRelic.Reflection;

namespace NewRelic.Providers.Wrapper.NLogLogging
{
public class NLogWrapper : IWrapper
{
private static Func<object, object> _getLogLevel;
private static Func<object, string> _getRenderedMessage;
private static Func<object, DateTime> _getTimestamp;
private static Func<object, string> _messageGetter;

public bool IsTransactionRequired => false;

private const string WrapperName = "nlog";

public CanWrapResponse CanWrap(InstrumentedMethodInfo methodInfo)
{
// Since NLog can alter the messages directly, we need to move the MEL check
return new CanWrapResponse(WrapperName.Equals(methodInfo.RequestedWrapperName));
}

public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction)
{
var logEvent = instrumentedMethodCall.MethodCall.MethodArguments[2];
var logEventType = logEvent.GetType();

if (!LogProviders.RegisteredLogProvider[(int)LogProvider.NLog])
{
RecordLogMessage(logEvent, logEventType, agent);
}

// We want this to happen instead of MEL so no provider check here.
DecorateLogMessage(logEvent, logEventType, agent);

return Delegates.NoOp;
}

private void RecordLogMessage(object logEvent, Type logEventType, IAgent agent)
{
var getLogLevelFunc = _getLogLevel ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<object>(logEventType, "Level");

var getRenderedMessageFunc = _getRenderedMessage ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<string>(logEventType, "FormattedMessage");

var getTimestampFunc = _getTimestamp ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<DateTime>(logEventType, "TimeStamp");

// This will either add the log message to the transaction or directly to the aggregator
var xapi = agent.GetExperimentalApi();
xapi.RecordLogMessage(WrapperName, logEvent, getTimestampFunc, getLogLevelFunc, getRenderedMessageFunc, agent.TraceMetadata.SpanId, agent.TraceMetadata.TraceId);
}

private void DecorateLogMessage(object logEvent, Type logEventType, IAgent agent)
{
if (!agent.Configuration.LogDecoratorEnabled)
{
return;
}

var formattedMetadata = LoggingHelpers.GetFormattedLinkingMetadata(agent);

var messageGetter = _messageGetter ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<string>(logEventType, "Message");

// Message should not be null, but better to be sure
var originalMessage = messageGetter(logEvent);
if (string.IsNullOrWhiteSpace(originalMessage))
{
return;
}

// this cannot be made a static since it is unique to each logEvent
var messageSetter = VisibilityBypasser.Instance.GeneratePropertySetter<string>(logEvent, "Message");
messageSetter(messageGetter + " " + formattedMetadata);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public enum LoggingFramework
Log4net,
Serilog,
MicrosoftLogging,
SerilogWeb
SerilogWeb,
NLog
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public void NoLogDataIsSent()
}

#region log4net

[NetFrameworkTest]
public class Log4netHSMDisablesForwardingTestsFWLatestTests : HSMOrCSPDisablesForwardingTestsBase<ConsoleDynamicMethodFixtureFWLatestHSM>
{
Expand Down Expand Up @@ -104,9 +105,11 @@ public Log4netCSPDisablesForwardingTestsNetCoreLatestTests(ConsoleDynamicMethodF
{
}
}

#endregion

#region MicrosoftLogging

[NetCoreTest]
public class MicrosoftLoggingHSMDisablesForwardingTestsNetCoreLatestTests : HSMOrCSPDisablesForwardingTestsBase<ConsoleDynamicMethodFixtureCoreLatestHSM>
{
Expand All @@ -123,9 +126,11 @@ public MicrosoftLoggingCSPDisablesForwardingTestsNetCoreLatestTests(ConsoleDynam
{
}
}

#endregion

#region Serilog

[NetFrameworkTest]
public class SerilogHSMDisablesForwardingTestsFWLatestTests : HSMOrCSPDisablesForwardingTestsBase<ConsoleDynamicMethodFixtureFWLatestHSM>
{
Expand Down Expand Up @@ -160,5 +165,45 @@ public SerilogCSPDisablesForwardingTestsNetCoreLatestTests(ConsoleDynamicMethodF
{
}
}

#endregion

#region NLog

[NetFrameworkTest]
public class NLogHSMDisablesForwardingTestsFWLatestTests : HSMOrCSPDisablesForwardingTestsBase<ConsoleDynamicMethodFixtureFWLatestHSM>
{
public NLogHSMDisablesForwardingTestsFWLatestTests(ConsoleDynamicMethodFixtureFWLatestHSM fixture, ITestOutputHelper output)
: base(fixture, output, LoggingFramework.NLog)
{
}
}

[NetFrameworkTest]
public class NLogCSPDisablesForwardingTestsFWLatestTests : HSMOrCSPDisablesForwardingTestsBase<ConsoleDynamicMethodFixtureFWLatestCSP>
{
public NLogCSPDisablesForwardingTestsFWLatestTests(ConsoleDynamicMethodFixtureFWLatestCSP fixture, ITestOutputHelper output)
: base(fixture, output, LoggingFramework.NLog)
{
}
}

[NetCoreTest]
public class NLogHSMDisablesForwardingTestsNetCoreLatestTests : HSMOrCSPDisablesForwardingTestsBase<ConsoleDynamicMethodFixtureCoreLatestHSM>
{
public NLogHSMDisablesForwardingTestsNetCoreLatestTests(ConsoleDynamicMethodFixtureCoreLatestHSM fixture, ITestOutputHelper output)
: base(fixture, output, LoggingFramework.NLog)
{
}
}
[NetCoreTest]
public class NLogCSPDisablesForwardingTestsNetCoreLatestTests : HSMOrCSPDisablesForwardingTestsBase<ConsoleDynamicMethodFixtureCoreLatestCSP>
{
public NLogCSPDisablesForwardingTestsNetCoreLatestTests(ConsoleDynamicMethodFixtureCoreLatestCSP fixture, ITestOutputHelper output)
: base(fixture, output, LoggingFramework.NLog)
{
}
}

#endregion
}
Loading