Skip to content

Commit 44a1de1

Browse files
committed
New Relic Logging sample for NServiceBus 10 #7325
1 parent a95c410 commit 44a1de1

File tree

9 files changed

+257
-0
lines changed

9 files changed

+257
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net10.0</TargetFramework>
4+
<OutputType>Exe</OutputType>
5+
<LangVersion>preview</LangVersion>
6+
</PropertyGroup>
7+
<ItemGroup>
8+
<PackageReference Include="NewRelic.Agent.Api" Version="10.*" />
9+
<PackageReference Include="NServiceBus" Version="10.0.0-alpha.1" />
10+
<PackageReference Include="NServiceBus.Metrics" Version="6.0.0-alpha.1" />
11+
<PackageReference Include="NServiceBus.Extensions.Hosting" Version="4.0.0-alpha.1" />
12+
</ItemGroup>
13+
</Project>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using NServiceBus;
5+
6+
// Simulates busy (almost no delay) / quiet time in a sine wave
7+
class LoadSimulator(IEndpointInstance endpointInstance, TimeSpan minimumDelay, TimeSpan idleDuration)
8+
{
9+
CancellationTokenSource tokenSource = new CancellationTokenSource();
10+
TimeSpan idleDuration = TimeSpan.FromTicks(idleDuration.Ticks / 2);
11+
Task fork;
12+
13+
public void Start()
14+
{
15+
fork = Task.Run(Loop, CancellationToken.None);
16+
}
17+
18+
async Task Loop()
19+
{
20+
try
21+
{
22+
while (!tokenSource.IsCancellationRequested)
23+
{
24+
await Work();
25+
var delay = NextDelay();
26+
await Task.Delay(delay, tokenSource.Token);
27+
}
28+
}
29+
catch (OperationCanceledException)
30+
{
31+
}
32+
}
33+
34+
int index;
35+
36+
TimeSpan NextDelay()
37+
{
38+
var angleInRadians = Math.PI / 180.0 * ++index;
39+
var delay = TimeSpan.FromMilliseconds(idleDuration.TotalMilliseconds * Math.Sin(angleInRadians));
40+
delay += idleDuration;
41+
delay += minimumDelay;
42+
return delay;
43+
}
44+
45+
Task Work()
46+
{
47+
return endpointInstance.SendLocal(new SomeCommand());
48+
}
49+
50+
public Task Stop()
51+
{
52+
tokenSource.Cancel();
53+
return fork;
54+
}
55+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
namespace Endpoint
2+
{
3+
using System;
4+
using System.Collections.Generic;
5+
using NewRelic.Api.Agent;
6+
using NServiceBus;
7+
using NServiceBus.Configuration.AdvancedExtensibility;
8+
9+
public class NewRelicMetrics
10+
{
11+
public static void Setup(EndpointConfiguration endpointConfiguration)
12+
{
13+
#region newrelic-name-mapping
14+
15+
var endpointName = endpointConfiguration.GetSettings().EndpointName();
16+
17+
var nameMapping = new Dictionary<string, string>
18+
{
19+
// https://docs.newrelic.com/docs/agents/manage-apm-agents/agent-data/collect-custom-metrics
20+
{"# of msgs successfully processed / sec", FormatMetric("Success_Total", endpointName)},
21+
{"# of msgs pulled from the input queue /sec", FormatMetric("Fetched_Total", endpointName)},
22+
{"# of msgs failures / sec", FormatMetric("Failure_Total", endpointName)},
23+
{"Critical Time", FormatMetric("CriticalTime_Seconds", endpointName)},
24+
{"Processing Time", FormatMetric("ProcessingTime_Seconds", endpointName)},
25+
};
26+
#endregion
27+
28+
#region newrelic-enable-nsb-metrics
29+
30+
var metricsOptions = endpointConfiguration.EnableMetrics();
31+
32+
#endregion
33+
34+
#region newrelic-register-probe
35+
36+
metricsOptions.RegisterObservers(
37+
register: probeContext =>
38+
{
39+
RegisterProbes(probeContext, endpointName, nameMapping);
40+
});
41+
42+
#endregion
43+
}
44+
45+
static void RegisterProbes(ProbeContext context, string endpointName, Dictionary<string, string> nameMapping)
46+
{
47+
#region newrelic-observers-registration
48+
49+
foreach (var duration in context.Durations)
50+
{
51+
duration.Register((ref DurationEvent @event) =>
52+
{
53+
nameMapping.TryGetValue(duration.Name, out var mappedName);
54+
var newRelicName = string.Format(mappedName ?? FormatMetric(duration.Name, endpointName), Normalize(@event.MessageType));
55+
NewRelic.RecordResponseTimeMetric(newRelicName, Convert.ToInt64(@event.Duration.TotalMilliseconds));
56+
});
57+
}
58+
59+
foreach (var signal in context.Signals)
60+
{
61+
signal.Register((ref SignalEvent @event) =>
62+
{
63+
nameMapping.TryGetValue(signal.Name, out var mappedName);
64+
var newRelicName = string.Format(mappedName ?? FormatMetric(signal.Name, endpointName), Normalize(@event.MessageType));
65+
NewRelic.RecordMetric(newRelicName, 1);
66+
});
67+
}
68+
69+
#endregion
70+
}
71+
72+
static string FormatMetric(string name, string prefix)
73+
{
74+
return Normalize($"Custom/NServiceBus/{prefix}/{{0}}/{name}");
75+
}
76+
77+
static string Normalize(string name)
78+
{
79+
var result = name.Split(SplitComma, StringSplitOptions.RemoveEmptyEntries);
80+
if (result.Length > 0)
81+
{
82+
name = result[0];
83+
}
84+
return name.Replace(" ", "_").Replace(".", "_").Replace("-", "_");
85+
}
86+
87+
static string[] SplitComma = new[] { "," };
88+
}
89+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System;
2+
using Endpoint;
3+
using NServiceBus;
4+
5+
var endpointConfiguration = new EndpointConfiguration(Console.Title = "TracingEndpoint");
6+
7+
endpointConfiguration.UseSerialization<SystemJsonSerializer>();
8+
endpointConfiguration.UseTransport<LearningTransport>();
9+
10+
NewRelicMetrics.Setup(endpointConfiguration);
11+
12+
var endpointInstance = await NServiceBus.Endpoint.Start(endpointConfiguration);
13+
14+
#region newrelic-load-simulator
15+
16+
var simulator = new LoadSimulator(endpointInstance, TimeSpan.Zero, TimeSpan.FromSeconds(10));
17+
simulator.Start();
18+
19+
#endregion
20+
21+
try
22+
{
23+
Console.WriteLine("Endpoint started.");
24+
Console.WriteLine("Press [ENTER] to send additional messages.");
25+
Console.WriteLine("Press [ESC] to quit.");
26+
27+
while (true)
28+
{
29+
switch (Console.ReadKey(true).Key)
30+
{
31+
case ConsoleKey.Escape:
32+
return;
33+
case ConsoleKey.Enter:
34+
await endpointInstance.SendLocal(new SomeCommand());
35+
break;
36+
}
37+
}
38+
}
39+
finally
40+
{
41+
await simulator.Stop();
42+
await endpointInstance.Stop();
43+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
using NServiceBus;
2+
3+
class SomeCommand :
4+
ICommand
5+
{
6+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using NServiceBus;
4+
using NServiceBus.Logging;
5+
6+
class SomeCommandHandler :
7+
IHandleMessages<SomeCommand>
8+
{
9+
static ILog log = LogManager.GetLogger<SomeCommandHandler>();
10+
static Random random = new Random();
11+
12+
public async Task Handle(SomeCommand message, IMessageHandlerContext context)
13+
{
14+
await Task.Delay(random.Next(50, 250), context.CancellationToken);
15+
16+
if (random.Next(10) <= 1)
17+
{
18+
throw new Exception("Random 10% chaos!");
19+
}
20+
21+
log.Info("Hello from SomeCommandHandler");
22+
}
23+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<configuration>
3+
<!-- startcode newrelic-appname -->
4+
<appSettings>
5+
<add key="NewRelic.AgentEnabled" value="true" />
6+
<add key="NewRelic.AppName" value="NewRelic_Metrics4" />
7+
</appSettings>
8+
<!-- endcode -->
9+
</configuration>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.29728.190
5+
MinimumVisualStudioVersion = 15.0.26730.12
6+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Endpoint", "Endpoint\Endpoint.csproj", "{FF5BEF96-88BA-470D-975A-4EF710EE2B7A}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
EndGlobalSection
12+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
13+
{FF5BEF96-88BA-470D-975A-4EF710EE2B7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
14+
{FF5BEF96-88BA-470D-975A-4EF710EE2B7A}.Debug|Any CPU.Build.0 = Debug|Any CPU
15+
EndGlobalSection
16+
GlobalSection(SolutionProperties) = preSolution
17+
HideSolutionNode = FALSE
18+
EndGlobalSection
19+
EndGlobal

samples/logging/new-relic/Metrics_6/prerelease.txt

Whitespace-only changes.

0 commit comments

Comments
 (0)