Skip to content

Commit

Permalink
feat: Add support for Sitecore.Logging. (#1790) (#1795)
Browse files Browse the repository at this point in the history
  • Loading branch information
chynesNR committed Jul 27, 2023
1 parent c89454a commit 6d1934a
Show file tree
Hide file tree
Showing 10 changed files with 293 additions and 7 deletions.
Expand Up @@ -10,5 +10,11 @@ SPDX-License-Identifier: Apache-2.0
<exactMethodMatcher methodName="CallAppenders" />
</match>
</tracerFactory>
<!-- Sitecore uses an old fork of log4net with the same namespace -->
<tracerFactory name="log4net">
<match assemblyName="Sitecore.Logging" className="log4net.Repository.Hierarchy.Logger">
<exactMethodMatcher methodName="CallAppenders" />
</match>
</tracerFactory>
</instrumentation>
</extension>
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NewRelic.Agent.Api;
using NewRelic.Agent.Api.Experimental;
using NewRelic.Agent.Extensions.Logging;
Expand All @@ -21,8 +22,14 @@ public class Log4netWrapper : IWrapper
private static Func<object, IDictionary> _getGetProperties; // calls GetProperties method
private static Func<object, IDictionary> _getProperties; // getter for Properties property

private static Func<object, object> _getLegacyProperties; // getter for legacy Properties property
private static Func<object, Hashtable> _getLegacyHashtable; // getter for Properties hashtable property

private static bool _legacyVersion = false;

public bool IsTransactionRequired => false;


private const string WrapperName = "log4net";

public CanWrapResponse CanWrap(InstrumentedMethodInfo methodInfo)
Expand Down Expand Up @@ -57,7 +64,27 @@ private void RecordLogMessage(object logEvent, Type logEventType, IAgent agent)
// Older versions of log4net only allow access to a timestamp in local time
var getTimestampFunc = _getTimestamp ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<DateTime>(logEventType, "TimeStamp");

var getLogExceptionFunc = _getLogException ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<Exception>(logEventType, "ExceptionObject");
Func<object, Exception> getLogExceptionFunc;

try
{
getLogExceptionFunc = _getLogException ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<Exception>(logEventType, "ExceptionObject");
}
catch
{
try
{
// Legacy property, mainly used by Sitecore
getLogExceptionFunc = _getLogException ??= VisibilityBypasser.Instance.GenerateFieldReadAccessor<Exception>(logEventType, "m_thrownException");
_legacyVersion = true;
}
catch
{
_getLogException = (x) => null;
getLogExceptionFunc = _getLogException;
}

}

// This will either add the log message to the transaction or directly to the aggregator
var xapi = agent.GetExperimentalApi();
Expand Down Expand Up @@ -90,21 +117,59 @@ private void DecorateLogMessage(object logEvent, Type logEventType, IAgent agent
private Dictionary<string, object> GetContextData(object logEvent)
{
var logEventType = logEvent.GetType();
var getProperties = _getGetProperties ??= VisibilityBypasser.Instance.GenerateParameterlessMethodCaller<IDictionary>(logEventType.Assembly.ToString(), logEventType.FullName, "GetProperties");
Func<object, IDictionary> getProperties;

try
{
getProperties = _getGetProperties ??= VisibilityBypasser.Instance.GenerateParameterlessMethodCaller<IDictionary>(logEventType.Assembly.ToString(), logEventType.FullName, "GetProperties");
}
catch
{
try
{
_legacyVersion = true;
// Legacy property, mainly used by Sitecore
getProperties = _getProperties ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<IDictionary>(logEventType, "MappedContext");
}
catch
{
_getProperties = (x) => null;
getProperties = _getProperties;
}
}

var contextData = new Dictionary<string, object>();
// In older versions of log4net, there may be additional properties
if (_legacyVersion)
{
// Properties is a "PropertiesCollection", an internal type
var getLegacyProperties = _getLegacyProperties ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<object>(logEventType, "Properties");
var legacyProperties = getLegacyProperties(logEvent);

// PropertyCollection has an internal hashtable that stores the data. The only public method for
// retrieving the data is the indexer [] which is more of a pain to get via reflection.
var propertyCollectionType = legacyProperties.GetType();
var getHashtable = _getLegacyHashtable ??= VisibilityBypasser.Instance.GenerateFieldReadAccessor<Hashtable>(propertyCollectionType.Assembly.ToString(), propertyCollectionType.FullName, "m_ht");

var hashtable = getHashtable(legacyProperties);

foreach (var key in hashtable.Keys)
{
contextData.Add(key.ToString(), hashtable[key]);
}
}

var propertiesDictionary = getProperties(logEvent);

if (propertiesDictionary != null && propertiesDictionary.Count > 0)
{
var contextData = new Dictionary<string, object>();
foreach (var key in propertiesDictionary.Keys)
{
contextData.Add(key.ToString(), propertiesDictionary[key]);
}

return contextData;
}

return null;
return contextData.Any() ? contextData : null;
}
}
}
Expand Up @@ -10,7 +10,8 @@ public enum LoggingFramework
MicrosoftLogging,
SerilogWeb,
NLog,
DummyMEL
DummyMEL,
Sitecore
}

