Skip to content

Commit 332529b

Browse files
authored
feat: Add support for using Sitecore.Logging and log4net together (#2537)
1 parent cfb2c28 commit 332529b

File tree

5 files changed

+242
-127
lines changed

5 files changed

+242
-127
lines changed

src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Log4NetLogging/Instrumentation.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ SPDX-License-Identifier: Apache-2.0
1111
</match>
1212
</tracerFactory>
1313
<!-- Sitecore uses an old fork of log4net with the same namespace -->
14-
<tracerFactory name="log4net">
14+
<tracerFactory name="SitecoreLogging">
1515
<match assemblyName="Sitecore.Logging" className="log4net.Repository.Hierarchy.Logger">
1616
<exactMethodMatcher methodName="CallAppenders" />
1717
</match>

src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/Log4NetLogging/Log4netWrapper.cs

+3-45
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2020 New Relic, Inc. All rights reserved.
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

44
using System;
@@ -22,11 +22,6 @@ public class Log4netWrapper : IWrapper
2222
private static Func<object, IDictionary> _getGetProperties; // calls GetProperties method
2323
private static Func<object, IDictionary> _getProperties; // getter for Properties property
2424

25-
private static Func<object, object> _getLegacyProperties; // getter for legacy Properties property
26-
private static Func<object, Hashtable> _getLegacyHashtable; // getter for Properties hashtable property
27-
28-
private static bool _legacyVersion = false;
29-
3025
public bool IsTransactionRequired => false;
3126

3227

@@ -59,17 +54,7 @@ private void RecordLogMessage(object logEvent, Type logEventType, IAgent agent)
5954
// Older versions of log4net only allow access to a timestamp in local time
6055
var getTimestampFunc = _getTimestamp ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<DateTime>(logEventType, "TimeStamp");
6156

62-
if (_getLogException == null)
63-
{
64-
if (!VisibilityBypasser.Instance.TryGeneratePropertyAccessor<Exception>(logEventType, "ExceptionObject", out _getLogException))
65-
{
66-
// Legacy property, mainly used by Sitecore
67-
if (!VisibilityBypasser.Instance.TryGeneratePropertyAccessor<Exception>(logEventType, "m_thrownException", out _getLogException))
68-
{
69-
_getLogException = (x) => null;
70-
}
71-
}
72-
}
57+
_getLogException ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<Exception>(logEventType, "ExceptionObject");
7358

7459
// This will either add the log message to the transaction or directly to the aggregator
7560
var xapi = agent.GetExperimentalApi();
@@ -102,36 +87,9 @@ private void DecorateLogMessage(object logEvent, Type logEventType, IAgent agent
10287
private Dictionary<string, object> GetContextData(object logEvent)
10388
{
10489
var logEventType = logEvent.GetType();
105-
106-
if (_getGetProperties == null && !VisibilityBypasser.Instance.TryGenerateParameterlessMethodCaller(logEventType.Assembly.ToString(), logEventType.FullName, "GetProperties", out _getGetProperties))
107-
{
108-
// Legacy property, mainly used by Sitecore
109-
if (VisibilityBypasser.Instance.TryGeneratePropertyAccessor(logEventType, "MappedContext", out _getGetProperties))
110-
_legacyVersion = true;
111-
else
112-
_getGetProperties = (x) => null;
113-
}
90+
_getGetProperties ??= VisibilityBypasser.Instance.GenerateParameterlessMethodCaller<IDictionary>(logEventType.Assembly.ToString(), logEventType.FullName, "GetProperties");
11491

11592
var contextData = new Dictionary<string, object>();
116-
// In older versions of log4net, there may be additional properties
117-
if (_legacyVersion)
118-
{
119-
// Properties is a "PropertiesCollection", an internal type
120-
var getLegacyProperties = _getLegacyProperties ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<object>(logEventType, "Properties");
121-
var legacyProperties = getLegacyProperties(logEvent);
122-
123-
// PropertyCollection has an internal hashtable that stores the data. The only public method for
124-
// retrieving the data is the indexer [] which is more of a pain to get via reflection.
125-
var propertyCollectionType = legacyProperties.GetType();
126-
var getHashtable = _getLegacyHashtable ??= VisibilityBypasser.Instance.GenerateFieldReadAccessor<Hashtable>(propertyCollectionType.Assembly.ToString(), propertyCollectionType.FullName, "m_ht");
127-
128-
var hashtable = getHashtable(legacyProperties);
129-
130-
foreach (var key in hashtable.Keys)
131-
{
132-
contextData.Add(key.ToString(), hashtable[key]);
133-
}
134-
}
13593

13694
var propertiesDictionary = _getGetProperties(logEvent);
13795

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Copyright 2020 New Relic, Inc. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System;
5+
using System.Collections;
6+
using System.Collections.Generic;
7+
using System.Linq;
8+
using NewRelic.Agent.Api;
9+
using NewRelic.Agent.Api.Experimental;
10+
using NewRelic.Agent.Extensions.Logging;
11+
using NewRelic.Agent.Extensions.Providers.Wrapper;
12+
using NewRelic.Reflection;
13+
14+
namespace NewRelic.Providers.Wrapper.Logging
15+
{
16+
public class SitecoreLoggingWrapper : IWrapper
17+
{
18+
private static Func<object, object> _getLevel;
19+
private static Func<object, string> _getRenderedMessage;
20+
private static Func<object, DateTime> _getTimestamp;
21+
private static Func<object, Exception> _getLogException;
22+
private static Func<object, IDictionary> _getGetProperties; // calls GetProperties method
23+
private static Func<object, IDictionary> _getProperties; // getter for Properties property
24+
25+
private static Func<object, object> _getLegacyProperties; // getter for legacy Properties property
26+
private static Func<object, Hashtable> _getLegacyHashtable; // getter for Properties hashtable property
27+
28+
public bool IsTransactionRequired => false;
29+
30+
31+
private const string WrapperName = "SitecoreLogging";
32+
33+
public CanWrapResponse CanWrap(InstrumentedMethodInfo methodInfo)
34+
{
35+
return new CanWrapResponse(WrapperName.Equals(methodInfo.RequestedWrapperName));
36+
}
37+
38+
public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction)
39+
{
40+
var logEvent = instrumentedMethodCall.MethodCall.MethodArguments[0];
41+
var logEventType = logEvent.GetType();
42+
43+
RecordLogMessage(logEvent, logEventType, agent);
44+
45+
DecorateLogMessage(logEvent, logEventType, agent);
46+
47+
return Delegates.NoOp;
48+
}
49+
50+
private void RecordLogMessage(object logEvent, Type logEventType, IAgent agent)
51+
{
52+
var getLevelFunc = _getLevel ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<object>(logEventType, "Level");
53+
54+
// RenderedMessage is get only
55+
var getRenderedMessageFunc = _getRenderedMessage ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<string>(logEventType, "RenderedMessage");
56+
57+
// Older versions of log4net only allow access to a timestamp in local time
58+
var getTimestampFunc = _getTimestamp ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<DateTime>(logEventType, "TimeStamp");
59+
60+
_getLogException ??= VisibilityBypasser.Instance.GenerateFieldReadAccessor<Exception>(logEventType, "m_thrownException");
61+
62+
// This will either add the log message to the transaction or directly to the aggregator
63+
var xapi = agent.GetExperimentalApi();
64+
65+
xapi.RecordLogMessage(WrapperName, logEvent, getTimestampFunc, getLevelFunc, getRenderedMessageFunc, _getLogException, GetContextData, agent.TraceMetadata.SpanId, agent.TraceMetadata.TraceId);
66+
}
67+
68+
private void DecorateLogMessage(object logEvent, Type logEventType, IAgent agent)
69+
{
70+
if (!agent.Configuration.LogDecoratorEnabled)
71+
{
72+
return;
73+
}
74+
75+
var getProperties = _getProperties ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<IDictionary>(logEventType, "Properties");
76+
var propertiesDictionary = getProperties(logEvent);
77+
78+
if (propertiesDictionary == null)
79+
{
80+
return;
81+
}
82+
83+
// uses the foratted metadata to make a single entry
84+
var formattedMetadata = LoggingHelpers.GetFormattedLinkingMetadata(agent);
85+
86+
// uses underscores to support other frameworks that do not allow hyphens (Serilog)
87+
propertiesDictionary["NR_LINKING"] = formattedMetadata;
88+
}
89+
90+
private Dictionary<string, object> GetContextData(object logEvent)
91+
{
92+
var logEventType = logEvent.GetType();
93+
_getGetProperties ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<IDictionary>(logEventType, "MappedContext");
94+
95+
var contextData = new Dictionary<string, object>();
96+
// In older versions of log4net, there may be additional properties
97+
98+
// Properties is a "PropertiesCollection", an internal type
99+
var getLegacyProperties = _getLegacyProperties ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<object>(logEventType, "Properties");
100+
var legacyProperties = getLegacyProperties(logEvent);
101+
102+
// PropertyCollection has an internal hashtable that stores the data. The only public method for
103+
// retrieving the data is the indexer [] which is more of a pain to get via reflection.
104+
var propertyCollectionType = legacyProperties.GetType();
105+
var getHashtable = _getLegacyHashtable ??= VisibilityBypasser.Instance.GenerateFieldReadAccessor<Hashtable>(propertyCollectionType.Assembly.ToString(), propertyCollectionType.FullName, "m_ht");
106+
107+
var hashtable = getHashtable(legacyProperties);
108+
109+
foreach (var key in hashtable.Keys)
110+
{
111+
contextData.Add(key.ToString(), hashtable[key]);
112+
}
113+
114+
var propertiesDictionary = _getGetProperties(logEvent);
115+
116+
if (propertiesDictionary != null && propertiesDictionary.Count > 0)
117+
{
118+
foreach (var key in propertiesDictionary.Keys)
119+
{
120+
contextData.Add(key.ToString(), propertiesDictionary[key]);
121+
}
122+
}
123+
124+
return contextData.Any() ? contextData : null;
125+
}
126+
}
127+
}

0 commit comments

Comments
 (0)