Skip to content

Commit

Permalink
Add additional logs injection fallback for NLog 1.x (#1614)
Browse files Browse the repository at this point in the history
This work follows from pull request #1607

The previous PR fixes NLog logs injection to work correctly when applications use NLog versions >= 2.0. This PR provides an additional fix for NLog logs injection to work correctly when applications use NLog versions >= 1.0. This is accomplished by adding a new NLog log provider that extends the built-in LibLog NLogLogProvider by also searching for the `NLog.MDC` type for the `Set`/`Remove` APIs which exists in NLog 1.x, in addition to `NLog.MappedDiagnosticsContext` which only exists in NLog 2.x and later.
  • Loading branch information
zacharycmontoya authored Jul 28, 2021
1 parent 9dd2b7d commit bbedc0c
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 10 deletions.
16 changes: 15 additions & 1 deletion src/Datadog.Trace/Logging/CustomNLogLogProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@

namespace Datadog.Trace.Logging
{
/// <summary>
/// <para>
/// Log provider that performs more efficient logs injection by adding a custom type
/// into the NLog MDC which can later be rendered with the properties of the active
/// Datadog scope.
/// </para>
///
/// <para>
/// Note: This logger is intended to be used when the application uses NLog &gt;= 4.1.
/// When the application uses NLog versions older than 4.1, use
/// <see cref="FallbackNLogLogProvider"/> which utilizes the original
/// Set(string, string) API to perform logs injection.
/// </para>
/// </summary>
internal class CustomNLogLogProvider : NLogLogProvider, ILogProviderWithEnricher
{
public ILogEnricher CreateEnricher() => new LogEnricher(this);
Expand Down Expand Up @@ -68,7 +82,7 @@ protected override OpenMdc GetOpenMdcMethod()
private static bool IsSetObjectAvailable()
{
var mdcContextType = FindType("NLog.MappedDiagnosticsContext", "NLog");
return mdcContextType.GetMethod("Set", typeof(string), typeof(object)) != null;
return mdcContextType?.GetMethod("Set", typeof(string), typeof(object)) != null;
}
}
}
82 changes: 82 additions & 0 deletions src/Datadog.Trace/Logging/FallbackNLogLogProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// <copyright file="FallbackNLogLogProvider.cs" company="Datadog">
// Unless explicitly stated otherwise all files in this repository are licensed under the Apache 2 License.
// This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc.
// </copyright>

using System;
using System.Linq.Expressions;
using Datadog.Trace.Logging.LogProviders;

namespace Datadog.Trace.Logging
{
/// <summary>
/// <para>
/// Log provider that enhances the built-in LibLog NLogLogProvider by adding
/// MDC support for NLog 1.0. The built-in NLogLogProvider only looked for
/// API's present on NLog 2.0 and newer.
/// </para>
///
/// <para>
/// Note: This logger is intended to be used when the application uses NLog &lt; 4.1.
/// When the application uses NLog versions 4.1 and newer, use
/// <see cref="CustomNLogLogProvider"/> which utilizes the Set(string, object)
/// API to perform logs injection more efficiently.
/// </para>
/// </summary>
internal class FallbackNLogLogProvider : NLogLogProvider
{
protected override OpenMdc GetOpenMdcMethod()
{
// This is a copy/paste of the base GetOpenMdcMethod, with an additional NLog 1.x fallback
var keyParam = Expression.Parameter(typeof(string), "key");

var ndlcContextType = FindType("NLog.NestedDiagnosticsLogicalContext", "NLog");
if (ndlcContextType != null)
{
var pushObjectMethod = ndlcContextType.GetMethod("PushObject", typeof(object));
if (pushObjectMethod != null)
{
// NLog 4.6 introduces SetScoped with correct handling of logical callcontext (MDLC)
var mdlcContextType = FindType("NLog.MappedDiagnosticsLogicalContext", "NLog");
if (mdlcContextType != null)
{
var setScopedMethod = mdlcContextType.GetMethod("SetScoped", typeof(string), typeof(object));
if (setScopedMethod != null)
{
var valueObjParam = Expression.Parameter(typeof(object), "value");
var setScopedMethodCall = Expression.Call(null, setScopedMethod, keyParam, valueObjParam);
var setMethodLambda = Expression.Lambda<Func<string, object, IDisposable>>(setScopedMethodCall, keyParam, valueObjParam).Compile();
return (key, value, _) => setMethodLambda(key, value);
}
}
}
}

var mdcContextType = FindType("NLog.MappedDiagnosticsContext", "NLog");
if (mdcContextType is null)
{
// Modification: Add fallback for NLog version 1.x
mdcContextType = FindType("NLog.MDC", "NLog");
}

var setMethod = mdcContextType.GetMethod("Set", typeof(string), typeof(string));
var removeMethod = mdcContextType.GetMethod("Remove", typeof(string));
var valueParam = Expression.Parameter(typeof(string), "value");
var setMethodCall = Expression.Call(null, setMethod, keyParam, valueParam);
var removeMethodCall = Expression.Call(null, removeMethod, keyParam);

var set = Expression
.Lambda<Action<string, string>>(setMethodCall, keyParam, valueParam)
.Compile();
var remove = Expression
.Lambda<Action<string>>(removeMethodCall, keyParam)
.Compile();

return (key, value, _) =>
{
set(key, value.ToString());
return new DisposableAction(() => remove(key));
};
}
}
}
24 changes: 15 additions & 9 deletions src/Datadog.Trace/Logging/LibLogScopeEventSubscriber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,26 +264,32 @@ private static void InitResolvers()
// - NLog
// - Log4net

// Register the custom log4net provider
// Register the custom Serilog provider
LogProvider.LogProviderResolvers.Insert(
0,
Tuple.Create<LogProvider.IsLoggerAvailable, LogProvider.CreateLogProvider>(
CustomLog4NetLogProvider.IsLoggerAvailable,
() => new CustomLog4NetLogProvider()));
CustomSerilogLogProvider.IsLoggerAvailable,
() => new CustomSerilogLogProvider()));

// Register the custom NLog provider
// Register the custom NLog providers
LogProvider.LogProviderResolvers.Insert(
0,
1,
Tuple.Create<LogProvider.IsLoggerAvailable, LogProvider.CreateLogProvider>(
CustomNLogLogProvider.IsLoggerAvailable,
() => new CustomNLogLogProvider()));

// Register the custom Serilog provider
LogProvider.LogProviderResolvers.Insert(
0,
2,
Tuple.Create<LogProvider.IsLoggerAvailable, LogProvider.CreateLogProvider>(
CustomSerilogLogProvider.IsLoggerAvailable,
() => new CustomSerilogLogProvider()));
FallbackNLogLogProvider.IsLoggerAvailable,
() => new FallbackNLogLogProvider()));

// Register the custom log4net provider
LogProvider.LogProviderResolvers.Insert(
3,
Tuple.Create<LogProvider.IsLoggerAvailable, LogProvider.CreateLogProvider>(
CustomLog4NetLogProvider.IsLoggerAvailable,
() => new CustomLog4NetLogProvider()));
}

private void SetDefaultValues()
Expand Down

0 comments on commit bbedc0c

Please sign in to comment.