public class LogUtils
Expand All @@ -30,6 +31,8 @@ public static string GetFrameworkName(LoggingFramework loggingFramework)
return "nlog";
case LoggingFramework.DummyMEL:
return "DummyMEL";
case LoggingFramework.Sitecore:
return "sitecore";
default:
return "unknown";
}
Expand All @@ -41,6 +44,7 @@ public static string GetLevelName(LoggingFramework loggingFramework, string leve
{
// log4net names are the same as our internal names
case LoggingFramework.Log4net:
case LoggingFramework.Sitecore:
switch (level)
{
case "NOMESSAGE":
Expand Down
Expand Up @@ -260,4 +260,23 @@ public MELContextDataNetCoreOldestTests(ConsoleDynamicMethodFixtureCoreOldest fi
}

#endregion

#region Sitecore
public class SitecoreContextDataFWLatestTests : ContextDataTestsBase<ConsoleDynamicMethodFixtureFWLatest>
{
public SitecoreContextDataFWLatestTests(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output)
: base(fixture, output, LoggingFramework.Sitecore)
{
}
}

public class SitecoreContextDataFW48Tests : ContextDataTestsBase<ConsoleDynamicMethodFixtureFW48>
{
public SitecoreContextDataFW48Tests(ConsoleDynamicMethodFixtureFW48 fixture, ITestOutputHelper output)
: base(fixture, output, LoggingFramework.Sitecore)
{
}
}

#endregion // Sitecore
}
Expand Up @@ -214,4 +214,27 @@ public NLogCSPDisablesForwardingTestsNetCoreLatestTests(ConsoleDynamicMethodFixt
}

#endregion

#region Sitecore

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

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

#endregion

}
Expand Up @@ -253,4 +253,25 @@ public NLogLogLevelTestsNetCoreOldestTests(ConsoleDynamicMethodFixtureCoreOldest

#endregion

#region Sitecore

[NetFrameworkTest]
public class SitecoreLogLevelTestsFWLatestTests : LogLevelTestsBase<ConsoleDynamicMethodFixtureFWLatest>
{
public SitecoreLogLevelTestsFWLatestTests(ConsoleDynamicMethodFixtureFWLatest fixture, ITestOutputHelper output)
: base(fixture, output, LoggingFramework.Sitecore)
{
}
}

[NetFrameworkTest]
public class SitecoreLogLevelTestsFW480Tests : LogLevelTestsBase<ConsoleDynamicMethodFixtureFW48>
{
public SitecoreLogLevelTestsFW480Tests(ConsoleDynamicMethodFixtureFW48 fixture, ITestOutputHelper output)
: base(fixture, output, LoggingFramework.Sitecore)
{
}
}

#endregion
}
Expand Up @@ -145,6 +145,10 @@
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" Condition="'$(TargetFramework)' == 'net481'" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" Condition="'$(TargetFramework)' == 'net481'" />

<!-- There is only a Framework 4.8 version of Sitecore.Logging -->
<PackageReference Include="Sitecore.Logging" Version="10.3.0" Condition="'$(TargetFramework)' == 'net48'" Aliases="Sitecore" />
<PackageReference Include="Sitecore.Logging" Version="10.0.0" Condition="'$(TargetFramework)' == 'net481'" Aliases="Sitecore" />

<!-- Serilog .NET core references -->
<!-- Can't go any earlier than 2.5.0 in .NET core due to minimum version required by
Serilog.Extensions.Hosting dependency of Microsoft.Extensions.Logging -->
Expand Down
Expand Up @@ -45,6 +45,11 @@ public static void SetFramework(string loggingFramework)
case "NLOG":
_log = new NLogLoggingAdapter();
break;
case "SITECORE":
#if NET48_OR_GREATER
_log = new SitecoreLoggingAdapter();
#endif
break;
default:
throw new System.ArgumentNullException(nameof(loggingFramework));
}
Expand Down
@@ -0,0 +1,126 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

