Skip to content

Commit

Permalink
Add auto-instrumentation for MassTransit v7 and v8.
Browse files Browse the repository at this point in the history
  • Loading branch information
jaffinito authored and nr-ahemsath committed Oct 9, 2023
1 parent c0e06e9 commit 2a1060a
Show file tree
Hide file tree
Showing 14 changed files with 579 additions and 1 deletion.
14 changes: 14 additions & 0 deletions FullAgent.sln
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StackExchangeRedis2Plus", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Elasticsearch", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\Elasticsearch\Elasticsearch.csproj", "{D9428449-3E4B-4723-A8AA-1191315C7AAD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MassTransit", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\MassTransit\MassTransit.csproj", "{0DC126D1-E782-4A41-BA3E-393083F08627}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MassTransitLegacy", "src\Agent\NewRelic\Agent\Extensions\Providers\Wrapper\MassTransitLegacy\MassTransitLegacy.csproj", "{F921A316-A8D2-4992-B894-69A3B5CA34E3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -438,6 +442,14 @@ Global
{D9428449-3E4B-4723-A8AA-1191315C7AAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9428449-3E4B-4723-A8AA-1191315C7AAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D9428449-3E4B-4723-A8AA-1191315C7AAD}.Release|Any CPU.Build.0 = Release|Any CPU
{0DC126D1-E782-4A41-BA3E-393083F08627}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0DC126D1-E782-4A41-BA3E-393083F08627}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0DC126D1-E782-4A41-BA3E-393083F08627}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0DC126D1-E782-4A41-BA3E-393083F08627}.Release|Any CPU.Build.0 = Release|Any CPU
{F921A316-A8D2-4992-B894-69A3B5CA34E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F921A316-A8D2-4992-B894-69A3B5CA34E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F921A316-A8D2-4992-B894-69A3B5CA34E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F921A316-A8D2-4992-B894-69A3B5CA34E3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -507,6 +519,8 @@ Global
{3D69B4C9-FD16-461F-95AF-6FCA6EAA914E} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A}
{EC34F023-223D-432F-9401-9C3ED1B75DE4} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A}
{D9428449-3E4B-4723-A8AA-1191315C7AAD} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A}
{0DC126D1-E782-4A41-BA3E-393083F08627} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A}
{F921A316-A8D2-4992-B894-69A3B5CA34E3} = {5E86E10A-C38F-48CB-ADE9-67B22BB2F50A}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.2\lib\NET35
Expand Down
7 changes: 6 additions & 1 deletion src/Agent/NewRelic/Agent/Core/Utilities/ExtensionsLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,12 @@ public static void Initialize(string installPathExtensionsDirectory)
//The NewRelic.Providers.Wrapper.SerilogLogging.dll depends on the Serilog.dll; therefore, it should
//only be loaded by the agent when Serilog is used otherwise assembly load exception will occur.
{ "SerilogCreateLoggerWrapper", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.SerilogLogging.dll") },
{ "SerilogDispatchWrapper", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.SerilogLogging.dll") }
{ "SerilogDispatchWrapper", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.SerilogLogging.dll") },

// Both NewRelic.Providers.Wrapper.MassTransit.dll and NewRelic.Providers.Wrapper.MassTransitLegacy.dll depend on MassTransit aseemblys;
// therefore, only be loaded by the agent when MassTransit is used otherwise assembly load exception will occur.
{ "TransportConfigWrapper", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.MassTransit.dll") },
{ "TransportConfigLegacyWrapper", Path.Combine(_installPathExtensionsDirectory, "NewRelic.Providers.Wrapper.MassTransitLegacy.dll") }
};

var nonAutoReflectedAssemblies = _dynamicLoadWrapperAssemblies.Values.Distinct().ToList();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?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>

<tracerFactory name="TransportConfigWrapper">

<match assemblyName="MassTransit" className="MassTransit.Configuration.TransportRegistrationBusFactory`1" minVersion="8.0.0">
<exactMethodMatcher methodName="CreateBus" />
</match>
</tracerFactory>

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

<PropertyGroup>
<TargetFrameworks>net462;netstandard2.0</TargetFrameworks>
<AssemblyName>NewRelic.Providers.Wrapper.MassTransit</AssemblyName>
<RootNamespace>NewRelic.Providers.Wrapper.MassTransit</RootNamespace>
<Description>MassTransit Wrapper Provider for New Relic .NET Agent</Description>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MassTransit.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
</ItemGroup>

<ItemGroup>
<Content Include="Instrumentation.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</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,47 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System;
using MassTransit;
using NewRelic.Agent.Api;
using NewRelic.Agent.Extensions.Providers.Wrapper;

namespace NewRelic.Providers.Wrapper.MassTransit
{
public class MassTransitHelpers
{
private static string[] GetQueueData(Uri sourceAddress)
{
// rabbitmq://localhost/NRHXPSQL3_MassTransitTest_bus_iyeoyyge44oc7yijbdp5i1opfd?temporary=true
var items = sourceAddress.AbsoluteUri.Split('_');
return items[items.Length - 1].Split('?');
}

public static string GetQueue(Uri sourceAddress)
{
var queueData = GetQueueData(sourceAddress);
return queueData[0];
}

public static MessageBrokerDestinationType GetBrokerDestinationType(Uri sourceAddress)
{
var queueData = GetQueueData(sourceAddress);
if (queueData.Length == 2 && queueData[1] == "temporary=true")
{
return MessageBrokerDestinationType.TempQueue;
}

return MessageBrokerDestinationType.Queue;
}

public static void InsertDistributedTraceHeaders(SendHeaders headers, ITransaction transaction)
{
var setHeaders = new Action<SendHeaders, string, string>((carrier, key, value) =>
{
carrier.Set(key, value);
});

transaction.InsertDistributedTraceHeaders(headers, setHeaders);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MassTransit;
using NewRelic.Agent.Extensions.Providers.Wrapper;
using MethodCall = NewRelic.Agent.Extensions.Providers.Wrapper.MethodCall;

namespace NewRelic.Providers.Wrapper.MassTransit
{
public class NewRelicFilter : IFilter<ConsumeContext>, IFilter<PublishContext>, IFilter<SendContext>
{
private const string SendMethodName = "Send";

private Method _consumeMethod;
private Method _publishMethod;
private Method _sendMethod;

private Agent.Api.IAgent _agent;

public NewRelicFilter(Agent.Api.IAgent agent)
{
_agent = agent;
}

public void Probe(ProbeContext context)
{
context.CreateFilterScope("newrelic-scope");
}

public async Task Send(ConsumeContext context, IPipe<ConsumeContext> next)
{
_ = _consumeMethod ??= new Method(context.GetType(), SendMethodName,
context.GetType().FullName + "," + next.GetType().FullName);

var mc = new MethodCall(_consumeMethod, context, default(string[]));

var destName = MassTransitHelpers.GetQueue(context.SourceAddress);

var transaction = _agent.CreateTransaction(
destinationType: MassTransitHelpers.GetBrokerDestinationType(context.SourceAddress),
brokerVendorName: "MassTransit",
destination: destName);

transaction.AttachToAsync();

transaction.AcceptDistributedTraceHeaders(context.Headers, GetHeaderValue, TransportType.AMQP);

var segment = transaction.StartMessageBrokerSegment(mc, MessageBrokerDestinationType.Queue, MessageBrokerAction.Consume, "MassTransit", destName);

await next.Send(context);
segment.End();
transaction.End();

IEnumerable<string> GetHeaderValue(Headers carrier, string key)
{
var headers = carrier.GetAll();
if (headers != null)
{
var headerValues = new List<string>();
foreach (var item in headers)
{
if (item.Key.Equals(key, StringComparison.OrdinalIgnoreCase))
{
headerValues.Add(item.Value.ToString());
}
}

return headerValues;
}

return null;
}
}

public async Task Send(PublishContext context, IPipe<PublishContext> next)
{
_ = _publishMethod ??= new Method(context.GetType(), SendMethodName,
context.GetType().FullName + "," + next.GetType().FullName);

var mc = new MethodCall(_publishMethod, context, default(string[]));

var destName = MassTransitHelpers.GetQueue(context.SourceAddress);
var destType = MassTransitHelpers.GetBrokerDestinationType(context.SourceAddress);

var transaction = _agent.CurrentTransaction;
MassTransitHelpers.InsertDistributedTraceHeaders(context.Headers, transaction);
var segment = transaction.StartMessageBrokerSegment(mc, destType, MessageBrokerAction.Produce, "MassTransit", destName);

await next.Send(context);
segment.End();
}

public async Task Send(SendContext context, IPipe<SendContext> next)
{
_ = _sendMethod ??= new Method(context.GetType(), SendMethodName,
context.GetType().FullName + "," + next.GetType().FullName);

var mc = new MethodCall(_sendMethod, context, default(string[]));

var destName = MassTransitHelpers.GetQueue(context.SourceAddress);
var destType = MassTransitHelpers.GetBrokerDestinationType(context.SourceAddress);

var transaction = _agent.CurrentTransaction;
MassTransitHelpers.InsertDistributedTraceHeaders(context.Headers, transaction);
var segment = transaction.StartMessageBrokerSegment(mc, destType, MessageBrokerAction.Produce, "MassTransit", destName);

await next.Send(context);
segment.End();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using MassTransit.Configuration;
using MassTransit;
using System.Collections.Generic;
using System.Linq;

namespace NewRelic.Providers.Wrapper.MassTransit
{
public class NewRelicPipeSpecification : IPipeSpecification<ConsumeContext>, IPipeSpecification<PublishContext>, IPipeSpecification<SendContext>
{
Agent.Api.IAgent _agent;

public NewRelicPipeSpecification(Agent.Api.IAgent agent)
{
_agent = agent;
}

public IEnumerable<ValidationResult> Validate()
{
return Enumerable.Empty<ValidationResult>();
}

public void Apply(IPipeBuilder<ConsumeContext> builder)
{
builder.AddFilter(new NewRelicFilter(_agent));
}

public void Apply(IPipeBuilder<PublishContext> builder)
{
builder.AddFilter(new NewRelicFilter(_agent));
}

public void Apply(IPipeBuilder<SendContext> builder)
{
builder.AddFilter(new NewRelicFilter(_agent));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using MassTransit;
using NewRelic.Agent.Api;
using NewRelic.Agent.Extensions.Providers.Wrapper;
using NewRelic.SystemExtensions;

namespace NewRelic.Providers.Wrapper.MassTransit
{
public class TransportConfigWrapper : IWrapper
{
private const string WrapperName = "TransportConfigWrapper";
public bool IsTransactionRequired => false;

public CanWrapResponse CanWrap(InstrumentedMethodInfo methodInfo)
{
return new CanWrapResponse(WrapperName.Equals(methodInfo.RequestedWrapperName));
}

public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, Agent.Api.IAgent agent, ITransaction transaction)
{
// This will be run for each bus. Each bus gets one transport.
// We can support more than on transport with this setup.
var configurator = instrumentedMethodCall.MethodCall.MethodArguments.ExtractNotNullAs<IBusFactoryConfigurator>(0);

var spec = new NewRelicPipeSpecification(agent);

configurator.ConfigurePublish(cfg => cfg.AddPipeSpecification(spec));
configurator.ConfigureSend(cfg => cfg.AddPipeSpecification(spec));
configurator.AddPipeSpecification(spec);

return Delegates.NoOp;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?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>

<tracerFactory name="TransportConfigLegacyWrapper">

<match assemblyName="MassTransit" className="MassTransit.Registration.TransportRegistrationBusFactory" maxVersion="7.99.0">
<exactMethodMatcher methodName="CreateBus" />
</match>
</tracerFactory>

</instrumentation>
</extension>
Loading

0 comments on commit 2a1060a

Please sign in to comment.