#if NET48_OR_GREATER

extern alias Sitecore;

using System;
using System.Collections.Generic;
using Sitecore.log4net;
using Sitecore.log4net.Appender;
using Sitecore.log4net.Config;
using Sitecore.log4net.Layout;
using Sitecore.log4net.spi;

namespace MultiFunctionApplicationHelpers.NetStandardLibraries.LogInstrumentation
{
class SitecoreLoggingAdapter : ILoggingAdapter
{
private static readonly ILog _log = LogManager.GetLogger(typeof(LoggingTester));

public SitecoreLoggingAdapter()
{
}

public void Debug(string message)
{
_log.Debug(message);
}

public void Info(string message)
{
_log.Info(message);
}

public void Info(string message, Dictionary<string, object> context)
{
var logEventData = new LoggingEventData()
{
Message = message,
Level = Level.INFO
};

var logEvent = new LoggingEvent(logEventData);
if (context.Count > 0)
{
// get keys as a list to allow assigning proprties directly
var keys = new List<string>(context.Keys);

// Direct properties method

logEvent.Properties[keys[0]] = context[keys[0]];
logEvent.Properties[keys[1]] = context[keys[1]];

MDC.Set(keys[2], context[keys[2]].ToString());
MDC.Set(keys[3], context[keys[3]].ToString());
}
_log.Logger.Log(logEvent);
}

public void InfoWithParam(string message, object param)
{
// TODO: Not sure what the equivalent would be (or if there is one)
//_log.InfoFormat(message, param);
throw new System.NotImplementedException();
}

public void Warn(string message)
{
_log.Warn(message);
}

public void Error(Exception exception)
{
_log.Error(exception.Message, exception);
}

public void ErrorNoMessage(Exception exception)
{
_log.Error(string.Empty, exception);
}

public void Fatal(string message)
{
_log.Fatal(message);
}

public void Configure()
{
BasicConfigurator.Configure(LogManager.GetLoggerRepository());
}

public void ConfigureWithInfoLevelEnabled()
{
Configure();
((Sitecore.log4net.Repository.Hierarchy.Hierarchy)LogManager.GetLoggerRepository()).Root.Level = Level.INFO;
// TODO: Do we need to signal that the config has changed?
}

public void ConfigurePatternLayoutAppenderForDecoration()
{
PatternLayout patternLayout = new PatternLayout();
patternLayout.ConversionPattern = "%timestamp [%thread] %level %logger %ndc - %message %property{NR_LINKING}%newline";
patternLayout.ActivateOptions();

ConsoleAppender consoleAppender = new ConsoleAppender();
consoleAppender.Layout = patternLayout;
consoleAppender.ActivateOptions();

BasicConfigurator.Configure(LogManager.GetLoggerRepository(), consoleAppender);
}

public void ConfigureJsonLayoutAppenderForDecoration()
{
// TODO: Not supported?
throw new System.NotImplementedException();
}

public void NoMessage()
{
throw new NotImplementedException();
}
}
}

#endif
13 changes: 13 additions & 0 deletions tests/Agent/IntegrationTests/nuget.config
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<!-- For testing purposes, we want to pull in just the Sitecore logging package -->
<add key="Sitecore-NuGet" value="https://nuget.sitecore.com/resources/v3/index.json"/>
</packageSources>
<packageSourceMapping>
<!-- key value for <packageSource> should match key values from <packageSources> element -->
<packageSource key="Sitecore-NuGet">
<package pattern="Sitecore.*" />
</packageSource>
</packageSourceMapping>
</configuration>

0 comments on commit 6d1934a

Please sign in to comment.